fleet-python 0.2.3__py3-none-any.whl → 0.2.5__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.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- examples/dsl_example.py +2 -1
- examples/example.py +2 -2
- examples/json_tasks_example.py +1 -1
- examples/nova_act_example.py +1 -1
- examples/openai_example.py +17 -24
- examples/openai_simple_example.py +11 -12
- fleet/__init__.py +18 -3
- fleet/_async/base.py +51 -0
- fleet/_async/client.py +133 -0
- fleet/_async/env/__init__.py +0 -0
- fleet/_async/env/client.py +15 -0
- fleet/_async/exceptions.py +73 -0
- fleet/_async/instance/__init__.py +24 -0
- fleet/_async/instance/base.py +37 -0
- fleet/_async/instance/client.py +278 -0
- fleet/_async/instance/models.py +141 -0
- fleet/_async/models.py +109 -0
- fleet/_async/playwright.py +291 -0
- fleet/_async/resources/__init__.py +0 -0
- fleet/_async/resources/base.py +26 -0
- fleet/_async/resources/browser.py +41 -0
- fleet/_async/resources/sqlite.py +41 -0
- fleet/base.py +1 -24
- fleet/client.py +31 -99
- fleet/env/__init__.py +13 -1
- fleet/env/client.py +7 -7
- fleet/instance/__init__.py +3 -2
- fleet/instance/base.py +1 -24
- fleet/instance/client.py +40 -57
- fleet/playwright.py +45 -47
- fleet/resources/__init__.py +0 -0
- fleet/resources/browser.py +14 -14
- fleet/resources/sqlite.py +11 -11
- fleet/verifiers/__init__.py +5 -10
- fleet/verifiers/code.py +1 -132
- {fleet_python-0.2.3.dist-info → fleet_python-0.2.5.dist-info}/METADATA +12 -11
- fleet_python-0.2.5.dist-info/RECORD +48 -0
- {fleet_python-0.2.3.dist-info → fleet_python-0.2.5.dist-info}/top_level.txt +1 -0
- scripts/unasync.py +28 -0
- fleet_python-0.2.3.dist-info/RECORD +0 -31
- {fleet_python-0.2.3.dist-info → fleet_python-0.2.5.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.3.dist-info → fleet_python-0.2.5.dist-info}/licenses/LICENSE +0 -0
fleet/instance/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Fleet SDK Environment Module."""
|
|
2
2
|
|
|
3
|
-
from .client import InstanceClient,
|
|
3
|
+
from .client import InstanceClient, ValidatorType
|
|
4
|
+
from .._async.instance.client import AsyncInstanceClient
|
|
4
5
|
from .models import (
|
|
5
6
|
ResetRequest,
|
|
6
7
|
ResetResponse,
|
|
@@ -22,4 +23,4 @@ __all__ = [
|
|
|
22
23
|
"ChromeStartResponse",
|
|
23
24
|
"ChromeStatusResponse",
|
|
24
25
|
"ExecuteFunctionResponse"
|
|
25
|
-
]
|
|
26
|
+
]
|
fleet/instance/base.py
CHANGED
|
@@ -34,27 +34,4 @@ class SyncWrapper(BaseWrapper):
|
|
|
34
34
|
params=params,
|
|
35
35
|
json=json,
|
|
36
36
|
**kwargs,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class AsyncWrapper(BaseWrapper):
|
|
41
|
-
def __init__(self, *, httpx_client: httpx.AsyncClient, **kwargs):
|
|
42
|
-
super().__init__(**kwargs)
|
|
43
|
-
self.httpx_client = httpx_client
|
|
44
|
-
|
|
45
|
-
async def request(
|
|
46
|
-
self,
|
|
47
|
-
method: str,
|
|
48
|
-
path: str,
|
|
49
|
-
params: Optional[Dict[str, Any]] = None,
|
|
50
|
-
json: Optional[Any] = None,
|
|
51
|
-
**kwargs,
|
|
52
|
-
) -> httpx.Response:
|
|
53
|
-
return await self.httpx_client.request(
|
|
54
|
-
method,
|
|
55
|
-
f"{self.url}{path}",
|
|
56
|
-
headers=self.get_headers(),
|
|
57
|
-
params=params,
|
|
58
|
-
json=json,
|
|
59
|
-
**kwargs,
|
|
60
|
-
)
|
|
37
|
+
)
|
fleet/instance/client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Fleet SDK
|
|
1
|
+
"""Fleet SDK Async Instance Client."""
|
|
2
2
|
|
|
3
3
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
4
4
|
import asyncio
|
|
@@ -8,15 +8,15 @@ import time
|
|
|
8
8
|
import logging
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
10
|
|
|
11
|
-
from ..resources.sqlite import
|
|
12
|
-
from ..resources.browser import
|
|
11
|
+
from ..resources.sqlite import SQLiteResource
|
|
12
|
+
from ..resources.browser import BrowserResource
|
|
13
13
|
from ..resources.base import Resource
|
|
14
14
|
|
|
15
15
|
from ..verifiers import DatabaseSnapshot
|
|
16
16
|
|
|
17
17
|
from ..exceptions import FleetEnvironmentError, FleetAPIError
|
|
18
18
|
|
|
19
|
-
from .base import SyncWrapper
|
|
19
|
+
from .base import SyncWrapper
|
|
20
20
|
from .models import (
|
|
21
21
|
ResetRequest,
|
|
22
22
|
ResetResponse,
|
|
@@ -32,8 +32,8 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
RESOURCE_TYPES = {
|
|
35
|
-
ResourceType.db:
|
|
36
|
-
ResourceType.cdp:
|
|
35
|
+
ResourceType.db: SQLiteResource,
|
|
36
|
+
ResourceType.cdp: BrowserResource,
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
ValidatorType = Callable[
|
|
@@ -50,38 +50,21 @@ class InstanceClient:
|
|
|
50
50
|
):
|
|
51
51
|
self.base_url = url
|
|
52
52
|
self.client = SyncWrapper(
|
|
53
|
-
url=self.base_url, httpx_client=httpx_client or httpx.Client()
|
|
54
|
-
)
|
|
55
|
-
raise NotImplementedError("SyncManager is not implemented")
|
|
56
|
-
|
|
57
|
-
def reset(self) -> ResetResponse:
|
|
58
|
-
response = self.client.request("POST", "/reset")
|
|
59
|
-
return ResetResponse(**response.json())
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class AsyncInstanceClient:
|
|
63
|
-
def __init__(
|
|
64
|
-
self,
|
|
65
|
-
url: str,
|
|
66
|
-
httpx_client: Optional[httpx.AsyncClient] = None,
|
|
67
|
-
):
|
|
68
|
-
self.base_url = url
|
|
69
|
-
self.client = AsyncWrapper(
|
|
70
53
|
url=self.base_url,
|
|
71
|
-
httpx_client=httpx_client or httpx.
|
|
54
|
+
httpx_client=httpx_client or httpx.Client(timeout=60.0),
|
|
72
55
|
)
|
|
73
56
|
self._resources: Optional[List[ResourceModel]] = None
|
|
74
57
|
self._resources_state: Dict[str, Dict[str, Resource]] = {
|
|
75
58
|
resource_type.value: {} for resource_type in ResourceType
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
def load(self) -> None:
|
|
62
|
+
self._load_resources()
|
|
80
63
|
|
|
81
|
-
|
|
64
|
+
def reset(
|
|
82
65
|
self, reset_request: Optional[ResetRequest] = None
|
|
83
66
|
) -> ResetResponse:
|
|
84
|
-
response =
|
|
67
|
+
response = self.client.request(
|
|
85
68
|
"POST", "/reset", json=reset_request.model_dump() if reset_request else None
|
|
86
69
|
)
|
|
87
70
|
return ResetResponse(**response.json())
|
|
@@ -90,7 +73,7 @@ class AsyncInstanceClient:
|
|
|
90
73
|
url = urlparse(uri)
|
|
91
74
|
return self._resources_state[url.scheme][url.netloc]
|
|
92
75
|
|
|
93
|
-
def db(self, name: str) ->
|
|
76
|
+
def db(self, name: str) -> SQLiteResource:
|
|
94
77
|
"""
|
|
95
78
|
Returns an AsyncSQLiteResource object for the given SQLite database name.
|
|
96
79
|
|
|
@@ -100,32 +83,32 @@ class AsyncInstanceClient:
|
|
|
100
83
|
Returns:
|
|
101
84
|
An AsyncSQLiteResource object for the given SQLite database name
|
|
102
85
|
"""
|
|
103
|
-
return
|
|
86
|
+
return SQLiteResource(
|
|
104
87
|
self._resources_state[ResourceType.db.value][name], self.client
|
|
105
88
|
)
|
|
106
89
|
|
|
107
|
-
def browser(self, name: str) ->
|
|
108
|
-
return
|
|
90
|
+
def browser(self, name: str) -> BrowserResource:
|
|
91
|
+
return BrowserResource(
|
|
109
92
|
self._resources_state[ResourceType.cdp.value][name], self.client
|
|
110
93
|
)
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
def resources(self) -> List[Resource]:
|
|
96
|
+
self._load_resources()
|
|
114
97
|
return [
|
|
115
98
|
resource
|
|
116
99
|
for resources_by_name in self._resources_state.values()
|
|
117
100
|
for resource in resources_by_name.values()
|
|
118
101
|
]
|
|
119
102
|
|
|
120
|
-
|
|
103
|
+
def verify(self, validator: ValidatorType) -> ExecuteFunctionResponse:
|
|
121
104
|
function_code = inspect.getsource(validator)
|
|
122
105
|
function_name = validator.__name__
|
|
123
|
-
return
|
|
106
|
+
return self.verify_raw(function_code, function_name)
|
|
124
107
|
|
|
125
|
-
|
|
108
|
+
def verify_raw(
|
|
126
109
|
self, function_code: str, function_name: str
|
|
127
110
|
) -> ExecuteFunctionResponse:
|
|
128
|
-
response =
|
|
111
|
+
response = self.client.request(
|
|
129
112
|
"POST",
|
|
130
113
|
"/execute_verifier_function",
|
|
131
114
|
json=ExecuteFunctionRequest(
|
|
@@ -135,9 +118,9 @@ class AsyncInstanceClient:
|
|
|
135
118
|
)
|
|
136
119
|
return ExecuteFunctionResponse(**response.json())
|
|
137
120
|
|
|
138
|
-
|
|
121
|
+
def _load_resources(self) -> None:
|
|
139
122
|
if self._resources is None:
|
|
140
|
-
response =
|
|
123
|
+
response = self.client.request("GET", "/resources")
|
|
141
124
|
if response.status_code != 200:
|
|
142
125
|
self._resources = []
|
|
143
126
|
return
|
|
@@ -159,7 +142,7 @@ class AsyncInstanceClient:
|
|
|
159
142
|
RESOURCE_TYPES[resource.type](resource, self.client)
|
|
160
143
|
)
|
|
161
144
|
|
|
162
|
-
|
|
145
|
+
def step(self, action: Dict[str, Any]) -> Tuple[Dict[str, Any], float, bool]:
|
|
163
146
|
"""Execute one step in the environment."""
|
|
164
147
|
if not self._instance_id:
|
|
165
148
|
raise FleetEnvironmentError(
|
|
@@ -172,20 +155,20 @@ class AsyncInstanceClient:
|
|
|
172
155
|
|
|
173
156
|
# Execute action through instance manager API
|
|
174
157
|
# This is a placeholder - actual implementation depends on the manager API spec
|
|
175
|
-
state, reward, done =
|
|
158
|
+
state, reward, done = self._execute_action(action)
|
|
176
159
|
|
|
177
160
|
return state, reward, done
|
|
178
161
|
|
|
179
162
|
except Exception as e:
|
|
180
163
|
raise FleetEnvironmentError(f"Failed to execute step: {e}")
|
|
181
164
|
|
|
182
|
-
|
|
165
|
+
def close(self) -> None:
|
|
183
166
|
"""Close the environment and clean up resources."""
|
|
184
167
|
try:
|
|
185
168
|
# Delete instance if it exists
|
|
186
169
|
if self._instance_id:
|
|
187
170
|
try:
|
|
188
|
-
|
|
171
|
+
self._client.delete_instance(self._instance_id)
|
|
189
172
|
logger.info(f"Deleted instance: {self._instance_id}")
|
|
190
173
|
except FleetAPIError as e:
|
|
191
174
|
logger.warning(f"Failed to delete instance: {e}")
|
|
@@ -195,20 +178,20 @@ class AsyncInstanceClient:
|
|
|
195
178
|
|
|
196
179
|
# Close manager client
|
|
197
180
|
if self._manager_client:
|
|
198
|
-
|
|
181
|
+
self._manager_client.close()
|
|
199
182
|
self._manager_client = None
|
|
200
183
|
|
|
201
184
|
# Close API client
|
|
202
|
-
|
|
185
|
+
self._client.close()
|
|
203
186
|
|
|
204
187
|
except Exception as e:
|
|
205
188
|
logger.error(f"Error closing environment: {e}")
|
|
206
189
|
|
|
207
|
-
|
|
208
|
-
response =
|
|
190
|
+
def manager_health_check(self) -> Optional[HealthResponse]:
|
|
191
|
+
response = self.client.request("GET", "/health")
|
|
209
192
|
return HealthResponse(**response.json())
|
|
210
193
|
|
|
211
|
-
|
|
194
|
+
def _wait_for_instance_ready(self, timeout: float = 300.0) -> None:
|
|
212
195
|
"""Wait for instance to be ready.
|
|
213
196
|
|
|
214
197
|
Args:
|
|
@@ -218,7 +201,7 @@ class AsyncInstanceClient:
|
|
|
218
201
|
|
|
219
202
|
while time.time() - start_time < timeout:
|
|
220
203
|
try:
|
|
221
|
-
instance =
|
|
204
|
+
instance = self._client.get_instance(self._instance_id)
|
|
222
205
|
self._instance_response = instance
|
|
223
206
|
|
|
224
207
|
if instance.status == "running":
|
|
@@ -231,20 +214,20 @@ class AsyncInstanceClient:
|
|
|
231
214
|
)
|
|
232
215
|
|
|
233
216
|
# Wait before checking again
|
|
234
|
-
|
|
217
|
+
asyncio.sleep(5)
|
|
235
218
|
|
|
236
219
|
except FleetAPIError as e:
|
|
237
220
|
if time.time() - start_time >= timeout:
|
|
238
221
|
raise FleetEnvironmentError(
|
|
239
222
|
f"Timeout waiting for instance to be ready: {e}"
|
|
240
223
|
)
|
|
241
|
-
|
|
224
|
+
asyncio.sleep(5)
|
|
242
225
|
|
|
243
226
|
raise FleetEnvironmentError(
|
|
244
227
|
f"Timeout waiting for instance {self._instance_id} to be ready"
|
|
245
228
|
)
|
|
246
229
|
|
|
247
|
-
|
|
230
|
+
def _execute_action(
|
|
248
231
|
self, action: Dict[str, Any]
|
|
249
232
|
) -> Tuple[Dict[str, Any], float, bool]:
|
|
250
233
|
"""Execute an action through the instance manager API.
|
|
@@ -259,7 +242,7 @@ class AsyncInstanceClient:
|
|
|
259
242
|
Tuple of (state, reward, done)
|
|
260
243
|
"""
|
|
261
244
|
# Ensure manager client is available
|
|
262
|
-
|
|
245
|
+
self._ensure_manager_client()
|
|
263
246
|
|
|
264
247
|
# TODO: In the future, this would use the manager API to execute actions
|
|
265
248
|
# For example: await self._manager_client.log_action(action)
|
|
@@ -286,10 +269,10 @@ class AsyncInstanceClient:
|
|
|
286
269
|
"status": "running",
|
|
287
270
|
}
|
|
288
271
|
|
|
289
|
-
|
|
272
|
+
def __enter__(self):
|
|
290
273
|
"""Async context manager entry."""
|
|
291
274
|
return self
|
|
292
275
|
|
|
293
|
-
|
|
276
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
294
277
|
"""Async context manager exit."""
|
|
295
|
-
|
|
278
|
+
self.close()
|
fleet/playwright.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
from typing import List, Dict, Any
|
|
3
|
-
from playwright.
|
|
4
|
-
from .client import
|
|
3
|
+
from playwright.sync_api import sync_playwright, Browser, Page
|
|
4
|
+
from .client import Environment
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Key mapping for computer use actions
|
|
@@ -65,7 +65,7 @@ class FleetPlaywrightWrapper:
|
|
|
65
65
|
|
|
66
66
|
def __init__(
|
|
67
67
|
self,
|
|
68
|
-
env:
|
|
68
|
+
env: Environment,
|
|
69
69
|
display_width: int = 1920,
|
|
70
70
|
display_height: int = 1080,
|
|
71
71
|
):
|
|
@@ -86,35 +86,33 @@ class FleetPlaywrightWrapper:
|
|
|
86
86
|
self._page: Page | None = None
|
|
87
87
|
self._started = False
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
def start(self):
|
|
90
90
|
"""Start the browser and establish connection."""
|
|
91
91
|
if self._started:
|
|
92
92
|
return
|
|
93
93
|
|
|
94
94
|
# Start Playwright
|
|
95
|
-
self._playwright =
|
|
95
|
+
self._playwright = sync_playwright().start()
|
|
96
96
|
|
|
97
97
|
# Start browser on the Fleet instance
|
|
98
98
|
print("Starting browser...")
|
|
99
|
-
|
|
100
|
-
cdp =
|
|
99
|
+
self.env.browser().start()
|
|
100
|
+
cdp = self.env.browser().describe()
|
|
101
101
|
|
|
102
102
|
# Connect to browser
|
|
103
|
-
self._browser =
|
|
104
|
-
cdp.cdp_browser_url
|
|
105
|
-
)
|
|
103
|
+
self._browser = self._playwright.chromium.connect_over_cdp(cdp.cdp_browser_url)
|
|
106
104
|
self._page = self._browser.contexts[0].pages[0]
|
|
107
|
-
|
|
105
|
+
self._page.set_viewport_size(
|
|
108
106
|
{"width": self.display_width, "height": self.display_height}
|
|
109
107
|
)
|
|
110
108
|
|
|
111
109
|
self._started = True
|
|
112
110
|
print(f"Track agent: {cdp.cdp_devtools_url}")
|
|
113
111
|
|
|
114
|
-
|
|
112
|
+
def close(self):
|
|
115
113
|
"""Close the browser connection."""
|
|
116
114
|
if self._playwright:
|
|
117
|
-
|
|
115
|
+
self._playwright.stop()
|
|
118
116
|
self._playwright = None
|
|
119
117
|
self._browser = None
|
|
120
118
|
self._page = None
|
|
@@ -140,7 +138,7 @@ class FleetPlaywrightWrapper:
|
|
|
140
138
|
"environment": "browser",
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
|
|
141
|
+
def screenshot(self) -> str:
|
|
144
142
|
"""
|
|
145
143
|
Take a screenshot and return base64 encoded string.
|
|
146
144
|
|
|
@@ -149,7 +147,7 @@ class FleetPlaywrightWrapper:
|
|
|
149
147
|
"""
|
|
150
148
|
self._ensure_started()
|
|
151
149
|
|
|
152
|
-
png_bytes =
|
|
150
|
+
png_bytes = self._page.screenshot(full_page=False)
|
|
153
151
|
return base64.b64encode(png_bytes).decode("utf-8")
|
|
154
152
|
|
|
155
153
|
def get_current_url(self) -> str:
|
|
@@ -157,7 +155,7 @@ class FleetPlaywrightWrapper:
|
|
|
157
155
|
self._ensure_started()
|
|
158
156
|
return self._page.url
|
|
159
157
|
|
|
160
|
-
|
|
158
|
+
def execute_computer_action(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
161
159
|
"""
|
|
162
160
|
Execute a computer action and return the result for OpenAI API.
|
|
163
161
|
|
|
@@ -177,12 +175,12 @@ class FleetPlaywrightWrapper:
|
|
|
177
175
|
# Execute the action
|
|
178
176
|
if hasattr(self, f"_{action_type}"):
|
|
179
177
|
method = getattr(self, f"_{action_type}")
|
|
180
|
-
|
|
178
|
+
method(**action_args)
|
|
181
179
|
else:
|
|
182
180
|
raise ValueError(f"Unsupported action type: {action_type}")
|
|
183
181
|
|
|
184
182
|
# Take screenshot after action
|
|
185
|
-
screenshot_base64 =
|
|
183
|
+
screenshot_base64 = self.screenshot()
|
|
186
184
|
|
|
187
185
|
return {
|
|
188
186
|
"type": "input_image",
|
|
@@ -191,81 +189,81 @@ class FleetPlaywrightWrapper:
|
|
|
191
189
|
}
|
|
192
190
|
|
|
193
191
|
# Computer action implementations
|
|
194
|
-
|
|
192
|
+
def _click(self, x: int, y: int, button: str = "left") -> None:
|
|
195
193
|
"""Click at coordinates."""
|
|
196
194
|
self._ensure_started()
|
|
197
|
-
|
|
195
|
+
self._page.mouse.click(x, y, button=button)
|
|
198
196
|
|
|
199
|
-
|
|
197
|
+
def _double_click(self, x: int, y: int) -> None:
|
|
200
198
|
"""Double-click at coordinates."""
|
|
201
199
|
self._ensure_started()
|
|
202
|
-
|
|
200
|
+
self._page.mouse.dblclick(x, y)
|
|
203
201
|
|
|
204
|
-
|
|
202
|
+
def _scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
|
|
205
203
|
"""Scroll from coordinates."""
|
|
206
204
|
self._ensure_started()
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
self._page.mouse.move(x, y)
|
|
206
|
+
self._page.evaluate(f"window.scrollBy({scroll_x}, {scroll_y})")
|
|
209
207
|
|
|
210
|
-
|
|
208
|
+
def _type(self, text: str) -> None:
|
|
211
209
|
"""Type text."""
|
|
212
210
|
self._ensure_started()
|
|
213
|
-
|
|
211
|
+
self._page.keyboard.type(text)
|
|
214
212
|
|
|
215
|
-
|
|
213
|
+
def _keypress(self, keys: List[str]) -> None:
|
|
216
214
|
"""Press key combination."""
|
|
217
215
|
self._ensure_started()
|
|
218
216
|
mapped_keys = [CUA_KEY_TO_PLAYWRIGHT_KEY.get(key.lower(), key) for key in keys]
|
|
219
217
|
for key in mapped_keys:
|
|
220
|
-
|
|
218
|
+
self._page.keyboard.down(key)
|
|
221
219
|
for key in reversed(mapped_keys):
|
|
222
|
-
|
|
220
|
+
self._page.keyboard.up(key)
|
|
223
221
|
|
|
224
|
-
|
|
222
|
+
def _move(self, x: int, y: int) -> None:
|
|
225
223
|
"""Move mouse to coordinates."""
|
|
226
224
|
self._ensure_started()
|
|
227
|
-
|
|
225
|
+
self._page.mouse.move(x, y)
|
|
228
226
|
|
|
229
|
-
|
|
227
|
+
def _drag(self, path: List[Dict[str, int]]) -> None:
|
|
230
228
|
"""Drag mouse along path."""
|
|
231
229
|
self._ensure_started()
|
|
232
230
|
if not path:
|
|
233
231
|
return
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
self._page.mouse.move(path[0]["x"], path[0]["y"])
|
|
233
|
+
self._page.mouse.down()
|
|
236
234
|
for point in path[1:]:
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
self._page.mouse.move(point["x"], point["y"])
|
|
236
|
+
self._page.mouse.up()
|
|
239
237
|
|
|
240
|
-
|
|
238
|
+
def _wait(self, ms: int = 1000) -> None:
|
|
241
239
|
"""Wait for specified milliseconds."""
|
|
242
240
|
import asyncio
|
|
243
241
|
|
|
244
|
-
|
|
242
|
+
asyncio.sleep(ms / 1000)
|
|
245
243
|
|
|
246
244
|
# Browser-specific actions
|
|
247
|
-
|
|
245
|
+
def _goto(self, url: str) -> None:
|
|
248
246
|
"""Navigate to URL."""
|
|
249
247
|
self._ensure_started()
|
|
250
248
|
try:
|
|
251
|
-
|
|
249
|
+
self._page.goto(url)
|
|
252
250
|
except Exception as e:
|
|
253
251
|
print(f"Error navigating to {url}: {e}")
|
|
254
252
|
|
|
255
|
-
|
|
253
|
+
def _back(self) -> None:
|
|
256
254
|
"""Go back in browser history."""
|
|
257
255
|
self._ensure_started()
|
|
258
|
-
|
|
256
|
+
self._page.go_back()
|
|
259
257
|
|
|
260
|
-
|
|
258
|
+
def _forward(self) -> None:
|
|
261
259
|
"""Go forward in browser history."""
|
|
262
260
|
self._ensure_started()
|
|
263
|
-
|
|
261
|
+
self._page.go_forward()
|
|
264
262
|
|
|
265
|
-
|
|
263
|
+
def _refresh(self) -> None:
|
|
266
264
|
"""Refresh the page."""
|
|
267
265
|
self._ensure_started()
|
|
268
|
-
|
|
266
|
+
self._page.reload()
|
|
269
267
|
|
|
270
268
|
# ------------------------------------------------------------------
|
|
271
269
|
# Public aliases (no leading underscore) expected by the Agent &
|
|
File without changes
|
fleet/resources/browser.py
CHANGED
|
@@ -10,32 +10,32 @@ from .base import Resource
|
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
-
from ..instance.base import
|
|
13
|
+
from ..instance.base import SyncWrapper
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
17
|
-
def __init__(self, resource: ResourceModel, client: "
|
|
16
|
+
class BrowserResource(Resource):
|
|
17
|
+
def __init__(self, resource: ResourceModel, client: "SyncWrapper"):
|
|
18
18
|
super().__init__(resource)
|
|
19
19
|
self.client = client
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
response =
|
|
21
|
+
def start(self, width: int = 1920, height: int = 1080) -> CDPDescribeResponse:
|
|
22
|
+
response = self.client.request(
|
|
23
23
|
"POST",
|
|
24
24
|
"/resources/cdp/start",
|
|
25
25
|
json=ChromeStartRequest(resolution=f"{width},{height}").model_dump(),
|
|
26
26
|
)
|
|
27
27
|
ChromeStartResponse(**response.json())
|
|
28
|
-
return
|
|
28
|
+
return self.describe()
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
response =
|
|
30
|
+
def describe(self) -> CDPDescribeResponse:
|
|
31
|
+
response = self.client.request("GET", "/resources/cdp/describe")
|
|
32
32
|
if response.status_code != 200:
|
|
33
|
-
|
|
34
|
-
response =
|
|
33
|
+
self.start()
|
|
34
|
+
response = self.client.request("GET", "/resources/cdp/describe")
|
|
35
35
|
return CDPDescribeResponse(**response.json())
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
return (
|
|
37
|
+
def cdp_url(self) -> str:
|
|
38
|
+
return (self.describe()).cdp_browser_url
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
return (
|
|
40
|
+
def devtools_url(self) -> str:
|
|
41
|
+
return (self.describe()).cdp_devtools_url
|
fleet/resources/sqlite.py
CHANGED
|
@@ -6,34 +6,34 @@ from .base import Resource
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from ..instance.base import
|
|
9
|
+
from ..instance.base import SyncWrapper
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
13
|
-
def __init__(self, resource: ResourceModel, client: "
|
|
12
|
+
class SQLiteResource(Resource):
|
|
13
|
+
def __init__(self, resource: ResourceModel, client: "SyncWrapper"):
|
|
14
14
|
super().__init__(resource)
|
|
15
15
|
self.client = client
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
def describe(self) -> DescribeResponse:
|
|
18
18
|
"""Describe the SQLite database schema."""
|
|
19
|
-
response =
|
|
19
|
+
response = self.client.request(
|
|
20
20
|
"GET", f"/resources/sqlite/{self.resource.name}/describe"
|
|
21
21
|
)
|
|
22
22
|
return DescribeResponse(**response.json())
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
def query(
|
|
25
25
|
self, query: str, args: Optional[List[Any]] = None
|
|
26
26
|
) -> QueryResponse:
|
|
27
|
-
return
|
|
27
|
+
return self._query(query, args, read_only=True)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
return
|
|
29
|
+
def exec(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
|
|
30
|
+
return self._query(query, args, read_only=False)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
def _query(
|
|
33
33
|
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
34
34
|
) -> QueryResponse:
|
|
35
35
|
request = QueryRequest(query=query, args=args, read_only=read_only)
|
|
36
|
-
response =
|
|
36
|
+
response = self.client.request(
|
|
37
37
|
"POST",
|
|
38
38
|
f"/resources/sqlite/{self.resource.name}/query",
|
|
39
39
|
json=request.model_dump(),
|
fleet/verifiers/__init__.py
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
execute_validation_function,
|
|
6
|
-
)
|
|
1
|
+
"""Fleet verifiers module - database snapshot validation utilities."""
|
|
2
|
+
|
|
3
|
+
from .db import DatabaseSnapshot, IgnoreConfig, SnapshotDiff
|
|
4
|
+
from .code import TASK_SUCCESSFUL_SCORE
|
|
7
5
|
|
|
8
6
|
__all__ = [
|
|
9
7
|
"DatabaseSnapshot",
|
|
10
|
-
"QueryBuilder",
|
|
11
|
-
"SnapshotDiff",
|
|
12
8
|
"IgnoreConfig",
|
|
9
|
+
"SnapshotDiff",
|
|
13
10
|
"TASK_SUCCESSFUL_SCORE",
|
|
14
|
-
"extract_last_assistant_message",
|
|
15
|
-
"execute_validation_function",
|
|
16
11
|
]
|