fleet-python 0.2.1__tar.gz → 0.2.3__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 (39) hide show
  1. {fleet_python-0.2.1/fleet_python.egg-info → fleet_python-0.2.3}/PKG-INFO +3 -1
  2. fleet_python-0.2.3/examples/dsl_example.py +127 -0
  3. fleet_python-0.2.3/examples/example.py +38 -0
  4. fleet_python-0.2.3/examples/json_tasks_example.py +82 -0
  5. fleet_python-0.2.3/examples/nova_act_example.py +29 -0
  6. fleet_python-0.2.3/examples/openai_example.py +233 -0
  7. fleet_python-0.2.3/examples/openai_simple_example.py +61 -0
  8. {fleet_python-0.2.1 → fleet_python-0.2.3}/examples/quickstart.py +5 -5
  9. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/__init__.py +17 -1
  10. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/base.py +1 -1
  11. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/client.py +77 -30
  12. fleet_python-0.2.3/fleet/env/__init__.py +3 -0
  13. fleet_python-0.2.3/fleet/env/client.py +15 -0
  14. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.3/fleet/instance}/__init__.py +6 -3
  15. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.3/fleet/instance}/client.py +44 -8
  16. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.3/fleet/instance}/models.py +13 -0
  17. fleet_python-0.2.3/fleet/playwright.py +291 -0
  18. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/resources/base.py +5 -2
  19. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/resources/browser.py +15 -8
  20. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/resources/sqlite.py +3 -3
  21. fleet_python-0.2.3/fleet/verifiers/__init__.py +16 -0
  22. fleet_python-0.2.3/fleet/verifiers/code.py +132 -0
  23. fleet_python-0.2.3/fleet/verifiers/db.py +706 -0
  24. fleet_python-0.2.3/fleet/verifiers/sql_differ.py +187 -0
  25. {fleet_python-0.2.1 → fleet_python-0.2.3/fleet_python.egg-info}/PKG-INFO +3 -1
  26. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet_python.egg-info/SOURCES.txt +12 -2
  27. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet_python.egg-info/requires.txt +3 -0
  28. {fleet_python-0.2.1 → fleet_python-0.2.3}/pyproject.toml +4 -1
  29. fleet_python-0.2.1/examples/example.py +0 -51
  30. fleet_python-0.2.1/examples/nova_act_example.py +0 -180
  31. fleet_python-0.2.1/examples/openai_example.py +0 -329
  32. {fleet_python-0.2.1 → fleet_python-0.2.3}/LICENSE +0 -0
  33. {fleet_python-0.2.1 → fleet_python-0.2.3}/README.md +0 -0
  34. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/exceptions.py +0 -0
  35. {fleet_python-0.2.1/fleet/env → fleet_python-0.2.3/fleet/instance}/base.py +0 -0
  36. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet/models.py +0 -0
  37. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet_python.egg-info/dependency_links.txt +0 -0
  38. {fleet_python-0.2.1 → fleet_python-0.2.3}/fleet_python.egg-info/top_level.txt +0 -0
  39. {fleet_python-0.2.1 → fleet_python-0.2.3}/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.3
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -31,6 +31,8 @@ Requires-Dist: black>=22.0.0; extra == "dev"
31
31
  Requires-Dist: isort>=5.0.0; extra == "dev"
32
32
  Requires-Dist: mypy>=1.0.0; extra == "dev"
33
33
  Requires-Dist: ruff>=0.1.0; extra == "dev"
34
+ Provides-Extra: playwright
35
+ Requires-Dist: playwright>=1.40.0; extra == "playwright"
34
36
  Dynamic: license-file
35
37
 
36
38
  # Fleet SDK
@@ -0,0 +1,127 @@
1
+ import asyncio
2
+ import fleet as flt
3
+ from fleet.verifiers import DatabaseSnapshot, IgnoreConfig, TASK_SUCCESSFUL_SCORE
4
+
5
+
6
+ def validate_new_deal_creation(
7
+ before: DatabaseSnapshot,
8
+ after: DatabaseSnapshot,
9
+ transcript: str | None = None,
10
+ ) -> int:
11
+ """Validate that a new deal was created"""
12
+
13
+ # Find the new deal entry
14
+ new_deal = after.table("entries").eq("id", 32302).first()
15
+ if not new_deal:
16
+ raise AssertionError("Expected new deal with id 32302 not found")
17
+
18
+ # Verify it's a deal type
19
+ if new_deal["type"] != "deal":
20
+ raise AssertionError(
21
+ f"Expected entry type to be 'deal', got '{new_deal['type']}'"
22
+ )
23
+
24
+ # Verify the deal has a name (should be "testing" based on the diff)
25
+ if not new_deal["name"]:
26
+ raise AssertionError("Expected deal to have a name")
27
+
28
+ # Parse the properties JSON to check basic deal properties
29
+ import json
30
+
31
+ properties = json.loads(new_deal["properties"])
32
+
33
+ # Verify it has basic deal properties
34
+ if "dealstage" not in properties:
35
+ raise AssertionError("Expected deal to have a dealstage property")
36
+
37
+ if "deal_type" not in properties:
38
+ raise AssertionError("Expected deal to have a deal_type property")
39
+
40
+ if "priority" not in properties:
41
+ raise AssertionError("Expected deal to have a priority property")
42
+
43
+ # Configure ignore settings
44
+ ignore_config = IgnoreConfig(
45
+ tables={"pageviews"},
46
+ table_fields={
47
+ "entries": {"createdDate", "lastModifiedDate", "createdAt", "updatedAt"},
48
+ },
49
+ )
50
+
51
+ # Build expected changes
52
+ expected_changes = [
53
+ {
54
+ "table": "entries",
55
+ "pk": 32302,
56
+ "field": None,
57
+ "after": "__added__",
58
+ }
59
+ ]
60
+
61
+ before.diff(after, ignore_config).expect_only(expected_changes)
62
+ return TASK_SUCCESSFUL_SCORE
63
+
64
+
65
+ async def main():
66
+ # Create a new instance
67
+ print("Creating new Hubspot instance...")
68
+ env = await flt.env.make("hubspot:v1.2.7")
69
+ print(f"New Instance: {env.instance_id}")
70
+
71
+ try:
72
+ # Reset the instance
73
+ response = await env.reset(seed=42)
74
+ print(f"Reset response: {response}")
75
+
76
+ # Run verifier before insertion (should fail)
77
+ print("\nRunning verifier before insertion...")
78
+ response = await env.verify(validate_new_deal_creation)
79
+ print(f"Success: {response.success}")
80
+ print(f"Result: {response.result}")
81
+ print(f"Error: {response.error}")
82
+ print(f"Message: {response.message}")
83
+
84
+ # Insert the deal entry
85
+ print("\nInserting deal entry...")
86
+ insert_query = """
87
+ INSERT INTO entries (id, name, type, owner_id, createdDate, lastModifiedDate, createdAt, updatedAt, archivedAt, properties)
88
+ VALUES (
89
+ 32302,
90
+ 'testing',
91
+ 'deal',
92
+ 1,
93
+ '2025-07-10T01:32:01.089Z',
94
+ '2025-07-10T01:32:01.089Z',
95
+ '2025-07-10 01:32:01',
96
+ '2025-07-10 01:32:01',
97
+ NULL,
98
+ '{"amount":null,"closedate":"2025-08-09","close_date":"2025-08-09","dealstage":"appointmentscheduled","pipeline":"default","description":null,"priority":"medium","deal_stage_probability":null,"deal_type":"newbusiness","hs_createdate":"2025-07-10T01:32:01.089Z","hs_object_source":"INTEGRATION","hs_object_source_id":"14696758","hs_object_source_label":"INTEGRATION","hs_is_closed":"false","hs_is_closed_count":"0","hs_is_closed_lost":"false","hs_is_closed_won":"false","hs_is_deal_split":"false","hs_is_open_count":"1","hs_num_associated_active_deal_registrations":"0","hs_num_associated_deal_registrations":"0","hs_num_associated_deal_splits":"0","hs_num_of_associated_line_items":"0","hs_num_target_accounts":"0","hs_number_of_call_engagements":"0","hs_number_of_inbound_calls":"0","hs_number_of_outbound_calls":"0","hs_number_of_overdue_tasks":"0","num_associated_contacts":"0","num_notes":"0","hs_closed_amount":"0","hs_closed_amount_in_home_currency":"0","hs_closed_deal_close_date":"0","hs_closed_deal_create_date":"0","hs_closed_won_count":"0","hs_v2_date_entered_current_stage":"2025-07-10T01:32:01.089Z","hs_v2_time_in_current_stage":"2025-07-10T01:32:01.089Z","hs_duration":"1752111121089","hs_open_deal_create_date":"1752111121089","days_to_close":"29","hs_days_to_close_raw":"29.936098506944443"}'
99
+ )
100
+ """
101
+
102
+ db = env.db()
103
+ insert_result = await db.exec(insert_query)
104
+ print(f"Insert result: {insert_result}")
105
+
106
+ # Verify the insertion
107
+ print("\nVerifying insertion...")
108
+ query_result = await db.query("SELECT * FROM entries WHERE id = 32302")
109
+ print(f"Query result: {query_result}")
110
+
111
+ # Run verifier after insertion (should succeed)
112
+ print("\nRunning verifier after insertion...")
113
+ response = await env.verify(validate_new_deal_creation)
114
+ print(f"Success: {response.success}")
115
+ print(f"Result: {response.result}")
116
+ print(f"Error: {response.error}")
117
+ print(f"Message: {response.message}")
118
+
119
+ finally:
120
+ # Delete the instance
121
+ print("\nDeleting instance...")
122
+ await env.close()
123
+ print("Instance deleted.")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ asyncio.run(main())
@@ -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())
@@ -0,0 +1,82 @@
1
+ import re
2
+ import asyncio
3
+ import argparse
4
+ import json
5
+ from typing import TypedDict, List
6
+ from pathlib import Path
7
+ import fleet as flt
8
+ from nova_act import NovaAct, ActResult
9
+
10
+
11
+ MAX_STEPS = 30
12
+
13
+
14
+ class Problem(TypedDict):
15
+ id: str
16
+ problem: str
17
+ category: str
18
+ difficulty: str
19
+ verifier_func: str
20
+
21
+
22
+ def extract_function_name(function_str: str) -> str | None:
23
+ match = re.search(r"(?:async\s+)?def\s+(\w+)\s*\(", function_str)
24
+ if match:
25
+ return match.group(1)
26
+ raise ValueError(f"No function name found in {function_str}")
27
+
28
+
29
+ async def main():
30
+ parser = argparse.ArgumentParser(
31
+ description="Load and display Jira problems from JSON file"
32
+ )
33
+ parser.add_argument(
34
+ "json_file", type=str, help="Path to the JSON file containing problems"
35
+ )
36
+ args = parser.parse_args()
37
+
38
+ file_path = Path(args.json_file)
39
+ if not file_path.exists():
40
+ raise FileNotFoundError(f"Error: File '{args.json_file}' not found")
41
+
42
+ env = await flt.env.make("fira:v1.2.7")
43
+ print(f"New Instance: {env.urls.app}")
44
+
45
+ successes = 0
46
+
47
+ try:
48
+ with open(args.json_file, "r") as f:
49
+ data = json.load(f)
50
+ problems: List[Problem] = data["problems"]
51
+
52
+ print(f"Loaded {len(problems)} problems from '{args.json_file}'")
53
+
54
+ for i, problem in enumerate(problems):
55
+ print(f"Solving problem {i + 1} of {len(problems)}: {problem['id']}")
56
+ await env.reset()
57
+
58
+ def run_nova() -> ActResult:
59
+ with NovaAct(starting_page=env.urls.app, headless=True) as nova:
60
+ return nova.act(problem["problem"], max_steps=MAX_STEPS)
61
+
62
+ try:
63
+ await asyncio.to_thread(run_nova)
64
+ except Exception as e:
65
+ print(f"Error: {e}")
66
+
67
+ function_name = extract_function_name(problem["verifier_func"])
68
+ print(f"Verifying {function_name} ({problem['id']})...")
69
+ response = await env.verify_raw(problem["verifier_func"], function_name)
70
+ print(response)
71
+ if response.success:
72
+ successes += 1
73
+
74
+ print(f"Successes: {successes}")
75
+ print(f"Total: {len(problems)}")
76
+ print(f"Success rate: {successes / len(problems)}")
77
+ finally:
78
+ await env.close()
79
+
80
+
81
+ if __name__ == "__main__":
82
+ asyncio.run(main())
@@ -0,0 +1,29 @@
1
+ import asyncio
2
+ import fleet as flt
3
+ from nova_act import NovaAct, ActResult
4
+
5
+
6
+ async def main():
7
+ instance = await flt.env.make("hubspot")
8
+ cdp_url = await instance.browser().cdp_url()
9
+
10
+ loop = asyncio.get_event_loop()
11
+
12
+ def run_nova() -> ActResult:
13
+ with NovaAct(
14
+ starting_page=instance.urls.app,
15
+ cdp_endpoint_url=cdp_url,
16
+ preview={"playwright_actuation": True},
17
+ ) as nova:
18
+ future = asyncio.run_coroutine_threadsafe(
19
+ instance.browser().devtools_url(), loop
20
+ )
21
+ print("Devtools URL:", future.result())
22
+ return nova.act("Create a deal")
23
+
24
+ await asyncio.to_thread(run_nova)
25
+ await instance.close()
26
+
27
+
28
+ if __name__ == "__main__":
29
+ asyncio.run(main())
@@ -0,0 +1,233 @@
1
+ import asyncio
2
+ from openai import AsyncOpenAI
3
+ import fleet as flt
4
+ import json
5
+ from typing import Callable
6
+
7
+
8
+ client = AsyncOpenAI()
9
+
10
+
11
+ def sanitize_message(msg: dict) -> dict:
12
+ """Return a copy of the message with image_url omitted for computer_call_output messages."""
13
+ if msg.get("type") == "computer_call_output":
14
+ output = msg.get("output", {})
15
+ if isinstance(output, dict):
16
+ sanitized = msg.copy()
17
+ sanitized["output"] = {**output, "image_url": "[omitted]"}
18
+ return sanitized
19
+ return msg
20
+
21
+
22
+ class Agent:
23
+ def __init__(
24
+ self,
25
+ browser,
26
+ model="computer-use-preview",
27
+ tools: list[dict] = [],
28
+ acknowledge_safety_check_callback: Callable = lambda: False,
29
+ ):
30
+ self.model = model
31
+ self.computer = browser
32
+ self.tools = tools
33
+ self.print_steps = True
34
+ self.debug = False
35
+ self.show_images = False
36
+ self.acknowledge_safety_check_callback = acknowledge_safety_check_callback
37
+
38
+ if browser:
39
+ dimensions = browser.get_dimensions()
40
+ self.tools += [
41
+ {
42
+ "type": "computer-preview",
43
+ "display_width": dimensions[0],
44
+ "display_height": dimensions[1],
45
+ "environment": browser.get_environment(),
46
+ },
47
+ ]
48
+
49
+ def debug_print(self, *args):
50
+ if self.debug:
51
+ print(*args)
52
+
53
+ async def handle_item(self, item):
54
+ """Handle each item; may cause a computer action + screenshot."""
55
+ if self.debug:
56
+ print(f"Handling item of type: {item.get('type')}")
57
+
58
+ if item["type"] == "message":
59
+ if self.print_steps:
60
+ print(item["content"][0]["text"])
61
+
62
+ if item["type"] == "function_call":
63
+ name, args = item["name"], json.loads(item["arguments"])
64
+ if self.print_steps:
65
+ print(f"{name}({args})")
66
+
67
+ if hasattr(self.computer, name): # if function exists on computer, call it
68
+ method = getattr(self.computer, name)
69
+ await method(**args)
70
+ return [
71
+ {
72
+ "type": "function_call_output",
73
+ "call_id": item["call_id"],
74
+ "output": "success", # hard-coded output for demo
75
+ }
76
+ ]
77
+
78
+ if item["type"] == "computer_call":
79
+ action = item["action"]
80
+ action_type = action["type"]
81
+ action_args = {k: v for k, v in action.items() if k != "type"}
82
+ if self.print_steps:
83
+ print(f"{action_type}({action_args})")
84
+
85
+ method = getattr(self.computer, action_type)
86
+ await method(**action_args)
87
+
88
+ screenshot_base64 = await self.computer.screenshot()
89
+
90
+ # if user doesn't ack all safety checks exit with error
91
+ pending_checks = item.get("pending_safety_checks", [])
92
+ for check in pending_checks:
93
+ message = check["message"]
94
+ if not self.acknowledge_safety_check_callback(message):
95
+ raise ValueError(
96
+ f"Safety check failed: {message}. Cannot continue with unacknowledged safety checks."
97
+ )
98
+
99
+ call_output = {
100
+ "type": "computer_call_output",
101
+ "call_id": item["call_id"],
102
+ "acknowledged_safety_checks": pending_checks,
103
+ "output": {
104
+ "type": "input_image",
105
+ "image_url": f"data:image/png;base64,{screenshot_base64}",
106
+ },
107
+ }
108
+
109
+ # additional URL safety checks for browser environments
110
+ if self.computer.get_environment() == "browser":
111
+ current_url = self.computer.get_current_url()
112
+ call_output["output"]["current_url"] = current_url
113
+
114
+ return [call_output]
115
+ return []
116
+
117
+ async def run_full_turn(
118
+ self, input_items, print_steps=True, debug=False, show_images=False
119
+ ):
120
+ self.print_steps = print_steps
121
+ self.debug = debug
122
+ self.show_images = show_images
123
+ new_items = []
124
+
125
+ # keep looping until we get a final response
126
+ while new_items[-1].get("role") != "assistant" if new_items else True:
127
+ self.debug_print([sanitize_message(msg) for msg in input_items + new_items])
128
+
129
+ # The Responses API rejects unknown keys (e.g. `status`, `encrypted_content`).
130
+ # Strip them from every item before sending.
131
+ def _clean_item(msg: dict) -> dict:
132
+ unwanted_keys = {"status", "encrypted_content"}
133
+ return {k: v for k, v in msg.items() if k not in unwanted_keys}
134
+
135
+ clean_input = [_clean_item(m) for m in (input_items + new_items)]
136
+
137
+ response = await client.responses.create(
138
+ model=self.model,
139
+ input=clean_input,
140
+ tools=self.tools,
141
+ truncation="auto",
142
+ )
143
+
144
+ # The OpenAI SDK returns a Pydantic model object, not a plain dict.
145
+ # Convert it to a standard Python dict so the rest of the code can
146
+ # remain unchanged from the previous implementation.
147
+ response_dict = (
148
+ response.model_dump() # pydantic v2
149
+ if hasattr(response, "model_dump")
150
+ else (
151
+ response.to_dict_recursive()
152
+ if hasattr(response, "to_dict_recursive")
153
+ else dict(response)
154
+ )
155
+ )
156
+ self.debug_print(response_dict)
157
+
158
+ # Guard against missing/empty output in the response
159
+ if not response_dict.get("output"):
160
+ if self.debug:
161
+ print("Full response:", response_dict)
162
+ if response_dict.get("error") is not None:
163
+ error_msg = response_dict["error"].get("message", "Unknown error")
164
+ raise ValueError(f"API Error: {error_msg}")
165
+ else:
166
+ raise ValueError("No output from model")
167
+
168
+ # Append each item from the model output to conversation history
169
+ # in the exact order we received them, **without filtering** so that
170
+ # required pairs such as reasoning → computer_call are preserved.
171
+ for item in response_dict["output"]:
172
+ # First, record the original item itself.
173
+ new_items.append(item)
174
+
175
+ # Next, perform any local side-effects (browser actions, etc.).
176
+ handled_items = await self.handle_item(item)
177
+
178
+ # If the handler generated additional items (e.g. computer_call_output)
179
+ # we append those *immediately* so the order remains:
180
+ # reasoning → computer_call → computer_call_output
181
+ if handled_items:
182
+ new_items += handled_items
183
+
184
+ return new_items
185
+
186
+
187
+ tools = []
188
+
189
+
190
+ async def ainput(prompt: str = "") -> str:
191
+ """Async version of input()"""
192
+ loop = asyncio.get_event_loop()
193
+ return await loop.run_in_executor(None, input, prompt)
194
+
195
+
196
+ async def main():
197
+ # Create a Fleet environment instance
198
+ instance = await flt.env.make("hubspot")
199
+
200
+ # Create the Playwright wrapper
201
+ browser = flt.FleetPlaywrightWrapper(instance)
202
+ await browser.start()
203
+
204
+ try:
205
+ agent = Agent(browser, model="computer-use-preview", tools=[])
206
+ items = [
207
+ {
208
+ "role": "developer",
209
+ "content": "You have access to a clone of Hubspot. You can use the computer to navigate the browser and perform actions.",
210
+ }
211
+ ]
212
+
213
+ while True:
214
+ try:
215
+ user_input = await ainput("> ")
216
+ items.append({"role": "user", "content": user_input})
217
+ output_items = await agent.run_full_turn(
218
+ items, show_images=False, debug=False
219
+ )
220
+ items += output_items
221
+ except (EOFError, KeyboardInterrupt):
222
+ print("\nShutting down...")
223
+ break
224
+ except Exception as e:
225
+ print(f"Error during interaction: {e}")
226
+ # Continue the loop for other errors
227
+ finally:
228
+ await browser.close()
229
+ await instance.close()
230
+
231
+
232
+ if __name__ == "__main__":
233
+ asyncio.run(main())
@@ -0,0 +1,61 @@
1
+ import asyncio
2
+ from openai import AsyncOpenAI
3
+ import fleet as flt
4
+
5
+ client = AsyncOpenAI()
6
+
7
+
8
+ async def main():
9
+ instance = await flt.env.make("hubspot")
10
+
11
+ browser = flt.FleetPlaywrightWrapper(instance)
12
+ await browser.start()
13
+
14
+ try:
15
+ width, height = browser.get_dimensions()
16
+ tools = [
17
+ {
18
+ "type": "computer-preview",
19
+ "display_width": width,
20
+ "display_height": height,
21
+ "environment": browser.get_environment(),
22
+ }
23
+ ]
24
+
25
+ response = await client.responses.create(
26
+ model="computer-use-preview",
27
+ input=[
28
+ {
29
+ "role": "developer",
30
+ "content": "Create a HubSpot deal",
31
+ }
32
+ ],
33
+ tools=tools,
34
+ truncation="auto",
35
+ )
36
+
37
+ if len(response.output) != 0:
38
+ if response.output[0].type == "message":
39
+ print(response.output[0].content[0].text)
40
+
41
+ if response.output[0].type == "computer_call":
42
+ action = response.output[0].action
43
+ if action.type == "screenshot":
44
+ screenshot_base64 = await browser.screenshot()
45
+ result = {
46
+ "type": "input_image",
47
+ "image_url": f"data:image/png;base64,{screenshot_base64}",
48
+ "current_url": browser.get_current_url(),
49
+ }
50
+ else:
51
+ result = await browser.execute_computer_action(action)
52
+
53
+ print("Computer action result:")
54
+ print(result)
55
+ finally:
56
+ await browser.close()
57
+ await instance.close()
58
+
59
+
60
+ if __name__ == "__main__":
61
+ 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.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.env.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.env.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.env.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.env.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
@@ -21,7 +21,8 @@ from .exceptions import (
21
21
  FleetConfigurationError,
22
22
  )
23
23
  from .client import Fleet, AsyncFleet, InstanceRequest
24
- from .env import (
24
+ from .instance import (
25
+ AsyncInstanceClient,
25
26
  ResetRequest,
26
27
  ResetResponse,
27
28
  CDPDescribeResponse,
@@ -29,6 +30,16 @@ from .env import (
29
30
  ChromeStartResponse,
30
31
  ChromeStatusResponse,
31
32
  )
33
+ from .verifiers import *
34
+ from . import env
35
+
36
+ # Optional playwright integration
37
+ try:
38
+ from .playwright import FleetPlaywrightWrapper
39
+ _PLAYWRIGHT_AVAILABLE = True
40
+ except ImportError:
41
+ FleetPlaywrightWrapper = None
42
+ _PLAYWRIGHT_AVAILABLE = False
32
43
 
33
44
  __version__ = "0.1.1"
34
45
  __all__ = [
@@ -39,6 +50,7 @@ __all__ = [
39
50
  "FleetConfigurationError",
40
51
  "Fleet",
41
52
  "AsyncFleet",
53
+ "AsyncInstanceClient",
42
54
  "InstanceRequest",
43
55
  "ResetRequest",
44
56
  "ResetResponse",
@@ -47,3 +59,7 @@ __all__ = [
47
59
  "ChromeStartResponse",
48
60
  "ChromeStatusResponse",
49
61
  ]
62
+
63
+ # Add playwright wrapper to exports if available
64
+ if _PLAYWRIGHT_AVAILABLE:
65
+ __all__.append("FleetPlaywrightWrapper")
@@ -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}"