agentcrew-ai 0.8.13__py3-none-any.whl → 0.9.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 (55) hide show
  1. AgentCrew/__init__.py +1 -1
  2. AgentCrew/app.py +46 -634
  3. AgentCrew/main_docker.py +1 -30
  4. AgentCrew/modules/a2a/common/client/card_resolver.py +27 -8
  5. AgentCrew/modules/a2a/server.py +5 -0
  6. AgentCrew/modules/a2a/task_manager.py +1 -0
  7. AgentCrew/modules/agents/local_agent.py +2 -2
  8. AgentCrew/modules/chat/message/command_processor.py +33 -8
  9. AgentCrew/modules/chat/message/conversation.py +18 -1
  10. AgentCrew/modules/chat/message/handler.py +5 -1
  11. AgentCrew/modules/code_analysis/service.py +50 -7
  12. AgentCrew/modules/code_analysis/tool.py +9 -8
  13. AgentCrew/modules/console/completers.py +5 -1
  14. AgentCrew/modules/console/console_ui.py +23 -11
  15. AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
  16. AgentCrew/modules/console/conversation_browser/browser.py +84 -0
  17. AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
  18. AgentCrew/modules/console/{conversation_browser.py → conversation_browser/browser_ui.py} +249 -163
  19. AgentCrew/modules/console/conversation_handler.py +34 -1
  20. AgentCrew/modules/console/display_handlers.py +127 -7
  21. AgentCrew/modules/console/visual_mode/__init__.py +5 -0
  22. AgentCrew/modules/console/visual_mode/viewer.py +41 -0
  23. AgentCrew/modules/console/visual_mode/viewer_input_handler.py +315 -0
  24. AgentCrew/modules/console/visual_mode/viewer_ui.py +608 -0
  25. AgentCrew/modules/gui/components/command_handler.py +137 -29
  26. AgentCrew/modules/gui/components/menu_components.py +8 -7
  27. AgentCrew/modules/gui/themes/README.md +30 -14
  28. AgentCrew/modules/gui/themes/__init__.py +2 -1
  29. AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
  30. AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
  31. AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
  32. AgentCrew/modules/gui/themes/nord.yaml +1267 -0
  33. AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
  34. AgentCrew/modules/gui/themes/style_provider.py +78 -264
  35. AgentCrew/modules/gui/themes/theme_loader.py +379 -0
  36. AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
  37. AgentCrew/modules/gui/widgets/configs/global_settings.py +4 -4
  38. AgentCrew/modules/gui/widgets/history_sidebar.py +6 -1
  39. AgentCrew/modules/llm/constants.py +28 -9
  40. AgentCrew/modules/mcpclient/service.py +0 -1
  41. AgentCrew/modules/memory/base_service.py +13 -0
  42. AgentCrew/modules/memory/chroma_service.py +50 -0
  43. AgentCrew/setup.py +470 -0
  44. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/METADATA +1 -1
  45. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/RECORD +49 -40
  46. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/WHEEL +1 -1
  47. AgentCrew/modules/gui/themes/atom_light.py +0 -1365
  48. AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
  49. AgentCrew/modules/gui/themes/dracula.py +0 -1372
  50. AgentCrew/modules/gui/themes/nord.py +0 -1365
  51. AgentCrew/modules/gui/themes/saigontech.py +0 -1359
  52. AgentCrew/modules/gui/themes/unicorn.py +0 -1372
  53. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/entry_points.txt +0 -0
  54. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/licenses/LICENSE +0 -0
  55. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/top_level.txt +0 -0
AgentCrew/app.py CHANGED
@@ -1,51 +1,22 @@
1
1
  import os
2
2
  import sys
3
3
  import json
4
- import time
5
4
  import asyncio
6
- import webbrowser
5
+ import functools
7
6
  import nest_asyncio
8
7
  from typing import Optional, Dict, Any, List
9
8
 
10
9
  import click
11
- import requests
12
10
 
11
+ from AgentCrew.setup import ApplicationSetup, PROVIDER_LIST
13
12
  from AgentCrew.modules.config import ConfigManagement
14
- from AgentCrew.modules.llm.model_registry import ModelRegistry
15
13
  from AgentCrew.modules.llm.service_manager import ServiceManager
16
- from AgentCrew.modules.memory.chroma_service import ChromaMemoryService
17
- from AgentCrew.modules.memory.context_persistent import ContextPersistenceService
18
- from AgentCrew.modules.clipboard import ClipboardService
19
- from AgentCrew.modules.web_search import TavilySearchService
20
- from AgentCrew.modules.code_analysis import CodeAnalysisService
21
- from AgentCrew.modules.image_generation import ImageGenerationService
22
- from AgentCrew.modules.browser_automation import BrowserAutomationService
23
- from AgentCrew.modules.agents.manager import AgentManager
24
14
  from AgentCrew.modules.agents.local_agent import LocalAgent
25
- from AgentCrew.modules.agents.remote_agent import RemoteAgent
26
- from AgentCrew.modules.agents.example import (
27
- DEFAULT_NAME,
28
- DEFAULT_DESCRIPTION,
29
- DEFAULT_PROMPT,
30
- )
31
15
 
32
16
  nest_asyncio.apply()
33
17
 
34
18
 
35
- PROVIDER_LIST = [
36
- "claude",
37
- "groq",
38
- "openai",
39
- "google",
40
- "deepinfra",
41
- "github_copilot",
42
- "copilot_response",
43
- ]
44
-
45
-
46
19
  def common_options(func):
47
- import functools
48
-
49
20
  @click.option(
50
21
  "--provider",
51
22
  type=click.Choice(PROVIDER_LIST),
@@ -79,381 +50,18 @@ def common_options(func):
79
50
 
80
51
 
81
52
  class AgentCrewApplication:
82
- """
83
- Centralized application class for AgentCrew.
84
-
85
- This class handles:
86
- - API key loading from configuration
87
- - Service initialization and management
88
- - Agent setup and configuration
89
- - GitHub Copilot authentication
90
- - Running different modes (console, GUI, server, job)
91
- """
92
-
93
53
  def __init__(self):
94
- """Initialize the AgentCrew application."""
95
- self.services: Optional[Dict[str, Any]] = None
96
- self.agent_manager: Optional[AgentManager] = None
97
54
  self.config_manager = ConfigManagement()
55
+ self.setup = ApplicationSetup(self.config_manager)
56
+ self.setup.load_api_keys_from_config()
98
57
 
99
- self.load_api_keys_from_config()
100
-
101
- def load_api_keys_from_config(self) -> None:
102
- """
103
- Loads API keys from the global config file and sets them as environment variables,
104
- prioritizing them over any existing environment variables.
105
- """
106
- config_file_path = os.getenv("AGENTCREW_CONFIG_PATH")
107
- if not config_file_path:
108
- # Default for when AGENTCREW_CONFIG_PATH is not set (e.g. dev mode, not using cli_prod)
109
- config_file_path = "./config.json"
110
- config_file_path = os.path.expanduser(config_file_path)
111
-
112
- api_keys_config = {}
113
- if os.path.exists(config_file_path):
114
- try:
115
- with open(config_file_path, "r", encoding="utf-8") as f:
116
- loaded_config = json.load(f)
117
- if isinstance(loaded_config, dict) and isinstance(
118
- loaded_config.get("api_keys"), dict
119
- ):
120
- api_keys_config = loaded_config["api_keys"]
121
- else:
122
- click.echo(
123
- f"⚠️ API keys in {config_file_path} are not in the expected format.",
124
- err=True,
125
- )
126
- except json.JSONDecodeError:
127
- click.echo(
128
- f"⚠️ Error decoding API keys from {config_file_path}.", err=True
129
- )
130
- except Exception as e:
131
- click.echo(
132
- f"⚠️ Could not load API keys from {config_file_path}: {e}",
133
- err=True,
134
- )
135
-
136
- keys_to_check = [
137
- "ANTHROPIC_API_KEY",
138
- "GEMINI_API_KEY",
139
- "OPENAI_API_KEY",
140
- "GROQ_API_KEY",
141
- "DEEPINFRA_API_KEY",
142
- "GITHUB_COPILOT_API_KEY",
143
- "TAVILY_API_KEY",
144
- "VOYAGE_API_KEY",
145
- "ELEVENLABS_API_KEY",
146
- ]
147
-
148
- for key_name in keys_to_check:
149
- if key_name in api_keys_config and api_keys_config[key_name]:
150
- # Prioritize config file over existing environment variables
151
- os.environ[key_name] = str(api_keys_config[key_name]).strip()
152
-
153
- def _detect_provider(self) -> Optional[str]:
154
- """
155
- Detect available LLM provider from environment or last used provider.
156
-
157
- Returns:
158
- Provider name or None if no provider found
159
- """
160
- # Try to restore last used provider
161
- try:
162
- last_provider = self.config_manager.get_last_used_provider()
163
- if last_provider:
164
- # Verify the provider is still available
165
- if last_provider in PROVIDER_LIST:
166
- # Check if API key is available for this provider
167
- api_key_map = {
168
- "claude": "ANTHROPIC_API_KEY",
169
- "google": "GEMINI_API_KEY",
170
- "openai": "OPENAI_API_KEY",
171
- "groq": "GROQ_API_KEY",
172
- "deepinfra": "DEEPINFRA_API_KEY",
173
- "github_copilot": "GITHUB_COPILOT_API_KEY",
174
- "copilot_response": "GITHUB_COPILOT_API_KEY",
175
- }
176
- if os.getenv(api_key_map.get(last_provider, "")):
177
- return last_provider
178
- else:
179
- # Check if it's a custom provider
180
- custom_providers = (
181
- self.config_manager.read_custom_llm_providers_config()
182
- )
183
- if any(p["name"] == last_provider for p in custom_providers):
184
- return last_provider
185
- except Exception as e:
186
- click.echo(f"⚠️ Could not restore last used provider: {e}")
187
-
188
- # Fall back to environment variable detection
189
- if os.getenv("GITHUB_COPILOT_API_KEY"):
190
- return "github_copilot"
191
- elif os.getenv("ANTHROPIC_API_KEY"):
192
- return "claude"
193
- elif os.getenv("GEMINI_API_KEY"):
194
- return "google"
195
- elif os.getenv("OPENAI_API_KEY"):
196
- return "openai"
197
- elif os.getenv("GROQ_API_KEY"):
198
- return "groq"
199
- elif os.getenv("DEEPINFRA_API_KEY"):
200
- return "deepinfra"
201
- else:
202
- custom_providers = self.config_manager.read_custom_llm_providers_config()
203
- if len(custom_providers) > 0:
204
- return custom_providers[0]["name"]
205
-
206
- return None
207
-
208
- def setup_services(
209
- self, provider: str, memory_llm: Optional[str] = None, need_memory: bool = True
210
- ) -> Dict[str, Any]:
211
- """
212
- Initialize and configure all AgentCrew services.
213
-
214
- Args:
215
- provider: LLM provider name (e.g., 'claude', 'openai', 'google')
216
- memory_llm: Optional LLM provider for memory service
217
-
218
- Returns:
219
- Dictionary of initialized services
220
- """
221
- registry = ModelRegistry.get_instance()
222
- llm_manager = ServiceManager.get_instance()
223
-
224
- models = registry.get_models_by_provider(provider)
225
- if models:
226
- default_model = next((m for m in models if m.default), models[0])
227
- registry.set_current_model(f"{default_model.provider}/{default_model.id}")
228
-
229
- llm_service = llm_manager.get_service(provider)
230
-
231
- try:
232
- last_model = self.config_manager.get_last_used_model()
233
- last_provider = self.config_manager.get_last_used_provider()
234
-
235
- if last_model and last_provider:
236
- should_restore = False
237
- if provider == last_provider:
238
- should_restore = True
239
-
240
- last_model_class = registry.get_model(last_model)
241
- if should_restore and last_model_class:
242
- llm_service.model = last_model_class.id
243
- except Exception as e:
244
- click.echo(f"⚠️ Could not restore last used model: {e}")
245
-
246
- memory_service = None
247
- context_service = None
248
- if need_memory:
249
- if memory_llm:
250
- memory_service = ChromaMemoryService(
251
- llm_service=llm_manager.initialize_standalone_service(memory_llm)
252
- )
253
- else:
254
- memory_service = ChromaMemoryService(
255
- llm_service=llm_manager.initialize_standalone_service(provider)
256
- )
257
-
258
- context_service = ContextPersistenceService()
259
- clipboard_service = ClipboardService()
260
-
261
- try:
262
- search_service = TavilySearchService()
263
- except Exception as e:
264
- click.echo(f"⚠️ Web search tools not available: {str(e)}")
265
- search_service = None
266
-
267
- try:
268
- code_analysis_llm = llm_manager.initialize_standalone_service(provider)
269
- code_analysis_service = CodeAnalysisService(llm_service=code_analysis_llm)
270
- except Exception as e:
271
- click.echo(f"⚠️ Code analysis tool not available: {str(e)}")
272
- code_analysis_service = None
273
-
274
- try:
275
- if os.getenv("OPENAI_API_KEY"):
276
- image_gen_service = ImageGenerationService()
277
- else:
278
- image_gen_service = None
279
- click.echo(
280
- "⚠️ Image generation service not available: No API keys found."
281
- )
282
- except Exception as e:
283
- click.echo(f"⚠️ Image generation service not available: {str(e)}")
284
- image_gen_service = None
285
-
286
- try:
287
- browser_automation_service = BrowserAutomationService()
288
- except Exception as e:
289
- click.echo(f"⚠️ Browser automation service not available: {str(e)}")
290
- browser_automation_service = None
291
-
292
- try:
293
- from AgentCrew.modules.file_editing import FileEditingService
294
-
295
- file_editing_service = FileEditingService()
296
- except Exception as e:
297
- click.echo(f"⚠️ File editing service not available: {str(e)}")
298
- file_editing_service = None
299
-
300
- try:
301
- from AgentCrew.modules.command_execution import CommandExecutionService
302
-
303
- command_execution_service = CommandExecutionService.get_instance()
304
- except Exception as e:
305
- click.echo(f"⚠️ Command execution service not available: {str(e)}")
306
- command_execution_service = None
307
-
308
- self.services = {
309
- "llm": llm_service,
310
- "memory": memory_service,
311
- "clipboard": clipboard_service,
312
- "code_analysis": code_analysis_service,
313
- "web_search": search_service,
314
- "context_persistent": context_service,
315
- "image_generation": image_gen_service,
316
- "browser": browser_automation_service,
317
- "file_editing": file_editing_service,
318
- "command_execution": command_execution_service,
319
- }
320
- return self.services
321
-
322
- def setup_agents(
323
- self,
324
- services: Dict[str, Any],
325
- config_uri: Optional[str] = None,
326
- remoting_provider: Optional[str] = None,
327
- model_id: Optional[str] = None,
328
- ) -> AgentManager:
329
- """
330
- Set up the agent system with specialized agents.
331
-
332
- Args:
333
- services: Dictionary of services
334
- config_uri: Path to agent configuration file or url
335
- remoting_provider: Provider for remoting mode
336
- model_id: Model ID for remoting mode
337
-
338
- Returns:
339
- Configured AgentManager instance
340
- """
341
-
342
- self.agent_manager = AgentManager.get_instance()
343
- llm_manager = ServiceManager.get_instance()
344
-
345
- services["agent_manager"] = self.agent_manager
346
-
347
- global_config = self.config_manager.read_global_config_data()
348
- self.agent_manager.context_shrink_enabled = global_config.get(
349
- "global_settings", {}
350
- ).get("auto_context_shrink", True)
351
- self.agent_manager.shrink_excluded_list = global_config.get(
352
- "global_settings", {}
353
- ).get("shrink_excluded", [])
354
-
355
- llm_service = services["llm"]
356
-
357
- if config_uri:
358
- os.environ["SW_AGENTS_CONFIG"] = config_uri
359
- else:
360
- config_uri = os.getenv("SW_AGENTS_CONFIG")
361
- if not config_uri:
362
- config_uri = "./agents.toml"
363
- if not os.path.exists(config_uri):
364
- click.echo(
365
- f"Agent configuration not found at {config_uri}. Creating default configuration."
366
- )
367
- os.makedirs(os.path.dirname(config_uri), exist_ok=True)
368
-
369
- default_config = f"""
370
- [[agents]]
371
- name = "{DEFAULT_NAME}"
372
- description = "{DEFAULT_DESCRIPTION}"
373
- system_prompt = '''{DEFAULT_PROMPT}'''
374
- tools = ["memory", "browser", "web_search", "code_analysis"]
375
- """
376
-
377
- with open(config_uri, "w+", encoding="utf-8") as f:
378
- f.write(default_config)
379
-
380
- click.echo(f"Created default agent configuration at {config_uri}")
381
-
382
- agent_definitions = AgentManager.load_agents_from_config(config_uri)
383
-
384
- for agent_def in agent_definitions:
385
- if agent_def.get("base_url", ""):
386
- try:
387
- agent = RemoteAgent(
388
- agent_def["name"],
389
- agent_def.get("base_url"),
390
- headers=agent_def.get("headers", {}),
391
- )
392
- except Exception:
393
- print("Error: cannot connect to remote agent, skipping...")
394
- continue
395
- else:
396
- if remoting_provider:
397
- llm_service = llm_manager.initialize_standalone_service(
398
- remoting_provider
399
- )
400
- if model_id:
401
- llm_service.model = model_id
402
- agent = LocalAgent(
403
- name=agent_def["name"],
404
- description=agent_def["description"],
405
- llm_service=llm_service,
406
- services=services,
407
- tools=agent_def["tools"],
408
- temperature=agent_def.get("temperature", None),
409
- voice_enabled=agent_def.get("voice_enabled", "disabled"),
410
- voice_id=agent_def.get("voice_id", None),
411
- )
412
- agent.set_system_prompt(agent_def["system_prompt"])
413
- if remoting_provider:
414
- agent.set_custom_system_prompt(
415
- self.agent_manager.get_remote_system_prompt()
416
- )
417
- agent.is_remoting_mode = True
418
- agent.activate()
419
- self.agent_manager.register_agent(agent)
420
-
421
- from AgentCrew.modules.mcpclient.tool import register as mcp_register
422
-
423
- mcp_register()
58
+ @property
59
+ def services(self) -> Optional[Dict[str, Any]]:
60
+ return self.setup.services
424
61
 
425
- if remoting_provider:
426
- from AgentCrew.modules.mcpclient import MCPSessionManager
427
-
428
- mcp_manager = MCPSessionManager.get_instance()
429
- mcp_manager.initialize_for_agent()
430
- return self.agent_manager
431
-
432
- return self.agent_manager
433
-
434
- def restore_last_agent(self) -> None:
435
- initial_agent_selected = False
436
- try:
437
- last_agent = self.config_manager.get_last_used_agent()
438
-
439
- if (
440
- last_agent
441
- and self.agent_manager
442
- and last_agent in self.agent_manager.agents
443
- ):
444
- if self.agent_manager.select_agent(last_agent):
445
- initial_agent_selected = True
446
- except Exception as e:
447
- # Don't fail startup if restoration fails
448
- click.echo(f"⚠️ Could not restore last used agent: {e}")
449
-
450
- if not initial_agent_selected and self.agent_manager:
451
- first_agent_name = list(self.agent_manager.agents.keys())[0]
452
- if not self.agent_manager.select_agent(first_agent_name):
453
- available_agents = ", ".join(self.agent_manager.agents.keys())
454
- click.echo(
455
- f"⚠️ Unknown agent: {first_agent_name}. Using default agent. Available agents: {available_agents}"
456
- )
62
+ @property
63
+ def agent_manager(self):
64
+ return self.setup.agent_manager
457
65
 
458
66
  def run_console(
459
67
  self,
@@ -463,37 +71,26 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
463
71
  memory_llm: Optional[str] = None,
464
72
  with_voice: bool = False,
465
73
  ) -> None:
466
- """
467
- Run AgentCrew in console/terminal mode.
468
-
469
- Args:
470
- provider: LLM provider name
471
- agent_config: Path to agent configuration file
472
- mcp_config: Path to MCP servers configuration file
473
- memory_llm: LLM provider for memory service
474
- with_voice: Enable voice input/output
475
- """
476
74
  from AgentCrew.modules.console import ConsoleUI
477
75
  from AgentCrew.modules.chat import MessageHandler
478
76
  from AgentCrew.modules.mcpclient import MCPSessionManager
479
77
 
480
78
  try:
481
79
  if provider is None:
482
- provider = self._detect_provider()
80
+ provider = self.setup.detect_provider()
483
81
  if provider is None:
484
82
  raise ValueError(
485
83
  "No LLM API key found. Please set either ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, or DEEPINFRA_API_KEY"
486
84
  )
487
85
 
488
- services = self.setup_services(provider, memory_llm)
86
+ services = self.setup.setup_services(provider, memory_llm)
489
87
 
490
88
  if mcp_config:
491
89
  os.environ["MCP_CONFIG_PATH"] = mcp_config
492
90
 
493
- self.setup_agents(services, agent_config)
494
- self.restore_last_agent()
91
+ self.setup.setup_agents(services, agent_config)
92
+ self.setup.restore_last_agent()
495
93
 
496
- # Create the message handler
497
94
  message_handler = MessageHandler(
498
95
  services["memory"], services["context_persistent"], with_voice
499
96
  )
@@ -520,16 +117,6 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
520
117
  memory_llm: Optional[str] = None,
521
118
  with_voice: bool = False,
522
119
  ) -> None:
523
- """
524
- Run AgentCrew in GUI mode.
525
-
526
- Args:
527
- provider: LLM provider name
528
- agent_config: Path to agent configuration file
529
- mcp_config: Path to MCP servers configuration file
530
- memory_llm: LLM provider for memory service
531
- with_voice: Enable voice input/output
532
- """
533
120
  from PySide6.QtCore import QCoreApplication
534
121
  from PySide6.QtCore import Qt
535
122
  from PySide6.QtWidgets import QApplication
@@ -538,29 +125,25 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
538
125
  from AgentCrew.modules.mcpclient import MCPSessionManager
539
126
 
540
127
  try:
541
- # Detect provider if not specified
542
128
  if provider is None:
543
- provider = self._detect_provider()
129
+ provider = self.setup.detect_provider()
544
130
  if provider is None:
545
- # Show config window to setup API keys
546
131
  from AgentCrew.modules.gui.widgets.config_window import ConfigWindow
547
132
 
548
133
  app = QApplication(sys.argv)
549
134
  config_window = ConfigWindow()
550
- config_window.tab_widget.setCurrentIndex(3) # Show Settings tab
135
+ config_window.tab_widget.setCurrentIndex(3)
551
136
  config_window.show()
552
137
  sys.exit(app.exec())
553
138
 
554
- services = self.setup_services(provider, memory_llm)
139
+ services = self.setup.setup_services(provider, memory_llm)
555
140
 
556
141
  if mcp_config:
557
142
  os.environ["MCP_CONFIG_PATH"] = mcp_config
558
143
 
559
- # Set up the agent system
560
- self.setup_agents(services, agent_config)
561
- self.restore_last_agent()
144
+ self.setup.setup_agents(services, agent_config)
145
+ self.setup.restore_last_agent()
562
146
 
563
- # Create the message handler
564
147
  message_handler = MessageHandler(
565
148
  services["memory"], services["context_persistent"], with_voice
566
149
  )
@@ -590,53 +173,36 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
590
173
  mcp_config: Optional[str] = None,
591
174
  memory_llm: Optional[str] = None,
592
175
  ) -> None:
593
- """
594
- Run AgentCrew as an A2A server.
595
-
596
- Args:
597
- host: Host to bind the server to
598
- port: Port to bind the server to
599
- base_url: Base URL for agent endpoints
600
- provider: LLM provider name
601
- model_id: Model ID from provider
602
- agent_config: Path to agent configuration file
603
- api_key: API key for authentication (optional)
604
- mcp_config: Path to MCP servers configuration file
605
- memory_llm: LLM provider for memory service
606
- """
607
176
  from AgentCrew.modules.a2a.server import A2AServer
608
177
  from AgentCrew.modules.mcpclient import MCPSessionManager
609
178
 
610
179
  try:
611
- # Set default base URL if not provided
612
180
  if not base_url:
613
181
  base_url = f"http://{host}:{port}"
614
182
 
615
- # Detect provider if not specified
616
183
  if provider is None:
617
- provider = self._detect_provider()
184
+ provider = self.setup.detect_provider()
618
185
  if provider is None:
619
186
  raise ValueError(
620
187
  "No LLM API key found. Please set either ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, or DEEPINFRA_API_KEY"
621
188
  )
622
189
 
623
- services = self.setup_services(provider, memory_llm, need_memory=False)
190
+ services = self.setup.setup_services(
191
+ provider, memory_llm, need_memory=False
192
+ )
624
193
 
625
194
  if mcp_config:
626
195
  os.environ["MCP_CONFIG_PATH"] = mcp_config
627
196
 
628
197
  os.environ["AGENTCREW_DISABLE_GUI"] = "true"
629
198
 
630
- # Set up agents from configuration
631
- self.setup_agents(services, agent_config, provider, model_id)
199
+ self.setup.setup_agents(services, agent_config, provider, model_id)
632
200
 
633
201
  if self.agent_manager is None:
634
202
  raise ValueError("Agent manager is not initialized")
635
203
 
636
- # Get agent manager
637
204
  self.agent_manager.enforce_transfer = False
638
205
 
639
- # Create and start server
640
206
  server = A2AServer(
641
207
  agent_manager=self.agent_manager,
642
208
  host=host,
@@ -659,33 +225,17 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
659
225
  MCPSessionManager.get_instance().cleanup()
660
226
 
661
227
  def _parse_output_schema(self, schema_input: str) -> tuple[str, dict]:
662
- """
663
- Parse output schema from file or JSON string and create a custom system prompt.
664
-
665
- Args:
666
- schema_input: Either a file path to a JSON schema or a JSON schema string
667
-
668
- Returns:
669
- Custom system prompt instructing the agent to follow the schema
670
-
671
- Raises:
672
- ValueError: If schema is invalid JSON or file doesn't exist
673
- """
674
228
  try:
675
229
  from AgentCrew.modules.prompts.constants import SCHEMA_ENFORCEMENT_PROMPT
676
230
 
677
- # Try to parse as file path first
678
231
  if os.path.exists(schema_input):
679
232
  with open(schema_input, "r", encoding="utf-8") as f:
680
233
  schema_dict = json.load(f)
681
234
  else:
682
- # Try to parse as JSON string
683
235
  schema_dict = json.loads(schema_input)
684
236
 
685
- # Format the schema as a pretty-printed JSON string
686
237
  schema_json = json.dumps(schema_dict, indent=2)
687
238
 
688
- # Create enforcement prompt
689
239
  enforcement_prompt = SCHEMA_ENFORCEMENT_PROMPT.replace(
690
240
  "{schema_json}", schema_json
691
241
  )
@@ -696,33 +246,30 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
696
246
  except Exception as e:
697
247
  raise ValueError(f"Failed to load output schema: {e}")
698
248
 
249
+ def _clean_json_response(self, response: str) -> str:
250
+ import re
251
+
252
+ cleaned = response.strip()
253
+ pattern = r"^```(?:json)?\s*\n?(.*?)\n?```$"
254
+ match = re.match(pattern, cleaned, re.DOTALL)
255
+ if match:
256
+ cleaned = match.group(1).strip()
257
+ return cleaned
258
+
699
259
  def _validate_response_against_schema(
700
260
  self, response: str, schema_dict: Dict[str, Any]
701
261
  ) -> tuple[bool, Optional[str]]:
702
- """
703
- Validate agent response against JSON schema.
704
-
705
- Args:
706
- response: Agent's response string (expected to be valid JSON)
707
- schema_dict: JSON schema dictionary
708
-
709
- Returns:
710
- Tuple of (is_valid, error_message)
711
- - is_valid: True if response matches schema, False otherwise
712
- - error_message: Detailed error message if validation fails, None if valid
713
- """
714
262
  from jsonschema import validate, ValidationError
715
263
 
716
- # Parse response as JSON
717
264
  try:
718
- response_json = json.loads(response)
265
+ cleaned_response = self._clean_json_response(response)
266
+ response_json = json.loads(cleaned_response)
719
267
  except json.JSONDecodeError as e:
720
268
  return (
721
269
  False,
722
270
  f"Response is not valid JSON: {e}\n\nResponse received:\n{response[:500]}",
723
271
  )
724
272
 
725
- # Validate against schema
726
273
  try:
727
274
  validate(instance=response_json, schema=schema_dict)
728
275
  return True, None
@@ -751,46 +298,29 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
751
298
  memory_llm: Optional[str] = None,
752
299
  output_schema: Optional[str] = None,
753
300
  ) -> str:
754
- """
755
- Run a single job/task with an agent.
756
-
757
- Args:
758
- agent: Name of the agent to run
759
- task: Task description
760
- files: List of file paths to attach
761
- provider: LLM provider name
762
- model_id: Model ID from provider
763
- agent_config: Path to agent configuration file
764
- mcp_config: Path to MCP servers configuration file
765
- memory_llm: LLM provider for memory service
766
- output_schema: JSON schema (file path or JSON string) for enforcing structured output
767
-
768
- Returns:
769
- Agent response as string
770
- """
771
301
  from AgentCrew.modules.chat import MessageHandler
772
302
  from AgentCrew.modules.mcpclient import MCPSessionManager
303
+ from AgentCrew.modules.llm.model_registry import ModelRegistry
773
304
 
774
305
  try:
775
- # Detect provider if not specified
776
306
  if provider is None:
777
- provider = self._detect_provider()
307
+ provider = self.setup.detect_provider()
778
308
  if provider is None:
779
309
  raise ValueError(
780
310
  "No LLM API key found. Please set either ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, or DEEPINFRA_API_KEY"
781
311
  )
782
312
 
783
- services = self.setup_services(provider, memory_llm, need_memory=False)
313
+ services = self.setup.setup_services(
314
+ provider, memory_llm, need_memory=False
315
+ )
784
316
 
785
317
  if mcp_config:
786
318
  os.environ["MCP_CONFIG_PATH"] = mcp_config
787
319
 
788
320
  os.environ["AGENTCREW_DISABLE_GUI"] = "true"
789
321
 
790
- # Set up agents from configuration
791
- self.setup_agents(services, agent_config)
322
+ self.setup.setup_agents(services, agent_config)
792
323
 
793
- # Get agent manager
794
324
  llm_manager = ServiceManager.get_instance()
795
325
 
796
326
  llm_service = llm_manager.get_service(provider)
@@ -812,11 +342,8 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
812
342
  current_agent = self.agent_manager.get_local_agent(agent)
813
343
 
814
344
  if current_agent:
815
- # Parse schema if provided
816
345
  schema_dict = None
817
346
  if output_schema:
818
- from AgentCrew.modules.llm.model_registry import ModelRegistry
819
-
820
347
  schema_prompt, schema_dict = self._parse_output_schema(
821
348
  output_schema
822
349
  )
@@ -824,7 +351,6 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
824
351
  f"{provider}/{model_id}"
825
352
  ):
826
353
  current_agent.llm.structured_output = schema_dict
827
-
828
354
  else:
829
355
  current_agent.set_custom_system_prompt(schema_prompt)
830
356
 
@@ -836,14 +362,12 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
836
362
  message_handler.is_non_interactive = True
837
363
  message_handler.agent = current_agent
838
364
 
839
- # Process files if provided
840
365
  if files:
841
366
  for file_path in files:
842
367
  asyncio.run(
843
368
  message_handler.process_user_input(f"/file {file_path}")
844
369
  )
845
370
 
846
- # Process task with retry logic for schema validation
847
371
  max_attempts = 4
848
372
  attempt = 0
849
373
  response = None
@@ -856,7 +380,7 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
856
380
  message_handler.get_assistant_response()
857
381
  )
858
382
  if not output_schema or not schema_dict:
859
- break # No schema validation needed
383
+ break
860
384
 
861
385
  if response is None:
862
386
  asyncio.run(
@@ -864,13 +388,13 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
864
388
  "No response was generated. Please try again."
865
389
  )
866
390
  )
867
- continue # No response, retry
391
+ continue
868
392
 
869
393
  success, retry_message = self._validate_response_against_schema(
870
394
  response, schema_dict
871
395
  )
872
396
  if success:
873
- break # Valid response
397
+ break
874
398
  else:
875
399
  if retry_message:
876
400
  asyncio.run(
@@ -889,116 +413,4 @@ tools = ["memory", "browser", "web_search", "code_analysis"]
889
413
  raise
890
414
 
891
415
  def login(self) -> bool:
892
- """
893
- Authenticate with GitHub Copilot and save the API key to config.
894
-
895
- Returns:
896
- True if authentication succeeded, False otherwise
897
- """
898
- try:
899
- click.echo("🔐 Starting GitHub Copilot authentication...")
900
-
901
- # Step 1: Request device code
902
- resp = requests.post(
903
- "https://github.com/login/device/code",
904
- headers={
905
- "accept": "application/json",
906
- "editor-version": "vscode/1.100.3",
907
- "editor-plugin-version": "GitHub.copilot/1.330.0",
908
- "content-type": "application/json",
909
- "user-agent": "GithubCopilot/1.330.0",
910
- "accept-encoding": "gzip,deflate,br",
911
- },
912
- data='{"client_id":"Iv1.b507a08c87ecfe98","scope":"read:user"}',
913
- )
914
-
915
- if resp.status_code != 200:
916
- click.echo(
917
- f"❌ Failed to get device code: {resp.status_code}", err=True
918
- )
919
- return False
920
-
921
- # Parse the response json, isolating the device_code, user_code, and verification_uri
922
- resp_json = resp.json()
923
- device_code = resp_json.get("device_code")
924
- user_code = resp_json.get("user_code")
925
- verification_uri = resp_json.get("verification_uri")
926
-
927
- if not all([device_code, user_code, verification_uri]):
928
- click.echo("❌ Invalid response from GitHub", err=True)
929
- return False
930
-
931
- # Print the user code and verification uri
932
- click.echo(
933
- f"📋 Please visit {verification_uri} and enter code: {user_code}"
934
- )
935
- click.echo("⏳ Waiting for authentication...")
936
-
937
- webbrowser.open(verification_uri)
938
-
939
- # Step 2: Poll for access token
940
- while True:
941
- time.sleep(5)
942
-
943
- resp = requests.post(
944
- "https://github.com/login/oauth/access_token",
945
- headers={
946
- "accept": "application/json",
947
- "editor-version": "vscode/1.100.3",
948
- "editor-plugin-version": "GitHub.copilot/1.330.0",
949
- "content-type": "application/json",
950
- "user-agent": "GithubCopilot/1.330.0",
951
- "accept-encoding": "gzip,deflate,br",
952
- },
953
- data=f'{{"client_id":"Iv1.b507a08c87ecfe98","device_code":"{device_code}","grant_type":"urn:ietf:params:oauth:grant-type:device_code"}}',
954
- )
955
-
956
- # Parse the response json
957
- resp_json = resp.json()
958
- access_token = resp_json.get("access_token")
959
- error = resp_json.get("error")
960
-
961
- if access_token:
962
- click.echo("✅ Authentication successful!")
963
- break
964
- elif error == "authorization_pending":
965
- continue # Keep polling
966
- elif error == "slow_down":
967
- time.sleep(5) # Additional delay
968
- continue
969
- elif error == "expired_token":
970
- click.echo("❌ Authentication expired. Please try again.", err=True)
971
- return False
972
- elif error == "access_denied":
973
- click.echo("❌ Authentication denied by user.", err=True)
974
- return False
975
- else:
976
- click.echo(f"❌ Authentication error: {error}", err=True)
977
- return False
978
-
979
- # Step 3: Save the token to config
980
- global_config = self.config_manager.read_global_config_data()
981
-
982
- # Ensure api_keys section exists
983
- if "api_keys" not in global_config:
984
- global_config["api_keys"] = {}
985
-
986
- # Save the token
987
- global_config["api_keys"]["GITHUB_COPILOT_API_KEY"] = access_token
988
- self.config_manager.write_global_config_data(global_config)
989
-
990
- click.echo("💾 GitHub Copilot API key saved to config file!")
991
- click.echo(
992
- "🚀 You can now use GitHub Copilot with --provider github_copilot"
993
- )
994
- return True
995
-
996
- except ImportError:
997
- click.echo(
998
- "❌ Error: 'requests' package is required for authentication", err=True
999
- )
1000
- click.echo("Install it with: pip install requests")
1001
- return False
1002
- except Exception as e:
1003
- click.echo(f"❌ Authentication failed: {str(e)}", err=True)
1004
- return False
416
+ return self.setup.login()