fleet-python 0.2.3__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 (42) hide show
  1. examples/dsl_example.py +2 -1
  2. examples/example.py +2 -2
  3. examples/json_tasks_example.py +1 -1
  4. examples/nova_act_example.py +1 -1
  5. examples/openai_example.py +17 -24
  6. examples/openai_simple_example.py +11 -12
  7. fleet/__init__.py +18 -3
  8. fleet/_async/base.py +51 -0
  9. fleet/_async/client.py +133 -0
  10. fleet/_async/env/__init__.py +0 -0
  11. fleet/_async/env/client.py +15 -0
  12. fleet/_async/exceptions.py +73 -0
  13. fleet/_async/instance/__init__.py +24 -0
  14. fleet/_async/instance/base.py +37 -0
  15. fleet/_async/instance/client.py +278 -0
  16. fleet/_async/instance/models.py +141 -0
  17. fleet/_async/models.py +109 -0
  18. fleet/_async/playwright.py +291 -0
  19. fleet/_async/resources/__init__.py +0 -0
  20. fleet/_async/resources/base.py +26 -0
  21. fleet/_async/resources/browser.py +41 -0
  22. fleet/_async/resources/sqlite.py +41 -0
  23. fleet/base.py +1 -24
  24. fleet/client.py +31 -99
  25. fleet/env/__init__.py +13 -1
  26. fleet/env/client.py +7 -7
  27. fleet/instance/__init__.py +3 -2
  28. fleet/instance/base.py +1 -24
  29. fleet/instance/client.py +40 -57
  30. fleet/playwright.py +45 -47
  31. fleet/resources/__init__.py +0 -0
  32. fleet/resources/browser.py +14 -14
  33. fleet/resources/sqlite.py +11 -11
  34. fleet/verifiers/__init__.py +5 -10
  35. fleet/verifiers/code.py +1 -132
  36. {fleet_python-0.2.3.dist-info → fleet_python-0.2.4.dist-info}/METADATA +2 -1
  37. fleet_python-0.2.4.dist-info/RECORD +48 -0
  38. {fleet_python-0.2.3.dist-info → fleet_python-0.2.4.dist-info}/top_level.txt +1 -0
  39. scripts/unasync.py +28 -0
  40. fleet_python-0.2.3.dist-info/RECORD +0 -31
  41. {fleet_python-0.2.3.dist-info → fleet_python-0.2.4.dist-info}/WHEEL +0 -0
  42. {fleet_python-0.2.3.dist-info → fleet_python-0.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,278 @@
1
+ """Fleet SDK Async Instance Client."""
2
+
3
+ from typing import Any, Callable, Dict, List, Optional, Tuple
4
+ import asyncio
5
+ import httpx
6
+ import inspect
7
+ import time
8
+ import logging
9
+ from urllib.parse import urlparse
10
+
11
+ from ..resources.sqlite import AsyncSQLiteResource
12
+ from ..resources.browser import AsyncBrowserResource
13
+ from ..resources.base import Resource
14
+
15
+ from ...verifiers import DatabaseSnapshot
16
+
17
+ from ..exceptions import FleetEnvironmentError, FleetAPIError
18
+
19
+ from .base import AsyncWrapper
20
+ from .models import (
21
+ ResetRequest,
22
+ ResetResponse,
23
+ Resource as ResourceModel,
24
+ ResourceType,
25
+ HealthResponse,
26
+ ExecuteFunctionRequest,
27
+ ExecuteFunctionResponse,
28
+ )
29
+
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ RESOURCE_TYPES = {
35
+ ResourceType.db: AsyncSQLiteResource,
36
+ ResourceType.cdp: AsyncBrowserResource,
37
+ }
38
+
39
+ ValidatorType = Callable[
40
+ [DatabaseSnapshot, DatabaseSnapshot, Optional[str]],
41
+ int,
42
+ ]
43
+
44
+
45
+ class AsyncInstanceClient:
46
+ def __init__(
47
+ self,
48
+ url: str,
49
+ httpx_client: Optional[httpx.AsyncClient] = None,
50
+ ):
51
+ self.base_url = url
52
+ self.client = AsyncWrapper(
53
+ url=self.base_url,
54
+ httpx_client=httpx_client or httpx.AsyncClient(timeout=60.0),
55
+ )
56
+ self._resources: Optional[List[ResourceModel]] = None
57
+ self._resources_state: Dict[str, Dict[str, Resource]] = {
58
+ resource_type.value: {} for resource_type in ResourceType
59
+ }
60
+
61
+ async def load(self) -> None:
62
+ await self._load_resources()
63
+
64
+ async def reset(
65
+ self, reset_request: Optional[ResetRequest] = None
66
+ ) -> ResetResponse:
67
+ response = await self.client.request(
68
+ "POST", "/reset", json=reset_request.model_dump() if reset_request else None
69
+ )
70
+ return ResetResponse(**response.json())
71
+
72
+ def state(self, uri: str) -> Resource:
73
+ url = urlparse(uri)
74
+ return self._resources_state[url.scheme][url.netloc]
75
+
76
+ def db(self, name: str) -> AsyncSQLiteResource:
77
+ """
78
+ Returns an AsyncSQLiteResource object for the given SQLite database name.
79
+
80
+ Args:
81
+ name: The name of the SQLite database to return
82
+
83
+ Returns:
84
+ An AsyncSQLiteResource object for the given SQLite database name
85
+ """
86
+ return AsyncSQLiteResource(
87
+ self._resources_state[ResourceType.db.value][name], self.client
88
+ )
89
+
90
+ def browser(self, name: str) -> AsyncBrowserResource:
91
+ return AsyncBrowserResource(
92
+ self._resources_state[ResourceType.cdp.value][name], self.client
93
+ )
94
+
95
+ async def resources(self) -> List[Resource]:
96
+ await self._load_resources()
97
+ return [
98
+ resource
99
+ for resources_by_name in self._resources_state.values()
100
+ for resource in resources_by_name.values()
101
+ ]
102
+
103
+ async def verify(self, validator: ValidatorType) -> ExecuteFunctionResponse:
104
+ function_code = inspect.getsource(validator)
105
+ function_name = validator.__name__
106
+ return await self.verify_raw(function_code, function_name)
107
+
108
+ async def verify_raw(
109
+ self, function_code: str, function_name: str
110
+ ) -> ExecuteFunctionResponse:
111
+ response = await self.client.request(
112
+ "POST",
113
+ "/execute_verifier_function",
114
+ json=ExecuteFunctionRequest(
115
+ function_code=function_code,
116
+ function_name=function_name,
117
+ ).model_dump(),
118
+ )
119
+ return ExecuteFunctionResponse(**response.json())
120
+
121
+ async def _load_resources(self) -> None:
122
+ if self._resources is None:
123
+ response = await self.client.request("GET", "/resources")
124
+ if response.status_code != 200:
125
+ self._resources = []
126
+ return
127
+
128
+ # Handle both old and new response formats
129
+ response_data = response.json()
130
+ if isinstance(response_data, dict) and "resources" in response_data:
131
+ # Old format: {"resources": [...]}
132
+ resources_list = response_data["resources"]
133
+ else:
134
+ # New format: [...]
135
+ resources_list = response_data
136
+
137
+ self._resources = [ResourceModel(**resource) for resource in resources_list]
138
+ for resource in self._resources:
139
+ if resource.type not in self._resources_state:
140
+ self._resources_state[resource.type.value] = {}
141
+ self._resources_state[resource.type.value][resource.name] = (
142
+ RESOURCE_TYPES[resource.type](resource, self.client)
143
+ )
144
+
145
+ async def step(self, action: Dict[str, Any]) -> Tuple[Dict[str, Any], float, bool]:
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
+ try:
153
+ # Increment step count
154
+ self._increment_step()
155
+
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)
159
+
160
+ return state, reward, done
161
+
162
+ except Exception as e:
163
+ raise FleetEnvironmentError(f"Failed to execute step: {e}")
164
+
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
+ async def manager_health_check(self) -> Optional[HealthResponse]:
191
+ response = await self.client.request("GET", "/health")
192
+ return HealthResponse(**response.json())
193
+
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
+ async def __aenter__(self):
273
+ """Async context manager entry."""
274
+ return self
275
+
276
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
277
+ """Async context manager exit."""
278
+ await self.close()
@@ -0,0 +1,141 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: openapi (2).json
3
+ # timestamp: 2025-07-09T20:11:31+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional, Union
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class CDPDescribeResponse(BaseModel):
14
+ success: bool = Field(..., title="Success")
15
+ cdp_page_url: str = Field(..., title="Url")
16
+ cdp_browser_url: str = Field(..., title="Browser Url")
17
+ cdp_devtools_url: str = Field(..., title="Devtools Url")
18
+
19
+
20
+ class ChromeStartRequest(BaseModel):
21
+ resolution: Optional[str] = Field("1920x1080", title="Resolution")
22
+
23
+
24
+ class ChromeStartResponse(BaseModel):
25
+ success: bool = Field(..., title="Success")
26
+ message: str = Field(..., title="Message")
27
+
28
+
29
+ class ChromeStatusResponse(BaseModel):
30
+ running: bool = Field(..., title="Running")
31
+ message: str = Field(..., title="Message")
32
+
33
+
34
+ class CreateSnapshotsResponse(BaseModel):
35
+ success: bool = Field(..., title="Success")
36
+ initial_snapshot_path: Optional[str] = Field(None, title="Initial Snapshot Path")
37
+ final_snapshot_path: Optional[str] = Field(None, title="Final Snapshot Path")
38
+ message: str = Field(..., title="Message")
39
+
40
+
41
+ class HealthResponse(BaseModel):
42
+ status: str = Field(..., title="Status")
43
+ timestamp: str = Field(..., title="Timestamp")
44
+ service: str = Field(..., title="Service")
45
+
46
+
47
+ class LogActionRequest(BaseModel):
48
+ action_type: str = Field(..., title="Action Type")
49
+ sql: Optional[str] = Field(None, title="Sql")
50
+ args: Optional[str] = Field(None, title="Args")
51
+ path: Optional[str] = Field(None, title="Path")
52
+ raw_payload: Optional[str] = Field(None, title="Raw Payload")
53
+
54
+
55
+ class LogActionResponse(BaseModel):
56
+ success: bool = Field(..., title="Success")
57
+ message: str = Field(..., title="Message")
58
+
59
+
60
+ class QueryRequest(BaseModel):
61
+ query: str = Field(..., title="Query")
62
+ args: Optional[List] = Field(None, title="Args")
63
+ read_only: Optional[bool] = Field(True, title="Read Only")
64
+
65
+
66
+ class QueryResponse(BaseModel):
67
+ success: bool = Field(..., title="Success")
68
+ columns: Optional[List[str]] = Field(None, title="Columns")
69
+ rows: Optional[List[List]] = Field(None, title="Rows")
70
+ rows_affected: Optional[int] = Field(None, title="Rows Affected")
71
+ last_insert_id: Optional[int] = Field(None, title="Last Insert Id")
72
+ error: Optional[str] = Field(None, title="Error")
73
+ message: str = Field(..., title="Message")
74
+
75
+
76
+ class ResetRequest(BaseModel):
77
+ timestamp: Optional[int] = Field(None, title="Timestamp")
78
+ seed: Optional[int] = Field(None, title="Seed")
79
+
80
+
81
+ class ResetResponse(BaseModel):
82
+ success: bool = Field(..., title="Success")
83
+ message: str = Field(..., title="Message")
84
+
85
+
86
+ class ResourceMode(Enum):
87
+ ro = "ro"
88
+ rw = "rw"
89
+
90
+
91
+ class ResourceType(Enum):
92
+ db = "sqlite"
93
+ cdp = "cdp"
94
+
95
+
96
+ class TableSchema(BaseModel):
97
+ name: str = Field(..., title="Name")
98
+ sql: str = Field(..., title="Sql")
99
+ columns: List[Dict[str, Any]] = Field(..., title="Columns")
100
+
101
+
102
+ class TimestampResponse(BaseModel):
103
+ timestamp: str = Field(..., title="Timestamp")
104
+
105
+
106
+ class ValidationError(BaseModel):
107
+ loc: List[Union[str, int]] = Field(..., title="Location")
108
+ msg: str = Field(..., title="Message")
109
+ type: str = Field(..., title="Error Type")
110
+
111
+
112
+ class DescribeResponse(BaseModel):
113
+ success: bool = Field(..., title="Success")
114
+ resource_name: str = Field(..., title="Resource Name")
115
+ tables: Optional[List[TableSchema]] = Field(None, title="Tables")
116
+ error: Optional[str] = Field(None, title="Error")
117
+ message: str = Field(..., title="Message")
118
+
119
+
120
+ class HTTPValidationError(BaseModel):
121
+ detail: Optional[List[ValidationError]] = Field(None, title="Detail")
122
+
123
+
124
+ class Resource(BaseModel):
125
+ name: str = Field(..., title="Name")
126
+ type: ResourceType
127
+ mode: ResourceMode
128
+ label: Optional[str] = Field(None, title="Label")
129
+
130
+
131
+ class ExecuteFunctionRequest(BaseModel):
132
+ function_code: str
133
+ function_name: str
134
+ text_solution: Optional[str] = None
135
+
136
+
137
+ class ExecuteFunctionResponse(BaseModel):
138
+ success: bool
139
+ result: Optional[Any] = None
140
+ error: Optional[str] = None
141
+ message: str
fleet/_async/models.py ADDED
@@ -0,0 +1,109 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: openapi.json
3
+ # timestamp: 2025-07-08T18:21:47+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+ from typing import Dict, List, Optional, Union
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class Environment(BaseModel):
14
+ env_key: str = Field(..., title="Env Key")
15
+ name: str = Field(..., title="Name")
16
+ description: Optional[str] = Field(..., title="Description")
17
+ default_version: Optional[str] = Field(..., title="Default Version")
18
+ versions: Dict[str, str] = Field(..., title="Versions")
19
+
20
+
21
+ class Instance(BaseModel):
22
+ instance_id: str = Field(..., title="Instance Id")
23
+ env_key: str = Field(..., title="Env Key")
24
+ version: str = Field(..., title="Version")
25
+ status: str = Field(..., title="Status")
26
+ subdomain: str = Field(..., title="Subdomain")
27
+ created_at: str = Field(..., title="Created At")
28
+ updated_at: str = Field(..., title="Updated At")
29
+ terminated_at: Optional[str] = Field(None, title="Terminated At")
30
+ team_id: str = Field(..., title="Team Id")
31
+ region: str = Field(..., title="Region")
32
+
33
+
34
+ class InstanceRequest(BaseModel):
35
+ env_key: str = Field(..., title="Env Key")
36
+ version: Optional[str] = Field(None, title="Version")
37
+ region: Optional[str] = Field("us-east-2", title="Region")
38
+ seed: Optional[int] = Field(None, title="Seed")
39
+ timestamp: Optional[int] = Field(None, title="Timestamp")
40
+ p_error: Optional[float] = Field(None, title="P Error")
41
+ avg_latency: Optional[float] = Field(None, title="Avg Latency")
42
+ run_id: Optional[str] = Field(None, title="Run Id")
43
+ task_id: Optional[str] = Field(None, title="Task Id")
44
+
45
+
46
+ class InstanceStatus(Enum):
47
+ pending = "pending"
48
+ running = "running"
49
+ stopped = "stopped"
50
+ error = "error"
51
+
52
+
53
+ class ManagerURLs(BaseModel):
54
+ api: str = Field(..., title="Api")
55
+ docs: str = Field(..., title="Docs")
56
+ reset: str = Field(..., title="Reset")
57
+ diff: str = Field(..., title="Diff")
58
+ snapshot: str = Field(..., title="Snapshot")
59
+ execute_verifier_function: str = Field(..., title="Execute Verifier Function")
60
+ execute_verifier_function_with_upload: str = Field(
61
+ ..., title="Execute Verifier Function With Upload"
62
+ )
63
+
64
+
65
+ class ValidationError(BaseModel):
66
+ loc: List[Union[str, int]] = Field(..., title="Location")
67
+ msg: str = Field(..., title="Message")
68
+ type: str = Field(..., title="Error Type")
69
+
70
+
71
+ class HTTPValidationError(BaseModel):
72
+ detail: Optional[List[ValidationError]] = Field(None, title="Detail")
73
+
74
+
75
+ class InstanceURLs(BaseModel):
76
+ root: str = Field(..., title="Root")
77
+ app: str = Field(..., title="App")
78
+ api: Optional[str] = Field(None, title="Api")
79
+ health: Optional[str] = Field(None, title="Health")
80
+ api_docs: Optional[str] = Field(None, title="Api Docs")
81
+ manager: ManagerURLs
82
+
83
+
84
+ class InstanceResponse(BaseModel):
85
+ instance_id: str = Field(..., title="Instance Id")
86
+ env_key: str = Field(..., title="Env Key")
87
+ version: str = Field(..., title="Version")
88
+ status: str = Field(..., title="Status")
89
+ subdomain: str = Field(..., title="Subdomain")
90
+ created_at: str = Field(..., title="Created At")
91
+ updated_at: str = Field(..., title="Updated At")
92
+ terminated_at: Optional[str] = Field(None, title="Terminated At")
93
+ team_id: str = Field(..., title="Team Id")
94
+ region: str = Field(..., title="Region")
95
+ urls: InstanceURLs
96
+ health: Optional[bool] = Field(None, title="Health")
97
+
98
+
99
+ class InstanceRecord(BaseModel):
100
+ instance_id: str
101
+ env_key: str
102
+ version: str
103
+ status: str
104
+ subdomain: str
105
+ created_at: str
106
+ updated_at: str
107
+ terminated_at: Optional[str] = None
108
+ team_id: str
109
+ region: str