hud-python 0.4.1__py3-none-any.whl → 0.4.3__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 +261 -250
  6. hud/agents/misc/__init__.py +7 -7
  7. hud/agents/misc/response_agent.py +82 -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 +498 -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 +280 -281
  25. hud/cli/interactive.py +353 -353
  26. hud/cli/mcp_server.py +764 -756
  27. hud/cli/pull.py +330 -336
  28. hud/cli/push.py +404 -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 +378 -379
  44. hud/clients/fastmcp.py +222 -222
  45. hud/clients/mcp_use.py +298 -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 +327 -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 +236 -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.3.dist-info}/METADATA +10 -8
  125. hud_python-0.4.3.dist-info/RECORD +131 -0
  126. {hud_python-0.4.1.dist-info → hud_python-0.4.3.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.3.dist-info}/WHEEL +0 -0
  130. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/agents/langchain.py CHANGED
@@ -1,250 +1,261 @@
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
+
13
+ import hud
14
+
15
+ if TYPE_CHECKING:
16
+ from langchain.schema.language_model import BaseLanguageModel
17
+ from langchain_core.tools import BaseTool
18
+ from mcp_use.adapters.langchain_adapter import LangChainAdapter
19
+
20
+ try:
21
+ from mcp_use.adapters.langchain_adapter import LangChainAdapter
22
+ except ImportError:
23
+ LangChainAdapter = None # type: ignore[misc, assignment]
24
+
25
+ from hud.types import AgentResponse, MCPToolCall, MCPToolResult
26
+
27
+ from .base import MCPAgent
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class LangChainAgent(MCPAgent):
33
+ """
34
+ LangChain agent that uses MCP servers for tool execution.
35
+
36
+ This agent wraps any LangChain-compatible LLM and provides
37
+ access to MCP tools through LangChain's tool-calling interface.
38
+ """
39
+
40
+ metadata: ClassVar[dict[str, Any]] = {
41
+ "display_width": 1920,
42
+ "display_height": 1080,
43
+ }
44
+
45
+ def __init__(
46
+ self,
47
+ llm: BaseLanguageModel,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ """
51
+ Initialize LangChain MCP agent.
52
+
53
+ Args:
54
+ llm: Any LangChain-compatible language model
55
+ **kwargs: Additional arguments passed to BaseMCPAgent
56
+ """
57
+ super().__init__(**kwargs)
58
+
59
+ if LangChainAdapter is None:
60
+ raise ImportError(
61
+ "LangChainAdapter is not available. "
62
+ "Please install the optional agent dependencies: pip install 'hud-python[agent]'"
63
+ )
64
+
65
+ self.llm = llm
66
+ self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
67
+ self._langchain_tools: list[BaseTool] | None = None
68
+
69
+ self.model_name = (
70
+ "langchain-" + self.llm.model_name # type: ignore
71
+ if hasattr(self.llm, "model_name")
72
+ else "unknown"
73
+ )
74
+
75
+ def _get_langchain_tools(self) -> list[BaseTool]:
76
+ """Get or create LangChain tools from MCP tools."""
77
+ if self._langchain_tools is not None:
78
+ return self._langchain_tools
79
+
80
+ # Create LangChain tools from MCP tools using the adapter
81
+ self._langchain_tools = []
82
+
83
+ # Convert available tools using the adapter; no server grouping
84
+ langchain_tools = self.adapter._convert_tools(self._available_tools, "default") # type: ignore[reportAttributeAccessIssue]
85
+ self._langchain_tools.extend(langchain_tools)
86
+
87
+ logger.info("Created %s LangChain tools from MCP tools", len(self._langchain_tools))
88
+ return self._langchain_tools
89
+
90
+ async def get_system_messages(self) -> list[BaseMessage]:
91
+ """Get system messages for LangChain."""
92
+ return [SystemMessage(content=self.system_prompt)]
93
+
94
+ async def format_blocks(self, blocks: list[types.ContentBlock]) -> list[BaseMessage]:
95
+ """Create initial messages for LangChain."""
96
+ messages = []
97
+ for block in blocks:
98
+ if isinstance(block, types.TextContent):
99
+ messages.append(HumanMessage(content=block.text))
100
+ elif isinstance(block, types.ImageContent):
101
+ messages.append(HumanMessage(content=block.data))
102
+ return messages
103
+
104
+ @hud.instrument(
105
+ span_type="agent",
106
+ record_args=False, # Messages can be large
107
+ record_result=True,
108
+ )
109
+ async def get_response(self, messages: list[BaseMessage]) -> AgentResponse:
110
+ """Get response from LangChain model including any tool calls."""
111
+ # Get LangChain tools (created lazily)
112
+ langchain_tools = self._get_langchain_tools()
113
+
114
+ # Create a prompt template from current messages
115
+ # Extract system message if present
116
+ system_content = "You are a helpful assistant"
117
+ non_system_messages = []
118
+
119
+ for msg in messages:
120
+ if isinstance(msg, SystemMessage):
121
+ system_content = str(msg.content)
122
+ else:
123
+ non_system_messages.append(msg)
124
+
125
+ # Create prompt with placeholders
126
+ prompt = ChatPromptTemplate.from_messages(
127
+ [
128
+ ("system", system_content),
129
+ MessagesPlaceholder(variable_name="chat_history"),
130
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
131
+ ]
132
+ )
133
+
134
+ # Create agent with tools
135
+ agent = create_tool_calling_agent(
136
+ llm=self.llm,
137
+ tools=langchain_tools,
138
+ prompt=prompt,
139
+ )
140
+
141
+ # Create executor
142
+ executor = AgentExecutor(
143
+ agent=agent,
144
+ tools=langchain_tools,
145
+ verbose=False,
146
+ )
147
+
148
+ # Format the last user message as input
149
+ last_user_msg = None
150
+ for msg in reversed(non_system_messages):
151
+ if isinstance(msg, HumanMessage):
152
+ last_user_msg = msg
153
+ break
154
+
155
+ if not last_user_msg:
156
+ return AgentResponse(content="No user message found", tool_calls=[], done=True)
157
+
158
+ # Extract text from message content
159
+ input_text = ""
160
+ if isinstance(last_user_msg.content, str):
161
+ input_text = last_user_msg.content
162
+ elif isinstance(last_user_msg.content, list):
163
+ # Extract text from multimodal content
164
+ for item in last_user_msg.content:
165
+ if isinstance(item, dict) and item.get("type") == "text":
166
+ input_text = item.get("text", "")
167
+ break
168
+
169
+ # Build chat history (exclude last user message and system)
170
+ chat_history = []
171
+ for _, msg in enumerate(non_system_messages[:-1]):
172
+ if isinstance(msg, HumanMessage | AIMessage):
173
+ chat_history.append(msg)
174
+
175
+ # Execute the agent
176
+ try:
177
+ result = await executor.ainvoke(
178
+ {
179
+ "input": input_text,
180
+ "chat_history": chat_history,
181
+ }
182
+ )
183
+
184
+ # Process the result
185
+ output = result.get("output", "")
186
+
187
+ # Check if tools were called
188
+ if result.get("intermediate_steps"):
189
+ # Tools were called
190
+ tool_calls = []
191
+ for action, _ in result["intermediate_steps"]:
192
+ if hasattr(action, "tool") and hasattr(action, "tool_input"):
193
+ tool_calls.append(
194
+ MCPToolCall(
195
+ name=action.tool,
196
+ arguments=action.tool_input,
197
+ )
198
+ )
199
+
200
+ return AgentResponse(content=output, tool_calls=tool_calls, done=False)
201
+ else:
202
+ # No tools called, just text response
203
+ return AgentResponse(content=output, tool_calls=[], done=True)
204
+
205
+ except Exception as e:
206
+ logger.error("Agent execution failed: %s", e)
207
+ return AgentResponse(content=f"Error: {e!s}", tool_calls=[], done=True)
208
+
209
+ async def format_tool_results(
210
+ self, tool_calls: list[MCPToolCall], tool_results: list[MCPToolResult]
211
+ ) -> list[BaseMessage]:
212
+ """Format tool results into LangChain messages."""
213
+ # Create an AI message with the tool calls and results
214
+ messages = []
215
+
216
+ # First add an AI message indicating tools were called
217
+ tool_names = [tc.name for tc in tool_calls]
218
+ ai_content = f"I'll use the following tools: {', '.join(tool_names)}"
219
+ messages.append(AIMessage(content=ai_content))
220
+
221
+ # Build result text from tool results
222
+ text_parts = []
223
+ latest_screenshot = None
224
+
225
+ for tool_call, result in zip(tool_calls, tool_results, strict=False):
226
+ if result.isError:
227
+ error_text = "Tool execution failed"
228
+ for content in result.content:
229
+ if isinstance(content, types.TextContent):
230
+ error_text = content.text
231
+ break
232
+ text_parts.append(f"Error - {tool_call.name}: {error_text}")
233
+ else:
234
+ # Process success content
235
+ tool_output = []
236
+ for content in result.content:
237
+ if isinstance(content, types.TextContent):
238
+ tool_output.append(content.text)
239
+ elif isinstance(content, types.ImageContent):
240
+ latest_screenshot = content.data
241
+
242
+ if tool_output:
243
+ text_parts.append(f"{tool_call.name}: " + " ".join(tool_output))
244
+
245
+ result_text = "\n".join(text_parts) if text_parts else "No output from tools"
246
+
247
+ # Then add a human message with the tool results
248
+ if latest_screenshot:
249
+ # Include screenshot in multimodal format
250
+ content = [
251
+ {"type": "text", "text": f"Tool results:\n{result_text}"},
252
+ {
253
+ "type": "image_url",
254
+ "image_url": {"url": f"data:image/png;base64,{latest_screenshot}"},
255
+ },
256
+ ]
257
+ messages.append(HumanMessage(content=content))
258
+ else:
259
+ messages.append(HumanMessage(content=f"Tool results:\n{result_text}"))
260
+
261
+ 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"]