fleet-python 0.2.32__tar.gz → 0.2.34__tar.gz

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

Potentially problematic release.


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

Files changed (83) hide show
  1. {fleet_python-0.2.32 → fleet_python-0.2.34}/PKG-INFO +1 -1
  2. fleet_python-0.2.34/examples/exampleResume.py +191 -0
  3. fleet_python-0.2.34/examples/test_cdp_logging.py +80 -0
  4. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/client.py +37 -10
  5. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/env/client.py +1 -1
  6. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/global_client.py +1 -1
  7. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/instance/client.py +3 -1
  8. fleet_python-0.2.34/fleet/resources/mcp.py +55 -0
  9. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/resources/sqlite.py +9 -3
  10. fleet_python-0.2.34/fleet/tasks.py +155 -0
  11. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/verifier.py +7 -0
  12. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet_python.egg-info/PKG-INFO +1 -1
  13. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet_python.egg-info/SOURCES.txt +2 -0
  14. {fleet_python-0.2.32 → fleet_python-0.2.34}/pyproject.toml +1 -1
  15. fleet_python-0.2.32/fleet/resources/mcp.py +0 -54
  16. fleet_python-0.2.32/fleet/tasks.py +0 -263
  17. {fleet_python-0.2.32 → fleet_python-0.2.34}/LICENSE +0 -0
  18. {fleet_python-0.2.32 → fleet_python-0.2.34}/README.md +0 -0
  19. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/diff_example.py +0 -0
  20. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/dsl_example.py +0 -0
  21. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example.py +0 -0
  22. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_account.py +0 -0
  23. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_action_log.py +0 -0
  24. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_client.py +0 -0
  25. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_mcp_anthropic.py +0 -0
  26. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_mcp_openai.py +0 -0
  27. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_sync.py +0 -0
  28. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_task.py +0 -0
  29. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_tasks.py +0 -0
  30. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/example_verifier.py +0 -0
  31. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/gemini_example.py +0 -0
  32. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/json_tasks_example.py +0 -0
  33. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/nova_act_example.py +0 -0
  34. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/openai_example.py +0 -0
  35. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/openai_simple_example.py +0 -0
  36. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/query_builder_example.py +0 -0
  37. {fleet_python-0.2.32 → fleet_python-0.2.34}/examples/quickstart.py +0 -0
  38. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/__init__.py +0 -0
  39. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/__init__.py +0 -0
  40. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/base.py +0 -0
  41. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/client.py +0 -0
  42. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/env/__init__.py +0 -0
  43. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/env/client.py +0 -0
  44. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/exceptions.py +0 -0
  45. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/global_client.py +0 -0
  46. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/instance/__init__.py +0 -0
  47. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/instance/base.py +0 -0
  48. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/instance/client.py +0 -0
  49. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/models.py +0 -0
  50. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/resources/__init__.py +0 -0
  51. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/resources/base.py +0 -0
  52. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/resources/browser.py +0 -0
  53. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/resources/mcp.py +0 -0
  54. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/resources/sqlite.py +0 -0
  55. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/tasks.py +0 -0
  56. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/verifiers/__init__.py +0 -0
  57. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/verifiers/bundler.py +0 -0
  58. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/_async/verifiers/verifier.py +0 -0
  59. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/base.py +0 -0
  60. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/config.py +0 -0
  61. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/env/__init__.py +0 -0
  62. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/exceptions.py +0 -0
  63. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/instance/__init__.py +0 -0
  64. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/instance/base.py +0 -0
  65. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/instance/models.py +0 -0
  66. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/models.py +0 -0
  67. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/resources/__init__.py +0 -0
  68. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/resources/base.py +0 -0
  69. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/resources/browser.py +0 -0
  70. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/types.py +0 -0
  71. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/__init__.py +0 -0
  72. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/bundler.py +0 -0
  73. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/code.py +0 -0
  74. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/db.py +0 -0
  75. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/decorator.py +0 -0
  76. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/parse.py +0 -0
  77. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet/verifiers/sql_differ.py +0 -0
  78. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet_python.egg-info/dependency_links.txt +0 -0
  79. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet_python.egg-info/requires.txt +0 -0
  80. {fleet_python-0.2.32 → fleet_python-0.2.34}/fleet_python.egg-info/top_level.txt +0 -0
  81. {fleet_python-0.2.32 → fleet_python-0.2.34}/scripts/fix_sync_imports.py +0 -0
  82. {fleet_python-0.2.32 → fleet_python-0.2.34}/scripts/unasync.py +0 -0
  83. {fleet_python-0.2.32 → fleet_python-0.2.34}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.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()
@@ -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 MCPResource
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(self.client, bundle_data, args, kwargs, timeout)
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(self, serialized_tasks: List[Dict]) -> List[Task]:
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(self, env_key: Optional[str] = None) -> List[Task]:
307
- """Load tasks for the authenticated team, optionally filtered by environment.
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 a SyncVerifierFunction from verifier data.
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
- SyncVerifierFunction created from the verifier code
497
+ AsyncVerifierFunction created from the verifier code
477
498
  """
478
- from .tasks import verifier_from_string
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 a SyncVerifierFunction
516
+ # Ensure we return an AsyncVerifierFunction
490
517
  if not isinstance(verifier_func, SyncVerifierFunction):
491
518
  raise TypeError(
492
- f"Expected SyncVerifierFunction but got {type(verifier_func).__name__}"
519
+ f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
493
520
  )
494
521
 
495
522
  # Store the original verifier code for reference
@@ -7,7 +7,7 @@ def make(env_key: str, region: Optional[str] = None) -> SyncEnv:
7
7
  return Fleet().make(env_key, region=region)
8
8
 
9
9
 
10
- def make_for_task(task: Task) -> SyncEnv:
10
+ def make_for_task_async(task: Task) -> SyncEnv:
11
11
  return Fleet().make_for_task(task)
12
12
 
13
13
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from typing import Optional
4
4
 
5
5
  from .client import Fleet
6
- from .config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
6
+ from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
7
7
 
8
8
 
9
9
  _default_client: Optional[Fleet] = None
@@ -63,7 +63,9 @@ class InstanceClient:
63
63
  def load(self) -> None:
64
64
  self._load_resources()
65
65
 
66
- def reset(self, reset_request: Optional[ResetRequest] = None) -> ResetResponse:
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 []
@@ -651,7 +651,9 @@ class SQLiteResource(Resource):
651
651
  )
652
652
  return DescribeResponse(**response.json())
653
653
 
654
- def query(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
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(name=f"before_{datetime.utcnow().isoformat()}")
697
- after_snapshot = other.snapshot(name=f"after_{datetime.utcnow().isoformat()}")
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)
@@ -0,0 +1,155 @@
1
+ """Fleet SDK Task Model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from datetime import datetime
7
+ from typing import Any, Dict, Optional, List
8
+ from uuid import UUID
9
+
10
+ from pydantic import BaseModel, Field, validator
11
+
12
+ # Import the shared VerifierFunction type that works for both async and sync
13
+ from fleet.types import VerifierFunction
14
+
15
+
16
+ class Task(BaseModel):
17
+ """A task model representing a single task in the Fleet system."""
18
+
19
+ key: str = Field(..., description="Unique task key identifier")
20
+ prompt: str = Field(..., description="Task prompt or instruction")
21
+ env_id: str = Field(..., description="Environment identifier")
22
+ env_variables: Optional[Dict[str, Any]] = Field(
23
+ default_factory=dict, description="Environment variables"
24
+ )
25
+ created_at: Optional[datetime] = Field(None, description="Task creation timestamp")
26
+ version: Optional[str] = Field(None, description="Task version")
27
+ verifier_func: Optional[str] = Field(None, description="Verifier function code")
28
+ verifier: Optional[Any] = Field(
29
+ None, description="Verifier function with decorator (async or sync)"
30
+ )
31
+ verifier_id: Optional[str] = Field(None, description="Verifier identifier")
32
+ verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
33
+ metadata: Optional[Dict[str, Any]] = Field(
34
+ default_factory=dict, description="Additional task metadata"
35
+ )
36
+
37
+ @validator("key")
38
+ def validate_key_format(cls, v):
39
+ """Validate key follows kebab-case format."""
40
+ if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", v):
41
+ raise ValueError(
42
+ f"Invalid task key format: {v}. Must follow kebab-case format."
43
+ )
44
+ return v
45
+
46
+ @validator("created_at", pre=True, always=True)
47
+ def set_created_at(cls, v):
48
+ """Set created_at to current time if not provided."""
49
+ return v or datetime.now()
50
+
51
+ @property
52
+ def env_key(self) -> str:
53
+ """Get the environment key combining env_id and version."""
54
+ if self.version:
55
+ return f"{self.env_id}:{self.version}"
56
+ return self.env_id
57
+
58
+ class Config:
59
+ """Pydantic model configuration."""
60
+
61
+ json_encoders = {
62
+ datetime: lambda v: v.isoformat(),
63
+ }
64
+ # Allow arbitrary types for the verifier field
65
+ arbitrary_types_allowed = True
66
+
67
+ def verify(self, env, *args, **kwargs) -> float:
68
+ """Verify the task using the verifier function (sync version).
69
+
70
+ For sync environments, calls the sync verifier directly.
71
+ For async verifiers, automatically runs them with asyncio.run().
72
+ """
73
+ if self.verifier:
74
+ import inspect
75
+
76
+ result = self.verifier.remote(env, *args, **kwargs)
77
+
78
+ # If the result is a coroutine, we need to run it
79
+ if inspect.iscoroutine(result):
80
+ # Check if we're already in an event loop
81
+ try:
82
+ loop = asyncio.get_running_loop()
83
+ # We're in an async context, can't use asyncio.run()
84
+ raise RuntimeError(
85
+ "Cannot run async verifier in sync mode while event loop is running. "
86
+ "Use await task.verify_async() instead."
87
+ )
88
+ except RuntimeError:
89
+ # No event loop running, safe to use asyncio.run()
90
+ return asyncio.run(result)
91
+ else:
92
+ return result
93
+ else:
94
+ raise ValueError("No verifier function found for this task")
95
+
96
+ def verify_async(self, *args, **kwargs) -> float:
97
+ """Verify the task using the verifier function (async version).
98
+
99
+ For async environments, awaits the async verifier.
100
+ Works with both sync and async verifiers in async contexts.
101
+ """
102
+ if self.verifier:
103
+ result = self.verifier.remote(*args, **kwargs)
104
+ # If it's a coroutine, await it
105
+ import inspect
106
+
107
+ if inspect.iscoroutine(result):
108
+ return result
109
+ else:
110
+ return result
111
+ else:
112
+ raise ValueError("No verifier function found for this task")
113
+
114
+ def make_env(self, region: Optional[str] = None):
115
+ """Create an environment instance for this task's environment.
116
+
117
+ Uses the task's env_id (and version if present) to create the env.
118
+ """
119
+ if not self.env_id:
120
+ raise ValueError("Task has no env_id defined")
121
+ # Deferred import to avoid circular dependencies
122
+ from .client import Fleet
123
+
124
+ return Fleet().make(env_key=self.env_key, region=region)
125
+
126
+
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.
134
+
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")
145
+ """
146
+ # Use the global client by default so users can pre-configure it once
147
+ from .global_client import get_client
148
+
149
+ client = get_client()
150
+ return client.load_tasks(
151
+ env_key=env_key,
152
+ keys=keys,
153
+ version=version,
154
+ team_id=team_id
155
+ )
@@ -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})