11"""LLM chat and completion via TEE-verified execution with x402 payments."""
22
3+ import asyncio
34import json
45import logging
56import ssl
7+ import time
68from dataclasses import dataclass
79from typing import AsyncGenerator , Awaitable , Callable , Dict , List , Optional , TypeVar , Union
810import httpx
3234_CHAT_ENDPOINT = "/v1/chat/completions"
3335_COMPLETION_ENDPOINT = "/v1/completions"
3436_REQUEST_TIMEOUT = 60
37+ _TEE_REFRESH_INTERVAL = 300 # Re-resolve TEE from registry every 5 minutes
3538
3639
3740@dataclass
@@ -107,6 +110,8 @@ def __init__(
107110 register_upto_evm_client (self ._x402_client , signer , networks = [BASE_TESTNET_NETWORK ])
108111
109112 self ._connect_tee ()
113+ self ._tee_refreshed_at : float = time .monotonic ()
114+ self ._refresh_lock = asyncio .Lock ()
110115
111116 # ── TEE resolution and connection ───────────────────────────────────────────
112117
@@ -127,12 +132,27 @@ def _connect_tee(self) -> None:
127132
128133 async def _refresh_tee (self ) -> None :
129134 """Re-resolve TEE from the registry and rebuild the HTTP client."""
130- old_http_client = self ._http_client
131- self ._connect_tee ()
132- try :
133- await old_http_client .aclose ()
134- except Exception :
135- logger .debug ("Failed to close previous HTTP client during TEE refresh." , exc_info = True )
135+ async with self ._refresh_lock :
136+ old_http_client = self ._http_client
137+ self ._connect_tee ()
138+ self ._tee_refreshed_at = time .monotonic ()
139+ try :
140+ await old_http_client .aclose ()
141+ except Exception :
142+ logger .debug ("Failed to close previous HTTP client during TEE refresh." , exc_info = True )
143+
144+ async def _maybe_refresh_tee (self ) -> None :
145+ """Re-resolve TEE if the current one is older than ``_TEE_REFRESH_INTERVAL``.
146+
147+ Skips the refresh for explicit ``llm_server_url`` overrides since they
148+ bypass the registry entirely.
149+ """
150+ if self ._llm_server_url is not None :
151+ return
152+ if time .monotonic () - self ._tee_refreshed_at < _TEE_REFRESH_INTERVAL :
153+ return
154+ logger .debug ("TEE endpoint stale (>%ds); refreshing from registry." , _TEE_REFRESH_INTERVAL )
155+ await self ._refresh_tee ()
136156
137157
138158 @staticmethod
@@ -212,6 +232,7 @@ async def _call_with_tee_retry(
212232 Only retries when the request never reached the server (no HTTP response).
213233 Server-side errors (4xx/5xx) are not retried.
214234 """
235+ await self ._maybe_refresh_tee ()
215236 try :
216237 return await call ()
217238 except httpx .HTTPStatusError :
@@ -448,6 +469,7 @@ async def _chat_tools_as_stream(self, params: _ChatParams, messages: List[Dict])
448469
449470 async def _chat_stream (self , params : _ChatParams , messages : List [Dict ]) -> AsyncGenerator [StreamChunk , None ]:
450471 """Async SSE streaming implementation."""
472+ await self ._maybe_refresh_tee ()
451473 headers = self ._headers (params .x402_settlement_mode )
452474 payload = self ._chat_payload (params , messages , stream = True )
453475
0 commit comments