fleet-python 0.2.128__tar.gz → 0.2.130__tar.gz
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_python-0.2.128/fleet_python.egg-info → fleet_python-0.2.130}/PKG-INFO +1 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/__init__.py +7 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/__init__.py +1 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/base.py +1 -1
- fleet_python-0.2.130/fleet/_async/browser.py +191 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/client.py +100 -5
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/models.py +5 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/tasks.py +36 -6
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/verifiers/verifier.py +11 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/base.py +1 -1
- fleet_python-0.2.130/fleet/browser.py +216 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/client.py +105 -5
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/models.py +5 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/tasks.py +36 -6
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/verifier.py +11 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130/fleet_python.egg-info}/PKG-INFO +1 -1
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet_python.egg-info/SOURCES.txt +3 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/pyproject.toml +1 -1
- fleet_python-0.2.130/tests/test_verifier_cost_team.py +198 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/LICENSE +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/README.md +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/diff_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/dsl_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/exampleResume.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_account.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_action_log.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_mcp_anthropic.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_mcp_openai.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_sync.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_task.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_tasks.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/example_verifier.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/export_tasks.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/export_tasks_filtered.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/fetch_tasks.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/gemini_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/import_tasks.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/iterate_verifiers.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/json_tasks_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/nova_act_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/openai_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/openai_simple_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/query_builder_example.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/quickstart.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/examples/test_cdp_logging.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/env/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/env/client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/exceptions.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/global_client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/instance/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/instance/base.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/instance/client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/judge.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/api.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/base.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/browser.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/filesystem.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/mcp.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/resources/sqlite.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/verifiers/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/_async/verifiers/bundler.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/Dockerfile +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/agent.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/mcp/main.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/mcp_server/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/mcp_server/main.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/mcp_server/tools.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/requirements.txt +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/gemini_cua/start.sh +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/orchestrator.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/types.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/agent/utils.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/cli.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/config.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/env/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/env/client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/eval/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/eval/uploader.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/exceptions.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/global_client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/instance/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/instance/base.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/instance/client.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/instance/models.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/judge.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/proxy/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/proxy/proxy.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/proxy/whitelist.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/api.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/base.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/browser.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/filesystem.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/mcp.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/resources/sqlite.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/types.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/utils/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/utils/http_logging.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/utils/logging.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/utils/playwright.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/bundler.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/code.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/db.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/decorator.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/parse.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet/verifiers/sql_differ.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet_python.egg-info/dependency_links.txt +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet_python.egg-info/entry_points.txt +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet_python.egg-info/requires.txt +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/fleet_python.egg-info/top_level.txt +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/scripts/fix_sync_imports.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/scripts/unasync.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/setup.cfg +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/__init__.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_app_method.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_expect_exactly.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_expect_only.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_instance_dispatch.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_sqlite_resource_dual_mode.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_sqlite_shared_memory_behavior.py +0 -0
- {fleet_python-0.2.128 → fleet_python-0.2.130}/tests/test_verifier_from_string.py +0 -0
|
@@ -26,6 +26,8 @@ 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
|
|
29
31
|
from .models import InstanceResponse, Environment, Run
|
|
30
32
|
from .instance.models import Resource, ResetResponse
|
|
31
33
|
|
|
@@ -76,7 +78,7 @@ from . import env
|
|
|
76
78
|
from . import global_client as _global_client
|
|
77
79
|
from ._async import global_client as _async_global_client
|
|
78
80
|
|
|
79
|
-
__version__ = "0.2.
|
|
81
|
+
__version__ = "0.2.130"
|
|
80
82
|
|
|
81
83
|
__all__ = [
|
|
82
84
|
# Core classes
|
|
@@ -84,6 +86,10 @@ __all__ = [
|
|
|
84
86
|
"SyncEnv",
|
|
85
87
|
"AsyncFleet",
|
|
86
88
|
"AsyncEnv",
|
|
89
|
+
# Browser lease (orchestrator-managed /v1/browser)
|
|
90
|
+
"BrowserLease",
|
|
91
|
+
"AsyncBrowserLease",
|
|
92
|
+
"host_from_url",
|
|
87
93
|
# Models
|
|
88
94
|
"InstanceResponse",
|
|
89
95
|
"SyncEnv",
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Copyright 2025 Fleet AI
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
"""Async mirror of ``fleet.browser``.
|
|
10
|
+
|
|
11
|
+
See :mod:`fleet.browser` for design notes. Kept as a separate file so the
|
|
12
|
+
async surface stays cleanly importable without dragging the sync wrapper
|
|
13
|
+
through any asyncio shims.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from ..browser import host_from_url # re-export, identical logic
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .base import AsyncWrapper
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_BROWSER_PATH = "/v1/browser"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _extra_headers(
|
|
31
|
+
jwt_token: Optional[str] = None, team_id: Optional[str] = None
|
|
32
|
+
) -> Optional[Dict[str, str]]:
|
|
33
|
+
headers: Dict[str, str] = {}
|
|
34
|
+
if jwt_token:
|
|
35
|
+
headers["X-JWT-Token"] = jwt_token
|
|
36
|
+
if team_id:
|
|
37
|
+
headers["X-Team-ID"] = team_id
|
|
38
|
+
return headers or None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AsyncBrowserLease:
|
|
42
|
+
"""Async counterpart of :class:`fleet.browser.BrowserLease`."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, client: "AsyncWrapper", data: Dict[str, Any]):
|
|
45
|
+
self._client = client
|
|
46
|
+
self._raw: Dict[str, Any] = dict(data)
|
|
47
|
+
self._jwt_token: Optional[str] = None
|
|
48
|
+
self._team_id: Optional[str] = None
|
|
49
|
+
self._apply(data)
|
|
50
|
+
|
|
51
|
+
def _apply(self, data: Dict[str, Any]) -> None:
|
|
52
|
+
self.id: str = data.get("id") or data["lease_id"]
|
|
53
|
+
self.lease_id: str = data["lease_id"]
|
|
54
|
+
self.browser_id: Optional[str] = data.get("browser_id")
|
|
55
|
+
self.host_domain: Optional[str] = data.get("host_domain")
|
|
56
|
+
self.mcp_url: Optional[str] = data.get("mcp_url")
|
|
57
|
+
self.cdp_url: Optional[str] = data.get("cdp_url")
|
|
58
|
+
self.stream_url: Optional[str] = data.get("stream_url")
|
|
59
|
+
self.status: Optional[str] = data.get("status")
|
|
60
|
+
self.stream_ready: Optional[bool] = data.get("stream_ready")
|
|
61
|
+
self.allowed_hosts: Optional[List[str]] = data.get("allowed_hosts")
|
|
62
|
+
self.created_timestamp_ms: Optional[int] = data.get("created_timestamp_ms")
|
|
63
|
+
self.expires_timestamp_ms: Optional[int] = data.get("expires_timestamp_ms")
|
|
64
|
+
self.age_duration_ms: Optional[int] = data.get("age_duration_ms")
|
|
65
|
+
self.cluster_name: Optional[str] = data.get("cluster_name")
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def raw(self) -> Dict[str, Any]:
|
|
69
|
+
return self._raw
|
|
70
|
+
|
|
71
|
+
async def refresh(self) -> "AsyncBrowserLease":
|
|
72
|
+
response = await self._client.request(
|
|
73
|
+
"GET",
|
|
74
|
+
f"{_BROWSER_PATH}/{self.lease_id}",
|
|
75
|
+
extra_headers=_extra_headers(self._jwt_token, self._team_id),
|
|
76
|
+
)
|
|
77
|
+
self._raw = response.json()
|
|
78
|
+
self._apply(self._raw)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
async def wait_until_running(
|
|
82
|
+
self,
|
|
83
|
+
timeout: float = 60.0,
|
|
84
|
+
poll_interval: float = 1.0,
|
|
85
|
+
require_stream_ready: bool = False,
|
|
86
|
+
) -> "AsyncBrowserLease":
|
|
87
|
+
loop = asyncio.get_event_loop()
|
|
88
|
+
deadline = loop.time() + timeout
|
|
89
|
+
while True:
|
|
90
|
+
await self.refresh()
|
|
91
|
+
running = self.status == "running"
|
|
92
|
+
if running and (not require_stream_ready or self.stream_ready):
|
|
93
|
+
return self
|
|
94
|
+
if loop.time() >= deadline:
|
|
95
|
+
raise TimeoutError(
|
|
96
|
+
f"Browser lease {self.lease_id} not running after "
|
|
97
|
+
f"{timeout}s (status={self.status}, stream_ready={self.stream_ready})"
|
|
98
|
+
)
|
|
99
|
+
await asyncio.sleep(poll_interval)
|
|
100
|
+
|
|
101
|
+
async def mcp_tools(self) -> Dict[str, Any]:
|
|
102
|
+
response = await self._client.request(
|
|
103
|
+
"GET",
|
|
104
|
+
f"{_BROWSER_PATH}/{self.lease_id}/mcp-tools",
|
|
105
|
+
extra_headers=_extra_headers(self._jwt_token, self._team_id),
|
|
106
|
+
)
|
|
107
|
+
return response.json()
|
|
108
|
+
|
|
109
|
+
async def delete(self) -> None:
|
|
110
|
+
from ..exceptions import FleetAPIError
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
await self._client.request(
|
|
114
|
+
"DELETE",
|
|
115
|
+
f"{_BROWSER_PATH}/{self.lease_id}",
|
|
116
|
+
extra_headers=_extra_headers(self._jwt_token, self._team_id),
|
|
117
|
+
)
|
|
118
|
+
except FleetAPIError as exc:
|
|
119
|
+
status = getattr(exc, "status_code", None)
|
|
120
|
+
if status not in (404, 410):
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
async def __aenter__(self) -> "AsyncBrowserLease":
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
127
|
+
await self.delete()
|
|
128
|
+
|
|
129
|
+
def __repr__(self) -> str:
|
|
130
|
+
return (
|
|
131
|
+
f"AsyncBrowserLease(lease_id={self.lease_id!r}, status={self.status!r}, "
|
|
132
|
+
f"cdp_url={self.cdp_url!r})"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def create_browser(
|
|
137
|
+
client: "AsyncWrapper",
|
|
138
|
+
*,
|
|
139
|
+
ttl_seconds: int = 300,
|
|
140
|
+
lease_id: Optional[str] = None,
|
|
141
|
+
allowed_hosts: Optional[List[str]] = None,
|
|
142
|
+
request_timestamp_ms: Optional[int] = None,
|
|
143
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
144
|
+
jwt_token: Optional[str] = None,
|
|
145
|
+
team_id: Optional[str] = None,
|
|
146
|
+
wait_until_running: bool = False,
|
|
147
|
+
wait_timeout: float = 60.0,
|
|
148
|
+
) -> AsyncBrowserLease:
|
|
149
|
+
payload: Dict[str, Any] = {"ttl_seconds": ttl_seconds}
|
|
150
|
+
if lease_id is not None:
|
|
151
|
+
payload["lease_id"] = lease_id
|
|
152
|
+
if allowed_hosts is not None:
|
|
153
|
+
payload["allowed_hosts"] = allowed_hosts
|
|
154
|
+
if request_timestamp_ms is not None:
|
|
155
|
+
payload["request_timestamp_ms"] = request_timestamp_ms
|
|
156
|
+
if extra:
|
|
157
|
+
payload.update(extra)
|
|
158
|
+
|
|
159
|
+
response = await client.request(
|
|
160
|
+
"POST",
|
|
161
|
+
_BROWSER_PATH,
|
|
162
|
+
json=payload,
|
|
163
|
+
extra_headers=_extra_headers(jwt_token, team_id),
|
|
164
|
+
)
|
|
165
|
+
lease = AsyncBrowserLease(client, response.json())
|
|
166
|
+
lease._jwt_token = jwt_token
|
|
167
|
+
lease._team_id = team_id
|
|
168
|
+
if wait_until_running:
|
|
169
|
+
await lease.wait_until_running(timeout=wait_timeout)
|
|
170
|
+
return lease
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def get_browser(
|
|
174
|
+
client: "AsyncWrapper",
|
|
175
|
+
lease_id: str,
|
|
176
|
+
*,
|
|
177
|
+
jwt_token: Optional[str] = None,
|
|
178
|
+
team_id: Optional[str] = None,
|
|
179
|
+
) -> AsyncBrowserLease:
|
|
180
|
+
response = await client.request(
|
|
181
|
+
"GET",
|
|
182
|
+
f"{_BROWSER_PATH}/{lease_id}",
|
|
183
|
+
extra_headers=_extra_headers(jwt_token, team_id),
|
|
184
|
+
)
|
|
185
|
+
lease = AsyncBrowserLease(client, response.json())
|
|
186
|
+
lease._jwt_token = jwt_token
|
|
187
|
+
lease._team_id = team_id
|
|
188
|
+
return lease
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
__all__ = ["AsyncBrowserLease", "create_browser", "get_browser", "host_from_url"]
|
|
@@ -171,10 +171,15 @@ 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
|
|
175
174
|
from .resources.filesystem import AsyncFilesystemResource
|
|
176
175
|
from .resources.mcp import AsyncMCPResource
|
|
177
176
|
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
|
+
)
|
|
178
183
|
|
|
179
184
|
logger = logging.getLogger(__name__)
|
|
180
185
|
|
|
@@ -386,8 +391,51 @@ class AsyncEnv(EnvironmentBase):
|
|
|
386
391
|
def db(self, name: str = "current") -> AsyncSQLiteResource:
|
|
387
392
|
return self.instance.db(name)
|
|
388
393
|
|
|
389
|
-
def browser(
|
|
390
|
-
|
|
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
|
|
391
439
|
|
|
392
440
|
def fs(self) -> AsyncFilesystemResource:
|
|
393
441
|
"""Get a filesystem diff resource for inspecting file changes."""
|
|
@@ -484,6 +532,7 @@ class AsyncEnv(EnvironmentBase):
|
|
|
484
532
|
verifier_runtime_version: Optional[str] = None,
|
|
485
533
|
async_: bool = False,
|
|
486
534
|
poll_interval: float = 5.0,
|
|
535
|
+
cost_team_id: Optional[str] = None,
|
|
487
536
|
) -> VerifiersExecuteResponse:
|
|
488
537
|
return await _execute_verifier_remote(
|
|
489
538
|
self._load_client,
|
|
@@ -499,6 +548,7 @@ class AsyncEnv(EnvironmentBase):
|
|
|
499
548
|
verifier_runtime_version,
|
|
500
549
|
async_=async_,
|
|
501
550
|
poll_interval=poll_interval,
|
|
551
|
+
cost_team_id=cost_team_id,
|
|
502
552
|
)
|
|
503
553
|
|
|
504
554
|
def __getstate__(self):
|
|
@@ -605,7 +655,9 @@ class AsyncFleet:
|
|
|
605
655
|
)
|
|
606
656
|
|
|
607
657
|
instance = AsyncEnv(client=self.client, **response.json())
|
|
608
|
-
|
|
658
|
+
# Resources are loaded lazily on first `db()`/`browser()`/`resources()` access
|
|
659
|
+
# via `_load_resources()`, so we don't preload here. Eagerly loading would
|
|
660
|
+
# fail-fast with a 502 while the container is still warming up.
|
|
609
661
|
return instance
|
|
610
662
|
|
|
611
663
|
async def make_for_task(self, task: Task) -> AsyncEnv:
|
|
@@ -657,7 +709,7 @@ class AsyncFleet:
|
|
|
657
709
|
else:
|
|
658
710
|
response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
|
|
659
711
|
instance = AsyncEnv(client=self.client, **response.json())
|
|
660
|
-
|
|
712
|
+
# Resources load lazily on first `db()`/`browser()`/`resources()` access.
|
|
661
713
|
return instance
|
|
662
714
|
|
|
663
715
|
def _create_url_instance(self, base_url: str) -> AsyncEnv:
|
|
@@ -798,6 +850,45 @@ class AsyncFleet:
|
|
|
798
850
|
self.client, bundle_data, args, kwargs, timeout
|
|
799
851
|
)
|
|
800
852
|
|
|
853
|
+
async def create_browser(
|
|
854
|
+
self,
|
|
855
|
+
ttl_seconds: int = 300,
|
|
856
|
+
*,
|
|
857
|
+
lease_id: Optional[str] = None,
|
|
858
|
+
allowed_hosts: Optional[List[str]] = None,
|
|
859
|
+
request_timestamp_ms: Optional[int] = None,
|
|
860
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
861
|
+
jwt_token: Optional[str] = None,
|
|
862
|
+
team_id: Optional[str] = None,
|
|
863
|
+
wait_until_running: bool = False,
|
|
864
|
+
wait_timeout: float = 60.0,
|
|
865
|
+
) -> AsyncBrowserLease:
|
|
866
|
+
"""Create a Fleet Browser lease (``POST /v1/browser``)."""
|
|
867
|
+
return await _create_browser_lease(
|
|
868
|
+
self.client,
|
|
869
|
+
ttl_seconds=ttl_seconds,
|
|
870
|
+
lease_id=lease_id,
|
|
871
|
+
allowed_hosts=allowed_hosts,
|
|
872
|
+
request_timestamp_ms=request_timestamp_ms,
|
|
873
|
+
extra=extra,
|
|
874
|
+
jwt_token=jwt_token,
|
|
875
|
+
team_id=team_id,
|
|
876
|
+
wait_until_running=wait_until_running,
|
|
877
|
+
wait_timeout=wait_timeout,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
async def get_browser(
|
|
881
|
+
self,
|
|
882
|
+
lease_id: str,
|
|
883
|
+
*,
|
|
884
|
+
jwt_token: Optional[str] = None,
|
|
885
|
+
team_id: Optional[str] = None,
|
|
886
|
+
) -> AsyncBrowserLease:
|
|
887
|
+
"""Inspect an existing browser lease (``GET /v1/browser/{lease_id}``)."""
|
|
888
|
+
return await _get_browser_lease(
|
|
889
|
+
self.client, lease_id, jwt_token=jwt_token, team_id=team_id
|
|
890
|
+
)
|
|
891
|
+
|
|
801
892
|
async def delete(self, instance_id: str) -> InstanceResponse:
|
|
802
893
|
return await _delete_instance(self.client, instance_id)
|
|
803
894
|
|
|
@@ -1731,6 +1822,7 @@ async def _execute_verifier_remote(
|
|
|
1731
1822
|
verifier_runtime_version: Optional[str] = None,
|
|
1732
1823
|
async_: bool = False,
|
|
1733
1824
|
poll_interval: float = 5.0,
|
|
1825
|
+
cost_team_id: Optional[str] = None,
|
|
1734
1826
|
) -> VerifiersExecuteResponse:
|
|
1735
1827
|
# Pickle args and kwargs together
|
|
1736
1828
|
# The first arg should be None as a placeholder for env
|
|
@@ -1758,6 +1850,9 @@ async def _execute_verifier_remote(
|
|
|
1758
1850
|
if verifier_runtime_version:
|
|
1759
1851
|
request_data["verifier_runtime_version"] = verifier_runtime_version
|
|
1760
1852
|
|
|
1853
|
+
if cost_team_id is not None:
|
|
1854
|
+
request_data["cost_team_id"] = cost_team_id
|
|
1855
|
+
|
|
1761
1856
|
# Async submit-and-poll path. When async_ is False the behavior below is
|
|
1762
1857
|
# identical to the original synchronous request.
|
|
1763
1858
|
if async_:
|
|
@@ -51,6 +51,7 @@ 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")
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class InstanceRequest(BaseModel):
|
|
@@ -221,6 +222,9 @@ class VerifiersCheckResponse(BaseModel):
|
|
|
221
222
|
|
|
222
223
|
|
|
223
224
|
class VerifiersExecuteRequest(BaseModel):
|
|
225
|
+
cost_team_id: Optional[str] = Field(
|
|
226
|
+
None, description="Team to attribute verifier costs to", title="Cost Team Id"
|
|
227
|
+
)
|
|
224
228
|
key: Optional[str] = Field(
|
|
225
229
|
None, description="Key of the verifier artifact", title="Key"
|
|
226
230
|
)
|
|
@@ -373,6 +377,7 @@ class InstanceResponse(BaseModel):
|
|
|
373
377
|
data_version: Optional[str] = Field(None, title="Data Version")
|
|
374
378
|
urls: Optional[InstanceURLs] = Field(None, title="Urls")
|
|
375
379
|
health: Optional[bool] = Field(None, title="Health")
|
|
380
|
+
multi_env_list: Optional[List[str]] = Field(None, title="Multi Env List")
|
|
376
381
|
|
|
377
382
|
|
|
378
383
|
class AccountResponse(BaseModel):
|
|
@@ -85,6 +85,7 @@ class Task(BaseModel):
|
|
|
85
85
|
self,
|
|
86
86
|
env,
|
|
87
87
|
*args,
|
|
88
|
+
cost_team_id: Optional[str] = None,
|
|
88
89
|
async_: bool = False,
|
|
89
90
|
poll_interval: float = 5.0,
|
|
90
91
|
**kwargs,
|
|
@@ -108,7 +109,12 @@ class Task(BaseModel):
|
|
|
108
109
|
import inspect
|
|
109
110
|
|
|
110
111
|
result = self.verifier.remote(
|
|
111
|
-
env,
|
|
112
|
+
env,
|
|
113
|
+
*args,
|
|
114
|
+
cost_team_id=cost_team_id,
|
|
115
|
+
async_=async_,
|
|
116
|
+
poll_interval=poll_interval,
|
|
117
|
+
**kwargs,
|
|
112
118
|
)
|
|
113
119
|
|
|
114
120
|
# If the result is a coroutine, we need to run it
|
|
@@ -130,7 +136,12 @@ class Task(BaseModel):
|
|
|
130
136
|
raise ValueError("No verifier function found for this task")
|
|
131
137
|
|
|
132
138
|
async def verify_async(
|
|
133
|
-
self,
|
|
139
|
+
self,
|
|
140
|
+
*args,
|
|
141
|
+
cost_team_id: Optional[str] = None,
|
|
142
|
+
async_: bool = False,
|
|
143
|
+
poll_interval: float = 5.0,
|
|
144
|
+
**kwargs,
|
|
134
145
|
) -> float:
|
|
135
146
|
"""Verify the task using the verifier function (async version).
|
|
136
147
|
|
|
@@ -148,7 +159,11 @@ class Task(BaseModel):
|
|
|
148
159
|
|
|
149
160
|
if self.verifier:
|
|
150
161
|
result = self.verifier.remote(
|
|
151
|
-
*args,
|
|
162
|
+
*args,
|
|
163
|
+
cost_team_id=cost_team_id,
|
|
164
|
+
async_=async_,
|
|
165
|
+
poll_interval=poll_interval,
|
|
166
|
+
**kwargs,
|
|
152
167
|
)
|
|
153
168
|
# If it's a coroutine, await it
|
|
154
169
|
import inspect
|
|
@@ -161,7 +176,12 @@ class Task(BaseModel):
|
|
|
161
176
|
raise ValueError("No verifier function found for this task")
|
|
162
177
|
|
|
163
178
|
async def verify_detailed_async(
|
|
164
|
-
self,
|
|
179
|
+
self,
|
|
180
|
+
*args,
|
|
181
|
+
cost_team_id: Optional[str] = None,
|
|
182
|
+
async_: bool = False,
|
|
183
|
+
poll_interval: float = 5.0,
|
|
184
|
+
**kwargs,
|
|
165
185
|
) -> "VerifiersExecuteResponse":
|
|
166
186
|
"""Verify the task and return the full execute response model.
|
|
167
187
|
|
|
@@ -179,7 +199,11 @@ class Task(BaseModel):
|
|
|
179
199
|
|
|
180
200
|
if self.verifier:
|
|
181
201
|
result = self.verifier.remote_with_response(
|
|
182
|
-
*args,
|
|
202
|
+
*args,
|
|
203
|
+
cost_team_id=cost_team_id,
|
|
204
|
+
async_=async_,
|
|
205
|
+
poll_interval=poll_interval,
|
|
206
|
+
**kwargs,
|
|
183
207
|
)
|
|
184
208
|
# If it's a coroutine, await it
|
|
185
209
|
import inspect
|
|
@@ -195,6 +219,7 @@ class Task(BaseModel):
|
|
|
195
219
|
self,
|
|
196
220
|
env,
|
|
197
221
|
*args,
|
|
222
|
+
cost_team_id: Optional[str] = None,
|
|
198
223
|
async_: bool = False,
|
|
199
224
|
poll_interval: float = 5.0,
|
|
200
225
|
**kwargs,
|
|
@@ -219,7 +244,12 @@ class Task(BaseModel):
|
|
|
219
244
|
|
|
220
245
|
# Check if verifier has remote_with_response method (for decorated verifiers)
|
|
221
246
|
result = self.verifier.remote_with_response(
|
|
222
|
-
env,
|
|
247
|
+
env,
|
|
248
|
+
*args,
|
|
249
|
+
cost_team_id=cost_team_id,
|
|
250
|
+
async_=async_,
|
|
251
|
+
poll_interval=poll_interval,
|
|
252
|
+
**kwargs,
|
|
223
253
|
)
|
|
224
254
|
|
|
225
255
|
# If the result is a coroutine, we need to run it
|
|
@@ -158,6 +158,7 @@ class AsyncVerifierFunction:
|
|
|
158
158
|
self,
|
|
159
159
|
env: AsyncEnv,
|
|
160
160
|
*args,
|
|
161
|
+
cost_team_id: Optional[str] = None,
|
|
161
162
|
async_: bool = False,
|
|
162
163
|
poll_interval: float = 5.0,
|
|
163
164
|
**kwargs,
|
|
@@ -171,7 +172,12 @@ class AsyncVerifierFunction:
|
|
|
171
172
|
request blocks until the verifier finishes).
|
|
172
173
|
"""
|
|
173
174
|
response = await self.remote_with_response(
|
|
174
|
-
env,
|
|
175
|
+
env,
|
|
176
|
+
*args,
|
|
177
|
+
cost_team_id=cost_team_id,
|
|
178
|
+
async_=async_,
|
|
179
|
+
poll_interval=poll_interval,
|
|
180
|
+
**kwargs,
|
|
175
181
|
)
|
|
176
182
|
|
|
177
183
|
# Handle response
|
|
@@ -247,6 +253,7 @@ Remote traceback:
|
|
|
247
253
|
self,
|
|
248
254
|
env: "AsyncEnv",
|
|
249
255
|
*args,
|
|
256
|
+
cost_team_id: Optional[str] = None,
|
|
250
257
|
async_: bool = False,
|
|
251
258
|
poll_interval: float = 5.0,
|
|
252
259
|
**kwargs,
|
|
@@ -281,6 +288,7 @@ Remote traceback:
|
|
|
281
288
|
kwargs=kwargs,
|
|
282
289
|
needs_upload=True,
|
|
283
290
|
verifier_runtime_version=self.verifier_runtime_version,
|
|
291
|
+
cost_team_id=cost_team_id,
|
|
284
292
|
async_=async_,
|
|
285
293
|
poll_interval=poll_interval,
|
|
286
294
|
)
|
|
@@ -300,6 +308,7 @@ Remote traceback:
|
|
|
300
308
|
kwargs=kwargs,
|
|
301
309
|
needs_upload=False,
|
|
302
310
|
verifier_runtime_version=self.verifier_runtime_version,
|
|
311
|
+
cost_team_id=cost_team_id,
|
|
303
312
|
async_=async_,
|
|
304
313
|
poll_interval=poll_interval,
|
|
305
314
|
)
|
|
@@ -323,6 +332,7 @@ Remote traceback:
|
|
|
323
332
|
kwargs=kwargs,
|
|
324
333
|
needs_upload=True,
|
|
325
334
|
verifier_runtime_version=self.verifier_runtime_version,
|
|
335
|
+
cost_team_id=cost_team_id,
|
|
326
336
|
async_=async_,
|
|
327
337
|
poll_interval=poll_interval,
|
|
328
338
|
)
|