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
@@ -40,7 +40,7 @@ class TestA2ATaskSerialization:
40
40
  self,
41
41
  task_id: str = "task_123",
42
42
  context_id: str = "ctx_456",
43
- state: TaskState = TaskState.WORKING
43
+ state: TaskState = TaskState.WORKING,
44
44
  ) -> A2ATask:
45
45
  """Helper to create a comprehensive test task"""
46
46
  return A2ATask(
@@ -54,9 +54,9 @@ class TestA2ATaskSerialization:
54
54
  parts=[A2ATextPart(kind="text", text=f"Processing task {task_id}...")],
55
55
  messageId=f"msg_{task_id}",
56
56
  contextId=context_id,
57
- kind="message"
57
+ kind="message",
58
58
  ),
59
- timestamp=datetime.now(timezone.utc).isoformat()
59
+ timestamp=datetime.now(timezone.utc).isoformat(),
60
60
  ),
61
61
  history=[
62
62
  A2AMessage(
@@ -64,15 +64,15 @@ class TestA2ATaskSerialization:
64
64
  parts=[A2ATextPart(kind="text", text="Hello, please help me")],
65
65
  messageId="msg_001",
66
66
  contextId=context_id,
67
- kind="message"
67
+ kind="message",
68
68
  ),
69
69
  A2AMessage(
70
70
  role="agent",
71
71
  parts=[A2ADataPart(kind="data", data={"progress": 50, "status": "processing"})],
72
72
  messageId="msg_002",
73
73
  contextId=context_id,
74
- kind="message"
75
- )
74
+ kind="message",
75
+ ),
76
76
  ],
77
77
  artifacts=[
78
78
  A2AArtifact(
@@ -81,15 +81,17 @@ class TestA2ATaskSerialization:
81
81
  description="A comprehensive test artifact",
82
82
  parts=[
83
83
  A2ATextPart(kind="text", text="Artifact content"),
84
- A2AFilePart(kind="file", file=A2AFile(name="test.txt", mimeType="text/plain"))
85
- ]
84
+ A2AFilePart(
85
+ kind="file", file=A2AFile(name="test.txt", mimeType="text/plain")
86
+ ),
87
+ ],
86
88
  )
87
89
  ],
88
90
  metadata={
89
91
  "createdAt": datetime.now(timezone.utc).isoformat(),
90
92
  "priority": "normal",
91
- "source": "test"
92
- }
93
+ "source": "test",
94
+ },
93
95
  )
94
96
 
95
97
 
@@ -137,7 +139,7 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
137
139
  id="task_minimal",
138
140
  contextId="ctx_minimal",
139
141
  kind="task",
140
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
142
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
141
143
  )
142
144
 
143
145
  result = serialize_a2a_task(minimal_task)
@@ -151,12 +153,14 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
151
153
  """Should gracefully handle circular references"""
152
154
  task = self.create_test_task()
153
155
  # Create circular reference by adding task to its own metadata
154
- task.metadata['circular'] = task
156
+ task.metadata["circular"] = task
155
157
 
156
158
  result = serialize_a2a_task(task)
157
-
159
+
158
160
  assert result.error is not None, "Should fail with circular reference"
159
- assert "JSON serializable" in str(result.error.cause), "Should indicate JSON serialization error"
161
+ assert "JSON serializable" in str(result.error.cause), (
162
+ "Should indicate JSON serialization error"
163
+ )
160
164
 
161
165
  def test_serialize_datetime_objects(self):
162
166
  """Should properly convert datetime objects to ISO strings"""
@@ -169,7 +173,7 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
169
173
  stored_metadata = json.loads(result.data.metadata)
170
174
  assert isinstance(stored_metadata["timestamp"], str)
171
175
  # Should be parseable as ISO datetime
172
- datetime.fromisoformat(stored_metadata["timestamp"].replace('Z', '+00:00'))
176
+ datetime.fromisoformat(stored_metadata["timestamp"].replace("Z", "+00:00"))
173
177
 
174
178
 
175
179
  class TestDeserializeA2ATask(TestA2ATaskSerialization):
@@ -200,7 +204,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
200
204
  state="failed",
201
205
  task_data="invalid json {{{", # Malformed JSON
202
206
  created_at=datetime.now(timezone.utc).isoformat(),
203
- updated_at=datetime.now(timezone.utc).isoformat()
207
+ updated_at=datetime.now(timezone.utc).isoformat(),
204
208
  )
205
209
 
206
210
  result = deserialize_a2a_task(invalid_serialized)
@@ -221,7 +225,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
221
225
  state="failed",
222
226
  task_data=json.dumps(incomplete_task_data),
223
227
  created_at=datetime.now(timezone.utc).isoformat(),
224
- updated_at=datetime.now(timezone.utc).isoformat()
228
+ updated_at=datetime.now(timezone.utc).isoformat(),
225
229
  )
226
230
 
227
231
  result = deserialize_a2a_task(incomplete_serialized)
@@ -241,9 +245,9 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
241
245
  "role": "agent",
242
246
  "parts": "not_a_list", # Should be a list
243
247
  "messageId": "msg_corrupt",
244
- "kind": "message"
245
- }
246
- }
248
+ "kind": "message",
249
+ },
250
+ },
247
251
  }
248
252
 
249
253
  corrupt_serialized = A2ATaskSerialized(
@@ -252,7 +256,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
252
256
  state="working",
253
257
  task_data=json.dumps(task_data),
254
258
  created_at=datetime.now(timezone.utc).isoformat(),
255
- updated_at=datetime.now(timezone.utc).isoformat()
259
+ updated_at=datetime.now(timezone.utc).isoformat(),
256
260
  )
257
261
 
258
262
  result = deserialize_a2a_task(corrupt_serialized)
@@ -313,17 +317,20 @@ class TestRoundTripSerialization(TestA2ATaskSerialization):
313
317
  message=A2AMessage(
314
318
  role="agent",
315
319
  parts=[
316
- A2ADataPart(kind="data", data={
317
- "nested": {"level": 2, "items": [1, 2, 3]},
318
- "arrays": [{"id": 1}, {"id": 2}],
319
- "unicode": "🔥 Special chars & symbols"
320
- })
320
+ A2ADataPart(
321
+ kind="data",
322
+ data={
323
+ "nested": {"level": 2, "items": [1, 2, 3]},
324
+ "arrays": [{"id": 1}, {"id": 2}],
325
+ "unicode": "🔥 Special chars & symbols",
326
+ },
327
+ )
321
328
  ],
322
329
  messageId="complex_msg",
323
330
  contextId="complex_ctx",
324
- kind="message"
325
- )
326
- )
331
+ kind="message",
332
+ ),
333
+ ),
327
334
  )
328
335
 
329
336
  # Round trip
@@ -338,7 +345,7 @@ class TestRoundTripSerialization(TestA2ATaskSerialization):
338
345
  # Verify complex data structure preservation
339
346
  if round_trip_task.status.message and round_trip_task.status.message.parts:
340
347
  data_part = round_trip_task.status.message.parts[0]
341
- if hasattr(data_part, 'data'):
348
+ if hasattr(data_part, "data"):
342
349
  assert data_part.data["nested"]["level"] == 2
343
350
  assert data_part.data["arrays"][0]["id"] == 1
344
351
  assert data_part.data["unicode"] == "🔥 Special chars & symbols"
@@ -368,7 +375,7 @@ class TestCreateTaskIndex(TestA2ATaskSerialization):
368
375
  id="minimal",
369
376
  contextId="ctx_minimal",
370
377
  kind="task",
371
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
378
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
372
379
  )
373
380
 
374
381
  result = create_task_index(minimal_task)
@@ -412,19 +419,21 @@ class TestExtractTaskSearchText(TestA2ATaskSerialization):
412
419
  state=TaskState.WORKING,
413
420
  message=A2AMessage(
414
421
  role="agent",
415
- parts=[A2ADataPart(
416
- kind="data",
417
- data={
418
- "title": "Important Document",
419
- "summary": "This is a critical summary",
420
- "count": 42 # Non-string data should be ignored
421
- }
422
- )],
422
+ parts=[
423
+ A2ADataPart(
424
+ kind="data",
425
+ data={
426
+ "title": "Important Document",
427
+ "summary": "This is a critical summary",
428
+ "count": 42, # Non-string data should be ignored
429
+ },
430
+ )
431
+ ],
423
432
  messageId="data_msg",
424
433
  contextId="ctx_data",
425
- kind="message"
426
- )
427
- )
434
+ kind="message",
435
+ ),
436
+ ),
428
437
  )
429
438
 
430
439
  result = extract_task_search_text(task_with_data)
@@ -440,7 +449,7 @@ class TestExtractTaskSearchText(TestA2ATaskSerialization):
440
449
  id="empty",
441
450
  contextId="ctx_empty",
442
451
  kind="task",
443
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
452
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
444
453
  )
445
454
 
446
455
  result = extract_task_search_text(empty_task)
@@ -468,7 +477,9 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
468
477
  result = validate_task_integrity(None) # Simulate missing ID scenario
469
478
 
470
479
  assert result.error is not None
471
- assert "Task ID is required" in str(result.error.message) or "validate" in str(result.error.message)
480
+ assert "Task ID is required" in str(result.error.message) or "validate" in str(
481
+ result.error.message
482
+ )
472
483
 
473
484
  def test_reject_task_without_context_id(self):
474
485
  """Should reject task missing contextId"""
@@ -478,7 +489,7 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
478
489
  id="test",
479
490
  contextId="", # Invalid empty context ID
480
491
  kind="task",
481
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
492
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
482
493
  )
483
494
 
484
495
  result = validate_task_integrity(invalid_task)
@@ -515,7 +526,7 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
515
526
  id="test",
516
527
  contextId="ctx",
517
528
  kind="task", # This is actually correct, but we'll test the validation logic
518
- status=A2ATaskStatus(state=TaskState.SUBMITTED)
529
+ status=A2ATaskStatus(state=TaskState.SUBMITTED),
519
530
  )
520
531
 
521
532
  # Manually set wrong kind for test
@@ -581,10 +592,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
581
592
  id="timestamp_test",
582
593
  contextId="ctx_test",
583
594
  kind="task",
584
- status=A2ATaskStatus(
585
- state=TaskState.WORKING,
586
- timestamp="invalid-date-string"
587
- )
595
+ status=A2ATaskStatus(state=TaskState.WORKING, timestamp="invalid-date-string"),
588
596
  )
589
597
 
590
598
  result = sanitize_task(invalid_timestamp_task)
@@ -595,7 +603,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
595
603
  # The sanitizer should either fix or remove the invalid timestamp
596
604
  if sanitized.status.timestamp:
597
605
  # If timestamp exists, it should be valid
598
- datetime.fromisoformat(sanitized.status.timestamp.replace('Z', '+00:00'))
606
+ datetime.fromisoformat(sanitized.status.timestamp.replace("Z", "+00:00"))
599
607
 
600
608
  def test_convert_valid_timestamp_strings(self):
601
609
  """Should convert valid timestamp strings to ISO format"""
@@ -603,10 +611,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
603
611
  id="timestamp_convert",
604
612
  contextId="ctx_convert",
605
613
  kind="task",
606
- status=A2ATaskStatus(
607
- state=TaskState.WORKING,
608
- timestamp="2024-01-01T12:00:00.000Z"
609
- )
614
+ status=A2ATaskStatus(state=TaskState.WORKING, timestamp="2024-01-01T12:00:00.000Z"),
610
615
  )
611
616
 
612
617
  result = sanitize_task(task)
@@ -615,7 +620,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
615
620
  # Should have valid ISO timestamp
616
621
  if result.data.status.timestamp:
617
622
  # Should be parseable as ISO datetime
618
- datetime.fromisoformat(result.data.status.timestamp.replace('Z', '+00:00'))
623
+ datetime.fromisoformat(result.data.status.timestamp.replace("Z", "+00:00"))
619
624
 
620
625
  def test_reject_fundamentally_invalid_tasks(self):
621
626
  """Should reject tasks that cannot be sanitized"""
@@ -644,9 +649,9 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
644
649
  parts=[A2ATextPart(kind="text", text=large_text)],
645
650
  messageId="large_msg",
646
651
  contextId="large_ctx",
647
- kind="message"
648
- )
649
- )
652
+ kind="message",
653
+ ),
654
+ ),
650
655
  )
651
656
 
652
657
  # Should handle serialization
@@ -677,9 +682,9 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
677
682
  parts=[A2ADataPart(kind="data", data=nested_data)],
678
683
  messageId="nested_msg",
679
684
  contextId="nested_ctx",
680
- kind="message"
681
- )
682
- )
685
+ kind="message",
686
+ ),
687
+ ),
683
688
  )
684
689
 
685
690
  # Should handle without errors
@@ -701,13 +706,13 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
701
706
  role="agent",
702
707
  parts=[
703
708
  A2ATextPart(kind="text", text=special_chars),
704
- A2ATextPart(kind="text", text=unicode_text)
709
+ A2ATextPart(kind="text", text=unicode_text),
705
710
  ],
706
711
  messageId="unicode_msg",
707
712
  contextId="unicode_ctx",
708
- kind="message"
709
- )
710
- )
713
+ kind="message",
714
+ ),
715
+ ),
711
716
  )
712
717
 
713
718
  # Round trip should preserve special characters
@@ -734,11 +739,11 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
734
739
  status=A2ATaskStatus(
735
740
  state=TaskState.WORKING,
736
741
  message=None, # None message
737
- timestamp=None # None timestamp
742
+ timestamp=None, # None timestamp
738
743
  ),
739
744
  history=None, # None history
740
745
  artifacts=None, # None artifacts
741
- metadata=None # None metadata
746
+ metadata=None, # None metadata
742
747
  )
743
748
 
744
749
  # Should handle gracefully