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/memory/types.py CHANGED
@@ -14,29 +14,36 @@ from pydantic import BaseModel, Field
14
14
  from ..core.types import Message, TraceId, MessageId
15
15
 
16
16
  # Generic Result type for functional error handling
17
- T = TypeVar('T')
18
- E = TypeVar('E', bound='MemoryError')
17
+ T = TypeVar("T")
18
+ E = TypeVar("E", bound="MemoryError")
19
+
19
20
 
20
21
  @dataclass(frozen=True)
21
22
  class Success(Generic[T]):
22
23
  """Represents a successful operation result."""
24
+
23
25
  data: T
24
26
 
27
+
25
28
  @dataclass(frozen=True)
26
29
  class Failure(Generic[E]):
27
30
  """Represents a failed operation result."""
31
+
28
32
  error: E
29
33
 
34
+
30
35
  Result = Union[Success[T], Failure[E]]
31
36
 
37
+
32
38
  @dataclass(frozen=True)
33
39
  class ConversationMemory:
34
40
  """
35
41
  Immutable conversation memory object containing conversation history and metadata.
36
-
42
+
37
43
  This represents a complete conversation stored in memory, including all messages
38
44
  and associated metadata like creation time, user information, and trace data.
39
45
  """
46
+
40
47
  conversation_id: str
41
48
  user_id: Optional[str] = None
42
49
  messages: List[Message] = field(default_factory=list)
@@ -45,11 +52,13 @@ class ConversationMemory:
45
52
  def __post_init__(self):
46
53
  """Ensure messages list is frozen (immutable)."""
47
54
  if self.messages is not None:
48
- object.__setattr__(self, 'messages', tuple(self.messages))
55
+ object.__setattr__(self, "messages", tuple(self.messages))
56
+
49
57
 
50
58
  @dataclass(frozen=True)
51
59
  class MemoryQuery:
52
60
  """Query parameters for searching conversations in memory providers."""
61
+
53
62
  conversation_id: Optional[str] = None
54
63
  user_id: Optional[str] = None
55
64
  trace_id: Optional[TraceId] = None
@@ -58,10 +67,11 @@ class MemoryQuery:
58
67
  since: Optional[datetime] = None
59
68
  until: Optional[datetime] = None
60
69
 
70
+
61
71
  class MemoryProvider(Protocol):
62
72
  """
63
73
  Protocol defining the interface that all memory providers must implement.
64
-
74
+
65
75
  This protocol ensures consistent behavior across different storage backends
66
76
  (in-memory, Redis, PostgreSQL) while maintaining type safety.
67
77
  """
@@ -70,15 +80,14 @@ class MemoryProvider(Protocol):
70
80
  self,
71
81
  conversation_id: str,
72
82
  messages: List[Message],
73
- metadata: Optional[Dict[str, Any]] = None
74
- ) -> Result[None, 'MemoryStorageError']:
83
+ metadata: Optional[Dict[str, Any]] = None,
84
+ ) -> Result[None, "MemoryStorageError"]:
75
85
  """Store messages for a conversation."""
76
86
  ...
77
87
 
78
88
  async def get_conversation(
79
- self,
80
- conversation_id: str
81
- ) -> Result[Optional[ConversationMemory], 'MemoryStorageError']:
89
+ self, conversation_id: str
90
+ ) -> Result[Optional[ConversationMemory], "MemoryStorageError"]:
82
91
  """Retrieve conversation history."""
83
92
  ...
84
93
 
@@ -86,61 +95,49 @@ class MemoryProvider(Protocol):
86
95
  self,
87
96
  conversation_id: str,
88
97
  messages: List[Message],
89
- metadata: Optional[Dict[str, Any]] = None
90
- ) -> Result[None, Union['MemoryNotFoundError', 'MemoryStorageError']]:
98
+ metadata: Optional[Dict[str, Any]] = None,
99
+ ) -> Result[None, Union["MemoryNotFoundError", "MemoryStorageError"]]:
91
100
  """Append new messages to existing conversation."""
92
101
  ...
93
102
 
94
103
  async def find_conversations(
95
- self,
96
- query: MemoryQuery
97
- ) -> Result[List[ConversationMemory], 'MemoryStorageError']:
104
+ self, query: MemoryQuery
105
+ ) -> Result[List[ConversationMemory], "MemoryStorageError"]:
98
106
  """Search conversations by query parameters."""
99
107
  ...
100
108
 
101
109
  async def get_recent_messages(
102
- self,
103
- conversation_id: str,
104
- limit: int = 50
105
- ) -> Result[List[Message], Union['MemoryNotFoundError', 'MemoryStorageError']]:
110
+ self, conversation_id: str, limit: int = 50
111
+ ) -> Result[List[Message], Union["MemoryNotFoundError", "MemoryStorageError"]]:
106
112
  """Get recent messages from a conversation."""
107
113
  ...
108
114
 
109
- async def delete_conversation(
110
- self,
111
- conversation_id: str
112
- ) -> Result[bool, 'MemoryStorageError']:
115
+ async def delete_conversation(self, conversation_id: str) -> Result[bool, "MemoryStorageError"]:
113
116
  """Delete conversation and return True if it existed."""
114
117
  ...
115
118
 
116
- async def clear_user_conversations(
117
- self,
118
- user_id: str
119
- ) -> Result[int, 'MemoryStorageError']:
119
+ async def clear_user_conversations(self, user_id: str) -> Result[int, "MemoryStorageError"]:
120
120
  """Clear all conversations for a user and return count deleted."""
121
121
  ...
122
122
 
123
123
  async def get_stats(
124
- self,
125
- user_id: Optional[str] = None
126
- ) -> Result[Dict[str, Any], 'MemoryStorageError']:
124
+ self, user_id: Optional[str] = None
125
+ ) -> Result[Dict[str, Any], "MemoryStorageError"]:
127
126
  """Get conversation statistics."""
128
127
  ...
129
128
 
130
- async def health_check(self) -> Result[Dict[str, Any], 'MemoryConnectionError']:
129
+ async def health_check(self) -> Result[Dict[str, Any], "MemoryConnectionError"]:
131
130
  """Check provider health and return status information."""
132
131
  ...
133
132
 
134
- async def close(self) -> Result[None, 'MemoryConnectionError']:
133
+ async def close(self) -> Result[None, "MemoryConnectionError"]:
135
134
  """Close/cleanup the provider."""
136
135
  ...
137
136
 
138
137
  # Regeneration support methods
139
138
  async def truncate_conversation_after(
140
- self,
141
- conversation_id: str,
142
- message_id: MessageId
143
- ) -> Result[int, Union['MemoryNotFoundError', 'MemoryStorageError']]:
139
+ self, conversation_id: str, message_id: MessageId
140
+ ) -> Result[int, Union["MemoryNotFoundError", "MemoryStorageError"]]:
144
141
  """
145
142
  Truncate conversation after (and including) the specified message ID.
146
143
  Returns the number of messages removed.
@@ -148,10 +145,8 @@ class MemoryProvider(Protocol):
148
145
  ...
149
146
 
150
147
  async def get_conversation_until_message(
151
- self,
152
- conversation_id: str,
153
- message_id: MessageId
154
- ) -> Result[Optional[ConversationMemory], Union['MemoryNotFoundError', 'MemoryStorageError']]:
148
+ self, conversation_id: str, message_id: MessageId
149
+ ) -> Result[Optional[ConversationMemory], Union["MemoryNotFoundError", "MemoryStorageError"]]:
155
150
  """
156
151
  Get conversation history up to (but not including) the specified message ID.
157
152
  Useful for regeneration scenarios.
@@ -159,26 +154,28 @@ class MemoryProvider(Protocol):
159
154
  ...
160
155
 
161
156
  async def mark_regeneration_point(
162
- self,
163
- conversation_id: str,
164
- message_id: MessageId,
165
- regeneration_metadata: Dict[str, Any]
166
- ) -> Result[None, Union['MemoryNotFoundError', 'MemoryStorageError']]:
157
+ self, conversation_id: str, message_id: MessageId, regeneration_metadata: Dict[str, Any]
158
+ ) -> Result[None, Union["MemoryNotFoundError", "MemoryStorageError"]]:
167
159
  """
168
160
  Mark a regeneration point in the conversation for audit purposes.
169
161
  """
170
162
  ...
171
163
 
164
+
172
165
  # Configuration models using Pydantic for validation
173
166
 
167
+
174
168
  class InMemoryConfig(BaseModel):
175
169
  """Configuration for in-memory provider."""
170
+
176
171
  type: str = Field(default="memory", json_schema_extra={"literal": True})
177
172
  max_conversations: int = Field(default=1000, ge=1)
178
173
  max_messages_per_conversation: int = Field(default=1000, ge=1)
179
174
 
175
+
180
176
  class RedisConfig(BaseModel):
181
177
  """Configuration for Redis provider."""
178
+
182
179
  type: str = Field(default="redis", json_schema_extra={"literal": True})
183
180
  url: Optional[str] = None
184
181
  host: str = Field(default="localhost")
@@ -188,8 +185,10 @@ class RedisConfig(BaseModel):
188
185
  key_prefix: str = Field(default="jaf:memory:")
189
186
  ttl: Optional[int] = Field(default=None, ge=1) # seconds
190
187
 
188
+
191
189
  class PostgresConfig(BaseModel):
192
190
  """Configuration for PostgreSQL provider."""
191
+
193
192
  type: str = Field(default="postgres", json_schema_extra={"literal": True})
194
193
  connection_string: Optional[str] = None
195
194
  host: str = Field(default="localhost")
@@ -201,13 +200,16 @@ class PostgresConfig(BaseModel):
201
200
  table_name: str = Field(default="conversations")
202
201
  max_connections: int = Field(default=10, ge=1)
203
202
 
203
+
204
204
  # Union type for all provider configurations
205
205
  MemoryProviderConfig = Union[InMemoryConfig, RedisConfig, PostgresConfig]
206
206
 
207
207
  # Functional error types for memory providers
208
208
 
209
+
209
210
  class MemoryError:
210
211
  """Base class for memory-related errors."""
212
+
211
213
  def __init__(self, message: str, provider: str, cause: Optional[Exception] = None):
212
214
  self.message = message
213
215
  self.provider = provider
@@ -216,20 +218,28 @@ class MemoryError:
216
218
  def __eq__(self, other):
217
219
  if not isinstance(other, MemoryError):
218
220
  return False
219
- return (self.message == other.message and
220
- self.provider == other.provider and
221
- self.cause == other.cause)
221
+ return (
222
+ self.message == other.message
223
+ and self.provider == other.provider
224
+ and self.cause == other.cause
225
+ )
222
226
 
223
227
  def __repr__(self):
224
228
  return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, cause={self.cause!r})"
225
229
 
230
+
226
231
  class MemoryConnectionError(MemoryError):
227
232
  """Error for connection failures."""
233
+
228
234
  pass
229
235
 
236
+
230
237
  class MemoryNotFoundError(MemoryError):
231
238
  """Error when a conversation is not found."""
232
- def __init__(self, message: str, provider: str, conversation_id: str, cause: Optional[Exception] = None):
239
+
240
+ def __init__(
241
+ self, message: str, provider: str, conversation_id: str, cause: Optional[Exception] = None
242
+ ):
233
243
  super().__init__(message, provider, cause)
234
244
  self.conversation_id = conversation_id
235
245
 
@@ -241,9 +251,13 @@ class MemoryNotFoundError(MemoryError):
241
251
  def __repr__(self):
242
252
  return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, conversation_id={self.conversation_id!r}, cause={self.cause!r})"
243
253
 
254
+
244
255
  class MemoryStorageError(MemoryError):
245
256
  """Error for storage operation failures."""
246
- def __init__(self, message: str, provider: str, operation: str, cause: Optional[Exception] = None):
257
+
258
+ def __init__(
259
+ self, message: str, provider: str, operation: str, cause: Optional[Exception] = None
260
+ ):
247
261
  super().__init__(message, provider, cause)
248
262
  self.operation = operation
249
263
 
@@ -255,16 +269,21 @@ class MemoryStorageError(MemoryError):
255
269
  def __repr__(self):
256
270
  return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, operation={self.operation!r}, cause={self.cause!r})"
257
271
 
272
+
258
273
  # Union of all possible memory errors
259
274
  MemoryErrorUnion = Union[MemoryConnectionError, MemoryNotFoundError, MemoryStorageError]
260
275
 
276
+
261
277
  # Memory configuration for the engine - using dataclass instead of Pydantic
262
278
  @dataclass(frozen=True)
263
279
  class MemoryConfig:
264
280
  """Configuration for memory integration in the engine."""
281
+
265
282
  provider: MemoryProvider
266
283
  auto_store: bool = True
267
284
  max_messages: Optional[int] = None
268
285
  ttl: Optional[int] = None
269
286
  compression_threshold: Optional[int] = None
270
- store_on_completion: bool = True # Store conversation on completion (in addition to interruptions)
287
+ store_on_completion: bool = (
288
+ True # Store conversation on completion (in addition to interruptions)
289
+ )
jaf/memory/utils.py CHANGED
@@ -16,7 +16,7 @@ from .types import ConversationMemory
16
16
  def serialize_message(msg: Message) -> dict:
17
17
  """
18
18
  Convert Message dataclass to dict for storage.
19
-
19
+
20
20
  This provides a consistent serialization format across all memory providers.
21
21
  """
22
22
  return {
@@ -28,19 +28,19 @@ def serialize_message(msg: Message) -> dict:
28
28
  {
29
29
  "id": tc.id,
30
30
  "type": tc.type,
31
- "function": {
32
- "name": tc.function.name,
33
- "arguments": tc.function.arguments
34
- }
35
- } for tc in msg.tool_calls
36
- ] if msg.tool_calls else None
31
+ "function": {"name": tc.function.name, "arguments": tc.function.arguments},
32
+ }
33
+ for tc in msg.tool_calls
34
+ ]
35
+ if msg.tool_calls
36
+ else None,
37
37
  }
38
38
 
39
39
 
40
40
  def deserialize_message(msg_data: dict) -> Message:
41
41
  """
42
42
  Convert dict back to Message dataclass from storage.
43
-
43
+
44
44
  This provides a consistent deserialization format across all memory providers.
45
45
  """
46
46
  tool_calls = None
@@ -50,10 +50,10 @@ def deserialize_message(msg_data: dict) -> Message:
50
50
  id=tc["id"],
51
51
  type=tc["type"],
52
52
  function=ToolCallFunction(
53
- name=tc["function"]["name"],
54
- arguments=tc["function"]["arguments"]
55
- )
56
- ) for tc in msg_data["tool_calls"]
53
+ name=tc["function"]["name"], arguments=tc["function"]["arguments"]
54
+ ),
55
+ )
56
+ for tc in msg_data["tool_calls"]
57
57
  ]
58
58
 
59
59
  return Message(
@@ -61,17 +61,17 @@ def deserialize_message(msg_data: dict) -> Message:
61
61
  content=msg_data["content"],
62
62
  message_id=msg_data.get("message_id"),
63
63
  tool_call_id=msg_data.get("tool_call_id"),
64
- tool_calls=tool_calls
64
+ tool_calls=tool_calls,
65
65
  )
66
66
 
67
67
 
68
68
  def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
69
69
  """
70
70
  Serialize conversation to JSON string for storage (Redis, file systems, etc.).
71
-
71
+
72
72
  Args:
73
73
  conversation: The conversation memory to serialize
74
-
74
+
75
75
  Returns:
76
76
  JSON string representation
77
77
  """
@@ -82,7 +82,7 @@ def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
82
82
  "metadata": {
83
83
  k: v.isoformat() if isinstance(v, datetime) else v
84
84
  for k, v in conversation.metadata.items()
85
- }
85
+ },
86
86
  }
87
87
  return json.dumps(data)
88
88
 
@@ -90,10 +90,10 @@ def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
90
90
  def deserialize_conversation_from_json(data: str) -> ConversationMemory:
91
91
  """
92
92
  Deserialize conversation from JSON string.
93
-
93
+
94
94
  Args:
95
95
  data: JSON string representation
96
-
96
+
97
97
  Returns:
98
98
  ConversationMemory instance
99
99
  """
@@ -114,17 +114,17 @@ def deserialize_conversation_from_json(data: str) -> ConversationMemory:
114
114
  conversation_id=parsed["conversation_id"],
115
115
  user_id=parsed.get("user_id"),
116
116
  messages=[deserialize_message(msg) for msg in parsed.get("messages", [])],
117
- metadata=metadata
117
+ metadata=metadata,
118
118
  )
119
119
 
120
120
 
121
121
  def prepare_message_list_for_db(messages: List[Message]) -> str:
122
122
  """
123
123
  Prepare a list of messages for database storage.
124
-
124
+
125
125
  Args:
126
126
  messages: List of Message objects
127
-
127
+
128
128
  Returns:
129
129
  JSON string suitable for database storage
130
130
  """
@@ -134,10 +134,10 @@ def prepare_message_list_for_db(messages: List[Message]) -> str:
134
134
  def extract_messages_from_db_row(messages_json: str) -> List[Message]:
135
135
  """
136
136
  Extract messages from database row JSON.
137
-
137
+
138
138
  Args:
139
139
  messages_json: JSON string from database
140
-
140
+
141
141
  Returns:
142
142
  List of Message objects
143
143
  """
@@ -148,10 +148,10 @@ def extract_messages_from_db_row(messages_json: str) -> List[Message]:
148
148
  def sanitize_conversation_id(conversation_id: str) -> str:
149
149
  """
150
150
  Sanitize conversation ID to ensure it's safe for storage.
151
-
151
+
152
152
  Args:
153
153
  conversation_id: Raw conversation ID
154
-
154
+
155
155
  Returns:
156
156
  Sanitized conversation ID
157
157
  """
@@ -160,14 +160,16 @@ def sanitize_conversation_id(conversation_id: str) -> str:
160
160
  return sanitized[:100] # Limit length for database compatibility
161
161
 
162
162
 
163
- def create_default_metadata(user_id: Optional[str] = None, message_count: int = 0) -> Dict[str, Any]:
163
+ def create_default_metadata(
164
+ user_id: Optional[str] = None, message_count: int = 0
165
+ ) -> Dict[str, Any]:
164
166
  """
165
167
  Create default metadata for a new conversation.
166
-
168
+
167
169
  Args:
168
170
  user_id: Optional user ID
169
171
  message_count: Initial message count
170
-
172
+
171
173
  Returns:
172
174
  Default metadata dictionary
173
175
  """
@@ -177,23 +179,23 @@ def create_default_metadata(user_id: Optional[str] = None, message_count: int =
177
179
  "updated_at": now,
178
180
  "last_activity": now,
179
181
  "total_messages": message_count,
180
- "user_id": user_id
182
+ "user_id": user_id,
181
183
  }
182
184
 
183
185
 
184
186
  def update_conversation_metadata(
185
187
  existing_metadata: Dict[str, Any],
186
188
  new_message_count: int,
187
- additional_metadata: Optional[Dict[str, Any]] = None
189
+ additional_metadata: Optional[Dict[str, Any]] = None,
188
190
  ) -> Dict[str, Any]:
189
191
  """
190
192
  Update conversation metadata with new activity.
191
-
193
+
192
194
  Args:
193
195
  existing_metadata: Current metadata
194
196
  new_message_count: Updated message count
195
197
  additional_metadata: Additional metadata to merge
196
-
198
+
197
199
  Returns:
198
200
  Updated metadata dictionary
199
201
  """
@@ -215,10 +217,10 @@ def update_conversation_metadata(
215
217
  def validate_conversation_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]:
216
218
  """
217
219
  Validate and clean conversation metadata.
218
-
220
+
219
221
  Args:
220
222
  metadata: Raw metadata dictionary
221
-
223
+
222
224
  Returns:
223
225
  Validated and cleaned metadata
224
226
  """
jaf/plugins/__init__.py CHANGED
@@ -13,16 +13,16 @@ from .manager import PluginManager
13
13
  from .decorators import plugin, tool_provider, model_provider, memory_provider
14
14
 
15
15
  __all__ = [
16
- 'JAFPlugin',
17
- 'PluginMetadata',
18
- 'PluginStatus',
19
- 'PluginRegistry',
20
- 'PluginLoader',
21
- 'PluginManager',
22
- 'get_plugin_registry',
23
- 'load_plugins_from_directory',
24
- 'plugin',
25
- 'tool_provider',
26
- 'model_provider',
27
- 'memory_provider'
16
+ "JAFPlugin",
17
+ "PluginMetadata",
18
+ "PluginStatus",
19
+ "PluginRegistry",
20
+ "PluginLoader",
21
+ "PluginManager",
22
+ "get_plugin_registry",
23
+ "load_plugins_from_directory",
24
+ "plugin",
25
+ "tool_provider",
26
+ "model_provider",
27
+ "memory_provider",
28
28
  ]