fleet-python 0.2.2__tar.gz → 0.2.4__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 (60) hide show
  1. {fleet_python-0.2.2/fleet_python.egg-info → fleet_python-0.2.4}/PKG-INFO +4 -1
  2. fleet_python-0.2.4/examples/dsl_example.py +128 -0
  3. {fleet_python-0.2.2 → fleet_python-0.2.4}/examples/example.py +2 -2
  4. fleet_python-0.2.4/examples/json_tasks_example.py +82 -0
  5. fleet_python-0.2.4/examples/nova_act_example.py +29 -0
  6. fleet_python-0.2.4/examples/openai_example.py +226 -0
  7. fleet_python-0.2.4/examples/openai_simple_example.py +60 -0
  8. {fleet_python-0.2.2 → fleet_python-0.2.4}/examples/quickstart.py +5 -5
  9. {fleet_python-0.2.2 → fleet_python-0.2.4}/fleet/__init__.py +32 -3
  10. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/base.py +1 -24
  11. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/client.py +19 -72
  12. fleet_python-0.2.4/fleet/_async/env/__init__.py +0 -0
  13. fleet_python-0.2.4/fleet/_async/env/client.py +15 -0
  14. {fleet_python-0.2.2/fleet/manager → fleet_python-0.2.4/fleet/_async/instance}/__init__.py +4 -2
  15. {fleet_python-0.2.2/fleet/manager → fleet_python-0.2.4/fleet/_async/instance}/base.py +1 -24
  16. {fleet_python-0.2.2/fleet/manager → fleet_python-0.2.4/fleet/_async/instance}/client.py +44 -24
  17. {fleet_python-0.2.2/fleet/manager → fleet_python-0.2.4/fleet/_async/instance}/models.py +13 -0
  18. fleet_python-0.2.4/fleet/_async/playwright.py +291 -0
  19. fleet_python-0.2.4/fleet/_async/resources/__init__.py +0 -0
  20. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/resources/base.py +1 -1
  21. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/resources/browser.py +6 -9
  22. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/resources/sqlite.py +3 -3
  23. fleet_python-0.2.4/fleet/base.py +51 -0
  24. fleet_python-0.2.4/fleet/client.py +133 -0
  25. fleet_python-0.2.4/fleet/env/__init__.py +15 -0
  26. fleet_python-0.2.4/fleet/env/client.py +15 -0
  27. fleet_python-0.2.4/fleet/exceptions.py +73 -0
  28. fleet_python-0.2.4/fleet/instance/__init__.py +26 -0
  29. fleet_python-0.2.4/fleet/instance/base.py +37 -0
  30. fleet_python-0.2.4/fleet/instance/client.py +278 -0
  31. fleet_python-0.2.4/fleet/instance/models.py +141 -0
  32. fleet_python-0.2.4/fleet/models.py +109 -0
  33. fleet_python-0.2.4/fleet/playwright.py +289 -0
  34. fleet_python-0.2.4/fleet/resources/__init__.py +0 -0
  35. fleet_python-0.2.4/fleet/resources/base.py +26 -0
  36. fleet_python-0.2.4/fleet/resources/browser.py +41 -0
  37. fleet_python-0.2.4/fleet/resources/sqlite.py +41 -0
  38. fleet_python-0.2.4/fleet/verifiers/__init__.py +11 -0
  39. fleet_python-0.2.4/fleet/verifiers/code.py +1 -0
  40. fleet_python-0.2.2/fleet/verifiers/database_snapshot.py → fleet_python-0.2.4/fleet/verifiers/db.py +62 -22
  41. {fleet_python-0.2.2 → fleet_python-0.2.4}/fleet/verifiers/sql_differ.py +1 -1
  42. {fleet_python-0.2.2 → fleet_python-0.2.4/fleet_python.egg-info}/PKG-INFO +4 -1
  43. fleet_python-0.2.4/fleet_python.egg-info/SOURCES.txt +51 -0
  44. {fleet_python-0.2.2 → fleet_python-0.2.4}/fleet_python.egg-info/requires.txt +4 -0
  45. {fleet_python-0.2.2 → fleet_python-0.2.4}/pyproject.toml +26 -2
  46. fleet_python-0.2.4/scripts/unasync.py +28 -0
  47. fleet_python-0.2.2/examples/dsl_example.py +0 -112
  48. fleet_python-0.2.2/examples/nova_act_example.py +0 -180
  49. fleet_python-0.2.2/examples/openai_example.py +0 -448
  50. fleet_python-0.2.2/fleet/env/__init__.py +0 -3
  51. fleet_python-0.2.2/fleet/env/client.py +0 -15
  52. fleet_python-0.2.2/fleet/verifiers/__init__.py +0 -4
  53. fleet_python-0.2.2/fleet_python.egg-info/SOURCES.txt +0 -30
  54. {fleet_python-0.2.2 → fleet_python-0.2.4}/LICENSE +0 -0
  55. {fleet_python-0.2.2 → fleet_python-0.2.4}/README.md +0 -0
  56. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/exceptions.py +0 -0
  57. {fleet_python-0.2.2/fleet → fleet_python-0.2.4/fleet/_async}/models.py +0 -0
  58. {fleet_python-0.2.2 → fleet_python-0.2.4}/fleet_python.egg-info/dependency_links.txt +0 -0
  59. {fleet_python-0.2.2 → fleet_python-0.2.4}/fleet_python.egg-info/top_level.txt +0 -0
  60. {fleet_python-0.2.2 → fleet_python-0.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.2
3
+ Version: 0.2.4
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,9 @@ 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
+ Requires-Dist: unasync>=0.6.0; extra == "dev"
35
+ Provides-Extra: playwright
36
+ Requires-Dist: playwright>=1.40.0; extra == "playwright"
34
37
  Dynamic: license-file
35
38
 
36
39
  # Fleet SDK
@@ -0,0 +1,128 @@
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_async("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
+ print("RESOURCES", await env.resources())
103
+ db = env.db()
104
+ insert_result = await db.exec(insert_query)
105
+ print(f"Insert result: {insert_result}")
106
+
107
+ # Verify the insertion
108
+ print("\nVerifying insertion...")
109
+ query_result = await db.query("SELECT * FROM entries WHERE id = 32302")
110
+ print(f"Query result: {query_result}")
111
+
112
+ # Run verifier after insertion (should succeed)
113
+ print("\nRunning verifier after insertion...")
114
+ response = await env.verify(validate_new_deal_creation)
115
+ print(f"Success: {response.success}")
116
+ print(f"Result: {response.result}")
117
+ print(f"Error: {response.error}")
118
+ print(f"Message: {response.message}")
119
+
120
+ finally:
121
+ # Delete the instance
122
+ print("\nDeleting instance...")
123
+ await env.close()
124
+ print("Instance deleted.")
125
+
126
+
127
+ if __name__ == "__main__":
128
+ asyncio.run(main())
@@ -6,11 +6,11 @@ import fleet as flt
6
6
 
7
7
 
8
8
  async def main():
9
- environments = await flt.env.list_envs()
9
+ environments = await flt.env.list_envs_async()
10
10
  print("Environments:", len(environments))
11
11
 
12
12
  # Create a new instance
13
- env = await flt.env.make("hubspot:v1.2.7")
13
+ env = await flt.env.make_async("hubspot:v1.2.7")
14
14
  print("New Instance:", env.instance_id)
15
15
 
16
16
  response = await env.reset(seed=42)
@@ -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_async("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_async("hubspot:v1.2.7")
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,226 @@
1
+ from openai import OpenAI
2
+ import fleet as flt
3
+ import json
4
+ from typing import Callable
5
+
6
+
7
+ client = OpenAI()
8
+
9
+
10
+ def sanitize_message(msg: dict) -> dict:
11
+ """Return a copy of the message with image_url omitted for computer_call_output messages."""
12
+ if msg.get("type") == "computer_call_output":
13
+ output = msg.get("output", {})
14
+ if isinstance(output, dict):
15
+ sanitized = msg.copy()
16
+ sanitized["output"] = {**output, "image_url": "[omitted]"}
17
+ return sanitized
18
+ return msg
19
+
20
+
21
+ class Agent:
22
+ def __init__(
23
+ self,
24
+ browser,
25
+ model="computer-use-preview",
26
+ tools: list[dict] = [],
27
+ acknowledge_safety_check_callback: Callable = lambda: False,
28
+ ):
29
+ self.model = model
30
+ self.computer = browser
31
+ self.tools = tools
32
+ self.print_steps = True
33
+ self.debug = False
34
+ self.show_images = False
35
+ self.acknowledge_safety_check_callback = acknowledge_safety_check_callback
36
+
37
+ if browser:
38
+ dimensions = browser.get_dimensions()
39
+ self.tools += [
40
+ {
41
+ "type": "computer-preview",
42
+ "display_width": dimensions[0],
43
+ "display_height": dimensions[1],
44
+ "environment": browser.get_environment(),
45
+ },
46
+ ]
47
+
48
+ def debug_print(self, *args):
49
+ if self.debug:
50
+ print(*args)
51
+
52
+ def handle_item(self, item):
53
+ """Handle each item; may cause a computer action + screenshot."""
54
+ if self.debug:
55
+ print(f"Handling item of type: {item.get('type')}")
56
+
57
+ if item["type"] == "message":
58
+ if self.print_steps:
59
+ print(item["content"][0]["text"])
60
+
61
+ if item["type"] == "function_call":
62
+ name, args = item["name"], json.loads(item["arguments"])
63
+ if self.print_steps:
64
+ print(f"{name}({args})")
65
+
66
+ if hasattr(self.computer, name): # if function exists on computer, call it
67
+ method = getattr(self.computer, name)
68
+ method(**args)
69
+ return [
70
+ {
71
+ "type": "function_call_output",
72
+ "call_id": item["call_id"],
73
+ "output": "success", # hard-coded output for demo
74
+ }
75
+ ]
76
+
77
+ if item["type"] == "computer_call":
78
+ action = item["action"]
79
+ action_type = action["type"]
80
+ action_args = {k: v for k, v in action.items() if k != "type"}
81
+ if self.print_steps:
82
+ print(f"{action_type}({action_args})")
83
+
84
+ method = getattr(self.computer, action_type)
85
+ method(**action_args)
86
+
87
+ screenshot_base64 = self.computer.screenshot()
88
+
89
+ # if user doesn't ack all safety checks exit with error
90
+ pending_checks = item.get("pending_safety_checks", [])
91
+ for check in pending_checks:
92
+ message = check["message"]
93
+ if not self.acknowledge_safety_check_callback(message):
94
+ raise ValueError(
95
+ f"Safety check failed: {message}. Cannot continue with unacknowledged safety checks."
96
+ )
97
+
98
+ call_output = {
99
+ "type": "computer_call_output",
100
+ "call_id": item["call_id"],
101
+ "acknowledged_safety_checks": pending_checks,
102
+ "output": {
103
+ "type": "input_image",
104
+ "image_url": f"data:image/png;base64,{screenshot_base64}",
105
+ },
106
+ }
107
+
108
+ # additional URL safety checks for browser environments
109
+ if self.computer.get_environment() == "browser":
110
+ current_url = self.computer.get_current_url()
111
+ call_output["output"]["current_url"] = current_url
112
+
113
+ return [call_output]
114
+ return []
115
+
116
+ def run_full_turn(
117
+ self, input_items, print_steps=True, debug=False, show_images=False
118
+ ):
119
+ self.print_steps = print_steps
120
+ self.debug = debug
121
+ self.show_images = show_images
122
+ new_items = []
123
+
124
+ # keep looping until we get a final response
125
+ while new_items[-1].get("role") != "assistant" if new_items else True:
126
+ self.debug_print([sanitize_message(msg) for msg in input_items + new_items])
127
+
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(
137
+ model=self.model,
138
+ input=clean_input,
139
+ tools=self.tools,
140
+ truncation="auto",
141
+ )
142
+
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"):
159
+ if self.debug:
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")
163
+ raise ValueError(f"API Error: {error_msg}")
164
+ else:
165
+ raise ValueError("No output from model")
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
182
+
183
+ return new_items
184
+
185
+
186
+ tools = []
187
+
188
+
189
+ def main():
190
+ # Create a Fleet environment instance
191
+ instance = flt.env.make("hubspot")
192
+
193
+ # Create the Playwright wrapper
194
+ browser = flt.FleetPlaywrightWrapper(instance)
195
+ browser.start()
196
+
197
+ try:
198
+ agent = Agent(browser, model="computer-use-preview", tools=[])
199
+ items = [
200
+ {
201
+ "role": "developer",
202
+ "content": "You have access to a clone of Hubspot. You can use the computer to navigate the browser and perform actions.",
203
+ }
204
+ ]
205
+
206
+ while True:
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()
223
+
224
+
225
+ if __name__ == "__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()
@@ -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
@@ -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")