fleet-python 0.2.2__py3-none-any.whl → 0.2.4__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.

Files changed (47) hide show
  1. examples/dsl_example.py +108 -92
  2. examples/example.py +2 -2
  3. examples/json_tasks_example.py +82 -0
  4. examples/nova_act_example.py +18 -169
  5. examples/openai_example.py +89 -311
  6. examples/openai_simple_example.py +60 -0
  7. examples/quickstart.py +5 -5
  8. fleet/__init__.py +32 -3
  9. fleet/_async/base.py +51 -0
  10. fleet/_async/client.py +133 -0
  11. fleet/_async/env/__init__.py +0 -0
  12. fleet/_async/env/client.py +15 -0
  13. fleet/_async/exceptions.py +73 -0
  14. fleet/{manager → _async/instance}/__init__.py +4 -2
  15. fleet/{manager → _async/instance}/base.py +1 -24
  16. fleet/{manager → _async/instance}/client.py +44 -24
  17. fleet/{manager → _async/instance}/models.py +13 -0
  18. fleet/_async/models.py +109 -0
  19. fleet/_async/playwright.py +291 -0
  20. fleet/_async/resources/__init__.py +0 -0
  21. fleet/_async/resources/base.py +26 -0
  22. fleet/_async/resources/browser.py +41 -0
  23. fleet/_async/resources/sqlite.py +41 -0
  24. fleet/base.py +1 -24
  25. fleet/client.py +42 -95
  26. fleet/env/__init__.py +13 -1
  27. fleet/env/client.py +7 -7
  28. fleet/instance/__init__.py +26 -0
  29. fleet/instance/base.py +37 -0
  30. fleet/instance/client.py +278 -0
  31. fleet/instance/models.py +141 -0
  32. fleet/playwright.py +289 -0
  33. fleet/resources/__init__.py +0 -0
  34. fleet/resources/base.py +1 -1
  35. fleet/resources/browser.py +20 -23
  36. fleet/resources/sqlite.py +13 -13
  37. fleet/verifiers/__init__.py +10 -3
  38. fleet/verifiers/code.py +1 -0
  39. fleet/verifiers/{database_snapshot.py → db.py} +62 -22
  40. fleet/verifiers/sql_differ.py +1 -1
  41. {fleet_python-0.2.2.dist-info → fleet_python-0.2.4.dist-info}/METADATA +4 -1
  42. fleet_python-0.2.4.dist-info/RECORD +48 -0
  43. {fleet_python-0.2.2.dist-info → fleet_python-0.2.4.dist-info}/top_level.txt +1 -0
  44. scripts/unasync.py +28 -0
  45. fleet_python-0.2.2.dist-info/RECORD +0 -27
  46. {fleet_python-0.2.2.dist-info → fleet_python-0.2.4.dist-info}/WHEEL +0 -0
  47. {fleet_python-0.2.2.dist-info → fleet_python-0.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,10 @@
1
- import base64
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
1
+ from openai import OpenAI
11
2
  import fleet as flt
3
+ import json
4
+ from typing import Callable
5
+
6
+
7
+ client = OpenAI()
12
8
 
13
9
 
14
10
  def sanitize_message(msg: dict) -> dict:
@@ -22,292 +18,42 @@ def sanitize_message(msg: dict) -> dict:
22
18
  return msg
23
19
 
24
20
 
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
-
63
-
64
- # Optional: key mapping if your model uses "CUA" style keys
65
- CUA_KEY_TO_PLAYWRIGHT_KEY = {
66
- "/": "Divide",
67
- "\\": "Backslash",
68
- "alt": "Alt",
69
- "arrowdown": "ArrowDown",
70
- "arrowleft": "ArrowLeft",
71
- "arrowright": "ArrowRight",
72
- "arrowup": "ArrowUp",
73
- "backspace": "Backspace",
74
- "capslock": "CapsLock",
75
- "cmd": "Meta",
76
- "ctrl": "Control",
77
- "delete": "Delete",
78
- "end": "End",
79
- "enter": "Enter",
80
- "esc": "Escape",
81
- "home": "Home",
82
- "insert": "Insert",
83
- "option": "Alt",
84
- "pagedown": "PageDown",
85
- "pageup": "PageUp",
86
- "shift": "Shift",
87
- "space": " ",
88
- "super": "Meta",
89
- "tab": "Tab",
90
- "win": "Meta",
91
- }
92
-
93
-
94
- class BasePlaywrightComputer:
95
- """
96
- Abstract base for Playwright-based computers:
97
-
98
- - Subclasses override `_get_browser_and_page()` to do local or remote connection,
99
- returning (Browser, Page).
100
- - This base class handles context creation (`__enter__`/`__exit__`),
101
- plus standard "Computer" actions like click, scroll, etc.
102
- - We also have extra browser actions: `goto(url)` and `back()`.
103
- """
104
-
105
- def get_environment(self):
106
- return "browser"
107
-
108
- def get_dimensions(self):
109
- return (1920, 1080)
110
-
111
- def __init__(self):
112
- self._playwright = None
113
- self._browser: Browser | None = None
114
- self._page: Page | None = None
115
-
116
- async def __aenter__(self):
117
- # Start Playwright and call the subclass hook for getting browser/page
118
- self._playwright = await async_playwright().start()
119
- self._browser, self._page = await self._get_browser_and_page()
120
-
121
- # Set up network interception to flag URLs matching domains in BLOCKED_DOMAINS
122
- async def handle_route(route, request):
123
- await route.continue_()
124
-
125
- await self._page.route("**/*", handle_route)
126
-
127
- return self
128
-
129
- async def __aexit__(self, exc_type, exc_val, exc_tb):
130
- # if self._browser:
131
- # await self._browser.close()
132
- if self._playwright:
133
- await self._playwright.stop()
134
-
135
- def get_current_url(self) -> str:
136
- return self._page.url
137
-
138
- # --- Common "Computer" actions ---
139
- async def screenshot(self) -> str:
140
- """Capture only the viewport (not full_page)."""
141
- png_bytes = await self._page.screenshot(full_page=False)
142
- return base64.b64encode(png_bytes).decode("utf-8")
143
-
144
- async def click(self, x: int, y: int, button: str = "left") -> None:
145
- if button == "back":
146
- await self.back()
147
- elif button == "forward":
148
- await self.forward()
149
- elif button == "wheel":
150
- await self._page.mouse.wheel(x, y)
151
- else:
152
- button_mapping = {"left": "left", "right": "right"}
153
- button_type = button_mapping.get(button, "left")
154
- await self._page.mouse.click(x, y, button=button_type)
155
-
156
- async def double_click(self, x: int, y: int) -> None:
157
- await self._page.mouse.dblclick(x, y)
158
-
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})")
162
-
163
- async def type(self, text: str) -> None:
164
- await self._page.keyboard.type(text)
165
-
166
- async def wait(self, ms: int = 1000) -> None:
167
- await asyncio.sleep(ms / 1000)
168
-
169
- async def move(self, x: int, y: int) -> None:
170
- await self._page.mouse.move(x, y)
171
-
172
- async def keypress(self, keys: List[str]) -> None:
173
- mapped_keys = [CUA_KEY_TO_PLAYWRIGHT_KEY.get(key.lower(), key) for key in keys]
174
- for key in mapped_keys:
175
- await self._page.keyboard.down(key)
176
- for key in reversed(mapped_keys):
177
- await self._page.keyboard.up(key)
178
-
179
- async def drag(self, path: List[Dict[str, int]]) -> None:
180
- if not path:
181
- return
182
- await self._page.mouse.move(path[0]["x"], path[0]["y"])
183
- await self._page.mouse.down()
184
- for point in path[1:]:
185
- await self._page.mouse.move(point["x"], point["y"])
186
- await self._page.mouse.up()
187
-
188
- # --- Extra browser-oriented actions ---
189
- async def goto(self, url: str) -> None:
190
- try:
191
- return await self._page.goto(url)
192
- except Exception as e:
193
- print(f"Error navigating to {url}: {e}")
194
-
195
- async def back(self) -> None:
196
- return await self._page.go_back()
197
-
198
- async def forward(self) -> None:
199
- return await self._page.go_forward()
200
-
201
- # --- Subclass hook ---
202
- async def _get_browser_and_page(self) -> tuple[Browser, Page]:
203
- """Subclasses must implement, returning (Browser, Page)."""
204
- raise NotImplementedError
205
-
206
-
207
- class FleetPlaywrightBrowser(BasePlaywrightComputer):
208
- """Launches a local Chromium instance using Playwright."""
209
-
210
- def __init__(
211
- self,
212
- fleet: flt.AsyncFleet,
213
- env_key: str,
214
- version: Optional[str] = None,
215
- headless: bool = False,
216
- ):
217
- super().__init__()
218
- self.fleet = fleet
219
- self.env_key = env_key
220
- self.version = version
221
- self.headless = headless
222
-
223
- async def _get_browser_and_page(self) -> tuple[Browser, Page]:
224
- width, height = self.get_dimensions()
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)
230
- )
231
-
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)
241
-
242
- # Add event listeners for page creation and closure
243
- context = browser.contexts[0]
244
- context.on("page", self._handle_new_page)
245
-
246
- page = context.pages[0]
247
- await page.set_viewport_size({"width": width, "height": height})
248
- page.on("close", self._handle_page_close)
249
-
250
- return browser, page
251
-
252
- def _handle_new_page(self, page: Page):
253
- """Handle the creation of a new page."""
254
- print("New page created")
255
- self._page = page
256
- page.on("close", self._handle_page_close)
257
-
258
- def _handle_page_close(self, page: Page):
259
- """Handle the closure of a page."""
260
- print("Page closed")
261
- if self._page == page:
262
- if self._browser.contexts[0].pages:
263
- self._page = self._browser.contexts[0].pages[-1]
264
- else:
265
- print("Warning: All pages have been closed.")
266
- self._page = None
267
-
268
-
269
21
  class Agent:
270
- """
271
- A sample agent class that can be used to interact with a computer.
272
-
273
- (See simple_cua_loop.py for a simple example without an agent.)
274
- """
275
-
276
22
  def __init__(
277
23
  self,
24
+ browser,
278
25
  model="computer-use-preview",
279
- computer: FleetPlaywrightBrowser = None,
280
26
  tools: list[dict] = [],
281
27
  acknowledge_safety_check_callback: Callable = lambda: False,
282
28
  ):
283
29
  self.model = model
284
- self.computer = computer
30
+ self.computer = browser
285
31
  self.tools = tools
286
32
  self.print_steps = True
287
33
  self.debug = False
288
34
  self.show_images = False
289
35
  self.acknowledge_safety_check_callback = acknowledge_safety_check_callback
290
36
 
291
- if computer:
292
- dimensions = computer.get_dimensions()
37
+ if browser:
38
+ dimensions = browser.get_dimensions()
293
39
  self.tools += [
294
40
  {
295
41
  "type": "computer-preview",
296
42
  "display_width": dimensions[0],
297
43
  "display_height": dimensions[1],
298
- "environment": computer.get_environment(),
44
+ "environment": browser.get_environment(),
299
45
  },
300
46
  ]
301
47
 
302
48
  def debug_print(self, *args):
303
49
  if self.debug:
304
- pp(*args)
50
+ print(*args)
305
51
 
306
- async def handle_item(self, item):
52
+ def handle_item(self, item):
307
53
  """Handle each item; may cause a computer action + screenshot."""
308
54
  if self.debug:
309
55
  print(f"Handling item of type: {item.get('type')}")
310
-
56
+
311
57
  if item["type"] == "message":
312
58
  if self.print_steps:
313
59
  print(item["content"][0]["text"])
@@ -319,7 +65,7 @@ class Agent:
319
65
 
320
66
  if hasattr(self.computer, name): # if function exists on computer, call it
321
67
  method = getattr(self.computer, name)
322
- await method(**args)
68
+ method(**args)
323
69
  return [
324
70
  {
325
71
  "type": "function_call_output",
@@ -336,11 +82,9 @@ class Agent:
336
82
  print(f"{action_type}({action_args})")
337
83
 
338
84
  method = getattr(self.computer, action_type)
339
- await method(**action_args)
85
+ method(**action_args)
340
86
 
341
- screenshot_base64 = await self.computer.screenshot()
342
- if self.show_images:
343
- show_image(screenshot_base64)
87
+ screenshot_base64 = self.computer.screenshot()
344
88
 
345
89
  # if user doesn't ack all safety checks exit with error
346
90
  pending_checks = item.get("pending_safety_checks", [])
@@ -369,7 +113,7 @@ class Agent:
369
113
  return [call_output]
370
114
  return []
371
115
 
372
- async def run_full_turn(
116
+ def run_full_turn(
373
117
  self, input_items, print_steps=True, debug=False, show_images=False
374
118
  ):
375
119
  self.print_steps = print_steps
@@ -381,38 +125,60 @@ class Agent:
381
125
  while new_items[-1].get("role") != "assistant" if new_items else True:
382
126
  self.debug_print([sanitize_message(msg) for msg in input_items + new_items])
383
127
 
384
- response = await create_response(
128
+ # The Responses API rejects unknown keys (e.g. `status`, `encrypted_content`).
129
+ # Strip them from every item before sending.
130
+ def _clean_item(msg: dict) -> dict:
131
+ unwanted_keys = {"status", "encrypted_content"}
132
+ return {k: v for k, v in msg.items() if k not in unwanted_keys}
133
+
134
+ clean_input = [_clean_item(m) for m in (input_items + new_items)]
135
+
136
+ response = client.responses.create(
385
137
  model=self.model,
386
- input=input_items + new_items,
138
+ input=clean_input,
387
139
  tools=self.tools,
388
140
  truncation="auto",
389
141
  )
390
- self.debug_print(response)
391
142
 
392
- if "output" not in response:
143
+ # The OpenAI SDK returns a Pydantic model object, not a plain dict.
144
+ # Convert it to a standard Python dict so the rest of the code can
145
+ # remain unchanged from the previous implementation.
146
+ response_dict = (
147
+ response.model_dump() # pydantic v2
148
+ if hasattr(response, "model_dump")
149
+ else (
150
+ response.to_dict_recursive()
151
+ if hasattr(response, "to_dict_recursive")
152
+ else dict(response)
153
+ )
154
+ )
155
+ self.debug_print(response_dict)
156
+
157
+ # Guard against missing/empty output in the response
158
+ if not response_dict.get("output"):
393
159
  if self.debug:
394
- print("Full response:", response)
395
- if "error" in response:
396
- error_msg = response["error"].get("message", "Unknown error")
160
+ print("Full response:", response_dict)
161
+ if response_dict.get("error") is not None:
162
+ error_msg = response_dict["error"].get("message", "Unknown error")
397
163
  raise ValueError(f"API Error: {error_msg}")
398
164
  else:
399
165
  raise ValueError("No output from model")
400
- else:
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.
404
- for item in response["output"]:
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
166
+
167
+ # Append each item from the model output to conversation history
168
+ # in the exact order we received them, **without filtering** so that
169
+ # required pairs such as reasoning → computer_call are preserved.
170
+ for item in response_dict["output"]:
171
+ # First, record the original item itself.
172
+ new_items.append(item)
173
+
174
+ # Next, perform any local side-effects (browser actions, etc.).
175
+ handled_items = self.handle_item(item)
176
+
177
+ # If the handler generated additional items (e.g. computer_call_output)
178
+ # we append those *immediately* so the order remains:
179
+ # reasoning → computer_call → computer_call_output
180
+ if handled_items:
181
+ new_items += handled_items
416
182
 
417
183
  return new_items
418
184
 
@@ -420,29 +186,41 @@ class Agent:
420
186
  tools = []
421
187
 
422
188
 
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)
189
+ def main():
190
+ # Create a Fleet environment instance
191
+ instance = flt.env.make("hubspot")
427
192
 
193
+ # Create the Playwright wrapper
194
+ browser = flt.FleetPlaywrightWrapper(instance)
195
+ browser.start()
428
196
 
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)
197
+ try:
198
+ agent = Agent(browser, model="computer-use-preview", tools=[])
434
199
  items = [
435
200
  {
436
201
  "role": "developer",
437
202
  "content": "You have access to a clone of Hubspot. You can use the computer to navigate the browser and perform actions.",
438
203
  }
439
204
  ]
205
+
440
206
  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
207
+ try:
208
+ user_input = input("> ")
209
+ items.append({"role": "user", "content": user_input})
210
+ output_items = agent.run_full_turn(
211
+ items, show_images=False, debug=False
212
+ )
213
+ items += output_items
214
+ except (EOFError, KeyboardInterrupt):
215
+ print("\nShutting down...")
216
+ break
217
+ except Exception as e:
218
+ print(f"Error during interaction: {e}")
219
+ # Continue the loop for other errors
220
+ finally:
221
+ browser.close()
222
+ instance.close()
445
223
 
446
224
 
447
225
  if __name__ == "__main__":
448
- asyncio.run(main())
226
+ main()
@@ -0,0 +1,60 @@
1
+ from openai import OpenAI
2
+ import fleet as flt
3
+
4
+ client = OpenAI()
5
+
6
+
7
+ def main():
8
+ instance = flt.env.make("hubspot")
9
+
10
+ browser = flt.FleetPlaywrightWrapper(instance)
11
+ browser.start()
12
+
13
+ try:
14
+ width, height = browser.get_dimensions()
15
+ tools = [
16
+ {
17
+ "type": "computer-preview",
18
+ "display_width": width,
19
+ "display_height": height,
20
+ "environment": browser.get_environment(),
21
+ }
22
+ ]
23
+
24
+ response = client.responses.create(
25
+ model="computer-use-preview",
26
+ input=[
27
+ {
28
+ "role": "developer",
29
+ "content": "Create a HubSpot deal",
30
+ }
31
+ ],
32
+ tools=tools,
33
+ truncation="auto",
34
+ )
35
+
36
+ if len(response.output) != 0:
37
+ if response.output[0].type == "message":
38
+ print(response.output[0].content[0].text)
39
+
40
+ if response.output[0].type == "computer_call":
41
+ action = response.output[0].action
42
+ if action.type == "screenshot":
43
+ screenshot_base64 = browser.screenshot()
44
+ result = {
45
+ "type": "input_image",
46
+ "image_url": f"data:image/png;base64,{screenshot_base64}",
47
+ "current_url": browser.get_current_url(),
48
+ }
49
+ else:
50
+ result = browser.execute_computer_action(action)
51
+
52
+ print("Computer action result:")
53
+ print(result)
54
+ finally:
55
+ browser.close()
56
+ instance.close()
57
+
58
+
59
+ if __name__ == "__main__":
60
+ main()
examples/quickstart.py CHANGED
@@ -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.manager.list_envs()
38
+ environments = await fleet.instance.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.manager.make("fira:v1.2.5", region="us-west-1")
51
+ env = await fleet.instance.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.manager.list_instances(status="running")
88
+ instances = await fleet.instance.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.manager.list_instances(status="running")
102
+ running_instances = await fleet.instance.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.manager.get(target_instance.instance_id)
108
+ env = await fleet.instance.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
fleet/__init__.py CHANGED
@@ -20,8 +20,12 @@ from .exceptions import (
20
20
  FleetTimeoutError,
21
21
  FleetConfigurationError,
22
22
  )
23
- from .client import Fleet, AsyncFleet, InstanceRequest
24
- from .manager import (
23
+ from .client import Fleet, Environment
24
+ from ._async.client import AsyncFleet, AsyncEnvironment
25
+ from .models import InstanceRequest
26
+ from .instance import (
27
+ InstanceClient,
28
+ AsyncInstanceClient,
25
29
  ResetRequest,
26
30
  ResetResponse,
27
31
  CDPDescribeResponse,
@@ -29,9 +33,22 @@ from .manager import (
29
33
  ChromeStartResponse,
30
34
  ChromeStatusResponse,
31
35
  )
32
- from .verifiers import *
36
+ from .verifiers import (
37
+ DatabaseSnapshot,
38
+ IgnoreConfig,
39
+ SnapshotDiff,
40
+ TASK_SUCCESSFUL_SCORE,
41
+ )
33
42
  from . import env
34
43
 
44
+ # Optional playwright integration
45
+ try:
46
+ from .playwright import FleetPlaywrightWrapper
47
+ _PLAYWRIGHT_AVAILABLE = True
48
+ except ImportError:
49
+ FleetPlaywrightWrapper = None
50
+ _PLAYWRIGHT_AVAILABLE = False
51
+
35
52
  __version__ = "0.1.1"
36
53
  __all__ = [
37
54
  "env",
@@ -40,7 +57,11 @@ __all__ = [
40
57
  "FleetTimeoutError",
41
58
  "FleetConfigurationError",
42
59
  "Fleet",
60
+ "Environment",
43
61
  "AsyncFleet",
62
+ "AsyncEnvironment",
63
+ "InstanceClient",
64
+ "AsyncInstanceClient",
44
65
  "InstanceRequest",
45
66
  "ResetRequest",
46
67
  "ResetResponse",
@@ -48,4 +69,12 @@ __all__ = [
48
69
  "ChromeStartRequest",
49
70
  "ChromeStartResponse",
50
71
  "ChromeStatusResponse",
72
+ "DatabaseSnapshot",
73
+ "IgnoreConfig",
74
+ "SnapshotDiff",
75
+ "TASK_SUCCESSFUL_SCORE",
51
76
  ]
77
+
78
+ # Add playwright wrapper to exports if available
79
+ if _PLAYWRIGHT_AVAILABLE:
80
+ __all__.append("FleetPlaywrightWrapper")