fleet-python 0.2.32__py3-none-any.whl → 0.2.34__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/exampleResume.py +191 -0
- examples/test_cdp_logging.py +80 -0
- fleet/client.py +37 -10
- fleet/env/client.py +1 -1
- fleet/global_client.py +1 -1
- fleet/instance/client.py +3 -1
- fleet/resources/mcp.py +27 -26
- fleet/resources/sqlite.py +9 -3
- fleet/tasks.py +25 -133
- fleet/verifiers/verifier.py +7 -0
- {fleet_python-0.2.32.dist-info → fleet_python-0.2.34.dist-info}/METADATA +1 -1
- {fleet_python-0.2.32.dist-info → fleet_python-0.2.34.dist-info}/RECORD +15 -13
- {fleet_python-0.2.32.dist-info → fleet_python-0.2.34.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.32.dist-info → fleet_python-0.2.34.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.32.dist-info → fleet_python-0.2.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Interactive Browser Snapshot/Resume Example
|
|
4
|
+
|
|
5
|
+
This script:
|
|
6
|
+
1. Creates an environment and browser
|
|
7
|
+
2. Lets you interact with it manually
|
|
8
|
+
3. Takes a snapshot when you type 'close'
|
|
9
|
+
4. Resumes from the snapshot with a new environment
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
18
|
+
|
|
19
|
+
from fleet import Fleet
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
# Initialize Fleet client
|
|
23
|
+
api_key = os.getenv("FLEET_API_KEY")
|
|
24
|
+
if not api_key:
|
|
25
|
+
print("Error: FLEET_API_KEY environment variable not set")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
fleet = Fleet(api_key=api_key)
|
|
29
|
+
|
|
30
|
+
# Initialize environment variables
|
|
31
|
+
env = None
|
|
32
|
+
new_env = None
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# Step 1: Create environment and browser
|
|
36
|
+
print("🚀 Creating new environment...")
|
|
37
|
+
env_key = "fira:v1.3.2"
|
|
38
|
+
|
|
39
|
+
env = fleet.make(env_key)
|
|
40
|
+
print(f"✅ Environment created: {env.instance_id}")
|
|
41
|
+
print(f"📝 Session ID: {env.session_id}")
|
|
42
|
+
|
|
43
|
+
# Start browser
|
|
44
|
+
print("\n🌐 Starting browser...")
|
|
45
|
+
browser = env.browser()
|
|
46
|
+
browser.start(width=1920, height=1080)
|
|
47
|
+
|
|
48
|
+
# Get browser URLs with session ID for automatic logging
|
|
49
|
+
cdp_page_url = browser.cdp_page_url()
|
|
50
|
+
cdp_browser_url = browser.cdp_browser_url()
|
|
51
|
+
devtools_url = browser.devtools_url()
|
|
52
|
+
|
|
53
|
+
print(f"✅ Browser started!")
|
|
54
|
+
print(f"🔗 CDP Page URL: {cdp_page_url}")
|
|
55
|
+
print(f"🔗 CDP Browser URL: {cdp_browser_url}")
|
|
56
|
+
print(f"🔗 DevTools URL: {devtools_url}")
|
|
57
|
+
|
|
58
|
+
# Show connection info for debugging
|
|
59
|
+
conn_info = browser.get_connection_info()
|
|
60
|
+
print(f"📊 Logging enabled: {conn_info['logging_enabled']}")
|
|
61
|
+
if conn_info['logging_enabled']:
|
|
62
|
+
print(f"📝 All CDP actions will be automatically logged to session: {env.session_id}")
|
|
63
|
+
|
|
64
|
+
# Step 2: Wait for user interaction
|
|
65
|
+
print("\n" + "="*60)
|
|
66
|
+
print("🎮 INTERACTIVE MODE")
|
|
67
|
+
print("="*60)
|
|
68
|
+
print("You can now interact with the browser manually.")
|
|
69
|
+
print("Open the DevTools URL in Chrome to see and control the browser.")
|
|
70
|
+
print("\nType 'close' and press Enter when you're done to save a snapshot.")
|
|
71
|
+
print("="*60 + "\n")
|
|
72
|
+
|
|
73
|
+
while True:
|
|
74
|
+
user_input = input(">>> ").strip().lower()
|
|
75
|
+
|
|
76
|
+
if user_input == "close":
|
|
77
|
+
break
|
|
78
|
+
elif user_input == "status":
|
|
79
|
+
print(f"Environment: {env.instance_id}")
|
|
80
|
+
print(f"Session: {env.session_id}")
|
|
81
|
+
else:
|
|
82
|
+
print("Type 'close' to save snapshot and exit, or 'status' to see current info")
|
|
83
|
+
|
|
84
|
+
# Step 3: Take snapshot
|
|
85
|
+
print("\n📸 Taking snapshot...")
|
|
86
|
+
snapshot = env.get_snapshot(browser)
|
|
87
|
+
|
|
88
|
+
# Save snapshot to file
|
|
89
|
+
snapshot_file = f"snapshot_{env.session_id}_{int(datetime.now().timestamp())}.json"
|
|
90
|
+
with open(snapshot_file, "w") as f:
|
|
91
|
+
json.dump(snapshot.model_dump(), f, indent=2)
|
|
92
|
+
|
|
93
|
+
print(f"✅ Snapshot saved to: {snapshot_file}")
|
|
94
|
+
print(f" - Tool logs: {len(snapshot.tool_logs)} entries")
|
|
95
|
+
print(f" - Action logs: {len(snapshot.action_logs)} entries")
|
|
96
|
+
print(f" - Page URL: {snapshot.page_url}")
|
|
97
|
+
print(f" - Viewport: {snapshot.viewport_size}")
|
|
98
|
+
|
|
99
|
+
# Close original environment
|
|
100
|
+
print("\n🏁 Closing original environment...")
|
|
101
|
+
env.close()
|
|
102
|
+
env = None # Mark as closed
|
|
103
|
+
|
|
104
|
+
# Step 4: Resume from snapshot
|
|
105
|
+
print("\n" + "="*60)
|
|
106
|
+
print("🔄 RESUMING FROM SNAPSHOT")
|
|
107
|
+
print("="*60)
|
|
108
|
+
|
|
109
|
+
input("\nPress Enter to resume from snapshot...")
|
|
110
|
+
|
|
111
|
+
print("\n🚀 Creating new environment from snapshot...")
|
|
112
|
+
new_env, validation = fleet.resume(
|
|
113
|
+
snapshot,
|
|
114
|
+
validate=True,
|
|
115
|
+
playback_speed=2.0 # Replay at 2x speed
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(f"✅ Environment resumed: {new_env.instance_id}")
|
|
119
|
+
print(f"📝 New session ID: {new_env.session_id}")
|
|
120
|
+
|
|
121
|
+
# Show validation results
|
|
122
|
+
print(f"\n📊 Validation Results:")
|
|
123
|
+
print(f" - Success: {validation.success}")
|
|
124
|
+
print(f" - Page match: {validation.page_match}")
|
|
125
|
+
print(f" - Action log match: {validation.action_log_match}")
|
|
126
|
+
|
|
127
|
+
if validation.discrepancies:
|
|
128
|
+
print(f" - Discrepancies ({len(validation.discrepancies)}):")
|
|
129
|
+
for disc in validation.discrepancies[:5]: # Show first 5
|
|
130
|
+
print(f" • {disc}")
|
|
131
|
+
if len(validation.discrepancies) > 5:
|
|
132
|
+
print(f" • ... and {len(validation.discrepancies) - 5} more")
|
|
133
|
+
|
|
134
|
+
# Get new browser reference
|
|
135
|
+
new_browser = new_env.browser()
|
|
136
|
+
print(f"\n🌐 New browser ready!")
|
|
137
|
+
print(f"🔗 CDP Page URL: {new_browser.cdp_page_url()}")
|
|
138
|
+
print(f"🔗 DevTools URL: {new_browser.devtools_url()}")
|
|
139
|
+
print(f"📝 Resume session logging to: {new_env.session_id}")
|
|
140
|
+
|
|
141
|
+
# Step 5: Keep new environment open for inspection
|
|
142
|
+
print("\n" + "="*60)
|
|
143
|
+
print("🎮 RESUMED ENVIRONMENT READY")
|
|
144
|
+
print("="*60)
|
|
145
|
+
print("The resumed environment is now ready. You can inspect it to verify")
|
|
146
|
+
print("it matches your original state.")
|
|
147
|
+
print("\nType 'done' when finished to close the resumed environment.")
|
|
148
|
+
print("="*60 + "\n")
|
|
149
|
+
|
|
150
|
+
while True:
|
|
151
|
+
user_input = input(">>> ").strip().lower()
|
|
152
|
+
|
|
153
|
+
if user_input == "done":
|
|
154
|
+
break
|
|
155
|
+
elif user_input == "status":
|
|
156
|
+
print(f"Environment: {new_env.instance_id}")
|
|
157
|
+
print(f"Session: {new_env.session_id}")
|
|
158
|
+
else:
|
|
159
|
+
print("Type 'done' to close the environment, or 'status' to see current info")
|
|
160
|
+
|
|
161
|
+
# Clean up
|
|
162
|
+
print("\n🏁 Closing resumed environment...")
|
|
163
|
+
new_env.close()
|
|
164
|
+
new_env = None # Mark as closed
|
|
165
|
+
print("✅ All done!")
|
|
166
|
+
|
|
167
|
+
except KeyboardInterrupt:
|
|
168
|
+
print("\n\n⚠️ Interrupted by user")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f"\n❌ Error: {e}")
|
|
171
|
+
finally:
|
|
172
|
+
# Always clean up environments
|
|
173
|
+
if env is not None:
|
|
174
|
+
try:
|
|
175
|
+
print("\n🧹 Cleaning up original environment...")
|
|
176
|
+
env.close()
|
|
177
|
+
print("✅ Original environment closed")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"⚠️ Failed to close original environment: {e}")
|
|
180
|
+
|
|
181
|
+
if new_env is not None:
|
|
182
|
+
try:
|
|
183
|
+
print("\n🧹 Cleaning up resumed environment...")
|
|
184
|
+
new_env.close()
|
|
185
|
+
print("✅ Resumed environment closed")
|
|
186
|
+
except Exception as e:
|
|
187
|
+
print(f"⚠️ Failed to close resumed environment: {e}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test CDP Logging Example
|
|
4
|
+
|
|
5
|
+
This script demonstrates the new automatic CDP logging functionality.
|
|
6
|
+
All CDP commands and events are now logged automatically at the proxy level.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
14
|
+
|
|
15
|
+
from fleet import Fleet
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
# Initialize Fleet client
|
|
19
|
+
api_key = os.getenv("FLEET_API_KEY")
|
|
20
|
+
if not api_key:
|
|
21
|
+
print("Error: FLEET_API_KEY environment variable not set")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
fleet = Fleet(api_key=api_key)
|
|
25
|
+
env = None
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# Create environment
|
|
29
|
+
print("🚀 Creating new environment...")
|
|
30
|
+
env_key = "fira:v1.3.2" # Replace with your actual environment key
|
|
31
|
+
|
|
32
|
+
env = fleet.make(env_key)
|
|
33
|
+
print(f"✅ Environment created: {env.instance_id}")
|
|
34
|
+
print(f"📝 Session ID: {env.session_id}")
|
|
35
|
+
|
|
36
|
+
# Start browser
|
|
37
|
+
print("\n🌐 Starting browser with automatic CDP logging...")
|
|
38
|
+
browser = env.browser()
|
|
39
|
+
browser.start(width=1920, height=1080)
|
|
40
|
+
|
|
41
|
+
# Show connection information
|
|
42
|
+
conn_info = browser.get_connection_info()
|
|
43
|
+
print("\n📊 Connection Information:")
|
|
44
|
+
print(f" Session ID: {conn_info['session_id']}")
|
|
45
|
+
print(f" CDP Page URL: {conn_info['cdp_page_url']}")
|
|
46
|
+
print(f" CDP Browser URL: {conn_info['cdp_browser_url']}")
|
|
47
|
+
print(f" Logging Enabled: {conn_info['logging_enabled']}")
|
|
48
|
+
|
|
49
|
+
if conn_info['logging_enabled']:
|
|
50
|
+
print(f"\n✅ All CDP actions will be automatically logged!")
|
|
51
|
+
print(f" - Connect any CDP client to: {conn_info['cdp_page_url']}")
|
|
52
|
+
print(f" - All commands and events will be logged to session: {env.session_id}")
|
|
53
|
+
print(f" - Use DevTools at: {conn_info['devtools_url']}")
|
|
54
|
+
else:
|
|
55
|
+
print(f"\n⚠️ Logging not enabled - no session ID available")
|
|
56
|
+
|
|
57
|
+
print("\n" + "="*60)
|
|
58
|
+
print("🎯 LOGGING TEST COMPLETE")
|
|
59
|
+
print("="*60)
|
|
60
|
+
print("The browser is ready with automatic CDP logging enabled.")
|
|
61
|
+
print("Connect any CDP client or Playwright to the URLs above.")
|
|
62
|
+
print("All actions will be automatically captured in the tool_log table.")
|
|
63
|
+
print("\nPress Enter to close the environment...")
|
|
64
|
+
print("="*60 + "\n")
|
|
65
|
+
|
|
66
|
+
input()
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"\n❌ Error: {e}")
|
|
70
|
+
finally:
|
|
71
|
+
if env is not None:
|
|
72
|
+
try:
|
|
73
|
+
print("\n🧹 Cleaning up environment...")
|
|
74
|
+
env.close()
|
|
75
|
+
print("✅ Environment closed")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"⚠️ Failed to close environment: {e}")
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
fleet/client.py
CHANGED
|
@@ -50,7 +50,7 @@ from .instance.client import ValidatorType
|
|
|
50
50
|
from .resources.base import Resource
|
|
51
51
|
from .resources.sqlite import SQLiteResource
|
|
52
52
|
from .resources.browser import BrowserResource
|
|
53
|
-
from
|
|
53
|
+
from ..resources.mcp import MCPResource
|
|
54
54
|
|
|
55
55
|
logger = logging.getLogger(__name__)
|
|
56
56
|
|
|
@@ -254,7 +254,9 @@ class Fleet:
|
|
|
254
254
|
def execute_verifier_remote(
|
|
255
255
|
self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
|
|
256
256
|
) -> VerifiersExecuteResponse:
|
|
257
|
-
return _execute_verifier_remote(
|
|
257
|
+
return _execute_verifier_remote(
|
|
258
|
+
self.client, bundle_data, args, kwargs, timeout
|
|
259
|
+
)
|
|
258
260
|
|
|
259
261
|
def delete(self, instance_id: str) -> InstanceResponse:
|
|
260
262
|
return _delete_instance(self.client, instance_id)
|
|
@@ -265,7 +267,9 @@ class Fleet:
|
|
|
265
267
|
|
|
266
268
|
return self.load_task_array_from_string(tasks_data)
|
|
267
269
|
|
|
268
|
-
def load_task_array_from_string(
|
|
270
|
+
def load_task_array_from_string(
|
|
271
|
+
self, serialized_tasks: List[Dict]
|
|
272
|
+
) -> List[Task]:
|
|
269
273
|
tasks = []
|
|
270
274
|
|
|
271
275
|
json_tasks = json.loads(serialized_tasks)
|
|
@@ -303,11 +307,20 @@ class Fleet:
|
|
|
303
307
|
)
|
|
304
308
|
return task
|
|
305
309
|
|
|
306
|
-
def load_tasks(
|
|
307
|
-
|
|
310
|
+
def load_tasks(
|
|
311
|
+
self,
|
|
312
|
+
env_key: Optional[str] = None,
|
|
313
|
+
keys: Optional[List[str]] = None,
|
|
314
|
+
version: Optional[str] = None,
|
|
315
|
+
team_id: Optional[str] = None
|
|
316
|
+
) -> List[Task]:
|
|
317
|
+
"""Load tasks for the authenticated team, with optional filtering.
|
|
308
318
|
|
|
309
319
|
Args:
|
|
310
320
|
env_key: Optional environment key to filter tasks by
|
|
321
|
+
keys: Optional list of task keys to filter by
|
|
322
|
+
version: Optional version to filter tasks by (client-side filter)
|
|
323
|
+
team_id: Optional team_id to filter by (admin only)
|
|
311
324
|
|
|
312
325
|
Returns:
|
|
313
326
|
List[Task] containing Task objects
|
|
@@ -315,6 +328,10 @@ class Fleet:
|
|
|
315
328
|
params = {}
|
|
316
329
|
if env_key is not None:
|
|
317
330
|
params["env_key"] = env_key
|
|
331
|
+
if keys is not None:
|
|
332
|
+
params["task_keys"] = keys
|
|
333
|
+
if team_id is not None:
|
|
334
|
+
params["team_id"] = team_id
|
|
318
335
|
|
|
319
336
|
response = self.client.request("GET", "/v1/tasks", params=params)
|
|
320
337
|
task_list_response = TaskListResponse(**response.json())
|
|
@@ -375,6 +392,10 @@ class Fleet:
|
|
|
375
392
|
)
|
|
376
393
|
tasks.append(task)
|
|
377
394
|
|
|
395
|
+
# Apply client-side filtering for version if specified
|
|
396
|
+
if version is not None:
|
|
397
|
+
tasks = [task for task in tasks if task.version == version]
|
|
398
|
+
|
|
378
399
|
return tasks
|
|
379
400
|
|
|
380
401
|
def export_tasks(
|
|
@@ -464,7 +485,7 @@ class Fleet:
|
|
|
464
485
|
def _create_verifier_from_data(
|
|
465
486
|
self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
|
|
466
487
|
) -> "SyncVerifierFunction":
|
|
467
|
-
"""Create
|
|
488
|
+
"""Create an AsyncVerifierFunction from verifier data.
|
|
468
489
|
|
|
469
490
|
Args:
|
|
470
491
|
verifier_id: The verifier ID
|
|
@@ -473,10 +494,16 @@ class Fleet:
|
|
|
473
494
|
verifier_sha: The verifier SHA256
|
|
474
495
|
|
|
475
496
|
Returns:
|
|
476
|
-
|
|
497
|
+
AsyncVerifierFunction created from the verifier code
|
|
477
498
|
"""
|
|
478
|
-
from
|
|
499
|
+
from ..tasks import verifier_from_string
|
|
479
500
|
from .verifiers.verifier import SyncVerifierFunction
|
|
501
|
+
|
|
502
|
+
# Convert async verifier code to sync
|
|
503
|
+
if 'async def' in verifier_code:
|
|
504
|
+
verifier_code = verifier_code.replace('async def', 'def')
|
|
505
|
+
if 'await ' in verifier_code:
|
|
506
|
+
verifier_code = verifier_code.replace('await ', '')
|
|
480
507
|
|
|
481
508
|
# Use verifier_from_string to create the verifier
|
|
482
509
|
verifier_func = verifier_from_string(
|
|
@@ -486,10 +513,10 @@ class Fleet:
|
|
|
486
513
|
sha256=verifier_sha,
|
|
487
514
|
)
|
|
488
515
|
|
|
489
|
-
# Ensure we return
|
|
516
|
+
# Ensure we return an AsyncVerifierFunction
|
|
490
517
|
if not isinstance(verifier_func, SyncVerifierFunction):
|
|
491
518
|
raise TypeError(
|
|
492
|
-
f"Expected
|
|
519
|
+
f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
493
520
|
)
|
|
494
521
|
|
|
495
522
|
# Store the original verifier code for reference
|
fleet/env/client.py
CHANGED
fleet/global_client.py
CHANGED
fleet/instance/client.py
CHANGED
|
@@ -63,7 +63,9 @@ class InstanceClient:
|
|
|
63
63
|
def load(self) -> None:
|
|
64
64
|
self._load_resources()
|
|
65
65
|
|
|
66
|
-
def reset(
|
|
66
|
+
def reset(
|
|
67
|
+
self, reset_request: Optional[ResetRequest] = None
|
|
68
|
+
) -> ResetResponse:
|
|
67
69
|
response = self.client.request(
|
|
68
70
|
"POST", "/reset", json=reset_request.model_dump() if reset_request else None
|
|
69
71
|
)
|
fleet/resources/mcp.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Dict
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class SyncMCPResource:
|
|
5
5
|
def __init__(self, url: str, env_key: str):
|
|
6
6
|
self.url = url
|
|
7
7
|
self._env_key = env_key
|
|
@@ -22,33 +22,34 @@ class MCPResource:
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
def list_tools(self):
|
|
25
|
-
import
|
|
25
|
+
import aiohttp
|
|
26
26
|
|
|
27
27
|
"""
|
|
28
|
-
Make
|
|
29
|
-
|
|
28
|
+
Make an async request to list available tools from the MCP endpoint.
|
|
29
|
+
|
|
30
30
|
Returns:
|
|
31
31
|
List of available tools with name, description, and input_schema
|
|
32
32
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
33
|
+
with aiohttp.ClientSession() as session:
|
|
34
|
+
payload = {"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2}
|
|
35
|
+
|
|
36
|
+
with session.post(self.url, json=payload) as response:
|
|
37
|
+
data = response.json()
|
|
38
|
+
|
|
39
|
+
# Extract tools from the response
|
|
40
|
+
if "result" in data and "tools" in data["result"]:
|
|
41
|
+
tools = data["result"]["tools"]
|
|
42
|
+
|
|
43
|
+
available_tools = [
|
|
44
|
+
{
|
|
45
|
+
"name": tool.get("name"),
|
|
46
|
+
"description": tool.get("description"),
|
|
47
|
+
"input_schema": tool.get("inputSchema"),
|
|
48
|
+
}
|
|
49
|
+
for tool in tools
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
return available_tools
|
|
53
|
+
else:
|
|
54
|
+
# Handle error or empty response
|
|
55
|
+
return []
|
fleet/resources/sqlite.py
CHANGED
|
@@ -651,7 +651,9 @@ class SQLiteResource(Resource):
|
|
|
651
651
|
)
|
|
652
652
|
return DescribeResponse(**response.json())
|
|
653
653
|
|
|
654
|
-
def query(
|
|
654
|
+
def query(
|
|
655
|
+
self, query: str, args: Optional[List[Any]] = None
|
|
656
|
+
) -> QueryResponse:
|
|
655
657
|
return self._query(query, args, read_only=True)
|
|
656
658
|
|
|
657
659
|
def exec(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
|
|
@@ -693,8 +695,12 @@ class SQLiteResource(Resource):
|
|
|
693
695
|
AsyncSnapshotDiff: Object containing the differences between the two databases
|
|
694
696
|
"""
|
|
695
697
|
# Create snapshots of both databases
|
|
696
|
-
before_snapshot = self.snapshot(
|
|
697
|
-
|
|
698
|
+
before_snapshot = self.snapshot(
|
|
699
|
+
name=f"before_{datetime.utcnow().isoformat()}"
|
|
700
|
+
)
|
|
701
|
+
after_snapshot = other.snapshot(
|
|
702
|
+
name=f"after_{datetime.utcnow().isoformat()}"
|
|
703
|
+
)
|
|
698
704
|
|
|
699
705
|
# Return the diff between the snapshots
|
|
700
706
|
return before_snapshot.diff(after_snapshot, ignore_config)
|
fleet/tasks.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import asyncio
|
|
6
5
|
import re
|
|
7
6
|
from datetime import datetime
|
|
8
7
|
from typing import Any, Dict, Optional, List
|
|
8
|
+
from uuid import UUID
|
|
9
9
|
|
|
10
10
|
from pydantic import BaseModel, Field, validator
|
|
11
11
|
|
|
@@ -13,133 +13,6 @@ from pydantic import BaseModel, Field, validator
|
|
|
13
13
|
from fleet.types import VerifierFunction
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def verifier_from_string(
|
|
17
|
-
verifier_func: str,
|
|
18
|
-
verifier_id: Optional[str] = None,
|
|
19
|
-
verifier_key: Optional[str] = None,
|
|
20
|
-
sha256: Optional[str] = None,
|
|
21
|
-
) -> VerifierFunction:
|
|
22
|
-
"""Create a verifier function from a string of Python code.
|
|
23
|
-
|
|
24
|
-
This function creates either a SyncVerifierFunction or AsyncVerifierFunction
|
|
25
|
-
based on whether the code contains async function definitions.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
verifier_func: String containing the verifier function code
|
|
29
|
-
verifier_id: Optional verifier ID to use
|
|
30
|
-
verifier_key: Optional verifier key to use (defaults to function name)
|
|
31
|
-
sha256: Optional SHA256 hash of existing server-side bundle
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
VerifierFunction: Either SyncVerifierFunction or AsyncVerifierFunction
|
|
35
|
-
|
|
36
|
-
Raises:
|
|
37
|
-
ValueError: If function name cannot be extracted from the code
|
|
38
|
-
"""
|
|
39
|
-
from fleet.verifiers.parse import extract_function_name, convert_new_to_old_verifier
|
|
40
|
-
from fleet.verifiers.verifier import SyncVerifierFunction
|
|
41
|
-
from fleet._async.verifiers.verifier import AsyncVerifierFunction
|
|
42
|
-
from fleet.verifiers.db import IgnoreConfig, DatabaseSnapshot
|
|
43
|
-
|
|
44
|
-
# Determine if this is an async verifier
|
|
45
|
-
is_async = "async def" in verifier_func
|
|
46
|
-
|
|
47
|
-
# Store original code for later
|
|
48
|
-
original_code = verifier_func
|
|
49
|
-
|
|
50
|
-
# Check if this is a new format verifier (has before/after parameters)
|
|
51
|
-
if (
|
|
52
|
-
"before: DatabaseSnapshot" in verifier_func
|
|
53
|
-
and "after: DatabaseSnapshot" in verifier_func
|
|
54
|
-
):
|
|
55
|
-
# Convert new format to old format
|
|
56
|
-
verifier_func = convert_new_to_old_verifier(verifier_func)
|
|
57
|
-
# Update function name since wrapper adds _wrapper suffix
|
|
58
|
-
original_name = extract_function_name(verifier_func.split("\n")[0])
|
|
59
|
-
if original_name and original_name.endswith("_wrapper"):
|
|
60
|
-
function_name = original_name
|
|
61
|
-
else:
|
|
62
|
-
function_name = extract_function_name(verifier_func)
|
|
63
|
-
else:
|
|
64
|
-
# Extract function name from code
|
|
65
|
-
function_name = extract_function_name(verifier_func)
|
|
66
|
-
|
|
67
|
-
if not function_name:
|
|
68
|
-
raise ValueError("Could not extract function name from verifier code")
|
|
69
|
-
|
|
70
|
-
# Create a namespace for the function
|
|
71
|
-
namespace = {
|
|
72
|
-
"__builtins__": __builtins__,
|
|
73
|
-
"Environment": object, # Placeholder, will be provided at runtime
|
|
74
|
-
"IgnoreConfig": IgnoreConfig,
|
|
75
|
-
"DatabaseSnapshot": DatabaseSnapshot,
|
|
76
|
-
"TASK_FAILED_SCORE": 0,
|
|
77
|
-
"TASK_SUCCESSFUL_SCORE": 1,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Execute the code to create the function
|
|
81
|
-
exec(verifier_func, namespace)
|
|
82
|
-
|
|
83
|
-
# Get the function from the namespace
|
|
84
|
-
if function_name not in namespace:
|
|
85
|
-
raise ValueError(f"Function {function_name} not found after execution")
|
|
86
|
-
|
|
87
|
-
func = namespace[function_name]
|
|
88
|
-
|
|
89
|
-
# Use provided key or default to function name
|
|
90
|
-
key = verifier_key or function_name
|
|
91
|
-
|
|
92
|
-
# Create appropriate verifier function
|
|
93
|
-
if is_async:
|
|
94
|
-
# Create AsyncVerifierFunction
|
|
95
|
-
return AsyncVerifierFunction(
|
|
96
|
-
func=func,
|
|
97
|
-
key=key,
|
|
98
|
-
verifier_id=verifier_id,
|
|
99
|
-
sha256=sha256,
|
|
100
|
-
raw_code=original_code,
|
|
101
|
-
extra_requirements=None,
|
|
102
|
-
)
|
|
103
|
-
else:
|
|
104
|
-
# For sync verifiers, we need to handle the case where the original was async
|
|
105
|
-
# but got converted during unasync processing
|
|
106
|
-
if "async def" in original_code and "await " in original_code:
|
|
107
|
-
# Convert async code to sync for SyncVerifierFunction
|
|
108
|
-
sync_code = original_code.replace("async def", "def")
|
|
109
|
-
sync_code = sync_code.replace("await ", "")
|
|
110
|
-
|
|
111
|
-
# Re-execute with sync code
|
|
112
|
-
namespace = {
|
|
113
|
-
"__builtins__": __builtins__,
|
|
114
|
-
"Environment": object,
|
|
115
|
-
"IgnoreConfig": IgnoreConfig,
|
|
116
|
-
"DatabaseSnapshot": DatabaseSnapshot,
|
|
117
|
-
"TASK_FAILED_SCORE": 0,
|
|
118
|
-
"TASK_SUCCESSFUL_SCORE": 1,
|
|
119
|
-
}
|
|
120
|
-
exec(sync_code, namespace)
|
|
121
|
-
func = namespace[function_name]
|
|
122
|
-
|
|
123
|
-
return SyncVerifierFunction(
|
|
124
|
-
func=func,
|
|
125
|
-
key=key,
|
|
126
|
-
verifier_id=verifier_id,
|
|
127
|
-
sha256=sha256,
|
|
128
|
-
raw_code=sync_code,
|
|
129
|
-
extra_requirements=None,
|
|
130
|
-
)
|
|
131
|
-
else:
|
|
132
|
-
# Already sync code
|
|
133
|
-
return SyncVerifierFunction(
|
|
134
|
-
func=func,
|
|
135
|
-
key=key,
|
|
136
|
-
verifier_id=verifier_id,
|
|
137
|
-
sha256=sha256,
|
|
138
|
-
raw_code=original_code,
|
|
139
|
-
extra_requirements=None,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
16
|
class Task(BaseModel):
|
|
144
17
|
"""A task model representing a single task in the Fleet system."""
|
|
145
18
|
|
|
@@ -206,7 +79,7 @@ class Task(BaseModel):
|
|
|
206
79
|
if inspect.iscoroutine(result):
|
|
207
80
|
# Check if we're already in an event loop
|
|
208
81
|
try:
|
|
209
|
-
|
|
82
|
+
loop = asyncio.get_running_loop()
|
|
210
83
|
# We're in an async context, can't use asyncio.run()
|
|
211
84
|
raise RuntimeError(
|
|
212
85
|
"Cannot run async verifier in sync mode while event loop is running. "
|
|
@@ -251,13 +124,32 @@ class Task(BaseModel):
|
|
|
251
124
|
return Fleet().make(env_key=self.env_key, region=region)
|
|
252
125
|
|
|
253
126
|
|
|
254
|
-
def load_tasks(
|
|
255
|
-
|
|
127
|
+
def load_tasks(
|
|
128
|
+
env_key: Optional[str] = None,
|
|
129
|
+
keys: Optional[List[str]] = None,
|
|
130
|
+
version: Optional[str] = None,
|
|
131
|
+
team_id: Optional[str] = None
|
|
132
|
+
) -> List[Task]:
|
|
133
|
+
"""Convenience function to load tasks with optional filtering.
|
|
256
134
|
|
|
257
|
-
|
|
135
|
+
Args:
|
|
136
|
+
env_key: Optional environment key to filter tasks by
|
|
137
|
+
keys: Optional list of task keys to filter by
|
|
138
|
+
version: Optional version to filter tasks by
|
|
139
|
+
team_id: Optional team_id to filter by (admin only)
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
tasks = await fleet.load_tasks(env_key="fira")
|
|
143
|
+
tasks = await fleet.load_tasks(keys=["task1", "task2"])
|
|
144
|
+
tasks = await fleet.load_tasks(env_key="fira", version="v1.0")
|
|
258
145
|
"""
|
|
259
146
|
# Use the global client by default so users can pre-configure it once
|
|
260
147
|
from .global_client import get_client
|
|
261
148
|
|
|
262
149
|
client = get_client()
|
|
263
|
-
return client.load_tasks(
|
|
150
|
+
return client.load_tasks(
|
|
151
|
+
env_key=env_key,
|
|
152
|
+
keys=keys,
|
|
153
|
+
version=version,
|
|
154
|
+
team_id=team_id
|
|
155
|
+
)
|
fleet/verifiers/verifier.py
CHANGED
|
@@ -162,6 +162,13 @@ class SyncVerifierFunction:
|
|
|
162
162
|
|
|
163
163
|
def remote(self, env: SyncEnv, *args, **kwargs) -> float:
|
|
164
164
|
"""Remote execution of the verifier function with SHA-based bundle caching."""
|
|
165
|
+
# Async verifiers are now supported by the backend
|
|
166
|
+
# if self._is_async:
|
|
167
|
+
# raise NotImplementedError(
|
|
168
|
+
# f"Async verifier '{self.key}' cannot be executed remotely. "
|
|
169
|
+
# "The remote execution environment only supports synchronous functions. "
|
|
170
|
+
# "Please provide a synchronous version of your verifier."
|
|
171
|
+
# )
|
|
165
172
|
|
|
166
173
|
args_array = list(args)
|
|
167
174
|
args_array.append({"env": env.instance_id})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
examples/diff_example.py,sha256=iLlpBW_NBjzXBqlvYwjx74uxYZkMGJfea6s3tJhvuNY,5684
|
|
2
2
|
examples/dsl_example.py,sha256=yFLgM-Was4-w575xJgPk9DIBmXa34hLJsIB4XwTADOE,7252
|
|
3
3
|
examples/example.py,sha256=yn9mqS2yJ6896s25btnJx9-_SLLbyS-Fu-SIcas6jok,1081
|
|
4
|
+
examples/exampleResume.py,sha256=hzdL9QfYtwlje5geWS2cgWgjcnLX4UtXSAd-92F66Lw,7044
|
|
4
5
|
examples/example_account.py,sha256=t5_Tnr7DcLYfNpEAbuBySQIqsqiQQGySuiItIghCjAM,225
|
|
5
6
|
examples/example_action_log.py,sha256=pwvLro_Fkrw4DII002bHGuWfoZ6QRvUMDD9BnMqJgLQ,622
|
|
6
7
|
examples/example_client.py,sha256=M9Mfi1FcD2LtSDVk89R_-tgG98swvDYy4qx2zVayJ-0,1025
|
|
@@ -17,14 +18,15 @@ examples/openai_example.py,sha256=dEWERrTEP5xBiGkLkQjBQGd2NqoxX6gcW6XteBPsWFQ,82
|
|
|
17
18
|
examples/openai_simple_example.py,sha256=HmiufucrAZne7tHq9uoEsDWlEhjNC265bQAyIGBRU2o,1745
|
|
18
19
|
examples/query_builder_example.py,sha256=-cOMfWGNifYfYEt_Ds73XpwATZvFDL6F4KTkVxdMjzg,3951
|
|
19
20
|
examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
|
|
21
|
+
examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
|
|
20
22
|
fleet/__init__.py,sha256=LYJ8zS6Ldo5wLpRqsFMoiikkyosTmm7sRTUY4SnJAgE,3880
|
|
21
23
|
fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
|
|
22
|
-
fleet/client.py,sha256=
|
|
24
|
+
fleet/client.py,sha256=JG9soTIkGK_uRezHU79PFn7PBXeN1iNpun1D4JDFTyE,22310
|
|
23
25
|
fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
|
|
24
26
|
fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
25
|
-
fleet/global_client.py,sha256=
|
|
27
|
+
fleet/global_client.py,sha256=oSWhV1cggVKQ0ec0YDOGu6Zr0Tgdcx3oKMM6s2Y9fQw,1073
|
|
26
28
|
fleet/models.py,sha256=9tDjgcgKPMnf-R_MDh-Ocp_UMbyJ8tJyjb15XqU0N94,12454
|
|
27
|
-
fleet/tasks.py,sha256=
|
|
29
|
+
fleet/tasks.py,sha256=1AlNhe0WRggj3pIwTeaxMhpmMqI0MGBVjEmODVu37YI,5648
|
|
28
30
|
fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
|
|
29
31
|
fleet/_async/__init__.py,sha256=1X00u-0Zu4S-e8MGE9nJWbZEt3H-DHZA-gfwN-JuP2U,7415
|
|
30
32
|
fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
|
|
@@ -47,16 +49,16 @@ fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMT
|
|
|
47
49
|
fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
48
50
|
fleet/_async/verifiers/verifier.py,sha256=lwVIV5ZpWJhM87tXShtjwN5KP7n5XDcPq0XX7AjV6_E,14343
|
|
49
51
|
fleet/env/__init__.py,sha256=6-zgP705M61tCquiDKw29dBZGq1US9mtsB1gQngJ4FQ,664
|
|
50
|
-
fleet/env/client.py,sha256=
|
|
52
|
+
fleet/env/client.py,sha256=I4pjnXGzifZSr7iZFdn0cTX6nLuGYo-sCt-qk4ez29Y,805
|
|
51
53
|
fleet/instance/__init__.py,sha256=CyWUkbGAK-DBPw4DC4AnCW-MqqheGhZMA5QSRVu-ws4,479
|
|
52
54
|
fleet/instance/base.py,sha256=OYqzBwZFfTX9wlBGSG5gljqj98NbiJeKIfFJ3uj5I4s,1587
|
|
53
|
-
fleet/instance/client.py,sha256=
|
|
55
|
+
fleet/instance/client.py,sha256=O6B0A2Z0b5SxOLs4TipZ9Ol8yG-b-LG15vVOKMmd6BQ,5908
|
|
54
56
|
fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
|
|
55
57
|
fleet/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
58
|
fleet/resources/base.py,sha256=AXZzT0_yWHkT497q3yekfr0xsD4cPGMCC6y7C43TIkk,663
|
|
57
59
|
fleet/resources/browser.py,sha256=hRNM0YMsVQUAraZGNi_B-KXxLpuddy4ntoEDFSw7czU,1295
|
|
58
|
-
fleet/resources/mcp.py,sha256=
|
|
59
|
-
fleet/resources/sqlite.py,sha256=
|
|
60
|
+
fleet/resources/mcp.py,sha256=c6O4vVJnXANuHMGMe4IPxgp4zBEbFaGm6_d9e6j8Myc,1695
|
|
61
|
+
fleet/resources/sqlite.py,sha256=rwpYmfyw8CjlA-HEsD4fCqYS8wFMxaTiX_tO3gJxDjg,25763
|
|
60
62
|
fleet/verifiers/__init__.py,sha256=IlImvlfgMRQ2V6f4_n7skXYBV8ZoUUjnWaFKnYQQsU8,459
|
|
61
63
|
fleet/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
62
64
|
fleet/verifiers/code.py,sha256=A1i_UabZspbyj1awzKVQ_HRxgMO3fU7NbkxYyTrp7So,48
|
|
@@ -64,11 +66,11 @@ fleet/verifiers/db.py,sha256=tssmvJjDHuBIy8qlL_P5-UdmEFUw2DZcqLsWZ8ot3Xw,27766
|
|
|
64
66
|
fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,3260
|
|
65
67
|
fleet/verifiers/parse.py,sha256=0bAbj9VvT__yU4ZVREUK-Tn9dukh9LCpmfVsgj1DfP4,8508
|
|
66
68
|
fleet/verifiers/sql_differ.py,sha256=dmiGCFXVMEMbAX519OjhVqgA8ZvhnvdmC1BVpL7QCF0,6490
|
|
67
|
-
fleet/verifiers/verifier.py,sha256=
|
|
68
|
-
fleet_python-0.2.
|
|
69
|
+
fleet/verifiers/verifier.py,sha256=53oBWAf0yy3bZmZx9eH9AWIf65H7OP2UUm0YwWCL6Mc,14286
|
|
70
|
+
fleet_python-0.2.34.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
69
71
|
scripts/fix_sync_imports.py,sha256=0XKTkAV7WdMxRfk8-x4Ts1LjSbUpyI0tPL0DcTQ_38w,7308
|
|
70
72
|
scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
|
|
71
|
-
fleet_python-0.2.
|
|
72
|
-
fleet_python-0.2.
|
|
73
|
-
fleet_python-0.2.
|
|
74
|
-
fleet_python-0.2.
|
|
73
|
+
fleet_python-0.2.34.dist-info/METADATA,sha256=WmolSCZR49qXHttFTyPhRnEJxj6po_YbDeUhOnk4t9M,3354
|
|
74
|
+
fleet_python-0.2.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
fleet_python-0.2.34.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
|
|
76
|
+
fleet_python-0.2.34.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|