fleet-python 0.2.32__tar.gz → 0.2.35__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- {fleet_python-0.2.32 → fleet_python-0.2.35}/PKG-INFO +1 -1
- fleet_python-0.2.35/examples/exampleResume.py +191 -0
- fleet_python-0.2.35/examples/test_cdp_logging.py +80 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/__init__.py +8 -10
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/__init__.py +1 -28
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/client.py +3 -9
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/tasks.py +50 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/client.py +31 -16
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/env/__init__.py +2 -2
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/env/client.py +1 -1
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/client.py +3 -1
- fleet_python-0.2.35/fleet/resources/mcp.py +55 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/sqlite.py +9 -3
- fleet_python-0.2.35/fleet/tasks.py +205 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/verifier.py +7 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/PKG-INFO +1 -1
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/SOURCES.txt +2 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/pyproject.toml +1 -1
- fleet_python-0.2.32/fleet/resources/mcp.py +0 -54
- fleet_python-0.2.32/fleet/tasks.py +0 -263
- {fleet_python-0.2.32 → fleet_python-0.2.35}/LICENSE +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/README.md +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/diff_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/dsl_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_account.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_action_log.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_client.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_mcp_anthropic.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_mcp_openai.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_sync.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_task.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_tasks.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_verifier.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/gemini_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/json_tasks_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/nova_act_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/openai_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/openai_simple_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/query_builder_example.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/quickstart.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/env/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/env/client.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/exceptions.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/global_client.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/client.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/models.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/browser.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/mcp.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/sqlite.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/bundler.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/verifier.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/config.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/exceptions.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/global_client.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/models.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/models.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/base.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/browser.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/types.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/__init__.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/bundler.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/code.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/db.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/decorator.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/parse.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/sql_differ.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/dependency_links.txt +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/requires.txt +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/top_level.txt +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/scripts/fix_sync_imports.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/scripts/unasync.py +0 -0
- {fleet_python-0.2.32 → fleet_python-0.2.35}/setup.cfg +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()
|
|
@@ -46,8 +46,11 @@ from ._async.verifiers import (
|
|
|
46
46
|
AsyncVerifierFunction,
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
# Import async tasks (default tasks are async for modern usage)
|
|
50
|
-
from ._async.tasks import Task
|
|
49
|
+
# Import async tasks (default tasks are async for modern usage)
|
|
50
|
+
from ._async.tasks import Task, load_tasks
|
|
51
|
+
|
|
52
|
+
# Import sync load_tasks function
|
|
53
|
+
from .tasks import load_tasks as load_tasks_sync
|
|
51
54
|
|
|
52
55
|
# Import shared types
|
|
53
56
|
from .types import VerifierFunction
|
|
@@ -94,19 +97,14 @@ __all__ = [
|
|
|
94
97
|
"configure",
|
|
95
98
|
"get_client",
|
|
96
99
|
"reset_client",
|
|
100
|
+
# Module-level functions (async is default)
|
|
101
|
+
"load_tasks",
|
|
102
|
+
"load_tasks_sync",
|
|
97
103
|
# Version
|
|
98
104
|
"__version__",
|
|
99
105
|
]
|
|
100
106
|
|
|
101
107
|
|
|
102
|
-
def load_tasks(env_key: Optional[str] = None) -> List[Task]:
|
|
103
|
-
"""Load tasks without explicitly creating a client.
|
|
104
|
-
|
|
105
|
-
Example:
|
|
106
|
-
tasks = fleet.load_tasks(env_key="fira")
|
|
107
|
-
"""
|
|
108
|
-
# Use global client by default so users can configure once
|
|
109
|
-
return _global_client.get_client().load_tasks(env_key=env_key)
|
|
110
108
|
|
|
111
109
|
|
|
112
110
|
def configure(
|
|
@@ -35,7 +35,7 @@ from .verifiers import (
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
# Import async tasks
|
|
38
|
-
from .tasks import Task
|
|
38
|
+
from .tasks import Task, load_tasks
|
|
39
39
|
|
|
40
40
|
# Import shared types
|
|
41
41
|
from ..types import VerifierFunction
|
|
@@ -93,33 +93,6 @@ __all__ = [
|
|
|
93
93
|
]
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
async def load_tasks(
|
|
97
|
-
env_key: Optional[str] = None,
|
|
98
|
-
keys: Optional[List[str]] = None,
|
|
99
|
-
version: Optional[str] = None,
|
|
100
|
-
team_id: Optional[str] = None
|
|
101
|
-
) -> List[Task]:
|
|
102
|
-
"""Load tasks with optional filtering.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
env_key: Optional environment key to filter tasks by
|
|
106
|
-
keys: Optional list of task keys to filter by
|
|
107
|
-
version: Optional version to filter tasks by
|
|
108
|
-
|
|
109
|
-
Examples:
|
|
110
|
-
tasks = await fleet.load_tasks(env_key="fira")
|
|
111
|
-
tasks = await fleet.load_tasks(keys=["task1", "task2"])
|
|
112
|
-
tasks = await fleet.load_tasks(env_key="fira", version="v1.0")
|
|
113
|
-
"""
|
|
114
|
-
# Use global client by default so users can configure once
|
|
115
|
-
return await _async_global_client.get_client().load_tasks(
|
|
116
|
-
env_key=env_key,
|
|
117
|
-
keys=keys,
|
|
118
|
-
version=version,
|
|
119
|
-
team_id=team_id
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
96
|
async def list_envs() -> List[Environment]:
|
|
124
97
|
"""List all available environments."""
|
|
125
98
|
return await _async_global_client.get_client().list_envs()
|
|
@@ -50,7 +50,7 @@ from .instance.client import ValidatorType
|
|
|
50
50
|
from .resources.base import Resource
|
|
51
51
|
from .resources.sqlite import AsyncSQLiteResource
|
|
52
52
|
from .resources.browser import AsyncBrowserResource
|
|
53
|
-
from
|
|
53
|
+
from .resources.mcp import AsyncMCPResource
|
|
54
54
|
|
|
55
55
|
logger = logging.getLogger(__name__)
|
|
56
56
|
|
|
@@ -105,9 +105,9 @@ class AsyncEnv(EnvironmentBase):
|
|
|
105
105
|
return self.instance.browser(name)
|
|
106
106
|
|
|
107
107
|
@property
|
|
108
|
-
def mcp(self) ->
|
|
108
|
+
def mcp(self) -> AsyncMCPResource:
|
|
109
109
|
mcp_url = f"{self.urls.root}mcp"
|
|
110
|
-
return
|
|
110
|
+
return AsyncMCPResource(url=mcp_url, env_key=self.env_key)
|
|
111
111
|
|
|
112
112
|
def state(self, uri: str) -> Resource:
|
|
113
113
|
return self.instance.state(uri)
|
|
@@ -507,12 +507,6 @@ class AsyncFleet:
|
|
|
507
507
|
sha256=verifier_sha,
|
|
508
508
|
)
|
|
509
509
|
|
|
510
|
-
# Ensure we return an AsyncVerifierFunction
|
|
511
|
-
if not isinstance(verifier_func, AsyncVerifierFunction):
|
|
512
|
-
raise TypeError(
|
|
513
|
-
f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
514
|
-
)
|
|
515
|
-
|
|
516
510
|
# Store the original verifier code for reference
|
|
517
511
|
verifier_func._verifier_code = verifier_code
|
|
518
512
|
|
|
@@ -125,6 +125,56 @@ class Task(BaseModel):
|
|
|
125
125
|
return await AsyncFleet().make(env_key=self.env_key, region=region)
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
def verifier_from_string(
|
|
129
|
+
verifier_func: str,
|
|
130
|
+
verifier_id: str,
|
|
131
|
+
verifier_key: str,
|
|
132
|
+
sha256: str = ""
|
|
133
|
+
) -> 'VerifierFunction':
|
|
134
|
+
"""Create a verifier function from string code.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
verifier_func: The verifier function code as a string
|
|
138
|
+
verifier_id: Unique identifier for the verifier
|
|
139
|
+
verifier_key: Key/name for the verifier
|
|
140
|
+
sha256: SHA256 hash of the verifier code
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
VerifierFunction instance that can be used to verify tasks
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
import inspect
|
|
147
|
+
from .verifiers import verifier, AsyncVerifierFunction
|
|
148
|
+
|
|
149
|
+
# Create a local namespace for executing the code
|
|
150
|
+
local_namespace = {}
|
|
151
|
+
|
|
152
|
+
# Execute the verifier code in the namespace
|
|
153
|
+
exec(verifier_func, globals(), local_namespace)
|
|
154
|
+
|
|
155
|
+
# Find the function that was defined
|
|
156
|
+
func_obj = None
|
|
157
|
+
for name, obj in local_namespace.items():
|
|
158
|
+
if inspect.isfunction(obj):
|
|
159
|
+
func_obj = obj
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
if func_obj is None:
|
|
163
|
+
raise ValueError("No function found in verifier code")
|
|
164
|
+
|
|
165
|
+
# Create an AsyncVerifierFunction instance
|
|
166
|
+
verifier_instance = AsyncVerifierFunction(func_obj, verifier_key, verifier_id)
|
|
167
|
+
|
|
168
|
+
# Store additional metadata
|
|
169
|
+
verifier_instance._verifier_code = verifier_func
|
|
170
|
+
verifier_instance._sha256 = sha256
|
|
171
|
+
|
|
172
|
+
return verifier_instance
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
raise ValueError(f"Failed to create verifier from string: {e}")
|
|
176
|
+
|
|
177
|
+
|
|
128
178
|
async def load_tasks(
|
|
129
179
|
env_key: Optional[str] = None,
|
|
130
180
|
keys: Optional[List[str]] = None,
|
|
@@ -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 .resources.mcp import
|
|
53
|
+
from .resources.mcp import SyncMCPResource
|
|
54
54
|
|
|
55
55
|
logger = logging.getLogger(__name__)
|
|
56
56
|
|
|
@@ -105,9 +105,9 @@ class SyncEnv(EnvironmentBase):
|
|
|
105
105
|
return self.instance.browser(name)
|
|
106
106
|
|
|
107
107
|
@property
|
|
108
|
-
def mcp(self) ->
|
|
108
|
+
def mcp(self) -> SyncMCPResource:
|
|
109
109
|
mcp_url = f"{self.urls.root}mcp"
|
|
110
|
-
return
|
|
110
|
+
return SyncMCPResource(url=mcp_url, env_key=self.env_key)
|
|
111
111
|
|
|
112
112
|
def state(self, uri: str) -> Resource:
|
|
113
113
|
return self.instance.state(uri)
|
|
@@ -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,10 @@ 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
499
|
from .tasks import verifier_from_string
|
|
479
|
-
from .verifiers
|
|
500
|
+
from .verifiers import SyncVerifierFunction
|
|
480
501
|
|
|
481
502
|
# Use verifier_from_string to create the verifier
|
|
482
503
|
verifier_func = verifier_from_string(
|
|
@@ -486,12 +507,6 @@ class Fleet:
|
|
|
486
507
|
sha256=verifier_sha,
|
|
487
508
|
)
|
|
488
509
|
|
|
489
|
-
# Ensure we return a SyncVerifierFunction
|
|
490
|
-
if not isinstance(verifier_func, SyncVerifierFunction):
|
|
491
|
-
raise TypeError(
|
|
492
|
-
f"Expected SyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
493
|
-
)
|
|
494
|
-
|
|
495
510
|
# Store the original verifier code for reference
|
|
496
511
|
verifier_func._verifier_code = verifier_code
|
|
497
512
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .client import (
|
|
4
4
|
make,
|
|
5
|
-
|
|
5
|
+
make_for_task_async,
|
|
6
6
|
list_envs,
|
|
7
7
|
list_regions,
|
|
8
8
|
get,
|
|
@@ -22,7 +22,7 @@ from .._async.env.client import (
|
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
24
|
"make",
|
|
25
|
-
"
|
|
25
|
+
"make_for_task_async",
|
|
26
26
|
"list_envs",
|
|
27
27
|
"list_regions",
|
|
28
28
|
"list_instances",
|
|
@@ -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
|
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SyncMCPResource:
|
|
5
|
+
def __init__(self, url: str, env_key: str):
|
|
6
|
+
self.url = url
|
|
7
|
+
self._env_key = env_key
|
|
8
|
+
|
|
9
|
+
def openai(self) -> Dict[str, str]:
|
|
10
|
+
return {
|
|
11
|
+
"type": "mcp",
|
|
12
|
+
"server_label": self._env_key,
|
|
13
|
+
"server_url": self.url,
|
|
14
|
+
"require_approval": "never",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def anthropic(self) -> Dict[str, str]:
|
|
18
|
+
return {
|
|
19
|
+
"type": "url",
|
|
20
|
+
"url": self.url,
|
|
21
|
+
"name": self._env_key,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def list_tools(self):
|
|
25
|
+
import aiohttp
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Make an async request to list available tools from the MCP endpoint.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of available tools with name, description, and input_schema
|
|
32
|
+
"""
|
|
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 []
|