@@ -40,13 +40,42 @@ def __init__(
4040 cells = cells ,
4141 workspace_path = workspace_path ,
4242 )
43- self ._endpoint = endpoint_url .rstrip ("/" )
43+ self ._cloudflare_endpoint = endpoint_url .rstrip ("/" ) # https://sp-xxx.4nton.ai
44+ self ._direct_endpoint : str | None = None # resolved to http://IP:port
4445 self ._api_key = api_key
4546
4647 # ------------------------------------------------------------------
4748 # HTTP helpers
4849 # ------------------------------------------------------------------
4950
51+ async def _resolve_endpoint (self ) -> str :
52+ """Resolve the Cloudflare endpoint to a direct IP endpoint.
53+
54+ Calls /resolve on the Cloudflare Worker which returns the instance's
55+ direct IP. Caches the result for subsequent calls.
56+ """
57+ if self ._direct_endpoint :
58+ return self ._direct_endpoint
59+
60+ import aiohttp
61+
62+ url = f"{ self ._cloudflare_endpoint } /resolve"
63+ async with aiohttp .ClientSession () as session :
64+ async with session .get (
65+ url , headers = self ._headers (), timeout = aiohttp .ClientTimeout (total = 15 )
66+ ) as resp :
67+ if resp .status >= 400 :
68+ text = await resp .text ()
69+ raise RuntimeError (f"Failed to resolve remote scratchpad ({ resp .status } ): { text } " )
70+ data = await resp .json ()
71+
72+ endpoint = data .get ("endpoint" , "" )
73+ if not endpoint :
74+ raise RuntimeError (f"No endpoint returned from /resolve: { data } " )
75+
76+ self ._direct_endpoint = endpoint .rstrip ("/" )
77+ return self ._direct_endpoint
78+
5079 def _headers (self ) -> dict [str , str ]:
5180 return {
5281 "Authorization" : f"Bearer { self ._api_key } " ,
@@ -58,7 +87,8 @@ async def _post(self, path: str, body: dict | None = None) -> dict:
5887 """POST to the remote service and return parsed JSON."""
5988 import aiohttp
6089
61- url = f"{ self ._endpoint } { path } "
90+ endpoint = await self ._resolve_endpoint ()
91+ url = f"{ endpoint } { path } "
6292 async with aiohttp .ClientSession () as session :
6393 async with session .post (
6494 url , json = body or {}, headers = self ._headers (), timeout = aiohttp .ClientTimeout (total = 300 )
@@ -72,7 +102,8 @@ async def _get(self, path: str, params: dict | None = None) -> dict:
72102 """GET from the remote service and return parsed JSON."""
73103 import aiohttp
74104
75- url = f"{ self ._endpoint } { path } "
105+ endpoint = await self ._resolve_endpoint ()
106+ url = f"{ endpoint } { path } "
76107 async with aiohttp .ClientSession () as session :
77108 async with session .get (
78109 url , params = params , headers = self ._headers (), timeout = aiohttp .ClientTimeout (total = 30 )
@@ -86,7 +117,8 @@ async def _sse(self, path: str, body: dict) -> AsyncIterator[dict]:
86117 """POST to an SSE endpoint and yield parsed events."""
87118 import aiohttp
88119
89- url = f"{ self ._endpoint } { path } "
120+ endpoint = await self ._resolve_endpoint ()
121+ url = f"{ endpoint } { path } "
90122 async with aiohttp .ClientSession () as session :
91123 async with session .post (
92124 url , json = body , headers = self ._headers (), timeout = aiohttp .ClientTimeout (total = 600 )
0 commit comments