fleet-python 0.2.127__py3-none-any.whl → 0.2.128__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fleet/__init__.py CHANGED
@@ -26,8 +26,6 @@ from .exceptions import (
26
26
  )
27
27
  from .client import Fleet, SyncEnv, Session
28
28
  from ._async.client import AsyncFleet, AsyncEnv, AsyncSession
29
- from .browser import BrowserLease, host_from_url
30
- from ._async.browser import AsyncBrowserLease
31
29
  from .models import InstanceResponse, Environment, Run
32
30
  from .instance.models import Resource, ResetResponse
33
31
 
@@ -78,7 +76,7 @@ from . import env
78
76
  from . import global_client as _global_client
79
77
  from ._async import global_client as _async_global_client
80
78
 
81
- __version__ = "0.2.127"
79
+ __version__ = "0.2.128"
82
80
 
83
81
  __all__ = [
84
82
  # Core classes
@@ -86,10 +84,6 @@ __all__ = [
86
84
  "SyncEnv",
87
85
  "AsyncFleet",
88
86
  "AsyncEnv",
89
- # Browser lease (orchestrator-managed /v1/browser)
90
- "BrowserLease",
91
- "AsyncBrowserLease",
92
- "host_from_url",
93
87
  # Models
94
88
  "InstanceResponse",
95
89
  "SyncEnv",
fleet/_async/__init__.py CHANGED
@@ -44,7 +44,7 @@ from ..types import VerifierFunction
44
44
  from .. import env
45
45
  from . import global_client as _async_global_client
46
46
 
47
- __version__ = "0.2.127"
47
+ __version__ = "0.2.128"
48
48
 
49
49
  __all__ = [
50
50
  # Core classes
fleet/_async/base.py CHANGED
@@ -26,7 +26,7 @@ from .exceptions import (
26
26
  try:
27
27
  from .. import __version__
28
28
  except ImportError:
29
- __version__ = "0.2.127"
29
+ __version__ = "0.2.128"
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
fleet/_async/client.py CHANGED
@@ -171,15 +171,10 @@ from .instance.base import default_httpx_client
171
171
  from .instance.client import ValidatorType
172
172
  from .resources.base import Resource
173
173
  from .resources.sqlite import AsyncSQLiteResource
174
+ from .resources.browser import AsyncBrowserResource
174
175
  from .resources.filesystem import AsyncFilesystemResource
175
176
  from .resources.mcp import AsyncMCPResource
176
177
  from .resources.api import AsyncAPIResource
177
- from .browser import (
178
- AsyncBrowserLease,
179
- create_browser as _create_browser_lease,
180
- get_browser as _get_browser_lease,
181
- host_from_url,
182
- )
183
178
 
184
179
  logger = logging.getLogger(__name__)
185
180
 
@@ -391,51 +386,8 @@ class AsyncEnv(EnvironmentBase):
391
386
  def db(self, name: str = "current") -> AsyncSQLiteResource:
392
387
  return self.instance.db(name)
393
388
 
394
- async def browser(
395
- self,
396
- ttl_seconds: int = 300,
397
- *,
398
- lease_id: Optional[str] = None,
399
- allowed_hosts: Optional[List[str]] = None,
400
- include_root_host: bool = True,
401
- wait_until_running: bool = False,
402
- wait_timeout: float = 60.0,
403
- extra: Optional[Dict[str, Any]] = None,
404
- jwt_token: Optional[str] = None,
405
- team_id: Optional[str] = None,
406
- ) -> AsyncBrowserLease:
407
- """Spin up an orchestrator-managed Fleet Browser lease for this env.
408
-
409
- ``await env.browser()`` posts to ``/v1/browser`` and returns an
410
- :class:`fleet._async.browser.AsyncBrowserLease` with ``cdp_url`` /
411
- ``mcp_url`` / ``stream_url`` and a ``mcp_tools()`` accessor. By
412
- default the host derived from ``self.urls.root`` is prepended to
413
- ``allowed_hosts`` so the browser can reach the instance — pass
414
- ``include_root_host=False`` to opt out.
415
- """
416
- hosts: Optional[List[str]] = list(allowed_hosts) if allowed_hosts else None
417
- if include_root_host and self.urls and self.urls.root:
418
- root_host = host_from_url(self.urls.root)
419
- if root_host:
420
- hosts = hosts or []
421
- if root_host not in hosts:
422
- hosts.insert(0, root_host)
423
- return await _create_browser_lease(
424
- self._load_client,
425
- ttl_seconds=ttl_seconds,
426
- lease_id=lease_id,
427
- allowed_hosts=hosts,
428
- extra=extra,
429
- jwt_token=jwt_token,
430
- team_id=team_id,
431
- wait_until_running=wait_until_running,
432
- wait_timeout=wait_timeout,
433
- )
434
-
435
- @property
436
- def root_url(self) -> Optional[str]:
437
- """Convenience: ``self.urls.root`` if available."""
438
- return self.urls.root if self.urls else None
389
+ def browser(self, name: str = "cdp") -> AsyncBrowserResource:
390
+ return self.instance.browser(name)
439
391
 
440
392
  def fs(self) -> AsyncFilesystemResource:
441
393
  """Get a filesystem diff resource for inspecting file changes."""
@@ -530,6 +482,8 @@ class AsyncEnv(EnvironmentBase):
530
482
  timeout: Optional[int] = 30,
531
483
  needs_upload: bool = True,
532
484
  verifier_runtime_version: Optional[str] = None,
485
+ async_: bool = False,
486
+ poll_interval: float = 5.0,
533
487
  ) -> VerifiersExecuteResponse:
534
488
  return await _execute_verifier_remote(
535
489
  self._load_client,
@@ -543,6 +497,8 @@ class AsyncEnv(EnvironmentBase):
543
497
  timeout,
544
498
  needs_upload,
545
499
  verifier_runtime_version,
500
+ async_=async_,
501
+ poll_interval=poll_interval,
546
502
  )
547
503
 
548
504
  def __getstate__(self):
@@ -649,9 +605,7 @@ class AsyncFleet:
649
605
  )
650
606
 
651
607
  instance = AsyncEnv(client=self.client, **response.json())
652
- # Resources are loaded lazily on first `db()`/`browser()`/`resources()` access
653
- # via `_load_resources()`, so we don't preload here. Eagerly loading would
654
- # fail-fast with a 502 while the container is still warming up.
608
+ await instance.instance.load()
655
609
  return instance
656
610
 
657
611
  async def make_for_task(self, task: Task) -> AsyncEnv:
@@ -703,7 +657,7 @@ class AsyncFleet:
703
657
  else:
704
658
  response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
705
659
  instance = AsyncEnv(client=self.client, **response.json())
706
- # Resources load lazily on first `db()`/`browser()`/`resources()` access.
660
+ await instance.instance.load()
707
661
  return instance
708
662
 
709
663
  def _create_url_instance(self, base_url: str) -> AsyncEnv:
@@ -844,45 +798,6 @@ class AsyncFleet:
844
798
  self.client, bundle_data, args, kwargs, timeout
845
799
  )
846
800
 
847
- async def create_browser(
848
- self,
849
- ttl_seconds: int = 300,
850
- *,
851
- lease_id: Optional[str] = None,
852
- allowed_hosts: Optional[List[str]] = None,
853
- request_timestamp_ms: Optional[int] = None,
854
- extra: Optional[Dict[str, Any]] = None,
855
- jwt_token: Optional[str] = None,
856
- team_id: Optional[str] = None,
857
- wait_until_running: bool = False,
858
- wait_timeout: float = 60.0,
859
- ) -> AsyncBrowserLease:
860
- """Create a Fleet Browser lease (``POST /v1/browser``)."""
861
- return await _create_browser_lease(
862
- self.client,
863
- ttl_seconds=ttl_seconds,
864
- lease_id=lease_id,
865
- allowed_hosts=allowed_hosts,
866
- request_timestamp_ms=request_timestamp_ms,
867
- extra=extra,
868
- jwt_token=jwt_token,
869
- team_id=team_id,
870
- wait_until_running=wait_until_running,
871
- wait_timeout=wait_timeout,
872
- )
873
-
874
- async def get_browser(
875
- self,
876
- lease_id: str,
877
- *,
878
- jwt_token: Optional[str] = None,
879
- team_id: Optional[str] = None,
880
- ) -> AsyncBrowserLease:
881
- """Inspect an existing browser lease (``GET /v1/browser/{lease_id}``)."""
882
- return await _get_browser_lease(
883
- self.client, lease_id, jwt_token=jwt_token, team_id=team_id
884
- )
885
-
886
801
  async def delete(self, instance_id: str) -> InstanceResponse:
887
802
  return await _delete_instance(self.client, instance_id)
888
803
 
@@ -1814,6 +1729,8 @@ async def _execute_verifier_remote(
1814
1729
  timeout: Optional[int] = 30,
1815
1730
  needs_upload: bool = True,
1816
1731
  verifier_runtime_version: Optional[str] = None,
1732
+ async_: bool = False,
1733
+ poll_interval: float = 5.0,
1817
1734
  ) -> VerifiersExecuteResponse:
1818
1735
  # Pickle args and kwargs together
1819
1736
  # The first arg should be None as a placeholder for env
@@ -1841,6 +1758,11 @@ async def _execute_verifier_remote(
1841
1758
  if verifier_runtime_version:
1842
1759
  request_data["verifier_runtime_version"] = verifier_runtime_version
1843
1760
 
1761
+ # Async submit-and-poll path. When async_ is False the behavior below is
1762
+ # identical to the original synchronous request.
1763
+ if async_:
1764
+ request_data["async"] = True
1765
+
1844
1766
  # Debug logging
1845
1767
  # logger.debug(
1846
1768
  # f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
@@ -1862,4 +1784,21 @@ async def _execute_verifier_remote(
1862
1784
  response_json = response.json()
1863
1785
  # logger.debug(f"Verifier execute response: {response_json}")
1864
1786
 
1865
- return VerifiersExecuteResponse(**response_json)
1787
+ if not async_:
1788
+ return VerifiersExecuteResponse(**response_json)
1789
+
1790
+ # Async: the submit returns a job handle; poll until the job reaches a
1791
+ # terminal state (completed/failed). Branch on `status`, never `success`.
1792
+ job_id = response_json.get("job_id")
1793
+ if not job_id:
1794
+ # No job handle returned (e.g. server ran it inline) - surface as-is.
1795
+ return VerifiersExecuteResponse(**response_json)
1796
+
1797
+ while True:
1798
+ poll_response = await client.request(
1799
+ "GET", f"/v1/verifiers/jobs/{job_id}"
1800
+ )
1801
+ poll_json = poll_response.json()
1802
+ if poll_json.get("status") in ("completed", "failed"):
1803
+ return VerifiersExecuteResponse(**poll_json)
1804
+ await asyncio.sleep(poll_interval)
fleet/_async/models.py CHANGED
@@ -51,7 +51,6 @@ class Instance(BaseModel):
51
51
  team_id: str = Field(..., title="Team Id")
52
52
  region: str = Field(..., title="Region")
53
53
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
54
- multi_env_list: Optional[List[str]] = Field(None, title="Multi Env List")
55
54
 
56
55
 
57
56
  class InstanceRequest(BaseModel):
@@ -259,6 +258,12 @@ class VerifiersExecuteRequest(BaseModel):
259
258
  display_src: Optional[str] = Field(
260
259
  None, description="Display source code", title="Display Src"
261
260
  )
261
+ async_: Optional[bool] = Field(
262
+ None,
263
+ alias="async",
264
+ description="Submit asynchronously and return a job handle instead of waiting",
265
+ title="Async",
266
+ )
262
267
 
263
268
 
264
269
  class VerifiersExecuteResponse(BaseModel):
@@ -303,6 +308,16 @@ class VerifiersExecuteResponse(BaseModel):
303
308
  stdout: Optional[str] = Field(
304
309
  None, description="Captured stdout from execution", title="Stdout"
305
310
  )
311
+ status: Optional[str] = Field(
312
+ None,
313
+ description="Job status for async execution (pending/running/completed/failed)",
314
+ title="Status",
315
+ )
316
+ job_id: Optional[str] = Field(
317
+ None,
318
+ description="Job handle for async execution; poll GET /v1/verifiers/jobs/{job_id}",
319
+ title="Job Id",
320
+ )
306
321
 
307
322
 
308
323
  class DescribeResponse(BaseModel):
@@ -358,7 +373,6 @@ class InstanceResponse(BaseModel):
358
373
  data_version: Optional[str] = Field(None, title="Data Version")
359
374
  urls: Optional[InstanceURLs] = Field(None, title="Urls")
360
375
  health: Optional[bool] = Field(None, title="Health")
361
- multi_env_list: Optional[List[str]] = Field(None, title="Multi Env List")
362
376
 
363
377
 
364
378
  class AccountResponse(BaseModel):
fleet/_async/tasks.py CHANGED
@@ -81,11 +81,23 @@ class Task(BaseModel):
81
81
  # Allow arbitrary types for the verifier field
82
82
  arbitrary_types_allowed = True
83
83
 
84
- def verify(self, env, *args, **kwargs) -> float:
84
+ def verify(
85
+ self,
86
+ env,
87
+ *args,
88
+ async_: bool = False,
89
+ poll_interval: float = 5.0,
90
+ **kwargs,
91
+ ) -> float:
85
92
  """Verify the task using the verifier function (sync version).
86
93
 
87
94
  For sync environments, calls the sync verifier directly.
88
95
  For async verifiers, automatically runs them with asyncio.run().
96
+
97
+ When ``async_`` is True the verifier is submitted to run in the
98
+ background and polled (every ``poll_interval`` seconds) until it
99
+ completes, avoiding HTTP/edge idle timeouts for long-running
100
+ verifiers. When False the behavior is unchanged.
89
101
  """
90
102
  # If verifier doesn't exist but verifier_func does, rebuild it
91
103
  if not self.verifier and self.verifier_func:
@@ -95,7 +107,9 @@ class Task(BaseModel):
95
107
  import asyncio
96
108
  import inspect
97
109
 
98
- result = self.verifier.remote(env, *args, **kwargs)
110
+ result = self.verifier.remote(
111
+ env, *args, async_=async_, poll_interval=poll_interval, **kwargs
112
+ )
99
113
 
100
114
  # If the result is a coroutine, we need to run it
101
115
  if inspect.iscoroutine(result):
@@ -115,18 +129,27 @@ class Task(BaseModel):
115
129
  else:
116
130
  raise ValueError("No verifier function found for this task")
117
131
 
118
- async def verify_async(self, *args, **kwargs) -> float:
132
+ async def verify_async(
133
+ self, *args, async_: bool = False, poll_interval: float = 5.0, **kwargs
134
+ ) -> float:
119
135
  """Verify the task using the verifier function (async version).
120
136
 
121
137
  For async environments, awaits the async verifier.
122
138
  Works with both sync and async verifiers in async contexts.
139
+
140
+ When ``async_`` is True the verifier is submitted to run in the
141
+ background and polled (every ``poll_interval`` seconds) until it
142
+ completes, avoiding HTTP/edge idle timeouts for long-running
143
+ verifiers. When False the behavior is unchanged.
123
144
  """
124
145
  # If verifier doesn't exist but verifier_func does, rebuild it
125
146
  if not self.verifier and self.verifier_func:
126
147
  self._rebuild_verifier()
127
148
 
128
149
  if self.verifier:
129
- result = self.verifier.remote(*args, **kwargs)
150
+ result = self.verifier.remote(
151
+ *args, async_=async_, poll_interval=poll_interval, **kwargs
152
+ )
130
153
  # If it's a coroutine, await it
131
154
  import inspect
132
155
 
@@ -138,19 +161,26 @@ class Task(BaseModel):
138
161
  raise ValueError("No verifier function found for this task")
139
162
 
140
163
  async def verify_detailed_async(
141
- self, *args, **kwargs
164
+ self, *args, async_: bool = False, poll_interval: float = 5.0, **kwargs
142
165
  ) -> "VerifiersExecuteResponse":
143
166
  """Verify the task and return the full execute response model.
144
167
 
145
168
  For async environments, awaits the async verifier.
146
169
  Works with both sync and async verifiers in async contexts.
170
+
171
+ When ``async_`` is True the verifier is submitted to run in the
172
+ background and polled (every ``poll_interval`` seconds) until it
173
+ completes, avoiding HTTP/edge idle timeouts for long-running
174
+ verifiers. When False the behavior is unchanged.
147
175
  """
148
176
  # If verifier doesn't exist but verifier_func does, rebuild it
149
177
  if not self.verifier and self.verifier_func:
150
178
  self._rebuild_verifier()
151
179
 
152
180
  if self.verifier:
153
- result = self.verifier.remote_with_response(*args, **kwargs)
181
+ result = self.verifier.remote_with_response(
182
+ *args, async_=async_, poll_interval=poll_interval, **kwargs
183
+ )
154
184
  # If it's a coroutine, await it
155
185
  import inspect
156
186
 
@@ -161,11 +191,23 @@ class Task(BaseModel):
161
191
  else:
162
192
  raise ValueError("No verifier function found for this task")
163
193
 
164
- def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
194
+ def verify_detailed(
195
+ self,
196
+ env,
197
+ *args,
198
+ async_: bool = False,
199
+ poll_interval: float = 5.0,
200
+ **kwargs,
201
+ ) -> "VerifiersExecuteResponse":
165
202
  """Verify the task and return the full execute response model (sync version).
166
203
 
167
204
  For sync environments, calls the sync verifier directly.
168
205
  For async verifiers, automatically runs them with asyncio.run().
206
+
207
+ When ``async_`` is True the verifier is submitted to run in the
208
+ background and polled (every ``poll_interval`` seconds) until it
209
+ completes, avoiding HTTP/edge idle timeouts for long-running
210
+ verifiers. When False the behavior is unchanged.
169
211
  """
170
212
  # If verifier doesn't exist but verifier_func does, rebuild it
171
213
  if not self.verifier and self.verifier_func:
@@ -176,7 +218,9 @@ class Task(BaseModel):
176
218
  import inspect
177
219
 
178
220
  # Check if verifier has remote_with_response method (for decorated verifiers)
179
- result = self.verifier.remote_with_response(env, *args, **kwargs)
221
+ result = self.verifier.remote_with_response(
222
+ env, *args, async_=async_, poll_interval=poll_interval, **kwargs
223
+ )
180
224
 
181
225
  # If the result is a coroutine, we need to run it
182
226
  if inspect.iscoroutine(result):
@@ -154,9 +154,25 @@ class AsyncVerifierFunction:
154
154
  # Return error score 0
155
155
  return 0.0
156
156
 
157
- async def remote(self, env: AsyncEnv, *args, **kwargs) -> float:
158
- """Remote execution of the verifier function with SHA-based bundle caching."""
159
- response = await self.remote_with_response(env, *args, **kwargs)
157
+ async def remote(
158
+ self,
159
+ env: AsyncEnv,
160
+ *args,
161
+ async_: bool = False,
162
+ poll_interval: float = 5.0,
163
+ **kwargs,
164
+ ) -> float:
165
+ """Remote execution of the verifier function with SHA-based bundle caching.
166
+
167
+ When ``async_`` is True the verifier is submitted to run in the
168
+ background and the result is polled (every ``poll_interval`` seconds)
169
+ until it completes — this avoids HTTP/edge idle timeouts for
170
+ long-running verifiers. When False the behavior is unchanged (the
171
+ request blocks until the verifier finishes).
172
+ """
173
+ response = await self.remote_with_response(
174
+ env, *args, async_=async_, poll_interval=poll_interval, **kwargs
175
+ )
160
176
 
161
177
  # Handle response
162
178
  if response.stdout:
@@ -228,9 +244,20 @@ Remote traceback:
228
244
  )
229
245
 
230
246
  async def remote_with_response(
231
- self, env: "AsyncEnv", *args, **kwargs
247
+ self,
248
+ env: "AsyncEnv",
249
+ *args,
250
+ async_: bool = False,
251
+ poll_interval: float = 5.0,
252
+ **kwargs,
232
253
  ) -> "VerifiersExecuteResponse":
233
- """Remote execution of the verifier function that returns the full response model."""
254
+ """Remote execution of the verifier function that returns the full response model.
255
+
256
+ When ``async_`` is True the verifier is submitted asynchronously and
257
+ polled (every ``poll_interval`` seconds) until it reaches a terminal
258
+ state; the returned response is the completed/failed job result. When
259
+ False the request blocks until the verifier finishes (unchanged).
260
+ """
234
261
  args_array = list(args)
235
262
  args_array.append({"env": env.instance_id})
236
263
  args = tuple(args_array)
@@ -254,6 +281,8 @@ Remote traceback:
254
281
  kwargs=kwargs,
255
282
  needs_upload=True,
256
283
  verifier_runtime_version=self.verifier_runtime_version,
284
+ async_=async_,
285
+ poll_interval=poll_interval,
257
286
  )
258
287
 
259
288
  # logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
@@ -271,6 +300,8 @@ Remote traceback:
271
300
  kwargs=kwargs,
272
301
  needs_upload=False,
273
302
  verifier_runtime_version=self.verifier_runtime_version,
303
+ async_=async_,
304
+ poll_interval=poll_interval,
274
305
  )
275
306
 
276
307
  return response
@@ -292,6 +323,8 @@ Remote traceback:
292
323
  kwargs=kwargs,
293
324
  needs_upload=True,
294
325
  verifier_runtime_version=self.verifier_runtime_version,
326
+ async_=async_,
327
+ poll_interval=poll_interval,
295
328
  )
296
329
  return response
297
330
  else:
fleet/base.py CHANGED
@@ -27,7 +27,7 @@ from .exceptions import (
27
27
  try:
28
28
  from . import __version__
29
29
  except ImportError:
30
- __version__ = "0.2.127"
30
+ __version__ = "0.2.128"
31
31
 
32
32
  logger = logging.getLogger(__name__)
33
33