hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.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.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +4 -17
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +8 -17
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +2 -4
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +6 -7
- hanzo_mcp/tools/__init__.py +29 -32
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +23 -17
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +76 -75
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +7 -19
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +3 -5
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +33 -40
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +7 -19
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +27 -81
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +11 -30
- hanzo_mcp/tools/vector/mock_infinity.py +159 -0
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -723
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -1,723 +0,0 @@
|
|
|
1
|
-
"""Swarm tool implementation using hanzo-agents SDK.
|
|
2
|
-
|
|
3
|
-
This module implements the SwarmTool that leverages the hanzo-agents SDK
|
|
4
|
-
for sophisticated multi-agent orchestration with flexible network topologies.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
from typing import (
|
|
9
|
-
Any,
|
|
10
|
-
Dict,
|
|
11
|
-
List,
|
|
12
|
-
Unpack,
|
|
13
|
-
Optional,
|
|
14
|
-
TypedDict,
|
|
15
|
-
final,
|
|
16
|
-
override,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
from mcp.server import FastMCP
|
|
20
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
21
|
-
|
|
22
|
-
# Import hanzo-agents SDK with fallback
|
|
23
|
-
try:
|
|
24
|
-
from hanzo_agents import (
|
|
25
|
-
Tool,
|
|
26
|
-
Agent,
|
|
27
|
-
State,
|
|
28
|
-
Router,
|
|
29
|
-
History,
|
|
30
|
-
Network,
|
|
31
|
-
ToolCall,
|
|
32
|
-
ModelRegistry,
|
|
33
|
-
InferenceResult,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
HANZO_AGENTS_AVAILABLE = True
|
|
37
|
-
except ImportError:
|
|
38
|
-
# Define minimal stubs if hanzo-agents is not available
|
|
39
|
-
HANZO_AGENTS_AVAILABLE = False
|
|
40
|
-
|
|
41
|
-
class Agent:
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
class State:
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
class Network:
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
class Tool:
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
class History:
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
class ModelRegistry:
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
class InferenceResult:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
class ToolCall:
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
class Router:
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Import optional components with fallbacks
|
|
70
|
-
try:
|
|
71
|
-
from hanzo_agents import LLMRouter, HybridRouter, DeterministicRouter
|
|
72
|
-
except ImportError:
|
|
73
|
-
try:
|
|
74
|
-
# Try core module import
|
|
75
|
-
from hanzo_agents.core.router import (
|
|
76
|
-
LLMRouter,
|
|
77
|
-
HybridRouter,
|
|
78
|
-
DeterministicRouter,
|
|
79
|
-
)
|
|
80
|
-
except ImportError:
|
|
81
|
-
# Define stubs if not available
|
|
82
|
-
class DeterministicRouter:
|
|
83
|
-
pass
|
|
84
|
-
|
|
85
|
-
class LLMRouter:
|
|
86
|
-
pass
|
|
87
|
-
|
|
88
|
-
class HybridRouter:
|
|
89
|
-
pass
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
from hanzo_agents import create_memory_kv, create_memory_vector
|
|
94
|
-
except ImportError:
|
|
95
|
-
try:
|
|
96
|
-
# Try core module import
|
|
97
|
-
from hanzo_agents.core.memory import create_memory_kv, create_memory_vector
|
|
98
|
-
except ImportError:
|
|
99
|
-
# Define fallback functions if not available
|
|
100
|
-
def create_memory_kv(*args, **kwargs):
|
|
101
|
-
"""Fallback: Key-value memory not available."""
|
|
102
|
-
return {}
|
|
103
|
-
|
|
104
|
-
def create_memory_vector(*args, **kwargs):
|
|
105
|
-
"""Fallback: Vector memory not available."""
|
|
106
|
-
return {}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
from hanzo_agents import sequential_router, conditional_router, state_based_router
|
|
111
|
-
except ImportError:
|
|
112
|
-
try:
|
|
113
|
-
# Try core module import
|
|
114
|
-
from hanzo_agents.core.router import (
|
|
115
|
-
sequential_router,
|
|
116
|
-
conditional_router,
|
|
117
|
-
state_based_router,
|
|
118
|
-
)
|
|
119
|
-
except ImportError:
|
|
120
|
-
# Define fallback functions if not available
|
|
121
|
-
def sequential_router(*args, **kwargs):
|
|
122
|
-
"""Fallback: Sequential router not available."""
|
|
123
|
-
return lambda agents, task: None
|
|
124
|
-
|
|
125
|
-
def conditional_router(*args, **kwargs):
|
|
126
|
-
"""Fallback: Conditional router not available."""
|
|
127
|
-
return lambda agents, task: None
|
|
128
|
-
|
|
129
|
-
def state_based_router(*args, **kwargs):
|
|
130
|
-
"""Fallback: State-based router not available."""
|
|
131
|
-
return lambda agents, task: None
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
from hanzo_agents.core.cli_agent import (
|
|
136
|
-
GrokAgent,
|
|
137
|
-
GeminiAgent,
|
|
138
|
-
ClaudeCodeAgent,
|
|
139
|
-
OpenAICodexAgent,
|
|
140
|
-
)
|
|
141
|
-
except ImportError:
|
|
142
|
-
# Define stub classes if not available
|
|
143
|
-
class ClaudeCodeAgent(Agent):
|
|
144
|
-
pass
|
|
145
|
-
|
|
146
|
-
class OpenAICodexAgent(Agent):
|
|
147
|
-
pass
|
|
148
|
-
|
|
149
|
-
class GeminiAgent(Agent):
|
|
150
|
-
pass
|
|
151
|
-
|
|
152
|
-
class GrokAgent(Agent):
|
|
153
|
-
pass
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
|
|
157
|
-
from hanzo_mcp.tools.filesystem import Edit, MultiEdit, get_read_only_filesystem_tools
|
|
158
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
159
|
-
from hanzo_mcp.tools.common.context import create_tool_context
|
|
160
|
-
from hanzo_mcp.tools.agent.agent_tool import MCPAgent
|
|
161
|
-
from hanzo_mcp.tools.common.batch_tool import BatchTool
|
|
162
|
-
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class AgentNode(TypedDict):
|
|
166
|
-
"""Node in the agent network."""
|
|
167
|
-
|
|
168
|
-
id: str
|
|
169
|
-
query: str
|
|
170
|
-
model: Optional[str]
|
|
171
|
-
role: Optional[str]
|
|
172
|
-
connections: Optional[List[str]]
|
|
173
|
-
receives_from: Optional[List[str]]
|
|
174
|
-
file_path: Optional[str]
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
class SwarmConfig(TypedDict):
|
|
178
|
-
"""Configuration for an agent network."""
|
|
179
|
-
|
|
180
|
-
agents: Dict[str, AgentNode]
|
|
181
|
-
entry_point: Optional[str]
|
|
182
|
-
topology: Optional[str]
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
class SwarmToolParams(TypedDict):
|
|
186
|
-
"""Parameters for the SwarmTool."""
|
|
187
|
-
|
|
188
|
-
config: SwarmConfig
|
|
189
|
-
query: str
|
|
190
|
-
context: Optional[str]
|
|
191
|
-
max_concurrent: Optional[int]
|
|
192
|
-
use_memory: Optional[bool]
|
|
193
|
-
memory_backend: Optional[str]
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
class SwarmState(State):
|
|
197
|
-
"""State for swarm execution."""
|
|
198
|
-
|
|
199
|
-
def __init__(
|
|
200
|
-
self, config: SwarmConfig, initial_query: str, context: Optional[str] = None
|
|
201
|
-
):
|
|
202
|
-
"""Initialize swarm state."""
|
|
203
|
-
super().__init__()
|
|
204
|
-
self.config = config
|
|
205
|
-
self.initial_query = initial_query
|
|
206
|
-
self.context = context
|
|
207
|
-
self.agent_results = {}
|
|
208
|
-
self.completed_agents = set()
|
|
209
|
-
self.current_agent = None
|
|
210
|
-
self.execution_order = []
|
|
211
|
-
|
|
212
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
213
|
-
"""Convert to dictionary."""
|
|
214
|
-
base_dict = super().to_dict()
|
|
215
|
-
base_dict.update(
|
|
216
|
-
{
|
|
217
|
-
"config": self.config,
|
|
218
|
-
"initial_query": self.initial_query,
|
|
219
|
-
"context": self.context,
|
|
220
|
-
"agent_results": self.agent_results,
|
|
221
|
-
"completed_agents": list(self.completed_agents),
|
|
222
|
-
"current_agent": self.current_agent,
|
|
223
|
-
"execution_order": self.execution_order,
|
|
224
|
-
}
|
|
225
|
-
)
|
|
226
|
-
return base_dict
|
|
227
|
-
|
|
228
|
-
@classmethod
|
|
229
|
-
def from_dict(cls, data: Dict[str, Any]) -> "SwarmState":
|
|
230
|
-
"""Create from dictionary."""
|
|
231
|
-
state = cls(
|
|
232
|
-
config=data.get("config", {}),
|
|
233
|
-
initial_query=data.get("initial_query", ""),
|
|
234
|
-
context=data.get("context"),
|
|
235
|
-
)
|
|
236
|
-
state.agent_results = data.get("agent_results", {})
|
|
237
|
-
state.completed_agents = set(data.get("completed_agents", []))
|
|
238
|
-
state.current_agent = data.get("current_agent")
|
|
239
|
-
state.execution_order = data.get("execution_order", [])
|
|
240
|
-
return state
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class SwarmAgent(MCPAgent):
|
|
244
|
-
"""Agent that executes within a swarm network."""
|
|
245
|
-
|
|
246
|
-
def __init__(
|
|
247
|
-
self,
|
|
248
|
-
agent_id: str,
|
|
249
|
-
agent_config: AgentNode,
|
|
250
|
-
available_tools: List[BaseTool],
|
|
251
|
-
permission_manager: PermissionManager,
|
|
252
|
-
ctx: MCPContext,
|
|
253
|
-
**kwargs,
|
|
254
|
-
):
|
|
255
|
-
"""Initialize swarm agent."""
|
|
256
|
-
# Set name and description from config
|
|
257
|
-
self.name = agent_id
|
|
258
|
-
self.description = agent_config.get("role", f"Agent {agent_id}")
|
|
259
|
-
self.agent_config = agent_config
|
|
260
|
-
|
|
261
|
-
# Initialize with specified model
|
|
262
|
-
model = agent_config.get("model")
|
|
263
|
-
if model:
|
|
264
|
-
model = self._normalize_model(model)
|
|
265
|
-
else:
|
|
266
|
-
model = "model://anthropic/claude-3-5-sonnet-20241022"
|
|
267
|
-
|
|
268
|
-
super().__init__(
|
|
269
|
-
available_tools=available_tools,
|
|
270
|
-
permission_manager=permission_manager,
|
|
271
|
-
ctx=ctx,
|
|
272
|
-
model=model,
|
|
273
|
-
**kwargs,
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
def _normalize_model(self, model: str) -> str:
|
|
277
|
-
"""Normalize model names to full format."""
|
|
278
|
-
model_map = {
|
|
279
|
-
"claude-3-5-sonnet": "model://anthropic/claude-3-5-sonnet-20241022",
|
|
280
|
-
"claude-3-opus": "model://anthropic/claude-3-opus-20240229",
|
|
281
|
-
"gpt-4o": "model://openai/gpt-4o",
|
|
282
|
-
"gpt-4": "model://openai/gpt-4",
|
|
283
|
-
"gemini-1.5-pro": "model://google/gemini-1.5-pro",
|
|
284
|
-
"gemini-1.5-flash": "model://google/gemini-1.5-flash",
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
# Check if it's already a model:// URI
|
|
288
|
-
if model.startswith("model://"):
|
|
289
|
-
return model
|
|
290
|
-
|
|
291
|
-
# Check mapping
|
|
292
|
-
if model in model_map:
|
|
293
|
-
return model_map[model]
|
|
294
|
-
|
|
295
|
-
# Assume it's a provider/model format
|
|
296
|
-
if "/" in model:
|
|
297
|
-
return f"model://{model}"
|
|
298
|
-
|
|
299
|
-
# Default to anthropic
|
|
300
|
-
return f"model://anthropic/{model}"
|
|
301
|
-
|
|
302
|
-
async def run(
|
|
303
|
-
self, state: SwarmState, history: History, network: Network
|
|
304
|
-
) -> InferenceResult:
|
|
305
|
-
"""Execute the swarm agent."""
|
|
306
|
-
# Build prompt with context
|
|
307
|
-
prompt_parts = []
|
|
308
|
-
|
|
309
|
-
# Add role context
|
|
310
|
-
if self.agent_config.get("role"):
|
|
311
|
-
prompt_parts.append(f"Your role: {self.agent_config['role']}")
|
|
312
|
-
|
|
313
|
-
# Add shared context
|
|
314
|
-
if state.context:
|
|
315
|
-
prompt_parts.append(f"Context:\n{state.context}")
|
|
316
|
-
|
|
317
|
-
# Add inputs from connected agents
|
|
318
|
-
receives_from = self.agent_config.get("receives_from", [])
|
|
319
|
-
if receives_from:
|
|
320
|
-
inputs = {}
|
|
321
|
-
for agent_id in receives_from:
|
|
322
|
-
if agent_id in state.agent_results:
|
|
323
|
-
inputs[agent_id] = state.agent_results[agent_id]
|
|
324
|
-
|
|
325
|
-
if inputs:
|
|
326
|
-
prompt_parts.append("Input from previous agents:")
|
|
327
|
-
for input_agent, input_result in inputs.items():
|
|
328
|
-
prompt_parts.append(f"\n--- From {input_agent} ---\n{input_result}")
|
|
329
|
-
|
|
330
|
-
# Add file context if specified
|
|
331
|
-
if self.agent_config.get("file_path"):
|
|
332
|
-
prompt_parts.append(f"\nFile to work on: {self.agent_config['file_path']}")
|
|
333
|
-
|
|
334
|
-
# Add the main query
|
|
335
|
-
prompt_parts.append(f"\nTask: {self.agent_config['query']}")
|
|
336
|
-
|
|
337
|
-
# Add initial query if this is entry point
|
|
338
|
-
if state.current_agent == state.config.get("entry_point"):
|
|
339
|
-
prompt_parts.append(f"\nMain objective: {state.initial_query}")
|
|
340
|
-
|
|
341
|
-
full_prompt = "\n\n".join(prompt_parts)
|
|
342
|
-
|
|
343
|
-
# Execute using base class
|
|
344
|
-
messages = [
|
|
345
|
-
{"role": "system", "content": self._get_system_prompt()},
|
|
346
|
-
{"role": "user", "content": full_prompt},
|
|
347
|
-
]
|
|
348
|
-
|
|
349
|
-
# Call model
|
|
350
|
-
from hanzo_agents import ModelRegistry
|
|
351
|
-
|
|
352
|
-
adapter = ModelRegistry.get_adapter(self.model)
|
|
353
|
-
response = await adapter.chat(messages)
|
|
354
|
-
|
|
355
|
-
# Store result in state
|
|
356
|
-
state.agent_results[self.name] = response
|
|
357
|
-
state.completed_agents.add(self.name)
|
|
358
|
-
state.execution_order.append(self.name)
|
|
359
|
-
|
|
360
|
-
# Return result
|
|
361
|
-
return InferenceResult(
|
|
362
|
-
agent=self.name,
|
|
363
|
-
content=response,
|
|
364
|
-
metadata={
|
|
365
|
-
"agent_id": self.name,
|
|
366
|
-
"role": self.agent_config.get("role"),
|
|
367
|
-
"connections": self.agent_config.get("connections", []),
|
|
368
|
-
},
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
class SwarmRouter(DeterministicRouter):
|
|
373
|
-
"""Router for swarm agent orchestration."""
|
|
374
|
-
|
|
375
|
-
def __init__(self, swarm_config: SwarmConfig):
|
|
376
|
-
"""Initialize swarm router."""
|
|
377
|
-
self.swarm_config = swarm_config
|
|
378
|
-
self.agents_config = swarm_config["agents"]
|
|
379
|
-
self.entry_point = swarm_config.get("entry_point")
|
|
380
|
-
|
|
381
|
-
# Build dependency graph
|
|
382
|
-
self.dependencies = {}
|
|
383
|
-
self.dependents = {}
|
|
384
|
-
|
|
385
|
-
for agent_id, config in self.agents_config.items():
|
|
386
|
-
# Dependencies (agents this one waits for)
|
|
387
|
-
self.dependencies[agent_id] = set(config.get("receives_from", []))
|
|
388
|
-
|
|
389
|
-
# Dependents (agents that wait for this one)
|
|
390
|
-
connections = config.get("connections", [])
|
|
391
|
-
for conn in connections:
|
|
392
|
-
if conn not in self.dependents:
|
|
393
|
-
self.dependents[conn] = set()
|
|
394
|
-
self.dependents[conn].add(agent_id)
|
|
395
|
-
|
|
396
|
-
def route(self, network, call_count, last_result, agent_stack):
|
|
397
|
-
"""Determine next agent to execute."""
|
|
398
|
-
state = network.state
|
|
399
|
-
|
|
400
|
-
# First call - start with entry point or roots
|
|
401
|
-
if call_count == 0:
|
|
402
|
-
if self.entry_point:
|
|
403
|
-
state.current_agent = self.entry_point
|
|
404
|
-
return self._get_agent_class(self.entry_point, agent_stack)
|
|
405
|
-
else:
|
|
406
|
-
# Find roots (no dependencies)
|
|
407
|
-
roots = [aid for aid, deps in self.dependencies.items() if not deps]
|
|
408
|
-
if roots:
|
|
409
|
-
state.current_agent = roots[0]
|
|
410
|
-
return self._get_agent_class(roots[0], agent_stack)
|
|
411
|
-
|
|
412
|
-
# Find next agent to execute
|
|
413
|
-
for agent_id in self.agents_config:
|
|
414
|
-
if agent_id in state.completed_agents:
|
|
415
|
-
continue
|
|
416
|
-
|
|
417
|
-
# Check if all dependencies are met
|
|
418
|
-
deps = self.dependencies.get(agent_id, set())
|
|
419
|
-
if deps.issubset(state.completed_agents):
|
|
420
|
-
state.current_agent = agent_id
|
|
421
|
-
return self._get_agent_class(agent_id, agent_stack)
|
|
422
|
-
|
|
423
|
-
# No more agents to execute
|
|
424
|
-
return None
|
|
425
|
-
|
|
426
|
-
def _get_agent_class(
|
|
427
|
-
self, agent_id: str, agent_stack: List[type[Agent]]
|
|
428
|
-
) -> type[Agent]:
|
|
429
|
-
"""Get agent class for given agent ID."""
|
|
430
|
-
# Find matching agent by name
|
|
431
|
-
for agent_class in agent_stack:
|
|
432
|
-
if hasattr(agent_class, "name") and agent_class.name == agent_id:
|
|
433
|
-
return agent_class
|
|
434
|
-
|
|
435
|
-
# Not found - this shouldn't happen
|
|
436
|
-
return None
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
@final
|
|
440
|
-
class SwarmTool(BaseTool):
|
|
441
|
-
"""Tool for executing agent networks using hanzo-agents SDK."""
|
|
442
|
-
|
|
443
|
-
@property
|
|
444
|
-
@override
|
|
445
|
-
def name(self) -> str:
|
|
446
|
-
"""Get the tool name."""
|
|
447
|
-
return "swarm"
|
|
448
|
-
|
|
449
|
-
@property
|
|
450
|
-
@override
|
|
451
|
-
def description(self) -> str:
|
|
452
|
-
"""Get the tool description."""
|
|
453
|
-
return """Execute a network of AI agents with flexible connection topologies.
|
|
454
|
-
|
|
455
|
-
This tool enables sophisticated agent orchestration where agents can be connected
|
|
456
|
-
in various network patterns. Each agent can pass results to connected agents,
|
|
457
|
-
enabling complex workflows.
|
|
458
|
-
|
|
459
|
-
Features:
|
|
460
|
-
- Flexible agent networks (tree, DAG, pipeline, star, mesh)
|
|
461
|
-
- Each agent can use different models (Claude, GPT-4, Gemini, etc.)
|
|
462
|
-
- Agents automatically pass results to connected agents
|
|
463
|
-
- Parallel execution with dependency management
|
|
464
|
-
- Full editing capabilities for each agent
|
|
465
|
-
- Memory and state management via hanzo-agents SDK
|
|
466
|
-
|
|
467
|
-
Common Topologies:
|
|
468
|
-
1. Tree (Architect pattern):
|
|
469
|
-
architect → [frontend, backend, database] → reviewer
|
|
470
|
-
|
|
471
|
-
2. Pipeline (Sequential processing):
|
|
472
|
-
analyzer → planner → implementer → tester → reviewer
|
|
473
|
-
|
|
474
|
-
3. Star (Central coordinator):
|
|
475
|
-
coordinator ← → [agent1, agent2, agent3, agent4]
|
|
476
|
-
|
|
477
|
-
4. DAG (Complex dependencies):
|
|
478
|
-
Multiple agents with custom connections
|
|
479
|
-
|
|
480
|
-
Models can be specified as:
|
|
481
|
-
- Full: 'anthropic/claude-3-5-sonnet-20241022'
|
|
482
|
-
- Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
|
|
483
|
-
- CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
|
|
484
|
-
- Model URIs: 'model://anthropic/claude-3-opus'
|
|
485
|
-
"""
|
|
486
|
-
|
|
487
|
-
def __init__(
|
|
488
|
-
self,
|
|
489
|
-
permission_manager: PermissionManager,
|
|
490
|
-
model: str | None = None,
|
|
491
|
-
api_key: str | None = None,
|
|
492
|
-
base_url: str | None = None,
|
|
493
|
-
max_tokens: int | None = None,
|
|
494
|
-
agent_max_iterations: int = 10,
|
|
495
|
-
agent_max_tool_uses: int = 30,
|
|
496
|
-
):
|
|
497
|
-
"""Initialize the swarm tool."""
|
|
498
|
-
self.permission_manager = permission_manager
|
|
499
|
-
# Default to latest Claude Sonnet if no model specified
|
|
500
|
-
from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
|
|
501
|
-
|
|
502
|
-
self.model = model or f"anthropic/{get_latest_claude_model()}"
|
|
503
|
-
self.api_key = (
|
|
504
|
-
api_key
|
|
505
|
-
or os.environ.get("ANTHROPIC_API_KEY")
|
|
506
|
-
or os.environ.get("CLAUDE_API_KEY")
|
|
507
|
-
)
|
|
508
|
-
self.base_url = base_url
|
|
509
|
-
self.max_tokens = max_tokens
|
|
510
|
-
self.agent_max_iterations = agent_max_iterations
|
|
511
|
-
self.agent_max_tool_uses = agent_max_tool_uses
|
|
512
|
-
|
|
513
|
-
# Set up available tools for agents
|
|
514
|
-
self.available_tools: list[BaseTool] = []
|
|
515
|
-
self.available_tools.extend(
|
|
516
|
-
get_read_only_filesystem_tools(self.permission_manager)
|
|
517
|
-
)
|
|
518
|
-
self.available_tools.extend(
|
|
519
|
-
get_read_only_jupyter_tools(self.permission_manager)
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
# Add edit tools
|
|
523
|
-
self.available_tools.append(Edit(self.permission_manager))
|
|
524
|
-
self.available_tools.append(MultiEdit(self.permission_manager))
|
|
525
|
-
|
|
526
|
-
# Add batch tool
|
|
527
|
-
self.available_tools.append(
|
|
528
|
-
BatchTool({t.name: t for t in self.available_tools})
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
@override
|
|
532
|
-
async def call(
|
|
533
|
-
self,
|
|
534
|
-
ctx: MCPContext,
|
|
535
|
-
**params: Unpack[SwarmToolParams],
|
|
536
|
-
) -> str:
|
|
537
|
-
"""Execute the swarm tool."""
|
|
538
|
-
tool_ctx = create_tool_context(ctx)
|
|
539
|
-
await tool_ctx.set_tool_info(self.name)
|
|
540
|
-
|
|
541
|
-
# Extract parameters
|
|
542
|
-
config = params.get("config", {})
|
|
543
|
-
initial_query = params.get("query", "")
|
|
544
|
-
context = params.get("context", "")
|
|
545
|
-
max_concurrent = params.get("max_concurrent", 10)
|
|
546
|
-
use_memory = params.get("use_memory", False)
|
|
547
|
-
memory_backend = params.get("memory_backend", "sqlite")
|
|
548
|
-
|
|
549
|
-
agents_config = config.get("agents", {})
|
|
550
|
-
|
|
551
|
-
if not agents_config:
|
|
552
|
-
await tool_ctx.error("No agents provided")
|
|
553
|
-
return "Error: At least one agent must be provided."
|
|
554
|
-
|
|
555
|
-
# hanzo-agents SDK is required (already imported above)
|
|
556
|
-
|
|
557
|
-
await tool_ctx.info(
|
|
558
|
-
f"Starting swarm execution with {len(agents_config)} agents using hanzo-agents SDK"
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
# Create state
|
|
562
|
-
state = SwarmState(config=config, initial_query=initial_query, context=context)
|
|
563
|
-
|
|
564
|
-
# Create agent classes dynamically
|
|
565
|
-
agent_classes = []
|
|
566
|
-
for agent_id, agent_config in agents_config.items():
|
|
567
|
-
# Check for CLI agents
|
|
568
|
-
model = agent_config.get("model", self.model)
|
|
569
|
-
|
|
570
|
-
cli_agents = {
|
|
571
|
-
"claude_cli": ClaudeCodeAgent,
|
|
572
|
-
"codex_cli": OpenAICodexAgent,
|
|
573
|
-
"gemini_cli": GeminiAgent,
|
|
574
|
-
"grok_cli": GrokAgent,
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if model in cli_agents:
|
|
578
|
-
# Use CLI agent
|
|
579
|
-
agent_class = type(
|
|
580
|
-
f"Swarm{agent_id}",
|
|
581
|
-
(cli_agents[model],),
|
|
582
|
-
{
|
|
583
|
-
"name": agent_id,
|
|
584
|
-
"description": agent_config.get("role", f"Agent {agent_id}"),
|
|
585
|
-
"agent_config": agent_config,
|
|
586
|
-
},
|
|
587
|
-
)
|
|
588
|
-
else:
|
|
589
|
-
# Create dynamic SwarmAgent class
|
|
590
|
-
agent_class = type(
|
|
591
|
-
f"Swarm{agent_id}",
|
|
592
|
-
(SwarmAgent,),
|
|
593
|
-
{
|
|
594
|
-
"name": agent_id,
|
|
595
|
-
"__init__": lambda self, aid=agent_id, acfg=agent_config: SwarmAgent.__init__(
|
|
596
|
-
self,
|
|
597
|
-
agent_id=aid,
|
|
598
|
-
agent_config=acfg,
|
|
599
|
-
available_tools=self.available_tools,
|
|
600
|
-
permission_manager=self.permission_manager,
|
|
601
|
-
ctx=ctx,
|
|
602
|
-
),
|
|
603
|
-
},
|
|
604
|
-
)
|
|
605
|
-
|
|
606
|
-
agent_classes.append(agent_class)
|
|
607
|
-
|
|
608
|
-
# Create memory if requested
|
|
609
|
-
memory_kv = None
|
|
610
|
-
memory_vector = None
|
|
611
|
-
if use_memory:
|
|
612
|
-
memory_kv = create_memory_kv(memory_backend)
|
|
613
|
-
memory_vector = create_memory_vector("simple")
|
|
614
|
-
|
|
615
|
-
# Create router
|
|
616
|
-
router = SwarmRouter(config)
|
|
617
|
-
|
|
618
|
-
# Create network
|
|
619
|
-
network = Network(
|
|
620
|
-
state=state,
|
|
621
|
-
agents=agent_classes,
|
|
622
|
-
router=router,
|
|
623
|
-
memory_kv=memory_kv,
|
|
624
|
-
memory_vector=memory_vector,
|
|
625
|
-
max_steps=self.agent_max_iterations * len(agents_config),
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
# Execute
|
|
629
|
-
try:
|
|
630
|
-
final_state = await network.run()
|
|
631
|
-
|
|
632
|
-
# Format results
|
|
633
|
-
return self._format_network_results(
|
|
634
|
-
agents_config,
|
|
635
|
-
final_state.agent_results,
|
|
636
|
-
final_state.execution_order,
|
|
637
|
-
config.get("entry_point"),
|
|
638
|
-
)
|
|
639
|
-
|
|
640
|
-
except Exception as e:
|
|
641
|
-
await tool_ctx.error(f"Swarm execution failed: {str(e)}")
|
|
642
|
-
return f"Error: {str(e)}"
|
|
643
|
-
|
|
644
|
-
def _format_network_results(
|
|
645
|
-
self,
|
|
646
|
-
agents_config: Dict[str, Any],
|
|
647
|
-
results: Dict[str, str],
|
|
648
|
-
execution_order: List[str],
|
|
649
|
-
entry_point: Optional[str],
|
|
650
|
-
) -> str:
|
|
651
|
-
"""Format results from agent network execution."""
|
|
652
|
-
output = ["Agent Network Execution Results (hanzo-agents SDK)"]
|
|
653
|
-
output.append("=" * 80)
|
|
654
|
-
output.append(f"Total agents: {len(agents_config)}")
|
|
655
|
-
output.append(f"Completed: {len(results)}")
|
|
656
|
-
output.append(
|
|
657
|
-
f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}"
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
if entry_point:
|
|
661
|
-
output.append(f"Entry point: {entry_point}")
|
|
662
|
-
|
|
663
|
-
output.append(f"\nExecution Order: {' → '.join(execution_order)}")
|
|
664
|
-
output.append("-" * 40)
|
|
665
|
-
|
|
666
|
-
# Detailed results
|
|
667
|
-
output.append("\n\nDetailed Results:")
|
|
668
|
-
output.append("=" * 80)
|
|
669
|
-
|
|
670
|
-
for agent_id in execution_order:
|
|
671
|
-
if agent_id in results:
|
|
672
|
-
config = agents_config.get(agent_id, {})
|
|
673
|
-
role = config.get("role", "Agent")
|
|
674
|
-
model = config.get("model", "default")
|
|
675
|
-
|
|
676
|
-
output.append(f"\n### {agent_id} ({role}) [{model}]")
|
|
677
|
-
output.append("-" * 40)
|
|
678
|
-
|
|
679
|
-
result = results[agent_id]
|
|
680
|
-
if result.startswith("Error:"):
|
|
681
|
-
output.append(result)
|
|
682
|
-
else:
|
|
683
|
-
# Show first part of result
|
|
684
|
-
lines = result.split("\n")
|
|
685
|
-
preview_lines = lines[:10]
|
|
686
|
-
output.extend(preview_lines)
|
|
687
|
-
|
|
688
|
-
if len(lines) > 10:
|
|
689
|
-
output.append(f"... ({len(lines) - 10} more lines)")
|
|
690
|
-
|
|
691
|
-
return "\n".join(output)
|
|
692
|
-
|
|
693
|
-
@override
|
|
694
|
-
def register(self, mcp_server: FastMCP) -> None:
|
|
695
|
-
"""Register this swarm tool with the MCP server."""
|
|
696
|
-
tool_self = self
|
|
697
|
-
|
|
698
|
-
@mcp_server.tool(name=self.name, description=self.description)
|
|
699
|
-
async def swarm(
|
|
700
|
-
ctx: MCPContext,
|
|
701
|
-
config: dict[str, Any],
|
|
702
|
-
query: str,
|
|
703
|
-
context: Optional[str] = None,
|
|
704
|
-
max_concurrent: int = 10,
|
|
705
|
-
use_memory: bool = False,
|
|
706
|
-
memory_backend: str = "sqlite",
|
|
707
|
-
) -> str:
|
|
708
|
-
# Convert to typed format
|
|
709
|
-
typed_config = SwarmConfig(
|
|
710
|
-
agents=config.get("agents", {}),
|
|
711
|
-
entry_point=config.get("entry_point"),
|
|
712
|
-
topology=config.get("topology"),
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
return await tool_self.call(
|
|
716
|
-
ctx,
|
|
717
|
-
config=typed_config,
|
|
718
|
-
query=query,
|
|
719
|
-
context=context,
|
|
720
|
-
max_concurrent=max_concurrent,
|
|
721
|
-
use_memory=use_memory,
|
|
722
|
-
memory_backend=memory_backend,
|
|
723
|
-
)
|