jaf-py 2.5.10__py3-none-any.whl → 2.5.12__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 +310 -210
  54. jaf/core/types.py +403 -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 +475 -283
  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.12.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.12.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.12.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
@@ -29,7 +29,7 @@ from ..types import (
29
29
  class InMemoryProvider(MemoryProvider):
30
30
  """
31
31
  In-memory implementation of MemoryProvider.
32
-
32
+
33
33
  Uses OrderedDict for LRU behavior and enforces memory limits through
34
34
  automatic eviction of oldest conversations.
35
35
  """
@@ -39,7 +39,9 @@ class InMemoryProvider(MemoryProvider):
39
39
  self._conversations: OrderedDict[str, ConversationMemory] = OrderedDict()
40
40
  self._lock = asyncio.Lock()
41
41
 
42
- print(f"[MEMORY:InMemory] Initialized with max {config.max_conversations} conversations, {config.max_messages_per_conversation} messages each")
42
+ print(
43
+ f"[MEMORY:InMemory] Initialized with max {config.max_conversations} conversations, {config.max_messages_per_conversation} messages each"
44
+ )
43
45
 
44
46
  async def _enforce_memory_limits(self) -> None:
45
47
  """Enforce conversation and message limits through LRU eviction."""
@@ -51,48 +53,51 @@ class InMemoryProvider(MemoryProvider):
51
53
  self,
52
54
  conversation_id: str,
53
55
  messages: List[Message],
54
- metadata: Optional[Dict[str, Any]] = None
56
+ metadata: Optional[Dict[str, Any]] = None,
55
57
  ) -> Result[None, MemoryStorageError]:
56
58
  """Store messages for a conversation."""
57
59
  async with self._lock:
58
60
  try:
59
61
  now = datetime.now()
60
62
 
61
- limited_messages = messages[-self.config.max_messages_per_conversation:]
63
+ limited_messages = messages[-self.config.max_messages_per_conversation :]
62
64
 
63
65
  conversation_metadata = {
64
66
  "created_at": now,
65
67
  "updated_at": now,
66
68
  "total_messages": len(limited_messages),
67
69
  "last_activity": now,
68
- **(metadata or {})
70
+ **(metadata or {}),
69
71
  }
70
72
 
71
73
  conversation = ConversationMemory(
72
74
  conversation_id=conversation_id,
73
75
  user_id=metadata.get("user_id") if metadata else None,
74
76
  messages=limited_messages,
75
- metadata=conversation_metadata
77
+ metadata=conversation_metadata,
76
78
  )
77
79
 
78
80
  self._conversations[conversation_id] = conversation
79
81
  self._conversations.move_to_end(conversation_id)
80
82
  await self._enforce_memory_limits()
81
83
 
82
- print(f"[MEMORY:InMemory] Stored {len(limited_messages)} messages for conversation {conversation_id}")
84
+ print(
85
+ f"[MEMORY:InMemory] Stored {len(limited_messages)} messages for conversation {conversation_id}"
86
+ )
83
87
  return Success(None)
84
88
 
85
89
  except Exception as e:
86
- return Failure(MemoryStorageError(
87
- operation="store_messages",
88
- provider="InMemory",
89
- message=f"Failed to store messages: {e}",
90
- cause=e
91
- ))
90
+ return Failure(
91
+ MemoryStorageError(
92
+ operation="store_messages",
93
+ provider="InMemory",
94
+ message=f"Failed to store messages: {e}",
95
+ cause=e,
96
+ )
97
+ )
92
98
 
93
99
  async def get_conversation(
94
- self,
95
- conversation_id: str
100
+ self, conversation_id: str
96
101
  ) -> Result[Optional[ConversationMemory], MemoryStorageError]:
97
102
  """Retrieve conversation history."""
98
103
  async with self._lock:
@@ -108,65 +113,74 @@ class InMemoryProvider(MemoryProvider):
108
113
  return Success(conversation)
109
114
 
110
115
  except Exception as e:
111
- return Failure(MemoryStorageError(
112
- message=f"Error retrieving conversation: {e}",
113
- provider="InMemory",
114
- operation="get_conversation",
115
- cause=e
116
- ))
116
+ return Failure(
117
+ MemoryStorageError(
118
+ message=f"Error retrieving conversation: {e}",
119
+ provider="InMemory",
120
+ operation="get_conversation",
121
+ cause=e,
122
+ )
123
+ )
117
124
 
118
125
  async def append_messages(
119
126
  self,
120
127
  conversation_id: str,
121
128
  messages: List[Message],
122
- metadata: Optional[Dict[str, Any]] = None
129
+ metadata: Optional[Dict[str, Any]] = None,
123
130
  ) -> Result[None, Union[MemoryNotFoundError, MemoryStorageError]]:
124
131
  """Append new messages to existing conversation."""
125
132
  async with self._lock:
126
133
  try:
127
134
  existing = self._conversations.get(conversation_id)
128
135
  if existing is None:
129
- return Failure(MemoryNotFoundError(
130
- message=f"Conversation {conversation_id} not found",
131
- provider="InMemory",
132
- conversation_id=conversation_id
133
- ))
136
+ return Failure(
137
+ MemoryNotFoundError(
138
+ message=f"Conversation {conversation_id} not found",
139
+ provider="InMemory",
140
+ conversation_id=conversation_id,
141
+ )
142
+ )
134
143
 
135
144
  combined_messages = list(existing.messages) + messages
136
- limited_messages = combined_messages[-self.config.max_messages_per_conversation:]
145
+ limited_messages = combined_messages[-self.config.max_messages_per_conversation :]
137
146
 
138
147
  now = datetime.now()
139
- existing.metadata.update({
140
- "updated_at": now,
141
- "last_activity": now,
142
- "total_messages": len(limited_messages),
143
- **(metadata or {})
144
- })
148
+ existing.metadata.update(
149
+ {
150
+ "updated_at": now,
151
+ "last_activity": now,
152
+ "total_messages": len(limited_messages),
153
+ **(metadata or {}),
154
+ }
155
+ )
145
156
 
146
157
  updated_conversation = ConversationMemory(
147
158
  conversation_id=conversation_id,
148
159
  user_id=existing.user_id,
149
160
  messages=limited_messages,
150
- metadata=existing.metadata
161
+ metadata=existing.metadata,
151
162
  )
152
163
 
153
164
  self._conversations[conversation_id] = updated_conversation
154
165
  self._conversations.move_to_end(conversation_id)
155
166
 
156
- print(f"[MEMORY:InMemory] Appended {len(messages)} messages to conversation {conversation_id}")
167
+ print(
168
+ f"[MEMORY:InMemory] Appended {len(messages)} messages to conversation {conversation_id}"
169
+ )
157
170
  return Success(None)
158
171
 
159
172
  except Exception as e:
160
- return Failure(MemoryStorageError(
161
- message=f"Failed to append messages: {e}",
162
- provider="InMemory",
163
- operation="append_messages",
164
- cause=e
165
- ))
173
+ return Failure(
174
+ MemoryStorageError(
175
+ message=f"Failed to append messages: {e}",
176
+ provider="InMemory",
177
+ operation="append_messages",
178
+ cause=e,
179
+ )
180
+ )
166
181
 
167
182
  async def find_conversations(
168
- self,
169
- query: MemoryQuery
183
+ self, query: MemoryQuery
170
184
  ) -> Result[List[ConversationMemory], MemoryStorageError]:
171
185
  """Search conversations by query parameters."""
172
186
  async with self._lock:
@@ -185,26 +199,28 @@ class InMemoryProvider(MemoryProvider):
185
199
  continue
186
200
  results.append(conv)
187
201
 
188
- results.sort(key=lambda c: c.metadata.get("last_activity", datetime.min), reverse=True)
202
+ results.sort(
203
+ key=lambda c: c.metadata.get("last_activity", datetime.min), reverse=True
204
+ )
189
205
 
190
206
  offset = query.offset or 0
191
207
  limit = query.limit or len(results)
192
- paginated_results = results[offset:offset + limit]
208
+ paginated_results = results[offset : offset + limit]
193
209
 
194
210
  return Success(paginated_results)
195
211
 
196
212
  except Exception as e:
197
- return Failure(MemoryStorageError(
198
- message=f"Error finding conversations: {e}",
199
- provider="InMemory",
200
- operation="find_conversations",
201
- cause=e
202
- ))
213
+ return Failure(
214
+ MemoryStorageError(
215
+ message=f"Error finding conversations: {e}",
216
+ provider="InMemory",
217
+ operation="find_conversations",
218
+ cause=e,
219
+ )
220
+ )
203
221
 
204
222
  async def get_recent_messages(
205
- self,
206
- conversation_id: str,
207
- limit: int = 50
223
+ self, conversation_id: str, limit: int = 50
208
224
  ) -> Result[List[Message], Union[MemoryNotFoundError, MemoryStorageError]]:
209
225
  """Get recent messages from a conversation."""
210
226
  result = await self.get_conversation(conversation_id)
@@ -213,18 +229,17 @@ class InMemoryProvider(MemoryProvider):
213
229
 
214
230
  conversation = result.data
215
231
  if not conversation:
216
- return Failure(MemoryNotFoundError(
217
- message=f"Conversation {conversation_id} not found",
218
- provider="InMemory",
219
- conversation_id=conversation_id
220
- ))
232
+ return Failure(
233
+ MemoryNotFoundError(
234
+ message=f"Conversation {conversation_id} not found",
235
+ provider="InMemory",
236
+ conversation_id=conversation_id,
237
+ )
238
+ )
221
239
 
222
240
  return Success(list(conversation.messages[-limit:]))
223
241
 
224
- async def delete_conversation(
225
- self,
226
- conversation_id: str
227
- ) -> Result[bool, MemoryStorageError]:
242
+ async def delete_conversation(self, conversation_id: str) -> Result[bool, MemoryStorageError]:
228
243
  """Delete conversation and return True if it existed."""
229
244
  async with self._lock:
230
245
  try:
@@ -233,84 +248,91 @@ class InMemoryProvider(MemoryProvider):
233
248
  del self._conversations[conversation_id]
234
249
  return Success(existed)
235
250
  except Exception as e:
236
- return Failure(MemoryStorageError(
237
- message=f"Error deleting conversation: {e}",
238
- provider="InMemory",
239
- operation="delete_conversation",
240
- cause=e
241
- ))
251
+ return Failure(
252
+ MemoryStorageError(
253
+ message=f"Error deleting conversation: {e}",
254
+ provider="InMemory",
255
+ operation="delete_conversation",
256
+ cause=e,
257
+ )
258
+ )
242
259
 
243
- async def clear_user_conversations(
244
- self,
245
- user_id: str
246
- ) -> Result[int, MemoryStorageError]:
260
+ async def clear_user_conversations(self, user_id: str) -> Result[int, MemoryStorageError]:
247
261
  """Clear all conversations for a user and return count deleted."""
248
262
  async with self._lock:
249
263
  try:
250
264
  to_delete = [
251
- conv_id for conv_id, conv in self._conversations.items()
265
+ conv_id
266
+ for conv_id, conv in self._conversations.items()
252
267
  if conv.user_id == user_id
253
268
  ]
254
269
  for conv_id in to_delete:
255
270
  del self._conversations[conv_id]
256
271
  return Success(len(to_delete))
257
272
  except Exception as e:
258
- return Failure(MemoryStorageError(
259
- message=f"Error clearing conversations: {e}",
260
- provider="InMemory",
261
- operation="clear_user_conversations",
262
- cause=e
263
- ))
273
+ return Failure(
274
+ MemoryStorageError(
275
+ message=f"Error clearing conversations: {e}",
276
+ provider="InMemory",
277
+ operation="clear_user_conversations",
278
+ cause=e,
279
+ )
280
+ )
264
281
 
265
282
  async def get_stats(
266
- self,
267
- user_id: Optional[str] = None
283
+ self, user_id: Optional[str] = None
268
284
  ) -> Result[Dict[str, Any], MemoryStorageError]:
269
285
  """Get conversation statistics."""
270
286
  async with self._lock:
271
287
  try:
272
- convs = [c for c in self._conversations.values() if not user_id or c.user_id == user_id]
288
+ convs = [
289
+ c for c in self._conversations.values() if not user_id or c.user_id == user_id
290
+ ]
273
291
  total_messages = sum(len(c.messages) for c in convs)
274
- created_dates = [c.metadata["created_at"] for c in convs if "created_at" in c.metadata]
275
-
276
- return Success({
277
- "total_conversations": len(convs),
278
- "total_messages": total_messages,
279
- "oldest_conversation": min(created_dates) if created_dates else None,
280
- "newest_conversation": max(created_dates) if created_dates else None
281
- })
292
+ created_dates = [
293
+ c.metadata["created_at"] for c in convs if "created_at" in c.metadata
294
+ ]
295
+
296
+ return Success(
297
+ {
298
+ "total_conversations": len(convs),
299
+ "total_messages": total_messages,
300
+ "oldest_conversation": min(created_dates) if created_dates else None,
301
+ "newest_conversation": max(created_dates) if created_dates else None,
302
+ }
303
+ )
282
304
  except Exception as e:
283
- return Failure(MemoryStorageError(
284
- message=f"Error getting stats: {e}",
285
- provider="InMemory",
286
- operation="get_stats",
287
- cause=e
288
- ))
305
+ return Failure(
306
+ MemoryStorageError(
307
+ message=f"Error getting stats: {e}",
308
+ provider="InMemory",
309
+ operation="get_stats",
310
+ cause=e,
311
+ )
312
+ )
289
313
 
290
314
  async def health_check(self) -> Result[Dict[str, Any], MemoryConnectionError]:
291
315
  """Check provider health and return status information."""
292
316
  start_time = datetime.now()
293
317
  try:
294
318
  latency_ms = (datetime.now() - start_time).total_seconds() * 1000
295
- return Success({
296
- "healthy": True,
297
- "latency_ms": latency_ms,
298
- "provider": "InMemory",
299
- "details": {
300
- "conversations_count": len(self._conversations)
319
+ return Success(
320
+ {
321
+ "healthy": True,
322
+ "latency_ms": latency_ms,
323
+ "provider": "InMemory",
324
+ "details": {"conversations_count": len(self._conversations)},
301
325
  }
302
- })
326
+ )
303
327
  except Exception as e:
304
- return Failure(MemoryConnectionError(
305
- message=f"Health check failed: {e}",
306
- provider="InMemory",
307
- cause=e
308
- ))
328
+ return Failure(
329
+ MemoryConnectionError(
330
+ message=f"Health check failed: {e}", provider="InMemory", cause=e
331
+ )
332
+ )
309
333
 
310
334
  async def truncate_conversation_after(
311
- self,
312
- conversation_id: str,
313
- message_id: MessageId
335
+ self, conversation_id: str, message_id: MessageId
314
336
  ) -> Result[int, Union[MemoryNotFoundError, MemoryStorageError]]:
315
337
  """
316
338
  Truncate conversation after (and including) the specified message ID.
@@ -320,24 +342,26 @@ class InMemoryProvider(MemoryProvider):
320
342
  try:
321
343
  conversation = self._conversations.get(conversation_id)
322
344
  if conversation is None:
323
- return Failure(MemoryNotFoundError(
324
- message=f"Conversation {conversation_id} not found",
325
- provider="InMemory",
326
- conversation_id=conversation_id
327
- ))
345
+ return Failure(
346
+ MemoryNotFoundError(
347
+ message=f"Conversation {conversation_id} not found",
348
+ provider="InMemory",
349
+ conversation_id=conversation_id,
350
+ )
351
+ )
328
352
 
329
353
  messages = list(conversation.messages)
330
354
  truncate_index = find_message_index(messages, message_id)
331
-
355
+
332
356
  if truncate_index is None:
333
357
  # Message not found, nothing to truncate
334
358
  return Success(0)
335
-
359
+
336
360
  # Truncate messages from the found index onwards
337
361
  original_count = len(messages)
338
362
  truncated_messages = messages[:truncate_index]
339
363
  removed_count = original_count - len(truncated_messages)
340
-
364
+
341
365
  # Update conversation with truncated messages
342
366
  now = datetime.now()
343
367
  updated_metadata = {
@@ -347,34 +371,36 @@ class InMemoryProvider(MemoryProvider):
347
371
  "total_messages": len(truncated_messages),
348
372
  "regeneration_truncated": True,
349
373
  "truncated_at": now.isoformat(),
350
- "messages_removed": removed_count
374
+ "messages_removed": removed_count,
351
375
  }
352
-
376
+
353
377
  updated_conversation = ConversationMemory(
354
378
  conversation_id=conversation_id,
355
379
  user_id=conversation.user_id,
356
380
  messages=truncated_messages,
357
- metadata=updated_metadata
381
+ metadata=updated_metadata,
358
382
  )
359
-
383
+
360
384
  self._conversations[conversation_id] = updated_conversation
361
385
  self._conversations.move_to_end(conversation_id)
362
-
363
- print(f"[MEMORY:InMemory] Truncated conversation {conversation_id}: removed {removed_count} messages after message {message_id}")
386
+
387
+ print(
388
+ f"[MEMORY:InMemory] Truncated conversation {conversation_id}: removed {removed_count} messages after message {message_id}"
389
+ )
364
390
  return Success(removed_count)
365
-
391
+
366
392
  except Exception as e:
367
- return Failure(MemoryStorageError(
368
- message=f"Failed to truncate conversation: {e}",
369
- provider="InMemory",
370
- operation="truncate_conversation_after",
371
- cause=e
372
- ))
393
+ return Failure(
394
+ MemoryStorageError(
395
+ message=f"Failed to truncate conversation: {e}",
396
+ provider="InMemory",
397
+ operation="truncate_conversation_after",
398
+ cause=e,
399
+ )
400
+ )
373
401
 
374
402
  async def get_conversation_until_message(
375
- self,
376
- conversation_id: str,
377
- message_id: MessageId
403
+ self, conversation_id: str, message_id: MessageId
378
404
  ) -> Result[Optional[ConversationMemory], Union[MemoryNotFoundError, MemoryStorageError]]:
379
405
  """
380
406
  Get conversation history up to (but not including) the specified message ID.
@@ -385,18 +411,20 @@ class InMemoryProvider(MemoryProvider):
385
411
  conversation = self._conversations.get(conversation_id)
386
412
  if conversation is None:
387
413
  return Success(None)
388
-
414
+
389
415
  messages = list(conversation.messages)
390
416
  until_index = find_message_index(messages, message_id)
391
-
417
+
392
418
  if until_index is None:
393
419
  # Message not found, return None as lightweight indicator
394
- print(f"[MEMORY:InMemory] Message {message_id} not found in conversation {conversation_id}")
420
+ print(
421
+ f"[MEMORY:InMemory] Message {message_id} not found in conversation {conversation_id}"
422
+ )
395
423
  return Success(None)
396
-
424
+
397
425
  # Return conversation up to (but not including) the specified message
398
426
  truncated_messages = messages[:until_index]
399
-
427
+
400
428
  # Create a copy of the conversation with truncated messages
401
429
  truncated_conversation = ConversationMemory(
402
430
  conversation_id=conversation.conversation_id,
@@ -407,26 +435,27 @@ class InMemoryProvider(MemoryProvider):
407
435
  "truncated_for_regeneration": True,
408
436
  "truncated_until_message": str(message_id),
409
437
  "original_message_count": len(messages),
410
- "truncated_message_count": len(truncated_messages)
411
- }
438
+ "truncated_message_count": len(truncated_messages),
439
+ },
440
+ )
441
+
442
+ print(
443
+ f"[MEMORY:InMemory] Retrieved conversation {conversation_id} until message {message_id}: {len(truncated_messages)} messages (found at index {until_index})"
412
444
  )
413
-
414
- print(f"[MEMORY:InMemory] Retrieved conversation {conversation_id} until message {message_id}: {len(truncated_messages)} messages (found at index {until_index})")
415
445
  return Success(truncated_conversation)
416
-
446
+
417
447
  except Exception as e:
418
- return Failure(MemoryStorageError(
419
- message=f"Failed to get conversation until message: {e}",
420
- provider="InMemory",
421
- operation="get_conversation_until_message",
422
- cause=e
423
- ))
448
+ return Failure(
449
+ MemoryStorageError(
450
+ message=f"Failed to get conversation until message: {e}",
451
+ provider="InMemory",
452
+ operation="get_conversation_until_message",
453
+ cause=e,
454
+ )
455
+ )
424
456
 
425
457
  async def mark_regeneration_point(
426
- self,
427
- conversation_id: str,
428
- message_id: MessageId,
429
- regeneration_metadata: Dict[str, Any]
458
+ self, conversation_id: str, message_id: MessageId, regeneration_metadata: Dict[str, Any]
430
459
  ) -> Result[None, Union[MemoryNotFoundError, MemoryStorageError]]:
431
460
  """
432
461
  Mark a regeneration point in the conversation for audit purposes.
@@ -435,50 +464,56 @@ class InMemoryProvider(MemoryProvider):
435
464
  try:
436
465
  conversation = self._conversations.get(conversation_id)
437
466
  if conversation is None:
438
- return Failure(MemoryNotFoundError(
439
- message=f"Conversation {conversation_id} not found",
440
- provider="InMemory",
441
- conversation_id=conversation_id
442
- ))
443
-
467
+ return Failure(
468
+ MemoryNotFoundError(
469
+ message=f"Conversation {conversation_id} not found",
470
+ provider="InMemory",
471
+ conversation_id=conversation_id,
472
+ )
473
+ )
474
+
444
475
  # Add regeneration point to metadata
445
476
  regeneration_points = conversation.metadata.get("regeneration_points", [])
446
477
  regeneration_point = {
447
478
  "message_id": str(message_id),
448
479
  "timestamp": datetime.now().isoformat(),
449
- **regeneration_metadata
480
+ **regeneration_metadata,
450
481
  }
451
482
  regeneration_points.append(regeneration_point)
452
-
483
+
453
484
  # Update conversation metadata
454
485
  updated_metadata = {
455
486
  **conversation.metadata,
456
487
  "regeneration_points": regeneration_points,
457
488
  "last_regeneration": regeneration_point,
458
489
  "updated_at": datetime.now(),
459
- "regeneration_count": len(regeneration_points)
490
+ "regeneration_count": len(regeneration_points),
460
491
  }
461
-
492
+
462
493
  updated_conversation = ConversationMemory(
463
494
  conversation_id=conversation.conversation_id,
464
495
  user_id=conversation.user_id,
465
496
  messages=conversation.messages,
466
- metadata=updated_metadata
497
+ metadata=updated_metadata,
467
498
  )
468
-
499
+
469
500
  self._conversations[conversation_id] = updated_conversation
470
501
  self._conversations.move_to_end(conversation_id)
471
-
472
- print(f"[MEMORY:InMemory] Marked regeneration point for conversation {conversation_id} at message {message_id}")
502
+
503
+ print(
504
+ f"[MEMORY:InMemory] Marked regeneration point for conversation {conversation_id} at message {message_id}"
505
+ )
473
506
  return Success(None)
474
-
507
+
475
508
  except Exception as e:
476
- return Failure(MemoryStorageError(
477
- message=f"Failed to mark regeneration point: {e}",
478
- provider="InMemory",
479
- operation="mark_regeneration_point",
480
- cause=e
481
- ))
509
+ return Failure(
510
+ MemoryStorageError(
511
+ message=f"Failed to mark regeneration point: {e}",
512
+ provider="InMemory",
513
+ operation="mark_regeneration_point",
514
+ cause=e,
515
+ )
516
+ )
482
517
 
483
518
  async def close(self) -> Result[None, MemoryConnectionError]:
484
519
  """Close/cleanup the provider."""
@@ -487,13 +522,14 @@ class InMemoryProvider(MemoryProvider):
487
522
  print("[MEMORY:InMemory] Closed provider, cleared all conversations")
488
523
  return Success(None)
489
524
 
525
+
490
526
  def create_in_memory_provider(config: Optional[InMemoryConfig] = None) -> InMemoryProvider:
491
527
  """
492
528
  Factory function to create an in-memory provider instance.
493
-
529
+
494
530
  Args:
495
531
  config: Configuration for the provider. If None, uses defaults.
496
-
532
+
497
533
  Returns:
498
534
  Configured InMemoryProvider instance.
499
535
  """