fleet-python 0.2.28__py3-none-any.whl → 0.2.32__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (61) hide show
  1. examples/diff_example.py +30 -20
  2. examples/dsl_example.py +12 -7
  3. examples/example.py +4 -4
  4. examples/example_account.py +8 -0
  5. examples/example_action_log.py +2 -2
  6. examples/example_client.py +2 -2
  7. examples/example_mcp_anthropic.py +8 -5
  8. examples/example_mcp_openai.py +2 -2
  9. examples/example_sync.py +4 -4
  10. examples/example_task.py +16 -6
  11. examples/example_tasks.py +3 -6
  12. examples/example_verifier.py +16 -3
  13. examples/gemini_example.py +6 -6
  14. examples/json_tasks_example.py +2 -2
  15. examples/nova_act_example.py +2 -2
  16. examples/openai_example.py +3 -3
  17. examples/openai_simple_example.py +3 -3
  18. examples/query_builder_example.py +11 -7
  19. fleet/__init__.py +60 -5
  20. fleet/_async/__init__.py +258 -1
  21. fleet/_async/base.py +2 -1
  22. fleet/_async/client.py +194 -127
  23. fleet/_async/env/client.py +5 -1
  24. fleet/_async/global_client.py +43 -0
  25. fleet/_async/instance/client.py +1 -1
  26. fleet/_async/models.py +172 -171
  27. fleet/_async/resources/base.py +1 -1
  28. fleet/_async/resources/mcp.py +55 -0
  29. fleet/_async/resources/sqlite.py +141 -130
  30. fleet/_async/tasks.py +71 -16
  31. fleet/_async/verifiers/__init__.py +2 -2
  32. fleet/_async/verifiers/bundler.py +18 -14
  33. fleet/_async/verifiers/verifier.py +77 -71
  34. fleet/base.py +2 -1
  35. fleet/client.py +176 -136
  36. fleet/config.py +3 -2
  37. fleet/env/__init__.py +10 -1
  38. fleet/env/client.py +5 -1
  39. fleet/global_client.py +43 -0
  40. fleet/instance/__init__.py +1 -1
  41. fleet/instance/client.py +2 -4
  42. fleet/models.py +172 -171
  43. fleet/resources/base.py +1 -1
  44. fleet/resources/mcp.py +27 -33
  45. fleet/resources/sqlite.py +136 -131
  46. fleet/tasks.py +197 -16
  47. fleet/types.py +1 -1
  48. fleet/verifiers/__init__.py +2 -2
  49. fleet/verifiers/bundler.py +18 -14
  50. fleet/verifiers/code.py +1 -1
  51. fleet/verifiers/decorator.py +25 -34
  52. fleet/verifiers/parse.py +98 -68
  53. fleet/verifiers/verifier.py +77 -78
  54. {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/METADATA +9 -9
  55. fleet_python-0.2.32.dist-info/RECORD +74 -0
  56. scripts/fix_sync_imports.py +87 -59
  57. scripts/unasync.py +10 -9
  58. fleet_python-0.2.28.dist-info/RECORD +0 -70
  59. {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/WHEEL +0 -0
  60. {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/licenses/LICENSE +0 -0
  61. {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/top_level.txt +0 -0
fleet/_async/client.py CHANGED
@@ -34,7 +34,6 @@ from ..models import (
34
34
  TaskRequest,
35
35
  )
36
36
  from .tasks import Task
37
- from ..verifiers.parse import extract_function_name, convert_new_to_old_verifier
38
37
 
39
38
  if TYPE_CHECKING:
40
39
  from .verifiers import AsyncVerifierFunction
@@ -51,6 +50,7 @@ from .instance.client import ValidatorType
51
50
  from .resources.base import Resource
52
51
  from .resources.sqlite import AsyncSQLiteResource
53
52
  from .resources.browser import AsyncBrowserResource
53
+ from ..resources.mcp import MCPResource
54
54
 
55
55
  logger = logging.getLogger(__name__)
56
56
 
@@ -69,18 +69,18 @@ class AsyncEnv(EnvironmentBase):
69
69
  self.manager_url, self._client.httpx_client if self._client else None
70
70
  )
71
71
  return self._instance
72
-
72
+
73
73
  def app(self, name: str) -> AsyncInstanceClient:
74
74
  if name not in self._apps:
75
75
  # Extract base URL by removing the current app path (e.g., /sentry/api/v1/env)
76
76
  # manager_url looks like: https://xxx.fleetai.com/sentry/api/v1/env
77
- base_url = self.manager_url.split('/api/v1/env')[0]
77
+ base_url = self.manager_url.split("/api/v1/env")[0]
78
78
  # Remove the current app name (e.g., /sentry) to get the root
79
- if '/' in base_url:
80
- parts = base_url.rsplit('/', 1)
79
+ if "/" in base_url:
80
+ parts = base_url.rsplit("/", 1)
81
81
  if len(parts) == 2 and parts[0] != "https:/":
82
82
  base_url = parts[0]
83
-
83
+
84
84
  self._apps[name] = AsyncInstanceClient(
85
85
  f"{base_url}/{name}/api/v1/env",
86
86
  self._client.httpx_client if self._client else None,
@@ -104,6 +104,11 @@ class AsyncEnv(EnvironmentBase):
104
104
  def browser(self, name: str = "cdp") -> AsyncBrowserResource:
105
105
  return self.instance.browser(name)
106
106
 
107
+ @property
108
+ def mcp(self) -> MCPResource:
109
+ mcp_url = f"{self.urls.root}mcp"
110
+ return MCPResource(url=mcp_url, env_key=self.env_key)
111
+
107
112
  def state(self, uri: str) -> Resource:
108
113
  return self.instance.state(uri)
109
114
 
@@ -125,28 +130,28 @@ class AsyncEnv(EnvironmentBase):
125
130
  return await _check_bundle_exists(self._load_client, bundle_hash)
126
131
 
127
132
  async def execute_verifier_remote(
128
- self,
129
- bundle_data: bytes,
133
+ self,
134
+ bundle_data: bytes,
130
135
  bundle_sha: str,
131
136
  key: str,
132
137
  function_name: str,
133
- args: tuple,
138
+ args: tuple,
134
139
  args_array: list,
135
- kwargs: dict,
140
+ kwargs: dict,
136
141
  timeout: Optional[int] = 30,
137
142
  needs_upload: bool = True,
138
143
  ) -> VerifiersExecuteResponse:
139
144
  return await _execute_verifier_remote(
140
- self._load_client,
141
- bundle_data,
145
+ self._load_client,
146
+ bundle_data,
142
147
  bundle_sha,
143
148
  key,
144
149
  function_name,
145
- args,
150
+ args,
146
151
  args_array,
147
- kwargs,
152
+ kwargs,
148
153
  timeout,
149
- needs_upload
154
+ needs_upload,
150
155
  )
151
156
 
152
157
  def __getstate__(self):
@@ -170,6 +175,8 @@ class AsyncFleet:
170
175
  ):
171
176
  if api_key is None:
172
177
  api_key = os.getenv("FLEET_API_KEY")
178
+ if base_url is None:
179
+ base_url = os.getenv("FLEET_BASE_URL")
173
180
  self._httpx_client = httpx_client or default_httpx_client(max_retries, timeout)
174
181
  self.client = AsyncWrapper(
175
182
  api_key=api_key,
@@ -189,23 +196,27 @@ class AsyncFleet:
189
196
  response = await self.client.request("GET", f"/v1/env/{env_key}")
190
197
  return EnvironmentModel(**response.json())
191
198
 
192
- async def make(
193
- self, env_key: str, region: Optional[str] = None
194
- ) -> AsyncEnv:
199
+ async def make(self, env_key: str, region: Optional[str] = None) -> AsyncEnv:
195
200
  if ":" in env_key:
196
201
  env_key_part, version = env_key.split(":", 1)
197
- if not version.startswith("v") and len(version) != 0 and version[0].isdigit():
202
+ if (
203
+ not version.startswith("v")
204
+ and len(version) != 0
205
+ and version[0].isdigit()
206
+ ):
198
207
  version = f"v{version}"
199
208
  else:
200
209
  env_key_part = env_key
201
210
  version = None
202
211
 
203
- request = InstanceRequest(env_key=env_key_part, version=version, region=region, created_from="sdk")
212
+ request = InstanceRequest(
213
+ env_key=env_key_part, version=version, region=region, created_from="sdk"
214
+ )
204
215
  region_base_url = REGION_BASE_URL.get(region)
205
216
  response = await self.client.request(
206
217
  "POST",
207
218
  "/v1/env/instances",
208
- json=request.model_dump(),
219
+ json=request.model_dump(exclude_none=True),
209
220
  base_url=region_base_url,
210
221
  )
211
222
 
@@ -213,6 +224,9 @@ class AsyncFleet:
213
224
  await instance.instance.load()
214
225
  return instance
215
226
 
227
+ async def make_for_task(self, task: Task) -> AsyncEnv:
228
+ return await self.make(env_key=f"{task.env_id}:{task.version}")
229
+
216
230
  async def instances(
217
231
  self, status: Optional[str] = None, region: Optional[str] = None
218
232
  ) -> List[AsyncEnv]:
@@ -247,44 +261,124 @@ class AsyncFleet:
247
261
  async def delete(self, instance_id: str) -> InstanceResponse:
248
262
  return await _delete_instance(self.client, instance_id)
249
263
 
250
- async def load_tasks(self, env_key: Optional[str] = None) -> List[Task]:
251
- """Load tasks for the authenticated team, optionally filtered by environment.
252
-
264
+ async def load_tasks_from_file(self, filename: str) -> List[Task]:
265
+ with open(filename, "r", encoding="utf-8") as f:
266
+ tasks_data = f.read()
267
+
268
+ return self.load_task_array_from_string(tasks_data)
269
+
270
+ async def load_task_array_from_string(
271
+ self, serialized_tasks: List[Dict]
272
+ ) -> List[Task]:
273
+ tasks = []
274
+
275
+ json_tasks = json.loads(serialized_tasks)
276
+ for json_task in json_tasks:
277
+ parsed_task = self.load_task_from_json(json_task)
278
+ tasks.append(parsed_task)
279
+ return tasks
280
+
281
+ async def load_task_from_string(self, task_string: str) -> Task:
282
+ task_json = json.loads(task_string)
283
+ return self.load_task_from_json(task_json)
284
+
285
+ async def load_task_from_json(self, task_json: Dict) -> Task:
286
+ try:
287
+ if "verifier_id" in task_json and task_json["verifier_id"]:
288
+ verifier = self._create_verifier_from_data(
289
+ verifier_id=task_json["verifier_id"],
290
+ verifier_key=task_json["key"],
291
+ verifier_code=task_json["verifier_func"],
292
+ verifier_sha=task_json.get("verifier_sha", ""),
293
+ )
294
+ except Exception as e:
295
+ logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
296
+
297
+ task = Task(
298
+ key=task_json["key"],
299
+ prompt=task_json["prompt"],
300
+ env_id=task_json["env_id"], # Use env_id from the data
301
+ created_at=task_json["created_at"],
302
+ version=task_json.get("version"),
303
+ env_variables=task_json.get("env_variables", {}),
304
+ verifier_func=task_json.get("verifier_func"), # Set verifier code
305
+ verifier=verifier, # Use created verifier or None
306
+ metadata=task_json.get("metadata", {}), # Default empty metadata
307
+ )
308
+ return task
309
+
310
+ async 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.
318
+
253
319
  Args:
254
320
  env_key: Optional environment key to filter tasks by
255
-
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)
324
+
256
325
  Returns:
257
326
  List[Task] containing Task objects
258
327
  """
259
328
  params = {}
260
329
  if env_key is not None:
261
330
  params["env_key"] = env_key
262
-
331
+ if keys is not None:
332
+ params["task_keys"] = keys
333
+ if team_id is not None:
334
+ params["team_id"] = team_id
335
+
263
336
  response = await self.client.request("GET", "/v1/tasks", params=params)
264
337
  task_list_response = TaskListResponse(**response.json())
265
-
338
+
266
339
  # Transform TaskResponse objects to Task objects
267
340
  tasks = []
268
341
  for task_response in task_list_response.tasks:
269
342
  # Create verifier function if verifier data is present
270
343
  verifier = None
271
344
  verifier_func = task_response.verifier_func
272
-
345
+
273
346
  if task_response.verifier:
274
- # Use verifier code from the embedded verifier object
275
- verifier_func = task_response.verifier.code
276
-
277
- # Create VerifierFunction from the embedded data
278
- try:
279
- verifier = await self._create_verifier_from_data(
280
- verifier_id=task_response.verifier.verifier_id,
281
- verifier_key=task_response.verifier.key,
282
- verifier_code=task_response.verifier.code,
283
- verifier_sha=task_response.verifier.sha256
284
- )
285
- except Exception as e:
286
- logger.warning(f"Failed to create verifier {task_response.verifier.key}: {e}")
287
-
347
+ embedded_code = task_response.verifier.code or ""
348
+ is_embedded_error = embedded_code.strip().startswith(
349
+ "<error loading code:"
350
+ )
351
+ if not is_embedded_error:
352
+ # Only override if the embedded code looks valid
353
+ verifier_func = embedded_code
354
+ # Create VerifierFunction from the embedded data
355
+ try:
356
+ verifier = await self._create_verifier_from_data(
357
+ verifier_id=task_response.verifier.verifier_id,
358
+ verifier_key=task_response.verifier.key,
359
+ verifier_code=embedded_code,
360
+ verifier_sha=task_response.verifier.sha256,
361
+ )
362
+ except Exception as e:
363
+ logger.warning(
364
+ f"Failed to create verifier {task_response.verifier.key}: {e}"
365
+ )
366
+ else:
367
+ # Fallback: try fetching by ID if embedded code failed to load
368
+ try:
369
+ logger.warning(
370
+ f"Embedded verifier code missing for {task_response.verifier.key} (NoSuchKey). "
371
+ f"Attempting to refetch by id {task_response.verifier.verifier_id}"
372
+ )
373
+ verifier = await self._load_verifier(
374
+ task_response.verifier.verifier_id
375
+ )
376
+ except Exception as e:
377
+ logger.warning(
378
+ f"Refetch by verifier id failed for {task_response.verifier.key}: {e}. "
379
+ "Leaving verifier unset."
380
+ )
381
+
288
382
  task = Task(
289
383
  key=task_response.key,
290
384
  prompt=task_response.prompt,
@@ -294,19 +388,25 @@ class AsyncFleet:
294
388
  env_variables=task_response.env_variables or {},
295
389
  verifier_func=verifier_func, # Set verifier code
296
390
  verifier=verifier, # Use created verifier or None
297
- metadata={} # Default empty metadata
391
+ metadata={}, # Default empty metadata
298
392
  )
299
393
  tasks.append(task)
300
-
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
+
301
399
  return tasks
302
400
 
303
- async def export_tasks(self, env_key: Optional[str] = None, filename: Optional[str] = None):
401
+ async def export_tasks(
402
+ self, env_key: Optional[str] = None, filename: Optional[str] = None
403
+ ):
304
404
  """Export tasks for the authenticated team, optionally filtered by environment.
305
-
405
+
306
406
  Args:
307
407
  env_key: Optional environment key to filter tasks by
308
408
  filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
309
-
409
+
310
410
  Returns:
311
411
  str: Path to the exported file if tasks were written, None if no tasks found
312
412
  """
@@ -318,38 +418,38 @@ class AsyncFleet:
318
418
  filename = f"tasks_{env_key}.json"
319
419
  else:
320
420
  filename = "tasks.json"
321
-
421
+
322
422
  # Convert tasks to serializable format
323
423
  tasks_data = []
324
424
  for task in tasks:
325
425
  task_dict = task.model_dump()
326
426
  # Remove non-serializable verifier object, keep verifier_func (code string)
327
- if 'verifier' in task_dict:
328
- task_dict.pop('verifier')
427
+ if "verifier" in task_dict:
428
+ task_dict.pop("verifier")
329
429
  tasks_data.append(task_dict)
330
-
430
+
331
431
  # Write to JSON file
332
- with open(filename, 'w', encoding='utf-8') as f:
432
+ with open(filename, "w", encoding="utf-8") as f:
333
433
  json.dump(tasks_data, f, indent=2, default=str)
334
-
434
+
335
435
  logger.info(f"Exported {len(tasks)} tasks to {filename}")
336
436
  return filename
337
437
  else:
338
438
  logger.info("No tasks found to export")
339
439
  return None
340
-
440
+
341
441
  async def import_tasks(self, filename: str):
342
442
  """Import tasks from a JSON file.
343
-
443
+
344
444
  Args:
345
445
  filename: Path to the JSON file of Task objects to import
346
-
446
+
347
447
  Returns:
348
448
  List[Task] containing imported Task objects
349
449
  """
350
- with open(filename, 'r', encoding='utf-8') as f:
450
+ with open(filename, "r", encoding="utf-8") as f:
351
451
  tasks_data = json.load(f)
352
-
452
+
353
453
  # Create tasks from the loaded data
354
454
  tasks = []
355
455
  for task_data in tasks_data:
@@ -366,12 +466,13 @@ class AsyncFleet:
366
466
  env_variables=task.env_variables or {},
367
467
  )
368
468
  try:
369
- response = await self.client.request("POST", "/v1/tasks", json=payload.model_dump())
469
+ response = await self.client.request(
470
+ "POST", "/v1/tasks", json=payload.model_dump()
471
+ )
370
472
  except Exception as e:
371
473
  logger.error(f"Failed to import task {task.key}: {e}")
372
474
  continue
373
475
 
374
-
375
476
  async def account(self) -> AccountResponse:
376
477
  """Get account information including instance limits and usage.
377
478
 
@@ -380,102 +481,62 @@ class AsyncFleet:
380
481
  """
381
482
  response = await self.client.request("GET", "/v1/account")
382
483
  return AccountResponse(**response.json())
383
-
484
+
384
485
  async def _create_verifier_from_data(
385
- self,
386
- verifier_id: str,
387
- verifier_key: str,
388
- verifier_code: str,
389
- verifier_sha: str
486
+ self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
390
487
  ) -> "AsyncVerifierFunction":
391
488
  """Create an AsyncVerifierFunction from verifier data.
392
-
489
+
393
490
  Args:
394
491
  verifier_id: The verifier ID
395
492
  verifier_key: The verifier key
396
493
  verifier_code: The verifier code
397
494
  verifier_sha: The verifier SHA256
398
-
495
+
399
496
  Returns:
400
497
  AsyncVerifierFunction created from the verifier code
401
498
  """
499
+ from ..tasks import verifier_from_string
402
500
  from .verifiers.verifier import AsyncVerifierFunction
403
501
 
404
- # Check if this is a new format verifier (has before/after parameters)
405
- if 'before: DatabaseSnapshot' in verifier_code and 'after: DatabaseSnapshot' in verifier_code:
406
- # Convert new format to old format
407
- verifier_code = convert_new_to_old_verifier(verifier_code)
408
- # Update function name since wrapper adds _wrapper suffix
409
- original_name = extract_function_name(verifier_code.split('\n')[0])
410
- if original_name and original_name.endswith('_wrapper'):
411
- function_name = original_name
412
- else:
413
- function_name = extract_function_name(verifier_code)
414
- else:
415
- # Extract function name from code
416
- function_name = extract_function_name(verifier_code)
417
-
418
- if not function_name:
419
- raise ValueError(f"Could not extract function name from verifier {verifier_id}")
420
-
421
- # Create a function object from the code
422
- # Import necessary classes for the namespace
423
- from ..verifiers.db import IgnoreConfig, DatabaseSnapshot
424
-
425
- # Create a namespace for the function
426
- namespace = {
427
- '__builtins__': __builtins__,
428
- 'Environment': object, # Placeholder, will be provided at runtime
429
- 'IgnoreConfig': IgnoreConfig,
430
- 'DatabaseSnapshot': DatabaseSnapshot,
431
- 'TASK_FAILED_SCORE': 0,
432
- 'TASK_SUCCESSFUL_SCORE': 1,
433
- }
434
-
435
- # Execute the code to define the function
436
- exec(verifier_code, namespace)
437
-
438
- # Get the function object
439
- if function_name not in namespace:
440
- raise ValueError(f"Function {function_name} not found in verifier code")
441
-
442
- func = namespace[function_name]
443
-
444
- # Create and return AsyncVerifierFunction with SHA if available
445
- # Since the function was created via exec, we need to provide the raw code
446
- verifier_func = AsyncVerifierFunction(
447
- func=func,
448
- key=verifier_key,
449
- extra_requirements=[],
502
+ # Use verifier_from_string to create the verifier
503
+ verifier_func = verifier_from_string(
504
+ verifier_func=verifier_code,
450
505
  verifier_id=verifier_id,
451
- sha256=verifier_sha, # Pass SHA directly to constructor
452
- raw_code=verifier_code # Provide raw code since func won't have extractable source
506
+ verifier_key=verifier_key,
507
+ sha256=verifier_sha,
453
508
  )
454
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
+
455
516
  # Store the original verifier code for reference
456
517
  verifier_func._verifier_code = verifier_code
457
-
518
+
458
519
  return verifier_func
459
-
520
+
460
521
  async def _load_verifier(self, verifier_id: str) -> "AsyncVerifierFunction":
461
522
  """Load a verifier by ID and create an AsyncVerifierFunction.
462
-
523
+
463
524
  Args:
464
525
  verifier_id: The verifier ID to fetch
465
-
526
+
466
527
  Returns:
467
528
  AsyncVerifierFunction created from the verifier code
468
529
  """
469
530
  # Fetch verifier from API
470
531
  response = await self.client.request("GET", f"/v1/verifier/{verifier_id}")
471
532
  verifier_data = response.json()
472
-
533
+
473
534
  # Use the common method to create verifier
474
535
  return await self._create_verifier_from_data(
475
536
  verifier_id=verifier_id,
476
537
  verifier_key=verifier_data["key"],
477
538
  verifier_code=verifier_data["code"],
478
- verifier_sha=verifier_data.get("sha256", "")
539
+ verifier_sha=verifier_data.get("sha256", ""),
479
540
  )
480
541
 
481
542
 
@@ -520,23 +581,29 @@ async def _execute_verifier_remote(
520
581
  "timeout": timeout,
521
582
  "region": "us-west-1", # TODO: make configurable
522
583
  }
523
-
584
+
524
585
  # Add bundle data only if upload is needed
525
586
  if needs_upload:
526
587
  bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
527
588
  request_data["bundle"] = bundle_b64
528
-
589
+
529
590
  # Debug logging
530
- logger.debug(f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}")
591
+ logger.debug(
592
+ f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
593
+ )
531
594
  logger.debug(f"Request has bundle: {needs_upload}")
532
595
  logger.debug(f"Using client with base_url: {client.base_url}")
533
596
  logger.debug(f"Request data keys: {list(request_data.keys())}")
534
- logger.debug(f"Bundle size: {len(request_data.get('bundle', ''))} chars" if 'bundle' in request_data else "No bundle")
597
+ logger.debug(
598
+ f"Bundle size: {len(request_data.get('bundle', ''))} chars"
599
+ if "bundle" in request_data
600
+ else "No bundle"
601
+ )
535
602
 
536
603
  # Note: This should be called on the instance URL, not the orchestrator
537
604
  # The instance has manager URLs for verifier execution
538
605
  response = await client.request("POST", "/v1/verifiers/execute", json=request_data)
539
-
606
+
540
607
  # Debug the response
541
608
  response_json = response.json()
542
609
  logger.debug(f"Verifier execute response: {response_json}")
@@ -1,4 +1,4 @@
1
- from ..client import AsyncFleet, AsyncEnv
1
+ from ..client import AsyncFleet, AsyncEnv, Task
2
2
  from ...models import Environment as EnvironmentModel, AccountResponse
3
3
  from typing import List, Optional
4
4
 
@@ -7,6 +7,10 @@ async def make_async(env_key: str, region: Optional[str] = None) -> AsyncEnv:
7
7
  return await AsyncFleet().make(env_key, region=region)
8
8
 
9
9
 
10
+ async def make_for_task_async(task: Task) -> AsyncEnv:
11
+ return await AsyncFleet().make_for_task(task)
12
+
13
+
10
14
  async def list_envs_async() -> List[EnvironmentModel]:
11
15
  return await AsyncFleet().list_envs()
12
16
 
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from .client import AsyncFleet
6
+ from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
7
+
8
+
9
+ _default_client: Optional[AsyncFleet] = None
10
+
11
+
12
+ def get_client() -> AsyncFleet:
13
+ """Get the global default AsyncFleet client, creating it if needed."""
14
+ global _default_client
15
+ if _default_client is None:
16
+ _default_client = AsyncFleet()
17
+ return _default_client
18
+
19
+
20
+ def configure(
21
+ api_key: Optional[str] = None,
22
+ base_url: Optional[str] = None,
23
+ max_retries: int = DEFAULT_MAX_RETRIES,
24
+ timeout: float = DEFAULT_TIMEOUT,
25
+ ) -> AsyncFleet:
26
+ """Configure the global default AsyncFleet client.
27
+
28
+ Returns the configured client instance.
29
+ """
30
+ global _default_client
31
+ _default_client = AsyncFleet(
32
+ api_key=api_key,
33
+ base_url=base_url,
34
+ max_retries=max_retries,
35
+ timeout=timeout,
36
+ )
37
+ return _default_client
38
+
39
+
40
+ def reset_client() -> None:
41
+ """Reset the global default client. A new one will be created on next access."""
42
+ global _default_client
43
+ _default_client = None
@@ -130,7 +130,7 @@ class AsyncInstanceClient:
130
130
 
131
131
  async def _load_resources(self) -> None:
132
132
  if self._resources is None:
133
- response = await self.client.request("GET", "/resources")
133
+ response = await self.client.request("GET", "/resources", timeout=1.0)
134
134
  if response.status_code != 200:
135
135
  self._resources = []
136
136
  return