jaf-py 2.5.9__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 +361 -280
  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.9.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.9.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
@@ -36,12 +36,14 @@ from jaf.a2a.types import (
36
36
  # Import other providers when they're available
37
37
  try:
38
38
  from jaf.a2a.memory.providers.redis import create_a2a_redis_task_provider
39
+
39
40
  REDIS_AVAILABLE = True
40
41
  except ImportError:
41
42
  REDIS_AVAILABLE = False
42
43
 
43
44
  try:
44
45
  from jaf.a2a.memory.providers.postgres import create_a2a_postgres_task_provider
46
+
45
47
  POSTGRES_AVAILABLE = True
46
48
  except ImportError:
47
49
  POSTGRES_AVAILABLE = False
@@ -51,9 +53,7 @@ class TaskLifecycleTestBase:
51
53
  """Base test class with helper methods for task lifecycle testing"""
52
54
 
53
55
  def create_submission_task(
54
- self,
55
- task_id: str = "lifecycle_task_001",
56
- context_id: str = "lifecycle_ctx_001"
56
+ self, task_id: str = "lifecycle_task_001", context_id: str = "lifecycle_ctx_001"
57
57
  ) -> A2ATask:
58
58
  """Create a task in submitted state"""
59
59
  return A2ATask(
@@ -67,43 +67,36 @@ class TaskLifecycleTestBase:
67
67
  parts=[A2ATextPart(kind="text", text="Please help me with this task")],
68
68
  messageId=f"submit_{task_id}",
69
69
  contextId=context_id,
70
- kind="message"
70
+ kind="message",
71
71
  ),
72
- timestamp=datetime.now(timezone.utc).isoformat()
72
+ timestamp=datetime.now(timezone.utc).isoformat(),
73
73
  ),
74
- metadata={
75
- "created_at": datetime.now(timezone.utc).isoformat(),
76
- "priority": "normal"
77
- }
74
+ metadata={"created_at": datetime.now(timezone.utc).isoformat(), "priority": "normal"},
78
75
  )
79
76
 
80
77
  def create_working_task_update(
81
- self,
82
- base_task: A2ATask,
83
- progress_message: str = "Processing your request..."
78
+ self, base_task: A2ATask, progress_message: str = "Processing your request..."
84
79
  ) -> A2ATask:
85
80
  """Create task update transitioning to working state"""
86
- return base_task.model_copy(update={
87
- "status": A2ATaskStatus(
88
- state=TaskState.WORKING,
89
- message=A2AMessage(
90
- role="agent",
91
- parts=[A2ATextPart(kind="text", text=progress_message)],
92
- messageId=f"working_{base_task.id}",
93
- contextId=base_task.context_id,
94
- kind="message"
81
+ return base_task.model_copy(
82
+ update={
83
+ "status": A2ATaskStatus(
84
+ state=TaskState.WORKING,
85
+ message=A2AMessage(
86
+ role="agent",
87
+ parts=[A2ATextPart(kind="text", text=progress_message)],
88
+ messageId=f"working_{base_task.id}",
89
+ contextId=base_task.context_id,
90
+ kind="message",
91
+ ),
92
+ timestamp=datetime.now(timezone.utc).isoformat(),
95
93
  ),
96
- timestamp=datetime.now(timezone.utc).isoformat()
97
- ),
98
- "history": [
99
- base_task.status.message
100
- ] if base_task.status.message else []
101
- })
94
+ "history": [base_task.status.message] if base_task.status.message else [],
95
+ }
96
+ )
102
97
 
103
98
  def create_completed_task_update(
104
- self,
105
- working_task: A2ATask,
106
- result_text: str = "Task completed successfully"
99
+ self, working_task: A2ATask, result_text: str = "Task completed successfully"
107
100
  ) -> A2ATask:
108
101
  """Create task update transitioning to completed state with artifacts"""
109
102
  completion_message = A2AMessage(
@@ -111,7 +104,7 @@ class TaskLifecycleTestBase:
111
104
  parts=[A2ATextPart(kind="text", text=result_text)],
112
105
  messageId=f"complete_{working_task.id}",
113
106
  contextId=working_task.context_id,
114
- kind="message"
107
+ kind="message",
115
108
  )
116
109
 
117
110
  result_artifact = A2AArtifact(
@@ -120,8 +113,11 @@ class TaskLifecycleTestBase:
120
113
  description="Final result of the completed task",
121
114
  parts=[
122
115
  A2ATextPart(kind="text", text="Here is your completed result."),
123
- A2ADataPart(kind="data", data={"success": True, "timestamp": datetime.now(timezone.utc).isoformat()})
124
- ]
116
+ A2ADataPart(
117
+ kind="data",
118
+ data={"success": True, "timestamp": datetime.now(timezone.utc).isoformat()},
119
+ ),
120
+ ],
125
121
  )
126
122
 
127
123
  # Build complete history
@@ -129,20 +125,20 @@ class TaskLifecycleTestBase:
129
125
  if working_task.status.message:
130
126
  history.append(working_task.status.message)
131
127
 
132
- return working_task.model_copy(update={
133
- "status": A2ATaskStatus(
134
- state=TaskState.COMPLETED,
135
- message=completion_message,
136
- timestamp=datetime.now(timezone.utc).isoformat()
137
- ),
138
- "history": history,
139
- "artifacts": [result_artifact]
140
- })
128
+ return working_task.model_copy(
129
+ update={
130
+ "status": A2ATaskStatus(
131
+ state=TaskState.COMPLETED,
132
+ message=completion_message,
133
+ timestamp=datetime.now(timezone.utc).isoformat(),
134
+ ),
135
+ "history": history,
136
+ "artifacts": [result_artifact],
137
+ }
138
+ )
141
139
 
142
140
  def create_failed_task_update(
143
- self,
144
- working_task: A2ATask,
145
- error_message: str = "Task failed due to an error"
141
+ self, working_task: A2ATask, error_message: str = "Task failed due to an error"
146
142
  ) -> A2ATask:
147
143
  """Create task update transitioning to failed state"""
148
144
  failure_message = A2AMessage(
@@ -150,26 +146,26 @@ class TaskLifecycleTestBase:
150
146
  parts=[A2ATextPart(kind="text", text=error_message)],
151
147
  messageId=f"failed_{working_task.id}",
152
148
  contextId=working_task.context_id,
153
- kind="message"
149
+ kind="message",
154
150
  )
155
151
 
156
152
  history = list(working_task.history or [])
157
153
  if working_task.status.message:
158
154
  history.append(working_task.status.message)
159
155
 
160
- return working_task.model_copy(update={
161
- "status": A2ATaskStatus(
162
- state=TaskState.FAILED,
163
- message=failure_message,
164
- timestamp=datetime.now(timezone.utc).isoformat()
165
- ),
166
- "history": history
167
- })
156
+ return working_task.model_copy(
157
+ update={
158
+ "status": A2ATaskStatus(
159
+ state=TaskState.FAILED,
160
+ message=failure_message,
161
+ timestamp=datetime.now(timezone.utc).isoformat(),
162
+ ),
163
+ "history": history,
164
+ }
165
+ )
168
166
 
169
167
  def create_canceled_task_update(
170
- self,
171
- working_task: A2ATask,
172
- cancel_reason: str = "Task was canceled by user"
168
+ self, working_task: A2ATask, cancel_reason: str = "Task was canceled by user"
173
169
  ) -> A2ATask:
174
170
  """Create task update transitioning to canceled state"""
175
171
  cancel_message = A2AMessage(
@@ -177,28 +173,35 @@ class TaskLifecycleTestBase:
177
173
  parts=[A2ATextPart(kind="text", text=cancel_reason)],
178
174
  messageId=f"cancel_{working_task.id}",
179
175
  contextId=working_task.context_id,
180
- kind="message"
176
+ kind="message",
181
177
  )
182
178
 
183
179
  history = list(working_task.history or [])
184
180
  if working_task.status.message:
185
181
  history.append(working_task.status.message)
186
182
 
187
- return working_task.model_copy(update={
188
- "status": A2ATaskStatus(
189
- state=TaskState.CANCELED,
190
- message=cancel_message,
191
- timestamp=datetime.now(timezone.utc).isoformat()
192
- ),
193
- "history": history
194
- })
183
+ return working_task.model_copy(
184
+ update={
185
+ "status": A2ATaskStatus(
186
+ state=TaskState.CANCELED,
187
+ message=cancel_message,
188
+ timestamp=datetime.now(timezone.utc).isoformat(),
189
+ ),
190
+ "history": history,
191
+ }
192
+ )
195
193
 
196
194
 
197
195
  # Provider parameter list for running tests across all providers
198
196
  PROVIDER_TYPES = [
199
197
  "in_memory",
200
- pytest.param("redis", marks=pytest.mark.skipif(not REDIS_AVAILABLE, reason="Redis not available")),
201
- pytest.param("postgres", marks=pytest.mark.skipif(not POSTGRES_AVAILABLE, reason="PostgreSQL not available"))
198
+ pytest.param(
199
+ "redis", marks=pytest.mark.skipif(not REDIS_AVAILABLE, reason="Redis not available")
200
+ ),
201
+ pytest.param(
202
+ "postgres",
203
+ marks=pytest.mark.skipif(not POSTGRES_AVAILABLE, reason="PostgreSQL not available"),
204
+ ),
202
205
  ]
203
206
 
204
207
 
@@ -220,21 +223,25 @@ async def provider(request):
220
223
  port=6379,
221
224
  db=15, # Use separate DB for testing
222
225
  key_prefix="jaf_test:a2a:tasks:",
223
- password="12345678"
226
+ password="12345678",
224
227
  )
225
228
  try:
226
229
  redis_client = redis.Redis(
227
- host=config.host,
228
- port=config.port,
229
- db=config.db,
230
- password=config.password,
231
- decode_responses=True
230
+ host=config.host,
231
+ port=config.port,
232
+ db=config.db,
233
+ password=config.password,
234
+ decode_responses=True,
232
235
  )
233
236
  await redis_client.ping()
234
237
  await redis_client.flushdb()
235
238
  p_result = await create_a2a_redis_task_provider(config, redis_client)
236
239
  p = p_result.data
237
- except (redis.exceptions.ConnectionError, ConnectionRefusedError, redis.exceptions.AuthenticationError) as e:
240
+ except (
241
+ redis.exceptions.ConnectionError,
242
+ ConnectionRefusedError,
243
+ redis.exceptions.AuthenticationError,
244
+ ) as e:
238
245
  pytest.skip(f"Redis not available at {config.host}:{config.port}: {e}")
239
246
 
240
247
  elif provider_type == "postgres":
@@ -243,7 +250,7 @@ async def provider(request):
243
250
  port=5432,
244
251
  database="jaf_test",
245
252
  username="postgres",
246
- table_name="a2a_tasks_test"
253
+ table_name="a2a_tasks_test",
247
254
  )
248
255
  try:
249
256
  pg_pool = await asyncpg.create_pool(
@@ -265,7 +272,7 @@ async def provider(request):
265
272
  await redis_client.aclose()
266
273
  if pg_pool:
267
274
  await pg_pool.close()
268
- elif provider_type not in ['redis', 'postgres']: # Don't fail if skipped
275
+ elif provider_type not in ["redis", "postgres"]: # Don't fail if skipped
269
276
  pytest.fail(f"Unknown provider type or provider failed to initialize: {provider_type}")
270
277
 
271
278
 
@@ -293,7 +300,9 @@ class TestTaskLifecycleHappyPath(TaskLifecycleTestBase):
293
300
  assert stored_task.status.state == TaskState.SUBMITTED
294
301
 
295
302
  # Step 2: Transition to working state
296
- working_task = self.create_working_task_update(stored_task, "Starting to work on your request...")
303
+ working_task = self.create_working_task_update(
304
+ stored_task, "Starting to work on your request..."
305
+ )
297
306
 
298
307
  update_result = await provider.update_task(working_task)
299
308
  assert update_result.data is None, "Update should succeed"
@@ -314,13 +323,15 @@ class TestTaskLifecycleHappyPath(TaskLifecycleTestBase):
314
323
  parts=[A2ATextPart(kind="text", text="50% complete...")],
315
324
  messageId="progress_001",
316
325
  contextId="happy_ctx_001",
317
- kind="message"
318
- )
326
+ kind="message",
327
+ ),
319
328
  )
320
329
  assert intermediate_update_result.data is None, "Status update should succeed"
321
330
 
322
331
  # Step 4: Transition to completed state
323
- completed_task = self.create_completed_task_update(working_stored, "Your task has been completed successfully!")
332
+ completed_task = self.create_completed_task_update(
333
+ working_stored, "Your task has been completed successfully!"
334
+ )
324
335
 
325
336
  complete_result = await provider.update_task(completed_task)
326
337
  assert complete_result.data is None, "Completion should succeed"
@@ -365,7 +376,7 @@ class TestTaskLifecycleHappyPath(TaskLifecycleTestBase):
365
376
  "25% complete - analyzing request",
366
377
  "50% complete - processing data",
367
378
  "75% complete - generating results",
368
- "90% complete - finalizing output"
379
+ "90% complete - finalizing output",
369
380
  ]
370
381
 
371
382
  for i, message in enumerate(progress_messages):
@@ -374,13 +385,11 @@ class TestTaskLifecycleHappyPath(TaskLifecycleTestBase):
374
385
  parts=[A2ATextPart(kind="text", text=message)],
375
386
  messageId=f"progress_{i}",
376
387
  contextId="multi_ctx_001",
377
- kind="message"
388
+ kind="message",
378
389
  )
379
390
 
380
391
  update_result = await provider.update_task_status(
381
- "multi_001",
382
- TaskState.WORKING,
383
- status_message
392
+ "multi_001", TaskState.WORKING, status_message
384
393
  )
385
394
  assert update_result.data is None, f"Progress update {i} should succeed"
386
395
 
@@ -413,7 +422,9 @@ class TestTaskLifecycleUnhappyPath(TaskLifecycleTestBase):
413
422
  await provider.update_task(working_task)
414
423
 
415
424
  # Transition to failed state
416
- failed_task = self.create_failed_task_update(working_task, "Encountered an unexpected error during processing")
425
+ failed_task = self.create_failed_task_update(
426
+ working_task, "Encountered an unexpected error during processing"
427
+ )
417
428
 
418
429
  fail_result = await provider.update_task(failed_task)
419
430
  assert fail_result.data is None, "Failure update should succeed"
@@ -454,12 +465,14 @@ class TestTaskLifecycleUnhappyPath(TaskLifecycleTestBase):
454
465
  parts=[A2ATextPart(kind="text", text="Working on your request...")],
455
466
  messageId="working_msg",
456
467
  contextId="cancel_ctx_001",
457
- kind="message"
458
- )
468
+ kind="message",
469
+ ),
459
470
  )
460
471
 
461
472
  # Cancel the task
462
- canceled_task = self.create_canceled_task_update(working_task, "Task was canceled at user request")
473
+ canceled_task = self.create_canceled_task_update(
474
+ working_task, "Task was canceled at user request"
475
+ )
463
476
 
464
477
  cancel_result = await provider.update_task(canceled_task)
465
478
  assert cancel_result.data is None, "Cancellation should succeed"
@@ -498,7 +511,13 @@ class TestMultiTaskContextManagement(TaskLifecycleTestBase):
498
511
  assert store_result.data is None, f"Task {task_id} should store successfully"
499
512
 
500
513
  # Progress tasks to different states
501
- states = [TaskState.SUBMITTED, TaskState.WORKING, TaskState.COMPLETED, TaskState.FAILED, TaskState.CANCELED]
514
+ states = [
515
+ TaskState.SUBMITTED,
516
+ TaskState.WORKING,
517
+ TaskState.COMPLETED,
518
+ TaskState.FAILED,
519
+ TaskState.CANCELED,
520
+ ]
502
521
 
503
522
  for i, (task_id, target_state) in enumerate(zip(task_ids, states)):
504
523
  get_result = await provider.get_task(task_id)
@@ -667,11 +686,7 @@ class TestTaskQueryAndPagination(TaskLifecycleTestBase):
667
686
  offset = 0
668
687
 
669
688
  while True:
670
- query = A2ATaskQuery(
671
- context_id=context_id,
672
- limit=page_size,
673
- offset=offset
674
- )
689
+ query = A2ATaskQuery(context_id=context_id, limit=page_size, offset=offset)
675
690
 
676
691
  page_result = await provider.find_tasks(query)
677
692
  assert page_result.data is not None
@@ -688,7 +703,9 @@ class TestTaskQueryAndPagination(TaskLifecycleTestBase):
688
703
  break
689
704
 
690
705
  # Verify we got all tasks
691
- assert len(all_retrieved) == total_tasks, f"Expected {total_tasks} tasks, got {len(all_retrieved)}"
706
+ assert len(all_retrieved) == total_tasks, (
707
+ f"Expected {total_tasks} tasks, got {len(all_retrieved)}"
708
+ )
692
709
 
693
710
  # Verify no duplicates
694
711
  task_ids = [task.id for task in all_retrieved]
@@ -708,30 +725,27 @@ class TestTaskQueryAndPagination(TaskLifecycleTestBase):
708
725
  # Tasks from 1 hour ago
709
726
  old_time = base_time - timedelta(hours=1)
710
727
  old_task = self.create_submission_task("old_task", context_id)
711
- old_task = old_task.model_copy(update={
712
- "status": old_task.status.model_copy(update={
713
- "timestamp": old_time.isoformat()
714
- })
715
- })
728
+ old_task = old_task.model_copy(
729
+ update={
730
+ "status": old_task.status.model_copy(update={"timestamp": old_time.isoformat()})
731
+ }
732
+ )
716
733
  # Store with created_at metadata to control the timestamp used for filtering
717
734
  await provider.store_task(old_task, metadata={"created_at": old_time.isoformat()})
718
735
 
719
736
  # Tasks from now
720
737
  new_task = self.create_submission_task("new_task", context_id)
721
- new_task = new_task.model_copy(update={
722
- "status": new_task.status.model_copy(update={
723
- "timestamp": base_time.isoformat()
724
- })
725
- })
738
+ new_task = new_task.model_copy(
739
+ update={
740
+ "status": new_task.status.model_copy(update={"timestamp": base_time.isoformat()})
741
+ }
742
+ )
726
743
  # Store with created_at metadata to control the timestamp used for filtering
727
744
  await provider.store_task(new_task, metadata={"created_at": base_time.isoformat()})
728
745
 
729
746
  # Query tasks since 30 minutes ago
730
747
  since_time = base_time - timedelta(minutes=30)
731
- time_query = A2ATaskQuery(
732
- context_id=context_id,
733
- since=since_time
734
- )
748
+ time_query = A2ATaskQuery(context_id=context_id, since=since_time)
735
749
 
736
750
  recent_result = await provider.find_tasks(time_query)
737
751
  assert recent_result.data is not None
@@ -743,7 +757,7 @@ class TestTaskQueryAndPagination(TaskLifecycleTestBase):
743
757
  # All tasks should be newer than since_time
744
758
  for task in recent_tasks:
745
759
  if task.status.timestamp:
746
- task_time = datetime.fromisoformat(task.status.timestamp.replace('Z', '+00:00'))
760
+ task_time = datetime.fromisoformat(task.status.timestamp.replace("Z", "+00:00"))
747
761
  assert task_time >= since_time, f"Task {task.id} is older than since_time"
748
762
 
749
763
 
@@ -768,9 +782,9 @@ class TestTaskErrorHandling(TaskLifecycleTestBase):
768
782
  result = await provider.update_task(nonexistent_task)
769
783
 
770
784
  # Should fail with appropriate error
771
- if hasattr(result, 'data'):
785
+ if hasattr(result, "data"):
772
786
  assert result.data is None
773
- elif hasattr(result, 'error'):
787
+ elif hasattr(result, "error"):
774
788
  assert result.error is not None
775
789
  else:
776
790
  assert False, "Result should have either data or error"
@@ -794,7 +808,7 @@ class TestTaskErrorHandling(TaskLifecycleTestBase):
794
808
  id="", # Invalid empty ID
795
809
  contextId="invalid_ctx",
796
810
  kind="task",
797
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
811
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
798
812
  )
799
813
 
800
814
  result = await provider.store_task(invalid_task)
@@ -845,11 +859,15 @@ class TestProviderHealthAndCleanup(TaskLifecycleTestBase):
845
859
 
846
860
  completed_old = self.create_completed_task_update(working_old)
847
861
  # Set old timestamp
848
- completed_old = completed_old.model_copy(update={
849
- "status": completed_old.status.model_copy(update={
850
- "timestamp": (datetime.now(timezone.utc) - timedelta(days=8)).isoformat()
851
- })
852
- })
862
+ completed_old = completed_old.model_copy(
863
+ update={
864
+ "status": completed_old.status.model_copy(
865
+ update={
866
+ "timestamp": (datetime.now(timezone.utc) - timedelta(days=8)).isoformat()
867
+ }
868
+ )
869
+ }
870
+ )
853
871
  await provider.update_task(completed_old)
854
872
 
855
873
  # Run cleanup