fleet-python 0.2.5__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

examples/dsl_example.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import asyncio
2
2
  import fleet as flt
3
3
  from fleet.verifiers import DatabaseSnapshot, IgnoreConfig, TASK_SUCCESSFUL_SCORE
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
4
7
 
5
8
 
6
9
  def validate_new_deal_creation(
examples/example.py CHANGED
@@ -3,6 +3,9 @@
3
3
 
4
4
  import asyncio
5
5
  import fleet as flt
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
6
9
 
7
10
 
8
11
  async def main():
@@ -11,7 +14,7 @@ async def main():
11
14
 
12
15
  # Create a new instance
13
16
  env = await flt.env.make_async("hubspot:v1.2.7")
14
- print("New Instance:", env.instance_id)
17
+ print(f"New Instance: {env.instance_id} ({env.region})")
15
18
 
16
19
  response = await env.reset(seed=42)
17
20
  print("Reset response:", response)
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ """Example demonstrating browser control with Fleet Manager Client."""
3
+
4
+ import asyncio
5
+ import fleet as flt
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+
11
+ async def main():
12
+ fleet = flt.AsyncFleet()
13
+
14
+ environments = await fleet.list_envs()
15
+ print("Environments:", len(environments))
16
+
17
+ # Create a new instance
18
+ env = await fleet.make("fira")
19
+ print(f"New Instance: {env.instance_id} ({env.region})")
20
+
21
+ response = await env.reset(seed=42)
22
+ print("Reset response:", response)
23
+
24
+ print(await env.resources())
25
+
26
+ sqlite = env.db()
27
+ print("SQLite:", await sqlite.describe())
28
+
29
+ print("Query:", await sqlite.query("SELECT * FROM users"))
30
+
31
+ sqlite = await env.state("sqlite://current").describe()
32
+ print("SQLite:", sqlite)
33
+
34
+ browser = env.browser()
35
+ print("CDP URL:", await browser.cdp_url())
36
+ print("Devtools URL:", await browser.devtools_url())
37
+
38
+ # Delete the instance
39
+ await fleet.delete(env.instance_id)
40
+
41
+
42
+ if __name__ == "__main__":
43
+ asyncio.run(main())
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ """Example demonstrating browser control with Fleet Manager Client."""
3
+
4
+ import fleet as flt
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+
10
+ def main():
11
+ environments = flt.env.list_envs()
12
+ print("Environments:", len(environments))
13
+
14
+ instances = flt.env.list_instances()
15
+ print("Instances:", len(instances))
16
+
17
+ # Create a new instance
18
+ env = flt.env.make("hubspot:v1.2.7")
19
+ print("New Instance:", env.instance_id)
20
+
21
+ response = env.reset(seed=42)
22
+ print("Reset response:", response)
23
+
24
+ print(env.resources())
25
+
26
+ sqlite = env.db()
27
+ print("SQLite:", sqlite.describe())
28
+
29
+ print("Query:", sqlite.query("SELECT * FROM users"))
30
+
31
+ sqlite = env.state("sqlite://current").describe()
32
+ print("SQLite:", sqlite)
33
+
34
+ browser = env.browser()
35
+ print("CDP URL:", browser.cdp_url())
36
+ print("Devtools URL:", browser.devtools_url())
37
+
38
+ # Delete the instance
39
+ env.close()
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -6,6 +6,9 @@ from typing import TypedDict, List
6
6
  from pathlib import Path
7
7
  import fleet as flt
8
8
  from nova_act import NovaAct, ActResult
9
+ from dotenv import load_dotenv
10
+
11
+ load_dotenv()
9
12
 
10
13
 
11
14
  MAX_STEPS = 30
@@ -1,6 +1,9 @@
1
1
  import asyncio
2
2
  import fleet as flt
3
3
  from nova_act import NovaAct, ActResult
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
4
7
 
5
8
 
6
9
  async def main():
@@ -2,6 +2,9 @@ from openai import OpenAI
2
2
  import fleet as flt
3
3
  import json
4
4
  from typing import Callable
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
5
8
 
6
9
 
7
10
  client = OpenAI()
@@ -1,5 +1,8 @@
1
1
  from openai import OpenAI
2
2
  import fleet as flt
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
3
6
 
4
7
  client = OpenAI()
5
8
 
examples/quickstart.py CHANGED
@@ -7,9 +7,11 @@ This example demonstrates basic usage of the Fleet SDK for environment managemen
7
7
 
8
8
  import asyncio
9
9
  import logging
10
- from typing import Dict, Any
10
+ from dotenv import load_dotenv
11
11
 
12
- import fleet
12
+ load_dotenv()
13
+
14
+ from fleet import AsyncFleet
13
15
 
14
16
 
15
17
  # Configure logging
@@ -19,23 +21,14 @@ logger = logging.getLogger(__name__)
19
21
 
20
22
  async def main():
21
23
  """Main example function."""
22
-
23
- # Check API health
24
- print("🔍 Checking Fleet API health...")
25
- try:
26
- config = fleet.get_config()
27
- client = fleet.FleetAPIClient(config)
28
- health = await client.health_check()
29
- print(f"✅ API Status: {health.status}")
30
- await client.close()
31
- except Exception as e:
32
- print(f"❌ API Health Check failed: {e}")
33
- return
34
-
24
+
25
+ # Initialize the Fleet client
26
+ fleet_client = AsyncFleet()
27
+
35
28
  # 1. List available environments
36
29
  print("\n📋 Available environments:")
37
30
  try:
38
- environments = await fleet.instance.list_envs()
31
+ environments = await fleet_client.list_envs()
39
32
  for env in environments:
40
33
  print(f" - {env.env_key}: {env.name}")
41
34
  print(f" Description: {env.description}")
@@ -44,77 +37,66 @@ async def main():
44
37
  except Exception as e:
45
38
  print(f"❌ Failed to list environments: {e}")
46
39
  return
47
-
40
+
48
41
  # 2. Create a new environment instance
49
42
  print("\n🚀 Creating new environment...")
50
43
  try:
51
- env = await fleet.instance.make("fira:v1.2.5", region="us-west-1")
44
+ env = await fleet_client.make("fira:v1.3.1")
52
45
  print(f"✅ Environment created with instance ID: {env.instance_id}")
53
-
54
- # Execute a simple action
46
+
47
+ # TODO: Execute a simple action
55
48
  print("\n⚡ Executing a simple action...")
56
49
  action = {"type": "test", "data": {"message": "Hello Fleet!"}}
57
- state, reward, done = await env.step(action)
50
+ state, reward, done = await env.instance.step(action)
58
51
  print(f"✅ Action executed successfully!")
59
52
  print(f" Reward: {reward}")
60
53
  print(f" Done: {done}")
61
54
  print(f" State keys: {list(state.keys())}")
62
-
63
- # Check manager API health
64
- print("\n🏥 Checking manager API health...")
65
- try:
66
- manager_health = await env.manager_health_check()
67
- if manager_health:
68
- print(f"✅ Manager API Status: {manager_health.status}")
69
- print(f" Service: {manager_health.service}")
70
- print(f" Timestamp: {manager_health.timestamp}")
71
- else:
72
- print("❌ Manager API not available")
73
- except Exception as e:
74
- print(f"❌ Manager health check failed: {e}")
75
-
55
+
76
56
  # Clean up
77
57
  print("\n🧹 Cleaning up...")
78
58
  await env.close()
79
59
  print("✅ Environment closed")
80
-
60
+
81
61
  except Exception as e:
82
62
  print(f"❌ Environment creation failed: {e}")
83
63
  return
84
-
64
+
85
65
  # 3. List running instances
86
66
  print("\n🏃 Listing running instances...")
87
67
  try:
88
- instances = await fleet.instance.list_instances(status="running")
68
+ instances = await fleet_client.instances(status="running")
89
69
  if instances:
90
70
  print(f"Found {len(instances)} running instances:")
91
71
  for instance in instances:
92
- print(f" - {instance.instance_id}: {instance.env_key} ({instance.status})")
72
+ print(
73
+ f" - {instance.instance_id}: {instance.env_key} ({instance.status})"
74
+ )
93
75
  else:
94
76
  print("No running instances found")
95
77
  except Exception as e:
96
78
  print(f"❌ Failed to list instances: {e}")
97
-
79
+
98
80
  # 4. Connect to an existing instance (if any)
99
81
  print("\n🔗 Connecting to existing instance...")
100
82
  try:
101
83
  # Only get running instances
102
- running_instances = await fleet.instance.list_instances(status="running")
84
+ running_instances = await fleet_client.instances(status="running")
103
85
  if running_instances:
104
86
  # Find a running instance that's not the one we just created/deleted
105
87
  target_instance = running_instances[0]
106
88
  print(f"Connecting to running instance: {target_instance.instance_id}")
107
-
108
- env = await fleet.instance.get(target_instance.instance_id)
89
+
90
+ env = await fleet_client.instance(target_instance.instance_id)
109
91
  print(f"✅ Connected to instance: {env.instance_id}")
110
-
92
+
111
93
  # Execute an action on the existing instance
112
94
  action = {"type": "ping", "data": {"timestamp": "2024-01-01T00:00:00Z"}}
113
- state, reward, done = await env.step(action)
95
+ state, reward, done = await env.instance.step(action)
114
96
  print(f"✅ Action executed on existing instance!")
115
97
  print(f" Reward: {reward}")
116
98
  print(f" Done: {done}")
117
-
99
+
118
100
  # Clean up (this will delete the instance)
119
101
  await env.close()
120
102
  print("✅ Connection closed (instance deleted)")
@@ -122,9 +104,9 @@ async def main():
122
104
  print("No running instances to connect to")
123
105
  except Exception as e:
124
106
  print(f"❌ Failed to connect to existing instance: {e}")
125
-
107
+
126
108
  print("\n🎉 Quickstart complete!")
127
109
 
128
110
 
129
111
  if __name__ == "__main__":
130
- asyncio.run(main())
112
+ asyncio.run(main())
fleet/__init__.py CHANGED
@@ -25,7 +25,6 @@ from ._async.client import AsyncFleet, AsyncEnvironment
25
25
  from .models import InstanceRequest
26
26
  from .instance import (
27
27
  InstanceClient,
28
- AsyncInstanceClient,
29
28
  ResetRequest,
30
29
  ResetResponse,
31
30
  CDPDescribeResponse,
@@ -33,6 +32,7 @@ from .instance import (
33
32
  ChromeStartResponse,
34
33
  ChromeStatusResponse,
35
34
  )
35
+ from ._async.instance import AsyncInstanceClient
36
36
  from .verifiers import (
37
37
  DatabaseSnapshot,
38
38
  IgnoreConfig,
@@ -0,0 +1 @@
1
+ # This file makes the _async directory a proper Python package
fleet/_async/client.py CHANGED
@@ -126,8 +126,12 @@ class AsyncFleet:
126
126
 
127
127
  async def instance(self, instance_id: str) -> AsyncEnvironment:
128
128
  response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
129
- return AsyncEnvironment(**response.json())
129
+ instance = AsyncEnvironment(**response.json())
130
+ await instance.instance.load()
131
+ return instance
130
132
 
131
133
  async def delete(self, instance_id: str) -> InstanceRecord:
132
- response = await self.client.request("DELETE", f"/v1/env/instances/{instance_id}")
133
- return InstanceRecord(**response.json())
134
+ response = await self.client.request(
135
+ "DELETE", f"/v1/env/instances/{instance_id}"
136
+ )
137
+ return InstanceRecord(**response.json())
@@ -1,6 +1,6 @@
1
1
  from ..client import AsyncFleet, AsyncEnvironment
2
2
  from ..models import Environment as EnvironmentModel
3
- from typing import List
3
+ from typing import List, Optional
4
4
 
5
5
 
6
6
  async def make_async(env_key: str) -> AsyncEnvironment:
@@ -11,5 +11,9 @@ async def list_envs_async() -> List[EnvironmentModel]:
11
11
  return await AsyncFleet().list_envs()
12
12
 
13
13
 
14
+ async def list_instances_async(status: Optional[str] = None) -> List[AsyncEnvironment]:
15
+ return await AsyncFleet().instances(status=status)
16
+
17
+
14
18
  async def get_async(instance_id: str) -> AsyncEnvironment:
15
- return await AsyncFleet().instance(instance_id)
19
+ return await AsyncFleet().instance(instance_id)
@@ -1,4 +1,4 @@
1
- """Fleet SDK Async Instance Client."""
1
+ """Fleet SDK Instance Client."""
2
2
 
3
3
  from typing import Any, Callable, Dict, List, Optional, Tuple
4
4
  import asyncio
@@ -12,7 +12,7 @@ from ..resources.sqlite import AsyncSQLiteResource
12
12
  from ..resources.browser import AsyncBrowserResource
13
13
  from ..resources.base import Resource
14
14
 
15
- from ...verifiers import DatabaseSnapshot
15
+ from fleet.verifiers import DatabaseSnapshot
16
16
 
17
17
  from ..exceptions import FleetEnvironmentError, FleetAPIError
18
18
 
@@ -75,13 +75,13 @@ class AsyncInstanceClient:
75
75
 
76
76
  def db(self, name: str) -> AsyncSQLiteResource:
77
77
  """
78
- Returns an AsyncSQLiteResource object for the given SQLite database name.
78
+ Returns an SQLite database resource for the given database name.
79
79
 
80
80
  Args:
81
81
  name: The name of the SQLite database to return
82
82
 
83
83
  Returns:
84
- An AsyncSQLiteResource object for the given SQLite database name
84
+ An SQLite database resource for the given database name
85
85
  """
86
86
  return AsyncSQLiteResource(
87
87
  self._resources_state[ResourceType.db.value][name], self.client
@@ -144,135 +144,33 @@ class AsyncInstanceClient:
144
144
 
145
145
  async def step(self, action: Dict[str, Any]) -> Tuple[Dict[str, Any], float, bool]:
146
146
  """Execute one step in the environment."""
147
- if not self._instance_id:
148
- raise FleetEnvironmentError(
149
- "Environment not initialized. Call reset() first."
150
- )
151
-
152
147
  try:
153
- # Increment step count
154
- self._increment_step()
148
+ # Create a placeholder state
149
+ state = {
150
+ "action": action,
151
+ "timestamp": time.time(),
152
+ "status": "completed",
153
+ }
154
+
155
+ # Create a placeholder reward
156
+ reward = 0.0
155
157
 
156
- # Execute action through instance manager API
157
- # This is a placeholder - actual implementation depends on the manager API spec
158
- state, reward, done = await self._execute_action(action)
158
+ # Determine if episode is done (placeholder logic)
159
+ done = False
159
160
 
160
161
  return state, reward, done
161
162
 
162
163
  except Exception as e:
163
164
  raise FleetEnvironmentError(f"Failed to execute step: {e}")
164
165
 
165
- async def close(self) -> None:
166
- """Close the environment and clean up resources."""
167
- try:
168
- # Delete instance if it exists
169
- if self._instance_id:
170
- try:
171
- await self._client.delete_instance(self._instance_id)
172
- logger.info(f"Deleted instance: {self._instance_id}")
173
- except FleetAPIError as e:
174
- logger.warning(f"Failed to delete instance: {e}")
175
- finally:
176
- self._instance_id = None
177
- self._instance_response = None
178
-
179
- # Close manager client
180
- if self._manager_client:
181
- await self._manager_client.close()
182
- self._manager_client = None
183
-
184
- # Close API client
185
- await self._client.close()
186
-
187
- except Exception as e:
188
- logger.error(f"Error closing environment: {e}")
189
-
190
166
  async def manager_health_check(self) -> Optional[HealthResponse]:
191
167
  response = await self.client.request("GET", "/health")
192
168
  return HealthResponse(**response.json())
193
169
 
194
- async def _wait_for_instance_ready(self, timeout: float = 300.0) -> None:
195
- """Wait for instance to be ready.
196
-
197
- Args:
198
- timeout: Maximum time to wait in seconds
199
- """
200
- start_time = time.time()
201
-
202
- while time.time() - start_time < timeout:
203
- try:
204
- instance = await self._client.get_instance(self._instance_id)
205
- self._instance_response = instance
206
-
207
- if instance.status == "running":
208
- logger.info(f"Instance {self._instance_id} is ready")
209
- return
210
-
211
- elif instance.status == "error":
212
- raise FleetEnvironmentError(
213
- f"Instance {self._instance_id} failed to start"
214
- )
215
-
216
- # Wait before checking again
217
- await asyncio.sleep(5)
218
-
219
- except FleetAPIError as e:
220
- if time.time() - start_time >= timeout:
221
- raise FleetEnvironmentError(
222
- f"Timeout waiting for instance to be ready: {e}"
223
- )
224
- await asyncio.sleep(5)
225
-
226
- raise FleetEnvironmentError(
227
- f"Timeout waiting for instance {self._instance_id} to be ready"
228
- )
229
-
230
- async def _execute_action(
231
- self, action: Dict[str, Any]
232
- ) -> Tuple[Dict[str, Any], float, bool]:
233
- """Execute an action through the instance manager API.
234
-
235
- This is a placeholder implementation that should be extended based on
236
- the actual manager API specification.
237
-
238
- Args:
239
- action: The action to execute as a dictionary
240
-
241
- Returns:
242
- Tuple of (state, reward, done)
243
- """
244
- # Ensure manager client is available
245
- await self._ensure_manager_client()
246
-
247
- # TODO: In the future, this would use the manager API to execute actions
248
- # For example: await self._manager_client.log_action(action)
249
- # For now, return placeholder values
250
-
251
- # Create a placeholder state
252
- state = self._create_state_from_action(action)
253
-
254
- # Create a placeholder reward
255
- reward = 0.0
256
-
257
- # Determine if episode is done (placeholder logic)
258
- done = self._step_count >= 100 # Example: done after 100 steps
259
-
260
- return state, reward, done
261
-
262
- def _create_state_from_action(self, action: Dict[str, Any]) -> Dict[str, Any]:
263
- """Create state based on executed action."""
264
- return {
265
- "instance_id": self._instance_id,
266
- "step": self._step_count,
267
- "last_action": action,
268
- "timestamp": time.time(),
269
- "status": "running",
270
- }
271
-
272
170
  async def __aenter__(self):
273
171
  """Async context manager entry."""
274
172
  return self
275
173
 
276
174
  async def __aexit__(self, exc_type, exc_val, exc_tb):
277
175
  """Async context manager exit."""
278
- await self.close()
176
+ await self.close()
@@ -34,7 +34,7 @@ CUA_KEY_TO_PLAYWRIGHT_KEY = {
34
34
  }
35
35
 
36
36
 
37
- class FleetPlaywrightWrapper:
37
+ class AsyncFleetPlaywrightWrapper:
38
38
  """
39
39
  A wrapper that adds Playwright browser automation to Fleet environment instances.
40
40
 
@@ -46,7 +46,7 @@ class FleetPlaywrightWrapper:
46
46
 
47
47
  Usage:
48
48
  instance = await fleet.env.make(env_key="hubspot", version="v1.2.7")
49
- browser = FleetPlaywrightWrapper(instance)
49
+ browser = AsyncFleetPlaywrightWrapper(instance)
50
50
  await browser.start()
51
51
 
52
52
  # Use browser methods
fleet/client.py CHANGED
@@ -126,8 +126,12 @@ class Fleet:
126
126
 
127
127
  def instance(self, instance_id: str) -> Environment:
128
128
  response = self.client.request("GET", f"/v1/env/instances/{instance_id}")
129
- return Environment(**response.json())
129
+ instance = Environment(**response.json())
130
+ instance.instance.load()
131
+ return instance
130
132
 
131
133
  def delete(self, instance_id: str) -> InstanceRecord:
132
- response = self.client.request("DELETE", f"/v1/env/instances/{instance_id}")
133
- return InstanceRecord(**response.json())
134
+ response = self.client.request(
135
+ "DELETE", f"/v1/env/instances/{instance_id}"
136
+ )
137
+ return InstanceRecord(**response.json())
fleet/env/__init__.py CHANGED
@@ -1,15 +1,17 @@
1
1
  """Fleet env module - convenience functions for environment management."""
2
2
 
3
- from .client import make, list_envs, get
3
+ from .client import make, list_envs, get, list_instances
4
4
 
5
5
  # Import async versions from _async
6
- from .._async.env.client import make_async, list_envs_async, get_async
6
+ from .._async.env.client import make_async, list_envs_async, get_async, list_instances_async
7
7
 
8
8
  __all__ = [
9
9
  "make",
10
10
  "list_envs",
11
+ "list_instances",
11
12
  "get",
12
13
  "make_async",
13
14
  "list_envs_async",
15
+ "list_instances_async",
14
16
  "get_async",
15
17
  ]
fleet/env/client.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from ..client import Fleet, Environment
2
2
  from ..models import Environment as EnvironmentModel
3
- from typing import List
3
+ from typing import List, Optional
4
4
 
5
5
 
6
6
  def make(env_key: str) -> Environment:
@@ -11,5 +11,9 @@ def list_envs() -> List[EnvironmentModel]:
11
11
  return Fleet().list_envs()
12
12
 
13
13
 
14
+ def list_instances(status: Optional[str] = None) -> List[Environment]:
15
+ return Fleet().instances(status=status)
16
+
17
+
14
18
  def get(instance_id: str) -> Environment:
15
- return Fleet().instance(instance_id)
19
+ return Fleet().instance(instance_id)
@@ -1,7 +1,6 @@
1
1
  """Fleet SDK Environment Module."""
2
2
 
3
3
  from .client import InstanceClient, ValidatorType
4
- from .._async.instance.client import AsyncInstanceClient
5
4
  from .models import (
6
5
  ResetRequest,
7
6
  ResetResponse,
@@ -15,7 +14,6 @@ from .models import (
15
14
  __all__ = [
16
15
  "ValidatorType",
17
16
  "InstanceClient",
18
- "AsyncInstanceClient",
19
17
  "ResetRequest",
20
18
  "ResetResponse",
21
19
  "CDPDescribeResponse",
@@ -23,4 +21,4 @@ __all__ = [
23
21
  "ChromeStartResponse",
24
22
  "ChromeStatusResponse",
25
23
  "ExecuteFunctionResponse"
26
- ]
24
+ ]
fleet/instance/client.py CHANGED
@@ -1,7 +1,6 @@
1
- """Fleet SDK Async Instance Client."""
1
+ """Fleet SDK Instance Client."""
2
2
 
3
3
  from typing import Any, Callable, Dict, List, Optional, Tuple
4
- import asyncio
5
4
  import httpx
6
5
  import inspect
7
6
  import time
@@ -75,13 +74,13 @@ class InstanceClient:
75
74
 
76
75
  def db(self, name: str) -> SQLiteResource:
77
76
  """
78
- Returns an AsyncSQLiteResource object for the given SQLite database name.
77
+ Returns an SQLite database resource for the given database name.
79
78
 
80
79
  Args:
81
80
  name: The name of the SQLite database to return
82
81
 
83
82
  Returns:
84
- An AsyncSQLiteResource object for the given SQLite database name
83
+ An SQLite database resource for the given database name
85
84
  """
86
85
  return SQLiteResource(
87
86
  self._resources_state[ResourceType.db.value][name], self.client
@@ -144,135 +143,33 @@ class InstanceClient:
144
143
 
145
144
  def step(self, action: Dict[str, Any]) -> Tuple[Dict[str, Any], float, bool]:
146
145
  """Execute one step in the environment."""
147
- if not self._instance_id:
148
- raise FleetEnvironmentError(
149
- "Environment not initialized. Call reset() first."
150
- )
151
-
152
146
  try:
153
- # Increment step count
154
- self._increment_step()
147
+ # Create a placeholder state
148
+ state = {
149
+ "action": action,
150
+ "timestamp": time.time(),
151
+ "status": "completed",
152
+ }
153
+
154
+ # Create a placeholder reward
155
+ reward = 0.0
155
156
 
156
- # Execute action through instance manager API
157
- # This is a placeholder - actual implementation depends on the manager API spec
158
- state, reward, done = self._execute_action(action)
157
+ # Determine if episode is done (placeholder logic)
158
+ done = False
159
159
 
160
160
  return state, reward, done
161
161
 
162
162
  except Exception as e:
163
163
  raise FleetEnvironmentError(f"Failed to execute step: {e}")
164
164
 
165
- def close(self) -> None:
166
- """Close the environment and clean up resources."""
167
- try:
168
- # Delete instance if it exists
169
- if self._instance_id:
170
- try:
171
- self._client.delete_instance(self._instance_id)
172
- logger.info(f"Deleted instance: {self._instance_id}")
173
- except FleetAPIError as e:
174
- logger.warning(f"Failed to delete instance: {e}")
175
- finally:
176
- self._instance_id = None
177
- self._instance_response = None
178
-
179
- # Close manager client
180
- if self._manager_client:
181
- self._manager_client.close()
182
- self._manager_client = None
183
-
184
- # Close API client
185
- self._client.close()
186
-
187
- except Exception as e:
188
- logger.error(f"Error closing environment: {e}")
189
-
190
165
  def manager_health_check(self) -> Optional[HealthResponse]:
191
166
  response = self.client.request("GET", "/health")
192
167
  return HealthResponse(**response.json())
193
168
 
194
- def _wait_for_instance_ready(self, timeout: float = 300.0) -> None:
195
- """Wait for instance to be ready.
196
-
197
- Args:
198
- timeout: Maximum time to wait in seconds
199
- """
200
- start_time = time.time()
201
-
202
- while time.time() - start_time < timeout:
203
- try:
204
- instance = self._client.get_instance(self._instance_id)
205
- self._instance_response = instance
206
-
207
- if instance.status == "running":
208
- logger.info(f"Instance {self._instance_id} is ready")
209
- return
210
-
211
- elif instance.status == "error":
212
- raise FleetEnvironmentError(
213
- f"Instance {self._instance_id} failed to start"
214
- )
215
-
216
- # Wait before checking again
217
- asyncio.sleep(5)
218
-
219
- except FleetAPIError as e:
220
- if time.time() - start_time >= timeout:
221
- raise FleetEnvironmentError(
222
- f"Timeout waiting for instance to be ready: {e}"
223
- )
224
- asyncio.sleep(5)
225
-
226
- raise FleetEnvironmentError(
227
- f"Timeout waiting for instance {self._instance_id} to be ready"
228
- )
229
-
230
- def _execute_action(
231
- self, action: Dict[str, Any]
232
- ) -> Tuple[Dict[str, Any], float, bool]:
233
- """Execute an action through the instance manager API.
234
-
235
- This is a placeholder implementation that should be extended based on
236
- the actual manager API specification.
237
-
238
- Args:
239
- action: The action to execute as a dictionary
240
-
241
- Returns:
242
- Tuple of (state, reward, done)
243
- """
244
- # Ensure manager client is available
245
- self._ensure_manager_client()
246
-
247
- # TODO: In the future, this would use the manager API to execute actions
248
- # For example: await self._manager_client.log_action(action)
249
- # For now, return placeholder values
250
-
251
- # Create a placeholder state
252
- state = self._create_state_from_action(action)
253
-
254
- # Create a placeholder reward
255
- reward = 0.0
256
-
257
- # Determine if episode is done (placeholder logic)
258
- done = self._step_count >= 100 # Example: done after 100 steps
259
-
260
- return state, reward, done
261
-
262
- def _create_state_from_action(self, action: Dict[str, Any]) -> Dict[str, Any]:
263
- """Create state based on executed action."""
264
- return {
265
- "instance_id": self._instance_id,
266
- "step": self._step_count,
267
- "last_action": action,
268
- "timestamp": time.time(),
269
- "status": "running",
270
- }
271
-
272
169
  def __enter__(self):
273
170
  """Async context manager entry."""
274
171
  return self
275
172
 
276
173
  def __exit__(self, exc_type, exc_val, exc_tb):
277
174
  """Async context manager exit."""
278
- self.close()
175
+ self.close()
fleet/playwright.py CHANGED
@@ -45,16 +45,16 @@ class FleetPlaywrightWrapper:
45
45
  - Integration with OpenAI computer use API
46
46
 
47
47
  Usage:
48
- instance = await fleet.env.make(env_key="hubspot", version="v1.2.7")
48
+ instance = fleet.env.make(env_key="hubspot", version="v1.2.7")
49
49
  browser = FleetPlaywrightWrapper(instance)
50
- await browser.start()
50
+ browser.start()
51
51
 
52
52
  # Use browser methods
53
- screenshot = await browser.screenshot()
53
+ screenshot = browser.screenshot()
54
54
  tools = [browser.openai_cua_tool]
55
55
 
56
56
  # Clean up when done
57
- await browser.close()
57
+ browser.close()
58
58
  """
59
59
 
60
60
  def get_environment(self):
@@ -100,7 +100,9 @@ class FleetPlaywrightWrapper:
100
100
  cdp = self.env.browser().describe()
101
101
 
102
102
  # Connect to browser
103
- self._browser = self._playwright.chromium.connect_over_cdp(cdp.cdp_browser_url)
103
+ self._browser = self._playwright.chromium.connect_over_cdp(
104
+ cdp.cdp_browser_url
105
+ )
104
106
  self._page = self._browser.contexts[0].pages[0]
105
107
  self._page.set_viewport_size(
106
108
  {"width": self.display_width, "height": self.display_height}
@@ -121,7 +123,7 @@ class FleetPlaywrightWrapper:
121
123
  def _ensure_started(self):
122
124
  """Ensure browser is started before operations."""
123
125
  if not self._started:
124
- raise RuntimeError("Browser not started. Call await browser.start() first.")
126
+ raise RuntimeError("Browser not started. Call browser.start() first.")
125
127
 
126
128
  @property
127
129
  def openai_cua_tool(self) -> Dict[str, Any]:
@@ -237,9 +239,8 @@ class FleetPlaywrightWrapper:
237
239
 
238
240
  def _wait(self, ms: int = 1000) -> None:
239
241
  """Wait for specified milliseconds."""
240
- import asyncio
241
242
 
242
- asyncio.sleep(ms / 1000)
243
+ time.sleep(ms / 1000)
243
244
 
244
245
  # Browser-specific actions
245
246
  def _goto(self, url: str) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.5
3
+ Version: 0.2.8
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -32,6 +32,7 @@ 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
34
  Requires-Dist: unasync>=0.6.0; extra == "dev"
35
+ Requires-Dist: python-dotenv>=1.1.1; extra == "dev"
35
36
  Provides-Extra: playwright
36
37
  Requires-Dist: playwright>=1.40.0; extra == "playwright"
37
38
  Dynamic: license-file
@@ -112,3 +113,44 @@ running_instances = flt.env.list_instances(status_filter="running")
112
113
  # List available environment types
113
114
  available_envs = flt.env.list_envs()
114
115
  ```
116
+
117
+ ## Development
118
+
119
+ ### Code Structure
120
+
121
+ This SDK uses `unasync` to maintain both async and sync versions of the code from a single source:
122
+
123
+ - **`fleet/_async/`** - The source code (async version) - **EDIT THIS**
124
+ - **`fleet/`** - The generated sync version - **DO NOT EDIT** (auto-generated)
125
+
126
+ ### Making Changes
127
+
128
+ ⚠️ **Important**: All code modifications should be made in the `fleet/_async/` directory. The synchronous versions in `fleet/` are automatically generated.
129
+
130
+ 1. Make your changes in `fleet/_async/`
131
+ 2. Run `make unasync` to generate the sync versions
132
+ 3. Test both async and sync versions
133
+ 4. Commit all changes (both async source and generated sync files)
134
+
135
+ Example workflow:
136
+ ```bash
137
+ # Edit the async source files
138
+ vim fleet/_async/client.py
139
+
140
+ # Generate sync versions
141
+ make unasync
142
+
143
+ # Run tests
144
+ python examples/examle.py
145
+
146
+ # Commit both source and generated files
147
+ git add fleet/_async/ fleet/
148
+ git commit -m "Add new feature"
149
+ ```
150
+
151
+ ### Why This Structure?
152
+
153
+ - Single source of truth for business logic
154
+ - Automatic sync/async API generation
155
+ - Consistent behavior between sync and async versions
156
+ - Easier maintenance and testing
@@ -1,36 +1,39 @@
1
- examples/dsl_example.py,sha256=nBfKdv4u6XuM97TG-ugfVStPdtoYq86ildFzLcQqGIk,5315
2
- examples/example.py,sha256=B1ERVudPsPx1D1od3gCQU8MnG4FxuSBm0p7otkvIaLw,945
3
- examples/json_tasks_example.py,sha256=_y4w94FfAaMBGgIqhVlrDU0PXbinNDWrT28DsUMvd9Y,2354
4
- examples/nova_act_example.py,sha256=YMfx_7O1_6pqsy0Rf6Yabyqyg4_kI3cgMBlMJjAQXW4,790
5
- examples/openai_example.py,sha256=5ZvBC0dcw8gPHoC6Gn-1KZx3CmUQBlBmPJgOrcgMSVM,8188
6
- examples/openai_simple_example.py,sha256=pS2z7Q3s7tml5hbahc1NGgMXoF0mWwhi7VeLDTA4sE4,1702
7
- examples/quickstart.py,sha256=lVRzbnWIweU9ioe7uk6R2Rm7oSpt4mt8Jq_VUUp1zKg,4696
8
- fleet/__init__.py,sha256=CxDokocjQsv_JUNwhSqnijpQVXhQx73CXLu2Cp-rtgk,2087
1
+ examples/dsl_example.py,sha256=3Eu5924a8x61nuSGXqGz8XjPLNKKH8Ye7lSYHSvixtk,5361
2
+ examples/example.py,sha256=FFPfM5Oso7IP9Q8aELpof1J41zslELdHHJhAAck9vLk,1008
3
+ examples/example_client.py,sha256=70HKEhz_Gb79YcvKQauCPdS08AAwjo9unt2dh1jN_Oo,1030
4
+ examples/example_sync.py,sha256=Kj_Mj2G88ADdQ2Pw_JQqJNbnpZrfMGSNR9paRQhSHLY,967
5
+ examples/json_tasks_example.py,sha256=EhsJKVWoJFcrqhIIKF5lVrnIqpZDtizQqYblJaOZtmk,2400
6
+ examples/nova_act_example.py,sha256=hZLpObVsiXKQzqGwMZVMf4A2j_z4TYE-YO9pgNmaKPk,836
7
+ examples/openai_example.py,sha256=I2vk_SJN9BkSRQCYRJfbtGJ-HJ2xzQj-lOjwqmLos5M,8234
8
+ examples/openai_simple_example.py,sha256=I42ytIwv0INgDO39pp1MOQSqsJz2YYH8GeNNBaUtq3A,1748
9
+ examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
10
+ fleet/__init__.py,sha256=p-0zZMxKoD6dlQ9IGQ753QQWuwcw-RPl4E6HezhKfV4,2111
9
11
  fleet/base.py,sha256=t4xkgazl8kEP05JFjNByyf39RvvASRP0GsvxuoqKPY0,1395
10
- fleet/client.py,sha256=SQEUoj4DMuXa4m5wfPPCkBEhQc7f6941K9V8Qk6ZF0U,4675
12
+ fleet/client.py,sha256=xPLlHS14Q2F8jn8e5lSLMUFE6o6ro7oM0XclOjVrxE4,4759
11
13
  fleet/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
12
14
  fleet/models.py,sha256=Jf6Zmk689TPXhTSnVENK_VCw0VsujWzEWsN3T29MQ0k,3713
13
- fleet/playwright.py,sha256=LWx_1UlNZPo117Lf7qnBlWT4RJA0OcwqRVRdFE98bSQ,8627
15
+ fleet/playwright.py,sha256=BmRvez5DUa0ttAQB084hPAyt9_8WxdzCGBGF-GZbTuQ,8593
16
+ fleet/_async/__init__.py,sha256=AJWCnuo7XKja4yBb8fK2wX7ntciLXQrpzdRHwjTRP6M,62
14
17
  fleet/_async/base.py,sha256=hUch1I5oUPgaCXR3IpJ8f_PjigifAZg2-LR7BJdZSo8,1413
15
- fleet/_async/client.py,sha256=pL0csdrAJKltA9km6DHDRogPpTEzi8AH32mS1efxNgg,4920
18
+ fleet/_async/client.py,sha256=T15EwovKCCshr4sF_HeHNdnbIUFoyZqjrN6_tU5o4aY,5010
16
19
  fleet/_async/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
17
20
  fleet/_async/models.py,sha256=Jf6Zmk689TPXhTSnVENK_VCw0VsujWzEWsN3T29MQ0k,3713
18
- fleet/_async/playwright.py,sha256=5MLFPE5P_-MpzAQ3EJ6GsLthuJiWwYkNuvhPE_rwe_E,8914
21
+ fleet/_async/playwright.py,sha256=2r4ywuv2ZqT0Qu3-k8A7V4YijeAOHnN8HiqJreLEYGI,8924
19
22
  fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- fleet/_async/env/client.py,sha256=Gft6pNkUK4PYMV8VE3KyS5R_-HuqGO5HEjAUuCPObiw,440
23
+ fleet/_async/env/client.py,sha256=CO2KuD6FFkP1SdOJ2ehwBKrCdLEFsz5tj3jPR5tVu4U,596
21
24
  fleet/_async/instance/__init__.py,sha256=jIt-7EEJ0WM_ipheT_s0lniCbLei6yUdN0qQv1bMJ3E,524
22
25
  fleet/_async/instance/base.py,sha256=QgcCTHdcqhi5VQi6_a1uuR-uO2_2Z19-RwVPp1k266A,947
23
- fleet/_async/instance/client.py,sha256=Llc-yefnbL3fkBsayU4ANEqy_fFB2Uy6jNhTkfGj1mU,9362
26
+ fleet/_async/instance/client.py,sha256=qmX5g6lPrq0b3BQ6LvTApeyquTtCse98Cu_Kwc72y6A,5653
24
27
  fleet/_async/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
25
28
  fleet/_async/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
29
  fleet/_async/resources/base.py,sha256=203gD54NP1IvjuSqFo-f7FvrkhtjChggtzrxJK7xf2E,667
27
30
  fleet/_async/resources/browser.py,sha256=x11y4aKHogIEv83FByHtExerjV-cDWI3U62349Guq_Q,1368
28
31
  fleet/_async/resources/sqlite.py,sha256=sRiII_qJ8X6-FSemlBsXThz4ZPjkNy9wDT8g5UAz2XM,1501
29
- fleet/env/__init__.py,sha256=_lvYBqieXWmvU_dyPi2seSpLO3AZh5kdprdqFeefkzk,338
30
- fleet/env/client.py,sha256=UGPrRlu89NM2RFdY7m6cGwUjOHJdtiPe4vkz-f2ByRg,351
31
- fleet/instance/__init__.py,sha256=cdVC50HLLp2y7yf1Ga5wpLiy-hmamxmyibH0NDG7xI4,597
32
+ fleet/env/__init__.py,sha256=dlh41oWCKwg1-wUsy1c4lCA34hRKlBjfGpgEXO96TyY,426
33
+ fleet/env/client.py,sha256=LVHXnC19bl0zEW20qykC63wJ0nO9gETF6z0_74dCdCo,479
34
+ fleet/instance/__init__.py,sha256=Hr8xPPoqzKOViXZXWmaL6dQ7NOBn-GooTGzoIvGmiE4,514
32
35
  fleet/instance/base.py,sha256=U-qW1EQVBo6yvMpP1JeKiPRhCjZ3y3aTsYFhLPNOTtQ,929
33
- fleet/instance/client.py,sha256=RFp5R703pP3tjKd_HBozJJ2qNie7LpMUx9NYjyPnPOI,9128
36
+ fleet/instance/client.py,sha256=2EcuBpq21kUHCFommYdS9Ya-unLn-e8mrdAZBIZea3Q,5467
34
37
  fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
35
38
  fleet/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
39
  fleet/resources/base.py,sha256=203gD54NP1IvjuSqFo-f7FvrkhtjChggtzrxJK7xf2E,667
@@ -40,9 +43,10 @@ fleet/verifiers/__init__.py,sha256=mRMN8x0gDWFJ1MRLqdBtQw0gn_q8kDV3lMLyoiEf1yY,2
40
43
  fleet/verifiers/code.py,sha256=NJ4OLZnpqLkI1lXY7-5m2GuZklLxMzHUCnRMVyN2_OI,25
41
44
  fleet/verifiers/db.py,sha256=tssmvJjDHuBIy8qlL_P5-UdmEFUw2DZcqLsWZ8ot3Xw,27766
42
45
  fleet/verifiers/sql_differ.py,sha256=dmiGCFXVMEMbAX519OjhVqgA8ZvhnvdmC1BVpL7QCF0,6490
43
- fleet_python-0.2.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
46
+ fleet_python-0.2.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ scripts/fix_sync_imports.py,sha256=b7tRvShgOFqyildqs1qI-Io0gaHappykBI-PSWWqUwE,2941
44
48
  scripts/unasync.py,sha256=--Fmaae47o-dZ1HYgX1c3Nvi-rMjcFymTRlJcWWnmpw,725
45
- fleet_python-0.2.5.dist-info/METADATA,sha256=MqmuFENsTkHkP2gU9_UQB35NQH4hL5POBYGpYCASRIY,3139
46
- fleet_python-0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- fleet_python-0.2.5.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
48
- fleet_python-0.2.5.dist-info/RECORD,,
49
+ fleet_python-0.2.8.dist-info/METADATA,sha256=ptLDBsLE2Rz82QwWegFogUl94d933bcu6STz7DH9UPQ,4321
50
+ fleet_python-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ fleet_python-0.2.8.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
52
+ fleet_python-0.2.8.dist-info/RECORD,,
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """Fix imports in sync files after unasync runs."""
3
+
4
+ import re
5
+ from pathlib import Path
6
+
7
+ def fix_file(filepath: Path) -> bool:
8
+ """Fix imports and sleep calls in a single file."""
9
+ content = filepath.read_text()
10
+ original = content
11
+
12
+ # Remove asyncio import if it exists
13
+ content = re.sub(r'^import asyncio.*\n', '', content, flags=re.MULTILINE)
14
+ content = re.sub(r'^import asyncio as async_time.*\n', '', content, flags=re.MULTILINE)
15
+ # Also remove indented asyncio imports (like in functions)
16
+ content = re.sub(r'^\s+import asyncio.*\n', '', content, flags=re.MULTILINE)
17
+
18
+ # Fix any remaining asyncio.sleep or async_time.sleep calls
19
+ content = content.replace('asyncio.sleep(', 'time.sleep(')
20
+ content = content.replace('async_time.sleep(', 'time.sleep(')
21
+
22
+ # Fix absolute imports to relative imports for verifiers
23
+ content = content.replace('from fleet.verifiers import', 'from ..verifiers import')
24
+
25
+ # Fix any remaining AsyncFleetPlaywrightWrapper references in docstrings
26
+ content = content.replace('AsyncFleetPlaywrightWrapper', 'FleetPlaywrightWrapper')
27
+
28
+ # Fix playwright imports for sync version
29
+ if 'playwright' in str(filepath):
30
+ # Fix the import statement
31
+ content = content.replace('from playwright.async_api import sync_playwright, Browser, Page',
32
+ 'from playwright.sync_api import sync_playwright, Browser, Page')
33
+ content = content.replace('from playwright.async_api import async_playwright, Browser, Page',
34
+ 'from playwright.sync_api import sync_playwright, Browser, Page')
35
+ # Replace any remaining async_playwright references
36
+ content = content.replace('async_playwright', 'sync_playwright')
37
+ # Fix await statements in docstrings
38
+ content = content.replace('await browser.start()', 'browser.start()')
39
+ content = content.replace('await browser.screenshot()', 'browser.screenshot()')
40
+ content = content.replace('await browser.close()', 'browser.close()')
41
+ content = content.replace('await fleet.env.make(', 'fleet.env.make(')
42
+ # Fix error message
43
+ content = content.replace('Call await browser.start() first', 'Call browser.start() first')
44
+
45
+ if content != original:
46
+ filepath.write_text(content)
47
+ return True
48
+ return False
49
+
50
+ def main():
51
+ """Fix all sync files."""
52
+ sync_dir = Path(__file__).parent.parent / "fleet"
53
+
54
+ # Files to fix
55
+ files_to_fix = [
56
+ sync_dir / "instance" / "client.py",
57
+ sync_dir / "playwright.py",
58
+ # Add other files here as needed
59
+ ]
60
+
61
+ for filepath in files_to_fix:
62
+ if filepath.exists():
63
+ if fix_file(filepath):
64
+ print(f"Fixed {filepath}")
65
+ else:
66
+ print(f"No changes needed for {filepath}")
67
+
68
+ if __name__ == "__main__":
69
+ main()