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
@@ -18,6 +18,7 @@ from .types import A2AResult, create_a2a_failure, create_a2a_success, create_a2a
18
18
  @dataclass(frozen=True)
19
19
  class A2ATaskSerialized:
20
20
  """Serialized representation of an A2A task for storage"""
21
+
21
22
  task_id: str
22
23
  context_id: str
23
24
  state: str
@@ -27,17 +28,17 @@ class A2ATaskSerialized:
27
28
  status_message: Optional[str] = None # Serialized status message for quick access
28
29
  metadata: Optional[str] = None # JSON string of metadata
29
30
 
31
+
30
32
  def serialize_a2a_task(
31
- task: A2ATask,
32
- metadata: Optional[Dict[str, Any]] = None
33
+ task: A2ATask, metadata: Optional[Dict[str, Any]] = None
33
34
  ) -> A2AResult[A2ATaskSerialized]:
34
35
  """
35
36
  Pure function to serialize an A2A task for storage
36
-
37
+
37
38
  Args:
38
39
  task: The A2A task to serialize
39
40
  metadata: Optional metadata to store with the task
40
-
41
+
41
42
  Returns:
42
43
  A2AResult containing the serialized task or an error
43
44
  """
@@ -67,7 +68,7 @@ def serialize_a2a_task(
67
68
  for key, value in task.metadata.items():
68
69
  if isinstance(value, A2ATask):
69
70
  raise TypeError(f"Object of type A2ATask is not JSON serializable")
70
-
71
+
71
72
  task_data = task.model_dump_json(by_alias=True)
72
73
  except TypeError as e:
73
74
  # Re-raise TypeError (including circular reference errors) directly
@@ -83,9 +84,10 @@ def serialize_a2a_task(
83
84
  if isinstance(obj, datetime):
84
85
  return obj.isoformat()
85
86
  raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
87
+
86
88
  metadata_str = None
87
89
  if metadata:
88
- metadata_str = json.dumps(metadata, separators=(',', ':'), default=datetime_converter)
90
+ metadata_str = json.dumps(metadata, separators=(",", ":"), default=datetime_converter)
89
91
 
90
92
  serialized = A2ATaskSerialized(
91
93
  task_id=task.id,
@@ -102,16 +104,17 @@ def serialize_a2a_task(
102
104
 
103
105
  except Exception as error:
104
106
  return create_a2a_failure(
105
- create_a2a_task_storage_error('serialize', 'memory', task.id, error)
107
+ create_a2a_task_storage_error("serialize", "memory", task.id, error)
106
108
  )
107
109
 
110
+
108
111
  def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
109
112
  """
110
113
  Pure function to deserialize an A2A task from storage
111
-
114
+
112
115
  Args:
113
116
  stored: The serialized task data
114
-
117
+
115
118
  Returns:
116
119
  A2AResult containing the deserialized task or an error
117
120
  """
@@ -126,10 +129,7 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
126
129
  # Convert Pydantic validation errors to our format
127
130
  return create_a2a_failure(
128
131
  create_a2a_task_storage_error(
129
- 'deserialize',
130
- 'memory',
131
- stored.task_id,
132
- Exception('Invalid task structure')
132
+ "deserialize", "memory", stored.task_id, Exception("Invalid task structure")
133
133
  )
134
134
  )
135
135
 
@@ -137,10 +137,7 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
137
137
  if not task.id or not task.context_id or not task.status or task.kind != "task":
138
138
  return create_a2a_failure(
139
139
  create_a2a_task_storage_error(
140
- 'deserialize',
141
- 'memory',
142
- stored.task_id,
143
- Exception('Invalid task structure')
140
+ "deserialize", "memory", stored.task_id, Exception("Invalid task structure")
144
141
  )
145
142
  )
146
143
 
@@ -148,43 +145,43 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
148
145
 
149
146
  except Exception as error:
150
147
  return create_a2a_failure(
151
- create_a2a_task_storage_error('deserialize', 'memory', stored.task_id, error)
148
+ create_a2a_task_storage_error("deserialize", "memory", stored.task_id, error)
152
149
  )
153
150
 
151
+
154
152
  def create_task_index(task: A2ATask) -> A2AResult[Dict[str, Any]]:
155
153
  """
156
154
  Pure function to create a minimal task representation for indexing
157
-
155
+
158
156
  Args:
159
157
  task: The A2A task to index
160
-
158
+
161
159
  Returns:
162
160
  A2AResult containing the task index data or an error
163
161
  """
164
162
  try:
165
163
  index_data = {
166
- 'task_id': task.id,
167
- 'context_id': task.context_id,
168
- 'state': task.status.state.value,
169
- 'timestamp': task.status.timestamp or datetime.now().isoformat(),
170
- 'has_history': bool(task.history and len(task.history) > 0),
171
- 'has_artifacts': bool(task.artifacts and len(task.artifacts) > 0)
164
+ "task_id": task.id,
165
+ "context_id": task.context_id,
166
+ "state": task.status.state.value,
167
+ "timestamp": task.status.timestamp or datetime.now().isoformat(),
168
+ "has_history": bool(task.history and len(task.history) > 0),
169
+ "has_artifacts": bool(task.artifacts and len(task.artifacts) > 0),
172
170
  }
173
171
 
174
172
  return create_a2a_success(index_data)
175
173
 
176
174
  except Exception as error:
177
- return create_a2a_failure(
178
- create_a2a_task_storage_error('index', 'memory', task.id, error)
179
- )
175
+ return create_a2a_failure(create_a2a_task_storage_error("index", "memory", task.id, error))
176
+
180
177
 
181
178
  def extract_task_search_text(task: A2ATask) -> A2AResult[str]:
182
179
  """
183
180
  Pure function to extract searchable text from a task for full-text search
184
-
181
+
185
182
  Args:
186
183
  task: The A2A task to extract text from
187
-
184
+
188
185
  Returns:
189
186
  A2AResult containing the extracted text or an error
190
187
  """
@@ -212,13 +209,14 @@ def extract_task_search_text(task: A2ATask) -> A2AResult[str]:
212
209
  if part.kind == "text":
213
210
  text_parts.append(part.text)
214
211
 
215
- return create_a2a_success(' '.join(text_parts).strip())
212
+ return create_a2a_success(" ".join(text_parts).strip())
216
213
 
217
214
  except Exception as error:
218
215
  return create_a2a_failure(
219
- create_a2a_task_storage_error('extract-text', 'memory', task.id, error)
216
+ create_a2a_task_storage_error("extract-text", "memory", task.id, error)
220
217
  )
221
218
 
219
+
222
220
  def _extract_text_from_message(message: A2AMessage, text_parts: List[str]) -> None:
223
221
  """Helper function to extract text from A2A message parts"""
224
222
  for part in message.parts:
@@ -232,13 +230,14 @@ def _extract_text_from_message(message: A2AMessage, text_parts: List[str]) -> No
232
230
  elif part.kind == "file" and part.file.name:
233
231
  text_parts.append(part.file.name)
234
232
 
233
+
235
234
  def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
236
235
  """
237
236
  Pure function to validate task data integrity
238
-
237
+
239
238
  Args:
240
239
  task: The A2A task to validate
241
-
240
+
242
241
  Returns:
243
242
  A2AResult containing True if valid or an error
244
243
  """
@@ -247,40 +246,31 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
247
246
  if not task.id or not isinstance(task.id, str):
248
247
  return create_a2a_failure(
249
248
  create_a2a_task_storage_error(
250
- 'validate',
251
- 'memory',
252
- getattr(task, 'id', None),
253
- Exception('Task ID is required and must be a string')
249
+ "validate",
250
+ "memory",
251
+ getattr(task, "id", None),
252
+ Exception("Task ID is required and must be a string"),
254
253
  )
255
254
  )
256
255
 
257
256
  if not task.context_id or not isinstance(task.context_id, str):
258
257
  return create_a2a_failure(
259
258
  create_a2a_task_storage_error(
260
- 'validate',
261
- 'memory',
262
- task.id,
263
- Exception('Context ID is required')
259
+ "validate", "memory", task.id, Exception("Context ID is required")
264
260
  )
265
261
  )
266
262
 
267
263
  if not task.status or not task.status.state:
268
264
  return create_a2a_failure(
269
265
  create_a2a_task_storage_error(
270
- 'validate',
271
- 'memory',
272
- task.id,
273
- Exception('Task status and state are required')
266
+ "validate", "memory", task.id, Exception("Task status and state are required")
274
267
  )
275
268
  )
276
269
 
277
270
  if task.kind != "task":
278
271
  return create_a2a_failure(
279
272
  create_a2a_task_storage_error(
280
- 'validate',
281
- 'memory',
282
- task.id,
283
- Exception('Task kind must be "task"')
273
+ "validate", "memory", task.id, Exception('Task kind must be "task"')
284
274
  )
285
275
  )
286
276
 
@@ -289,21 +279,22 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
289
279
  if not isinstance(task.history, list):
290
280
  return create_a2a_failure(
291
281
  create_a2a_task_storage_error(
292
- 'validate',
293
- 'memory',
294
- task.id,
295
- Exception('Task history must be a list')
282
+ "validate", "memory", task.id, Exception("Task history must be a list")
296
283
  )
297
284
  )
298
285
 
299
286
  for i, message in enumerate(task.history):
300
- if not message.message_id or not message.parts or not isinstance(message.parts, list):
287
+ if (
288
+ not message.message_id
289
+ or not message.parts
290
+ or not isinstance(message.parts, list)
291
+ ):
301
292
  return create_a2a_failure(
302
293
  create_a2a_task_storage_error(
303
- 'validate',
304
- 'memory',
294
+ "validate",
295
+ "memory",
305
296
  task.id,
306
- Exception(f'Invalid message at index {i} in task history')
297
+ Exception(f"Invalid message at index {i} in task history"),
307
298
  )
308
299
  )
309
300
 
@@ -312,21 +303,22 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
312
303
  if not isinstance(task.artifacts, list):
313
304
  return create_a2a_failure(
314
305
  create_a2a_task_storage_error(
315
- 'validate',
316
- 'memory',
317
- task.id,
318
- Exception('Task artifacts must be a list')
306
+ "validate", "memory", task.id, Exception("Task artifacts must be a list")
319
307
  )
320
308
  )
321
309
 
322
310
  for i, artifact in enumerate(task.artifacts):
323
- if not artifact.artifact_id or not artifact.parts or not isinstance(artifact.parts, list):
311
+ if (
312
+ not artifact.artifact_id
313
+ or not artifact.parts
314
+ or not isinstance(artifact.parts, list)
315
+ ):
324
316
  return create_a2a_failure(
325
317
  create_a2a_task_storage_error(
326
- 'validate',
327
- 'memory',
318
+ "validate",
319
+ "memory",
328
320
  task.id,
329
- Exception(f'Invalid artifact at index {i} in task')
321
+ Exception(f"Invalid artifact at index {i} in task"),
330
322
  )
331
323
  )
332
324
 
@@ -334,16 +326,17 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
334
326
 
335
327
  except Exception as error:
336
328
  return create_a2a_failure(
337
- create_a2a_task_storage_error('validate', 'memory', getattr(task, 'id', None), error)
329
+ create_a2a_task_storage_error("validate", "memory", getattr(task, "id", None), error)
338
330
  )
339
331
 
332
+
340
333
  def clone_task(task: A2ATask) -> A2AResult[A2ATask]:
341
334
  """
342
335
  Pure function to create a deep copy of a task (for immutability)
343
-
336
+
344
337
  Args:
345
338
  task: The A2A task to clone
346
-
339
+
347
340
  Returns:
348
341
  A2AResult containing the cloned task or an error
349
342
  """
@@ -353,18 +346,17 @@ def clone_task(task: A2ATask) -> A2AResult[A2ATask]:
353
346
  return create_a2a_success(cloned)
354
347
 
355
348
  except Exception as error:
356
- return create_a2a_failure(
357
- create_a2a_task_storage_error('clone', 'memory', task.id, error)
358
- )
349
+ return create_a2a_failure(create_a2a_task_storage_error("clone", "memory", task.id, error))
350
+
359
351
 
360
352
  def sanitize_task(task: A2ATask) -> A2AResult[A2ATask]:
361
353
  """
362
354
  Pure function to sanitize task data for storage
363
355
  Removes any potentially dangerous or invalid data
364
-
356
+
365
357
  Args:
366
358
  task: The A2A task to sanitize
367
-
359
+
368
360
  Returns:
369
361
  A2AResult containing the sanitized task or an error
370
362
  """
@@ -385,24 +377,22 @@ def sanitize_task(task: A2ATask) -> A2AResult[A2ATask]:
385
377
  if sanitized.status.timestamp:
386
378
  try:
387
379
  # Parse and re-format timestamp to ensure it's valid
388
- dt = datetime.fromisoformat(sanitized.status.timestamp.replace('Z', '+00:00'))
380
+ dt = datetime.fromisoformat(sanitized.status.timestamp.replace("Z", "+00:00"))
389
381
  # Create a new task with the corrected timestamp
390
- sanitized = sanitized.model_copy(update={
391
- 'status': sanitized.status.model_copy(update={
392
- 'timestamp': dt.isoformat()
393
- })
394
- })
382
+ sanitized = sanitized.model_copy(
383
+ update={
384
+ "status": sanitized.status.model_copy(update={"timestamp": dt.isoformat()})
385
+ }
386
+ )
395
387
  except Exception:
396
388
  # Remove invalid timestamp
397
- sanitized = sanitized.model_copy(update={
398
- 'status': sanitized.status.model_copy(update={
399
- 'timestamp': None
400
- })
401
- })
389
+ sanitized = sanitized.model_copy(
390
+ update={"status": sanitized.status.model_copy(update={"timestamp": None})}
391
+ )
402
392
 
403
393
  return create_a2a_success(sanitized)
404
394
 
405
395
  except Exception as error:
406
396
  return create_a2a_failure(
407
- create_a2a_task_storage_error('sanitize', 'memory', getattr(task, 'id', None), error)
397
+ create_a2a_task_storage_error("sanitize", "memory", getattr(task, "id", None), error)
408
398
  )