feat(lifecycle): pass ToolContext to on_tool_start/end for all tool types#3403
feat(lifecycle): pass ToolContext to on_tool_start/end for all tool types#3403adityasingh2400 wants to merge 1 commit into
Conversation
…ypes Computer, local shell, shell, and apply_patch tools previously passed a plain RunContextWrapper to on_tool_start/on_tool_end, leaving callers unable to distinguish parallel tool calls. Construct a ToolContext at each of those call sites so hooks consistently receive tool_call_id, tool_name, and tool_arguments. Refine the parameter annotation on RunHooksBase.on_tool_start, RunHooksBase.on_tool_end, AgentHooksBase.on_tool_start, and AgentHooksBase.on_tool_end from RunContextWrapper to ToolContext (a subclass) so subclasses can rely on the tool-call-specific fields. Closes openai#1849
|
I don't think this change could work due to #1849 (comment) |
|
@seratch — that comment was about why we couldn't just refine the annotation. This PR addresses ihower's concern directly by converting the four non-function-tool call sites ( Specifically:
Happy to walk through any specific call site if there's a concrete path I missed, or split the call-site rewrites and the annotation refinement into two PRs if that's easier to review. |
|
Thanks for clarifying it. Either way, this could be a breaking change, so we don't immediately plan to make this type of change. |
|
Closing per @seratch's call — appreciate the consideration. If the team revisits this and decides a future major release can absorb the subtype refinement, happy to revive this work; in the meantime the docstring already steers users toward checking for |
Summary
Users who track timing metrics inside
on_tool_startandon_tool_endcannotcorrelate the start and end of the same tool call when calls execute in
parallel, because the
RunContextWrapperthey receive has notool_call_id.Function tools already pass a
ToolContext(which exposestool_call_id,tool_name, andtool_arguments), butComputerTool,LocalShellTool,ShellTool, andApplyPatchToolall passed a plainRunContextWrapper, sohooks could not rely on the field being present.
This PR constructs a
ToolContextviaToolContext.from_agent_context(...)at the four remaining call sites in
run_internal/tool_actions.py. Each sitepulls
tool_call_idfrom the call'scall_idfield, setstool_namefromthe tool's
.name, and serializes the structured action payload intotool_argumentsfor visibility. The sametool_contextis then passed toboth global
RunHooksand per-agentAgentHooksforon_tool_startandon_tool_end, mirroring the pattern already used for function tools andcustom tools.
The parameter annotation on
RunHooksBase.on_tool_start,RunHooksBase.on_tool_end,AgentHooksBase.on_tool_start, andAgentHooksBase.on_tool_endis refined fromRunContextWrapper[TContext]toToolContext[TContext]. BecauseToolContextis a subclass ofRunContextWrapper, existing user subclasses that accept the wider typecontinue to type-check; new hooks can rely on
tool_call_id,tool_name,and
tool_argumentsdirectly. New regression tests intests/test_computer_action.py,tests/test_local_shell_tool.py,tests/test_shell_tool.py, andtests/test_apply_patch_tool.pycapturethe hook context for each tool family and assert the
tool_call_idmatchesthe actual call's
call_id.Closes #1849
Test plan
make formatmake lintmake typecheck(no new errors introduced in touched files)uv run pytest tests/test_run_hooks.py tests/test_agent_hooks.py tests/test_computer_action.py tests/test_local_shell_tool.py tests/test_apply_patch_tool.py tests/test_shell_tool.py tests/test_custom_tool.py tests/test_global_hooks.py tests/test_agent_llm_hooks.py tests/test_computer_tool_lifecycle.py tests/test_agent_runner.py tests/test_agent_runner_streamed.py tests/test_function_tool.py