hud-python 0.4.1__py3-none-any.whl → 0.4.2__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 hud-python might be problematic. Click here for more details.

Files changed (130) hide show
  1. hud/__init__.py +22 -22
  2. hud/agents/__init__.py +13 -15
  3. hud/agents/base.py +599 -599
  4. hud/agents/claude.py +373 -373
  5. hud/agents/langchain.py +250 -250
  6. hud/agents/misc/__init__.py +7 -7
  7. hud/agents/misc/response_agent.py +80 -80
  8. hud/agents/openai.py +352 -352
  9. hud/agents/openai_chat_generic.py +154 -154
  10. hud/agents/tests/__init__.py +1 -1
  11. hud/agents/tests/test_base.py +742 -742
  12. hud/agents/tests/test_claude.py +324 -324
  13. hud/agents/tests/test_client.py +363 -363
  14. hud/agents/tests/test_openai.py +237 -237
  15. hud/cli/__init__.py +617 -617
  16. hud/cli/__main__.py +8 -8
  17. hud/cli/analyze.py +371 -371
  18. hud/cli/analyze_metadata.py +230 -230
  19. hud/cli/build.py +427 -427
  20. hud/cli/clone.py +185 -185
  21. hud/cli/cursor.py +92 -92
  22. hud/cli/debug.py +392 -392
  23. hud/cli/docker_utils.py +83 -83
  24. hud/cli/init.py +281 -281
  25. hud/cli/interactive.py +353 -353
  26. hud/cli/mcp_server.py +789 -756
  27. hud/cli/pull.py +336 -336
  28. hud/cli/push.py +370 -370
  29. hud/cli/remote_runner.py +311 -311
  30. hud/cli/runner.py +160 -160
  31. hud/cli/tests/__init__.py +3 -3
  32. hud/cli/tests/test_analyze.py +284 -284
  33. hud/cli/tests/test_cli_init.py +265 -265
  34. hud/cli/tests/test_cli_main.py +27 -27
  35. hud/cli/tests/test_clone.py +142 -142
  36. hud/cli/tests/test_cursor.py +253 -253
  37. hud/cli/tests/test_debug.py +453 -453
  38. hud/cli/tests/test_mcp_server.py +139 -139
  39. hud/cli/tests/test_utils.py +388 -388
  40. hud/cli/utils.py +263 -263
  41. hud/clients/README.md +143 -143
  42. hud/clients/__init__.py +16 -16
  43. hud/clients/base.py +379 -379
  44. hud/clients/fastmcp.py +202 -222
  45. hud/clients/mcp_use.py +278 -278
  46. hud/clients/tests/__init__.py +1 -1
  47. hud/clients/tests/test_client_integration.py +111 -111
  48. hud/clients/tests/test_fastmcp.py +342 -342
  49. hud/clients/tests/test_protocol.py +188 -188
  50. hud/clients/utils/__init__.py +1 -1
  51. hud/clients/utils/retry_transport.py +160 -160
  52. hud/datasets.py +322 -322
  53. hud/misc/__init__.py +1 -1
  54. hud/misc/claude_plays_pokemon.py +292 -292
  55. hud/otel/__init__.py +35 -35
  56. hud/otel/collector.py +142 -142
  57. hud/otel/config.py +164 -164
  58. hud/otel/context.py +536 -536
  59. hud/otel/exporters.py +366 -366
  60. hud/otel/instrumentation.py +97 -97
  61. hud/otel/processors.py +118 -118
  62. hud/otel/tests/__init__.py +1 -1
  63. hud/otel/tests/test_processors.py +197 -197
  64. hud/server/__init__.py +5 -5
  65. hud/server/context.py +114 -114
  66. hud/server/helper/__init__.py +5 -5
  67. hud/server/low_level.py +132 -132
  68. hud/server/server.py +170 -166
  69. hud/server/tests/__init__.py +3 -3
  70. hud/settings.py +73 -73
  71. hud/shared/__init__.py +5 -5
  72. hud/shared/exceptions.py +180 -180
  73. hud/shared/requests.py +264 -264
  74. hud/shared/tests/test_exceptions.py +157 -157
  75. hud/shared/tests/test_requests.py +275 -275
  76. hud/telemetry/__init__.py +25 -25
  77. hud/telemetry/instrument.py +379 -379
  78. hud/telemetry/job.py +309 -309
  79. hud/telemetry/replay.py +74 -74
  80. hud/telemetry/trace.py +83 -83
  81. hud/tools/__init__.py +33 -33
  82. hud/tools/base.py +365 -365
  83. hud/tools/bash.py +161 -161
  84. hud/tools/computer/__init__.py +15 -15
  85. hud/tools/computer/anthropic.py +437 -437
  86. hud/tools/computer/hud.py +376 -376
  87. hud/tools/computer/openai.py +295 -295
  88. hud/tools/computer/settings.py +82 -82
  89. hud/tools/edit.py +314 -314
  90. hud/tools/executors/__init__.py +30 -30
  91. hud/tools/executors/base.py +539 -539
  92. hud/tools/executors/pyautogui.py +621 -621
  93. hud/tools/executors/tests/__init__.py +1 -1
  94. hud/tools/executors/tests/test_base_executor.py +338 -338
  95. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  96. hud/tools/executors/xdo.py +511 -511
  97. hud/tools/playwright.py +412 -412
  98. hud/tools/tests/__init__.py +3 -3
  99. hud/tools/tests/test_base.py +282 -282
  100. hud/tools/tests/test_bash.py +158 -158
  101. hud/tools/tests/test_bash_extended.py +197 -197
  102. hud/tools/tests/test_computer.py +425 -425
  103. hud/tools/tests/test_computer_actions.py +34 -34
  104. hud/tools/tests/test_edit.py +259 -259
  105. hud/tools/tests/test_init.py +27 -27
  106. hud/tools/tests/test_playwright_tool.py +183 -183
  107. hud/tools/tests/test_tools.py +145 -145
  108. hud/tools/tests/test_utils.py +156 -156
  109. hud/tools/types.py +72 -72
  110. hud/tools/utils.py +50 -50
  111. hud/types.py +136 -136
  112. hud/utils/__init__.py +10 -10
  113. hud/utils/async_utils.py +65 -65
  114. hud/utils/design.py +168 -168
  115. hud/utils/mcp.py +55 -55
  116. hud/utils/progress.py +149 -149
  117. hud/utils/telemetry.py +66 -66
  118. hud/utils/tests/test_async_utils.py +173 -173
  119. hud/utils/tests/test_init.py +17 -17
  120. hud/utils/tests/test_progress.py +261 -261
  121. hud/utils/tests/test_telemetry.py +82 -82
  122. hud/utils/tests/test_version.py +8 -8
  123. hud/version.py +7 -7
  124. {hud_python-0.4.1.dist-info → hud_python-0.4.2.dist-info}/METADATA +10 -8
  125. hud_python-0.4.2.dist-info/RECORD +131 -0
  126. {hud_python-0.4.1.dist-info → hud_python-0.4.2.dist-info}/licenses/LICENSE +21 -21
  127. hud/agents/art.py +0 -101
  128. hud_python-0.4.1.dist-info/RECORD +0 -132
  129. {hud_python-0.4.1.dist-info → hud_python-0.4.2.dist-info}/WHEEL +0 -0
  130. {hud_python-0.4.1.dist-info → hud_python-0.4.2.dist-info}/entry_points.txt +0 -0
hud/agents/langchain.py CHANGED
@@ -1,250 +1,250 @@
1
- """LangChain MCP Agent implementation."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import TYPE_CHECKING, Any, ClassVar
7
-
8
- import mcp.types as types
9
- from langchain.agents import AgentExecutor, create_tool_calling_agent
10
- from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
11
- from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
12
- from mcp_use.adapters.langchain_adapter import LangChainAdapter
13
-
14
- import hud
15
-
16
- if TYPE_CHECKING:
17
- from langchain.schema.language_model import BaseLanguageModel
18
- from langchain_core.tools import BaseTool
19
-
20
- from hud.types import AgentResponse, MCPToolCall, MCPToolResult
21
-
22
- from .base import MCPAgent
23
-
24
- logger = logging.getLogger(__name__)
25
-
26
-
27
- class LangChainAgent(MCPAgent):
28
- """
29
- LangChain agent that uses MCP servers for tool execution.
30
-
31
- This agent wraps any LangChain-compatible LLM and provides
32
- access to MCP tools through LangChain's tool-calling interface.
33
- """
34
-
35
- metadata: ClassVar[dict[str, Any]] = {
36
- "display_width": 1920,
37
- "display_height": 1080,
38
- }
39
-
40
- def __init__(
41
- self,
42
- llm: BaseLanguageModel,
43
- **kwargs: Any,
44
- ) -> None:
45
- """
46
- Initialize LangChain MCP agent.
47
-
48
- Args:
49
- llm: Any LangChain-compatible language model
50
- **kwargs: Additional arguments passed to BaseMCPAgent
51
- """
52
- super().__init__(**kwargs)
53
-
54
- self.llm = llm
55
- self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
56
- self._langchain_tools: list[BaseTool] | None = None
57
-
58
- self.model_name = (
59
- "langchain-" + self.llm.model_name # type: ignore
60
- if hasattr(self.llm, "model_name")
61
- else "unknown"
62
- )
63
-
64
- def _get_langchain_tools(self) -> list[BaseTool]:
65
- """Get or create LangChain tools from MCP tools."""
66
- if self._langchain_tools is not None:
67
- return self._langchain_tools
68
-
69
- # Create LangChain tools from MCP tools using the adapter
70
- self._langchain_tools = []
71
-
72
- # Convert available tools using the adapter; no server grouping
73
- langchain_tools = self.adapter._convert_tools(self._available_tools, "default") # type: ignore[reportAttributeAccessIssue]
74
- self._langchain_tools.extend(langchain_tools)
75
-
76
- logger.info("Created %s LangChain tools from MCP tools", len(self._langchain_tools))
77
- return self._langchain_tools
78
-
79
- async def get_system_messages(self) -> list[BaseMessage]:
80
- """Get system messages for LangChain."""
81
- return [SystemMessage(content=self.system_prompt)]
82
-
83
- async def format_blocks(self, blocks: list[types.ContentBlock]) -> list[BaseMessage]:
84
- """Create initial messages for LangChain."""
85
- messages = []
86
- for block in blocks:
87
- if isinstance(block, types.TextContent):
88
- messages.append(HumanMessage(content=block.text))
89
- elif isinstance(block, types.ImageContent):
90
- messages.append(HumanMessage(content=block.data))
91
- return messages
92
-
93
- @hud.instrument(
94
- span_type="agent",
95
- record_args=False, # Messages can be large
96
- record_result=True,
97
- )
98
- async def get_response(self, messages: list[BaseMessage]) -> AgentResponse:
99
- """Get response from LangChain model including any tool calls."""
100
- # Get LangChain tools (created lazily)
101
- langchain_tools = self._get_langchain_tools()
102
-
103
- # Create a prompt template from current messages
104
- # Extract system message if present
105
- system_content = "You are a helpful assistant"
106
- non_system_messages = []
107
-
108
- for msg in messages:
109
- if isinstance(msg, SystemMessage):
110
- system_content = str(msg.content)
111
- else:
112
- non_system_messages.append(msg)
113
-
114
- # Create prompt with placeholders
115
- prompt = ChatPromptTemplate.from_messages(
116
- [
117
- ("system", system_content),
118
- MessagesPlaceholder(variable_name="chat_history"),
119
- MessagesPlaceholder(variable_name="agent_scratchpad"),
120
- ]
121
- )
122
-
123
- # Create agent with tools
124
- agent = create_tool_calling_agent(
125
- llm=self.llm,
126
- tools=langchain_tools,
127
- prompt=prompt,
128
- )
129
-
130
- # Create executor
131
- executor = AgentExecutor(
132
- agent=agent,
133
- tools=langchain_tools,
134
- verbose=False,
135
- )
136
-
137
- # Format the last user message as input
138
- last_user_msg = None
139
- for msg in reversed(non_system_messages):
140
- if isinstance(msg, HumanMessage):
141
- last_user_msg = msg
142
- break
143
-
144
- if not last_user_msg:
145
- return AgentResponse(content="No user message found", tool_calls=[], done=True)
146
-
147
- # Extract text from message content
148
- input_text = ""
149
- if isinstance(last_user_msg.content, str):
150
- input_text = last_user_msg.content
151
- elif isinstance(last_user_msg.content, list):
152
- # Extract text from multimodal content
153
- for item in last_user_msg.content:
154
- if isinstance(item, dict) and item.get("type") == "text":
155
- input_text = item.get("text", "")
156
- break
157
-
158
- # Build chat history (exclude last user message and system)
159
- chat_history = []
160
- for _, msg in enumerate(non_system_messages[:-1]):
161
- if isinstance(msg, HumanMessage | AIMessage):
162
- chat_history.append(msg)
163
-
164
- # Execute the agent
165
- try:
166
- result = await executor.ainvoke(
167
- {
168
- "input": input_text,
169
- "chat_history": chat_history,
170
- }
171
- )
172
-
173
- # Process the result
174
- output = result.get("output", "")
175
-
176
- # Check if tools were called
177
- if result.get("intermediate_steps"):
178
- # Tools were called
179
- tool_calls = []
180
- for action, _ in result["intermediate_steps"]:
181
- if hasattr(action, "tool") and hasattr(action, "tool_input"):
182
- tool_calls.append(
183
- MCPToolCall(
184
- name=action.tool,
185
- arguments=action.tool_input,
186
- )
187
- )
188
-
189
- return AgentResponse(content=output, tool_calls=tool_calls, done=False)
190
- else:
191
- # No tools called, just text response
192
- return AgentResponse(content=output, tool_calls=[], done=True)
193
-
194
- except Exception as e:
195
- logger.error("Agent execution failed: %s", e)
196
- return AgentResponse(content=f"Error: {e!s}", tool_calls=[], done=True)
197
-
198
- async def format_tool_results(
199
- self, tool_calls: list[MCPToolCall], tool_results: list[MCPToolResult]
200
- ) -> list[BaseMessage]:
201
- """Format tool results into LangChain messages."""
202
- # Create an AI message with the tool calls and results
203
- messages = []
204
-
205
- # First add an AI message indicating tools were called
206
- tool_names = [tc.name for tc in tool_calls]
207
- ai_content = f"I'll use the following tools: {', '.join(tool_names)}"
208
- messages.append(AIMessage(content=ai_content))
209
-
210
- # Build result text from tool results
211
- text_parts = []
212
- latest_screenshot = None
213
-
214
- for tool_call, result in zip(tool_calls, tool_results, strict=False):
215
- if result.isError:
216
- error_text = "Tool execution failed"
217
- for content in result.content:
218
- if isinstance(content, types.TextContent):
219
- error_text = content.text
220
- break
221
- text_parts.append(f"Error - {tool_call.name}: {error_text}")
222
- else:
223
- # Process success content
224
- tool_output = []
225
- for content in result.content:
226
- if isinstance(content, types.TextContent):
227
- tool_output.append(content.text)
228
- elif isinstance(content, types.ImageContent):
229
- latest_screenshot = content.data
230
-
231
- if tool_output:
232
- text_parts.append(f"{tool_call.name}: " + " ".join(tool_output))
233
-
234
- result_text = "\n".join(text_parts) if text_parts else "No output from tools"
235
-
236
- # Then add a human message with the tool results
237
- if latest_screenshot:
238
- # Include screenshot in multimodal format
239
- content = [
240
- {"type": "text", "text": f"Tool results:\n{result_text}"},
241
- {
242
- "type": "image_url",
243
- "image_url": {"url": f"data:image/png;base64,{latest_screenshot}"},
244
- },
245
- ]
246
- messages.append(HumanMessage(content=content))
247
- else:
248
- messages.append(HumanMessage(content=f"Tool results:\n{result_text}"))
249
-
250
- return messages
1
+ """LangChain MCP Agent implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, Any, ClassVar
7
+
8
+ import mcp.types as types
9
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
10
+ from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
11
+ from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
12
+ from mcp_use.adapters.langchain_adapter import LangChainAdapter
13
+
14
+ import hud
15
+
16
+ if TYPE_CHECKING:
17
+ from langchain.schema.language_model import BaseLanguageModel
18
+ from langchain_core.tools import BaseTool
19
+
20
+ from hud.types import AgentResponse, MCPToolCall, MCPToolResult
21
+
22
+ from .base import MCPAgent
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class LangChainAgent(MCPAgent):
28
+ """
29
+ LangChain agent that uses MCP servers for tool execution.
30
+
31
+ This agent wraps any LangChain-compatible LLM and provides
32
+ access to MCP tools through LangChain's tool-calling interface.
33
+ """
34
+
35
+ metadata: ClassVar[dict[str, Any]] = {
36
+ "display_width": 1920,
37
+ "display_height": 1080,
38
+ }
39
+
40
+ def __init__(
41
+ self,
42
+ llm: BaseLanguageModel,
43
+ **kwargs: Any,
44
+ ) -> None:
45
+ """
46
+ Initialize LangChain MCP agent.
47
+
48
+ Args:
49
+ llm: Any LangChain-compatible language model
50
+ **kwargs: Additional arguments passed to BaseMCPAgent
51
+ """
52
+ super().__init__(**kwargs)
53
+
54
+ self.llm = llm
55
+ self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
56
+ self._langchain_tools: list[BaseTool] | None = None
57
+
58
+ self.model_name = (
59
+ "langchain-" + self.llm.model_name # type: ignore
60
+ if hasattr(self.llm, "model_name")
61
+ else "unknown"
62
+ )
63
+
64
+ def _get_langchain_tools(self) -> list[BaseTool]:
65
+ """Get or create LangChain tools from MCP tools."""
66
+ if self._langchain_tools is not None:
67
+ return self._langchain_tools
68
+
69
+ # Create LangChain tools from MCP tools using the adapter
70
+ self._langchain_tools = []
71
+
72
+ # Convert available tools using the adapter; no server grouping
73
+ langchain_tools = self.adapter._convert_tools(self._available_tools, "default") # type: ignore[reportAttributeAccessIssue]
74
+ self._langchain_tools.extend(langchain_tools)
75
+
76
+ logger.info("Created %s LangChain tools from MCP tools", len(self._langchain_tools))
77
+ return self._langchain_tools
78
+
79
+ async def get_system_messages(self) -> list[BaseMessage]:
80
+ """Get system messages for LangChain."""
81
+ return [SystemMessage(content=self.system_prompt)]
82
+
83
+ async def format_blocks(self, blocks: list[types.ContentBlock]) -> list[BaseMessage]:
84
+ """Create initial messages for LangChain."""
85
+ messages = []
86
+ for block in blocks:
87
+ if isinstance(block, types.TextContent):
88
+ messages.append(HumanMessage(content=block.text))
89
+ elif isinstance(block, types.ImageContent):
90
+ messages.append(HumanMessage(content=block.data))
91
+ return messages
92
+
93
+ @hud.instrument(
94
+ span_type="agent",
95
+ record_args=False, # Messages can be large
96
+ record_result=True,
97
+ )
98
+ async def get_response(self, messages: list[BaseMessage]) -> AgentResponse:
99
+ """Get response from LangChain model including any tool calls."""
100
+ # Get LangChain tools (created lazily)
101
+ langchain_tools = self._get_langchain_tools()
102
+
103
+ # Create a prompt template from current messages
104
+ # Extract system message if present
105
+ system_content = "You are a helpful assistant"
106
+ non_system_messages = []
107
+
108
+ for msg in messages:
109
+ if isinstance(msg, SystemMessage):
110
+ system_content = str(msg.content)
111
+ else:
112
+ non_system_messages.append(msg)
113
+
114
+ # Create prompt with placeholders
115
+ prompt = ChatPromptTemplate.from_messages(
116
+ [
117
+ ("system", system_content),
118
+ MessagesPlaceholder(variable_name="chat_history"),
119
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
120
+ ]
121
+ )
122
+
123
+ # Create agent with tools
124
+ agent = create_tool_calling_agent(
125
+ llm=self.llm,
126
+ tools=langchain_tools,
127
+ prompt=prompt,
128
+ )
129
+
130
+ # Create executor
131
+ executor = AgentExecutor(
132
+ agent=agent,
133
+ tools=langchain_tools,
134
+ verbose=False,
135
+ )
136
+
137
+ # Format the last user message as input
138
+ last_user_msg = None
139
+ for msg in reversed(non_system_messages):
140
+ if isinstance(msg, HumanMessage):
141
+ last_user_msg = msg
142
+ break
143
+
144
+ if not last_user_msg:
145
+ return AgentResponse(content="No user message found", tool_calls=[], done=True)
146
+
147
+ # Extract text from message content
148
+ input_text = ""
149
+ if isinstance(last_user_msg.content, str):
150
+ input_text = last_user_msg.content
151
+ elif isinstance(last_user_msg.content, list):
152
+ # Extract text from multimodal content
153
+ for item in last_user_msg.content:
154
+ if isinstance(item, dict) and item.get("type") == "text":
155
+ input_text = item.get("text", "")
156
+ break
157
+
158
+ # Build chat history (exclude last user message and system)
159
+ chat_history = []
160
+ for _, msg in enumerate(non_system_messages[:-1]):
161
+ if isinstance(msg, HumanMessage | AIMessage):
162
+ chat_history.append(msg)
163
+
164
+ # Execute the agent
165
+ try:
166
+ result = await executor.ainvoke(
167
+ {
168
+ "input": input_text,
169
+ "chat_history": chat_history,
170
+ }
171
+ )
172
+
173
+ # Process the result
174
+ output = result.get("output", "")
175
+
176
+ # Check if tools were called
177
+ if result.get("intermediate_steps"):
178
+ # Tools were called
179
+ tool_calls = []
180
+ for action, _ in result["intermediate_steps"]:
181
+ if hasattr(action, "tool") and hasattr(action, "tool_input"):
182
+ tool_calls.append(
183
+ MCPToolCall(
184
+ name=action.tool,
185
+ arguments=action.tool_input,
186
+ )
187
+ )
188
+
189
+ return AgentResponse(content=output, tool_calls=tool_calls, done=False)
190
+ else:
191
+ # No tools called, just text response
192
+ return AgentResponse(content=output, tool_calls=[], done=True)
193
+
194
+ except Exception as e:
195
+ logger.error("Agent execution failed: %s", e)
196
+ return AgentResponse(content=f"Error: {e!s}", tool_calls=[], done=True)
197
+
198
+ async def format_tool_results(
199
+ self, tool_calls: list[MCPToolCall], tool_results: list[MCPToolResult]
200
+ ) -> list[BaseMessage]:
201
+ """Format tool results into LangChain messages."""
202
+ # Create an AI message with the tool calls and results
203
+ messages = []
204
+
205
+ # First add an AI message indicating tools were called
206
+ tool_names = [tc.name for tc in tool_calls]
207
+ ai_content = f"I'll use the following tools: {', '.join(tool_names)}"
208
+ messages.append(AIMessage(content=ai_content))
209
+
210
+ # Build result text from tool results
211
+ text_parts = []
212
+ latest_screenshot = None
213
+
214
+ for tool_call, result in zip(tool_calls, tool_results, strict=False):
215
+ if result.isError:
216
+ error_text = "Tool execution failed"
217
+ for content in result.content:
218
+ if isinstance(content, types.TextContent):
219
+ error_text = content.text
220
+ break
221
+ text_parts.append(f"Error - {tool_call.name}: {error_text}")
222
+ else:
223
+ # Process success content
224
+ tool_output = []
225
+ for content in result.content:
226
+ if isinstance(content, types.TextContent):
227
+ tool_output.append(content.text)
228
+ elif isinstance(content, types.ImageContent):
229
+ latest_screenshot = content.data
230
+
231
+ if tool_output:
232
+ text_parts.append(f"{tool_call.name}: " + " ".join(tool_output))
233
+
234
+ result_text = "\n".join(text_parts) if text_parts else "No output from tools"
235
+
236
+ # Then add a human message with the tool results
237
+ if latest_screenshot:
238
+ # Include screenshot in multimodal format
239
+ content = [
240
+ {"type": "text", "text": f"Tool results:\n{result_text}"},
241
+ {
242
+ "type": "image_url",
243
+ "image_url": {"url": f"data:image/png;base64,{latest_screenshot}"},
244
+ },
245
+ ]
246
+ messages.append(HumanMessage(content=content))
247
+ else:
248
+ messages.append(HumanMessage(content=f"Tool results:\n{result_text}"))
249
+
250
+ return messages
@@ -1,7 +1,7 @@
1
- """Miscellaneous agents."""
2
-
3
- from __future__ import annotations
4
-
5
- from .response_agent import ResponseAgent
6
-
7
- __all__ = ["ResponseAgent"]
1
+ """Miscellaneous agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .response_agent import ResponseAgent
6
+
7
+ __all__ = ["ResponseAgent"]