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
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
"""Text-based storage provider with dynamic paths."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar
|
|
6
|
-
|
|
7
|
-
from upathtools import to_upath
|
|
8
|
-
|
|
9
|
-
from agentpool.log import get_logger
|
|
10
|
-
from agentpool.utils.now import get_now
|
|
11
|
-
from agentpool_storage.base import StorageProvider
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
|
|
17
|
-
from jinja2 import Template
|
|
18
|
-
from upathtools import JoinablePathLike, UPath
|
|
19
|
-
|
|
20
|
-
from agentpool.common_types import JsonValue
|
|
21
|
-
from agentpool_config.storage import LogFormat, TextLogConfig
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
logger = get_logger(__name__)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
CONVERSATIONS_TEMPLATE = """\
|
|
28
|
-
=== AgentPool Log ===
|
|
29
|
-
|
|
30
|
-
{%- for conv_id, conv in conversations.items() %}
|
|
31
|
-
=== Conversation {{ conv_id }} (agent: {{ conv.agent_name }}, started: {{ conv.start_time.strftime('%Y-%m-%d %H:%M:%S') }}) ===
|
|
32
|
-
|
|
33
|
-
{%- for msg in messages if msg.conversation_id == conv_id %}
|
|
34
|
-
[{{ msg.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ msg.sender }}{% if msg.model %} ({{ msg.model }}){% endif %}: {{ msg.content }}
|
|
35
|
-
{%- if msg.cost_info %}
|
|
36
|
-
Tokens: {{ msg.cost_info.token_usage.total }} (prompt: {{ msg.cost_info.token_usage.prompt }}, completion: {{ msg.cost_info.token_usage.completion }})
|
|
37
|
-
Cost: ${{ "%.4f"|format(msg.cost_info.total_cost) }}
|
|
38
|
-
{%- endif %}
|
|
39
|
-
{%- if msg.response_time %}
|
|
40
|
-
Response time: {{ "%.1f"|format(msg.response_time) }}s
|
|
41
|
-
{%- endif %}
|
|
42
|
-
{%- if msg.forwarded_from %}
|
|
43
|
-
Forwarded via: {{ msg.forwarded_from|join(' -> ') }}
|
|
44
|
-
{%- endif %}
|
|
45
|
-
|
|
46
|
-
{%- for tool in tool_calls if tool.message_id == msg.id %}
|
|
47
|
-
Tool Call: {{ tool.tool_name }}
|
|
48
|
-
Args: {{ tool.args|pprint }}
|
|
49
|
-
Result: {{ tool.result }}
|
|
50
|
-
{%- endfor %}
|
|
51
|
-
{%- endfor %}
|
|
52
|
-
{%- endfor %}
|
|
53
|
-
|
|
54
|
-
=== Commands ===
|
|
55
|
-
{%- for cmd in commands %}
|
|
56
|
-
[{{ cmd.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ cmd.agent_name }} ({{ cmd.session_id }}): {{ cmd.command }}
|
|
57
|
-
{%- endfor %}
|
|
58
|
-
""" # noqa: E501
|
|
59
|
-
|
|
60
|
-
CHRONOLOGICAL_TEMPLATE = """\
|
|
61
|
-
=== AgentPool Log ===
|
|
62
|
-
|
|
63
|
-
{%- for entry in entries|sort(attribute="timestamp") %}
|
|
64
|
-
{%- if entry.type == "conversation_start" %}
|
|
65
|
-
=== Conversation {{ entry.conversation_id }} (agent: {{ entry.agent_name }}) started ===
|
|
66
|
-
|
|
67
|
-
{%- elif entry.type == "message" %}
|
|
68
|
-
[{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ entry.sender }}{% if entry.model %} ({{ entry.model }}){% endif %}: {{ entry.content }}
|
|
69
|
-
{%- if entry.cost_info %}
|
|
70
|
-
Tokens: {{ entry.cost_info.token_usage.total }} (prompt: {{ entry.cost_info.token_usage.prompt }}, completion: {{ entry.cost_info.token_usage.completion }})
|
|
71
|
-
Cost: ${{ "%.4f"|format(entry.cost_info.total_cost) }}
|
|
72
|
-
{%- endif %}
|
|
73
|
-
{%- if entry.response_time %}
|
|
74
|
-
Response time: {{ "%.1f"|format(entry.response_time) }}s
|
|
75
|
-
{%- endif %}
|
|
76
|
-
{%- if entry.forwarded_from %}
|
|
77
|
-
Forwarded via: {{ entry.forwarded_from|join(' -> ') }}
|
|
78
|
-
{%- endif %}
|
|
79
|
-
|
|
80
|
-
{%- elif entry.type == "tool_call" %}
|
|
81
|
-
[{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Tool Call: {{ entry.tool_name }}
|
|
82
|
-
Args: {{ entry.args|pprint }}
|
|
83
|
-
Result: {{ entry.result }}
|
|
84
|
-
|
|
85
|
-
{%- elif entry.type == "command" %}
|
|
86
|
-
[{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Command by {{ entry.agent_name }}: {{ entry.command }}
|
|
87
|
-
|
|
88
|
-
{%- endif %}
|
|
89
|
-
{%- endfor %}
|
|
90
|
-
""" # noqa: E501
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class TextLogProvider(StorageProvider):
|
|
94
|
-
"""Human-readable text log provider with dynamic paths.
|
|
95
|
-
|
|
96
|
-
Available template variables:
|
|
97
|
-
- now: datetime - Current timestamp
|
|
98
|
-
- date: date - Current date
|
|
99
|
-
- operation: str - Type of operation (message/conversation/tool_call/command)
|
|
100
|
-
- conversation_id: str - ID of current conversation
|
|
101
|
-
- agent_name: str - Name of the agent
|
|
102
|
-
- content: str - Message content
|
|
103
|
-
- role: str - Message role
|
|
104
|
-
- model: str - Model name
|
|
105
|
-
- session_id: str - Session ID
|
|
106
|
-
- tool_name: str - Name of tool being called
|
|
107
|
-
- command: str - Command being executed
|
|
108
|
-
|
|
109
|
-
All variables default to empty string if not available for current operation.
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
TEMPLATES: ClassVar[dict[LogFormat, str]] = {
|
|
113
|
-
"chronological": CHRONOLOGICAL_TEMPLATE,
|
|
114
|
-
"conversations": CONVERSATIONS_TEMPLATE,
|
|
115
|
-
}
|
|
116
|
-
can_load_history = False
|
|
117
|
-
|
|
118
|
-
def __init__(self, config: TextLogConfig) -> None:
|
|
119
|
-
"""Initialize text log provider."""
|
|
120
|
-
from jinja2 import Environment, Undefined
|
|
121
|
-
|
|
122
|
-
class EmptyStringUndefined(Undefined):
|
|
123
|
-
"""Return empty string for undefined variables."""
|
|
124
|
-
|
|
125
|
-
def __str__(self) -> str:
|
|
126
|
-
return ""
|
|
127
|
-
|
|
128
|
-
super().__init__(config)
|
|
129
|
-
self.encoding = config.encoding
|
|
130
|
-
self.content_template = self._load_template(config.template)
|
|
131
|
-
# Configure Jinja env with empty string for undefined
|
|
132
|
-
env = Environment(undefined=EmptyStringUndefined, enable_async=True)
|
|
133
|
-
self.path_template = env.from_string(config.path)
|
|
134
|
-
|
|
135
|
-
self._entries: list[dict[str, Any]] = []
|
|
136
|
-
|
|
137
|
-
def _load_template(
|
|
138
|
-
self,
|
|
139
|
-
template: LogFormat | JoinablePathLike | None,
|
|
140
|
-
) -> Template:
|
|
141
|
-
"""Load template from predefined or file."""
|
|
142
|
-
from jinja2 import Template
|
|
143
|
-
|
|
144
|
-
if template is None:
|
|
145
|
-
template_str = self.TEMPLATES["chronological"]
|
|
146
|
-
elif template in self.TEMPLATES:
|
|
147
|
-
template_str = self.TEMPLATES[template] # type: ignore
|
|
148
|
-
else: # Assume it's a path
|
|
149
|
-
template_str = to_upath(template).read_text()
|
|
150
|
-
return Template(template_str)
|
|
151
|
-
|
|
152
|
-
def _get_base_context(self, operation: str) -> dict[str, Any]:
|
|
153
|
-
"""Get base context with defaults.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
operation: Type of operation being logged
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
Base context dict with defaults
|
|
160
|
-
"""
|
|
161
|
-
# All other variables will default to empty string via EmptyStringUndefined
|
|
162
|
-
return {"now": get_now(), "date": get_now().date(), "operation": operation}
|
|
163
|
-
|
|
164
|
-
async def _get_path(self, operation: str, **context: Any) -> UPath:
|
|
165
|
-
"""Render path template with context.
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
operation: Type of operation being logged
|
|
169
|
-
**context: Additional context variables
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
Concrete path for current operation
|
|
173
|
-
"""
|
|
174
|
-
# Combine base context with provided values
|
|
175
|
-
path_context = self._get_base_context(operation)
|
|
176
|
-
path_context.update(context)
|
|
177
|
-
|
|
178
|
-
path = await self.path_template.render_async(**path_context)
|
|
179
|
-
resolved_path = to_upath(path)
|
|
180
|
-
resolved_path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
-
return resolved_path
|
|
182
|
-
|
|
183
|
-
async def log_message(
|
|
184
|
-
self,
|
|
185
|
-
*,
|
|
186
|
-
conversation_id: str,
|
|
187
|
-
message_id: str,
|
|
188
|
-
content: str,
|
|
189
|
-
role: str,
|
|
190
|
-
name: str | None = None,
|
|
191
|
-
cost_info: Any | None = None,
|
|
192
|
-
model: str | None = None,
|
|
193
|
-
response_time: float | None = None,
|
|
194
|
-
forwarded_from: list[str] | None = None,
|
|
195
|
-
provider_name: str | None = None,
|
|
196
|
-
provider_response_id: str | None = None,
|
|
197
|
-
messages: str | None = None,
|
|
198
|
-
finish_reason: str | None = None,
|
|
199
|
-
parent_id: str | None = None,
|
|
200
|
-
) -> None:
|
|
201
|
-
"""Store message and update log."""
|
|
202
|
-
entry = {
|
|
203
|
-
"type": "message",
|
|
204
|
-
"timestamp": get_now(),
|
|
205
|
-
"conversation_id": conversation_id,
|
|
206
|
-
"message_id": message_id,
|
|
207
|
-
"content": content,
|
|
208
|
-
"role": role,
|
|
209
|
-
"agent_name": name,
|
|
210
|
-
"model": model,
|
|
211
|
-
"cost_info": cost_info,
|
|
212
|
-
"response_time": response_time,
|
|
213
|
-
"forwarded_from": forwarded_from,
|
|
214
|
-
"provider_name": provider_name,
|
|
215
|
-
"provider_response_id": provider_response_id,
|
|
216
|
-
"messages": messages,
|
|
217
|
-
"finish_reason": finish_reason,
|
|
218
|
-
}
|
|
219
|
-
self._entries.append(entry)
|
|
220
|
-
|
|
221
|
-
path = await self._get_path("message", **entry)
|
|
222
|
-
self._write(path)
|
|
223
|
-
|
|
224
|
-
async def log_conversation(
|
|
225
|
-
self,
|
|
226
|
-
*,
|
|
227
|
-
conversation_id: str,
|
|
228
|
-
node_name: str,
|
|
229
|
-
start_time: datetime | None = None,
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Store conversation start."""
|
|
232
|
-
entry = {
|
|
233
|
-
"type": "conversation",
|
|
234
|
-
"timestamp": start_time or get_now(),
|
|
235
|
-
"conversation_id": conversation_id,
|
|
236
|
-
"agent_name": node_name,
|
|
237
|
-
}
|
|
238
|
-
self._entries.append(entry)
|
|
239
|
-
|
|
240
|
-
path = await self._get_path("conversation", **entry)
|
|
241
|
-
self._write(path)
|
|
242
|
-
|
|
243
|
-
async def log_command(
|
|
244
|
-
self,
|
|
245
|
-
*,
|
|
246
|
-
agent_name: str,
|
|
247
|
-
session_id: str,
|
|
248
|
-
command: str,
|
|
249
|
-
context_type: type | None = None,
|
|
250
|
-
metadata: dict[str, JsonValue] | None = None,
|
|
251
|
-
) -> None:
|
|
252
|
-
"""Store command."""
|
|
253
|
-
entry = {
|
|
254
|
-
"type": "command",
|
|
255
|
-
"timestamp": get_now(),
|
|
256
|
-
"agent_name": agent_name,
|
|
257
|
-
"session_id": session_id,
|
|
258
|
-
"command": command,
|
|
259
|
-
"context_type": context_type.__name__ if context_type else "",
|
|
260
|
-
"metadata": metadata or {},
|
|
261
|
-
}
|
|
262
|
-
self._entries.append(entry)
|
|
263
|
-
|
|
264
|
-
path = await self._get_path("command", **entry)
|
|
265
|
-
self._write(path)
|
|
266
|
-
|
|
267
|
-
def _write(self, path: UPath) -> None:
|
|
268
|
-
"""Write current state to file at given path."""
|
|
269
|
-
context = {"entries": self._entries}
|
|
270
|
-
try:
|
|
271
|
-
text = self.content_template.render(context)
|
|
272
|
-
path.write_text(text, encoding=self.encoding)
|
|
273
|
-
except Exception as e:
|
|
274
|
-
logger.exception("Failed to write to log file", path=path)
|
|
275
|
-
msg = f"Failed to write to log file: {e}"
|
|
276
|
-
raise RuntimeError(msg) from e
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
"""Tool to chain multiple function calls."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from enum import StrEnum
|
|
8
|
-
from typing import Any, Literal, assert_never
|
|
9
|
-
|
|
10
|
-
import anyio
|
|
11
|
-
from pydantic import Field
|
|
12
|
-
from pydantic_ai import ModelRetry
|
|
13
|
-
from schemez import Schema
|
|
14
|
-
|
|
15
|
-
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ErrorStrategy(StrEnum):
|
|
19
|
-
"""Strategy for handling errors in the pipeline."""
|
|
20
|
-
|
|
21
|
-
STOP = "stop" # Stop pipeline on error
|
|
22
|
-
SKIP = "skip" # Skip failed step, continue with previous result
|
|
23
|
-
DEFAULT = "default" # Use provided default value
|
|
24
|
-
RETRY = "retry" # Retry the step
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class StepCondition(Schema):
|
|
28
|
-
"""Condition for conditional execution."""
|
|
29
|
-
|
|
30
|
-
field: str # Field to check in result
|
|
31
|
-
operator: Literal["eq", "gt", "lt", "contains", "exists"]
|
|
32
|
-
value: Any = None
|
|
33
|
-
|
|
34
|
-
def evaluate_with_value(self, value: Any) -> bool:
|
|
35
|
-
"""Evaluate this condition against a value.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
value: The value to evaluate against the condition.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
bool: True if the condition is met, False otherwise.
|
|
42
|
-
"""
|
|
43
|
-
field_value = value.get(self.field) if isinstance(value, dict) else value
|
|
44
|
-
|
|
45
|
-
match self.operator:
|
|
46
|
-
case "eq":
|
|
47
|
-
return bool(field_value == self.value)
|
|
48
|
-
case "gt":
|
|
49
|
-
return bool(field_value > self.value)
|
|
50
|
-
case "lt":
|
|
51
|
-
return bool(field_value < self.value)
|
|
52
|
-
case "contains":
|
|
53
|
-
try:
|
|
54
|
-
return self.value in field_value # type: ignore[operator]
|
|
55
|
-
except TypeError:
|
|
56
|
-
return False
|
|
57
|
-
case "exists":
|
|
58
|
-
return field_value is not None
|
|
59
|
-
case _ as unreachable:
|
|
60
|
-
assert_never(unreachable)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@dataclass
|
|
64
|
-
class StepResult:
|
|
65
|
-
"""Result of a pipeline step execution."""
|
|
66
|
-
|
|
67
|
-
success: bool
|
|
68
|
-
result: Any
|
|
69
|
-
error: Exception | None = None
|
|
70
|
-
retries: int = 0
|
|
71
|
-
duration: float = 0.0
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# Type alias for step results during execution
|
|
75
|
-
type StepResults = dict[str, StepResult]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class PipelineStep(Schema):
|
|
79
|
-
"""Single step in a tool pipeline."""
|
|
80
|
-
|
|
81
|
-
tool: str
|
|
82
|
-
input_kwarg: str = "text"
|
|
83
|
-
keyword_args: dict[str, Any] = Field(default_factory=dict)
|
|
84
|
-
name: str | None = None # Optional step name for referencing
|
|
85
|
-
condition: StepCondition | None = None # Conditional execution
|
|
86
|
-
error_strategy: ErrorStrategy = ErrorStrategy.STOP
|
|
87
|
-
default_value: Any = None # Used with ErrorStrategy.DEFAULT
|
|
88
|
-
max_retries: int = 0
|
|
89
|
-
retry_delay: float = 1.0
|
|
90
|
-
timeout: float | None = None
|
|
91
|
-
depends_on: list[str] = Field(default_factory=list) # Step dependencies
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class Pipeline(Schema):
|
|
95
|
-
"""A pipeline of tool operations."""
|
|
96
|
-
|
|
97
|
-
input: str | dict[str, Any]
|
|
98
|
-
steps: list[PipelineStep]
|
|
99
|
-
mode: Literal["sequential", "parallel"] = "sequential"
|
|
100
|
-
max_parallel: int = 5 # Max concurrent steps
|
|
101
|
-
collect_metrics: bool = False # Collect execution metrics
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
async def _execute_step(
|
|
105
|
-
ctx: AgentContext,
|
|
106
|
-
step: PipelineStep,
|
|
107
|
-
input_value: Any,
|
|
108
|
-
results: StepResults,
|
|
109
|
-
) -> StepResult:
|
|
110
|
-
"""Execute a single pipeline step."""
|
|
111
|
-
start_time = asyncio.get_event_loop().time()
|
|
112
|
-
retries = 0
|
|
113
|
-
|
|
114
|
-
while True:
|
|
115
|
-
try:
|
|
116
|
-
# Check condition if any
|
|
117
|
-
if step.condition and not step.condition.evaluate_with_value(input_value):
|
|
118
|
-
return StepResult(success=True, result=input_value, duration=0)
|
|
119
|
-
|
|
120
|
-
tool_info = await ctx.agent.tools.get_tool(step.tool) # Get the tool
|
|
121
|
-
if isinstance(input_value, dict): # Prepare kwargs
|
|
122
|
-
kwargs = {**input_value, **step.keyword_args}
|
|
123
|
-
else:
|
|
124
|
-
kwargs = {step.input_kwarg: input_value, **step.keyword_args}
|
|
125
|
-
|
|
126
|
-
# Execute with timeout if specified
|
|
127
|
-
if step.timeout:
|
|
128
|
-
fut = tool_info.execute(ctx, **kwargs)
|
|
129
|
-
result = await asyncio.wait_for(fut, timeout=step.timeout)
|
|
130
|
-
else:
|
|
131
|
-
result = await tool_info.execute(ctx, **kwargs)
|
|
132
|
-
|
|
133
|
-
duration = asyncio.get_event_loop().time() - start_time
|
|
134
|
-
return StepResult(success=True, result=result, duration=duration)
|
|
135
|
-
|
|
136
|
-
except Exception as exc:
|
|
137
|
-
match step.error_strategy:
|
|
138
|
-
case ErrorStrategy.STOP:
|
|
139
|
-
raise
|
|
140
|
-
|
|
141
|
-
case ErrorStrategy.SKIP:
|
|
142
|
-
duration = asyncio.get_event_loop().time() - start_time
|
|
143
|
-
return StepResult(
|
|
144
|
-
success=False,
|
|
145
|
-
result=input_value,
|
|
146
|
-
error=exc,
|
|
147
|
-
duration=duration,
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
case ErrorStrategy.DEFAULT:
|
|
151
|
-
duration = asyncio.get_event_loop().time() - start_time
|
|
152
|
-
return StepResult(
|
|
153
|
-
success=False,
|
|
154
|
-
result=step.default_value,
|
|
155
|
-
error=exc,
|
|
156
|
-
duration=duration,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
case ErrorStrategy.RETRY:
|
|
160
|
-
retries += 1
|
|
161
|
-
if retries <= step.max_retries:
|
|
162
|
-
await anyio.sleep(step.retry_delay)
|
|
163
|
-
continue
|
|
164
|
-
raise # Max retries exceeded
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
async def _execute_sequential(ctx: AgentContext, pipeline: Pipeline, results: StepResults) -> Any:
|
|
168
|
-
"""Execute steps sequentially."""
|
|
169
|
-
current = pipeline.input
|
|
170
|
-
for step in pipeline.steps:
|
|
171
|
-
result = await _execute_step(ctx, step, current, results)
|
|
172
|
-
if step.name:
|
|
173
|
-
results[step.name] = result
|
|
174
|
-
current = result.result
|
|
175
|
-
return current
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
async def _execute_parallel(ctx: AgentContext, pipeline: Pipeline, results: StepResults) -> Any:
|
|
179
|
-
"""Execute independent steps in parallel."""
|
|
180
|
-
semaphore = asyncio.Semaphore(pipeline.max_parallel)
|
|
181
|
-
|
|
182
|
-
async def run_step(step: PipelineStep) -> None:
|
|
183
|
-
async with semaphore:
|
|
184
|
-
# Wait for dependencies
|
|
185
|
-
for dep in step.depends_on:
|
|
186
|
-
while dep not in results:
|
|
187
|
-
await anyio.sleep(0.1)
|
|
188
|
-
|
|
189
|
-
# Get input from dependency or pipeline input
|
|
190
|
-
input_value = results[step.depends_on[-1]].result if step.depends_on else pipeline.input
|
|
191
|
-
result = await _execute_step(ctx, step, input_value, results)
|
|
192
|
-
if step.name:
|
|
193
|
-
results[step.name] = result
|
|
194
|
-
|
|
195
|
-
# Create tasks for all steps
|
|
196
|
-
tasks = [run_step(step) for step in pipeline.steps]
|
|
197
|
-
await asyncio.gather(*tasks)
|
|
198
|
-
# Return last result
|
|
199
|
-
return results[name].result if (name := pipeline.steps[-1].name) else None
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
async def chain_tools(
|
|
203
|
-
ctx: AgentContext,
|
|
204
|
-
input_data: str | dict[str, Any],
|
|
205
|
-
steps: list[dict[str, Any]],
|
|
206
|
-
mode: Literal["sequential", "parallel"] = "sequential",
|
|
207
|
-
max_parallel: int = 5,
|
|
208
|
-
collect_metrics: bool = False,
|
|
209
|
-
) -> Any:
|
|
210
|
-
"""Execute multiple tool operations in sequence or parallel.
|
|
211
|
-
|
|
212
|
-
WHEN TO USE THIS TOOL:
|
|
213
|
-
- Use this when you can plan multiple operations confidently in advance
|
|
214
|
-
- Use this for common sequences you've successfully used before
|
|
215
|
-
- Use this to reduce interaction rounds for known operation patterns
|
|
216
|
-
- Use this when all steps are independent of intermediate results
|
|
217
|
-
|
|
218
|
-
DO NOT USE THIS TOOL:
|
|
219
|
-
- When you need to inspect intermediate results
|
|
220
|
-
- When next steps depend on analyzing previous results
|
|
221
|
-
- When you're unsure about the complete sequence
|
|
222
|
-
- When you need to handle errors at each step individually
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
ctx: Agent context for tool execution
|
|
226
|
-
input_data: Initial input for the pipeline
|
|
227
|
-
steps: List of step configurations, each containing:
|
|
228
|
-
- tool: Name of the tool to execute
|
|
229
|
-
- input_kwarg: Keyword argument name for input (default: "text")
|
|
230
|
-
- keyword_args: Additional keyword arguments
|
|
231
|
-
- name: Optional step name for referencing
|
|
232
|
-
- condition: Optional execution condition
|
|
233
|
-
- error_strategy: How to handle errors ("stop", "skip", "default", "retry")
|
|
234
|
-
- default_value: Value to use with "default" error strategy
|
|
235
|
-
- max_retries: Maximum retry attempts
|
|
236
|
-
- retry_delay: Delay between retries in seconds
|
|
237
|
-
- timeout: Step timeout in seconds
|
|
238
|
-
- depends_on: List of step names this depends on
|
|
239
|
-
mode: Execution mode - "sequential" or "parallel"
|
|
240
|
-
max_parallel: Maximum concurrent steps for parallel mode
|
|
241
|
-
collect_metrics: Whether to collect execution metrics
|
|
242
|
-
|
|
243
|
-
Examples:
|
|
244
|
-
# Sequential processing
|
|
245
|
-
await chain_tools(
|
|
246
|
-
ctx,
|
|
247
|
-
input_data="main.py",
|
|
248
|
-
steps=[
|
|
249
|
-
{"tool": "load_resource", "input_kwarg": "name"},
|
|
250
|
-
{"tool": "analyze_code", "input_kwarg": "code"},
|
|
251
|
-
{"tool": "format_output", "input_kwarg": "text"}
|
|
252
|
-
]
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
# Parallel processing with dependencies
|
|
256
|
-
await chain_tools(
|
|
257
|
-
ctx,
|
|
258
|
-
input_data="test.py",
|
|
259
|
-
mode="parallel",
|
|
260
|
-
steps=[
|
|
261
|
-
{"tool": "load_resource", "input_kwarg": "name", "name": "load"},
|
|
262
|
-
{"tool": "analyze_code", "input_kwarg": "code", "depends_on": ["load"]},
|
|
263
|
-
{"tool": "count_tokens", "input_kwarg": "text", "depends_on": ["load"]}
|
|
264
|
-
]
|
|
265
|
-
)
|
|
266
|
-
"""
|
|
267
|
-
try:
|
|
268
|
-
pipeline = Pipeline(
|
|
269
|
-
input=input_data,
|
|
270
|
-
steps=[PipelineStep.model_validate(step) for step in steps],
|
|
271
|
-
mode=mode,
|
|
272
|
-
max_parallel=max_parallel,
|
|
273
|
-
collect_metrics=collect_metrics,
|
|
274
|
-
)
|
|
275
|
-
except Exception as e:
|
|
276
|
-
msg = f"Invalid pipeline configuration: {e}"
|
|
277
|
-
raise ModelRetry(msg) from e
|
|
278
|
-
results: StepResults = {}
|
|
279
|
-
|
|
280
|
-
try:
|
|
281
|
-
match pipeline.mode:
|
|
282
|
-
case "sequential":
|
|
283
|
-
return await _execute_sequential(ctx, pipeline, results)
|
|
284
|
-
case "parallel":
|
|
285
|
-
return await _execute_parallel(ctx, pipeline, results)
|
|
286
|
-
except Exception as e:
|
|
287
|
-
msg = f"Failed to execute pipeline: {e}"
|
|
288
|
-
raise ModelRetry(msg) from e
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"""Provider for user interaction tools."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any, assert_never
|
|
6
|
-
|
|
7
|
-
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
8
|
-
from agentpool.resource_providers import StaticResourceProvider
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
async def ask_user( # noqa: D417
|
|
12
|
-
ctx: AgentContext,
|
|
13
|
-
prompt: str,
|
|
14
|
-
response_schema: dict[str, Any] | None = None,
|
|
15
|
-
) -> str:
|
|
16
|
-
"""Allow LLM to ask user a clarifying question during processing.
|
|
17
|
-
|
|
18
|
-
This tool enables agents to ask users for additional information or clarification
|
|
19
|
-
when needed to complete a task effectively.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
prompt: Question to ask the user
|
|
23
|
-
response_schema: Optional JSON schema for structured response (defaults to string)
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
The user's response as a string
|
|
27
|
-
"""
|
|
28
|
-
from mcp.types import ElicitRequestFormParams, ElicitResult, ErrorData
|
|
29
|
-
|
|
30
|
-
schema = response_schema or {"type": "string"} # string schema if no none provided
|
|
31
|
-
params = ElicitRequestFormParams(message=prompt, requestedSchema=schema)
|
|
32
|
-
result = await ctx.handle_elicitation(params)
|
|
33
|
-
|
|
34
|
-
match result:
|
|
35
|
-
case ElicitResult(action="accept", content=content):
|
|
36
|
-
return str(content)
|
|
37
|
-
case ElicitResult(action="cancel"):
|
|
38
|
-
return "User cancelled the request"
|
|
39
|
-
case ElicitResult():
|
|
40
|
-
return "User declined to answer"
|
|
41
|
-
case ErrorData(message=message):
|
|
42
|
-
return f"Error: {message}"
|
|
43
|
-
case _ as unreachable:
|
|
44
|
-
assert_never(unreachable)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class UserInteractionTools(StaticResourceProvider):
|
|
48
|
-
"""Provider for user interaction tools."""
|
|
49
|
-
|
|
50
|
-
def __init__(self, name: str = "user_interaction") -> None:
|
|
51
|
-
super().__init__(name=name)
|
|
52
|
-
self._tools = [self.create_tool(ask_user, category="other", open_world=True)]
|