hud-python 0.4.19__py3-none-any.whl → 0.4.21__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.

@@ -1,179 +1,420 @@
1
- """Tests for server exceptions."""
1
+ """Tests for the HUD SDK Exception System.
2
+
3
+ This module tests the intelligent exception handling with automatic error
4
+ classification and helpful hints for users.
5
+ """
2
6
 
3
7
  from __future__ import annotations
4
8
 
5
- from unittest.mock import MagicMock
9
+ import json
10
+ from unittest.mock import Mock, patch
6
11
 
7
12
  import httpx
13
+ import pytest
8
14
 
9
15
  from hud.shared.exceptions import (
10
- GymMakeException,
11
16
  HudAuthenticationError,
17
+ HudClientError,
18
+ HudConfigError,
12
19
  HudException,
13
- HudNetworkError,
20
+ HudRateLimitError,
14
21
  HudRequestError,
15
22
  HudTimeoutError,
23
+ HudToolNotFoundError,
24
+ )
25
+ from hud.shared.hints import (
26
+ CLIENT_NOT_INITIALIZED,
27
+ HUD_API_KEY_MISSING,
28
+ INVALID_CONFIG,
29
+ RATE_LIMIT_HIT,
30
+ TOOL_NOT_FOUND,
16
31
  )
17
32
 
18
33
 
19
- class TestHudRequestError:
20
- """Test HudRequestError exception."""
21
-
22
- def test_from_httpx_error_with_json_detail(self):
23
- """Test creating from httpx error with JSON detail."""
24
- response = MagicMock()
25
- response.status_code = 400
26
- response.json.return_value = {"detail": "Bad request details"}
27
-
28
- error = httpx.HTTPStatusError("Test", request=MagicMock(), response=response)
29
-
30
- hud_error = HudRequestError.from_httpx_error(error, context="Test context")
31
-
32
- assert hud_error.status_code == 400
33
- assert "Test context" in str(hud_error)
34
- assert "Bad request details" in str(hud_error)
35
-
36
- def test_from_httpx_error_with_small_json_no_detail(self):
37
- """Test creating from httpx error with small JSON but no detail field."""
38
- response = MagicMock()
39
- response.status_code = 400
40
- response.json.return_value = {"error": "test", "code": 123}
41
-
42
- error = httpx.HTTPStatusError("Test", request=MagicMock(), response=response)
43
-
44
- hud_error = HudRequestError.from_httpx_error(error)
45
-
46
- assert hud_error.status_code == 400
47
- assert "JSON response:" in str(hud_error)
48
- # Check for the dictionary representation (not exact JSON string)
49
- assert "'error': 'test'" in str(hud_error)
50
- assert "'code': 123" in str(hud_error)
51
-
52
- def test_from_httpx_error_json_parse_failure(self):
53
- """Test creating from httpx error when JSON parsing fails."""
54
- response = MagicMock()
55
- response.status_code = 500
56
- response.json.side_effect = ValueError("Invalid JSON")
57
-
58
- error = httpx.HTTPStatusError("Test", request=MagicMock(), response=response)
59
-
60
- hud_error = HudRequestError.from_httpx_error(error)
61
-
62
- assert hud_error.status_code == 500
63
- assert "Request failed with status 500" in str(hud_error)
64
-
65
- def test_from_httpx_error_large_json_response(self):
66
- """Test creating from httpx error with large JSON response."""
67
- response = MagicMock()
68
- response.status_code = 400
69
- # Large JSON object (more than 5 keys)
70
- response.json.return_value = {
71
- "field1": "value1",
72
- "field2": "value2",
73
- "field3": "value3",
74
- "field4": "value4",
75
- "field5": "value5",
76
- "field6": "value6",
77
- }
78
-
79
- error = httpx.HTTPStatusError("Test", request=MagicMock(), response=response)
80
-
81
- hud_error = HudRequestError.from_httpx_error(error)
82
-
83
- assert hud_error.status_code == 400
84
- # Should not include JSON in message since it's large
85
- assert "JSON response:" not in str(hud_error)
86
- assert "Request failed with status 400" in str(hud_error)
87
-
88
- def test_str_method(self):
89
- """Test string representation of HudRequestError."""
90
- error = HudRequestError("Test error message", 404, '{"extra": "data"}')
91
-
92
- error_str = str(error)
93
- assert "Test error message" in error_str
94
- assert "404" in error_str
95
- assert "extra" in error_str
96
-
97
-
98
- class TestHudNetworkError:
99
- """Test HudNetworkError exception."""
100
-
101
- def test_initialization_and_str(self):
102
- """Test HudNetworkError initialization and string representation."""
103
- error = HudNetworkError("Network failure: Connection refused")
34
+ class TestHudExceptionAutoConversion:
35
+ """Test automatic exception conversion via 'raise HudException() from e'."""
36
+
37
+ def test_client_not_initialized_error(self):
38
+ """Test that 'not initialized' errors become HudClientError."""
39
+ try:
40
+ raise ValueError("Client not initialized - call initialize() first")
41
+ except Exception as e:
42
+ with pytest.raises(HudClientError) as exc_info:
43
+ raise HudException from e
44
+
45
+ assert exc_info.value.hints == [CLIENT_NOT_INITIALIZED]
46
+ assert str(exc_info.value) == "Client not initialized - call initialize() first"
47
+
48
+ def test_not_connected_error(self):
49
+ """Test that 'not connected' errors become HudClientError."""
50
+ try:
51
+ raise RuntimeError("Session not connected to server")
52
+ except Exception as e:
53
+ with pytest.raises(HudClientError) as exc_info:
54
+ raise HudException from e
55
+
56
+ assert exc_info.value.hints == [CLIENT_NOT_INITIALIZED]
57
+
58
+ def test_config_invalid_json_error(self):
59
+ """Test that JSON errors become HudConfigError."""
60
+ try:
61
+ json.loads("{invalid json}")
62
+ except json.JSONDecodeError as e:
63
+ with pytest.raises(HudConfigError) as exc_info:
64
+ raise HudException from e
65
+
66
+ assert exc_info.value.hints == [INVALID_CONFIG]
67
+
68
+ def test_config_error_keyword(self):
69
+ """Test that errors with 'config' become HudConfigError."""
70
+ try:
71
+ raise ValueError("Invalid config: missing required field 'url'")
72
+ except Exception as e:
73
+ with pytest.raises(HudConfigError) as exc_info:
74
+ raise HudException from e
75
+
76
+ assert exc_info.value.hints == [INVALID_CONFIG]
77
+
78
+ def test_tool_not_found_error(self):
79
+ """Test that tool not found errors become HudToolNotFoundError."""
80
+ try:
81
+ raise KeyError("Tool 'missing_tool' not found in registry")
82
+ except Exception as e:
83
+ with pytest.raises(HudToolNotFoundError) as exc_info:
84
+ raise HudException from e
85
+
86
+ assert exc_info.value.hints == [TOOL_NOT_FOUND]
87
+
88
+ def test_tool_not_exist_error(self):
89
+ """Test that tool not exist errors become HudToolNotFoundError."""
90
+ try:
91
+ raise RuntimeError("Tool does not exist: calculator")
92
+ except Exception as e:
93
+ with pytest.raises(HudToolNotFoundError) as exc_info:
94
+ raise HudException from e
95
+
96
+ assert exc_info.value.hints == [TOOL_NOT_FOUND]
97
+
98
+ def test_hud_api_key_error(self):
99
+ """Test that HUD API key errors become HudAuthenticationError."""
100
+ try:
101
+ raise ValueError("API key missing for mcp.hud.so")
102
+ except Exception as e:
103
+ with pytest.raises(HudAuthenticationError) as exc_info:
104
+ raise HudException from e
105
+
106
+ assert exc_info.value.hints == [HUD_API_KEY_MISSING]
107
+
108
+ def test_hud_authorization_error(self):
109
+ """Test that HUD authorization errors become HudAuthenticationError."""
110
+ try:
111
+ raise PermissionError("Authorization failed for HUD API")
112
+ except Exception as e:
113
+ with pytest.raises(HudAuthenticationError) as exc_info:
114
+ raise HudException from e
115
+
116
+ assert exc_info.value.hints == [HUD_API_KEY_MISSING]
117
+
118
+ def test_rate_limit_error(self):
119
+ """Test that rate limit errors become HudRateLimitError."""
120
+ try:
121
+ raise RuntimeError("Rate limit exceeded")
122
+ except Exception as e:
123
+ with pytest.raises(HudRateLimitError) as exc_info:
124
+ raise HudException from e
125
+
126
+ assert exc_info.value.hints == [RATE_LIMIT_HIT]
127
+
128
+ def test_too_many_requests_error(self):
129
+ """Test that 'too many request' errors become HudRateLimitError."""
130
+ try:
131
+ raise httpx.HTTPStatusError("Too many requests", request=Mock(), response=Mock())
132
+ except Exception as e:
133
+ with pytest.raises(HudRateLimitError) as exc_info:
134
+ raise HudException from e
135
+
136
+ assert exc_info.value.hints == [RATE_LIMIT_HIT]
137
+
138
+ def test_timeout_error(self):
139
+ """Test that TimeoutError becomes HudTimeoutError."""
140
+ try:
141
+ raise TimeoutError("Operation timed out")
142
+ except Exception as e:
143
+ with pytest.raises(HudTimeoutError) as exc_info:
144
+ raise HudException from e
145
+
146
+ assert exc_info.value.hints == [] # No default hints for timeout
147
+
148
+ def test_asyncio_timeout_error(self):
149
+ """Test that asyncio.TimeoutError becomes HudTimeoutError."""
150
+ try:
151
+ raise TimeoutError("Async operation timed out")
152
+ except Exception as e:
153
+ with pytest.raises(HudTimeoutError) as exc_info:
154
+ raise HudException from e
155
+
156
+ assert str(exc_info.value) == "Async operation timed out"
157
+
158
+ def test_generic_error_remains_hudexception(self):
159
+ """Test that unmatched errors remain as base HudException."""
160
+ try:
161
+ raise ValueError("Some random error")
162
+ except Exception as e:
163
+ with pytest.raises(HudException) as exc_info:
164
+ raise HudException from e
165
+
166
+ # Should be base HudException, not a subclass
167
+ assert type(exc_info.value) is HudException
168
+ assert exc_info.value.hints == []
169
+
170
+ def test_custom_message_override(self):
171
+ """Test that custom message overrides the original."""
172
+ try:
173
+ raise ValueError("Original error")
174
+ except Exception as e:
175
+ with pytest.raises(HudException) as exc_info:
176
+ raise HudException("Custom error message") from e
177
+
178
+ assert str(exc_info.value) == "Custom error message"
179
+
180
+ def test_already_hud_exception_passthrough(self):
181
+ """Test that existing HudExceptions are not re-wrapped."""
182
+ original = HudAuthenticationError("Already a HUD exception")
183
+
184
+ try:
185
+ raise original
186
+ except Exception as e:
187
+ with pytest.raises(HudAuthenticationError) as exc_info:
188
+ raise HudException from e
189
+
190
+ # Should be the same instance
191
+ assert exc_info.value is original
104
192
 
105
- error_str = str(error)
106
- assert "Network failure" in error_str
107
- assert "Connection refused" in error_str
108
193
 
194
+ class TestHudRequestError:
195
+ """Test HudRequestError specific behavior."""
196
+
197
+ def test_401_adds_auth_hint(self):
198
+ """Test that 401 status adds authentication hint."""
199
+ error = HudRequestError("Unauthorized", status_code=401)
200
+ assert HUD_API_KEY_MISSING in error.hints
201
+
202
+ def test_403_adds_auth_hint(self):
203
+ """Test that 403 status adds authentication hint."""
204
+ error = HudRequestError("Forbidden", status_code=403)
205
+ assert HUD_API_KEY_MISSING in error.hints
206
+
207
+ def test_429_adds_rate_limit_hint(self):
208
+ """Test that 429 status adds rate limit hint."""
209
+ error = HudRequestError("Too Many Requests", status_code=429)
210
+ assert RATE_LIMIT_HIT in error.hints
211
+
212
+ def test_other_status_no_default_hints(self):
213
+ """Test that other status codes don't add default hints."""
214
+ error = HudRequestError("Server Error", status_code=500)
215
+ assert error.hints == []
216
+
217
+ def test_explicit_hints_override_defaults(self):
218
+ """Test that explicit hints override status-based defaults."""
219
+ from hud.shared.hints import Hint
220
+
221
+ custom_hint = Hint(title="Custom Error", message="This is a custom hint")
222
+ error = HudRequestError("Unauthorized", status_code=401, hints=[custom_hint])
223
+ assert error.hints == [custom_hint]
224
+ assert HUD_API_KEY_MISSING not in error.hints
225
+
226
+ def test_from_httpx_error(self):
227
+ """Test creating from HTTPx error."""
228
+ request = httpx.Request("GET", "https://api.test.com")
229
+ response = httpx.Response(404, json={"detail": "Not found"}, request=request)
230
+ httpx_error = httpx.HTTPStatusError("Not found", request=request, response=response)
231
+
232
+ error = HudRequestError.from_httpx_error(httpx_error, context="Testing")
233
+
234
+ assert error.status_code == 404
235
+ assert "Testing" in str(error)
236
+ assert "Not found" in str(error)
237
+ assert error.response_json == {"detail": "Not found"}
238
+
239
+
240
+ class TestMCPErrorHandling:
241
+ """Test handling of MCP-specific errors."""
242
+
243
+ @pytest.mark.asyncio
244
+ async def test_mcp_error_handling(self):
245
+ """Test that McpError is handled appropriately."""
246
+ # Since McpError is imported dynamically, we'll mock it
247
+ with patch("hud.clients.mcp_use.McpError") as MockMcpError:
248
+ MockMcpError.side_effect = Exception
249
+
250
+ # Create a mock MCP error
251
+ mcp_error = Exception("MCP protocol error: Unknown method")
252
+ mcp_error.__class__.__name__ = "McpError"
253
+
254
+ try:
255
+ raise mcp_error
256
+ except Exception as e:
257
+ # This would typically be caught in the client code
258
+ # and re-raised as HudException
259
+ with pytest.raises(HudException) as exc_info:
260
+ raise HudException from e
261
+
262
+ assert "MCP protocol error" in str(exc_info.value)
263
+
264
+ def test_mcp_tool_error_result(self):
265
+ """Test handling of MCP tool execution errors (isError: true)."""
266
+ # Simulate an MCP tool result with error
267
+ tool_result = {
268
+ "content": [{"type": "text", "text": "Failed to fetch data: API rate limit exceeded"}],
269
+ "isError": True,
270
+ }
109
271
 
110
- class TestHudTimeoutError:
111
- """Test HudTimeoutError exception."""
272
+ # In real usage, this would be checked in the client
273
+ if tool_result.get("isError"):
274
+ error_text = tool_result["content"][0]["text"]
112
275
 
113
- def test_initialization(self):
114
- """Test HudTimeoutError initialization."""
115
- error = HudTimeoutError("Request timed out after 30.0 seconds")
276
+ try:
277
+ raise RuntimeError(error_text)
278
+ except Exception as e:
279
+ with pytest.raises(HudRateLimitError) as exc_info:
280
+ raise HudException from e
116
281
 
117
- error_str = str(error)
118
- assert "Request timed out" in error_str
119
- assert "30.0" in error_str
282
+ assert exc_info.value.hints == [RATE_LIMIT_HIT]
120
283
 
121
- def test_str_method(self):
122
- """Test string representation of HudTimeoutError."""
123
- error = HudTimeoutError("Timeout occurred after 60.0 seconds")
124
284
 
125
- error_str = str(error)
126
- assert "Timeout occurred" in error_str
127
- assert "60.0" in error_str
285
+ class TestExceptionIntegration:
286
+ """Test exception handling in integrated scenarios."""
128
287
 
288
+ @pytest.mark.asyncio
289
+ async def test_client_initialization_flow(self):
290
+ """Test exception flow during client initialization."""
291
+ from hud.clients.base import BaseHUDClient
129
292
 
130
- class TestHudAuthenticationError:
131
- """Test HudAuthenticationError exception."""
293
+ # Mock a client that fails initialization
294
+ client = Mock(spec=BaseHUDClient)
132
295
 
133
- def test_inheritance(self):
134
- """Test that HudAuthenticationError inherits from HudException."""
135
- error = HudAuthenticationError("Auth failed")
136
-
137
- assert isinstance(error, HudException)
138
- error_str = str(error)
139
- assert "Auth failed" in error_str
296
+ # Simulate missing config
297
+ try:
298
+ if not hasattr(client, "_mcp_config"):
299
+ raise ValueError("MCP config not set")
300
+ except Exception as e:
301
+ with pytest.raises(HudConfigError) as exc_info:
302
+ raise HudException from e
140
303
 
304
+ assert exc_info.value.hints == [INVALID_CONFIG]
141
305
 
142
- class TestGymMakeException:
143
- """Test GymMakeException."""
306
+ def test_json_parsing_flow(self):
307
+ """Test exception flow during JSON parsing."""
308
+ invalid_json = '{"incomplete": '
144
309
 
145
- def test_initialization_and_str(self):
146
- """Test GymMakeException initialization and string representation."""
147
- data = {"env_id": "test-env", "error": "invalid config"}
148
- error = GymMakeException("Failed to create environment", data)
310
+ try:
311
+ _ = json.loads(invalid_json)
312
+ except json.JSONDecodeError as e:
313
+ with pytest.raises(HudConfigError) as exc_info:
314
+ raise HudException from e
149
315
 
150
- assert error.data == data
316
+ assert "Expecting value" in str(exc_info.value)
317
+ assert exc_info.value.hints == [INVALID_CONFIG]
151
318
 
152
- error_str = str(error)
153
- assert "Failed to create environment" in error_str
154
- assert "Data:" in error_str
155
- assert "env_id" in error_str
156
- assert "test-env" in error_str
157
- assert "invalid config" in error_str
319
+ @pytest.mark.asyncio
320
+ async def test_network_error_flow(self):
321
+ """Test exception flow during network operations."""
322
+ # Simulate a connection error
323
+ try:
324
+ raise ConnectionError("Connection refused")
325
+ except Exception as e:
326
+ with pytest.raises(HudException) as exc_info:
327
+ raise HudException("Failed to connect to server") from e
158
328
 
329
+ # Should remain base HudException for generic connection errors
330
+ assert type(exc_info.value) is HudException
331
+ assert str(exc_info.value) == "Failed to connect to server"
159
332
 
160
- class TestHudException:
161
- """Test base HudException class."""
162
333
 
163
- def test_str_with_response_json(self):
164
- """Test HudException string representation with response_json."""
165
- response_data = {"error": "test error", "code": 42}
166
- error = HudException("Base error message", response_data)
167
-
168
- error_str = str(error)
169
- assert "Base error message" in error_str
170
- assert "error" in error_str
171
- assert "test error" in error_str
334
+ class TestExceptionRendering:
335
+ """Test how exceptions are rendered and displayed."""
172
336
 
173
- def test_str_without_response_json(self):
174
- """Test HudException string representation without response_json."""
175
- error = HudException("Just a message")
337
+ def test_exception_string_representation(self):
338
+ """Test __str__ method of exceptions."""
339
+ error = HudRequestError(
340
+ "Request failed", status_code=404, response_json={"error": "Not found"}
341
+ )
176
342
 
177
343
  error_str = str(error)
178
- assert error_str == "Just a message"
179
- assert "Response:" not in error_str
344
+ assert "Request failed" in error_str
345
+ assert "Status: 404" in error_str
346
+ assert "Response JSON: {'error': 'Not found'}" in error_str
347
+
348
+ def test_exception_with_hints(self):
349
+ """Test that exceptions carry their hints properly."""
350
+ error = HudAuthenticationError("API key missing")
351
+
352
+ assert len(error.hints) == 1
353
+ assert error.hints[0] == HUD_API_KEY_MISSING
354
+ assert error.hints[0].title == "HUD API key required"
355
+ assert "Set HUD_API_KEY environment variable" in error.hints[0].tips[0]
356
+
357
+ def test_exception_type_preservation(self):
358
+ """Test that exception types are preserved through conversion."""
359
+ test_cases = [
360
+ ("Client not initialized", HudClientError),
361
+ ("Invalid JSON config", HudConfigError),
362
+ ("Tool 'test' not found", HudToolNotFoundError),
363
+ ("API key missing for HUD", HudAuthenticationError),
364
+ ("Rate limit exceeded", HudRateLimitError),
365
+ (TimeoutError("Timeout"), HudTimeoutError),
366
+ ]
367
+
368
+ for error_msg, expected_type in test_cases:
369
+ try:
370
+ if isinstance(error_msg, Exception):
371
+ raise error_msg
372
+ else:
373
+ raise ValueError(error_msg)
374
+ except Exception as e:
375
+ with pytest.raises(expected_type):
376
+ raise HudException from e
377
+
378
+
379
+ class TestEdgeCases:
380
+ """Test edge cases and error conditions."""
381
+
382
+ def test_none_exception_handling(self):
383
+ """Test handling when no exception context exists."""
384
+ # When there's no active exception, should create normal HudException
385
+ error = HudException("No chained exception")
386
+ assert type(error) is HudException
387
+ assert str(error) == "No chained exception"
388
+
389
+ def test_baseexception_not_converted(self):
390
+ """Test that BaseException (not Exception) is not converted."""
391
+ try:
392
+ raise KeyboardInterrupt("User interrupted")
393
+ except BaseException:
394
+ # Should not attempt to convert BaseException
395
+ error = HudException("Interrupted")
396
+ assert type(error) is HudException
397
+
398
+ def test_empty_error_message(self):
399
+ """Test handling of empty error messages."""
400
+ try:
401
+ raise ValueError("")
402
+ except Exception as e:
403
+ with pytest.raises(HudException) as exc_info:
404
+ raise HudException from e
405
+
406
+ # Should still have some message
407
+ assert str(exc_info.value) != ""
408
+
409
+ def test_circular_exception_chain(self):
410
+ """Test that we don't create circular exception chains."""
411
+ original = HudAuthenticationError("Original")
412
+
413
+ try:
414
+ raise original
415
+ except HudException as e:
416
+ # Raising HudException from HudException should not re-wrap
417
+ with pytest.raises(HudAuthenticationError) as exc_info:
418
+ raise HudException from e
419
+
420
+ assert exc_info.value is original
hud/tools/__init__.py CHANGED
@@ -9,6 +9,7 @@ from .bash import BashTool
9
9
  from .edit import EditTool
10
10
  from .playwright import PlaywrightTool
11
11
  from .response import ResponseTool
12
+ from .submit import SubmitTool
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from .computer import AnthropicComputerTool, HudComputerTool, OpenAIComputerTool
@@ -23,6 +24,7 @@ __all__ = [
23
24
  "OpenAIComputerTool",
24
25
  "PlaywrightTool",
25
26
  "ResponseTool",
27
+ "SubmitTool",
26
28
  ]
27
29
 
28
30
 
hud/tools/playwright.py CHANGED
@@ -153,7 +153,7 @@ class PlaywrightTool(BaseTool):
153
153
  """Ensure browser is launched and ready."""
154
154
  if self._browser is None or not self._browser.is_connected():
155
155
  if self._cdp_url:
156
- logger.info("Connecting to remote browser via CDP: %s", self._cdp_url)
156
+ logger.info("Connecting to remote browser via CDP")
157
157
  else:
158
158
  logger.info("Launching Playwright browser...")
159
159
 
hud/tools/submit.py ADDED
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ from mcp.types import ContentBlock, TextContent
6
+
7
+ from .response import ResponseTool
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ # Global submission storage
13
+ _SUBMISSION: str | None = None
14
+
15
+
16
+ def set_submission(value: str | None) -> None:
17
+ global _SUBMISSION
18
+ _SUBMISSION = value
19
+
20
+
21
+ def get_submission() -> str | None:
22
+ return _SUBMISSION
23
+
24
+
25
+ class SubmitTool(ResponseTool):
26
+ """Lifecycle tool to submit the agent's final answer for evaluation.
27
+
28
+ Accepts either a `response` string or a `messages` list and stores the
29
+ submission as a plain string, accessible via `get_submission()`.
30
+ Priority: The last text content in `messages` (if provided) overrides `response`.
31
+ """
32
+
33
+ name: str = "response"
34
+ title: str = "Submit Tool"
35
+ description: str = "Submit the agent's final response for later evaluation"
36
+
37
+ async def __call__(
38
+ self, response: str | None = None, messages: list[ContentBlock] | None = None
39
+ ) -> list[ContentBlock]:
40
+ # 1) If messages provided, take the last text block
41
+ # chosen: str | None = None
42
+
43
+ # if messages:
44
+ # # Gather all text blocks
45
+ # text_blocks: list[str] = []
46
+ # for block in messages:
47
+ # try:
48
+ # if isinstance(block, TextContent):
49
+ # text_blocks.append(str(block.text))
50
+ # except Exception:
51
+ # logger.debug("SubmitTool skipped non-text block: %s", block)
52
+ # continue
53
+ # if text_blocks:
54
+ # chosen = text_blocks[-1]
55
+
56
+ # # 2) Otherwise use `response` as-is
57
+ # if chosen is None and response is not None:
58
+ # chosen = response
59
+
60
+ set_submission(response)
61
+
62
+ # Echo back what we stored
63
+ blocks: list[ContentBlock] = []
64
+ if response:
65
+ blocks.append(TextContent(text=response, type="text"))
66
+ return blocks