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
jaf/plugins/base.py CHANGED
@@ -16,28 +16,31 @@ from ..core.types import Tool, ModelProvider, Agent
16
16
 
17
17
  class PluginStatus(str, Enum):
18
18
  """Plugin lifecycle status."""
19
- UNLOADED = 'unloaded'
20
- LOADING = 'loading'
21
- LOADED = 'loaded'
22
- ACTIVE = 'active'
23
- ERROR = 'error'
24
- DISABLED = 'disabled'
19
+
20
+ UNLOADED = "unloaded"
21
+ LOADING = "loading"
22
+ LOADED = "loaded"
23
+ ACTIVE = "active"
24
+ ERROR = "error"
25
+ DISABLED = "disabled"
25
26
 
26
27
 
27
28
  class PluginType(str, Enum):
28
29
  """Types of plugins supported by JAF."""
29
- TOOL_PROVIDER = 'tool_provider'
30
- MODEL_PROVIDER = 'model_provider'
31
- MEMORY_PROVIDER = 'memory_provider'
32
- AGENT_EXTENSION = 'agent_extension'
33
- MIDDLEWARE = 'middleware'
34
- INTEGRATION = 'integration'
35
- CUSTOM = 'custom'
30
+
31
+ TOOL_PROVIDER = "tool_provider"
32
+ MODEL_PROVIDER = "model_provider"
33
+ MEMORY_PROVIDER = "memory_provider"
34
+ AGENT_EXTENSION = "agent_extension"
35
+ MIDDLEWARE = "middleware"
36
+ INTEGRATION = "integration"
37
+ CUSTOM = "custom"
36
38
 
37
39
 
38
40
  @dataclass(frozen=True)
39
41
  class PluginDependency:
40
42
  """Represents a plugin dependency."""
43
+
41
44
  name: str
42
45
  version_constraint: str = "*" # Semantic version constraint
43
46
  optional: bool = False
@@ -47,6 +50,7 @@ class PluginDependency:
47
50
  @dataclass(frozen=True)
48
51
  class PluginMetadata:
49
52
  """Comprehensive metadata for a JAF plugin."""
53
+
50
54
  name: str
51
55
  version: str
52
56
  description: str
@@ -61,48 +65,49 @@ class PluginMetadata:
61
65
  keywords: List[str] = field(default_factory=list)
62
66
  configuration_schema: Optional[Dict[str, Any]] = None
63
67
  created_at: datetime = field(default_factory=datetime.now)
64
-
68
+
65
69
  def to_dict(self) -> Dict[str, Any]:
66
70
  """Convert metadata to dictionary for serialization."""
67
71
  return {
68
- 'name': self.name,
69
- 'version': self.version,
70
- 'description': self.description,
71
- 'author': self.author,
72
- 'plugin_type': self.plugin_type.value,
73
- 'entry_point': self.entry_point,
74
- 'dependencies': [
72
+ "name": self.name,
73
+ "version": self.version,
74
+ "description": self.description,
75
+ "author": self.author,
76
+ "plugin_type": self.plugin_type.value,
77
+ "entry_point": self.entry_point,
78
+ "dependencies": [
75
79
  {
76
- 'name': dep.name,
77
- 'version_constraint': dep.version_constraint,
78
- 'optional': dep.optional,
79
- 'description': dep.description
80
+ "name": dep.name,
81
+ "version_constraint": dep.version_constraint,
82
+ "optional": dep.optional,
83
+ "description": dep.description,
80
84
  }
81
85
  for dep in self.dependencies
82
86
  ],
83
- 'jaf_version_constraint': self.jaf_version_constraint,
84
- 'license': self.license,
85
- 'homepage': self.homepage,
86
- 'repository': self.repository,
87
- 'keywords': self.keywords,
88
- 'configuration_schema': self.configuration_schema,
89
- 'created_at': self.created_at.isoformat()
87
+ "jaf_version_constraint": self.jaf_version_constraint,
88
+ "license": self.license,
89
+ "homepage": self.homepage,
90
+ "repository": self.repository,
91
+ "keywords": self.keywords,
92
+ "configuration_schema": self.configuration_schema,
93
+ "created_at": self.created_at.isoformat(),
90
94
  }
91
95
 
92
96
 
93
97
  @dataclass
94
98
  class PluginContext:
95
99
  """Context provided to plugins during lifecycle operations."""
100
+
96
101
  plugin_metadata: PluginMetadata
97
102
  configuration: Dict[str, Any]
98
103
  jaf_version: str
99
104
  plugin_directory: Optional[str] = None
100
105
  logger: Optional[Any] = None
101
-
106
+
102
107
  def get_config(self, key: str, default: Any = None) -> Any:
103
108
  """Get configuration value with optional default."""
104
109
  return self.configuration.get(key, default)
105
-
110
+
106
111
  def has_config(self, key: str) -> bool:
107
112
  """Check if configuration key exists."""
108
113
  return key in self.configuration
@@ -112,53 +117,53 @@ class PluginContext:
112
117
  class JAFPlugin(Protocol):
113
118
  """
114
119
  Base protocol for all JAF plugins.
115
-
120
+
116
121
  Plugins must implement this interface to be compatible with the JAF plugin system.
117
122
  The plugin system provides lifecycle management, dependency resolution, and
118
123
  configuration management.
119
124
  """
120
-
125
+
121
126
  @property
122
127
  def metadata(self) -> PluginMetadata:
123
128
  """Get plugin metadata."""
124
129
  ...
125
-
130
+
126
131
  async def initialize(self, context: PluginContext) -> None:
127
132
  """
128
133
  Initialize the plugin with the provided context.
129
-
134
+
130
135
  This method is called once when the plugin is loaded. Use this for
131
136
  one-time setup operations like establishing connections, loading
132
137
  configuration, or preparing resources.
133
-
138
+
134
139
  Args:
135
140
  context: Plugin context with configuration and metadata
136
141
  """
137
142
  ...
138
-
143
+
139
144
  async def activate(self) -> None:
140
145
  """
141
146
  Activate the plugin and make it available for use.
142
-
147
+
143
148
  This method is called after initialization to activate the plugin's
144
149
  functionality. Register tools, providers, or other extensions here.
145
150
  """
146
151
  ...
147
-
152
+
148
153
  async def deactivate(self) -> None:
149
154
  """
150
155
  Deactivate the plugin and clean up resources.
151
-
156
+
152
157
  This method is called when the plugin is being unloaded or the
153
158
  system is shutting down. Clean up connections, unregister handlers,
154
159
  and release resources here.
155
160
  """
156
161
  ...
157
-
162
+
158
163
  def get_health_status(self) -> Dict[str, Any]:
159
164
  """
160
165
  Get the current health status of the plugin.
161
-
166
+
162
167
  Returns:
163
168
  Dictionary containing health information including status,
164
169
  last check time, and any relevant metrics or error information.
@@ -169,133 +174,133 @@ class JAFPlugin(Protocol):
169
174
  class BaseJAFPlugin:
170
175
  """
171
176
  Base implementation of JAFPlugin with common functionality.
172
-
177
+
173
178
  This class provides a foundation for creating JAF plugins with standard
174
179
  lifecycle management, health checking, and error handling.
175
180
  """
176
-
181
+
177
182
  def __init__(self, metadata: PluginMetadata):
178
183
  self._metadata = metadata
179
184
  self._status = PluginStatus.UNLOADED
180
185
  self._context: Optional[PluginContext] = None
181
186
  self._last_health_check = datetime.now()
182
- self._health_status = {'status': 'unknown', 'message': 'Not initialized'}
187
+ self._health_status = {"status": "unknown", "message": "Not initialized"}
183
188
  self._error_count = 0
184
189
  self._last_error: Optional[str] = None
185
-
190
+
186
191
  @property
187
192
  def metadata(self) -> PluginMetadata:
188
193
  """Get plugin metadata."""
189
194
  return self._metadata
190
-
195
+
191
196
  @property
192
197
  def status(self) -> PluginStatus:
193
198
  """Get current plugin status."""
194
199
  return self._status
195
-
200
+
196
201
  @property
197
202
  def context(self) -> Optional[PluginContext]:
198
203
  """Get plugin context (available after initialization)."""
199
204
  return self._context
200
-
205
+
201
206
  async def initialize(self, context: PluginContext) -> None:
202
207
  """Initialize the plugin with context."""
203
208
  try:
204
209
  self._status = PluginStatus.LOADING
205
210
  self._context = context
206
-
211
+
207
212
  # Validate configuration against schema if provided
208
213
  if self._metadata.configuration_schema:
209
214
  self._validate_configuration(context.configuration)
210
-
215
+
211
216
  # Call plugin-specific initialization
212
217
  await self._on_initialize(context)
213
-
218
+
214
219
  self._status = PluginStatus.LOADED
215
- self._health_status = {'status': 'healthy', 'message': 'Plugin loaded successfully'}
216
-
220
+ self._health_status = {"status": "healthy", "message": "Plugin loaded successfully"}
221
+
217
222
  except Exception as e:
218
223
  self._status = PluginStatus.ERROR
219
224
  self._error_count += 1
220
225
  self._last_error = str(e)
221
- self._health_status = {'status': 'error', 'message': f'Initialization failed: {e}'}
226
+ self._health_status = {"status": "error", "message": f"Initialization failed: {e}"}
222
227
  raise
223
-
228
+
224
229
  async def activate(self) -> None:
225
230
  """Activate the plugin."""
226
231
  try:
227
232
  if self._status != PluginStatus.LOADED:
228
233
  raise RuntimeError(f"Cannot activate plugin in status: {self._status}")
229
-
234
+
230
235
  await self._on_activate()
231
236
  self._status = PluginStatus.ACTIVE
232
- self._health_status = {'status': 'healthy', 'message': 'Plugin active'}
233
-
237
+ self._health_status = {"status": "healthy", "message": "Plugin active"}
238
+
234
239
  except Exception as e:
235
240
  self._status = PluginStatus.ERROR
236
241
  self._error_count += 1
237
242
  self._last_error = str(e)
238
- self._health_status = {'status': 'error', 'message': f'Activation failed: {e}'}
243
+ self._health_status = {"status": "error", "message": f"Activation failed: {e}"}
239
244
  raise
240
-
245
+
241
246
  async def deactivate(self) -> None:
242
247
  """Deactivate the plugin."""
243
248
  try:
244
249
  await self._on_deactivate()
245
250
  self._status = PluginStatus.LOADED
246
- self._health_status = {'status': 'inactive', 'message': 'Plugin deactivated'}
247
-
251
+ self._health_status = {"status": "inactive", "message": "Plugin deactivated"}
252
+
248
253
  except Exception as e:
249
254
  self._error_count += 1
250
255
  self._last_error = str(e)
251
256
  # Don't change status on deactivation error, but log it
252
257
  raise
253
-
258
+
254
259
  def get_health_status(self) -> Dict[str, Any]:
255
260
  """Get comprehensive health status."""
256
261
  self._last_health_check = datetime.now()
257
-
262
+
258
263
  # Get plugin-specific health info
259
264
  plugin_health = self._get_plugin_health()
260
-
265
+
261
266
  return {
262
- 'plugin_name': self._metadata.name,
263
- 'plugin_version': self._metadata.version,
264
- 'status': self._status.value,
265
- 'health': self._health_status,
266
- 'last_check': self._last_health_check.isoformat(),
267
- 'error_count': self._error_count,
268
- 'last_error': self._last_error,
269
- 'plugin_specific': plugin_health
267
+ "plugin_name": self._metadata.name,
268
+ "plugin_version": self._metadata.version,
269
+ "status": self._status.value,
270
+ "health": self._health_status,
271
+ "last_check": self._last_health_check.isoformat(),
272
+ "error_count": self._error_count,
273
+ "last_error": self._last_error,
274
+ "plugin_specific": plugin_health,
270
275
  }
271
-
276
+
272
277
  # Abstract methods for plugin-specific implementation
273
-
278
+
274
279
  async def _on_initialize(self, context: PluginContext) -> None:
275
280
  """Plugin-specific initialization logic. Override in subclasses."""
276
281
  pass
277
-
282
+
278
283
  async def _on_activate(self) -> None:
279
284
  """Plugin-specific activation logic. Override in subclasses."""
280
285
  pass
281
-
286
+
282
287
  async def _on_deactivate(self) -> None:
283
288
  """Plugin-specific deactivation logic. Override in subclasses."""
284
289
  pass
285
-
290
+
286
291
  def _get_plugin_health(self) -> Dict[str, Any]:
287
292
  """Get plugin-specific health information. Override in subclasses."""
288
- return {'status': 'ok'}
289
-
293
+ return {"status": "ok"}
294
+
290
295
  def _validate_configuration(self, config: Dict[str, Any]) -> None:
291
296
  """Validate configuration against schema. Override for custom validation."""
292
297
  # Basic validation - could be enhanced with jsonschema
293
298
  if not self._metadata.configuration_schema:
294
299
  return
295
-
300
+
296
301
  schema = self._metadata.configuration_schema
297
- required_fields = schema.get('required', [])
298
-
302
+ required_fields = schema.get("required", [])
303
+
299
304
  for field in required_fields:
300
305
  if field not in config:
301
306
  raise ValueError(f"Required configuration field '{field}' is missing")
@@ -304,11 +309,11 @@ class BaseJAFPlugin:
304
309
  @runtime_checkable
305
310
  class ToolProvider(Protocol):
306
311
  """Protocol for plugins that provide tools."""
307
-
312
+
308
313
  def get_tools(self) -> List[Tool[Any, Any]]:
309
314
  """Get list of tools provided by this plugin."""
310
315
  ...
311
-
316
+
312
317
  def get_tool_by_name(self, name: str) -> Optional[Tool[Any, Any]]:
313
318
  """Get a specific tool by name."""
314
319
  ...
@@ -317,11 +322,11 @@ class ToolProvider(Protocol):
317
322
  @runtime_checkable
318
323
  class ModelProviderPlugin(Protocol):
319
324
  """Protocol for plugins that provide model providers."""
320
-
325
+
321
326
  def get_model_provider(self) -> ModelProvider[Any]:
322
327
  """Get the model provider instance."""
323
328
  ...
324
-
329
+
325
330
  def get_supported_models(self) -> List[str]:
326
331
  """Get list of supported model names."""
327
332
  ...
@@ -330,11 +335,11 @@ class ModelProviderPlugin(Protocol):
330
335
  @runtime_checkable
331
336
  class MemoryProviderPlugin(Protocol):
332
337
  """Protocol for plugins that provide memory providers."""
333
-
338
+
334
339
  def get_memory_provider(self) -> Any: # MemoryProvider type
335
340
  """Get the memory provider instance."""
336
341
  ...
337
-
342
+
338
343
  def get_provider_capabilities(self) -> Dict[str, Any]:
339
344
  """Get capabilities and configuration options for the memory provider."""
340
345
  ...
@@ -343,11 +348,11 @@ class MemoryProviderPlugin(Protocol):
343
348
  @runtime_checkable
344
349
  class AgentExtension(Protocol):
345
350
  """Protocol for plugins that extend agent functionality."""
346
-
351
+
347
352
  def extend_agent(self, agent: Agent[Any, Any]) -> Agent[Any, Any]:
348
353
  """Extend an agent with additional functionality."""
349
354
  ...
350
-
355
+
351
356
  def get_extension_info(self) -> Dict[str, Any]:
352
357
  """Get information about what this extension provides."""
353
358
  ...
@@ -355,7 +360,7 @@ class AgentExtension(Protocol):
355
360
 
356
361
  class PluginError(Exception):
357
362
  """Base exception for plugin-related errors."""
358
-
363
+
359
364
  def __init__(self, message: str, plugin_name: str = "", error_code: str = ""):
360
365
  super().__init__(message)
361
366
  self.plugin_name = plugin_name
@@ -364,19 +369,23 @@ class PluginError(Exception):
364
369
 
365
370
  class PluginLoadError(PluginError):
366
371
  """Exception raised when a plugin fails to load."""
372
+
367
373
  pass
368
374
 
369
375
 
370
376
  class PluginDependencyError(PluginError):
371
377
  """Exception raised when plugin dependencies cannot be resolved."""
378
+
372
379
  pass
373
380
 
374
381
 
375
382
  class PluginConfigurationError(PluginError):
376
383
  """Exception raised when plugin configuration is invalid."""
384
+
377
385
  pass
378
386
 
379
387
 
380
388
  class PluginVersionError(PluginError):
381
389
  """Exception raised when plugin version constraints are not met."""
390
+
382
391
  pass
jaf/policies/__init__.py CHANGED
@@ -10,7 +10,6 @@ __all__ = [
10
10
  "create_json_validation_guardrail",
11
11
  "create_rate_limit_guardrail",
12
12
  "combine_guardrails",
13
-
14
13
  # Handoff policies
15
14
  "create_handoff_guardrail",
16
15
  "validate_handoff_permissions",