11from __future__ import annotations
22
33import weakref
4- from typing import TYPE_CHECKING
4+ from typing import TYPE_CHECKING , Any
55
66if TYPE_CHECKING :
77 from openai .types .responses .response_function_tool_call import ResponseFunctionToolCall
88
99 from .result import RunResult , RunResultStreaming
1010
11+ ToolCallSignature = tuple [str , str , str , str , str | None , str | None ]
12+ ScopedToolCallSignature = tuple [str | None , ToolCallSignature ]
13+
14+ _AGENT_TOOL_STATE_SCOPE_ATTR = "_agent_tool_state_scope_id"
15+
1116# Ephemeral maps linking tool call objects to nested agent results within the same run.
1217# Store by object identity, and index by a stable signature to avoid call ID collisions.
1318_agent_tool_run_results_by_obj : dict [int , RunResult | RunResultStreaming ] = {}
1419_agent_tool_run_results_by_signature : dict [
15- tuple [ str , str , str , str , str | None , str | None ] ,
20+ ScopedToolCallSignature ,
1621 set [int ],
1722] = {}
1823_agent_tool_run_result_signature_by_obj : dict [
1924 int ,
20- tuple [ str , str , str , str , str | None , str | None ] ,
25+ ScopedToolCallSignature ,
2126] = {}
2227_agent_tool_call_refs_by_obj : dict [int , weakref .ReferenceType [ResponseFunctionToolCall ]] = {}
2328
2429
30+ def get_agent_tool_state_scope (context : Any ) -> str | None :
31+ """Read the private agent-tool cache scope id from a context wrapper."""
32+ scope_id = getattr (context , _AGENT_TOOL_STATE_SCOPE_ATTR , None )
33+ return scope_id if isinstance (scope_id , str ) else None
34+
35+
36+ def set_agent_tool_state_scope (context : Any , scope_id : str | None ) -> None :
37+ """Attach or clear the private agent-tool cache scope id on a context wrapper."""
38+ if context is None :
39+ return
40+ if scope_id is None :
41+ try :
42+ delattr (context , _AGENT_TOOL_STATE_SCOPE_ATTR )
43+ except Exception :
44+ return
45+ return
46+ try :
47+ setattr (context , _AGENT_TOOL_STATE_SCOPE_ATTR , scope_id )
48+ except Exception :
49+ return
50+
51+
2552def _tool_call_signature (
2653 tool_call : ResponseFunctionToolCall ,
27- ) -> tuple [ str , str , str , str , str | None , str | None ] :
54+ ) -> ToolCallSignature :
2855 """Build a stable signature for fallback lookup across tool call instances."""
2956 return (
3057 tool_call .call_id ,
@@ -36,11 +63,21 @@ def _tool_call_signature(
3663 )
3764
3865
66+ def _scoped_tool_call_signature (
67+ tool_call : ResponseFunctionToolCall , * , scope_id : str | None
68+ ) -> ScopedToolCallSignature :
69+ """Build a scope-qualified signature so independently restored states do not collide."""
70+ return (scope_id , _tool_call_signature (tool_call ))
71+
72+
3973def _index_agent_tool_run_result (
40- tool_call : ResponseFunctionToolCall , tool_call_obj_id : int
74+ tool_call : ResponseFunctionToolCall ,
75+ tool_call_obj_id : int ,
76+ * ,
77+ scope_id : str | None ,
4178) -> None :
4279 """Track tool call objects by signature for fallback lookup."""
43- signature = _tool_call_signature (tool_call )
80+ signature = _scoped_tool_call_signature (tool_call , scope_id = scope_id )
4481 _agent_tool_run_result_signature_by_obj [tool_call_obj_id ] = signature
4582 _agent_tool_run_results_by_signature .setdefault (signature , set ()).add (tool_call_obj_id )
4683
@@ -80,26 +117,40 @@ def _on_tool_call_gc(_ref: weakref.ReferenceType[ResponseFunctionToolCall]) -> N
80117
81118
82119def record_agent_tool_run_result (
83- tool_call : ResponseFunctionToolCall , run_result : RunResult | RunResultStreaming
120+ tool_call : ResponseFunctionToolCall ,
121+ run_result : RunResult | RunResultStreaming ,
122+ * ,
123+ scope_id : str | None = None ,
84124) -> None :
85125 """Store the nested agent run result by tool call identity."""
86126 tool_call_obj_id = id (tool_call )
87127 _agent_tool_run_results_by_obj [tool_call_obj_id ] = run_result
88- _index_agent_tool_run_result (tool_call , tool_call_obj_id )
128+ _index_agent_tool_run_result (tool_call , tool_call_obj_id , scope_id = scope_id )
89129 _register_tool_call_ref (tool_call , tool_call_obj_id )
90130
91131
132+ def _tool_call_obj_matches_scope (tool_call_obj_id : int , * , scope_id : str | None ) -> bool :
133+ scoped_signature = _agent_tool_run_result_signature_by_obj .get (tool_call_obj_id )
134+ if scoped_signature is None :
135+ # Fallback for unindexed entries.
136+ return scope_id is None
137+ return scoped_signature [0 ] == scope_id
138+
139+
92140def consume_agent_tool_run_result (
93141 tool_call : ResponseFunctionToolCall ,
142+ * ,
143+ scope_id : str | None = None ,
94144) -> RunResult | RunResultStreaming | None :
95145 """Return and drop the stored nested agent run result for the given tool call."""
96146 obj_id = id (tool_call )
97- run_result = _agent_tool_run_results_by_obj .pop (obj_id , None )
98- if run_result is not None :
99- _drop_agent_tool_run_result (obj_id )
100- return run_result
147+ if _tool_call_obj_matches_scope (obj_id , scope_id = scope_id ):
148+ run_result = _agent_tool_run_results_by_obj .pop (obj_id , None )
149+ if run_result is not None :
150+ _drop_agent_tool_run_result (obj_id )
151+ return run_result
101152
102- signature = _tool_call_signature (tool_call )
153+ signature = _scoped_tool_call_signature (tool_call , scope_id = scope_id )
103154 candidate_ids = _agent_tool_run_results_by_signature .get (signature )
104155 if not candidate_ids :
105156 return None
@@ -115,14 +166,17 @@ def consume_agent_tool_run_result(
115166
116167def peek_agent_tool_run_result (
117168 tool_call : ResponseFunctionToolCall ,
169+ * ,
170+ scope_id : str | None = None ,
118171) -> RunResult | RunResultStreaming | None :
119172 """Return the stored nested agent run result without removing it."""
120173 obj_id = id (tool_call )
121- run_result = _agent_tool_run_results_by_obj .get (obj_id )
122- if run_result is not None :
123- return run_result
174+ if _tool_call_obj_matches_scope (obj_id , scope_id = scope_id ):
175+ run_result = _agent_tool_run_results_by_obj .get (obj_id )
176+ if run_result is not None :
177+ return run_result
124178
125- signature = _tool_call_signature (tool_call )
179+ signature = _scoped_tool_call_signature (tool_call , scope_id = scope_id )
126180 candidate_ids = _agent_tool_run_results_by_signature .get (signature )
127181 if not candidate_ids :
128182 return None
@@ -133,15 +187,20 @@ def peek_agent_tool_run_result(
133187 return _agent_tool_run_results_by_obj .get (candidate_id )
134188
135189
136- def drop_agent_tool_run_result (tool_call : ResponseFunctionToolCall ) -> None :
190+ def drop_agent_tool_run_result (
191+ tool_call : ResponseFunctionToolCall ,
192+ * ,
193+ scope_id : str | None = None ,
194+ ) -> None :
137195 """Drop the stored nested agent run result, if present."""
138196 obj_id = id (tool_call )
139- run_result = _agent_tool_run_results_by_obj .pop (obj_id , None )
140- if run_result is not None :
141- _drop_agent_tool_run_result (obj_id )
142- return
197+ if _tool_call_obj_matches_scope (obj_id , scope_id = scope_id ):
198+ run_result = _agent_tool_run_results_by_obj .pop (obj_id , None )
199+ if run_result is not None :
200+ _drop_agent_tool_run_result (obj_id )
201+ return
143202
144- signature = _tool_call_signature (tool_call )
203+ signature = _scoped_tool_call_signature (tool_call , scope_id = scope_id )
145204 candidate_ids = _agent_tool_run_results_by_signature .get (signature )
146205 if not candidate_ids :
147206 return
0 commit comments