Skip to content

Commit f64744f

Browse files
declan-scaleclaude
andcommitted
fix(openai-temporal): render args for computer/local_shell/image_generation hosted tools
Add _hosted_tool_request branches for computer_call and local_shell_call (both carry an action object) and surface a compact image reference for image_generation_call results, so these hosted tools show real detail in the trace instead of empty args. Harden the web_search test to use the real ResponseFunctionWebSearch/ActionSearch SDK types (the action field exists on the SDK type) and add coverage for the new branches. Addresses Greptile review feedback on #442. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ec0df6c commit f64744f

2 files changed

Lines changed: 73 additions & 4 deletions

File tree

src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ def _hosted_tool_request(item: Any) -> tuple[str, str, dict[str, Any]]:
178178
args = {"queries": list(getattr(item, "queries", []) or [])}
179179
elif itype == "code_interpreter_call":
180180
args = {"code": getattr(item, "code", "") or ""}
181+
elif itype in ("computer_call", "local_shell_call"):
182+
# Both carry an `action` object: a ComputerAction (click/scroll/type/...)
183+
# or a LocalShellCallAction (command/env/cwd). Surface it as the args so
184+
# the trace shows what the tool actually did, not just its status.
185+
action = getattr(item, "action", None)
186+
if action is not None:
187+
args = _coerce_args(action)
181188
elif itype == "mcp_call":
182189
mcp_name = getattr(item, "name", None) or "mcp"
183190
server = getattr(item, "server_label", None)
@@ -204,6 +211,12 @@ def _hosted_tool_result(item: Any) -> str:
204211
results = getattr(item, "results", None)
205212
if results:
206213
return json.dumps([_serialize_item(r) for r in results])[:_HOSTED_TOOL_RESULT_CAP]
214+
elif itype == "image_generation_call":
215+
# `result` is base64 image data; surface a compact reference instead of
216+
# dumping the (large) payload into the trace.
217+
result = getattr(item, "result", None)
218+
if result:
219+
return f"<image generated: {len(result)} bytes>"
207220
return str(getattr(item, "status", "completed") or "completed")
208221

209222

src/agentex/lib/core/temporal/plugins/openai_agents/tests/test_hosted_tools.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010

1111
from types import SimpleNamespace
1212

13+
from openai.types.responses.response_output_item import (
14+
LocalShellCall,
15+
ImageGenerationCall,
16+
LocalShellCallAction,
17+
)
18+
from openai.types.responses.response_computer_tool_call import ActionClick, ResponseComputerToolCall
19+
from openai.types.responses.response_function_web_search import ActionSearch, ResponseFunctionWebSearch
20+
1321
from agentex.lib.core.temporal.plugins.openai_agents.models.temporal_streaming_model import (
1422
_HOSTED_TOOL_TYPES,
1523
_coerce_args,
@@ -20,7 +28,7 @@
2028

2129
def test_hosted_tool_types_membership():
2230
for t in ("web_search_call", "file_search_call", "code_interpreter_call",
23-
"image_generation_call", "mcp_call"):
31+
"image_generation_call", "mcp_call", "computer_call", "local_shell_call"):
2432
assert t in _HOSTED_TOOL_TYPES
2533
assert "function_call" not in _HOSTED_TOOL_TYPES
2634

@@ -34,12 +42,50 @@ def test_coerce_args_variants():
3442

3543

3644
def test_hosted_tool_request_web_search():
37-
item = SimpleNamespace(type="web_search_call", id="ws_1",
38-
action={"query": "agentex"})
45+
# Use the real Responses-API type to prove `action` is a genuine SDK field
46+
# (it is on ResponseFunctionWebSearch), not a hand-crafted stand-in.
47+
item = ResponseFunctionWebSearch(
48+
id="ws_1",
49+
status="completed",
50+
type="web_search_call",
51+
action=ActionSearch(type="search", query="agentex"),
52+
)
3953
call_id, name, args = _hosted_tool_request(item)
4054
assert call_id == "ws_1"
4155
assert name == "web_search" # "_call" stripped
42-
assert args == {"query": "agentex"}
56+
assert args["query"] == "agentex"
57+
assert args["type"] == "search"
58+
59+
60+
def test_hosted_tool_request_computer_call():
61+
item = ResponseComputerToolCall(
62+
id="cc_1",
63+
call_id="ccall_1",
64+
type="computer_call",
65+
status="completed",
66+
pending_safety_checks=[],
67+
action=ActionClick(type="click", button="left", x=10, y=20),
68+
)
69+
call_id, name, args = _hosted_tool_request(item)
70+
assert call_id == "cc_1"
71+
assert name == "computer"
72+
assert args["type"] == "click"
73+
assert args["button"] == "left"
74+
assert args["x"] == 10 and args["y"] == 20
75+
76+
77+
def test_hosted_tool_request_local_shell_call():
78+
item = LocalShellCall(
79+
id="ls_1",
80+
call_id="lscall_1",
81+
type="local_shell_call",
82+
status="completed",
83+
action=LocalShellCallAction(type="exec", command=["ls", "-la"], env={}),
84+
)
85+
call_id, name, args = _hosted_tool_request(item)
86+
assert call_id == "ls_1"
87+
assert name == "local_shell"
88+
assert args["command"] == ["ls", "-la"]
4389

4490

4591
def test_hosted_tool_request_mcp_uses_server_label():
@@ -74,6 +120,16 @@ def test_hosted_tool_result_mcp_error_and_output():
74120
assert _hosted_tool_result(ok_item) == "done"
75121

76122

123+
def test_hosted_tool_result_image_generation():
124+
item = ImageGenerationCall(
125+
id="ig_1",
126+
type="image_generation_call",
127+
status="completed",
128+
result="QUJD", # 4 chars of (fake) base64
129+
)
130+
assert _hosted_tool_result(item) == "<image generated: 4 bytes>"
131+
132+
77133
def test_hosted_tool_result_falls_back_to_status():
78134
item = SimpleNamespace(type="web_search_call", status="completed")
79135
assert _hosted_tool_result(item) == "completed"

0 commit comments

Comments
 (0)