11from typing import Any
22from uuid import UUID
3- import uuid
43
5- from openhands .sdk .conversation import visualizer
6- from openhands .sdk .security .llm_analyzer import LLMSecurityAnalyzer
74from prompt_toolkit import HTML , print_formatted_text
85
96from openhands .sdk import Agent , BaseConversation , Conversation , Workspace
107from openhands .sdk .context import AgentContext , Skill
11- from openhands_cli .locations import CONVERSATIONS_DIR , WORK_DIR
12- from openhands_cli .tui .settings .store import AgentStore
13- from openhands .sdk .security .confirmation_policy import (
14- AlwaysConfirm ,
8+ from openhands .sdk .conversation import (
9+ visualizer , # noqa: F401 (ensures tools registered)
1510)
11+ from openhands .sdk .security .confirmation_policy import AlwaysConfirm
12+ from openhands .sdk .security .llm_analyzer import LLMSecurityAnalyzer
13+ from openhands_cli .locations import CONVERSATIONS_DIR , WORK_DIR
1614from openhands_cli .tui .settings .settings_screen import SettingsScreen
15+ from openhands_cli .tui .settings .store import AgentStore
1716from openhands_cli .tui .visualizer import CLIVisualizer
1817
18+
1919# register tools
20- from openhands .tools .terminal import TerminalTool
21- from openhands .tools .file_editor import FileEditorTool
22- from openhands .tools .task_tracker import TaskTrackerTool
20+ try :
21+ from openhands .tools .file_editor import (
22+ FileEditorTool , # type: ignore[attr-defined] # noqa: F401
23+ )
24+ from openhands .tools .task_tracker import (
25+ TaskTrackerTool , # type: ignore[attr-defined] # noqa: F401
26+ )
27+ from openhands .tools .terminal import (
28+ TerminalTool , # type: ignore[attr-defined] # noqa: F401
29+ )
30+ except ModuleNotFoundError :
31+ # Some platforms (e.g., Windows) may not have all low-level deps like fcntl.
32+ # The core CLI and tests don't require these tools at import time.
33+ TerminalTool = None # type: ignore[assignment]
34+ FileEditorTool = None # type: ignore[assignment]
35+ TaskTrackerTool = None # type: ignore[assignment]
2336
2437
2538class MissingAgentSpec (Exception ):
@@ -28,83 +41,95 @@ class MissingAgentSpec(Exception):
2841 pass
2942
3043
31-
3244def load_agent_specs (
3345 conversation_id : str | None = None ,
34- * ,
35- load_user_skills : bool = True ,
36- load_project_skills : bool = True ,
46+ mcp_servers : dict [str , dict [str , Any ]] | None = None ,
47+ skills : list [Skill ] | None = None ,
3748) -> Agent :
49+ """Load agent specifications.
50+
51+ Args:
52+ conversation_id: Optional conversation ID for session tracking
53+ mcp_servers: Optional dict of MCP servers to augment agent configuration
54+ skills: Optional list of skills to include in the agent configuration
55+
56+ Returns:
57+ Configured Agent instance
58+
59+ Raises:
60+ MissingAgentSpec: If agent specification is not found or invalid
61+ """
3862 agent_store = AgentStore ()
39- agent = agent_store .load (
40- session_id = conversation_id ,
41- load_user_skills = load_user_skills ,
42- load_project_skills = load_project_skills ,
43- )
63+ agent = agent_store .load (session_id = conversation_id )
4464 if not agent :
4565 raise MissingAgentSpec (
46- ' Agent specification not found. Please configure your agent settings.'
66+ " Agent specification not found. Please configure your agent settings."
4767 )
68+
69+ # If MCP servers are provided, augment the agent's MCP configuration
70+ if mcp_servers :
71+ # Merge with existing MCP configuration (provided servers take precedence)
72+ mcp_config : dict [str , Any ] = agent .mcp_config or {}
73+ existing_servers : dict [str , dict [str , Any ]] = mcp_config .get ("mcpServers" , {})
74+ existing_servers .update (mcp_servers )
75+ agent = agent .model_copy (
76+ update = {"mcp_config" : {"mcpServers" : existing_servers }}
77+ )
78+
79+ if skills :
80+ if agent .agent_context is not None :
81+ existing_skills = agent .agent_context .skills
82+ existing_skills .extend (skills )
83+ agent = agent .model_copy (
84+ update = {
85+ "agent_context" : agent .agent_context .model_copy (
86+ update = {"skills" : existing_skills }
87+ )
88+ }
89+ )
90+ else :
91+ agent = agent .model_copy (
92+ update = {"agent_context" : AgentContext (skills = skills )}
93+ )
94+
4895 return agent
4996
5097
51- def verify_agent_exists_or_setup_agent (
52- * ,
53- load_user_skills : bool = True ,
54- load_project_skills : bool = True ,
55- ) -> Agent :
98+ def verify_agent_exists_or_setup_agent () -> Agent :
5699 """Verify agent specs exists by attempting to load it.
57100
101+ If missing, run the settings flow and try once more.
58102 """
59103 settings_screen = SettingsScreen ()
60104 try :
61- agent = load_agent_specs (
62- load_user_skills = load_user_skills ,
63- load_project_skills = load_project_skills ,
64- )
105+ agent = load_agent_specs ()
65106 return agent
66107 except MissingAgentSpec :
67- # For first-time users, show the full settings flow with choice between basic/advanced
108+ # For first-time users, show the full settings flow with
109+ # choice between basic/advanced
68110 settings_screen .configure_settings (first_time = True )
69111
70-
71112 # Try once again after settings setup attempt
72- return load_agent_specs (
73- load_user_skills = load_user_skills ,
74- load_project_skills = load_project_skills ,
75- )
113+ return load_agent_specs ()
76114
77115
78116def setup_conversation (
79117 conversation_id : UUID ,
80118 include_security_analyzer : bool = True ,
81- * ,
82- load_user_skills : bool = True ,
83- load_project_skills : bool = True ,
84- mcp_servers : dict [str , dict [str , Any ]] | None = None ,
85- skills : list [Skill ] | None = None ,
86119) -> BaseConversation :
87- """
88- Setup the conversation with agent.
120+ """Setup the conversation with agent.
89121
90122 Args:
91- conversation_id: conversation ID to use. If not provided, a random UUID will be generated.
123+ conversation_id: conversation ID to use.
124+ If not provided, a random UUID will be generated.
92125
93126 Raises:
94127 MissingAgentSpec: If agent specification is not found or invalid.
95128 """
96129
97- print_formatted_text (
98- HTML (f'<white>Initializing agent...</white>' )
99- )
100-
101- agent = load_agent_specs (
102- str (conversation_id ),
103- load_user_skills = load_user_skills ,
104- load_project_skills = load_project_skills ,
105- )
106-
130+ print_formatted_text (HTML ("<white>Initializing agent...</white>" ))
107131
132+ agent = load_agent_specs (str (conversation_id ))
108133
109134 # Create conversation - agent context is now set in AgentStore.load()
110135 conversation : BaseConversation = Conversation (
@@ -113,7 +138,7 @@ def setup_conversation(
113138 # Conversation will add /<conversation_id> to this path
114139 persistence_dir = CONVERSATIONS_DIR ,
115140 conversation_id = conversation_id ,
116- visualizer = CLIVisualizer
141+ visualizer = CLIVisualizer ,
117142 )
118143
119144 # Security analyzer is set though conversation API now
@@ -124,6 +149,6 @@ def setup_conversation(
124149 conversation .set_confirmation_policy (AlwaysConfirm ())
125150
126151 print_formatted_text (
127- HTML (f' <green>✓ Agent initialized with model: { agent .llm .model } </green>' )
152+ HTML (f" <green>✓ Agent initialized with model: { agent .llm .model } </green>" )
128153 )
129- return conversation
154+ return conversation
0 commit comments