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.
- acp/__init__.py +13 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/notifications.py +2 -1
- acp/stdio.py +39 -9
- acp/transports.py +362 -2
- acp/utils.py +15 -2
- agentpool/__init__.py +4 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +203 -88
- agentpool/agents/acp_agent/acp_converters.py +46 -21
- agentpool/agents/acp_agent/client_handler.py +157 -3
- agentpool/agents/acp_agent/session_state.py +4 -1
- agentpool/agents/agent.py +314 -107
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +90 -21
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/base_agent.py +163 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
- agentpool/agents/claude_code_agent/converters.py +71 -3
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +2 -0
- agentpool/agents/events/builtin_handlers.py +2 -1
- agentpool/agents/events/event_emitter.py +29 -2
- agentpool/agents/events/events.py +20 -0
- agentpool/agents/modes.py +54 -0
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/common_types.py +21 -0
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/claude_code_agent.yml +3 -0
- agentpool/delegation/pool.py +37 -29
- agentpool/delegation/team.py +1 -0
- agentpool/delegation/teamrun.py +1 -0
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +12 -3
- agentpool/mcp_server/manager.py +25 -31
- agentpool/mcp_server/registries/official_registry_client.py +25 -0
- agentpool/mcp_server/tool_bridge.py +78 -66
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/message_history.py +12 -0
- agentpool/messaging/messages.py +52 -9
- agentpool/messaging/processing.py +3 -1
- agentpool/models/acp_agents/base.py +0 -22
- agentpool/models/acp_agents/mcp_capable.py +8 -148
- agentpool/models/acp_agents/non_mcp.py +129 -72
- agentpool/models/agents.py +35 -13
- agentpool/models/claude_code_agents.py +33 -2
- agentpool/models/manifest.py +43 -0
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +9 -1
- agentpool/resource_providers/aggregating.py +52 -3
- agentpool/resource_providers/base.py +57 -1
- agentpool/resource_providers/mcp_provider.py +23 -0
- agentpool/resource_providers/plan_provider.py +130 -41
- agentpool/resource_providers/pool.py +2 -0
- agentpool/resource_providers/static.py +2 -0
- agentpool/sessions/__init__.py +2 -1
- agentpool/sessions/manager.py +31 -2
- agentpool/sessions/models.py +50 -0
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +217 -1
- agentpool/testing.py +537 -19
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +690 -1
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +4 -0
- agentpool_cli/serve_acp.py +41 -20
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_commands/__init__.py +30 -0
- agentpool_commands/agents.py +74 -1
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +51 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/mcp_server.py +131 -1
- agentpool_config/storage.py +46 -1
- agentpool_config/tools.py +7 -1
- agentpool_config/toolsets.py +92 -148
- agentpool_server/acp_server/acp_agent.py +134 -150
- agentpool_server/acp_server/commands/acp_commands.py +216 -51
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
- agentpool_server/acp_server/server.py +23 -79
- agentpool_server/acp_server/session.py +181 -19
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +362 -0
- agentpool_server/opencode_server/__init__.py +27 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +869 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +269 -0
- agentpool_server/opencode_server/models/__init__.py +228 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +60 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +647 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +25 -0
- agentpool_server/opencode_server/models/message.py +162 -0
- agentpool_server/opencode_server/models/parts.py +190 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/session.py +99 -0
- agentpool_server/opencode_server/routes/__init__.py +25 -0
- agentpool_server/opencode_server/routes/agent_routes.py +442 -0
- agentpool_server/opencode_server/routes/app_routes.py +139 -0
- agentpool_server/opencode_server/routes/config_routes.py +241 -0
- agentpool_server/opencode_server/routes/file_routes.py +392 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +705 -0
- agentpool_server/opencode_server/routes/pty_routes.py +299 -0
- agentpool_server/opencode_server/routes/session_routes.py +1205 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +430 -0
- agentpool_server/opencode_server/state.py +121 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +16 -0
- agentpool_storage/base.py +103 -0
- agentpool_storage/claude_provider.py +907 -0
- agentpool_storage/file_provider.py +129 -0
- agentpool_storage/memory_provider.py +61 -0
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider.py +730 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +6 -0
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +134 -1
- agentpool_storage/sql_provider/utils.py +10 -1
- agentpool_storage/text_log_provider.py +1 -0
- agentpool_toolsets/builtin/__init__.py +0 -8
- agentpool_toolsets/builtin/code.py +95 -56
- agentpool_toolsets/builtin/debug.py +16 -21
- agentpool_toolsets/builtin/execution_environment.py +99 -103
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +86 -4
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +74 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +454 -0
- agentpool_toolsets/mcp_run_toolset.py +84 -6
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
- {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
|