jaf-py 2.5.10__py3-none-any.whl → 2.5.11__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.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +269 -210
  54. jaf/core/types.py +371 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +360 -279
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.11.dist-info/RECORD +97 -0
  88. jaf_py-2.5.10.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
@@ -39,22 +39,18 @@ class InMemoryTaskState:
39
39
  self.config = config
40
40
  self.tasks: Dict[str, A2ATaskStorage] = {}
41
41
  self.context_index: Dict[str, Set[str]] = defaultdict(set) # contextId -> Set[taskId]
42
- self.state_index: Dict[str, Set[str]] = defaultdict(set) # state -> Set[taskId]
43
- self.stats = {
44
- 'total_tasks': 0,
45
- 'created_at': datetime.now()
46
- }
42
+ self.state_index: Dict[str, Set[str]] = defaultdict(set) # state -> Set[taskId]
43
+ self.stats = {"total_tasks": 0, "created_at": datetime.now()}
47
44
  self.lock = asyncio.Lock()
48
45
 
49
- def create_a2a_in_memory_task_provider(
50
- config: A2AInMemoryTaskConfig
51
- ) -> A2ATaskProvider:
46
+
47
+ def create_a2a_in_memory_task_provider(config: A2AInMemoryTaskConfig) -> A2ATaskProvider:
52
48
  """
53
49
  Create an in-memory A2A task provider
54
-
50
+
55
51
  Args:
56
52
  config: Configuration for the in-memory provider
57
-
53
+
58
54
  Returns:
59
55
  A2ATaskProvider implementation
60
56
  """
@@ -64,13 +60,18 @@ def create_a2a_in_memory_task_provider(
64
60
  def _convert_storage_to_serialized(stored: A2ATaskStorage) -> A2ATaskSerialized:
65
61
  """Convert storage format to serialized format"""
66
62
  import json
63
+
67
64
  metadata_str = None
68
65
  if stored.metadata:
66
+
69
67
  def datetime_converter(obj):
70
68
  if isinstance(obj, datetime):
71
69
  return obj.isoformat()
72
70
  raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
73
- metadata_str = json.dumps(stored.metadata, separators=(',', ':'), default=datetime_converter)
71
+
72
+ metadata_str = json.dumps(
73
+ stored.metadata, separators=(",", ":"), default=datetime_converter
74
+ )
74
75
 
75
76
  return A2ATaskSerialized(
76
77
  task_id=stored.task_id,
@@ -80,7 +81,7 @@ def create_a2a_in_memory_task_provider(
80
81
  status_message=stored.status_message,
81
82
  created_at=stored.created_at.isoformat(),
82
83
  updated_at=stored.updated_at.isoformat(),
83
- metadata=metadata_str
84
+ metadata=metadata_str,
84
85
  )
85
86
 
86
87
  def _add_to_indices(task_id: str, context_id: str, task_state: TaskState) -> None:
@@ -103,17 +104,16 @@ def create_a2a_in_memory_task_provider(
103
104
  if len(state.tasks) >= config.max_tasks:
104
105
  return create_a2a_failure(
105
106
  create_a2a_task_storage_error(
106
- 'store',
107
- 'in-memory',
107
+ "store",
108
+ "in-memory",
108
109
  None,
109
- Exception(f'Storage limit exceeded: maximum {config.max_tasks} tasks allowed')
110
+ Exception(f"Storage limit exceeded: maximum {config.max_tasks} tasks allowed"),
110
111
  )
111
112
  )
112
113
  return create_a2a_success(None)
113
114
 
114
115
  async def store_task(
115
- task: A2ATask,
116
- metadata: Optional[Dict[str, Any]] = None
116
+ task: A2ATask, metadata: Optional[Dict[str, Any]] = None
117
117
  ) -> A2AResult[None]:
118
118
  """Store a new A2A task"""
119
119
  try:
@@ -143,21 +143,21 @@ def create_a2a_in_memory_task_provider(
143
143
  status_message=serialized_task.status_message,
144
144
  created_at=datetime.fromisoformat(serialized_task.created_at),
145
145
  updated_at=datetime.fromisoformat(serialized_task.updated_at),
146
- expires_at=metadata.get('expires_at') if metadata else None,
147
- metadata=metadata
146
+ expires_at=metadata.get("expires_at") if metadata else None,
147
+ metadata=metadata,
148
148
  )
149
149
 
150
150
  async with state.lock:
151
151
  # Store task and update indices
152
152
  state.tasks[task.id] = task_storage
153
153
  _add_to_indices(task.id, task.context_id, task.status.state)
154
- state.stats['total_tasks'] = len(state.tasks)
154
+ state.stats["total_tasks"] = len(state.tasks)
155
155
 
156
156
  return create_a2a_success(None)
157
157
 
158
158
  except Exception as error:
159
159
  return create_a2a_failure(
160
- create_a2a_task_storage_error('store', 'in-memory', task.id, error)
160
+ create_a2a_task_storage_error("store", "in-memory", task.id, error)
161
161
  )
162
162
 
163
163
  async def get_task(task_id: str) -> A2AResult[Optional[A2ATask]]:
@@ -182,20 +182,17 @@ def create_a2a_in_memory_task_provider(
182
182
 
183
183
  except Exception as error:
184
184
  return create_a2a_failure(
185
- create_a2a_task_storage_error('get', 'in-memory', task_id, error)
185
+ create_a2a_task_storage_error("get", "in-memory", task_id, error)
186
186
  )
187
187
 
188
188
  async def update_task(
189
- task: A2ATask,
190
- metadata: Optional[Dict[str, Any]] = None
189
+ task: A2ATask, metadata: Optional[Dict[str, Any]] = None
191
190
  ) -> A2AResult[None]:
192
191
  """Update an existing task"""
193
192
  try:
194
193
  existing = state.tasks.get(task.id)
195
194
  if not existing:
196
- return create_a2a_failure(
197
- create_a2a_task_not_found_error(task.id, 'in-memory')
198
- )
195
+ return create_a2a_failure(create_a2a_task_not_found_error(task.id, "in-memory"))
199
196
 
200
197
  # Validate and sanitize task
201
198
  sanitize_result = sanitize_task(task)
@@ -222,7 +219,7 @@ def create_a2a_in_memory_task_provider(
222
219
  created_at=existing.created_at,
223
220
  updated_at=datetime.fromisoformat(serialized_task.updated_at),
224
221
  expires_at=existing.expires_at,
225
- metadata=merged_metadata
222
+ metadata=merged_metadata,
226
223
  )
227
224
 
228
225
  # Update indices if state changed
@@ -237,22 +234,20 @@ def create_a2a_in_memory_task_provider(
237
234
 
238
235
  except Exception as error:
239
236
  return create_a2a_failure(
240
- create_a2a_task_storage_error('update', 'in-memory', task.id, error)
237
+ create_a2a_task_storage_error("update", "in-memory", task.id, error)
241
238
  )
242
239
 
243
240
  async def update_task_status(
244
241
  task_id: str,
245
242
  new_state: TaskState,
246
243
  status_message: Optional[Any] = None,
247
- timestamp: Optional[str] = None
244
+ timestamp: Optional[str] = None,
248
245
  ) -> A2AResult[None]:
249
246
  """Update task status only (optimized for frequent status changes)"""
250
247
  try:
251
248
  existing = state.tasks.get(task_id)
252
249
  if not existing:
253
- return create_a2a_failure(
254
- create_a2a_task_not_found_error(task_id, 'in-memory')
255
- )
250
+ return create_a2a_failure(create_a2a_task_not_found_error(task_id, "in-memory"))
256
251
 
257
252
  # Deserialize existing task
258
253
  serialized = _convert_storage_to_serialized(existing)
@@ -270,23 +265,23 @@ def create_a2a_in_memory_task_provider(
270
265
 
271
266
  # Update task status
272
267
  from ...types import A2ATaskStatus
268
+
273
269
  updated_status = A2ATaskStatus(
274
270
  state=new_state,
275
271
  message=status_message,
276
- timestamp=timestamp or datetime.now().isoformat()
272
+ timestamp=timestamp or datetime.now().isoformat(),
277
273
  )
278
274
 
279
- updated_task = task.model_copy(update={
280
- 'status': updated_status,
281
- 'history': updated_history
282
- })
275
+ updated_task = task.model_copy(
276
+ update={"status": updated_status, "history": updated_history}
277
+ )
283
278
 
284
279
  # Use update_task for the actual update
285
280
  return await update_task(updated_task)
286
281
 
287
282
  except Exception as error:
288
283
  return create_a2a_failure(
289
- create_a2a_task_storage_error('update-status', 'in-memory', task_id, error)
284
+ create_a2a_task_storage_error("update-status", "in-memory", task_id, error)
290
285
  )
291
286
 
292
287
  async def find_tasks(query: A2ATaskQuery) -> A2AResult[List[A2ATask]]:
@@ -305,20 +300,22 @@ def create_a2a_in_memory_task_provider(
305
300
  # Filter by task_id
306
301
  if query.task_id and task_id != query.task_id:
307
302
  continue
308
-
303
+
309
304
  # Check expiration
310
305
  if stored.expires_at and stored.expires_at < datetime.now():
311
306
  continue
312
307
 
313
308
  # Date filtering - use metadata created_at if available, otherwise use stored created_at
314
309
  task_timestamp = stored.created_at
315
- if stored.metadata and stored.metadata.get('created_at'):
310
+ if stored.metadata and stored.metadata.get("created_at"):
316
311
  try:
317
- task_timestamp = datetime.fromisoformat(stored.metadata['created_at'].replace('Z', '+00:00'))
312
+ task_timestamp = datetime.fromisoformat(
313
+ stored.metadata["created_at"].replace("Z", "+00:00")
314
+ )
318
315
  except:
319
316
  # Fall back to stored timestamp if parsing fails
320
317
  pass
321
-
318
+
322
319
  if query.since and task_timestamp < query.since:
323
320
  continue
324
321
  if query.until and task_timestamp > query.until:
@@ -330,28 +327,24 @@ def create_a2a_in_memory_task_provider(
330
327
 
331
328
  if isinstance(deserialize_result, Success):
332
329
  results.append(deserialize_result.data)
333
-
330
+
334
331
  # Sort by timestamp (newest first)
335
- results.sort(
336
- key=lambda t: t.status.timestamp or "1970-01-01T00:00:00Z",
337
- reverse=True
338
- )
332
+ results.sort(key=lambda t: t.status.timestamp or "1970-01-01T00:00:00Z", reverse=True)
339
333
 
340
334
  # Apply pagination
341
335
  offset = query.offset or 0
342
336
  limit = query.limit or len(results)
343
- paginated_results = results[offset:offset + limit]
337
+ paginated_results = results[offset : offset + limit]
344
338
 
345
339
  return create_a2a_success(paginated_results)
346
340
 
347
341
  except Exception as error:
348
342
  return create_a2a_failure(
349
- create_a2a_task_storage_error('find', 'in-memory', None, error)
343
+ create_a2a_task_storage_error("find", "in-memory", None, error)
350
344
  )
351
345
 
352
346
  async def get_tasks_by_context(
353
- context_id: str,
354
- limit: Optional[int] = None
347
+ context_id: str, limit: Optional[int] = None
355
348
  ) -> A2AResult[List[A2ATask]]:
356
349
  """Get tasks by context ID"""
357
350
  return await find_tasks(A2ATaskQuery(context_id=context_id, limit=limit))
@@ -366,13 +359,13 @@ def create_a2a_in_memory_task_provider(
366
359
  # Remove from storage and indices
367
360
  del state.tasks[task_id]
368
361
  _remove_from_indices(task_id, existing.context_id, existing.state)
369
- state.stats['total_tasks'] = len(state.tasks)
362
+ state.stats["total_tasks"] = len(state.tasks)
370
363
 
371
364
  return create_a2a_success(True)
372
365
 
373
366
  except Exception as error:
374
367
  return create_a2a_failure(
375
- create_a2a_task_storage_error('delete', 'in-memory', task_id, error)
368
+ create_a2a_task_storage_error("delete", "in-memory", task_id, error)
376
369
  )
377
370
 
378
371
  async def delete_tasks_by_context(context_id: str) -> A2AResult[int]:
@@ -390,13 +383,13 @@ def create_a2a_in_memory_task_provider(
390
383
  _remove_from_indices(task_id, stored_task.context_id, stored_task.state)
391
384
  deleted_count += 1
392
385
 
393
- state.stats['total_tasks'] = len(state.tasks)
386
+ state.stats["total_tasks"] = len(state.tasks)
394
387
 
395
388
  return create_a2a_success(deleted_count)
396
389
 
397
390
  except Exception as error:
398
391
  return create_a2a_failure(
399
- create_a2a_task_storage_error('delete-by-context', 'in-memory', None, error)
392
+ create_a2a_task_storage_error("delete-by-context", "in-memory", None, error)
400
393
  )
401
394
 
402
395
  async def cleanup_expired_tasks() -> A2AResult[int]:
@@ -421,7 +414,7 @@ def create_a2a_in_memory_task_provider(
421
414
 
422
415
  except Exception as error:
423
416
  return create_a2a_failure(
424
- create_a2a_task_storage_error('cleanup', 'in-memory', None, error)
417
+ create_a2a_task_storage_error("cleanup", "in-memory", None, error)
425
418
  )
426
419
 
427
420
  async def cleanup_expired_tasks_by_context(context_id: str) -> A2AResult[int]:
@@ -429,7 +422,7 @@ def create_a2a_in_memory_task_provider(
429
422
  try:
430
423
  now = datetime.now()
431
424
  cleaned_count = 0
432
-
425
+
433
426
  task_ids = state.context_index.get(context_id, set()).copy()
434
427
  expired_task_ids = []
435
428
  for task_id in task_ids:
@@ -441,11 +434,11 @@ def create_a2a_in_memory_task_provider(
441
434
  delete_result = await delete_task(task_id)
442
435
  if isinstance(delete_result.data, bool) and delete_result.data:
443
436
  cleaned_count += 1
444
-
437
+
445
438
  return create_a2a_success(cleaned_count)
446
439
  except Exception as error:
447
440
  return create_a2a_failure(
448
- create_a2a_task_storage_error('cleanup-by-context', 'in-memory', None, error)
441
+ create_a2a_task_storage_error("cleanup-by-context", "in-memory", None, error)
449
442
  )
450
443
 
451
444
  async def get_task_stats(context_id: Optional[str] = None) -> A2AResult[Dict[str, Any]]:
@@ -479,12 +472,12 @@ def create_a2a_in_memory_task_provider(
479
472
  oldest_task = stored.created_at
480
473
  if not newest_task or stored.created_at > newest_task:
481
474
  newest_task = stored.created_at
482
-
475
+
483
476
  stats = {
484
- 'total_tasks': total_tasks,
485
- 'tasks_by_state': dict(tasks_by_state),
486
- 'oldest_task': oldest_task,
487
- 'newest_task': newest_task
477
+ "total_tasks": total_tasks,
478
+ "tasks_by_state": dict(tasks_by_state),
479
+ "oldest_task": oldest_task,
480
+ "newest_task": newest_task,
488
481
  }
489
482
  # Also add individual state counts for backwards compatibility
490
483
  for task_state in TaskState:
@@ -494,7 +487,7 @@ def create_a2a_in_memory_task_provider(
494
487
 
495
488
  except Exception as error:
496
489
  return create_a2a_failure(
497
- create_a2a_task_storage_error('stats', 'in-memory', None, error)
490
+ create_a2a_task_storage_error("stats", "in-memory", None, error)
498
491
  )
499
492
 
500
493
  async def health_check() -> A2AResult[Dict[str, Any]]:
@@ -506,18 +499,17 @@ def create_a2a_in_memory_task_provider(
506
499
  task_count = len(state.tasks)
507
500
  latency_ms = (datetime.now() - start_time).total_seconds() * 1000
508
501
 
509
- return create_a2a_success({
510
- 'healthy': True,
511
- 'provider': 'in_memory',
512
- 'latency_ms': latency_ms,
513
- 'task_count': task_count
514
- })
502
+ return create_a2a_success(
503
+ {
504
+ "healthy": True,
505
+ "provider": "in_memory",
506
+ "latency_ms": latency_ms,
507
+ "task_count": task_count,
508
+ }
509
+ )
515
510
 
516
511
  except Exception as error:
517
- return create_a2a_success({
518
- 'healthy': False,
519
- 'error': str(error)
520
- })
512
+ return create_a2a_success({"healthy": False, "error": str(error)})
521
513
 
522
514
  async def close() -> A2AResult[None]:
523
515
  """Close/cleanup the provider"""
@@ -526,35 +518,47 @@ def create_a2a_in_memory_task_provider(
526
518
  state.tasks.clear()
527
519
  state.context_index.clear()
528
520
  state.state_index.clear()
529
- state.stats = {'total_tasks': 0, 'created_at': datetime.now()}
521
+ state.stats = {"total_tasks": 0, "created_at": datetime.now()}
530
522
 
531
523
  return create_a2a_success(None)
532
524
 
533
525
  except Exception as error:
534
526
  return create_a2a_failure(
535
- create_a2a_task_storage_error('close', 'in-memory', None, error)
527
+ create_a2a_task_storage_error("close", "in-memory", None, error)
536
528
  )
537
529
 
538
530
  # Create a simple class that implements the protocol
539
531
  class InMemoryA2ATaskProvider:
540
532
  """In-memory implementation of A2ATaskProvider"""
541
533
 
542
- async def store_task(self, task: A2ATask, metadata: Optional[Dict[str, Any]] = None) -> A2AResult[None]:
534
+ async def store_task(
535
+ self, task: A2ATask, metadata: Optional[Dict[str, Any]] = None
536
+ ) -> A2AResult[None]:
543
537
  return await store_task(task, metadata)
544
538
 
545
539
  async def get_task(self, task_id: str) -> A2AResult[Optional[A2ATask]]:
546
540
  return await get_task(task_id)
547
541
 
548
- async def update_task(self, task: A2ATask, metadata: Optional[Dict[str, Any]] = None) -> A2AResult[None]:
542
+ async def update_task(
543
+ self, task: A2ATask, metadata: Optional[Dict[str, Any]] = None
544
+ ) -> A2AResult[None]:
549
545
  return await update_task(task, metadata)
550
546
 
551
- async def update_task_status(self, task_id: str, state: TaskState, status_message: Optional[Any] = None, timestamp: Optional[str] = None) -> A2AResult[None]:
547
+ async def update_task_status(
548
+ self,
549
+ task_id: str,
550
+ state: TaskState,
551
+ status_message: Optional[Any] = None,
552
+ timestamp: Optional[str] = None,
553
+ ) -> A2AResult[None]:
552
554
  return await update_task_status(task_id, state, status_message, timestamp)
553
555
 
554
556
  async def find_tasks(self, query: A2ATaskQuery) -> A2AResult[List[A2ATask]]:
555
557
  return await find_tasks(query)
556
558
 
557
- async def get_tasks_by_context(self, context_id: str, limit: Optional[int] = None) -> A2AResult[List[A2ATask]]:
559
+ async def get_tasks_by_context(
560
+ self, context_id: str, limit: Optional[int] = None
561
+ ) -> A2AResult[List[A2ATask]]:
558
562
  return await get_tasks_by_context(context_id, limit)
559
563
 
560
564
  async def delete_task(self, task_id: str) -> A2AResult[bool]:
@@ -569,7 +573,9 @@ def create_a2a_in_memory_task_provider(
569
573
  async def cleanup_expired_tasks_by_context(self, context_id: str) -> A2AResult[int]:
570
574
  return await cleanup_expired_tasks_by_context(context_id)
571
575
 
572
- async def get_task_stats(self, context_id: Optional[str] = None) -> A2AResult[Dict[str, Any]]:
576
+ async def get_task_stats(
577
+ self, context_id: Optional[str] = None
578
+ ) -> A2AResult[Dict[str, Any]]:
573
579
  return await get_task_stats(context_id)
574
580
 
575
581
  async def health_check(self) -> A2AResult[Dict[str, Any]]: