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.

Files changed (83) hide show
  1. {fleet_python-0.2.32 → fleet_python-0.2.35}/PKG-INFO +1 -1
  2. fleet_python-0.2.35/examples/exampleResume.py +191 -0
  3. fleet_python-0.2.35/examples/test_cdp_logging.py +80 -0
  4. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/__init__.py +8 -10
  5. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/__init__.py +1 -28
  6. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/client.py +3 -9
  7. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/tasks.py +50 -0
  8. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/client.py +31 -16
  9. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/env/__init__.py +2 -2
  10. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/env/client.py +1 -1
  11. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/client.py +3 -1
  12. fleet_python-0.2.35/fleet/resources/mcp.py +55 -0
  13. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/sqlite.py +9 -3
  14. fleet_python-0.2.35/fleet/tasks.py +205 -0
  15. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/verifier.py +7 -0
  16. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/PKG-INFO +1 -1
  17. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/SOURCES.txt +2 -0
  18. {fleet_python-0.2.32 → fleet_python-0.2.35}/pyproject.toml +1 -1
  19. fleet_python-0.2.32/fleet/resources/mcp.py +0 -54
  20. fleet_python-0.2.32/fleet/tasks.py +0 -263
  21. {fleet_python-0.2.32 → fleet_python-0.2.35}/LICENSE +0 -0
  22. {fleet_python-0.2.32 → fleet_python-0.2.35}/README.md +0 -0
  23. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/diff_example.py +0 -0
  24. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/dsl_example.py +0 -0
  25. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example.py +0 -0
  26. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_account.py +0 -0
  27. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_action_log.py +0 -0
  28. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_client.py +0 -0
  29. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_mcp_anthropic.py +0 -0
  30. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_mcp_openai.py +0 -0
  31. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_sync.py +0 -0
  32. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_task.py +0 -0
  33. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_tasks.py +0 -0
  34. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/example_verifier.py +0 -0
  35. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/gemini_example.py +0 -0
  36. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/json_tasks_example.py +0 -0
  37. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/nova_act_example.py +0 -0
  38. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/openai_example.py +0 -0
  39. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/openai_simple_example.py +0 -0
  40. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/query_builder_example.py +0 -0
  41. {fleet_python-0.2.32 → fleet_python-0.2.35}/examples/quickstart.py +0 -0
  42. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/base.py +0 -0
  43. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/env/__init__.py +0 -0
  44. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/env/client.py +0 -0
  45. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/exceptions.py +0 -0
  46. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/global_client.py +0 -0
  47. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/__init__.py +0 -0
  48. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/base.py +0 -0
  49. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/instance/client.py +0 -0
  50. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/models.py +0 -0
  51. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/__init__.py +0 -0
  52. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/base.py +0 -0
  53. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/browser.py +0 -0
  54. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/mcp.py +0 -0
  55. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/resources/sqlite.py +0 -0
  56. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/__init__.py +0 -0
  57. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/bundler.py +0 -0
  58. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/_async/verifiers/verifier.py +0 -0
  59. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/base.py +0 -0
  60. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/config.py +0 -0
  61. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/exceptions.py +0 -0
  62. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/global_client.py +0 -0
  63. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/__init__.py +0 -0
  64. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/base.py +0 -0
  65. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/instance/models.py +0 -0
  66. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/models.py +0 -0
  67. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/__init__.py +0 -0
  68. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/base.py +0 -0
  69. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/resources/browser.py +0 -0
  70. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/types.py +0 -0
  71. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/__init__.py +0 -0
  72. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/bundler.py +0 -0
  73. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/code.py +0 -0
  74. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/db.py +0 -0
  75. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/decorator.py +0 -0
  76. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/parse.py +0 -0
  77. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet/verifiers/sql_differ.py +0 -0
  78. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/dependency_links.txt +0 -0
  79. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/requires.txt +0 -0
  80. {fleet_python-0.2.32 → fleet_python-0.2.35}/fleet_python.egg-info/top_level.txt +0 -0
  81. {fleet_python-0.2.32 → fleet_python-0.2.35}/scripts/fix_sync_imports.py +0 -0
  82. {fleet_python-0.2.32 → fleet_python-0.2.35}/scripts/unasync.py +0 -0
  83. {fleet_python-0.2.32 → fleet_python-0.2.35}/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.35
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()
@@ -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 ..resources.mcp import MCPResource
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) -> MCPResource:
108
+ def mcp(self) -> AsyncMCPResource:
109
109
  mcp_url = f"{self.urls.root}mcp"
110
- return MCPResource(url=mcp_url, env_key=self.env_key)
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 MCPResource
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) -> MCPResource:
108
+ def mcp(self) -> SyncMCPResource:
109
109
  mcp_url = f"{self.urls.root}mcp"
110
- return MCPResource(url=mcp_url, env_key=self.env_key)
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(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,10 @@ 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
499
  from .tasks import verifier_from_string
479
- from .verifiers.verifier import SyncVerifierFunction
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
- make_for_task,
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
- "make_for_task",
25
+ "make_for_task_async",
26
26
  "list_envs",
27
27
  "list_regions",
28
28
  "list_instances",
@@ -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
 
@@ -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 []