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,260 @@
1
+ """Pool-level commands for managing agent pools and configurations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from agentpool.agents.base_agent import BaseAgent
8
+ from agentpool_commands.base import NodeCommand
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from slashed import CommandContext
13
+
14
+ from agentpool.messaging.context import NodeContext
15
+
16
+
17
+ class ListPoolsCommand(NodeCommand):
18
+ """List available agent pool configurations.
19
+
20
+ Examples:
21
+ /list-pools
22
+ """
23
+
24
+ name = "list-pools"
25
+ category = "pool"
26
+
27
+ async def execute_command(self, ctx: CommandContext[NodeContext[Any]]) -> None:
28
+ """List available pool configurations.
29
+
30
+ Args:
31
+ ctx: Command context with node context
32
+ """
33
+ from agentpool_cli import agent_store
34
+
35
+ try:
36
+ output_lines = ["## 🏊 Agent Pool Configurations\n"]
37
+
38
+ # Show current pool info
39
+ output_lines.append("### 📍 Current Pool")
40
+
41
+ # Get config path from context config (works for all agent types)
42
+ current_config = ctx.context.config.config_file_path if ctx.context.config else None
43
+ if current_config:
44
+ output_lines.append(f"**Config:** `{current_config}`")
45
+ else:
46
+ output_lines.append("**Config:** *(default/built-in)*")
47
+
48
+ # Show agents in current pool
49
+ pool = ctx.context.pool
50
+ if pool:
51
+ agent_names = list(pool.all_agents.keys())
52
+ output_lines.append(f"**Agents:** {', '.join(f'`{n}`' for n in agent_names)}")
53
+ output_lines.append(f"**Active agent:** `{ctx.context.node.name}`")
54
+ else:
55
+ output_lines.append("**No pool available**")
56
+ output_lines.append("")
57
+
58
+ # Show stored configurations
59
+ output_lines.append("### 💾 Stored Configurations")
60
+ stored_configs = agent_store.list_configs()
61
+ active_config = agent_store.get_active()
62
+
63
+ if not stored_configs:
64
+ output_lines.append("*No stored configurations*")
65
+ output_lines.append("")
66
+ output_lines.append("Use `agentpool add <name> <path>` to add configurations.")
67
+ else:
68
+ # Build markdown table
69
+ output_lines.append("| Name | Path |")
70
+ output_lines.append("|------|------|")
71
+ for name, path in stored_configs:
72
+ is_active = active_config and active_config.name == name
73
+ is_current = current_config and path == current_config
74
+ markers = []
75
+ if is_active:
76
+ markers.append("default")
77
+ if is_current:
78
+ markers.append("current")
79
+ name_col = f"{name} ({', '.join(markers)})" if markers else name
80
+ output_lines.append(f"| {name_col} | `{path}` |")
81
+
82
+ output_lines.append("")
83
+ output_lines.append("*Use `/set-pool <name>` or `/set-pool <path>` to switch pools.*")
84
+
85
+ await ctx.output.print("\n".join(output_lines))
86
+
87
+ except Exception as e: # noqa: BLE001
88
+ await ctx.output.print(f"❌ **Error listing pools:** {e}")
89
+
90
+
91
+ class CompactCommand(NodeCommand):
92
+ """Compact the conversation history to reduce context size.
93
+
94
+ Uses the configured compaction pipeline from the agent pool manifest,
95
+ or falls back to a default summarizing pipeline.
96
+
97
+ Options:
98
+ --preset <name> Use a specific preset (minimal, balanced, summarizing)
99
+
100
+ Examples:
101
+ /compact
102
+ /compact --preset=minimal
103
+ """
104
+
105
+ name = "compact"
106
+ category = "pool"
107
+
108
+ async def execute_command(
109
+ self,
110
+ ctx: CommandContext[NodeContext[Any]],
111
+ *,
112
+ preset: str | None = None,
113
+ ) -> None:
114
+ """Compact the conversation history.
115
+
116
+ Args:
117
+ ctx: Command context with node context
118
+ preset: Optional preset name (minimal, balanced, summarizing)
119
+ """
120
+ from agentpool.agents.base_agent import BaseAgent
121
+
122
+ # Get agent from context
123
+ agent = ctx.context.node
124
+ if not isinstance(agent, BaseAgent):
125
+ await ctx.output.print(
126
+ "❌ **This command requires an agent with conversation history**"
127
+ )
128
+ return
129
+
130
+ # Check if there's any history to compact
131
+ if not agent.conversation.get_history():
132
+ await ctx.output.print("📭 **No message history to compact**")
133
+ return
134
+
135
+ try:
136
+ # Get compaction pipeline
137
+ from agentpool.messaging.compaction import (
138
+ balanced_context,
139
+ minimal_context,
140
+ summarizing_context,
141
+ )
142
+
143
+ pipeline = None
144
+
145
+ # Check for preset override
146
+ if preset:
147
+ match preset.lower():
148
+ case "minimal":
149
+ pipeline = minimal_context()
150
+ case "balanced":
151
+ pipeline = balanced_context()
152
+ case "summarizing":
153
+ pipeline = summarizing_context()
154
+ case _:
155
+ await ctx.output.print(
156
+ f"⚠️ **Unknown preset:** `{preset}`\n"
157
+ "Available: minimal, balanced, summarizing"
158
+ )
159
+ return
160
+
161
+ # Fall back to pool's configured pipeline
162
+ if pipeline is None and ctx.context.pool:
163
+ pipeline = ctx.context.pool.compaction_pipeline
164
+
165
+ # Fall back to default summarizing pipeline
166
+ if pipeline is None:
167
+ pipeline = summarizing_context()
168
+
169
+ await ctx.output.print("🔄 **Compacting conversation history...**")
170
+
171
+ # Apply the pipeline using shared helper
172
+ from agentpool.messaging.compaction import compact_conversation
173
+
174
+ original_count, compacted_count = await compact_conversation(
175
+ pipeline, agent.conversation
176
+ )
177
+ reduction = original_count - compacted_count
178
+
179
+ await ctx.output.print(
180
+ f"✅ **Compaction complete**\n"
181
+ f"- Messages: {original_count} → {compacted_count} ({reduction} removed)\n"
182
+ f"- Reduction: {reduction / original_count * 100:.1f}%"
183
+ if original_count > 0
184
+ else "✅ **Compaction complete** (no messages)"
185
+ )
186
+
187
+ except Exception as e: # noqa: BLE001
188
+ await ctx.output.print(f"❌ **Error compacting history:** {e}")
189
+
190
+
191
+ class SpawnCommand(NodeCommand):
192
+ """Spawn a subagent to execute a specific task.
193
+
194
+ The subagent runs and its progress is streamed back through the event system.
195
+ How the progress is displayed depends on the protocol (tool box in ACP, inline, etc.).
196
+
197
+ Examples:
198
+ /spawn agent-name "task description"
199
+ /spawn code-reviewer "Review main.py for bugs"
200
+ """
201
+
202
+ name = "spawn"
203
+ category = "pool"
204
+
205
+ @classmethod
206
+ def supports_node(cls, node: Any) -> bool:
207
+ """Only available when running from an agent (needs events)."""
208
+ return isinstance(node, BaseAgent)
209
+
210
+ async def execute_command(
211
+ self,
212
+ ctx: CommandContext[NodeContext[Any]],
213
+ agent_name: str,
214
+ task_prompt: str,
215
+ ) -> None:
216
+ """Spawn a subagent to execute a task.
217
+
218
+ Args:
219
+ ctx: Command context with node context
220
+ agent_name: Name of the agent to spawn
221
+ task_prompt: Task prompt for the subagent
222
+ """
223
+ from agentpool.agents.events import SubAgentEvent
224
+
225
+ pool = ctx.context.pool
226
+ if not pool:
227
+ await ctx.output.print("❌ **No agent pool available**")
228
+ return
229
+
230
+ if agent_name not in pool.nodes:
231
+ available = list(pool.nodes.keys())
232
+ await ctx.output.print(
233
+ f"❌ **Agent** `{agent_name}` **not found**\n\n"
234
+ f"Available agents: {', '.join(available)}"
235
+ )
236
+ return
237
+
238
+ from agentpool.common_types import SupportsRunStream
239
+
240
+ agent = pool.nodes[agent_name]
241
+
242
+ # Check if node supports streaming
243
+ if not isinstance(agent, SupportsRunStream):
244
+ await ctx.output.print(f"❌ **Agent** `{agent_name}` **does not support streaming**")
245
+ return
246
+
247
+ # Stream subagent execution by wrapping events in SubAgentEvent
248
+ # The event handler system (ACP, OpenCode, CLI, etc.) handles rendering
249
+ # Get parent agent's context to access event emitter
250
+ parent_ctx = ctx.context.agent.get_context()
251
+
252
+ async for event in agent.run_stream(task_prompt):
253
+ wrapped = SubAgentEvent(
254
+ source_name=agent_name,
255
+ source_type="agent",
256
+ event=event,
257
+ depth=1,
258
+ )
259
+ # Emit to parent agent's event stream
260
+ await parent_ctx.events.emit_event(wrapped)
@@ -23,7 +23,7 @@ class ClearCommand(AgentCommand):
23
23
  Args:
24
24
  ctx: Command context
25
25
  """
26
- ctx.context.agent.conversation.clear()
26
+ await ctx.context.agent.conversation.clear()
27
27
  await ctx.print("🧹 **Chat history cleared**")
28
28
 
29
29
 
@@ -0,0 +1,119 @@
1
+ """Text sharing services for sharing content via paste services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, assert_never, overload
6
+
7
+ from agentpool_commands.text_sharing.base import ShareResult, TextSharer, Visibility
8
+ from agentpool_commands.text_sharing.github_gist import GistSharer
9
+ from agentpool_commands.text_sharing.opencode import OpenCodeSharer
10
+ from agentpool_commands.text_sharing.paste_rs import PasteRsSharer
11
+ from agentpool_commands.text_sharing.pastebin import PastebinSharer
12
+ from agentpool_commands.text_sharing.shittycodingagent import ShittyCodingAgentSharer
13
+
14
+
15
+ TextSharerStr = Literal["gist", "pastebin", "paste_rs", "opencode", "shittycodingagent"]
16
+
17
+
18
+ @overload
19
+ def get_sharer(
20
+ provider: Literal["gist"],
21
+ *,
22
+ token: str | None = None,
23
+ ) -> GistSharer: ...
24
+
25
+
26
+ @overload
27
+ def get_sharer(
28
+ provider: Literal["pastebin"],
29
+ *,
30
+ api_key: str | None = None,
31
+ ) -> PastebinSharer: ...
32
+
33
+
34
+ @overload
35
+ def get_sharer(
36
+ provider: Literal["paste_rs"],
37
+ ) -> PasteRsSharer: ...
38
+
39
+
40
+ @overload
41
+ def get_sharer(
42
+ provider: Literal["opencode"],
43
+ *,
44
+ api_url: str | None = None,
45
+ ) -> OpenCodeSharer: ...
46
+
47
+
48
+ @overload
49
+ def get_sharer(
50
+ provider: Literal["shittycodingagent"],
51
+ *,
52
+ token: str | None = None,
53
+ ) -> ShittyCodingAgentSharer: ...
54
+
55
+
56
+ def get_sharer(
57
+ provider: TextSharerStr,
58
+ **kwargs: str | None,
59
+ ) -> TextSharer:
60
+ """Get a text sharer based on provider name.
61
+
62
+ Args:
63
+ provider: The text sharing provider to use
64
+ **kwargs: Keyword arguments to pass to the provider constructor
65
+
66
+ Returns:
67
+ An instance of the specified text sharer
68
+
69
+ Example:
70
+ ```python
71
+ # GitHub Gist (reads GITHUB_TOKEN/GH_TOKEN from env)
72
+ sharer = get_sharer("gist")
73
+
74
+ # GitHub Gist with explicit token
75
+ sharer = get_sharer("gist", token="ghp_...")
76
+
77
+ # Pastebin (reads PASTEBIN_API_KEY from env)
78
+ sharer = get_sharer("pastebin")
79
+
80
+ # Pastebin with explicit key
81
+ sharer = get_sharer("pastebin", api_key="...")
82
+
83
+ # paste.rs (no auth needed)
84
+ sharer = get_sharer("paste_rs")
85
+
86
+ # OpenCode (no auth needed)
87
+ sharer = get_sharer("opencode")
88
+
89
+ # OpenCode with custom API URL
90
+ sharer = get_sharer("opencode", api_url="https://api.dev.opencode.ai")
91
+ ```
92
+ """
93
+ match provider:
94
+ case "gist":
95
+ return GistSharer(**kwargs)
96
+ case "pastebin":
97
+ return PastebinSharer(**kwargs)
98
+ case "paste_rs":
99
+ return PasteRsSharer()
100
+ case "opencode":
101
+ return OpenCodeSharer(**kwargs) # type: ignore[arg-type]
102
+ case "shittycodingagent":
103
+ return ShittyCodingAgentSharer(**kwargs)
104
+ case _ as unreachable:
105
+ assert_never(unreachable)
106
+
107
+
108
+ __all__ = [
109
+ "GistSharer",
110
+ "OpenCodeSharer",
111
+ "PasteRsSharer",
112
+ "PastebinSharer",
113
+ "ShareResult",
114
+ "ShittyCodingAgentSharer",
115
+ "TextSharer",
116
+ "TextSharerStr",
117
+ "Visibility",
118
+ "get_sharer",
119
+ ]
@@ -0,0 +1,123 @@
1
+ """Base class for text sharing services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Literal
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from agentpool.messaging.message_history import MessageHistory
12
+
13
+
14
+ Visibility = Literal["public", "unlisted", "private"]
15
+
16
+
17
+ @dataclass
18
+ class ShareResult:
19
+ """Result of a text sharing operation."""
20
+
21
+ url: str
22
+ """URL to access the shared content."""
23
+
24
+ raw_url: str | None = None
25
+ """Direct URL to raw content, if available."""
26
+
27
+ delete_url: str | None = None
28
+ """URL to delete the content, if available."""
29
+
30
+ id: str | None = None
31
+ """Provider-specific ID of the shared content."""
32
+
33
+
34
+ class TextSharer(abc.ABC):
35
+ """Base class for text sharing services."""
36
+
37
+ @property
38
+ @abc.abstractmethod
39
+ def name(self) -> str:
40
+ """Name of the sharing service."""
41
+
42
+ @abc.abstractmethod
43
+ async def share(
44
+ self,
45
+ content: str,
46
+ *,
47
+ title: str | None = None,
48
+ syntax: str | None = None,
49
+ visibility: Visibility = "unlisted",
50
+ expires_in: int | None = None,
51
+ ) -> ShareResult:
52
+ """Share text content.
53
+
54
+ Args:
55
+ content: The text content to share
56
+ title: Optional title/filename for the content
57
+ syntax: Syntax highlighting hint (e.g. "python", "markdown")
58
+ visibility: Visibility level (not all providers support all levels)
59
+ expires_in: Expiration time in seconds (not supported by all providers)
60
+
61
+ Returns:
62
+ ShareResult with URL and metadata
63
+ """
64
+
65
+ async def share_conversation(
66
+ self,
67
+ conversation: MessageHistory,
68
+ *,
69
+ title: str | None = None,
70
+ visibility: Visibility = "unlisted",
71
+ expires_in: int | None = None,
72
+ num_messages: int | None = None,
73
+ ) -> ShareResult:
74
+ """Share a conversation in structured format.
75
+
76
+ Default implementation formats conversation as text and calls share().
77
+ Providers that support structured conversations (e.g., OpenCode)
78
+ should override this method.
79
+
80
+ Args:
81
+ conversation: MessageHistory object to share
82
+ title: Optional title for the conversation
83
+ visibility: Visibility level
84
+ expires_in: Expiration time in seconds
85
+ num_messages: Number of messages to include (None = all)
86
+
87
+ Returns:
88
+ ShareResult with URL and metadata
89
+ """
90
+ # Default: format as plain text
91
+ content = await conversation.format_history(
92
+ num_messages=num_messages,
93
+ )
94
+ return await self.share(
95
+ content,
96
+ title=title,
97
+ syntax="markdown",
98
+ visibility=visibility,
99
+ expires_in=expires_in,
100
+ )
101
+
102
+ def share_sync(
103
+ self,
104
+ content: str,
105
+ *,
106
+ title: str | None = None,
107
+ syntax: str | None = None,
108
+ visibility: Visibility = "unlisted",
109
+ expires_in: int | None = None,
110
+ ) -> ShareResult:
111
+ """Synchronous version of share."""
112
+ from anyenv import run_sync
113
+
114
+ async def wrapper() -> ShareResult:
115
+ return await self.share(
116
+ content,
117
+ title=title,
118
+ syntax=syntax,
119
+ visibility=visibility,
120
+ expires_in=expires_in,
121
+ )
122
+
123
+ return run_sync(wrapper())
@@ -0,0 +1,80 @@
1
+ """GitHub Gist text sharing provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from agentpool_commands.text_sharing.base import ShareResult, TextSharer
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from agentpool_commands.text_sharing.base import Visibility
13
+
14
+
15
+ class GistSharer(TextSharer):
16
+ """Share text via GitHub Gists."""
17
+
18
+ def __init__(self, token: str | None = None) -> None:
19
+ """Initialize the GitHub Gist sharer.
20
+
21
+ Args:
22
+ token: GitHub personal access token. If not provided,
23
+ reads from GITHUB_TOKEN environment variable.
24
+ """
25
+ self._token = token or os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
26
+ if not self._token:
27
+ msg = "GitHub token required. Set GITHUB_TOKEN/GH_TOKEN or pass token parameter."
28
+ raise ValueError(msg)
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ """Name of the sharing service."""
33
+ return "GitHub Gist"
34
+
35
+ async def share(
36
+ self,
37
+ content: str,
38
+ *,
39
+ title: str | None = None,
40
+ syntax: str | None = None,
41
+ visibility: Visibility = "unlisted",
42
+ expires_in: int | None = None,
43
+ ) -> ShareResult:
44
+ """Share content as a GitHub Gist.
45
+
46
+ Args:
47
+ content: The text content to share
48
+ title: Filename for the gist (defaults to "shared.txt" or "shared.{syntax}")
49
+ syntax: File extension hint (e.g. "py", "md")
50
+ visibility: "public" or "unlisted" (private not supported)
51
+ expires_in: Ignored (gists don't expire)
52
+ """
53
+ import anyenv
54
+
55
+ filename = title or f"shared.{syntax or 'txt'}"
56
+ public = visibility == "public"
57
+ payload: dict[str, Any] = {"files": {filename: {"content": content}}, "public": public}
58
+ headers = {
59
+ "Authorization": f"Bearer {self._token}",
60
+ "Accept": "application/vnd.github+json",
61
+ "X-GitHub-Api-Version": "2022-11-28",
62
+ }
63
+ url = "https://api.github.com/gists"
64
+ response: dict[str, Any] = await anyenv.post_json(url, payload, headers=headers)
65
+ raw_url = response["files"][filename]["raw_url"]
66
+ return ShareResult(url=response["html_url"], raw_url=raw_url, id=response["id"])
67
+
68
+
69
+ if __name__ == "__main__":
70
+ import asyncio
71
+
72
+ async def main() -> None:
73
+ """Example usage of the GistSharer class."""
74
+ sharer = GistSharer()
75
+ result = await sharer.share("# Test Gist!", title="test.md", syntax="md")
76
+ print(f"URL: {result.url}")
77
+ print(f"Raw: {result.raw_url}")
78
+ print(f"ID: {result.id}")
79
+
80
+ asyncio.run(main())