fleet-python 0.2.29__py3-none-any.whl → 0.2.34__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (63) hide show
  1. examples/diff_example.py +30 -20
  2. examples/dsl_example.py +12 -7
  3. examples/example.py +4 -4
  4. examples/exampleResume.py +191 -0
  5. examples/example_account.py +8 -0
  6. examples/example_action_log.py +2 -2
  7. examples/example_client.py +2 -2
  8. examples/example_mcp_anthropic.py +8 -5
  9. examples/example_mcp_openai.py +2 -2
  10. examples/example_sync.py +4 -4
  11. examples/example_task.py +16 -6
  12. examples/example_tasks.py +3 -6
  13. examples/example_verifier.py +16 -3
  14. examples/gemini_example.py +6 -6
  15. examples/json_tasks_example.py +2 -2
  16. examples/nova_act_example.py +2 -2
  17. examples/openai_example.py +3 -3
  18. examples/openai_simple_example.py +3 -3
  19. examples/query_builder_example.py +11 -7
  20. examples/test_cdp_logging.py +80 -0
  21. fleet/__init__.py +60 -5
  22. fleet/_async/__init__.py +258 -1
  23. fleet/_async/base.py +2 -1
  24. fleet/_async/client.py +164 -144
  25. fleet/_async/env/client.py +2 -0
  26. fleet/_async/global_client.py +43 -0
  27. fleet/_async/instance/client.py +1 -1
  28. fleet/_async/models.py +172 -171
  29. fleet/_async/resources/base.py +1 -1
  30. fleet/_async/resources/mcp.py +55 -0
  31. fleet/_async/resources/sqlite.py +141 -130
  32. fleet/_async/tasks.py +69 -16
  33. fleet/_async/verifiers/__init__.py +2 -2
  34. fleet/_async/verifiers/bundler.py +18 -14
  35. fleet/_async/verifiers/verifier.py +77 -71
  36. fleet/base.py +2 -1
  37. fleet/client.py +162 -148
  38. fleet/config.py +3 -2
  39. fleet/env/__init__.py +9 -1
  40. fleet/env/client.py +4 -1
  41. fleet/global_client.py +43 -0
  42. fleet/instance/__init__.py +1 -1
  43. fleet/instance/client.py +1 -1
  44. fleet/models.py +172 -171
  45. fleet/resources/base.py +1 -1
  46. fleet/resources/mcp.py +11 -16
  47. fleet/resources/sqlite.py +141 -130
  48. fleet/tasks.py +86 -15
  49. fleet/types.py +1 -1
  50. fleet/verifiers/__init__.py +2 -2
  51. fleet/verifiers/bundler.py +18 -14
  52. fleet/verifiers/code.py +1 -1
  53. fleet/verifiers/decorator.py +25 -34
  54. fleet/verifiers/parse.py +98 -68
  55. fleet/verifiers/verifier.py +77 -71
  56. {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/METADATA +9 -9
  57. fleet_python-0.2.34.dist-info/RECORD +76 -0
  58. scripts/fix_sync_imports.py +87 -59
  59. scripts/unasync.py +10 -9
  60. fleet_python-0.2.29.dist-info/RECORD +0 -70
  61. {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/WHEEL +0 -0
  62. {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/licenses/LICENSE +0 -0
  63. {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/top_level.txt +0 -0
fleet/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 SyncVerifierFunction
@@ -51,6 +50,7 @@ from .instance.client import ValidatorType
51
50
  from .resources.base import Resource
52
51
  from .resources.sqlite import SQLiteResource
53
52
  from .resources.browser import BrowserResource
53
+ from ..resources.mcp import MCPResource
54
54
 
55
55
  logger = logging.getLogger(__name__)
56
56
 
@@ -69,18 +69,18 @@ class SyncEnv(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) -> InstanceClient:
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] = InstanceClient(
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 SyncEnv(EnvironmentBase):
104
104
  def browser(self, name: str = "cdp") -> BrowserResource:
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 SyncEnv(EnvironmentBase):
125
130
  return _check_bundle_exists(self._load_client, bundle_hash)
126
131
 
127
132
  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 _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 Fleet:
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 = SyncWrapper(
175
182
  api_key=api_key,
@@ -189,23 +196,27 @@ class Fleet:
189
196
  response = self.client.request("GET", f"/v1/env/{env_key}")
190
197
  return EnvironmentModel(**response.json())
191
198
 
192
- def make(
193
- self, env_key: str, region: Optional[str] = None
194
- ) -> SyncEnv:
199
+ def make(self, env_key: str, region: Optional[str] = None) -> SyncEnv:
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 = 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
 
@@ -249,14 +260,16 @@ class Fleet:
249
260
 
250
261
  def delete(self, instance_id: str) -> InstanceResponse:
251
262
  return _delete_instance(self.client, instance_id)
252
-
263
+
253
264
  def load_tasks_from_file(self, filename: str) -> List[Task]:
254
- with open(filename, 'r', encoding='utf-8') as f:
265
+ with open(filename, "r", encoding="utf-8") as f:
255
266
  tasks_data = f.read()
256
267
 
257
268
  return self.load_task_array_from_string(tasks_data)
258
269
 
259
- 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]:
260
273
  tasks = []
261
274
 
262
275
  json_tasks = json.loads(serialized_tasks)
@@ -270,73 +283,102 @@ class Fleet:
270
283
  return self.load_task_from_json(task_json)
271
284
 
272
285
  def load_task_from_json(self, task_json: Dict) -> Task:
273
- verifier = None
274
286
  try:
275
- if 'verifier_id' in task_json and task_json['verifier_id']:
287
+ if "verifier_id" in task_json and task_json["verifier_id"]:
276
288
  verifier = self._create_verifier_from_data(
277
- verifier_id=task_json['verifier_id'],
278
- verifier_key=task_json['key'],
279
- verifier_code=task_json['verifier_func'],
280
- verifier_sha=task_json.get('verifier_sha', '')
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", ""),
281
293
  )
282
294
  except Exception as e:
283
295
  logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
284
-
296
+
285
297
  task = Task(
286
- key=task_json['key'],
287
- prompt=task_json['prompt'],
288
- env_id=task_json['env_id'], # Use env_id from the data
289
- created_at=task_json['created_at'],
290
- version=task_json.get('version'),
291
- env_variables=task_json.get('env_variables', {}),
292
- verifier_func=task_json.get('verifier_func'), # Set verifier code
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
293
305
  verifier=verifier, # Use created verifier or None
294
- metadata=task_json.get('metadata', {}) # Default empty metadata
306
+ metadata=task_json.get("metadata", {}), # Default empty metadata
295
307
  )
296
308
  return task
297
309
 
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.
298
318
 
299
- def load_tasks(self, env_key: Optional[str] = None, task_keys: Optional[List[str]] = None) -> List[Task]:
300
- """Load tasks for the authenticated team, optionally filtered by environment.
301
-
302
319
  Args:
303
320
  env_key: Optional environment key to filter tasks by
304
-
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
+
305
325
  Returns:
306
326
  List[Task] containing Task objects
307
327
  """
308
328
  params = {}
309
329
  if env_key is not None:
310
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
311
335
 
312
- if task_keys is not None:
313
- params["task_keys"] = task_keys
314
-
315
336
  response = self.client.request("GET", "/v1/tasks", params=params)
316
337
  task_list_response = TaskListResponse(**response.json())
317
-
338
+
318
339
  # Transform TaskResponse objects to Task objects
319
340
  tasks = []
320
341
  for task_response in task_list_response.tasks:
321
342
  # Create verifier function if verifier data is present
322
343
  verifier = None
323
344
  verifier_func = task_response.verifier_func
324
-
345
+
325
346
  if task_response.verifier:
326
- # Use verifier code from the embedded verifier object
327
- verifier_func = task_response.verifier.code
328
-
329
- # Create VerifierFunction from the embedded data
330
- try:
331
- verifier = self._create_verifier_from_data(
332
- verifier_id=task_response.verifier.verifier_id,
333
- verifier_key=task_response.verifier.key,
334
- verifier_code=task_response.verifier.code,
335
- verifier_sha=task_response.verifier.sha256
336
- )
337
- except Exception as e:
338
- logger.warning(f"Failed to create verifier {task_response.verifier.key}: {e}")
339
-
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 = 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 = 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
+
340
382
  task = Task(
341
383
  key=task_response.key,
342
384
  prompt=task_response.prompt,
@@ -346,21 +388,25 @@ class Fleet:
346
388
  env_variables=task_response.env_variables or {},
347
389
  verifier_func=verifier_func, # Set verifier code
348
390
  verifier=verifier, # Use created verifier or None
349
- verifier_id=task_response.verifier.verifier_id,
350
- verifier_sha=task_response.verifier.sha256,
351
- metadata={} # Default empty metadata
391
+ metadata={}, # Default empty metadata
352
392
  )
353
393
  tasks.append(task)
354
-
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
+
355
399
  return tasks
356
400
 
357
- def export_tasks(self, env_key: Optional[str] = None, filename: Optional[str] = None):
401
+ def export_tasks(
402
+ self, env_key: Optional[str] = None, filename: Optional[str] = None
403
+ ):
358
404
  """Export tasks for the authenticated team, optionally filtered by environment.
359
-
405
+
360
406
  Args:
361
407
  env_key: Optional environment key to filter tasks by
362
408
  filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
363
-
409
+
364
410
  Returns:
365
411
  str: Path to the exported file if tasks were written, None if no tasks found
366
412
  """
@@ -372,38 +418,38 @@ class Fleet:
372
418
  filename = f"tasks_{env_key}.json"
373
419
  else:
374
420
  filename = "tasks.json"
375
-
421
+
376
422
  # Convert tasks to serializable format
377
423
  tasks_data = []
378
424
  for task in tasks:
379
425
  task_dict = task.model_dump()
380
426
  # Remove non-serializable verifier object, keep verifier_func (code string)
381
- if 'verifier' in task_dict:
382
- task_dict.pop('verifier')
427
+ if "verifier" in task_dict:
428
+ task_dict.pop("verifier")
383
429
  tasks_data.append(task_dict)
384
-
430
+
385
431
  # Write to JSON file
386
- with open(filename, 'w', encoding='utf-8') as f:
432
+ with open(filename, "w", encoding="utf-8") as f:
387
433
  json.dump(tasks_data, f, indent=2, default=str)
388
-
434
+
389
435
  logger.info(f"Exported {len(tasks)} tasks to {filename}")
390
436
  return filename
391
437
  else:
392
438
  logger.info("No tasks found to export")
393
439
  return None
394
-
440
+
395
441
  def import_tasks(self, filename: str):
396
442
  """Import tasks from a JSON file.
397
-
443
+
398
444
  Args:
399
445
  filename: Path to the JSON file of Task objects to import
400
-
446
+
401
447
  Returns:
402
448
  List[Task] containing imported Task objects
403
449
  """
404
- with open(filename, 'r', encoding='utf-8') as f:
450
+ with open(filename, "r", encoding="utf-8") as f:
405
451
  tasks_data = json.load(f)
406
-
452
+
407
453
  # Create tasks from the loaded data
408
454
  tasks = []
409
455
  for task_data in tasks_data:
@@ -420,7 +466,9 @@ class Fleet:
420
466
  env_variables=task.env_variables or {},
421
467
  )
422
468
  try:
423
- response = self.client.request("POST", "/v1/tasks", json=payload.model_dump())
469
+ response = self.client.request(
470
+ "POST", "/v1/tasks", json=payload.model_dump()
471
+ )
424
472
  except Exception as e:
425
473
  logger.error(f"Failed to import task {task.key}: {e}")
426
474
  continue
@@ -433,25 +481,22 @@ class Fleet:
433
481
  """
434
482
  response = self.client.request("GET", "/v1/account")
435
483
  return AccountResponse(**response.json())
436
-
484
+
437
485
  def _create_verifier_from_data(
438
- self,
439
- verifier_id: str,
440
- verifier_key: str,
441
- verifier_code: str,
442
- verifier_sha: str
486
+ self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
443
487
  ) -> "SyncVerifierFunction":
444
488
  """Create an AsyncVerifierFunction from verifier data.
445
-
489
+
446
490
  Args:
447
491
  verifier_id: The verifier ID
448
492
  verifier_key: The verifier key
449
493
  verifier_code: The verifier code
450
494
  verifier_sha: The verifier SHA256
451
-
495
+
452
496
  Returns:
453
497
  AsyncVerifierFunction created from the verifier code
454
498
  """
499
+ from ..tasks import verifier_from_string
455
500
  from .verifiers.verifier import SyncVerifierFunction
456
501
 
457
502
  # Convert async verifier code to sync
@@ -460,81 +505,44 @@ class Fleet:
460
505
  if 'await ' in verifier_code:
461
506
  verifier_code = verifier_code.replace('await ', '')
462
507
 
463
- # Check if this is a new format verifier (has before/after parameters)
464
- if 'before: DatabaseSnapshot' in verifier_code and 'after: DatabaseSnapshot' in verifier_code:
465
- # Convert new format to old format
466
- verifier_code = convert_new_to_old_verifier(verifier_code)
467
- # Update function name since wrapper adds _wrapper suffix
468
- original_name = extract_function_name(verifier_code.split('\n')[0])
469
- if original_name and original_name.endswith('_wrapper'):
470
- function_name = original_name
471
- else:
472
- function_name = extract_function_name(verifier_code)
473
- else:
474
- # Extract function name from code
475
- function_name = extract_function_name(verifier_code)
476
-
477
- if not function_name:
478
- raise ValueError(f"Could not extract function name from verifier {verifier_id}")
479
-
480
- # Create a function object from the code
481
- # Import necessary classes for the namespace
482
- from .verifiers.db import IgnoreConfig, DatabaseSnapshot
483
-
484
- # Create a namespace for the function
485
- namespace = {
486
- '__builtins__': __builtins__,
487
- 'Environment': object, # Placeholder, will be provided at runtime
488
- 'IgnoreConfig': IgnoreConfig,
489
- 'DatabaseSnapshot': DatabaseSnapshot,
490
- 'TASK_FAILED_SCORE': 0,
491
- 'TASK_SUCCESSFUL_SCORE': 1,
492
- }
493
-
494
- # Execute the code to define the function
495
- exec(verifier_code, namespace)
496
-
497
- # Get the function object
498
- if function_name not in namespace:
499
- raise ValueError(f"Function {function_name} not found in verifier code")
500
-
501
- func = namespace[function_name]
502
-
503
- # Create and return AsyncVerifierFunction with SHA if available
504
- # Since the function was created via exec, we need to provide the raw code
505
- verifier_func = SyncVerifierFunction(
506
- func=func,
507
- key=verifier_key,
508
- extra_requirements=[],
508
+ # Use verifier_from_string to create the verifier
509
+ verifier_func = verifier_from_string(
510
+ verifier_func=verifier_code,
509
511
  verifier_id=verifier_id,
510
- sha256=verifier_sha, # Pass SHA directly to constructor
511
- raw_code=verifier_code # Provide raw code since func won't have extractable source
512
+ verifier_key=verifier_key,
513
+ sha256=verifier_sha,
512
514
  )
513
515
 
516
+ # Ensure we return an AsyncVerifierFunction
517
+ if not isinstance(verifier_func, SyncVerifierFunction):
518
+ raise TypeError(
519
+ f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
520
+ )
521
+
514
522
  # Store the original verifier code for reference
515
523
  verifier_func._verifier_code = verifier_code
516
-
524
+
517
525
  return verifier_func
518
-
526
+
519
527
  def _load_verifier(self, verifier_id: str) -> "SyncVerifierFunction":
520
528
  """Load a verifier by ID and create an AsyncVerifierFunction.
521
-
529
+
522
530
  Args:
523
531
  verifier_id: The verifier ID to fetch
524
-
532
+
525
533
  Returns:
526
534
  AsyncVerifierFunction created from the verifier code
527
535
  """
528
536
  # Fetch verifier from API
529
537
  response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
530
538
  verifier_data = response.json()
531
-
539
+
532
540
  # Use the common method to create verifier
533
541
  return self._create_verifier_from_data(
534
542
  verifier_id=verifier_id,
535
543
  verifier_key=verifier_data["key"],
536
544
  verifier_code=verifier_data["code"],
537
- verifier_sha=verifier_data.get("sha256", "")
545
+ verifier_sha=verifier_data.get("sha256", ""),
538
546
  )
539
547
 
540
548
 
@@ -579,23 +587,29 @@ def _execute_verifier_remote(
579
587
  "timeout": timeout,
580
588
  "region": "us-west-1", # TODO: make configurable
581
589
  }
582
-
590
+
583
591
  # Add bundle data only if upload is needed
584
592
  if needs_upload:
585
593
  bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
586
594
  request_data["bundle"] = bundle_b64
587
-
595
+
588
596
  # Debug logging
589
- logger.debug(f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}")
597
+ logger.debug(
598
+ f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
599
+ )
590
600
  logger.debug(f"Request has bundle: {needs_upload}")
591
601
  logger.debug(f"Using client with base_url: {client.base_url}")
592
602
  logger.debug(f"Request data keys: {list(request_data.keys())}")
593
- logger.debug(f"Bundle size: {len(request_data.get('bundle', ''))} chars" if 'bundle' in request_data else "No bundle")
603
+ logger.debug(
604
+ f"Bundle size: {len(request_data.get('bundle', ''))} chars"
605
+ if "bundle" in request_data
606
+ else "No bundle"
607
+ )
594
608
 
595
609
  # Note: This should be called on the instance URL, not the orchestrator
596
610
  # The instance has manager URLs for verifier execution
597
611
  response = client.request("POST", "/v1/verifiers/execute", json=request_data)
598
-
612
+
599
613
  # Debug the response
600
614
  response_json = response.json()
601
615
  logger.debug(f"Verifier execute response: {response_json}")
fleet/config.py CHANGED
@@ -1,9 +1,10 @@
1
- DEFAULT_MAX_RETRIES = 5
2
- DEFAULT_TIMEOUT = 60.0
1
+ DEFAULT_MAX_RETRIES = 3
2
+ DEFAULT_TIMEOUT = 180.0
3
3
 
4
4
  GLOBAL_BASE_URL = "https://orchestrator.fleetai.com"
5
5
  REGION_BASE_URL = {
6
6
  "us-west-1": "https://us-west-1.fleetai.com",
7
7
  "us-east-1": "https://us-east-1.fleetai.com",
8
8
  "eu-west-2": "https://eu-west-2.fleetai.com",
9
+ "staging": "https://staging.fleetai.com",
9
10
  }
fleet/env/__init__.py CHANGED
@@ -1,6 +1,14 @@
1
1
  """Fleet env module - convenience functions for environment management."""
2
2
 
3
- from .client import make, make_for_task, list_envs, list_regions, get, list_instances, account
3
+ from .client import (
4
+ make,
5
+ make_for_task,
6
+ list_envs,
7
+ list_regions,
8
+ get,
9
+ list_instances,
10
+ account,
11
+ )
4
12
 
5
13
  # Import async versions from _async
6
14
  from .._async.env.client import (
fleet/env/client.py CHANGED
@@ -6,12 +6,15 @@ from typing import List, Optional
6
6
  def make(env_key: str, region: Optional[str] = None) -> SyncEnv:
7
7
  return Fleet().make(env_key, region=region)
8
8
 
9
- def make_for_task(task: Task) -> SyncEnv:
9
+
10
+ def make_for_task_async(task: Task) -> SyncEnv:
10
11
  return Fleet().make_for_task(task)
11
12
 
13
+
12
14
  def list_envs() -> List[EnvironmentModel]:
13
15
  return Fleet().list_envs()
14
16
 
17
+
15
18
  def list_regions() -> List[str]:
16
19
  return Fleet().list_regions()
17
20
 
fleet/global_client.py ADDED
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from .client import Fleet
6
+ from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
7
+
8
+
9
+ _default_client: Optional[Fleet] = None
10
+
11
+
12
+ def get_client() -> Fleet:
13
+ """Get the global default AsyncFleet client, creating it if needed."""
14
+ global _default_client
15
+ if _default_client is None:
16
+ _default_client = Fleet()
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
+ ) -> Fleet:
26
+ """Configure the global default AsyncFleet client.
27
+
28
+ Returns the configured client instance.
29
+ """
30
+ global _default_client
31
+ _default_client = Fleet(
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
@@ -19,5 +19,5 @@ __all__ = [
19
19
  "ChromeStartRequest",
20
20
  "ChromeStartResponse",
21
21
  "ChromeStatusResponse",
22
- "ExecuteFunctionResponse"
22
+ "ExecuteFunctionResponse",
23
23
  ]
fleet/instance/client.py CHANGED
@@ -130,7 +130,7 @@ class InstanceClient:
130
130
 
131
131
  def _load_resources(self) -> None:
132
132
  if self._resources is None:
133
- response = self.client.request("GET", "/resources")
133
+ response = self.client.request("GET", "/resources", timeout=1.0)
134
134
  if response.status_code != 200:
135
135
  self._resources = []
136
136
  return