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.
- acp/__init__.py +0 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +18 -49
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from typing import TYPE_CHECKING, Any
|
|
6
7
|
import uuid
|
|
7
8
|
|
|
9
|
+
import anyenv
|
|
8
10
|
from pydantic_ai import (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
AudioUrl,
|
|
12
|
+
BinaryContent,
|
|
13
|
+
DocumentUrl,
|
|
14
|
+
ImageUrl,
|
|
13
15
|
ModelRequest,
|
|
14
16
|
ModelResponse,
|
|
17
|
+
RetryPromptPart,
|
|
18
|
+
TextPart as PydanticTextPart,
|
|
19
|
+
ToolCallPart as PydanticToolCallPart,
|
|
15
20
|
ToolReturnPart as PydanticToolReturnPart,
|
|
16
21
|
UserPromptPart,
|
|
22
|
+
VideoUrl,
|
|
17
23
|
)
|
|
18
24
|
|
|
19
25
|
from agentpool_server.opencode_server.models import (
|
|
@@ -37,6 +43,8 @@ from agentpool_server.opencode_server.models import (
|
|
|
37
43
|
from agentpool_server.opencode_server.models.common import TimeCreated
|
|
38
44
|
from agentpool_server.opencode_server.models.message import UserMessageModel
|
|
39
45
|
from agentpool_server.opencode_server.models.parts import (
|
|
46
|
+
APIErrorInfo,
|
|
47
|
+
RetryPart,
|
|
40
48
|
StepFinishPart,
|
|
41
49
|
StepFinishTokens,
|
|
42
50
|
StepStartPart,
|
|
@@ -49,9 +57,7 @@ from agentpool_server.opencode_server.time_utils import now_ms
|
|
|
49
57
|
if TYPE_CHECKING:
|
|
50
58
|
from collections.abc import Sequence
|
|
51
59
|
|
|
52
|
-
from pydantic_ai import
|
|
53
|
-
UserContent,
|
|
54
|
-
)
|
|
60
|
+
from pydantic_ai import UserContent
|
|
55
61
|
|
|
56
62
|
from agentpool.agents.events import (
|
|
57
63
|
ToolCallCompleteEvent,
|
|
@@ -62,6 +68,8 @@ if TYPE_CHECKING:
|
|
|
62
68
|
from agentpool_server.opencode_server.models import Part
|
|
63
69
|
|
|
64
70
|
|
|
71
|
+
logger = logging.getLogger(__name__)
|
|
72
|
+
|
|
65
73
|
# Parameter name mapping from snake_case to camelCase for OpenCode TUI compatibility
|
|
66
74
|
_PARAM_NAME_MAP: dict[str, str] = {
|
|
67
75
|
"path": "filePath",
|
|
@@ -175,6 +183,7 @@ def convert_pydantic_tool_return_part(
|
|
|
175
183
|
title=f"Completed {part.tool_name}",
|
|
176
184
|
input=existing_input,
|
|
177
185
|
output=output,
|
|
186
|
+
metadata=part.metadata or {}, # Extract metadata from ToolReturnPart
|
|
178
187
|
time=TimeStartEndCompacted(start=now_ms() - 1000, end=now_ms()),
|
|
179
188
|
)
|
|
180
189
|
|
|
@@ -287,6 +296,7 @@ def convert_tool_complete_event(
|
|
|
287
296
|
title=f"Completed {existing_part.tool}",
|
|
288
297
|
input=existing_input,
|
|
289
298
|
output=output,
|
|
299
|
+
metadata=event.metadata or {},
|
|
290
300
|
time=TimeStartEndCompacted(start=now_ms() - 1000, end=now_ms()),
|
|
291
301
|
)
|
|
292
302
|
|
|
@@ -320,14 +330,6 @@ def _convert_file_part_to_user_content(part: dict[str, Any]) -> Any:
|
|
|
320
330
|
Returns:
|
|
321
331
|
Appropriate pydantic-ai content type
|
|
322
332
|
"""
|
|
323
|
-
from pydantic_ai.messages import (
|
|
324
|
-
AudioUrl,
|
|
325
|
-
BinaryContent,
|
|
326
|
-
DocumentUrl,
|
|
327
|
-
ImageUrl,
|
|
328
|
-
VideoUrl,
|
|
329
|
-
)
|
|
330
|
-
|
|
331
333
|
mime = part.get("mime", "")
|
|
332
334
|
url = part.get("url", "")
|
|
333
335
|
|
|
@@ -378,6 +380,73 @@ def extract_user_prompt_from_parts(
|
|
|
378
380
|
content = _convert_file_part_to_user_content(part)
|
|
379
381
|
result.append(content)
|
|
380
382
|
|
|
383
|
+
elif part_type == "agent":
|
|
384
|
+
# Agent mention - inject instruction to delegate to sub-agent
|
|
385
|
+
# This mirrors OpenCode's server-side behavior: inject a synthetic
|
|
386
|
+
# text instruction telling the LLM to call the task tool
|
|
387
|
+
agent_name = part.get("name", "")
|
|
388
|
+
if agent_name:
|
|
389
|
+
# TODO: Implement proper agent delegation via task tool
|
|
390
|
+
# For now, we add the instruction as text that the LLM will see
|
|
391
|
+
instruction = (
|
|
392
|
+
f"Use the above message and context to generate a prompt "
|
|
393
|
+
f"and call the task tool with subagent: {agent_name}"
|
|
394
|
+
)
|
|
395
|
+
result.append(instruction)
|
|
396
|
+
|
|
397
|
+
elif part_type == "snapshot":
|
|
398
|
+
# File system snapshot reference
|
|
399
|
+
# TODO: Implement snapshot restoration/reference
|
|
400
|
+
snapshot_id = part.get("snapshot", "")
|
|
401
|
+
logger.debug("Ignoring snapshot part: %s", snapshot_id)
|
|
402
|
+
|
|
403
|
+
elif part_type == "patch":
|
|
404
|
+
# Diff/patch content
|
|
405
|
+
# TODO: Implement patch application
|
|
406
|
+
patch_hash = part.get("hash", "")
|
|
407
|
+
files = part.get("files", [])
|
|
408
|
+
logger.debug("Ignoring patch part: hash=%s, files=%s", patch_hash, files)
|
|
409
|
+
|
|
410
|
+
elif part_type == "reasoning":
|
|
411
|
+
# Extended thinking/reasoning content from the model
|
|
412
|
+
# Include as text context since it contains useful reasoning
|
|
413
|
+
reasoning_text = part.get("text", "")
|
|
414
|
+
if reasoning_text:
|
|
415
|
+
result.append(f"[Reasoning]: {reasoning_text}")
|
|
416
|
+
|
|
417
|
+
elif part_type == "compaction":
|
|
418
|
+
# Marks where conversation was compacted
|
|
419
|
+
# TODO: Handle compaction markers for context management
|
|
420
|
+
auto = part.get("auto", False)
|
|
421
|
+
logger.debug("Ignoring compaction part: auto=%s", auto)
|
|
422
|
+
|
|
423
|
+
elif part_type == "subtask":
|
|
424
|
+
# References a spawned subtask
|
|
425
|
+
# TODO: Implement subtask tracking/results
|
|
426
|
+
subtask_agent = part.get("agent", "")
|
|
427
|
+
subtask_desc = part.get("description", "")
|
|
428
|
+
logger.debug(
|
|
429
|
+
"Ignoring subtask part: agent=%s, description=%s", subtask_agent, subtask_desc
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
elif part_type == "retry":
|
|
433
|
+
# Marks a retry of a failed operation
|
|
434
|
+
# TODO: Handle retry tracking
|
|
435
|
+
attempt = part.get("attempt", 0)
|
|
436
|
+
logger.debug("Ignoring retry part: attempt=%s", attempt)
|
|
437
|
+
|
|
438
|
+
elif part_type == "step-start":
|
|
439
|
+
# Step start marker - informational only
|
|
440
|
+
logger.debug("Ignoring step-start part")
|
|
441
|
+
|
|
442
|
+
elif part_type == "step-finish":
|
|
443
|
+
# Step finish marker - informational only
|
|
444
|
+
logger.debug("Ignoring step-finish part")
|
|
445
|
+
|
|
446
|
+
else:
|
|
447
|
+
# Unknown part type
|
|
448
|
+
logger.warning("Unknown part type: %s", part_type)
|
|
449
|
+
|
|
381
450
|
# If only text parts, join them as a single string for simplicity
|
|
382
451
|
if all(isinstance(item, str) for item in result):
|
|
383
452
|
return "\n".join(result) # type: ignore[arg-type]
|
|
@@ -595,9 +664,52 @@ def chat_message_to_opencode( # noqa: PLR0915
|
|
|
595
664
|
parts.append(tool_part)
|
|
596
665
|
|
|
597
666
|
elif isinstance(model_msg, ModelRequest):
|
|
598
|
-
# Check for tool returns in requests (they come after responses)
|
|
667
|
+
# Check for tool returns and retries in requests (they come after responses)
|
|
599
668
|
for part in model_msg.parts:
|
|
600
|
-
if isinstance(part,
|
|
669
|
+
if isinstance(part, RetryPromptPart):
|
|
670
|
+
# Track retry attempts - count RetryPromptParts in message history
|
|
671
|
+
retry_count = sum(
|
|
672
|
+
1
|
|
673
|
+
for m in msg.messages
|
|
674
|
+
if isinstance(m, ModelRequest)
|
|
675
|
+
for p in m.parts
|
|
676
|
+
if isinstance(p, RetryPromptPart)
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Create error info from retry content
|
|
680
|
+
error_message = part.model_response()
|
|
681
|
+
|
|
682
|
+
# Try to extract more info if we have structured error details
|
|
683
|
+
is_retryable = True
|
|
684
|
+
if isinstance(part.content, list):
|
|
685
|
+
# Validation errors - always retryable
|
|
686
|
+
error_type = "validation_error"
|
|
687
|
+
elif part.tool_name:
|
|
688
|
+
# Tool-related retry
|
|
689
|
+
error_type = "tool_error"
|
|
690
|
+
else:
|
|
691
|
+
# Generic retry
|
|
692
|
+
error_type = "retry"
|
|
693
|
+
|
|
694
|
+
api_error = APIErrorInfo(
|
|
695
|
+
message=error_message,
|
|
696
|
+
status_code=None, # Not available from pydantic-ai
|
|
697
|
+
is_retryable=is_retryable,
|
|
698
|
+
metadata={"error_type": error_type} if error_type else None,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
parts.append(
|
|
702
|
+
RetryPart(
|
|
703
|
+
id=generate_part_id(),
|
|
704
|
+
message_id=message_id,
|
|
705
|
+
session_id=session_id,
|
|
706
|
+
attempt=retry_count,
|
|
707
|
+
error=api_error,
|
|
708
|
+
time=TimeCreated(created=int(part.timestamp.timestamp() * 1000)),
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
elif isinstance(part, PydanticToolReturnPart):
|
|
601
713
|
call_id = part.tool_call_id or ""
|
|
602
714
|
existing = tool_calls.get(call_id)
|
|
603
715
|
|
|
@@ -606,19 +718,13 @@ def chat_message_to_opencode( # noqa: PLR0915
|
|
|
606
718
|
if isinstance(content, str):
|
|
607
719
|
output = content
|
|
608
720
|
elif isinstance(content, dict):
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
output = json.dumps(content, indent=2)
|
|
721
|
+
output = anyenv.dump_json(content, indent=True)
|
|
612
722
|
else:
|
|
613
723
|
output = str(content) if content is not None else ""
|
|
614
|
-
|
|
615
|
-
# Check for error
|
|
616
|
-
is_error = isinstance(content, dict) and "error" in content
|
|
617
|
-
|
|
618
724
|
if existing:
|
|
619
725
|
# Update existing tool part with completion
|
|
620
726
|
existing_input = _get_input_from_state(existing.state)
|
|
621
|
-
if
|
|
727
|
+
if isinstance(content, dict) and "error" in content:
|
|
622
728
|
existing.state = ToolStateError(
|
|
623
729
|
status="error",
|
|
624
730
|
error=str(content.get("error", "Unknown error")),
|
|
@@ -636,7 +742,7 @@ def chat_message_to_opencode( # noqa: PLR0915
|
|
|
636
742
|
else:
|
|
637
743
|
# Orphan return - create completed tool part
|
|
638
744
|
state: ToolStateCompleted | ToolStateError
|
|
639
|
-
if
|
|
745
|
+
if isinstance(content, dict) and "error" in content:
|
|
640
746
|
state = ToolStateError(
|
|
641
747
|
status="error",
|
|
642
748
|
error=str(content.get("error", "Unknown error")),
|
|
@@ -14,9 +14,8 @@ from agentpool_server.opencode_server.models.events import PermissionRequestEven
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from agentpool.agents.context import ConfirmationResult
|
|
17
|
+
from agentpool.agents.context import AgentContext, ConfirmationResult
|
|
18
18
|
from agentpool.messaging import ChatMessage
|
|
19
|
-
from agentpool.messaging.context import NodeContext
|
|
20
19
|
from agentpool.tools.base import Tool
|
|
21
20
|
from agentpool_server.opencode_server.state import ServerState
|
|
22
21
|
|
|
@@ -68,7 +67,7 @@ class OpenCodeInputProvider(InputProvider):
|
|
|
68
67
|
|
|
69
68
|
async def get_tool_confirmation(
|
|
70
69
|
self,
|
|
71
|
-
context:
|
|
70
|
+
context: AgentContext[Any],
|
|
72
71
|
tool: Tool,
|
|
73
72
|
args: dict[str, Any],
|
|
74
73
|
message_history: list[ChatMessage[Any]] | None = None,
|
|
@@ -217,11 +216,10 @@ class OpenCodeInputProvider(InputProvider):
|
|
|
217
216
|
self,
|
|
218
217
|
params: types.ElicitRequestParams,
|
|
219
218
|
) -> types.ElicitResult | types.ErrorData:
|
|
220
|
-
"""Get user response to elicitation request.
|
|
219
|
+
"""Get user response to elicitation request via OpenCode questions.
|
|
221
220
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
for more complex elicitation flows.
|
|
221
|
+
Translates MCP elicitation requests to OpenCode question system.
|
|
222
|
+
Supports both single-select and multi-select questions.
|
|
225
223
|
|
|
226
224
|
Args:
|
|
227
225
|
params: MCP elicit request parameters
|
|
@@ -239,7 +237,22 @@ class OpenCodeInputProvider(InputProvider):
|
|
|
239
237
|
# Could potentially open URL in browser here
|
|
240
238
|
return types.ElicitResult(action="decline")
|
|
241
239
|
|
|
242
|
-
# For form elicitation
|
|
240
|
+
# For form elicitation with enum schema, use OpenCode questions
|
|
241
|
+
if isinstance(params, types.ElicitRequestFormParams):
|
|
242
|
+
schema = params.requestedSchema
|
|
243
|
+
|
|
244
|
+
# Check if schema defines options (enum)
|
|
245
|
+
enum_values = schema.get("enum")
|
|
246
|
+
if enum_values:
|
|
247
|
+
return await self._handle_question_elicitation(params, schema)
|
|
248
|
+
|
|
249
|
+
# Check if it's an array schema with enum items
|
|
250
|
+
if schema.get("type") == "array":
|
|
251
|
+
items = schema.get("items", {})
|
|
252
|
+
if items.get("enum"):
|
|
253
|
+
return await self._handle_question_elicitation(params, schema)
|
|
254
|
+
|
|
255
|
+
# For other form elicitation, we don't have UI support yet
|
|
243
256
|
logger.info(
|
|
244
257
|
"Form elicitation request (not supported)",
|
|
245
258
|
message=params.message,
|
|
@@ -247,12 +260,151 @@ class OpenCodeInputProvider(InputProvider):
|
|
|
247
260
|
)
|
|
248
261
|
return types.ElicitResult(action="decline")
|
|
249
262
|
|
|
263
|
+
async def _handle_question_elicitation(
|
|
264
|
+
self,
|
|
265
|
+
params: types.ElicitRequestFormParams,
|
|
266
|
+
schema: dict[str, Any],
|
|
267
|
+
) -> types.ElicitResult | types.ErrorData:
|
|
268
|
+
"""Handle elicitation via OpenCode question system.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
params: Form elicitation parameters
|
|
272
|
+
schema: JSON schema with enum values
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Elicit result with user's answer
|
|
276
|
+
"""
|
|
277
|
+
import asyncio
|
|
278
|
+
|
|
279
|
+
from agentpool_server.opencode_server.models.events import QuestionAskedEvent
|
|
280
|
+
|
|
281
|
+
# Extract enum values
|
|
282
|
+
is_multi = schema.get("type") == "array"
|
|
283
|
+
if is_multi:
|
|
284
|
+
enum_values = schema.get("items", {}).get("enum", [])
|
|
285
|
+
else:
|
|
286
|
+
enum_values = schema.get("enum", [])
|
|
287
|
+
|
|
288
|
+
if not enum_values:
|
|
289
|
+
return types.ElicitResult(action="decline")
|
|
290
|
+
|
|
291
|
+
# Extract descriptions if available (custom x-option-descriptions field)
|
|
292
|
+
descriptions = schema.get("x-option-descriptions", {})
|
|
293
|
+
|
|
294
|
+
# Build OpenCode question format
|
|
295
|
+
from agentpool_server.opencode_server.models.question import (
|
|
296
|
+
QuestionInfo,
|
|
297
|
+
QuestionOption,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
question_id = self._generate_permission_id() # Reuse ID generator
|
|
301
|
+
question_info = QuestionInfo(
|
|
302
|
+
question=params.message,
|
|
303
|
+
header=params.message[:12], # Truncate to 12 chars
|
|
304
|
+
options=[
|
|
305
|
+
QuestionOption(
|
|
306
|
+
label=str(val),
|
|
307
|
+
description=descriptions.get(str(val), ""),
|
|
308
|
+
)
|
|
309
|
+
for val in enum_values
|
|
310
|
+
],
|
|
311
|
+
multiple=is_multi or None,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Create future to wait for answer
|
|
315
|
+
future: asyncio.Future[list[list[str]]] = asyncio.get_event_loop().create_future()
|
|
316
|
+
|
|
317
|
+
# Store pending question
|
|
318
|
+
from agentpool_server.opencode_server.state import PendingQuestion
|
|
319
|
+
|
|
320
|
+
self.state.pending_questions[question_id] = PendingQuestion(
|
|
321
|
+
session_id=self.session_id,
|
|
322
|
+
questions=[question_info],
|
|
323
|
+
future=future,
|
|
324
|
+
tool=None, # Not associated with a specific tool call
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Broadcast event (serialize QuestionInfo to dict)
|
|
328
|
+
event = QuestionAskedEvent.create(
|
|
329
|
+
request_id=question_id,
|
|
330
|
+
session_id=self.session_id,
|
|
331
|
+
questions=[question_info.model_dump(mode="json", by_alias=True)],
|
|
332
|
+
)
|
|
333
|
+
await self.state.broadcast_event(event)
|
|
334
|
+
|
|
335
|
+
logger.info(
|
|
336
|
+
"Question asked",
|
|
337
|
+
question_id=question_id,
|
|
338
|
+
message=params.message,
|
|
339
|
+
is_multi=is_multi,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Wait for answer
|
|
343
|
+
try:
|
|
344
|
+
answers = await future # list[list[str]]
|
|
345
|
+
answer = answers[0] if answers else [] # Get first question's answer
|
|
346
|
+
|
|
347
|
+
# ElicitResult content must be a dict, not a plain value
|
|
348
|
+
# Wrap the answer in a dict with a "value" key
|
|
349
|
+
# Multi-select: return list in dict
|
|
350
|
+
# Single-select: return string in dict
|
|
351
|
+
content: dict[str, str | list[str]] = (
|
|
352
|
+
{"value": answer} if is_multi else {"value": answer[0] if answer else ""}
|
|
353
|
+
)
|
|
354
|
+
return types.ElicitResult(action="accept", content=content)
|
|
355
|
+
except asyncio.CancelledError:
|
|
356
|
+
logger.info("Question cancelled", question_id=question_id)
|
|
357
|
+
return types.ElicitResult(action="cancel")
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.exception("Question failed", question_id=question_id)
|
|
360
|
+
return types.ErrorData(
|
|
361
|
+
code=-1, # Generic error code
|
|
362
|
+
message=f"Elicitation failed: {e}",
|
|
363
|
+
)
|
|
364
|
+
finally:
|
|
365
|
+
# Clean up pending question
|
|
366
|
+
self.state.pending_questions.pop(question_id, None)
|
|
367
|
+
|
|
250
368
|
def clear_tool_approvals(self) -> None:
|
|
251
369
|
"""Clear all stored tool approval decisions."""
|
|
252
370
|
approval_count = len(self._tool_approvals)
|
|
253
371
|
self._tool_approvals.clear()
|
|
254
372
|
logger.info("Cleared tool approval decisions", count=approval_count)
|
|
255
373
|
|
|
374
|
+
def resolve_question(
|
|
375
|
+
self,
|
|
376
|
+
question_id: str,
|
|
377
|
+
answers: list[list[str]],
|
|
378
|
+
) -> bool:
|
|
379
|
+
"""Resolve a pending question request.
|
|
380
|
+
|
|
381
|
+
Called by the REST endpoint when the client responds.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
question_id: The question request ID
|
|
385
|
+
answers: User's answers (array of arrays per OpenCode format)
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
True if the question was found and resolved, False otherwise
|
|
389
|
+
"""
|
|
390
|
+
pending = self.state.pending_questions.get(question_id)
|
|
391
|
+
if pending is None:
|
|
392
|
+
logger.warning("Question not found", question_id=question_id)
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
future = pending.future
|
|
396
|
+
if future.done():
|
|
397
|
+
logger.warning("Question already resolved", question_id=question_id)
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
future.set_result(answers)
|
|
401
|
+
logger.info(
|
|
402
|
+
"Question resolved",
|
|
403
|
+
question_id=question_id,
|
|
404
|
+
answers=answers,
|
|
405
|
+
)
|
|
406
|
+
return True
|
|
407
|
+
|
|
256
408
|
def cancel_all_pending(self) -> int:
|
|
257
409
|
"""Cancel all pending permission requests.
|
|
258
410
|
|
|
@@ -17,6 +17,7 @@ from agentpool_server.opencode_server.models.app import (
|
|
|
17
17
|
PathInfo,
|
|
18
18
|
Project,
|
|
19
19
|
ProjectTime,
|
|
20
|
+
ProjectUpdateRequest,
|
|
20
21
|
VcsInfo,
|
|
21
22
|
)
|
|
22
23
|
from agentpool_server.opencode_server.models.provider import (
|
|
@@ -37,6 +38,7 @@ from agentpool_server.opencode_server.models.session import (
|
|
|
37
38
|
SessionRevert,
|
|
38
39
|
SessionShare,
|
|
39
40
|
SessionStatus,
|
|
41
|
+
SessionSummary,
|
|
40
42
|
SessionUpdateRequest,
|
|
41
43
|
SummarizeRequest,
|
|
42
44
|
Todo,
|
|
@@ -45,8 +47,10 @@ from agentpool_server.opencode_server.models.message import (
|
|
|
45
47
|
AssistantMessage,
|
|
46
48
|
CommandRequest,
|
|
47
49
|
FilePartInput,
|
|
50
|
+
MessageInfo,
|
|
48
51
|
MessagePath,
|
|
49
52
|
MessageRequest,
|
|
53
|
+
MessageSummary,
|
|
50
54
|
MessageTime,
|
|
51
55
|
MessageWithParts,
|
|
52
56
|
PartInput,
|
|
@@ -55,12 +59,20 @@ from agentpool_server.opencode_server.models.message import (
|
|
|
55
59
|
Tokens,
|
|
56
60
|
TokensCache,
|
|
57
61
|
UserMessage,
|
|
62
|
+
UserMessageModel,
|
|
58
63
|
)
|
|
59
64
|
from agentpool_server.opencode_server.models.parts import (
|
|
65
|
+
AgentPart,
|
|
66
|
+
CompactionPart,
|
|
60
67
|
FilePart,
|
|
61
68
|
Part,
|
|
69
|
+
PatchPart,
|
|
70
|
+
ReasoningPart,
|
|
71
|
+
RetryPart,
|
|
72
|
+
SnapshotPart,
|
|
62
73
|
StepFinishPart,
|
|
63
74
|
StepStartPart,
|
|
75
|
+
SubtaskPart,
|
|
64
76
|
TextPart,
|
|
65
77
|
TimeStart,
|
|
66
78
|
TimeStartEnd,
|
|
@@ -92,10 +104,13 @@ from agentpool_server.opencode_server.models.pty import (
|
|
|
92
104
|
)
|
|
93
105
|
from agentpool_server.opencode_server.models.events import (
|
|
94
106
|
Event,
|
|
107
|
+
MessageRemovedEvent,
|
|
95
108
|
MessageUpdatedEvent,
|
|
96
109
|
MessageUpdatedEventProperties,
|
|
110
|
+
PartRemovedEvent,
|
|
97
111
|
PartUpdatedEvent,
|
|
98
112
|
PartUpdatedEventProperties,
|
|
113
|
+
ProjectUpdatedEvent,
|
|
99
114
|
ServerConnectedEvent,
|
|
100
115
|
SessionCompactedEvent,
|
|
101
116
|
SessionCompactedProperties,
|
|
@@ -112,71 +127,76 @@ from agentpool_server.opencode_server.models.events import (
|
|
|
112
127
|
SessionStatusProperties,
|
|
113
128
|
SessionUpdatedEvent,
|
|
114
129
|
)
|
|
115
|
-
from agentpool_server.opencode_server.models.mcp import
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
from agentpool_server.opencode_server.models.mcp import LogRequest, MCPStatus, McpResource
|
|
131
|
+
from agentpool_server.opencode_server.models.config import Config
|
|
132
|
+
from agentpool_server.opencode_server.models.question import (
|
|
133
|
+
QuestionInfo,
|
|
134
|
+
QuestionOption,
|
|
135
|
+
QuestionReply,
|
|
136
|
+
QuestionRequest,
|
|
121
137
|
)
|
|
122
138
|
|
|
123
139
|
__all__ = [
|
|
124
|
-
# Agent
|
|
125
140
|
"Agent",
|
|
126
|
-
|
|
141
|
+
"AgentPart",
|
|
127
142
|
"App",
|
|
128
143
|
"AppTimeInfo",
|
|
129
|
-
# Message
|
|
130
144
|
"AssistantMessage",
|
|
131
145
|
"Command",
|
|
132
146
|
"CommandRequest",
|
|
133
|
-
|
|
147
|
+
"CompactionPart",
|
|
134
148
|
"Config",
|
|
135
|
-
# Events
|
|
136
149
|
"Event",
|
|
137
|
-
# File
|
|
138
150
|
"FileContent",
|
|
139
151
|
"FileNode",
|
|
140
|
-
# Parts
|
|
141
152
|
"FilePart",
|
|
142
153
|
"FilePartInput",
|
|
143
154
|
"FileStatus",
|
|
144
155
|
"FindMatch",
|
|
145
156
|
"HealthResponse",
|
|
146
|
-
# MCP
|
|
147
157
|
"LogRequest",
|
|
148
158
|
"MCPStatus",
|
|
159
|
+
"McpResource",
|
|
160
|
+
"MessageInfo",
|
|
149
161
|
"MessagePath",
|
|
162
|
+
"MessageRemovedEvent",
|
|
150
163
|
"MessageRequest",
|
|
164
|
+
"MessageSummary",
|
|
151
165
|
"MessageTime",
|
|
152
166
|
"MessageUpdatedEvent",
|
|
153
167
|
"MessageUpdatedEventProperties",
|
|
154
168
|
"MessageWithParts",
|
|
155
169
|
"Mode",
|
|
156
170
|
"ModeModel",
|
|
157
|
-
# Provider
|
|
158
171
|
"Model",
|
|
159
172
|
"ModelCost",
|
|
160
173
|
"ModelLimit",
|
|
161
|
-
# Base
|
|
162
174
|
"OpenCodeBaseModel",
|
|
163
175
|
"Part",
|
|
164
176
|
"PartInput",
|
|
177
|
+
"PartRemovedEvent",
|
|
165
178
|
"PartUpdatedEvent",
|
|
166
179
|
"PartUpdatedEventProperties",
|
|
180
|
+
"PatchPart",
|
|
167
181
|
"PathInfo",
|
|
168
182
|
"Project",
|
|
169
183
|
"ProjectTime",
|
|
184
|
+
"ProjectUpdateRequest",
|
|
185
|
+
"ProjectUpdatedEvent",
|
|
170
186
|
"Provider",
|
|
171
187
|
"ProviderListResponse",
|
|
172
188
|
"ProvidersResponse",
|
|
173
|
-
# PTY
|
|
174
189
|
"PtyCreateRequest",
|
|
175
190
|
"PtyInfo",
|
|
176
191
|
"PtySize",
|
|
177
192
|
"PtyUpdateRequest",
|
|
193
|
+
"QuestionInfo",
|
|
194
|
+
"QuestionOption",
|
|
195
|
+
"QuestionReply",
|
|
196
|
+
"QuestionRequest",
|
|
197
|
+
"ReasoningPart",
|
|
198
|
+
"RetryPart",
|
|
178
199
|
"ServerConnectedEvent",
|
|
179
|
-
# Session
|
|
180
200
|
"Session",
|
|
181
201
|
"SessionCompactedEvent",
|
|
182
202
|
"SessionCompactedProperties",
|
|
@@ -197,19 +217,20 @@ __all__ = [
|
|
|
197
217
|
"SessionStatus",
|
|
198
218
|
"SessionStatusEvent",
|
|
199
219
|
"SessionStatusProperties",
|
|
220
|
+
"SessionSummary",
|
|
200
221
|
"SessionUpdateRequest",
|
|
201
222
|
"SessionUpdatedEvent",
|
|
202
223
|
"ShellRequest",
|
|
224
|
+
"SnapshotPart",
|
|
203
225
|
"StepFinishPart",
|
|
204
226
|
"StepStartPart",
|
|
227
|
+
"SubtaskPart",
|
|
205
228
|
"SummarizeRequest",
|
|
206
229
|
"Symbol",
|
|
207
230
|
"TextPart",
|
|
208
231
|
"TextPartInput",
|
|
209
|
-
# Common
|
|
210
232
|
"TimeCreated",
|
|
211
233
|
"TimeCreatedUpdated",
|
|
212
|
-
# Time types (from parts.py)
|
|
213
234
|
"TimeStart",
|
|
214
235
|
"TimeStartEnd",
|
|
215
236
|
"TimeStartEndCompacted",
|
|
@@ -224,5 +245,6 @@ __all__ = [
|
|
|
224
245
|
"ToolStatePending",
|
|
225
246
|
"ToolStateRunning",
|
|
226
247
|
"UserMessage",
|
|
248
|
+
"UserMessageModel",
|
|
227
249
|
"VcsInfo",
|
|
228
250
|
]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""App, project, and path related models."""
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
3
5
|
from agentpool_server.opencode_server.models.base import OpenCodeBaseModel
|
|
4
6
|
|
|
5
7
|
|
|
@@ -58,3 +60,13 @@ class VcsInfo(OpenCodeBaseModel):
|
|
|
58
60
|
branch: str | None = None
|
|
59
61
|
dirty: bool = False
|
|
60
62
|
commit: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ProjectUpdateRequest(OpenCodeBaseModel):
|
|
66
|
+
"""Request to update project metadata."""
|
|
67
|
+
|
|
68
|
+
name: str | None = None
|
|
69
|
+
"""Optional friendly name for the project."""
|
|
70
|
+
|
|
71
|
+
settings: dict[str, Any] | None = None
|
|
72
|
+
"""Optional project-specific settings to update."""
|