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
jaf/core/tool_results.py CHANGED
@@ -12,32 +12,38 @@ from collections.abc import Awaitable
12
12
  from dataclasses import dataclass
13
13
  from typing import Any, Callable, Dict, Generic, List, Literal, Optional, TypeVar, Union
14
14
 
15
- T = TypeVar('T')
16
- TArgs = TypeVar('TArgs')
17
- TResult = TypeVar('TResult')
18
- TContext = TypeVar('TContext')
15
+ T = TypeVar("T")
16
+ TArgs = TypeVar("TArgs")
17
+ TResult = TypeVar("TResult")
18
+ TContext = TypeVar("TContext")
19
19
 
20
20
  # Type aliases matching TypeScript
21
- ToolResultStatus = Literal['success', 'error', 'validation_error', 'permission_denied', 'not_found']
21
+ ToolResultStatus = Literal["success", "error", "validation_error", "permission_denied", "not_found"]
22
+
22
23
 
23
24
  class ToolResultStatus:
24
25
  """Tool result status constants."""
25
- SUCCESS = 'success'
26
- ERROR = 'error'
27
- VALIDATION_ERROR = 'validation_error'
28
- PERMISSION_DENIED = 'permission_denied'
29
- NOT_FOUND = 'not_found'
26
+
27
+ SUCCESS = "success"
28
+ ERROR = "error"
29
+ VALIDATION_ERROR = "validation_error"
30
+ PERMISSION_DENIED = "permission_denied"
31
+ NOT_FOUND = "not_found"
32
+
30
33
 
31
34
  @dataclass(frozen=True)
32
35
  class ToolErrorInfo:
33
36
  """Error information for tool results."""
37
+
34
38
  code: str
35
39
  message: str
36
40
  details: Optional[Any] = None
37
41
 
42
+
38
43
  @dataclass(frozen=True)
39
44
  class ToolMetadata:
40
45
  """Metadata for tool execution."""
46
+
41
47
  execution_time_ms: Optional[int] = None
42
48
  tool_name: Optional[str] = None
43
49
  # Allow additional fields
@@ -47,44 +53,49 @@ class ToolMetadata:
47
53
  """Convert to dictionary for JSON serialization."""
48
54
  result = {}
49
55
  if self.execution_time_ms is not None:
50
- result['executionTimeMs'] = self.execution_time_ms
56
+ result["executionTimeMs"] = self.execution_time_ms
51
57
  if self.tool_name is not None:
52
- result['toolName'] = self.tool_name
58
+ result["toolName"] = self.tool_name
53
59
  if self.extra:
54
60
  result.update(self.extra)
55
61
  return result
56
62
 
63
+
57
64
  @dataclass(frozen=True)
58
65
  class ToolResult(Generic[T]):
59
66
  """Standardized tool result with status, data, and metadata."""
67
+
60
68
  status: ToolResultStatus
61
69
  data: Optional[T] = None
62
70
  error: Optional[ToolErrorInfo] = None
63
71
  metadata: Optional[ToolMetadata] = None
64
72
 
73
+
65
74
  # Common error codes - matching TypeScript constants
66
75
  class ToolErrorCodes:
67
76
  """Common error codes for tool execution."""
77
+
68
78
  # Validation errors
69
- INVALID_INPUT = 'INVALID_INPUT'
70
- MISSING_REQUIRED_FIELD = 'MISSING_REQUIRED_FIELD'
71
- INVALID_FORMAT = 'INVALID_FORMAT'
79
+ INVALID_INPUT = "INVALID_INPUT"
80
+ MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD"
81
+ INVALID_FORMAT = "INVALID_FORMAT"
72
82
 
73
83
  # Permission errors
74
- PERMISSION_DENIED = 'PERMISSION_DENIED'
75
- INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS'
84
+ PERMISSION_DENIED = "PERMISSION_DENIED"
85
+ INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
76
86
 
77
87
  # Resource errors
78
- NOT_FOUND = 'NOT_FOUND'
79
- RESOURCE_UNAVAILABLE = 'RESOURCE_UNAVAILABLE'
88
+ NOT_FOUND = "NOT_FOUND"
89
+ RESOURCE_UNAVAILABLE = "RESOURCE_UNAVAILABLE"
80
90
 
81
91
  # Execution errors
82
- EXECUTION_FAILED = 'EXECUTION_FAILED'
83
- TIMEOUT = 'TIMEOUT'
84
- EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR'
92
+ EXECUTION_FAILED = "EXECUTION_FAILED"
93
+ TIMEOUT = "TIMEOUT"
94
+ EXTERNAL_SERVICE_ERROR = "EXTERNAL_SERVICE_ERROR"
85
95
 
86
96
  # Generic
87
- UNKNOWN_ERROR = 'UNKNOWN_ERROR'
97
+ UNKNOWN_ERROR = "UNKNOWN_ERROR"
98
+
88
99
 
89
100
  class ToolResponse:
90
101
  """Helper functions for creating standardized tool results."""
@@ -95,98 +106,94 @@ class ToolResponse:
95
106
  tool_metadata = None
96
107
  if metadata:
97
108
  tool_metadata = ToolMetadata(
98
- execution_time_ms=metadata.get('executionTimeMs'),
99
- tool_name=metadata.get('toolName'),
100
- extra={k: v for k, v in metadata.items()
101
- if k not in ['executionTimeMs', 'toolName']}
109
+ execution_time_ms=metadata.get("executionTimeMs"),
110
+ tool_name=metadata.get("toolName"),
111
+ extra={
112
+ k: v for k, v in metadata.items() if k not in ["executionTimeMs", "toolName"]
113
+ },
102
114
  )
103
115
 
104
- return ToolResult(
105
- status='success',
106
- data=data,
107
- metadata=tool_metadata
108
- )
116
+ return ToolResult(status="success", data=data, metadata=tool_metadata)
109
117
 
110
118
  @staticmethod
111
119
  def error(
112
120
  code: str,
113
121
  message: str,
114
122
  details: Optional[Any] = None,
115
- metadata: Optional[Dict[str, Any]] = None
123
+ metadata: Optional[Dict[str, Any]] = None,
116
124
  ) -> ToolResult[None]:
117
125
  """Create an error tool result."""
118
126
  tool_metadata = None
119
127
  if metadata:
120
128
  tool_metadata = ToolMetadata(
121
- execution_time_ms=metadata.get('executionTimeMs'),
122
- tool_name=metadata.get('toolName'),
123
- extra={k: v for k, v in metadata.items()
124
- if k not in ['executionTimeMs', 'toolName']}
129
+ execution_time_ms=metadata.get("executionTimeMs"),
130
+ tool_name=metadata.get("toolName"),
131
+ extra={
132
+ k: v for k, v in metadata.items() if k not in ["executionTimeMs", "toolName"]
133
+ },
125
134
  )
126
135
 
127
136
  return ToolResult(
128
- status='error',
137
+ status="error",
129
138
  error=ToolErrorInfo(code=code, message=message, details=details),
130
- metadata=tool_metadata
139
+ metadata=tool_metadata,
131
140
  )
132
141
 
133
142
  @staticmethod
134
143
  def validation_error(
135
- message: str,
136
- details: Optional[Any] = None,
137
- metadata: Optional[Dict[str, Any]] = None
144
+ message: str, details: Optional[Any] = None, metadata: Optional[Dict[str, Any]] = None
138
145
  ) -> ToolResult[None]:
139
146
  """Create a validation error tool result."""
140
147
  tool_metadata = None
141
148
  if metadata:
142
149
  tool_metadata = ToolMetadata(
143
- execution_time_ms=metadata.get('executionTimeMs'),
144
- tool_name=metadata.get('toolName'),
145
- extra={k: v for k, v in metadata.items()
146
- if k not in ['executionTimeMs', 'toolName']}
150
+ execution_time_ms=metadata.get("executionTimeMs"),
151
+ tool_name=metadata.get("toolName"),
152
+ extra={
153
+ k: v for k, v in metadata.items() if k not in ["executionTimeMs", "toolName"]
154
+ },
147
155
  )
148
156
 
149
157
  return ToolResult(
150
- status='validation_error',
158
+ status="validation_error",
151
159
  error=ToolErrorInfo(
152
- code=ToolErrorCodes.INVALID_INPUT,
153
- message=message,
154
- details=details
160
+ code=ToolErrorCodes.INVALID_INPUT, message=message, details=details
155
161
  ),
156
- metadata=tool_metadata
162
+ metadata=tool_metadata,
157
163
  )
158
164
 
159
165
  @staticmethod
160
166
  def permission_denied(
161
167
  message: str,
162
168
  required_permissions: Optional[List[str]] = None,
163
- metadata: Optional[Dict[str, Any]] = None
169
+ metadata: Optional[Dict[str, Any]] = None,
164
170
  ) -> ToolResult[None]:
165
171
  """Create a permission denied tool result."""
166
172
  tool_metadata = None
167
173
  if metadata:
168
174
  tool_metadata = ToolMetadata(
169
- execution_time_ms=metadata.get('executionTimeMs'),
170
- tool_name=metadata.get('toolName'),
171
- extra={k: v for k, v in metadata.items()
172
- if k not in ['executionTimeMs', 'toolName']}
175
+ execution_time_ms=metadata.get("executionTimeMs"),
176
+ tool_name=metadata.get("toolName"),
177
+ extra={
178
+ k: v for k, v in metadata.items() if k not in ["executionTimeMs", "toolName"]
179
+ },
173
180
  )
174
181
 
175
182
  return ToolResult(
176
- status='permission_denied',
183
+ status="permission_denied",
177
184
  error=ToolErrorInfo(
178
185
  code=ToolErrorCodes.PERMISSION_DENIED,
179
186
  message=message,
180
- details={'requiredPermissions': required_permissions} if required_permissions else None
187
+ details={"requiredPermissions": required_permissions}
188
+ if required_permissions
189
+ else None,
181
190
  ),
182
- metadata=tool_metadata
191
+ metadata=tool_metadata,
183
192
  )
184
193
 
185
194
  @staticmethod
186
195
  def not_found(
187
- resource: str,
188
- identifier: Optional[str] = None,
189
- metadata: Optional[Dict[str, Any]] = None
196
+ resource: str, identifier: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None
190
197
  ) -> ToolResult[None]:
191
198
  """Create a not found tool result."""
192
199
  message = f"{resource} not found"
@@ -196,36 +203,38 @@ class ToolResponse:
196
203
  tool_metadata = None
197
204
  if metadata:
198
205
  tool_metadata = ToolMetadata(
199
- execution_time_ms=metadata.get('executionTimeMs'),
200
- tool_name=metadata.get('toolName'),
201
- extra={k: v for k, v in metadata.items()
202
- if k not in ['executionTimeMs', 'toolName']}
206
+ execution_time_ms=metadata.get("executionTimeMs"),
207
+ tool_name=metadata.get("toolName"),
208
+ extra={
209
+ k: v for k, v in metadata.items() if k not in ["executionTimeMs", "toolName"]
210
+ },
203
211
  )
204
212
 
205
213
  return ToolResult(
206
- status='not_found',
214
+ status="not_found",
207
215
  error=ToolErrorInfo(
208
216
  code=ToolErrorCodes.NOT_FOUND,
209
217
  message=message,
210
- details={'resource': resource, 'identifier': identifier}
218
+ details={"resource": resource, "identifier": identifier},
211
219
  ),
212
- metadata=tool_metadata
220
+ metadata=tool_metadata,
213
221
  )
214
222
 
223
+
215
224
  def with_error_handling(
216
- tool_name: str,
217
- executor: Callable[[TArgs, TContext], Union[TResult, Awaitable[TResult]]]
225
+ tool_name: str, executor: Callable[[TArgs, TContext], Union[TResult, Awaitable[TResult]]]
218
226
  ) -> Callable[[TArgs, TContext], Awaitable[ToolResult[TResult]]]:
219
227
  """
220
228
  Tool execution wrapper that provides standardized error handling.
221
-
229
+
222
230
  Args:
223
231
  tool_name: Name of the tool for logging and metadata
224
232
  executor: The actual tool execution function
225
-
233
+
226
234
  Returns:
227
235
  Wrapped function that returns a ToolResult
228
236
  """
237
+
229
238
  async def wrapper(args: TArgs, context: TContext) -> ToolResult[TResult]:
230
239
  start_time = time.time() * 1000 # Convert to milliseconds like TypeScript Date.now()
231
240
 
@@ -234,16 +243,15 @@ def with_error_handling(
234
243
 
235
244
  # Handle both sync and async executors
236
245
  result = executor(args, context)
237
- if hasattr(result, '__await__'): # Check if it's awaitable
246
+ if hasattr(result, "__await__"): # Check if it's awaitable
238
247
  result = await result
239
248
 
240
249
  execution_time = int(time.time() * 1000 - start_time)
241
250
  print(f"[TOOL:{tool_name}] Completed successfully in {execution_time}ms")
242
251
 
243
- return ToolResponse.success(result, {
244
- 'executionTimeMs': execution_time,
245
- 'toolName': tool_name
246
- })
252
+ return ToolResponse.success(
253
+ result, {"executionTimeMs": execution_time, "toolName": tool_name}
254
+ )
247
255
 
248
256
  except Exception as error:
249
257
  execution_time = int(time.time() * 1000 - start_time)
@@ -253,72 +261,74 @@ def with_error_handling(
253
261
  return ToolResponse.error(
254
262
  ToolErrorCodes.EXECUTION_FAILED,
255
263
  str(error),
256
- {'stack': traceback.format_exc()},
257
- {'executionTimeMs': execution_time, 'toolName': tool_name}
264
+ {"stack": traceback.format_exc()},
265
+ {"executionTimeMs": execution_time, "toolName": tool_name},
258
266
  )
259
267
 
260
268
  return ToolResponse.error(
261
269
  ToolErrorCodes.UNKNOWN_ERROR,
262
- 'Unknown error occurred',
270
+ "Unknown error occurred",
263
271
  error,
264
- {'executionTimeMs': execution_time, 'toolName': tool_name}
272
+ {"executionTimeMs": execution_time, "toolName": tool_name},
265
273
  )
266
274
 
267
275
  return wrapper
268
276
 
277
+
269
278
  def require_permissions(
270
- required_permissions: List[str]
279
+ required_permissions: List[str],
271
280
  ) -> Callable[[TContext], Optional[ToolResult[None]]]:
272
281
  """
273
282
  Permission checking helper.
274
-
283
+
275
284
  Args:
276
285
  required_permissions: List of permissions required
277
-
286
+
278
287
  Returns:
279
288
  Function that checks permissions and returns ToolResult if denied, None if allowed
280
289
  """
290
+
281
291
  def check_permissions(context: TContext) -> Optional[ToolResult[None]]:
282
292
  # Try to get permissions from context
283
- user_permissions = getattr(context, 'permissions', None)
293
+ user_permissions = getattr(context, "permissions", None)
284
294
  if user_permissions is None or not isinstance(user_permissions, list):
285
295
  user_permissions = []
286
296
 
287
297
  missing_permissions = [
288
- perm for perm in required_permissions
289
- if perm not in user_permissions
298
+ perm for perm in required_permissions if perm not in user_permissions
290
299
  ]
291
300
 
292
301
  if missing_permissions:
293
302
  return ToolResponse.permission_denied(
294
303
  f"Missing required permissions: {', '.join(missing_permissions)}",
295
- required_permissions
304
+ required_permissions,
296
305
  )
297
306
 
298
307
  return None # No error
299
308
 
300
309
  return check_permissions
301
310
 
311
+
302
312
  def tool_result_to_string(result: ToolResult[Any]) -> str:
303
313
  """
304
314
  Convert ToolResult to string for backward compatibility with existing tools.
305
-
315
+
306
316
  Args:
307
317
  result: The ToolResult to convert
308
-
318
+
309
319
  Returns:
310
320
  String representation of the result
311
321
  """
312
- if result.status == 'success':
322
+ if result.status == "success":
313
323
  # For successful results, include metadata if available
314
324
  if result.metadata:
315
325
  success_obj = {
316
- 'status': 'success',
317
- 'data': result.data,
318
- 'metadata': result.metadata.to_dict()
326
+ "status": "success",
327
+ "data": result.data,
328
+ "metadata": result.metadata.to_dict(),
319
329
  }
320
330
  return json.dumps(success_obj, default=str)
321
-
331
+
322
332
  # If no metadata, return just the data for backward compatibility
323
333
  if isinstance(result.data, str):
324
334
  return result.data
@@ -326,15 +336,15 @@ def tool_result_to_string(result: ToolResult[Any]) -> str:
326
336
 
327
337
  # For errors, return a structured error message
328
338
  error_obj = {
329
- 'error': result.status,
330
- 'code': result.error.code if result.error else 'UNKNOWN',
331
- 'message': result.error.message if result.error else 'Unknown error',
339
+ "error": result.status,
340
+ "code": result.error.code if result.error else "UNKNOWN",
341
+ "message": result.error.message if result.error else "Unknown error",
332
342
  }
333
343
 
334
344
  if result.error and result.error.details:
335
- error_obj['details'] = result.error.details
345
+ error_obj["details"] = result.error.details
336
346
 
337
347
  if result.metadata:
338
- error_obj['metadata'] = result.metadata.to_dict()
348
+ error_obj["metadata"] = result.metadata.to_dict()
339
349
 
340
350
  return json.dumps(error_obj, indent=2, default=str)