1818import asyncio
1919import inspect
2020import logging
21+ import sys
2122from typing import AsyncGenerator
2223from typing import Optional
2324from typing import TYPE_CHECKING
@@ -143,10 +144,11 @@ async def _resolve_toolset_auth(
143144 if not auth_config :
144145 continue
145146
147+ auth_config_copy = auth_config .model_copy (deep = True )
146148 try :
147- credential = await CredentialManager (auth_config ). get_auth_credential (
148- callback_context
149- )
149+ credential = await CredentialManager (
150+ auth_config_copy
151+ ). get_auth_credential ( callback_context )
150152 except ValueError as e :
151153 # Validation errors from CredentialManager should be logged but not
152154 # block the flow - the toolset may still work without auth
@@ -158,14 +160,16 @@ async def _resolve_toolset_auth(
158160 credential = None
159161
160162 if credential :
161- # Populate in-place for toolset to use in get_tools()
162- auth_config .exchanged_auth_credential = credential
163+ # Store in invocation context to avoid data leakage and race conditions
164+ invocation_context .credential_by_key [auth_config .credential_key ] = (
165+ credential
166+ )
163167 else :
164168 # Need auth - will interrupt
165169 toolset_id = (
166170 f'{ TOOLSET_AUTH_CREDENTIAL_ID_PREFIX } { type (tool_union ).__name__ } '
167171 )
168- pending_auth_requests [toolset_id ] = auth_config
172+ pending_auth_requests [toolset_id ] = auth_config_copy
169173
170174 if not pending_auth_requests :
171175 return
@@ -1165,7 +1169,9 @@ async def _call_llm_async(
11651169 ) -> AsyncGenerator [LlmResponse , None ]:
11661170
11671171 async def _call_llm_with_tracing () -> AsyncGenerator [LlmResponse , None ]:
1168- with tracer .start_as_current_span ('call_llm' ) as span :
1172+ cm = tracer .start_as_current_span ('call_llm' )
1173+ span = cm .__enter__ ()
1174+ try :
11691175 # Runs before_model_callback inside the call_llm span so
11701176 # plugins observe the same span as after/error callbacks.
11711177 if response := await self ._handle_before_model_callback (
@@ -1258,6 +1264,23 @@ async def _call_llm_with_tracing() -> AsyncGenerator[LlmResponse, None]:
12581264 llm_response = altered
12591265
12601266 yield llm_response
1267+ except BaseException :
1268+ try :
1269+ cm .__exit__ (* sys .exc_info ())
1270+ except ValueError :
1271+ logger .warning (
1272+ 'Failed to detach context during generator cleanup, likely due to'
1273+ ' cancellation.'
1274+ )
1275+ raise
1276+ else :
1277+ try :
1278+ cm .__exit__ (None , None , None )
1279+ except ValueError :
1280+ logger .warning (
1281+ 'Failed to detach context during generator cleanup, likely due to'
1282+ ' cancellation.'
1283+ )
12611284
12621285 async with Aclosing (_call_llm_with_tracing ()) as agen :
12631286 async for event in agen :
0 commit comments