fleet-python 0.2.1__tar.gz → 0.2.2__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.

Potentially problematic release.


This version of fleet-python might be problematic. Click here for more details.

Files changed (34) hide show
  1. {fleet_python-0.2.1/fleet_python.egg-info → fleet_python-0.2.2}/PKG-INFO +1 -1
  2. fleet_python-0.2.2/examples/dsl_example.py +112 -0
  3. fleet_python-0.2.2/examples/example.py +38 -0
  4. {fleet_python-0.2.1 → fleet_python-0.2.2}/examples/openai_example.py +197 -78
  5. {fleet_python-0.2.1 → fleet_python-0.2.2}/examples/quickstart.py +5 -5
  6. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/__init__.py +3 -1
  7. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/base.py +1 -1
  8. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/client.py +60 -28
  9. fleet_python-0.2.2/fleet/env/__init__.py +3 -0
  10. fleet_python-0.2.2/fleet/env/client.py +15 -0
  11. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.2/fleet/manager}/__init__.py +3 -3
  12. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.2/fleet/manager}/client.py +2 -3
  13. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/resources/base.py +5 -2
  14. fleet_python-0.2.2/fleet/resources/browser.py +44 -0
  15. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/resources/sqlite.py +3 -3
  16. fleet_python-0.2.2/fleet/verifiers/__init__.py +4 -0
  17. fleet_python-0.2.2/fleet/verifiers/database_snapshot.py +666 -0
  18. fleet_python-0.2.2/fleet/verifiers/sql_differ.py +187 -0
  19. {fleet_python-0.2.1 → fleet_python-0.2.2/fleet_python.egg-info}/PKG-INFO +1 -1
  20. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet_python.egg-info/SOURCES.txt +8 -2
  21. {fleet_python-0.2.1 → fleet_python-0.2.2}/pyproject.toml +1 -1
  22. fleet_python-0.2.1/examples/example.py +0 -51
  23. fleet_python-0.2.1/fleet/resources/browser.py +0 -34
  24. {fleet_python-0.2.1 → fleet_python-0.2.2}/LICENSE +0 -0
  25. {fleet_python-0.2.1 → fleet_python-0.2.2}/README.md +0 -0
  26. {fleet_python-0.2.1 → fleet_python-0.2.2}/examples/nova_act_example.py +0 -0
  27. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/exceptions.py +0 -0
  28. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.2/fleet/manager}/base.py +0 -0
  29. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.2/fleet/manager}/models.py +0 -0
  30. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet/models.py +0 -0
  31. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet_python.egg-info/dependency_links.txt +0 -0
  32. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet_python.egg-info/requires.txt +0 -0
  33. {fleet_python-0.2.1 → fleet_python-0.2.2}/fleet_python.egg-info/top_level.txt +0 -0
  34. {fleet_python-0.2.1 → fleet_python-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -0,0 +1,112 @@
1
+ from fleet.verifiers import DatabaseSnapshot, IgnoreConfig
2
+
3
+ async def validate_give_me_more_tasks(
4
+ before: DatabaseSnapshot,
5
+ after: DatabaseSnapshot,
6
+ transcript: str | None = None,
7
+ ) -> int:
8
+ """Validate that bugs are moved to sprint 3 and assigned correctly."""
9
+
10
+ # Get user IDs
11
+ raj_user = after.table("users").eq("name", "Raj Patel").first()
12
+ sarah_kim_user = after.table("users").eq("name", "Sarah Kim").first()
13
+
14
+ if not raj_user:
15
+ raise AssertionError("User 'Raj Patel' not found")
16
+ if not sarah_kim_user:
17
+ raise AssertionError("User 'Sarah Kim' not found")
18
+
19
+ raj_id = raj_user["id"]
20
+ sarah_kim_id = sarah_kim_user["id"]
21
+
22
+ # Verify SCRUM-555 (data pipeline bug) is assigned to Sarah Kim
23
+ after.table("issues").eq("id", "SCRUM-555").assert_eq("owner", sarah_kim_id)
24
+
25
+ # Verify other bugs are assigned to Raj Patel
26
+ other_bugs = [
27
+ "SCRUM-780",
28
+ "SCRUM-781",
29
+ "SCRUM-790",
30
+ "SCRUM-822",
31
+ "SCRUM-882",
32
+ "SCRUM-897",
33
+ "SCRUM-956",
34
+ "SCRUM-1331",
35
+ "SCRUM-1312",
36
+ "SCRUM-1210",
37
+ "SCRUM-1230",
38
+ "SCRUM-1282",
39
+ ]
40
+ for bug_id in other_bugs:
41
+ after.table("issues").eq("id", bug_id).assert_eq("owner", raj_id)
42
+
43
+ # Verify all bugs are in sprint_3
44
+ all_bugs = ["SCRUM-555"] + other_bugs
45
+ for bug_id in all_bugs:
46
+ after.table("sprint_issues").eq("issue_id", bug_id).assert_eq(
47
+ "sprint_id", "sprint_3"
48
+ )
49
+
50
+ # Configure ignore settings
51
+ ignore_config = IgnoreConfig(
52
+ tables={"activities", "pageviews", "sprint_issues"},
53
+ table_fields={
54
+ "issues": {"updated_at", "created_at", "rowid"},
55
+ "users": {"updated_at", "created_at", "rowid"},
56
+ "sprint_issues": {"updated_at", "created_at", "rowid"},
57
+ },
58
+ )
59
+
60
+ # Build expected changes
61
+ expected_changes: list[dict] = []
62
+
63
+ # Assignment changes
64
+ expected_changes.append(
65
+ {
66
+ "table": "issues",
67
+ "pk": "SCRUM-555",
68
+ "field": "owner",
69
+ "after": sarah_kim_id,
70
+ }
71
+ )
72
+ for bug_id in other_bugs:
73
+ expected_changes.append(
74
+ {
75
+ "table": "issues",
76
+ "pk": bug_id,
77
+ "field": "owner",
78
+ "after": raj_id,
79
+ }
80
+ )
81
+
82
+ # Sprint changes
83
+ for bug_id in all_bugs:
84
+ # Remove from previous sprint if present
85
+ before_assignment = (
86
+ before.table("sprint_issues").eq("issue_id", bug_id).first()
87
+ )
88
+ if before_assignment:
89
+ old_sprint = before_assignment.get("sprint_id")
90
+ expected_changes.append(
91
+ {
92
+ "table": "sprint_issues",
93
+ "pk": (old_sprint, bug_id),
94
+ "field": None,
95
+ "after": "__removed__",
96
+ }
97
+ )
98
+
99
+ # Add to sprint_3
100
+ expected_changes.append(
101
+ {
102
+ "table": "sprint_issues",
103
+ "pk": ("sprint_3", bug_id),
104
+ "field": None,
105
+ "after": "__added__",
106
+ }
107
+ )
108
+
109
+ # Enforce invariant
110
+ before.diff(after, ignore_config).expect_only(expected_changes)
111
+
112
+ return TASK_SUCCESSFUL_SCORE
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ """Example demonstrating browser control with Fleet Manager Client."""
3
+
4
+ import asyncio
5
+ import fleet as flt
6
+
7
+
8
+ async def main():
9
+ environments = await flt.env.list_envs()
10
+ print("Environments:", len(environments))
11
+
12
+ # Create a new instance
13
+ env = await flt.env.make("hubspot:v1.2.7")
14
+ print("New Instance:", env.instance_id)
15
+
16
+ response = await env.reset(seed=42)
17
+ print("Reset response:", response)
18
+
19
+ print(await env.resources())
20
+
21
+ sqlite = env.db()
22
+ print("SQLite:", await sqlite.describe())
23
+
24
+ print("Query:", await sqlite.query("SELECT * FROM users"))
25
+
26
+ sqlite = await env.state("sqlite://current").describe()
27
+ print("SQLite:", sqlite)
28
+
29
+ browser = env.browser()
30
+ print("CDP URL:", await browser.cdp_url())
31
+ print("Devtools URL:", await browser.devtools_url())
32
+
33
+ # Delete the instance
34
+ await env.close()
35
+
36
+
37
+ if __name__ == "__main__":
38
+ asyncio.run(main())
@@ -1,7 +1,65 @@
1
- import time
2
1
  import base64
3
- from typing import List, Dict, Callable
4
- from playwright.sync_api import sync_playwright, Browser, Page
2
+ from typing import List, Dict, Callable, Optional
3
+ from playwright.async_api import async_playwright, Browser, Page
4
+ import httpx
5
+ import json
6
+ import io
7
+ from io import BytesIO
8
+ from PIL import Image
9
+ import os
10
+ import asyncio
11
+ import fleet as flt
12
+
13
+
14
+ def sanitize_message(msg: dict) -> dict:
15
+ """Return a copy of the message with image_url omitted for computer_call_output messages."""
16
+ if msg.get("type") == "computer_call_output":
17
+ output = msg.get("output", {})
18
+ if isinstance(output, dict):
19
+ sanitized = msg.copy()
20
+ sanitized["output"] = {**output, "image_url": "[omitted]"}
21
+ return sanitized
22
+ return msg
23
+
24
+
25
+ async def create_response(**kwargs):
26
+ url = "https://api.openai.com/v1/responses"
27
+ headers = {
28
+ "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
29
+ "Content-Type": "application/json",
30
+ }
31
+
32
+ openai_org = os.getenv("OPENAI_ORG")
33
+ if openai_org:
34
+ headers["Openai-Organization"] = openai_org
35
+
36
+ # Configure timeout: 30 seconds for connect, 60 seconds for read
37
+ timeout = httpx.Timeout(connect=60.0, read=60.0, write=60.0, pool=60.0)
38
+
39
+ async with httpx.AsyncClient(timeout=timeout) as client:
40
+ response = await client.post(url, headers=headers, json=kwargs)
41
+
42
+ if response.status_code != 200:
43
+ print(f"Error: {response.status_code} {response.text}")
44
+
45
+ return response.json()
46
+
47
+
48
+ def pp(obj):
49
+ print(json.dumps(obj, indent=4))
50
+
51
+
52
+ def show_image(base_64_image):
53
+ image_data = base64.b64decode(base_64_image)
54
+ image = Image.open(BytesIO(image_data))
55
+ image.show()
56
+
57
+
58
+ def calculate_image_dimensions(base_64_image):
59
+ image_data = base64.b64decode(base_64_image)
60
+ image = Image.open(io.BytesIO(image_data))
61
+ return image.size
62
+
5
63
 
6
64
  # Optional: key mapping if your model uses "CUA" style keys
7
65
  CUA_KEY_TO_PLAYWRIGHT_KEY = {
@@ -48,136 +106,147 @@ class BasePlaywrightComputer:
48
106
  return "browser"
49
107
 
50
108
  def get_dimensions(self):
51
- return (1024, 768)
109
+ return (1920, 1080)
52
110
 
53
111
  def __init__(self):
54
112
  self._playwright = None
55
113
  self._browser: Browser | None = None
56
114
  self._page: Page | None = None
57
115
 
58
- def __enter__(self):
116
+ async def __aenter__(self):
59
117
  # Start Playwright and call the subclass hook for getting browser/page
60
- self._playwright = sync_playwright().start()
61
- self._browser, self._page = self._get_browser_and_page()
118
+ self._playwright = await async_playwright().start()
119
+ self._browser, self._page = await self._get_browser_and_page()
62
120
 
63
121
  # Set up network interception to flag URLs matching domains in BLOCKED_DOMAINS
64
- def handle_route(route, request):
65
- route.continue_()
122
+ async def handle_route(route, request):
123
+ await route.continue_()
66
124
 
67
- self._page.route("**/*", handle_route)
125
+ await self._page.route("**/*", handle_route)
68
126
 
69
127
  return self
70
128
 
71
- def __exit__(self, exc_type, exc_val, exc_tb):
72
- if self._browser:
73
- self._browser.close()
129
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
130
+ # if self._browser:
131
+ # await self._browser.close()
74
132
  if self._playwright:
75
- self._playwright.stop()
133
+ await self._playwright.stop()
76
134
 
77
135
  def get_current_url(self) -> str:
78
136
  return self._page.url
79
137
 
80
138
  # --- Common "Computer" actions ---
81
- def screenshot(self) -> str:
139
+ async def screenshot(self) -> str:
82
140
  """Capture only the viewport (not full_page)."""
83
- png_bytes = self._page.screenshot(full_page=False)
141
+ png_bytes = await self._page.screenshot(full_page=False)
84
142
  return base64.b64encode(png_bytes).decode("utf-8")
85
143
 
86
- def click(self, x: int, y: int, button: str = "left") -> None:
144
+ async def click(self, x: int, y: int, button: str = "left") -> None:
87
145
  if button == "back":
88
- self.back()
146
+ await self.back()
89
147
  elif button == "forward":
90
- self.forward()
148
+ await self.forward()
91
149
  elif button == "wheel":
92
- self._page.mouse.wheel(x, y)
150
+ await self._page.mouse.wheel(x, y)
93
151
  else:
94
152
  button_mapping = {"left": "left", "right": "right"}
95
153
  button_type = button_mapping.get(button, "left")
96
- self._page.mouse.click(x, y, button=button_type)
154
+ await self._page.mouse.click(x, y, button=button_type)
97
155
 
98
- def double_click(self, x: int, y: int) -> None:
99
- self._page.mouse.dblclick(x, y)
156
+ async def double_click(self, x: int, y: int) -> None:
157
+ await self._page.mouse.dblclick(x, y)
100
158
 
101
- def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
102
- self._page.mouse.move(x, y)
103
- self._page.evaluate(f"window.scrollBy({scroll_x}, {scroll_y})")
159
+ async def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
160
+ await self._page.mouse.move(x, y)
161
+ await self._page.evaluate(f"window.scrollBy({scroll_x}, {scroll_y})")
104
162
 
105
- def type(self, text: str) -> None:
106
- self._page.keyboard.type(text)
163
+ async def type(self, text: str) -> None:
164
+ await self._page.keyboard.type(text)
107
165
 
108
- def wait(self, ms: int = 1000) -> None:
109
- time.sleep(ms / 1000)
166
+ async def wait(self, ms: int = 1000) -> None:
167
+ await asyncio.sleep(ms / 1000)
110
168
 
111
- def move(self, x: int, y: int) -> None:
112
- self._page.mouse.move(x, y)
169
+ async def move(self, x: int, y: int) -> None:
170
+ await self._page.mouse.move(x, y)
113
171
 
114
- def keypress(self, keys: List[str]) -> None:
172
+ async def keypress(self, keys: List[str]) -> None:
115
173
  mapped_keys = [CUA_KEY_TO_PLAYWRIGHT_KEY.get(key.lower(), key) for key in keys]
116
174
  for key in mapped_keys:
117
- self._page.keyboard.down(key)
175
+ await self._page.keyboard.down(key)
118
176
  for key in reversed(mapped_keys):
119
- self._page.keyboard.up(key)
177
+ await self._page.keyboard.up(key)
120
178
 
121
- def drag(self, path: List[Dict[str, int]]) -> None:
179
+ async def drag(self, path: List[Dict[str, int]]) -> None:
122
180
  if not path:
123
181
  return
124
- self._page.mouse.move(path[0]["x"], path[0]["y"])
125
- self._page.mouse.down()
182
+ await self._page.mouse.move(path[0]["x"], path[0]["y"])
183
+ await self._page.mouse.down()
126
184
  for point in path[1:]:
127
- self._page.mouse.move(point["x"], point["y"])
128
- self._page.mouse.up()
185
+ await self._page.mouse.move(point["x"], point["y"])
186
+ await self._page.mouse.up()
129
187
 
130
188
  # --- Extra browser-oriented actions ---
131
- def goto(self, url: str) -> None:
189
+ async def goto(self, url: str) -> None:
132
190
  try:
133
- return self._page.goto(url)
191
+ return await self._page.goto(url)
134
192
  except Exception as e:
135
193
  print(f"Error navigating to {url}: {e}")
136
194
 
137
- def back(self) -> None:
138
- return self._page.go_back()
195
+ async def back(self) -> None:
196
+ return await self._page.go_back()
139
197
 
140
- def forward(self) -> None:
141
- return self._page.go_forward()
198
+ async def forward(self) -> None:
199
+ return await self._page.go_forward()
142
200
 
143
201
  # --- Subclass hook ---
144
- def _get_browser_and_page(self) -> tuple[Browser, Page]:
202
+ async def _get_browser_and_page(self) -> tuple[Browser, Page]:
145
203
  """Subclasses must implement, returning (Browser, Page)."""
146
204
  raise NotImplementedError
147
205
 
148
206
 
149
- class LocalPlaywrightBrowser(BasePlaywrightComputer):
207
+ class FleetPlaywrightBrowser(BasePlaywrightComputer):
150
208
  """Launches a local Chromium instance using Playwright."""
151
209
 
152
- def __init__(self, headless: bool = False):
210
+ def __init__(
211
+ self,
212
+ fleet: flt.AsyncFleet,
213
+ env_key: str,
214
+ version: Optional[str] = None,
215
+ headless: bool = False,
216
+ ):
153
217
  super().__init__()
218
+ self.fleet = fleet
219
+ self.env_key = env_key
220
+ self.version = version
154
221
  self.headless = headless
155
222
 
156
- def _get_browser_and_page(self) -> tuple[Browser, Page]:
223
+ async def _get_browser_and_page(self) -> tuple[Browser, Page]:
157
224
  width, height = self.get_dimensions()
158
- launch_args = [
159
- f"--window-size={width},{height}",
160
- "--disable-extensions",
161
- "--disable-file-system",
162
- ]
163
- browser = self._playwright.chromium.launch(
164
- chromium_sandbox=True,
165
- headless=self.headless,
166
- args=launch_args,
167
- env={"DISPLAY": ":0"},
225
+
226
+ # Create an instance of the environment
227
+ print(f"Creating instance of {self.env_key} {self.version}...")
228
+ self.instance = await self.fleet.make(
229
+ flt.InstanceRequest(env_key=self.env_key, version=self.version)
168
230
  )
169
231
 
170
- context = browser.new_context()
232
+ # Start the browser
233
+ print("Starting browser...")
234
+ await self.instance.env.browser("cdp").start()
235
+ print("Getting CDP URL...")
236
+ cdp = await self.instance.env.browser("cdp").describe()
237
+ print("DevTools URL:", cdp.cdp_devtools_url)
238
+
239
+ # Connect to the browser
240
+ browser = await self._playwright.chromium.connect_over_cdp(cdp.cdp_browser_url)
171
241
 
172
242
  # Add event listeners for page creation and closure
243
+ context = browser.contexts[0]
173
244
  context.on("page", self._handle_new_page)
174
245
 
175
- page = context.new_page()
176
- page.set_viewport_size({"width": width, "height": height})
246
+ page = context.pages[0]
247
+ await page.set_viewport_size({"width": width, "height": height})
177
248
  page.on("close", self._handle_page_close)
178
249
 
179
- page.goto("https://bing.com")
180
-
181
250
  return browser, page
182
251
 
183
252
  def _handle_new_page(self, page: Page):
@@ -207,7 +276,7 @@ class Agent:
207
276
  def __init__(
208
277
  self,
209
278
  model="computer-use-preview",
210
- computer: Computer = None,
279
+ computer: FleetPlaywrightBrowser = None,
211
280
  tools: list[dict] = [],
212
281
  acknowledge_safety_check_callback: Callable = lambda: False,
213
282
  ):
@@ -234,8 +303,11 @@ class Agent:
234
303
  if self.debug:
235
304
  pp(*args)
236
305
 
237
- def handle_item(self, item):
306
+ async def handle_item(self, item):
238
307
  """Handle each item; may cause a computer action + screenshot."""
308
+ if self.debug:
309
+ print(f"Handling item of type: {item.get('type')}")
310
+
239
311
  if item["type"] == "message":
240
312
  if self.print_steps:
241
313
  print(item["content"][0]["text"])
@@ -247,7 +319,7 @@ class Agent:
247
319
 
248
320
  if hasattr(self.computer, name): # if function exists on computer, call it
249
321
  method = getattr(self.computer, name)
250
- method(**args)
322
+ await method(**args)
251
323
  return [
252
324
  {
253
325
  "type": "function_call_output",
@@ -264,9 +336,9 @@ class Agent:
264
336
  print(f"{action_type}({action_args})")
265
337
 
266
338
  method = getattr(self.computer, action_type)
267
- method(**action_args)
339
+ await method(**action_args)
268
340
 
269
- screenshot_base64 = self.computer.screenshot()
341
+ screenshot_base64 = await self.computer.screenshot()
270
342
  if self.show_images:
271
343
  show_image(screenshot_base64)
272
344
 
@@ -292,13 +364,12 @@ class Agent:
292
364
  # additional URL safety checks for browser environments
293
365
  if self.computer.get_environment() == "browser":
294
366
  current_url = self.computer.get_current_url()
295
- check_blocklisted_url(current_url)
296
367
  call_output["output"]["current_url"] = current_url
297
368
 
298
369
  return [call_output]
299
370
  return []
300
371
 
301
- def run_full_turn(
372
+ async def run_full_turn(
302
373
  self, input_items, print_steps=True, debug=False, show_images=False
303
374
  ):
304
375
  self.print_steps = print_steps
@@ -310,7 +381,7 @@ class Agent:
310
381
  while new_items[-1].get("role") != "assistant" if new_items else True:
311
382
  self.debug_print([sanitize_message(msg) for msg in input_items + new_items])
312
383
 
313
- response = create_response(
384
+ response = await create_response(
314
385
  model=self.model,
315
386
  input=input_items + new_items,
316
387
  tools=self.tools,
@@ -318,12 +389,60 @@ class Agent:
318
389
  )
319
390
  self.debug_print(response)
320
391
 
321
- if "output" not in response and self.debug:
322
- print(response)
323
- raise ValueError("No output from model")
392
+ if "output" not in response:
393
+ if self.debug:
394
+ print("Full response:", response)
395
+ if "error" in response:
396
+ error_msg = response["error"].get("message", "Unknown error")
397
+ raise ValueError(f"API Error: {error_msg}")
398
+ else:
399
+ raise ValueError("No output from model")
324
400
  else:
325
- new_items += response["output"]
401
+ # Append each item from the model output to conversation history
402
+ # in the exact order we received them, **without filtering** so that
403
+ # required pairs such as reasoning → computer_call are preserved.
326
404
  for item in response["output"]:
327
- new_items += self.handle_item(item)
405
+ # First, record the original item itself.
406
+ new_items.append(item)
407
+
408
+ # Next, perform any local side-effects (browser actions, etc.).
409
+ handled_items = await self.handle_item(item)
410
+
411
+ # If the handler generated additional items (e.g. computer_call_output)
412
+ # we append those *immediately* so the order remains:
413
+ # reasoning → computer_call → computer_call_output
414
+ if handled_items:
415
+ new_items += handled_items
328
416
 
329
417
  return new_items
418
+
419
+
420
+ tools = []
421
+
422
+
423
+ async def ainput(prompt: str = "") -> str:
424
+ """Async version of input()"""
425
+ loop = asyncio.get_event_loop()
426
+ return await loop.run_in_executor(None, input, prompt)
427
+
428
+
429
+ async def main():
430
+ fleet = flt.AsyncFleet()
431
+
432
+ async with FleetPlaywrightBrowser(fleet, "hubspot", "v1.2.7") as computer:
433
+ agent = Agent(computer=computer, tools=tools)
434
+ items = [
435
+ {
436
+ "role": "developer",
437
+ "content": "You have access to a clone of Hubspot. You can use the computer to navigate the browser and perform actions.",
438
+ }
439
+ ]
440
+ while True:
441
+ user_input = await ainput("> ")
442
+ items.append({"role": "user", "content": user_input})
443
+ output_items = await agent.run_full_turn(items, show_images=False, debug=False)
444
+ items += output_items
445
+
446
+
447
+ if __name__ == "__main__":
448
+ asyncio.run(main())
@@ -35,7 +35,7 @@ async def main():
35
35
  # 1. List available environments
36
36
  print("\n📋 Available environments:")
37
37
  try:
38
- environments = await fleet.env.list_envs()
38
+ environments = await fleet.manager.list_envs()
39
39
  for env in environments:
40
40
  print(f" - {env.env_key}: {env.name}")
41
41
  print(f" Description: {env.description}")
@@ -48,7 +48,7 @@ async def main():
48
48
  # 2. Create a new environment instance
49
49
  print("\n🚀 Creating new environment...")
50
50
  try:
51
- env = await fleet.env.make("fira:v1.2.5", region="us-west-1")
51
+ env = await fleet.manager.make("fira:v1.2.5", region="us-west-1")
52
52
  print(f"✅ Environment created with instance ID: {env.instance_id}")
53
53
 
54
54
  # Execute a simple action
@@ -85,7 +85,7 @@ async def main():
85
85
  # 3. List running instances
86
86
  print("\n🏃 Listing running instances...")
87
87
  try:
88
- instances = await fleet.env.list_instances(status="running")
88
+ instances = await fleet.manager.list_instances(status="running")
89
89
  if instances:
90
90
  print(f"Found {len(instances)} running instances:")
91
91
  for instance in instances:
@@ -99,13 +99,13 @@ async def main():
99
99
  print("\n🔗 Connecting to existing instance...")
100
100
  try:
101
101
  # Only get running instances
102
- running_instances = await fleet.env.list_instances(status="running")
102
+ running_instances = await fleet.manager.list_instances(status="running")
103
103
  if running_instances:
104
104
  # Find a running instance that's not the one we just created/deleted
105
105
  target_instance = running_instances[0]
106
106
  print(f"Connecting to running instance: {target_instance.instance_id}")
107
107
 
108
- env = await fleet.env.get(target_instance.instance_id)
108
+ env = await fleet.manager.get(target_instance.instance_id)
109
109
  print(f"✅ Connected to instance: {env.instance_id}")
110
110
 
111
111
  # Execute an action on the existing instance
@@ -21,7 +21,7 @@ from .exceptions import (
21
21
  FleetConfigurationError,
22
22
  )
23
23
  from .client import Fleet, AsyncFleet, InstanceRequest
24
- from .env import (
24
+ from .manager import (
25
25
  ResetRequest,
26
26
  ResetResponse,
27
27
  CDPDescribeResponse,
@@ -29,6 +29,8 @@ from .env import (
29
29
  ChromeStartResponse,
30
30
  ChromeStatusResponse,
31
31
  )
32
+ from .verifiers import *
33
+ from . import env
32
34
 
33
35
  __version__ = "0.1.1"
34
36
  __all__ = [
@@ -4,7 +4,7 @@ from typing import Dict, Any, Optional
4
4
  from .models import InstanceResponse
5
5
 
6
6
 
7
- class InstanceBase(InstanceResponse):
7
+ class EnvironmentBase(InstanceResponse):
8
8
  @property
9
9
  def manager_url(self) -> str:
10
10
  return f"{self.urls.manager.api}"