mindroom 0.0.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. mindroom/__init__.py +3 -0
  2. mindroom/agent_prompts.py +963 -0
  3. mindroom/agents.py +248 -0
  4. mindroom/ai.py +421 -0
  5. mindroom/api/__init__.py +1 -0
  6. mindroom/api/credentials.py +137 -0
  7. mindroom/api/google_integration.py +355 -0
  8. mindroom/api/google_tools_helper.py +40 -0
  9. mindroom/api/homeassistant_integration.py +421 -0
  10. mindroom/api/integrations.py +189 -0
  11. mindroom/api/main.py +506 -0
  12. mindroom/api/matrix_operations.py +219 -0
  13. mindroom/api/tools.py +94 -0
  14. mindroom/background_tasks.py +87 -0
  15. mindroom/bot.py +2470 -0
  16. mindroom/cli.py +86 -0
  17. mindroom/commands.py +377 -0
  18. mindroom/config.py +343 -0
  19. mindroom/config_commands.py +324 -0
  20. mindroom/config_confirmation.py +411 -0
  21. mindroom/constants.py +52 -0
  22. mindroom/credentials.py +146 -0
  23. mindroom/credentials_sync.py +134 -0
  24. mindroom/custom_tools/__init__.py +8 -0
  25. mindroom/custom_tools/config_manager.py +765 -0
  26. mindroom/custom_tools/gmail.py +92 -0
  27. mindroom/custom_tools/google_calendar.py +92 -0
  28. mindroom/custom_tools/google_sheets.py +92 -0
  29. mindroom/custom_tools/homeassistant.py +341 -0
  30. mindroom/error_handling.py +35 -0
  31. mindroom/file_watcher.py +49 -0
  32. mindroom/interactive.py +313 -0
  33. mindroom/logging_config.py +207 -0
  34. mindroom/matrix/__init__.py +1 -0
  35. mindroom/matrix/client.py +782 -0
  36. mindroom/matrix/event_info.py +173 -0
  37. mindroom/matrix/identity.py +149 -0
  38. mindroom/matrix/large_messages.py +267 -0
  39. mindroom/matrix/mentions.py +141 -0
  40. mindroom/matrix/message_builder.py +94 -0
  41. mindroom/matrix/message_content.py +209 -0
  42. mindroom/matrix/presence.py +178 -0
  43. mindroom/matrix/rooms.py +311 -0
  44. mindroom/matrix/state.py +77 -0
  45. mindroom/matrix/typing.py +91 -0
  46. mindroom/matrix/users.py +217 -0
  47. mindroom/memory/__init__.py +21 -0
  48. mindroom/memory/config.py +137 -0
  49. mindroom/memory/functions.py +396 -0
  50. mindroom/py.typed +0 -0
  51. mindroom/response_tracker.py +128 -0
  52. mindroom/room_cleanup.py +139 -0
  53. mindroom/routing.py +107 -0
  54. mindroom/scheduling.py +758 -0
  55. mindroom/stop.py +207 -0
  56. mindroom/streaming.py +203 -0
  57. mindroom/teams.py +749 -0
  58. mindroom/thread_utils.py +318 -0
  59. mindroom/tools/__init__.py +520 -0
  60. mindroom/tools/agentql.py +64 -0
  61. mindroom/tools/airflow.py +57 -0
  62. mindroom/tools/apify.py +49 -0
  63. mindroom/tools/arxiv.py +64 -0
  64. mindroom/tools/aws_lambda.py +41 -0
  65. mindroom/tools/aws_ses.py +57 -0
  66. mindroom/tools/baidusearch.py +87 -0
  67. mindroom/tools/brightdata.py +116 -0
  68. mindroom/tools/browserbase.py +62 -0
  69. mindroom/tools/cal_com.py +98 -0
  70. mindroom/tools/calculator.py +112 -0
  71. mindroom/tools/cartesia.py +84 -0
  72. mindroom/tools/composio.py +166 -0
  73. mindroom/tools/config_manager.py +44 -0
  74. mindroom/tools/confluence.py +73 -0
  75. mindroom/tools/crawl4ai.py +101 -0
  76. mindroom/tools/csv.py +104 -0
  77. mindroom/tools/custom_api.py +106 -0
  78. mindroom/tools/dalle.py +85 -0
  79. mindroom/tools/daytona.py +180 -0
  80. mindroom/tools/discord.py +81 -0
  81. mindroom/tools/docker.py +73 -0
  82. mindroom/tools/duckdb.py +124 -0
  83. mindroom/tools/duckduckgo.py +99 -0
  84. mindroom/tools/e2b.py +121 -0
  85. mindroom/tools/eleven_labs.py +77 -0
  86. mindroom/tools/email.py +74 -0
  87. mindroom/tools/exa.py +246 -0
  88. mindroom/tools/fal.py +50 -0
  89. mindroom/tools/file.py +80 -0
  90. mindroom/tools/financial_datasets_api.py +112 -0
  91. mindroom/tools/firecrawl.py +124 -0
  92. mindroom/tools/gemini.py +85 -0
  93. mindroom/tools/giphy.py +49 -0
  94. mindroom/tools/github.py +376 -0
  95. mindroom/tools/gmail.py +102 -0
  96. mindroom/tools/google_calendar.py +55 -0
  97. mindroom/tools/google_maps.py +112 -0
  98. mindroom/tools/google_sheets.py +86 -0
  99. mindroom/tools/googlesearch.py +83 -0
  100. mindroom/tools/groq.py +77 -0
  101. mindroom/tools/hackernews.py +54 -0
  102. mindroom/tools/jina.py +108 -0
  103. mindroom/tools/jira.py +70 -0
  104. mindroom/tools/linear.py +103 -0
  105. mindroom/tools/linkup.py +65 -0
  106. mindroom/tools/lumalabs.py +71 -0
  107. mindroom/tools/mem0.py +82 -0
  108. mindroom/tools/modelslabs.py +85 -0
  109. mindroom/tools/moviepy_video_tools.py +62 -0
  110. mindroom/tools/newspaper4k.py +63 -0
  111. mindroom/tools/openai.py +143 -0
  112. mindroom/tools/openweather.py +89 -0
  113. mindroom/tools/oxylabs.py +54 -0
  114. mindroom/tools/pandas.py +35 -0
  115. mindroom/tools/pubmed.py +64 -0
  116. mindroom/tools/python.py +120 -0
  117. mindroom/tools/reddit.py +155 -0
  118. mindroom/tools/replicate.py +56 -0
  119. mindroom/tools/resend.py +55 -0
  120. mindroom/tools/scrapegraph.py +87 -0
  121. mindroom/tools/searxng.py +120 -0
  122. mindroom/tools/serpapi.py +55 -0
  123. mindroom/tools/serper.py +81 -0
  124. mindroom/tools/shell.py +46 -0
  125. mindroom/tools/slack.py +80 -0
  126. mindroom/tools/sleep.py +38 -0
  127. mindroom/tools/spider.py +62 -0
  128. mindroom/tools/sql.py +138 -0
  129. mindroom/tools/tavily.py +104 -0
  130. mindroom/tools/telegram.py +54 -0
  131. mindroom/tools/todoist.py +103 -0
  132. mindroom/tools/trello.py +121 -0
  133. mindroom/tools/twilio.py +97 -0
  134. mindroom/tools/web_browser_tools.py +37 -0
  135. mindroom/tools/webex.py +63 -0
  136. mindroom/tools/website.py +45 -0
  137. mindroom/tools/whatsapp.py +81 -0
  138. mindroom/tools/wikipedia.py +45 -0
  139. mindroom/tools/x.py +97 -0
  140. mindroom/tools/yfinance.py +121 -0
  141. mindroom/tools/youtube.py +81 -0
  142. mindroom/tools/zendesk.py +62 -0
  143. mindroom/tools/zep.py +107 -0
  144. mindroom/tools/zoom.py +62 -0
  145. mindroom/tools_metadata.json +7643 -0
  146. mindroom/tools_metadata.py +220 -0
  147. mindroom/topic_generator.py +153 -0
  148. mindroom/voice_handler.py +266 -0
  149. mindroom-0.1.1.dist-info/METADATA +425 -0
  150. mindroom-0.1.1.dist-info/RECORD +152 -0
  151. {mindroom-0.0.0.dist-info → mindroom-0.1.1.dist-info}/WHEEL +1 -2
  152. mindroom-0.1.1.dist-info/entry_points.txt +2 -0
  153. mindroom-0.0.0.dist-info/METADATA +0 -24
  154. mindroom-0.0.0.dist-info/RECORD +0 -4
  155. mindroom-0.0.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,765 @@
1
+ """Consolidated ConfigManager tool for building and managing MindRoom agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ import yaml
11
+ from agno.tools import Toolkit
12
+
13
+ from mindroom.commands import get_command_help
14
+ from mindroom.config import AgentConfig, Config, TeamConfig
15
+ from mindroom.constants import DEFAULT_AGENTS_CONFIG
16
+ from mindroom.logging_config import get_logger
17
+ from mindroom.tools_metadata import TOOL_METADATA, TOOL_REGISTRY, ToolCategory, ToolStatus
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class InfoType(str, Enum):
23
+ """Types of information that can be retrieved."""
24
+
25
+ MINDROOM_DOCS = "mindroom_docs"
26
+ CONFIG_SCHEMA = "config_schema"
27
+ AVAILABLE_MODELS = "available_models"
28
+ AGENTS = "agents"
29
+ TEAMS = "teams"
30
+ AVAILABLE_TOOLS = "available_tools"
31
+ TOOL_DETAILS = "tool_details"
32
+ AGENT_CONFIG = "agent_config"
33
+ AGENT_TEMPLATE = "agent_template"
34
+
35
+
36
+ class ConfigManagerTools(Toolkit):
37
+ """Consolidated tools for managing MindRoom agent configurations.
38
+
39
+ This toolkit provides comprehensive agent building capabilities with a minimal
40
+ number of tools to reduce cognitive load on AI models.
41
+ """
42
+
43
+ def __init__(self, config_path: Path | None = None) -> None:
44
+ """Initialize the ConfigManager toolkit.
45
+
46
+ Args:
47
+ config_path: Optional path to configuration file
48
+
49
+ """
50
+ self.config_path = config_path or DEFAULT_AGENTS_CONFIG
51
+ self._mindroom_docs: str | None = None
52
+ self._help_text: str | None = None
53
+
54
+ # Register only the consolidated tools
55
+ super().__init__(
56
+ name="config_manager",
57
+ tools=[
58
+ self.get_info,
59
+ self.manage_agent,
60
+ self.manage_team,
61
+ ],
62
+ )
63
+
64
+ def get_info( # noqa: C901, PLR0911, PLR0912
65
+ self,
66
+ info_type: str,
67
+ name: str | None = None,
68
+ ) -> str:
69
+ """Get various types of information about MindRoom, agents, tools, and configuration.
70
+
71
+ Args:
72
+ info_type: Type of information to retrieve. Options:
73
+ - "mindroom_docs": MindRoom documentation and help
74
+ - "config_schema": Configuration schema for agents and teams
75
+ - "available_models": List of configured AI models
76
+ - "agents": List all configured agents
77
+ - "teams": List all configured teams
78
+ - "available_tools": List all available tools by category
79
+ - "tool_details": Get details about a specific tool (requires name)
80
+ - "agent_config": Get configuration for a specific agent (requires name)
81
+ - "agent_template": Generate template for agent type (requires name as type)
82
+ name: Optional name/identifier for specific queries (tool name, agent name, or template type)
83
+
84
+ Returns:
85
+ Requested information as formatted string
86
+
87
+ """
88
+ try:
89
+ if info_type == InfoType.MINDROOM_DOCS:
90
+ return self._get_mindroom_info()
91
+ if info_type == InfoType.CONFIG_SCHEMA:
92
+ return self._get_config_schema()
93
+ if info_type == InfoType.AVAILABLE_MODELS:
94
+ return self._get_available_models()
95
+ if info_type == InfoType.AGENTS:
96
+ return self._list_agents()
97
+ if info_type == InfoType.TEAMS:
98
+ return self._list_teams()
99
+ if info_type == InfoType.AVAILABLE_TOOLS:
100
+ return self._list_available_tools()
101
+ if info_type == InfoType.TOOL_DETAILS:
102
+ if not name:
103
+ return "Error: tool_details requires 'name' parameter with the tool name"
104
+ return self._get_tool_details(name)
105
+ if info_type == InfoType.AGENT_CONFIG:
106
+ if not name:
107
+ return "Error: agent_config requires 'name' parameter with the agent name"
108
+ return self._get_agent_config(name)
109
+ if info_type == InfoType.AGENT_TEMPLATE:
110
+ if not name:
111
+ return "Error: agent_template requires 'name' parameter with the template type (researcher, developer, social, communicator, analyst, productivity)"
112
+ return self._generate_agent_template(name)
113
+ return f"Error: Unknown info_type '{info_type}'. Valid options: {', '.join([t.value for t in InfoType])}"
114
+ except Exception as e:
115
+ logger.exception(f"Failed to get info for type {info_type}")
116
+ return f"Error getting {info_type}: {e}"
117
+
118
+ def manage_agent(
119
+ self,
120
+ operation: Literal["create", "update", "validate"],
121
+ agent_name: str,
122
+ display_name: str | None = None,
123
+ role: str | None = None,
124
+ tools: list[str] | None = None,
125
+ instructions: list[str] | None = None,
126
+ model: str | None = None,
127
+ rooms: list[str] | None = None,
128
+ num_history_runs: int | None = None,
129
+ markdown: bool | None = None,
130
+ add_history_to_messages: bool | None = None,
131
+ ) -> str:
132
+ """Manage agent configurations - create, update, or validate agents.
133
+
134
+ Args:
135
+ operation: Operation to perform - "create", "update", or "validate"
136
+ agent_name: Internal name for the agent (alphanumeric, lowercase)
137
+ display_name: Human-readable display name (required for create)
138
+ role: Description of the agent's purpose (required for create)
139
+ tools: List of tool names to enable for the agent
140
+ instructions: List of instructions for the agent
141
+ model: Model to use (default: "default")
142
+ rooms: List of room IDs or names to auto-join
143
+ num_history_runs: Number of history runs to include
144
+ markdown: Whether to use markdown formatting
145
+ add_history_to_messages: Whether to add history to messages
146
+
147
+ Returns:
148
+ Success message or error details
149
+
150
+ """
151
+ if operation == "create":
152
+ if not display_name:
153
+ return "Error: display_name is required for create operation"
154
+ if role is None:
155
+ role = ""
156
+ return self._create_agent_config(
157
+ agent_name=agent_name,
158
+ display_name=display_name,
159
+ role=role,
160
+ tools=tools or [],
161
+ instructions=instructions or [],
162
+ model=model or "default",
163
+ rooms=rooms or [],
164
+ num_history_runs=num_history_runs,
165
+ markdown=markdown,
166
+ add_history_to_messages=add_history_to_messages,
167
+ )
168
+ if operation == "update":
169
+ return self._update_agent_config(
170
+ agent_name=agent_name,
171
+ display_name=display_name,
172
+ role=role,
173
+ tools=tools,
174
+ instructions=instructions,
175
+ model=model,
176
+ rooms=rooms,
177
+ num_history_runs=num_history_runs,
178
+ markdown=markdown,
179
+ add_history_to_messages=add_history_to_messages,
180
+ )
181
+ if operation == "validate":
182
+ return self._validate_agent_config(agent_name)
183
+ return f"Error: Unknown operation '{operation}'. Valid options: create, update, validate"
184
+
185
+ def manage_team(
186
+ self,
187
+ team_name: str,
188
+ display_name: str,
189
+ role: str,
190
+ agents: list[str],
191
+ mode: str = "coordinate",
192
+ ) -> str:
193
+ """Create or manage team configurations.
194
+
195
+ Args:
196
+ team_name: Internal name for the team
197
+ display_name: Human-readable display name
198
+ role: Description of the team's purpose
199
+ agents: List of agent names that compose this team
200
+ mode: Team mode - "coordinate" or "collaborate"
201
+
202
+ Returns:
203
+ Success message or error details
204
+
205
+ """
206
+ return self._create_team_config(team_name, display_name, role, agents, mode)
207
+
208
+ # ===== Internal helper methods (not exposed as tools) =====
209
+
210
+ def _load_mindroom_docs(self) -> str:
211
+ """Load MindRoom documentation once and cache it."""
212
+ if self._mindroom_docs is None:
213
+ readme_path = Path(__file__).parent.parent.parent.parent / "README.md"
214
+ try:
215
+ with readme_path.open() as f:
216
+ self._mindroom_docs = f.read()
217
+ except Exception as e:
218
+ logger.warning(f"Could not load README.md: {e}")
219
+ self._mindroom_docs = "README.md not available"
220
+ return self._mindroom_docs
221
+
222
+ def _load_help_text(self) -> str:
223
+ """Load help text once and cache it."""
224
+ if self._help_text is None:
225
+ self._help_text = get_command_help()
226
+ return self._help_text
227
+
228
+ def _get_available_models(self) -> str:
229
+ """Get the list of configured models from the current configuration."""
230
+ try:
231
+ config = Config.from_yaml(self.config_path)
232
+
233
+ output = ["# Available Models\n"]
234
+
235
+ if not config.models:
236
+ return "No models configured in the system."
237
+
238
+ output.append("These models are currently configured and can be used:\n")
239
+
240
+ for model_name, model_config in config.models.items():
241
+ provider = model_config.provider
242
+ model_id = model_config.id
243
+
244
+ output.append(f"## `{model_name}`")
245
+ output.append(f"- **Provider**: {provider}")
246
+ output.append(f"- **Model ID**: {model_id}")
247
+
248
+ if model_config.host:
249
+ output.append(f"- **Host**: {model_config.host}")
250
+
251
+ if model_name == "default":
252
+ output.append("- **Note**: This is typically the system default model")
253
+
254
+ output.append("")
255
+
256
+ if config.router and config.router.model:
257
+ output.append("## Router Configuration")
258
+ output.append(f"The router uses model: `{config.router.model}`")
259
+ output.append("")
260
+
261
+ return "\n".join(output)
262
+ except Exception as e:
263
+ return f"Error loading model configuration: {e}"
264
+
265
+ def _format_schema_field(self, field: str, info: dict, required_fields: list) -> list[str]:
266
+ """Format a single schema field for display."""
267
+ lines = []
268
+ required = field in required_fields
269
+ field_type = info.get("type", "unknown")
270
+ description = info.get("description", "")
271
+ default = info.get("default")
272
+
273
+ if "enum" in info:
274
+ field_type = f"enum: {info['enum']}"
275
+
276
+ lines.append(f"{field}: # {field_type}")
277
+ if description:
278
+ lines.append(f" # {description}")
279
+ if required:
280
+ lines.append(" # REQUIRED")
281
+ elif default is not None:
282
+ lines.append(f" # Default: {default}")
283
+ lines.append("")
284
+ return lines
285
+
286
+ def _get_config_schema(self) -> str:
287
+ """Get the JSON schema for MindRoom configuration."""
288
+ output = ["# MindRoom Configuration Schema\n"]
289
+
290
+ agent_schema = AgentConfig.model_json_schema()
291
+ team_schema = TeamConfig.model_json_schema()
292
+
293
+ output.append("## Agent Configuration Fields\n")
294
+ output.append("```yaml")
295
+ output.append("# Required fields:")
296
+ for field, info in agent_schema.get("properties", {}).items():
297
+ output.extend(self._format_schema_field(field, info, agent_schema.get("required", [])))
298
+ output.append("```\n")
299
+
300
+ output.append("## Team Configuration Fields\n")
301
+ output.append("```yaml")
302
+ for field, info in team_schema.get("properties", {}).items():
303
+ output.extend(self._format_schema_field(field, info, team_schema.get("required", [])))
304
+ output.append("```\n")
305
+
306
+ output.append("## Team Modes")
307
+ if "properties" in team_schema and "mode" in team_schema["properties"]:
308
+ mode_info = team_schema["properties"]["mode"]
309
+ if "enum" in mode_info:
310
+ output.extend(f"- `{mode}`: {mode.title()} mode" for mode in mode_info["enum"])
311
+
312
+ return "\n".join(output)
313
+
314
+ def _get_mindroom_info(self) -> str:
315
+ """Get comprehensive information about MindRoom."""
316
+ docs = self._load_mindroom_docs()
317
+ help_text = self._load_help_text()
318
+
319
+ return f"""# MindRoom Documentation
320
+
321
+ ## README Content:
322
+ {docs}
323
+
324
+ ## Available Commands:
325
+ {help_text}
326
+
327
+ ## Key Concepts:
328
+ - **Agents**: AI assistants with specific roles and tools
329
+ - **Teams**: Groups of agents that collaborate
330
+ - **Tools**: Integrations that give agents capabilities (80+ available)
331
+ - **Memory**: Persistent conversation memory across sessions
332
+ - **Threading**: Agents respond in threads for organized conversations
333
+ - **Routing**: Smart agent selection based on message content
334
+ - **Commands**: Special !commands for configuration and control
335
+ """
336
+
337
+ def _list_agents(self) -> str:
338
+ """List all configured agents and their details."""
339
+ try:
340
+ config = Config.from_yaml(self.config_path)
341
+ agents_info = []
342
+
343
+ for name, agent in config.agents.items():
344
+ tools_str = ", ".join(agent.tools) if agent.tools else "No tools"
345
+ role_line = f" - Role: {agent.role[:100]}..." if len(agent.role) > 100 else f" - Role: {agent.role}"
346
+ agents_info.append(
347
+ f"**{name}** ({agent.display_name})\n"
348
+ f"{role_line}\n"
349
+ f" - Tools: {tools_str}\n"
350
+ f" - Model: {agent.model}\n",
351
+ )
352
+
353
+ if not agents_info:
354
+ return "No agents configured yet."
355
+
356
+ return "## Configured Agents:\n\n" + "\n".join(agents_info)
357
+ except Exception as e:
358
+ return f"Error loading agents: {e}"
359
+
360
+ def _list_teams(self) -> str:
361
+ """List all configured teams and their composition."""
362
+ try:
363
+ config = Config.from_yaml(self.config_path)
364
+ teams_info = []
365
+
366
+ for name, team in config.teams.items():
367
+ agents_str = ", ".join(team.agents)
368
+ teams_info.append(
369
+ f"**{name}** ({team.display_name})\n"
370
+ f" - Role: {team.role}\n"
371
+ f" - Agents: {agents_str}\n"
372
+ f" - Mode: {team.mode}\n",
373
+ )
374
+
375
+ if not teams_info:
376
+ return "No teams configured yet."
377
+
378
+ return "## Configured Teams:\n\n" + "\n".join(teams_info)
379
+ except Exception as e:
380
+ return f"Error loading teams: {e}"
381
+
382
+ def _list_available_tools(self) -> str:
383
+ """List all available tools that can be used by agents."""
384
+ tools_by_category: dict[str, list[tuple[str, str]]] = {}
385
+
386
+ for tool_name in sorted(TOOL_REGISTRY.keys()):
387
+ if tool_name in TOOL_METADATA:
388
+ metadata = TOOL_METADATA[tool_name]
389
+ category = metadata.category.value
390
+ description = metadata.description
391
+
392
+ if category not in tools_by_category:
393
+ tools_by_category[category] = []
394
+ tools_by_category[category].append((tool_name, description))
395
+ else:
396
+ if "uncategorized" not in tools_by_category:
397
+ tools_by_category["uncategorized"] = []
398
+ tools_by_category["uncategorized"].append((tool_name, "No description available"))
399
+
400
+ output = ["## Available Tools by Category:\n"]
401
+ for category in sorted(tools_by_category.keys()):
402
+ output.append(f"\n### {category.title()}:")
403
+ for tool_name, description in tools_by_category[category]:
404
+ output.append(f"- **{tool_name}**: {description}")
405
+
406
+ return "\n".join(output)
407
+
408
+ def _get_tool_details(self, tool_name: str) -> str:
409
+ """Get detailed information about a specific tool."""
410
+ if tool_name not in TOOL_REGISTRY:
411
+ available = ", ".join(sorted(TOOL_REGISTRY.keys()))
412
+ return f"Unknown tool: {tool_name}\n\nAvailable tools: {available}"
413
+
414
+ output = [f"## Tool: {tool_name}\n"]
415
+
416
+ if tool_name in TOOL_METADATA:
417
+ metadata = TOOL_METADATA[tool_name]
418
+ output.append(f"**Display Name**: {metadata.display_name}")
419
+ output.append(f"**Description**: {metadata.description}")
420
+ output.append(f"**Category**: {metadata.category.value}")
421
+ output.append(f"**Status**: {metadata.status.value}")
422
+ output.append(f"**Setup Type**: {metadata.setup_type.value}")
423
+
424
+ if metadata.config_fields:
425
+ output.append("\n**Configuration Fields**:")
426
+ for field in metadata.config_fields:
427
+ required = "Required" if field.required else "Optional"
428
+ output.append(f"- **{field.name}** ({field.type}, {required}): {field.description}")
429
+ if field.default is not None:
430
+ output.append(f" Default: {field.default}")
431
+
432
+ if metadata.dependencies:
433
+ output.append(f"\n**Dependencies**: {', '.join(metadata.dependencies)}")
434
+
435
+ if metadata.docs_url:
436
+ output.append(f"\n**Documentation**: {metadata.docs_url}")
437
+ else:
438
+ output.append("No metadata available for this tool.")
439
+
440
+ return "\n".join(output)
441
+
442
+ def _create_agent_config(
443
+ self,
444
+ agent_name: str,
445
+ display_name: str,
446
+ role: str,
447
+ tools: list[str],
448
+ instructions: list[str],
449
+ model: str,
450
+ rooms: list[str],
451
+ num_history_runs: int | None,
452
+ markdown: bool | None,
453
+ add_history_to_messages: bool | None,
454
+ ) -> str:
455
+ """Create a new agent configuration."""
456
+ # Validate agent name
457
+ if not re.match(r"^[a-z0-9_]+$", agent_name):
458
+ return "Error: Agent name must be lowercase alphanumeric with underscores only"
459
+
460
+ # Validate tools
461
+ invalid_tools = [t for t in tools if t not in TOOL_REGISTRY]
462
+ if invalid_tools:
463
+ return f"Error: Unknown tools: {', '.join(invalid_tools)}\n\nUse get_info with info_type='available_tools' to see valid tools."
464
+
465
+ try:
466
+ config = Config.from_yaml(self.config_path)
467
+
468
+ if agent_name in config.agents:
469
+ return f"Error: Agent '{agent_name}' already exists. Use manage_agent with operation='update' to modify it."
470
+
471
+ # Create new agent config
472
+ new_agent = AgentConfig(
473
+ display_name=display_name,
474
+ role=role,
475
+ tools=tools,
476
+ instructions=instructions,
477
+ model=model,
478
+ rooms=rooms,
479
+ num_history_runs=num_history_runs,
480
+ markdown=markdown,
481
+ add_history_to_messages=add_history_to_messages,
482
+ )
483
+
484
+ # Add to config
485
+ config.agents[agent_name] = new_agent
486
+
487
+ # Save config
488
+ config.save_to_yaml(self.config_path)
489
+
490
+ # Build success message
491
+ tools_str = ", ".join(tools) if tools else "None"
492
+ rooms_str = ", ".join(rooms) if rooms else "None"
493
+ return ( # noqa: TRY300
494
+ f"✅ Successfully created agent '{agent_name}'!\n\n"
495
+ f"**Configuration:**\n"
496
+ f"- Display Name: {display_name}\n"
497
+ f"- Role: {role}\n"
498
+ f"- Tools: {tools_str}\n"
499
+ f"- Model: {model}\n"
500
+ f"- Rooms: {rooms_str}\n\n"
501
+ f"The agent is now available and can be mentioned with @{agent_name}"
502
+ )
503
+ except Exception as e:
504
+ logger.exception("Failed to create agent")
505
+ return f"Error creating agent: {e}"
506
+
507
+ def _update_agent_config(
508
+ self,
509
+ agent_name: str,
510
+ display_name: str | None,
511
+ role: str | None,
512
+ tools: list[str] | None,
513
+ instructions: list[str] | None,
514
+ model: str | None,
515
+ rooms: list[str] | None,
516
+ num_history_runs: int | None,
517
+ markdown: bool | None,
518
+ add_history_to_messages: bool | None,
519
+ ) -> str:
520
+ """Update an existing agent configuration."""
521
+ try:
522
+ config = Config.from_yaml(self.config_path)
523
+
524
+ if agent_name not in config.agents:
525
+ return f"Error: Agent '{agent_name}' not found. Use manage_agent with operation='create' to create it."
526
+
527
+ agent = config.agents[agent_name]
528
+
529
+ # Validate tools if provided
530
+ if tools is not None:
531
+ invalid_tools = [t for t in tools if t not in TOOL_REGISTRY]
532
+ if invalid_tools:
533
+ return f"Error: Unknown tools: {', '.join(invalid_tools)}"
534
+
535
+ # Map of field names to (new_value, display_formatter)
536
+ updates = {
537
+ "display_name": (display_name, lambda v: v),
538
+ "role": (role, lambda v: v),
539
+ "tools": (tools, lambda v: ", ".join(v) if v else "(empty)"),
540
+ "instructions": (instructions, lambda v: f"{len(v)} instructions" if v else "(empty)"),
541
+ "model": (model, lambda v: v),
542
+ "rooms": (rooms, lambda v: ", ".join(v) if v else "(empty)"),
543
+ "num_history_runs": (num_history_runs, lambda v: str(v)),
544
+ "markdown": (markdown, lambda v: str(v)),
545
+ "add_history_to_messages": (add_history_to_messages, lambda v: str(v)),
546
+ }
547
+
548
+ # Apply updates and track changes
549
+ changes = []
550
+ for field_name, (new_value, formatter) in updates.items():
551
+ if new_value is not None:
552
+ current_value = getattr(agent, field_name)
553
+ if new_value != current_value:
554
+ setattr(agent, field_name, new_value)
555
+ display_name = field_name.replace("_", " ").title()
556
+ changes.append(f"{display_name} -> {formatter(new_value)}")
557
+
558
+ if not changes:
559
+ return "No changes made. All provided values are the same as current configuration."
560
+
561
+ # Save config
562
+ config.save_to_yaml(self.config_path)
563
+
564
+ return f"✅ Successfully updated agent '{agent_name}'!\n\n**Changes:**\n" + "\n".join(
565
+ f"- {c}" for c in changes
566
+ )
567
+ except Exception as e:
568
+ logger.exception("Failed to update agent")
569
+ return f"Error updating agent: {e}"
570
+
571
+ def _create_team_config(
572
+ self,
573
+ team_name: str,
574
+ display_name: str,
575
+ role: str,
576
+ agents: list[str],
577
+ mode: str = "coordinate",
578
+ ) -> str:
579
+ """Create a new team configuration."""
580
+ if mode not in ["coordinate", "collaborate"]:
581
+ return "Error: Team mode must be 'coordinate' or 'collaborate'"
582
+
583
+ try:
584
+ config = Config.from_yaml(self.config_path)
585
+
586
+ if team_name in config.teams:
587
+ return f"Error: Team '{team_name}' already exists."
588
+
589
+ # Validate agents exist
590
+ invalid_agents = [a for a in agents if a not in config.agents]
591
+ if invalid_agents:
592
+ return f"Error: Unknown agents: {', '.join(invalid_agents)}"
593
+
594
+ # Create new team config
595
+ new_team = TeamConfig(
596
+ display_name=display_name,
597
+ role=role,
598
+ agents=agents,
599
+ mode=mode,
600
+ )
601
+
602
+ # Add to config
603
+ config.teams[team_name] = new_team
604
+
605
+ # Save config
606
+ config.save_to_yaml(self.config_path)
607
+
608
+ return (
609
+ f"✅ Successfully created team '{team_name}'!\n\n"
610
+ f"**Configuration:**\n"
611
+ f"- Display Name: {display_name}\n"
612
+ f"- Role: {role}\n"
613
+ f"- Agents: {', '.join(agents)}\n"
614
+ f"- Mode: {mode}\n\n"
615
+ f"The team can now be mentioned with @{team_name}"
616
+ )
617
+ except Exception as e:
618
+ logger.exception("Failed to create team")
619
+ return f"Error creating team: {e}"
620
+
621
+ def _validate_agent_config(self, agent_name: str) -> str: # noqa: C901, PLR0912
622
+ """Validate an agent's configuration."""
623
+ try:
624
+ config = Config.from_yaml(self.config_path)
625
+
626
+ if agent_name not in config.agents:
627
+ return f"Error: Agent '{agent_name}' not found."
628
+
629
+ agent = config.agents[agent_name]
630
+ issues = []
631
+ warnings = []
632
+
633
+ # Check display name
634
+ if not agent.display_name:
635
+ issues.append("Missing display name")
636
+
637
+ # Check role
638
+ if not agent.role:
639
+ warnings.append("No role description provided")
640
+ elif len(agent.role) < 20:
641
+ warnings.append("Role description is very short")
642
+
643
+ # Check tools
644
+ if not agent.tools:
645
+ warnings.append("No tools configured")
646
+ else:
647
+ invalid_tools = [t for t in agent.tools if t not in TOOL_REGISTRY]
648
+ if invalid_tools:
649
+ issues.append(f"Invalid tools: {', '.join(invalid_tools)}")
650
+
651
+ # Check model
652
+ available_models = list(config.models.keys()) if config.models else []
653
+ if available_models and agent.model not in available_models:
654
+ warnings.append(f"Model '{agent.model}' not in configured models: {', '.join(available_models)}")
655
+
656
+ # Format results
657
+ output = [f"## Validation Results for '{agent_name}':\n"]
658
+
659
+ if not issues and not warnings:
660
+ output.append("✅ Configuration is valid!")
661
+ else:
662
+ if issues:
663
+ output.append("### ❌ Issues (must fix):")
664
+ output.extend(f"- {issue}" for issue in issues)
665
+
666
+ if warnings:
667
+ output.append("\n### ⚠️ Warnings (consider fixing):")
668
+ output.extend(f"- {warning}" for warning in warnings)
669
+
670
+ # Add summary
671
+ output.append("\n### Configuration Summary:")
672
+ output.append(f"- Display Name: {agent.display_name}")
673
+ output.append(f"- Role: {agent.role[:100]}..." if len(agent.role) > 100 else f"- Role: {agent.role}")
674
+ output.append(f"- Tools: {', '.join(agent.tools) if agent.tools else 'None'}")
675
+ output.append(f"- Model: {agent.model}")
676
+
677
+ return "\n".join(output)
678
+ except Exception as e:
679
+ return f"Error validating agent: {e}"
680
+
681
+ def _get_agent_config(self, agent_name: str) -> str:
682
+ """Get the full configuration for a specific agent."""
683
+ try:
684
+ config = Config.from_yaml(self.config_path)
685
+
686
+ if agent_name not in config.agents:
687
+ return f"Error: Agent '{agent_name}' not found."
688
+
689
+ agent = config.agents[agent_name]
690
+ agent_dict = agent.model_dump(exclude_none=True)
691
+
692
+ yaml_str = yaml.dump(agent_dict, default_flow_style=False, sort_keys=False)
693
+ except Exception as e:
694
+ return f"Error loading agent config: {e}"
695
+ else:
696
+ return f"## Configuration for '{agent_name}':\n\n```yaml\n{yaml_str}```"
697
+
698
+ def _generate_agent_template(self, agent_type: str) -> str:
699
+ """Generate a template configuration for common agent types."""
700
+ # Map agent types to tool categories
701
+ type_to_category = {
702
+ "researcher": ToolCategory.RESEARCH,
703
+ "developer": ToolCategory.DEVELOPMENT,
704
+ "social": ToolCategory.SOCIAL,
705
+ "communicator": ToolCategory.COMMUNICATION,
706
+ "analyst": ToolCategory.INFORMATION,
707
+ "productivity": ToolCategory.PRODUCTIVITY,
708
+ }
709
+
710
+ if agent_type not in type_to_category:
711
+ available = ", ".join(type_to_category.keys())
712
+ return f"Unknown template type: {agent_type}\n\nAvailable templates: {available}"
713
+
714
+ category = type_to_category[agent_type]
715
+
716
+ # Get tools from this category that are available
717
+ tools = [
718
+ name
719
+ for name, metadata in TOOL_METADATA.items()
720
+ if metadata.category == category and metadata.status == ToolStatus.AVAILABLE
721
+ ][:5] # Limit to 5 tools
722
+
723
+ # Generate role based on category
724
+ role_descriptions = {
725
+ ToolCategory.RESEARCH: "Research specialist focused on finding and analyzing information",
726
+ ToolCategory.DEVELOPMENT: "Software development expert for coding and technical tasks",
727
+ ToolCategory.SOCIAL: "Social interaction specialist for community engagement",
728
+ ToolCategory.COMMUNICATION: "Communication expert for messaging and collaboration",
729
+ ToolCategory.INFORMATION: "Information analyst for data processing and insights",
730
+ ToolCategory.PRODUCTIVITY: "Productivity specialist for task and workflow management",
731
+ }
732
+
733
+ role = role_descriptions.get(category, f"Specialist in {category.value} tasks")
734
+
735
+ # Generic instructions
736
+ instructions = [
737
+ f"Focus on {category.value} tasks",
738
+ "Provide clear and actionable responses",
739
+ "Use available tools effectively",
740
+ ]
741
+
742
+ return f"""## Template for '{agent_type}' agent:
743
+
744
+ ```yaml
745
+ display_name: "{agent_type.title()} Agent"
746
+ role: "{role}"
747
+ tools: {yaml.dump(tools, default_flow_style=True).strip() if tools else "[]"}
748
+ instructions: {yaml.dump(instructions, default_flow_style=False).strip()}
749
+ model: "default"
750
+ ```
751
+
752
+ **Available tools in {category.value} category:**
753
+ {chr(10).join(f"- {name}: {metadata.description}" for name, metadata in TOOL_METADATA.items() if metadata.category == category)}
754
+
755
+ **To create this agent, use:**
756
+ ```
757
+ manage_agent(
758
+ operation="create",
759
+ agent_name="{agent_type}_agent",
760
+ display_name="{agent_type.title()} Agent",
761
+ role="{role}",
762
+ tools={tools},
763
+ instructions={instructions},
764
+ )
765
+ ```"""