agentpool 2.1.9__py3-none-any.whl → 2.2.3__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 (174) hide show
  1. acp/__init__.py +13 -0
  2. acp/bridge/README.md +15 -2
  3. acp/bridge/__init__.py +3 -2
  4. acp/bridge/__main__.py +60 -19
  5. acp/bridge/ws_server.py +173 -0
  6. acp/bridge/ws_server_cli.py +89 -0
  7. acp/notifications.py +2 -1
  8. acp/stdio.py +39 -9
  9. acp/transports.py +362 -2
  10. acp/utils.py +15 -2
  11. agentpool/__init__.py +4 -1
  12. agentpool/agents/__init__.py +2 -0
  13. agentpool/agents/acp_agent/acp_agent.py +203 -88
  14. agentpool/agents/acp_agent/acp_converters.py +46 -21
  15. agentpool/agents/acp_agent/client_handler.py +157 -3
  16. agentpool/agents/acp_agent/session_state.py +4 -1
  17. agentpool/agents/agent.py +314 -107
  18. agentpool/agents/agui_agent/__init__.py +0 -2
  19. agentpool/agents/agui_agent/agui_agent.py +90 -21
  20. agentpool/agents/agui_agent/agui_converters.py +0 -131
  21. agentpool/agents/base_agent.py +163 -1
  22. agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
  23. agentpool/agents/claude_code_agent/converters.py +71 -3
  24. agentpool/agents/claude_code_agent/history.py +474 -0
  25. agentpool/agents/context.py +40 -0
  26. agentpool/agents/events/__init__.py +2 -0
  27. agentpool/agents/events/builtin_handlers.py +2 -1
  28. agentpool/agents/events/event_emitter.py +29 -2
  29. agentpool/agents/events/events.py +20 -0
  30. agentpool/agents/modes.py +54 -0
  31. agentpool/agents/tool_call_accumulator.py +213 -0
  32. agentpool/common_types.py +21 -0
  33. agentpool/config_resources/__init__.py +38 -1
  34. agentpool/config_resources/claude_code_agent.yml +3 -0
  35. agentpool/delegation/pool.py +37 -29
  36. agentpool/delegation/team.py +1 -0
  37. agentpool/delegation/teamrun.py +1 -0
  38. agentpool/diagnostics/__init__.py +53 -0
  39. agentpool/diagnostics/lsp_manager.py +1593 -0
  40. agentpool/diagnostics/lsp_proxy.py +41 -0
  41. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  42. agentpool/diagnostics/models.py +398 -0
  43. agentpool/mcp_server/__init__.py +0 -2
  44. agentpool/mcp_server/client.py +12 -3
  45. agentpool/mcp_server/manager.py +25 -31
  46. agentpool/mcp_server/registries/official_registry_client.py +25 -0
  47. agentpool/mcp_server/tool_bridge.py +78 -66
  48. agentpool/messaging/__init__.py +0 -2
  49. agentpool/messaging/compaction.py +72 -197
  50. agentpool/messaging/message_history.py +12 -0
  51. agentpool/messaging/messages.py +52 -9
  52. agentpool/messaging/processing.py +3 -1
  53. agentpool/models/acp_agents/base.py +0 -22
  54. agentpool/models/acp_agents/mcp_capable.py +8 -148
  55. agentpool/models/acp_agents/non_mcp.py +129 -72
  56. agentpool/models/agents.py +35 -13
  57. agentpool/models/claude_code_agents.py +33 -2
  58. agentpool/models/manifest.py +43 -0
  59. agentpool/repomap.py +1 -1
  60. agentpool/resource_providers/__init__.py +9 -1
  61. agentpool/resource_providers/aggregating.py +52 -3
  62. agentpool/resource_providers/base.py +57 -1
  63. agentpool/resource_providers/mcp_provider.py +23 -0
  64. agentpool/resource_providers/plan_provider.py +130 -41
  65. agentpool/resource_providers/pool.py +2 -0
  66. agentpool/resource_providers/static.py +2 -0
  67. agentpool/sessions/__init__.py +2 -1
  68. agentpool/sessions/manager.py +31 -2
  69. agentpool/sessions/models.py +50 -0
  70. agentpool/skills/registry.py +13 -8
  71. agentpool/storage/manager.py +217 -1
  72. agentpool/testing.py +537 -19
  73. agentpool/utils/file_watcher.py +269 -0
  74. agentpool/utils/identifiers.py +121 -0
  75. agentpool/utils/pydantic_ai_helpers.py +46 -0
  76. agentpool/utils/streams.py +690 -1
  77. agentpool/utils/subprocess_utils.py +155 -0
  78. agentpool/utils/token_breakdown.py +461 -0
  79. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
  80. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
  81. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
  82. agentpool_cli/__main__.py +4 -0
  83. agentpool_cli/serve_acp.py +41 -20
  84. agentpool_cli/serve_agui.py +87 -0
  85. agentpool_cli/serve_opencode.py +119 -0
  86. agentpool_commands/__init__.py +30 -0
  87. agentpool_commands/agents.py +74 -1
  88. agentpool_commands/history.py +62 -0
  89. agentpool_commands/mcp.py +176 -0
  90. agentpool_commands/models.py +56 -3
  91. agentpool_commands/tools.py +57 -0
  92. agentpool_commands/utils.py +51 -0
  93. agentpool_config/builtin_tools.py +77 -22
  94. agentpool_config/commands.py +24 -1
  95. agentpool_config/compaction.py +258 -0
  96. agentpool_config/mcp_server.py +131 -1
  97. agentpool_config/storage.py +46 -1
  98. agentpool_config/tools.py +7 -1
  99. agentpool_config/toolsets.py +92 -148
  100. agentpool_server/acp_server/acp_agent.py +134 -150
  101. agentpool_server/acp_server/commands/acp_commands.py +216 -51
  102. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
  103. agentpool_server/acp_server/server.py +23 -79
  104. agentpool_server/acp_server/session.py +181 -19
  105. agentpool_server/opencode_server/.rules +95 -0
  106. agentpool_server/opencode_server/ENDPOINTS.md +362 -0
  107. agentpool_server/opencode_server/__init__.py +27 -0
  108. agentpool_server/opencode_server/command_validation.py +172 -0
  109. agentpool_server/opencode_server/converters.py +869 -0
  110. agentpool_server/opencode_server/dependencies.py +24 -0
  111. agentpool_server/opencode_server/input_provider.py +269 -0
  112. agentpool_server/opencode_server/models/__init__.py +228 -0
  113. agentpool_server/opencode_server/models/agent.py +53 -0
  114. agentpool_server/opencode_server/models/app.py +60 -0
  115. agentpool_server/opencode_server/models/base.py +26 -0
  116. agentpool_server/opencode_server/models/common.py +23 -0
  117. agentpool_server/opencode_server/models/config.py +37 -0
  118. agentpool_server/opencode_server/models/events.py +647 -0
  119. agentpool_server/opencode_server/models/file.py +88 -0
  120. agentpool_server/opencode_server/models/mcp.py +25 -0
  121. agentpool_server/opencode_server/models/message.py +162 -0
  122. agentpool_server/opencode_server/models/parts.py +190 -0
  123. agentpool_server/opencode_server/models/provider.py +81 -0
  124. agentpool_server/opencode_server/models/pty.py +43 -0
  125. agentpool_server/opencode_server/models/session.py +99 -0
  126. agentpool_server/opencode_server/routes/__init__.py +25 -0
  127. agentpool_server/opencode_server/routes/agent_routes.py +442 -0
  128. agentpool_server/opencode_server/routes/app_routes.py +139 -0
  129. agentpool_server/opencode_server/routes/config_routes.py +241 -0
  130. agentpool_server/opencode_server/routes/file_routes.py +392 -0
  131. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  132. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  133. agentpool_server/opencode_server/routes/message_routes.py +705 -0
  134. agentpool_server/opencode_server/routes/pty_routes.py +299 -0
  135. agentpool_server/opencode_server/routes/session_routes.py +1205 -0
  136. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  137. agentpool_server/opencode_server/server.py +430 -0
  138. agentpool_server/opencode_server/state.py +121 -0
  139. agentpool_server/opencode_server/time_utils.py +8 -0
  140. agentpool_storage/__init__.py +16 -0
  141. agentpool_storage/base.py +103 -0
  142. agentpool_storage/claude_provider.py +907 -0
  143. agentpool_storage/file_provider.py +129 -0
  144. agentpool_storage/memory_provider.py +61 -0
  145. agentpool_storage/models.py +3 -0
  146. agentpool_storage/opencode_provider.py +730 -0
  147. agentpool_storage/project_store.py +325 -0
  148. agentpool_storage/session_store.py +6 -0
  149. agentpool_storage/sql_provider/__init__.py +4 -2
  150. agentpool_storage/sql_provider/models.py +48 -0
  151. agentpool_storage/sql_provider/sql_provider.py +134 -1
  152. agentpool_storage/sql_provider/utils.py +10 -1
  153. agentpool_storage/text_log_provider.py +1 -0
  154. agentpool_toolsets/builtin/__init__.py +0 -8
  155. agentpool_toolsets/builtin/code.py +95 -56
  156. agentpool_toolsets/builtin/debug.py +16 -21
  157. agentpool_toolsets/builtin/execution_environment.py +99 -103
  158. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  159. agentpool_toolsets/builtin/skills.py +86 -4
  160. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  161. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  162. agentpool_toolsets/fsspec_toolset/grep.py +74 -2
  163. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  164. agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
  165. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  166. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  167. agentpool_toolsets/mcp_discovery/toolset.py +454 -0
  168. agentpool_toolsets/mcp_run_toolset.py +84 -6
  169. agentpool_toolsets/builtin/agent_management.py +0 -239
  170. agentpool_toolsets/builtin/history.py +0 -36
  171. agentpool_toolsets/builtin/integration.py +0 -85
  172. agentpool_toolsets/builtin/tool_management.py +0 -90
  173. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
  174. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,362 @@
1
+ # OpenCode API Compatibility Checklist
2
+
3
+ This document tracks the implementation status of OpenCode-compatible API endpoints.
4
+
5
+ ## Status Legend
6
+ - [ ] Not implemented
7
+ - [x] Implemented
8
+ - [~] Partial / Stub
9
+ - [-] Skipped (not needed)
10
+
11
+ ---
12
+
13
+ ## Global
14
+
15
+ | Status | Method | Path | Description |
16
+ |--------|--------|------|-------------|
17
+ | [x] | GET | `/global/health` | Get server health and version |
18
+ | [x] | GET | `/global/event` | Get global events (SSE stream) |
19
+
20
+ ---
21
+
22
+ ## Project & Path
23
+
24
+ | Status | Method | Path | Description |
25
+ |--------|--------|------|-------------|
26
+ | [x] | GET | `/project` | List all projects |
27
+ | [x] | GET | `/project/current` | Get the current project |
28
+ | [x] | GET | `/path` | Get the current path |
29
+ | [x] | GET | `/vcs` | Get VCS info for current project |
30
+
31
+ ---
32
+
33
+ ## Instance
34
+
35
+ | Status | Method | Path | Description |
36
+ |--------|--------|------|-------------|
37
+ | [ ] | POST | `/instance/dispose` | Dispose the current instance |
38
+
39
+ ---
40
+
41
+ ## Config
42
+
43
+ | Status | Method | Path | Description |
44
+ |--------|--------|------|-------------|
45
+ | [x] | GET | `/config` | Get config info |
46
+ | [ ] | PATCH | `/config` | Update config |
47
+ | [~] | GET | `/config/providers` | List providers and default models |
48
+
49
+ ---
50
+
51
+ ## Provider
52
+
53
+ | Status | Method | Path | Description |
54
+ |--------|--------|------|-------------|
55
+ | [~] | GET | `/provider` | List all providers |
56
+ | [x] | GET | `/provider/auth` | Get provider authentication methods |
57
+ | [x] | POST | `/provider/{id}/oauth/authorize` | Authorize provider via OAuth |
58
+ | [x] | POST | `/provider/{id}/oauth/callback` | Handle OAuth callback |
59
+
60
+ ---
61
+
62
+ ## Sessions
63
+
64
+ | Status | Method | Path | Description |
65
+ |--------|--------|------|-------------|
66
+ | [x] | GET | `/session` | List all sessions |
67
+ | [x] | POST | `/session` | Create a new session |
68
+ | [x] | GET | `/session/status` | Get session status for all sessions |
69
+ | [x] | GET | `/session/{id}` | Get session details |
70
+ | [x] | DELETE | `/session/{id}` | Delete a session |
71
+ | [x] | PATCH | `/session/{id}` | Update session properties |
72
+ | [ ] | GET | `/session/{id}/children` | Get child sessions |
73
+ | [x] | GET | `/session/{id}/todo` | Get todo list for session |
74
+ | [x] | POST | `/session/{id}/init` | Analyze app, create AGENTS.md |
75
+ | [x] | POST | `/session/{id}/fork` | Fork session at message |
76
+ | [x] | POST | `/session/{id}/abort` | Abort running session |
77
+ | [x] | POST | `/session/{id}/share` | Share a session |
78
+ | [x] | DELETE | `/session/{id}/share` | Unshare a session |
79
+ | [x] | GET | `/session/{id}/diff` | Get diff for session |
80
+ | [x] | POST | `/session/{id}/summarize` | Summarize the session |
81
+ | [x] | POST | `/session/{id}/revert` | Revert a message |
82
+ | [x] | POST | `/session/{id}/unrevert` | Restore reverted messages |
83
+ | [x] | GET | `/session/{id}/permissions` | Get pending permission requests |
84
+ | [x] | POST | `/session/{id}/permissions/{permissionID}` | Respond to permission request |
85
+
86
+ ---
87
+
88
+ ## Messages
89
+
90
+ | Status | Method | Path | Description |
91
+ |--------|--------|------|-------------|
92
+ | [x] | GET | `/session/{id}/message` | List messages in session |
93
+ | [~] | POST | `/session/{id}/message` | Send message (wait for response) |
94
+ | [x] | GET | `/session/{id}/message/{messageID}` | Get message details |
95
+ | [ ] | POST | `/session/{id}/prompt_async` | Send message async (no wait) |
96
+ | [x] | POST | `/session/{id}/command` | Execute slash command (MCP prompts) |
97
+ | [x] | POST | `/session/{id}/shell` | Run shell command |
98
+
99
+ ---
100
+
101
+ ## Commands
102
+
103
+ | Status | Method | Path | Description |
104
+ |--------|--------|------|-------------|
105
+ | [x] | GET | `/command` | List all commands (MCP prompts) |
106
+
107
+ ---
108
+
109
+ ## Files
110
+
111
+ | Status | Method | Path | Description |
112
+ |--------|--------|------|-------------|
113
+ | [x] | GET | `/find?pattern=` | Search for text in files |
114
+ | [x] | GET | `/find/file?query=` | Find files by name |
115
+ | [~] | GET | `/find/symbol?query=` | Find workspace symbols |
116
+ | [x] | GET | `/file?path=` | List files and directories |
117
+ | [x] | GET | `/file/content?path=` | Read a file |
118
+ | [~] | GET | `/file/status` | Get status for tracked files |
119
+
120
+ ---
121
+
122
+ ## Tools (Experimental)
123
+
124
+ | Status | Method | Path | Description |
125
+ |--------|--------|------|-------------|
126
+ | [x] | GET | `/experimental/tool/ids` | List all tool IDs |
127
+ | [x] | GET | `/experimental/tool?provider=&model=` | List tools with schemas |
128
+
129
+ ---
130
+
131
+ ## LSP, Formatters & MCP
132
+
133
+ | Status | Method | Path | Description |
134
+ |--------|--------|------|-------------|
135
+ | [x] | GET | `/lsp` | Get LSP server status |
136
+ | [x] | POST | `/lsp/start` | Start an LSP server |
137
+ | [x] | POST | `/lsp/stop` | Stop an LSP server |
138
+ | [x] | GET | `/lsp/servers` | List available LSP servers |
139
+ | [x] | GET | `/lsp/diagnostics` | Get LSP diagnostics (CLI-based) |
140
+ | [x] | GET | `/formatter` | Get formatter status (stub) |
141
+ | [~] | GET | `/mcp` | Get MCP server status |
142
+ | [x] | POST | `/mcp` | Add MCP server dynamically |
143
+
144
+ ---
145
+
146
+ ## Agents
147
+
148
+ | Status | Method | Path | Description |
149
+ |--------|--------|------|-------------|
150
+ | [~] | GET | `/agent` | List all available agents |
151
+
152
+ ---
153
+
154
+ ## Logging
155
+
156
+ | Status | Method | Path | Description |
157
+ |--------|--------|------|-------------|
158
+ | [x] | POST | `/log` | Write log entry |
159
+
160
+ ---
161
+
162
+ ## Modes
163
+
164
+ | Status | Method | Path | Description |
165
+ |--------|--------|------|-------------|
166
+ | [~] | GET | `/mode` | List all modes |
167
+
168
+ ---
169
+
170
+ ## PTY (Pseudo-Terminal)
171
+
172
+ | Status | Method | Path | Description |
173
+ |--------|--------|------|-------------|
174
+ | [ ] | GET | `/pty` | List all PTY sessions |
175
+ | [ ] | POST | `/pty` | Create a new PTY session |
176
+ | [ ] | GET | `/pty/{ptyID}` | Get PTY session details |
177
+ | [ ] | PATCH | `/pty/{ptyID}` | Update PTY session (resize, etc.) |
178
+ | [ ] | DELETE | `/pty/{ptyID}` | Remove/kill PTY session |
179
+ | [ ] | GET | `/pty/{ptyID}/connect` | Connect to PTY (WebSocket) |
180
+
181
+ ### PTY SSE Event Types
182
+
183
+ | Status | Event Type | Description |
184
+ |--------|------------|-------------|
185
+ | [x] | `pty.created` | PTY session created |
186
+ | [x] | `pty.updated` | PTY session updated |
187
+ | [x] | `pty.exited` | PTY process exited |
188
+ | [x] | `pty.deleted` | PTY session deleted |
189
+
190
+ ---
191
+
192
+ ## TUI (External Control)
193
+
194
+ These endpoints allow external integrations (e.g., VSCode extension) to control the TUI
195
+ by broadcasting events via SSE.
196
+
197
+ | Status | Method | Path | Description |
198
+ |--------|--------|------|-------------|
199
+ | [x] | POST | `/tui/append-prompt` | Append text to prompt |
200
+ | [x] | POST | `/tui/open-help` | Open help dialog |
201
+ | [x] | POST | `/tui/open-sessions` | Open session selector |
202
+ | [x] | POST | `/tui/open-themes` | Open theme selector |
203
+ | [x] | POST | `/tui/open-models` | Open model selector |
204
+ | [x] | POST | `/tui/submit-prompt` | Submit current prompt |
205
+ | [x] | POST | `/tui/clear-prompt` | Clear the prompt |
206
+ | [x] | POST | `/tui/execute-command` | Execute a command |
207
+ | [x] | POST | `/tui/show-toast` | Show toast notification |
208
+ | [-] | GET | `/tui/control/next` | Wait for next control request (not needed) |
209
+ | [-] | POST | `/tui/control/response` | Respond to control request (not needed) |
210
+
211
+ ---
212
+
213
+ ## Auth
214
+
215
+ | Status | Method | Path | Description |
216
+ |--------|--------|------|-------------|
217
+ | [ ] | PUT | `/auth/{id}` | Set authentication credentials |
218
+
219
+ ---
220
+
221
+ ## Events
222
+
223
+ | Status | Method | Path | Description |
224
+ |--------|--------|------|-------------|
225
+ | [x] | GET | `/event` | SSE event stream |
226
+
227
+ ### SSE Event Types
228
+
229
+ All event types supported by the OpenCode protocol:
230
+
231
+ | Status | Event Type | Description |
232
+ |--------|------------|-------------|
233
+ | [x] | `server.connected` | Server connected (sent on SSE connect) |
234
+ | [-] | `global.disposed` | Global instance disposed (multi-project, not needed) |
235
+ | [-] | `installation.updated` | Installation updated (auto-upgrade complete, not needed) |
236
+ | [x] | `installation.update-available` | Update available (via `tui.toast.show` workaround) |
237
+ | [ ] | `project.updated` | Project configuration updated |
238
+ | [-] | `server.instance.disposed` | Server instance disposed (multi-project, not needed) |
239
+ | [x] | `lsp.updated` | LSP server status updated |
240
+ | [~] | `lsp.client.diagnostics` | LSP client diagnostics received |
241
+ | [x] | `session.created` | Session created |
242
+ | [x] | `session.updated` | Session updated |
243
+ | [x] | `session.deleted` | Session deleted |
244
+ | [x] | `session.status` | Session status changed (running/idle/error) |
245
+ | [x] | `session.idle` | Session became idle (deprecated but used by TUI) |
246
+ | [x] | `session.compacted` | Session context was compacted/summarized |
247
+ | [ ] | `session.diff` | Session file diff updated |
248
+ | [x] | `session.error` | Session encountered an error |
249
+ | [x] | `message.updated` | Message created or updated |
250
+ | [ ] | `message.removed` | Message removed |
251
+ | [x] | `message.part.updated` | Message part (text, tool, etc.) updated |
252
+ | [ ] | `message.part.removed` | Message part removed |
253
+ | [x] | `permission.updated` | Tool permission requested (awaiting user response) |
254
+ | [x] | `permission.replied` | Permission request resolved (user responded) |
255
+ | [x] | `todo.updated` | Todo list item updated |
256
+ | [ ] | `file.edited` | File was edited |
257
+ | [x] | `file.watcher.updated` | File watcher detects project file changes |
258
+ | [x] | `vcs.branch.updated` | VCS branch changed (polling-based) |
259
+ | [ ] | `mcp.tools.changed` | MCP server tools changed |
260
+ | [ ] | `command.executed` | Slash command executed |
261
+ | [x] | `tui.prompt.append` | Append text to TUI prompt input |
262
+ | [x] | `tui.command.execute` | Execute a TUI command |
263
+ | [x] | `tui.toast.show` | Show toast notification in TUI |
264
+ | [x] | `pty.created` | PTY session created |
265
+ | [x] | `pty.updated` | PTY session updated |
266
+ | [x] | `pty.exited` | PTY process exited |
267
+ | [x] | `pty.deleted` | PTY session deleted |
268
+
269
+ ---
270
+
271
+ ## Docs
272
+
273
+ | Status | Method | Path | Description |
274
+ |--------|--------|------|-------------|
275
+ | [x] | GET | `/doc` | OpenAPI 3.1 specification |
276
+
277
+ ---
278
+
279
+ ## Implementation Summary
280
+
281
+ ### Completed (TUI can connect!)
282
+ - Health check and SSE events
283
+ - Session CRUD operations
284
+ - File listing and reading
285
+ - Path/Project/VCS info
286
+ - Config endpoint
287
+ - All stubs needed for TUI to render
288
+
289
+ ### Next Steps
290
+ 1. **Agent Integration** - Wire up actual LLM calls for `/session/{id}/message`
291
+ 2. **Provider Discovery** - Populate `/config/providers` with real models
292
+ 3. **File Search** - Implement `/find` endpoints
293
+
294
+ ---
295
+
296
+ ## Testing
297
+
298
+ **Terminal 1:** Start server
299
+ ```bash
300
+ duty opencode-server
301
+ ```
302
+
303
+ **Terminal 2:** Attach TUI
304
+ ```bash
305
+ duty opencode-tui
306
+ ```
307
+
308
+ Or combined (less reliable for interactive use):
309
+ ```bash
310
+ duty opencode
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Tool UI Rendering
316
+
317
+ The OpenCode TUI has special rendering for certain tool names. Tools must use these exact names
318
+ and parameter formats (after snake_case → camelCase conversion) to get custom UI treatment.
319
+
320
+ Parameter conversion is handled in `converters.py` via `_PARAM_NAME_MAP`.
321
+
322
+ | Tool Name | Expected Parameters (camelCase) | UI Treatment |
323
+ |-----------|--------------------------------|--------------|
324
+ | `read` | `filePath`, `offset`, `limit` | Glasses icon, shows filename |
325
+ | `list` | `path` | Bullet-list icon, shows directory |
326
+ | `glob` | `path`, `pattern` | Magnifying-glass icon, shows pattern |
327
+ | `grep` | `path`, `pattern`, `include` | Magnifying-glass icon, shows pattern |
328
+ | `webfetch` | `url`, `format` | Window icon, shows URL |
329
+ | `task` | `subagent_type`, `description` | Task icon, shows agent summary |
330
+ | `bash` | `command`, `description` | Console icon, shows command + output |
331
+ | `edit` | `filePath`, `oldString`, `newString` | Code icon, **diff view** |
332
+ | `write` | `filePath`, `content` | Code icon, **syntax-highlighted content** |
333
+ | `todowrite` | `todos` (array with `status`, `content`) | Checklist icon, checkbox list |
334
+ | `todoread` | - | Filtered out (not displayed) |
335
+
336
+ ### Metadata
337
+
338
+ Some tools also use `props.metadata` for additional UI data:
339
+
340
+ | Tool | Metadata Fields | Description |
341
+ |------|-----------------|-------------|
342
+ | `edit` | `filediff`, `diagnostics` | Diff data and LSP diagnostics |
343
+ | `write` | `diagnostics` | LSP diagnostics for the written file |
344
+ | `bash` | `command` | Fallback if `input.command` missing |
345
+ | `task` | `summary`, `sessionId` | Child tool summary and session ID |
346
+
347
+ ### Parameter Name Mapping
348
+
349
+ The `_PARAM_NAME_MAP` in `converters.py` converts our snake_case to TUI's camelCase:
350
+
351
+ ```python
352
+ _PARAM_NAME_MAP = {
353
+ "path": "filePath",
354
+ "file_path": "filePath",
355
+ "old_string": "oldString",
356
+ "new_string": "newString",
357
+ "replace_all": "replaceAll",
358
+ "line_hint": "lineHint",
359
+ }
360
+ ```
361
+
362
+
@@ -0,0 +1,27 @@
1
+ """OpenCode-compatible API server.
2
+
3
+ This module provides a FastAPI-based server that implements the OpenCode API,
4
+ allowing OpenCode SDK clients to interact with AgentPool agents.
5
+
6
+ Example usage:
7
+
8
+ from agentpool_server.opencode_server import OpenCodeServer
9
+
10
+ server = OpenCodeServer(port=4096)
11
+ server.run()
12
+
13
+ Or programmatically:
14
+
15
+ from agentpool_server.opencode_server import create_app
16
+
17
+ app = create_app(working_dir="/path/to/project")
18
+ # Use with uvicorn or other ASGI server
19
+ """
20
+
21
+ from agentpool_server.opencode_server.server import (
22
+ OpenCodeServer,
23
+ create_app,
24
+ run_server,
25
+ )
26
+
27
+ __all__ = ["OpenCodeServer", "create_app", "run_server"]
@@ -0,0 +1,172 @@
1
+ """Shell command validation and security checks.
2
+
3
+ Provides validation for shell commands to prevent dangerous operations
4
+ like destructive commands, privilege escalation, and path traversal.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ import re
11
+ import shlex
12
+
13
+ from fastapi import HTTPException
14
+
15
+
16
+ # Patterns for dangerous commands that should always be blocked
17
+ DANGEROUS_PATTERNS: list[tuple[re.Pattern[str], str]] = [
18
+ # Destructive file operations
19
+ (re.compile(r"\brm\s+(-[a-zA-Z]*)?.*\s+/\s*$"), "rm on root directory"),
20
+ (re.compile(r"\brm\s+-[a-zA-Z]*r[a-zA-Z]*f.*\s+/"), "recursive force delete"),
21
+ (re.compile(r"\brm\s+-[a-zA-Z]*f[a-zA-Z]*r.*\s+/"), "recursive force delete"),
22
+ (re.compile(r"\bmkfs\b"), "filesystem format"),
23
+ (re.compile(r"\bdd\s+.*of=/dev/"), "direct disk write"),
24
+ (re.compile(r">\s*/dev/sd[a-z]"), "overwrite disk device"),
25
+ # Privilege escalation
26
+ (re.compile(r"\bsudo\b"), "sudo command"),
27
+ (re.compile(r"\bsu\s+-?\s*$"), "switch user"),
28
+ (re.compile(r"\bsu\s+root\b"), "switch to root"),
29
+ (re.compile(r"\bdoas\b"), "doas command"),
30
+ # Remote code execution patterns
31
+ (re.compile(r"\bcurl\b.*\|\s*(ba)?sh"), "curl pipe to shell"),
32
+ (re.compile(r"\bwget\b.*\|\s*(ba)?sh"), "wget pipe to shell"),
33
+ (re.compile(r"\bcurl\b.*\|\s*python"), "curl pipe to python"),
34
+ (re.compile(r"\bwget\b.*\|\s*python"), "wget pipe to python"),
35
+ # Fork bombs and resource exhaustion
36
+ (re.compile(r":\(\)\s*\{\s*:\|:&\s*\}\s*;"), "fork bomb"),
37
+ (re.compile(r"\bfork\s*bomb\b", re.IGNORECASE), "fork bomb"),
38
+ # History/credential theft
39
+ (re.compile(r">\s*~/.bash_history"), "history manipulation"),
40
+ (re.compile(r"cat.*\.ssh/"), "SSH key access"),
41
+ (re.compile(r"cat.*/etc/shadow"), "shadow file access"),
42
+ # Shutdown/reboot
43
+ (re.compile(r"\bshutdown\b"), "shutdown command"),
44
+ (re.compile(r"\breboot\b"), "reboot command"),
45
+ (re.compile(r"\binit\s+0\b"), "init shutdown"),
46
+ (re.compile(r"\binit\s+6\b"), "init reboot"),
47
+ ]
48
+
49
+ # Sensitive paths that should not be accessed
50
+ SENSITIVE_PATHS = {
51
+ "/etc/passwd",
52
+ "/etc/shadow",
53
+ "/etc/sudoers",
54
+ "/etc/ssh",
55
+ "/root",
56
+ "~/.ssh",
57
+ "~/.gnupg",
58
+ "~/.aws",
59
+ "~/.config/gcloud",
60
+ }
61
+
62
+ # Patterns that indicate path traversal attempts
63
+ PATH_TRAVERSAL_PATTERN = re.compile(r"\.\.(/|\\)")
64
+
65
+
66
+ def validate_command(command: str, working_dir: str) -> None:
67
+ """Validate a shell command for security issues.
68
+
69
+ Args:
70
+ command: The shell command to validate.
71
+ working_dir: The working directory for the command.
72
+
73
+ Raises:
74
+ HTTPException: If the command is dangerous or restricted.
75
+ """
76
+ # Check for dangerous patterns
77
+ for pattern, description in DANGEROUS_PATTERNS:
78
+ if pattern.search(command):
79
+ raise HTTPException(
80
+ status_code=403,
81
+ detail=f"Command restricted: {description}",
82
+ )
83
+
84
+ # Check for sensitive path access
85
+ command_lower = command.lower()
86
+ for sensitive_path in SENSITIVE_PATHS:
87
+ # Normalize ~ to actual pattern
88
+ path_pattern = sensitive_path.replace("~", "(/home/[^/]+|~)")
89
+ if re.search(path_pattern, command_lower):
90
+ raise HTTPException(
91
+ status_code=403,
92
+ detail=f"Command restricted: access to sensitive path {sensitive_path}",
93
+ )
94
+
95
+ # Check for path traversal in the command
96
+ if PATH_TRAVERSAL_PATTERN.search(command):
97
+ # Parse the command to check if traversal escapes working_dir
98
+ _check_path_traversal(command, working_dir)
99
+
100
+
101
+ def _check_path_traversal(command: str, working_dir: str) -> None:
102
+ """Check if command contains path traversal that escapes working directory.
103
+
104
+ Args:
105
+ command: The shell command to check.
106
+ working_dir: The working directory.
107
+
108
+ Raises:
109
+ HTTPException: If path traversal escapes the working directory.
110
+ """
111
+ working_path = Path(working_dir).resolve()
112
+
113
+ # Try to extract paths from the command
114
+ try:
115
+ tokens = shlex.split(command)
116
+ except ValueError:
117
+ # If we can't parse, be conservative
118
+ tokens = command.split()
119
+
120
+ for token in tokens:
121
+ # Skip flags and operators
122
+ if token.startswith("-") or token in ("&&", "||", "|", ";", ">", "<", ">>"):
123
+ continue
124
+
125
+ # Check if token looks like a path with traversal
126
+ if ".." in token:
127
+ # Resolve the path relative to working_dir
128
+ try:
129
+ if token.startswith("/"):
130
+ resolved = Path(token).resolve()
131
+ else:
132
+ resolved = (working_path / token).resolve()
133
+
134
+ # Check if resolved path is within working_dir
135
+ try:
136
+ resolved.relative_to(working_path)
137
+ except ValueError:
138
+ raise HTTPException(
139
+ status_code=403,
140
+ detail="Command restricted: path escapes project directory",
141
+ ) from None
142
+ except (OSError, RuntimeError):
143
+ # Path resolution failed, be conservative and block
144
+ raise HTTPException(
145
+ status_code=403,
146
+ detail="Command restricted: invalid path in command",
147
+ ) from None
148
+
149
+
150
+ def validate_workdir(workdir: str | None, project_dir: str) -> None:
151
+ """Validate that a working directory is within the project.
152
+
153
+ Args:
154
+ workdir: The requested working directory (may be None).
155
+ project_dir: The project root directory.
156
+
157
+ Raises:
158
+ HTTPException: If workdir escapes the project directory.
159
+ """
160
+ if workdir is None:
161
+ return
162
+
163
+ project_path = Path(project_dir).resolve()
164
+ work_path = Path(workdir).resolve()
165
+
166
+ try:
167
+ work_path.relative_to(project_path)
168
+ except ValueError:
169
+ raise HTTPException(
170
+ status_code=403,
171
+ detail="Command restricted: working directory outside project",
172
+ ) from None