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
jaf/core/regeneration.py CHANGED
@@ -10,27 +10,34 @@ from dataclasses import replace
10
10
  from typing import Any, TypeVar, Optional
11
11
 
12
12
  from .types import (
13
- RunState, RunConfig, RunResult,
14
- RegenerationRequest, RegenerationContext,
15
- MessageId, Message, ErrorOutcome, ModelBehaviorError,
16
- find_message_index, truncate_messages_after, get_message_by_id,
17
- generate_run_id, generate_trace_id
13
+ RunState,
14
+ RunConfig,
15
+ RunResult,
16
+ RegenerationRequest,
17
+ RegenerationContext,
18
+ MessageId,
19
+ Message,
20
+ ErrorOutcome,
21
+ ModelBehaviorError,
22
+ find_message_index,
23
+ truncate_messages_after,
24
+ get_message_by_id,
25
+ generate_run_id,
26
+ generate_trace_id,
18
27
  )
19
28
  from .engine import run as engine_run
20
29
  from ..memory.types import Success, Failure
21
30
 
22
- Ctx = TypeVar('Ctx')
23
- Out = TypeVar('Out')
31
+ Ctx = TypeVar("Ctx")
32
+ Out = TypeVar("Out")
33
+
24
34
 
25
35
  async def regenerate_conversation(
26
- regeneration_request: RegenerationRequest,
27
- config: RunConfig[Ctx],
28
- context: Ctx,
29
- agent_name: str
36
+ regeneration_request: RegenerationRequest, config: RunConfig[Ctx], context: Ctx, agent_name: str
30
37
  ) -> RunResult[Out]:
31
38
  """
32
39
  Regenerate a conversation from a specific message ID.
33
-
40
+
34
41
  This function:
35
42
  1. Loads the full conversation from memory
36
43
  2. Finds the message to regenerate from
@@ -38,13 +45,13 @@ async def regenerate_conversation(
38
45
  4. Creates a new RunState with truncated conversation
39
46
  5. Executes the regeneration through the normal engine flow
40
47
  6. Updates memory with the new conversation path
41
-
48
+
42
49
  Args:
43
50
  regeneration_request: The regeneration request containing conversation_id and message_id
44
51
  config: The run configuration
45
52
  context: The context for the regeneration
46
53
  agent_name: The name of the agent to use for regeneration
47
-
54
+
48
55
  Returns:
49
56
  RunResult with the regenerated conversation outcome
50
57
  """
@@ -56,15 +63,19 @@ async def regenerate_conversation(
56
63
  messages=[],
57
64
  current_agent_name=agent_name,
58
65
  context=context,
59
- turn_count=0
66
+ turn_count=0,
67
+ ),
68
+ outcome=ErrorOutcome(
69
+ error=ModelBehaviorError(
70
+ detail="Regeneration requires memory provider and conversation_id to be configured"
71
+ )
60
72
  ),
61
- outcome=ErrorOutcome(error=ModelBehaviorError(
62
- detail="Regeneration requires memory provider and conversation_id to be configured"
63
- ))
64
73
  )
65
74
 
66
75
  # Load the conversation from memory
67
- conversation_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
76
+ conversation_result = await config.memory.provider.get_conversation(
77
+ regeneration_request.conversation_id
78
+ )
68
79
  if isinstance(conversation_result, Failure):
69
80
  return RunResult(
70
81
  final_state=RunState(
@@ -73,11 +84,13 @@ async def regenerate_conversation(
73
84
  messages=[],
74
85
  current_agent_name=agent_name,
75
86
  context=context,
76
- turn_count=0
87
+ turn_count=0,
88
+ ),
89
+ outcome=ErrorOutcome(
90
+ error=ModelBehaviorError(
91
+ detail=f"Failed to load conversation: {conversation_result.error}"
92
+ )
77
93
  ),
78
- outcome=ErrorOutcome(error=ModelBehaviorError(
79
- detail=f"Failed to load conversation: {conversation_result.error}"
80
- ))
81
94
  )
82
95
 
83
96
  conversation_memory = conversation_result.data
@@ -89,16 +102,18 @@ async def regenerate_conversation(
89
102
  messages=[],
90
103
  current_agent_name=agent_name,
91
104
  context=context,
92
- turn_count=0
105
+ turn_count=0,
106
+ ),
107
+ outcome=ErrorOutcome(
108
+ error=ModelBehaviorError(
109
+ detail=f"Conversation {regeneration_request.conversation_id} not found"
110
+ )
93
111
  ),
94
- outcome=ErrorOutcome(error=ModelBehaviorError(
95
- detail=f"Conversation {regeneration_request.conversation_id} not found"
96
- ))
97
112
  )
98
113
 
99
114
  # Convert tuple back to list for processing
100
115
  original_messages = list(conversation_memory.messages)
101
-
116
+
102
117
  # Find the message to regenerate from
103
118
  regenerate_message = get_message_by_id(original_messages, regeneration_request.message_id)
104
119
  if not regenerate_message:
@@ -109,11 +124,19 @@ async def regenerate_conversation(
109
124
  messages=original_messages,
110
125
  current_agent_name=agent_name,
111
126
  context=context,
112
- turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
127
+ turn_count=len(
128
+ [
129
+ m
130
+ for m in original_messages
131
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
132
+ ]
133
+ ),
134
+ ),
135
+ outcome=ErrorOutcome(
136
+ error=ModelBehaviorError(
137
+ detail=f"Message {regeneration_request.message_id} not found in conversation"
138
+ )
113
139
  ),
114
- outcome=ErrorOutcome(error=ModelBehaviorError(
115
- detail=f"Message {regeneration_request.message_id} not found in conversation"
116
- ))
117
140
  )
118
141
 
119
142
  # Get the index of the message to regenerate
@@ -126,77 +149,93 @@ async def regenerate_conversation(
126
149
  messages=original_messages,
127
150
  current_agent_name=agent_name,
128
151
  context=context,
129
- turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
152
+ turn_count=len(
153
+ [
154
+ m
155
+ for m in original_messages
156
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
157
+ ]
158
+ ),
159
+ ),
160
+ outcome=ErrorOutcome(
161
+ error=ModelBehaviorError(
162
+ detail=f"Failed to find index for message {regeneration_request.message_id}"
163
+ )
130
164
  ),
131
- outcome=ErrorOutcome(error=ModelBehaviorError(
132
- detail=f"Failed to find index for message {regeneration_request.message_id}"
133
- ))
134
165
  )
135
166
 
136
167
  def determine_regeneration_type(messages, regenerate_index, context):
137
168
  """Determine if this is pure regeneration or edit scenario."""
138
169
  if context and context.get("replace_user_message"):
139
170
  return "edit"
140
-
171
+
141
172
  regenerate_message = messages[regenerate_index]
142
- if regenerate_message.role in ['assistant', 'ASSISTANT']:
173
+ if regenerate_message.role in ["assistant", "ASSISTANT"]:
143
174
  for i in range(regenerate_index - 1, -1, -1):
144
- if messages[i].role in ['user', 'USER']:
175
+ if messages[i].role in ["user", "USER"]:
145
176
  return "pure"
146
177
  return "edit"
147
178
 
148
179
  # Determine regeneration type
149
- regen_type = determine_regeneration_type(original_messages, regenerate_index, regeneration_request.context or {})
180
+ regen_type = determine_regeneration_type(
181
+ original_messages, regenerate_index, regeneration_request.context or {}
182
+ )
150
183
  print(f"[JAF:REGENERATION] Detected regeneration type: {regen_type}")
151
184
 
152
185
  if regen_type == "pure":
153
186
  # For pure regeneration, find the user message that started this conversation turn
154
187
  user_message_index = None
155
188
  for i in range(regenerate_index - 1, -1, -1):
156
- if original_messages[i].role in ['user', 'USER']:
189
+ if original_messages[i].role in ["user", "USER"]:
157
190
  user_message_index = i
158
191
  break
159
-
192
+
160
193
  if user_message_index is not None:
161
194
  # Truncate AFTER the user message (keeps user message, removes tool calls/outputs)
162
- truncated_messages = original_messages[:user_message_index + 1]
163
- print(f"[JAF:REGENERATION] Pure regeneration: truncated to user message at index {user_message_index}")
195
+ truncated_messages = original_messages[: user_message_index + 1]
196
+ print(
197
+ f"[JAF:REGENERATION] Pure regeneration: truncated to user message at index {user_message_index}"
198
+ )
164
199
  else:
165
200
  truncated_messages = original_messages[:regenerate_index]
166
201
  print(f"[JAF:REGENERATION] Pure regeneration fallback: no user message found")
167
202
  else:
168
203
  # Edit regeneration: truncate at the specified point and add replacement query
169
204
  truncated_messages = original_messages[:regenerate_index]
170
-
171
- if (regeneration_request.context and
172
- regeneration_request.context.get("replace_user_message")):
173
-
205
+
206
+ if regeneration_request.context and regeneration_request.context.get(
207
+ "replace_user_message"
208
+ ):
174
209
  from .types import ContentRole, Message
210
+
175
211
  replacement_user_message = Message(
176
212
  role=ContentRole.USER,
177
- content=regeneration_request.context.get("replace_user_message")
213
+ content=regeneration_request.context.get("replace_user_message"),
178
214
  )
179
215
  truncated_messages.append(replacement_user_message)
180
- print(f"[JAF:REGENERATION] Edit regeneration: replaced user query with: {regeneration_request.context.get('replace_user_message')}")
181
-
216
+ print(
217
+ f"[JAF:REGENERATION] Edit regeneration: replaced user query with: {regeneration_request.context.get('replace_user_message')}"
218
+ )
219
+
182
220
  print(f"[JAF:REGENERATION] Truncated conversation to {len(truncated_messages)} messages")
183
-
184
221
 
185
- print(f"[JAF:REGENERATION] About to store {len(truncated_messages)} truncated messages to memory")
186
-
222
+ print(
223
+ f"[JAF:REGENERATION] About to store {len(truncated_messages)} truncated messages to memory"
224
+ )
225
+
187
226
  def serialize_metadata(metadata):
188
227
  import json
189
228
  import datetime
190
-
229
+
191
230
  def json_serializer(obj):
192
231
  if isinstance(obj, datetime.datetime):
193
232
  return obj.isoformat()
194
233
  elif isinstance(obj, datetime.date):
195
234
  return obj.isoformat()
196
- elif hasattr(obj, '__dict__'):
235
+ elif hasattr(obj, "__dict__"):
197
236
  return obj.__dict__
198
237
  return str(obj)
199
-
238
+
200
239
  try:
201
240
  json_str = json.dumps(metadata, default=json_serializer)
202
241
  return json.loads(json_str)
@@ -207,24 +246,36 @@ async def regenerate_conversation(
207
246
  "regeneration_point": str(regeneration_request.message_id),
208
247
  "original_message_count": len(original_messages),
209
248
  "truncated_at_index": regenerate_index,
210
- "turn_count": len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
249
+ "turn_count": len(
250
+ [
251
+ m
252
+ for m in truncated_messages
253
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
254
+ ]
255
+ ),
211
256
  }
212
-
213
- metadata = serialize_metadata({
214
- **conversation_memory.metadata,
215
- "regeneration_truncated": True,
216
- "regeneration_point": str(regeneration_request.message_id),
217
- "original_message_count": len(original_messages),
218
- "truncated_at_index": regenerate_index,
219
- "turn_count": len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
220
- })
221
-
257
+
258
+ metadata = serialize_metadata(
259
+ {
260
+ **conversation_memory.metadata,
261
+ "regeneration_truncated": True,
262
+ "regeneration_point": str(regeneration_request.message_id),
263
+ "original_message_count": len(original_messages),
264
+ "truncated_at_index": regenerate_index,
265
+ "turn_count": len(
266
+ [
267
+ m
268
+ for m in truncated_messages
269
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
270
+ ]
271
+ ),
272
+ }
273
+ )
274
+
222
275
  store_result = await config.memory.provider.store_messages(
223
- regeneration_request.conversation_id,
224
- truncated_messages,
225
- metadata
276
+ regeneration_request.conversation_id, truncated_messages, metadata
226
277
  )
227
-
278
+
228
279
  print(f"[JAF:REGENERATION] Store result type: {type(store_result)}")
229
280
  if isinstance(store_result, Failure):
230
281
  print(f"[JAF:REGENERATION] Store failed with error: {store_result.error}")
@@ -235,11 +286,19 @@ async def regenerate_conversation(
235
286
  messages=original_messages,
236
287
  current_agent_name=agent_name,
237
288
  context=context,
238
- turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
289
+ turn_count=len(
290
+ [
291
+ m
292
+ for m in original_messages
293
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
294
+ ]
295
+ ),
296
+ ),
297
+ outcome=ErrorOutcome(
298
+ error=ModelBehaviorError(
299
+ detail=f"Failed to store truncated conversation: {store_result.error}"
300
+ )
239
301
  ),
240
- outcome=ErrorOutcome(error=ModelBehaviorError(
241
- detail=f"Failed to store truncated conversation: {store_result.error}"
242
- ))
243
302
  )
244
303
  else:
245
304
  print(f"[JAF:REGENERATION] Store successful, proceeding to engine execution")
@@ -250,12 +309,18 @@ async def regenerate_conversation(
250
309
  truncated_at_index=regenerate_index,
251
310
  regenerated_message_id=regeneration_request.message_id,
252
311
  regeneration_id=f"regen_{int(time.time() * 1000)}_{regeneration_request.message_id}",
253
- timestamp=int(time.time() * 1000)
312
+ timestamp=int(time.time() * 1000),
254
313
  )
255
314
 
256
315
  # Calculate turn count from truncated messages
257
- truncated_turn_count = len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
258
-
316
+ truncated_turn_count = len(
317
+ [
318
+ m
319
+ for m in truncated_messages
320
+ if (m.role.value if hasattr(m.role, "value") else m.role) == "assistant"
321
+ ]
322
+ )
323
+
259
324
  final_context = context
260
325
  print(f"[JAF:REGENERATION] Using provided context: {type(context).__name__}")
261
326
 
@@ -263,50 +328,66 @@ async def regenerate_conversation(
263
328
  initial_state = RunState(
264
329
  run_id=generate_run_id(),
265
330
  trace_id=generate_trace_id(),
266
- messages=[],
331
+ messages=[],
267
332
  current_agent_name=agent_name,
268
333
  context=final_context,
269
334
  turn_count=truncated_turn_count,
270
- approvals={} # Reset approvals for regeneration
335
+ approvals={}, # Reset approvals for regeneration
271
336
  )
272
337
 
273
- print(f"[JAF:REGENERATION] Starting regeneration from message {regeneration_request.message_id}")
274
- print(f"[JAF:REGENERATION] Original messages: {len(original_messages)}, Truncated to: {len(truncated_messages)}")
338
+ print(
339
+ f"[JAF:REGENERATION] Starting regeneration from message {regeneration_request.message_id}"
340
+ )
341
+ print(
342
+ f"[JAF:REGENERATION] Original messages: {len(original_messages)}, Truncated to: {len(truncated_messages)}"
343
+ )
275
344
  print(f"[JAF:REGENERATION] Regeneration context: {regeneration_context}")
276
345
 
277
346
  # Create a modified config for regeneration that ensures memory storage
278
347
  regeneration_config = replace(
279
348
  config,
280
349
  conversation_id=regeneration_request.conversation_id,
281
- memory=replace(config.memory, auto_store=True, store_on_completion=True) if config.memory else None
350
+ memory=replace(config.memory, auto_store=True, store_on_completion=True)
351
+ if config.memory
352
+ else None,
282
353
  )
283
354
 
284
355
  # Execute the regeneration through the normal engine flow
285
356
  print(f"[JAF:REGENERATION] About to execute engine with {len(truncated_messages)} messages")
286
- print(f"[JAF:REGENERATION] Final message: {truncated_messages[-1] if truncated_messages else 'None'}")
287
-
357
+ print(
358
+ f"[JAF:REGENERATION] Final message: {truncated_messages[-1] if truncated_messages else 'None'}"
359
+ )
360
+
288
361
  result = await engine_run(initial_state, regeneration_config)
289
362
 
290
363
  print(f"[JAF:REGENERATION] Regeneration completed with status: {result.outcome.status}")
291
- if hasattr(result, 'final_state') and hasattr(result.final_state, 'messages'):
364
+ if hasattr(result, "final_state") and hasattr(result.final_state, "messages"):
292
365
  print(f"[JAF:REGENERATION] Final state has {len(result.final_state.messages)} messages")
293
- assistant_msgs = [m for m in result.final_state.messages if m.role in ['assistant', 'ASSISTANT']]
366
+ assistant_msgs = [
367
+ m for m in result.final_state.messages if m.role in ["assistant", "ASSISTANT"]
368
+ ]
294
369
  print(f"[JAF:REGENERATION] Found {len(assistant_msgs)} assistant messages in result")
295
-
370
+
296
371
  # After successful regeneration, mark the regeneration point and preserve metadata
297
- if result.outcome.status == 'completed' and config.memory and config.memory.provider:
372
+ if result.outcome.status == "completed" and config.memory and config.memory.provider:
298
373
  try:
299
374
  print(f"[JAF:REGENERATION] Marking regeneration point after successful regeneration")
300
-
375
+
301
376
  # Get the current conversation to preserve regeneration metadata
302
- current_conv_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
303
- print(f"[JAF:REGENERATION] Retrieved conversation for preservation: {hasattr(current_conv_result, 'data') and current_conv_result.data is not None}")
304
-
305
- if hasattr(current_conv_result, 'data') and current_conv_result.data:
377
+ current_conv_result = await config.memory.provider.get_conversation(
378
+ regeneration_request.conversation_id
379
+ )
380
+ print(
381
+ f"[JAF:REGENERATION] Retrieved conversation for preservation: {hasattr(current_conv_result, 'data') and current_conv_result.data is not None}"
382
+ )
383
+
384
+ if hasattr(current_conv_result, "data") and current_conv_result.data:
306
385
  current_metadata = current_conv_result.data.metadata
307
- regeneration_points = current_metadata.get('regeneration_points', [])
308
- print(f"[JAF:REGENERATION] Found {len(regeneration_points)} regeneration points in metadata before marking")
309
-
386
+ regeneration_points = current_metadata.get("regeneration_points", [])
387
+ print(
388
+ f"[JAF:REGENERATION] Found {len(regeneration_points)} regeneration points in metadata before marking"
389
+ )
390
+
310
391
  # Mark the regeneration point by calling the provider method directly
311
392
  mark_result = await config.memory.provider.mark_regeneration_point(
312
393
  regeneration_request.conversation_id,
@@ -315,78 +396,88 @@ async def regenerate_conversation(
315
396
  "regeneration_id": regeneration_context.regeneration_id,
316
397
  "original_message_count": len(original_messages),
317
398
  "truncated_at_index": regenerate_index,
318
- "timestamp": regeneration_context.timestamp
319
- }
399
+ "timestamp": regeneration_context.timestamp,
400
+ },
320
401
  )
321
-
402
+
322
403
  if isinstance(mark_result, Failure):
323
- print(f"[JAF:REGENERATION] Warning: Failed to mark regeneration point: {mark_result.error}")
404
+ print(
405
+ f"[JAF:REGENERATION] Warning: Failed to mark regeneration point: {mark_result.error}"
406
+ )
324
407
  else:
325
408
  print(f"[JAF:REGENERATION] Successfully marked regeneration point")
326
-
409
+
327
410
  # Get the updated conversation with the new regeneration point
328
- updated_conv_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
329
- if hasattr(updated_conv_result, 'data') and updated_conv_result.data:
411
+ updated_conv_result = await config.memory.provider.get_conversation(
412
+ regeneration_request.conversation_id
413
+ )
414
+ if hasattr(updated_conv_result, "data") and updated_conv_result.data:
330
415
  updated_metadata = updated_conv_result.data.metadata
331
- updated_regeneration_points = updated_metadata.get('regeneration_points', [])
332
- print(f"[JAF:REGENERATION] Found {len(updated_regeneration_points)} regeneration points after marking")
333
-
416
+ updated_regeneration_points = updated_metadata.get("regeneration_points", [])
417
+ print(
418
+ f"[JAF:REGENERATION] Found {len(updated_regeneration_points)} regeneration points after marking"
419
+ )
420
+
334
421
  # Ensure final metadata includes the regeneration points
335
422
  final_metadata = {
336
423
  **updated_metadata,
337
- 'regeneration_points': updated_regeneration_points,
338
- 'regeneration_count': len(updated_regeneration_points),
339
- 'last_regeneration': updated_regeneration_points[-1] if updated_regeneration_points else None,
340
- 'regeneration_preserved': True,
341
- 'final_preservation_timestamp': int(time.time() * 1000)
424
+ "regeneration_points": updated_regeneration_points,
425
+ "regeneration_count": len(updated_regeneration_points),
426
+ "last_regeneration": updated_regeneration_points[-1]
427
+ if updated_regeneration_points
428
+ else None,
429
+ "regeneration_preserved": True,
430
+ "final_preservation_timestamp": int(time.time() * 1000),
342
431
  }
343
-
432
+
344
433
  # Store the final conversation with preserved regeneration metadata
345
434
  await config.memory.provider.store_messages(
346
435
  regeneration_request.conversation_id,
347
436
  result.final_state.messages,
348
- final_metadata
437
+ final_metadata,
438
+ )
439
+ print(
440
+ f"[JAF:REGENERATION] Final preservation completed with {len(updated_regeneration_points)} regeneration points"
349
441
  )
350
- print(f"[JAF:REGENERATION] Final preservation completed with {len(updated_regeneration_points)} regeneration points")
351
442
  else:
352
443
  print(f"[JAF:REGENERATION] No conversation data found for preservation")
353
-
444
+
354
445
  except Exception as e:
355
446
  print(f"[JAF:REGENERATION] Warning: Failed to preserve regeneration points: {e}")
356
447
  import traceback
448
+
357
449
  traceback.print_exc()
358
-
450
+
359
451
  return result
360
452
 
361
453
 
362
- async def get_regeneration_points(
363
- conversation_id: str,
364
- config: RunConfig[Ctx]
365
- ) -> Optional[list]:
454
+ async def get_regeneration_points(conversation_id: str, config: RunConfig[Ctx]) -> Optional[list]:
366
455
  """
367
456
  Get all regeneration points for a conversation.
368
-
457
+
369
458
  Args:
370
459
  conversation_id: The conversation ID
371
460
  config: The run configuration
372
-
461
+
373
462
  Returns:
374
463
  List of regeneration points or None if not available
375
464
  """
376
465
  if not config.memory or not config.memory.provider:
377
466
  return None
378
-
467
+
379
468
  try:
380
469
  conversation_result = await config.memory.provider.get_conversation(conversation_id)
381
- if hasattr(conversation_result, 'data') and conversation_result.data:
470
+ if hasattr(conversation_result, "data") and conversation_result.data:
382
471
  metadata = conversation_result.data.metadata
383
- regeneration_points = metadata.get('regeneration_points', [])
384
- print(f"[JAF:REGENERATION] Retrieved {len(regeneration_points)} regeneration points for {conversation_id}")
472
+ regeneration_points = metadata.get("regeneration_points", [])
473
+ print(
474
+ f"[JAF:REGENERATION] Retrieved {len(regeneration_points)} regeneration points for {conversation_id}"
475
+ )
385
476
  return regeneration_points
386
477
  else:
387
478
  print(f"[JAF:REGENERATION] No conversation data found for {conversation_id}")
388
479
  return []
389
480
  except Exception as e:
390
481
  print(f"[JAF:REGENERATION] Failed to get regeneration points: {e}")
391
-
482
+
392
483
  return []