jaf-py 2.4.8__tar.gz → 2.5.0__tar.gz

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 (131) hide show
  1. {jaf_py-2.4.8/jaf_py.egg-info → jaf_py-2.5.0}/PKG-INFO +1 -1
  2. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/engine.py +20 -19
  3. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/tracing.py +5 -3
  4. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/types.py +30 -2
  5. {jaf_py-2.4.8 → jaf_py-2.5.0/jaf_py.egg-info}/PKG-INFO +1 -1
  6. {jaf_py-2.4.8 → jaf_py-2.5.0}/pyproject.toml +1 -1
  7. {jaf_py-2.4.8 → jaf_py-2.5.0}/LICENSE +0 -0
  8. {jaf_py-2.4.8 → jaf_py-2.5.0}/README.md +0 -0
  9. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/__init__.py +0 -0
  10. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/__init__.py +0 -0
  11. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/agent.py +0 -0
  12. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/agent_card.py +0 -0
  13. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/client.py +0 -0
  14. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/__init__.py +0 -0
  15. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/client_example.py +0 -0
  16. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/integration_example.py +0 -0
  17. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/rag_demo/__init__.py +0 -0
  18. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/server_demo/__init__.py +0 -0
  19. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/examples/server_example.py +0 -0
  20. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/__init__.py +0 -0
  21. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/cleanup.py +0 -0
  22. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/factory.py +0 -0
  23. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/providers/__init__.py +0 -0
  24. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/providers/composite.py +0 -0
  25. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/providers/in_memory.py +0 -0
  26. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/providers/postgres.py +0 -0
  27. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/providers/redis.py +0 -0
  28. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/serialization.py +0 -0
  29. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/__init__.py +0 -0
  30. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/run_comprehensive_tests.py +0 -0
  31. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/test_cleanup.py +0 -0
  32. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/test_serialization.py +0 -0
  33. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/test_stress_concurrency.py +0 -0
  34. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/tests/test_task_lifecycle.py +0 -0
  35. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/memory/types.py +0 -0
  36. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/protocol.py +0 -0
  37. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/server.py +0 -0
  38. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/standalone_client.py +0 -0
  39. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/__init__.py +0 -0
  40. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/run_tests.py +0 -0
  41. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/test_agent.py +0 -0
  42. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/test_client.py +0 -0
  43. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/test_integration.py +0 -0
  44. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/test_protocol.py +0 -0
  45. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/tests/test_types.py +0 -0
  46. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/a2a/types.py +0 -0
  47. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/cli.py +0 -0
  48. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/__init__.py +0 -0
  49. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/agent_tool.py +0 -0
  50. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/analytics.py +0 -0
  51. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/composition.py +0 -0
  52. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/errors.py +0 -0
  53. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/guardrails.py +0 -0
  54. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/parallel_agents.py +0 -0
  55. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/performance.py +0 -0
  56. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/proxy.py +0 -0
  57. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/proxy_helpers.py +0 -0
  58. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/state.py +0 -0
  59. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/streaming.py +0 -0
  60. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/tool_results.py +0 -0
  61. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/tools.py +0 -0
  62. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/core/workflows.py +0 -0
  63. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/exceptions.py +0 -0
  64. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/__init__.py +0 -0
  65. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/approval_storage.py +0 -0
  66. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/factory.py +0 -0
  67. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/providers/__init__.py +0 -0
  68. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/providers/in_memory.py +0 -0
  69. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/providers/postgres.py +0 -0
  70. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/providers/redis.py +0 -0
  71. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/types.py +0 -0
  72. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/memory/utils.py +0 -0
  73. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/plugins/__init__.py +0 -0
  74. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/plugins/base.py +0 -0
  75. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/policies/__init__.py +0 -0
  76. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/policies/handoff.py +0 -0
  77. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/policies/validation.py +0 -0
  78. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/providers/__init__.py +0 -0
  79. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/providers/mcp.py +0 -0
  80. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/providers/model.py +0 -0
  81. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/server/__init__.py +0 -0
  82. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/server/main.py +0 -0
  83. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/server/server.py +0 -0
  84. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/server/types.py +0 -0
  85. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/utils/__init__.py +0 -0
  86. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/utils/attachments.py +0 -0
  87. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/utils/document_processor.py +0 -0
  88. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/__init__.py +0 -0
  89. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/example.py +0 -0
  90. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/functional_core.py +0 -0
  91. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/graphviz.py +0 -0
  92. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/imperative_shell.py +0 -0
  93. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf/visualization/types.py +0 -0
  94. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf_py.egg-info/SOURCES.txt +0 -0
  95. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf_py.egg-info/dependency_links.txt +0 -0
  96. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf_py.egg-info/entry_points.txt +0 -0
  97. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf_py.egg-info/requires.txt +0 -0
  98. {jaf_py-2.4.8 → jaf_py-2.5.0}/jaf_py.egg-info/top_level.txt +0 -0
  99. {jaf_py-2.4.8 → jaf_py-2.5.0}/setup.cfg +0 -0
  100. {jaf_py-2.4.8 → jaf_py-2.5.0}/setup.py +0 -0
  101. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_a2a_deep.py +0 -0
  102. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_a2a_examples.py +0 -0
  103. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_api_reference_examples.py +0 -0
  104. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_attachments.py +0 -0
  105. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_callback_system_examples.py +0 -0
  106. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_coffee_tool.py +0 -0
  107. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_conversation_id_fix.py +0 -0
  108. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_deployment_examples.py +0 -0
  109. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_docs_code_examples.py +0 -0
  110. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_engine.py +0 -0
  111. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_engine_manual.py +0 -0
  112. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_error_handling_examples.py +0 -0
  113. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_getting_started_examples.py +0 -0
  114. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_manual.py +0 -0
  115. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_math_tool.py +0 -0
  116. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_mcp_comprehensive.py +0 -0
  117. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_mcp_docs.py +0 -0
  118. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_mcp_real_functionality.py +0 -0
  119. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_mcp_transports.py +0 -0
  120. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_memory_system_examples.py +0 -0
  121. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_model_providers_examples.py +0 -0
  122. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_property_based.py +0 -0
  123. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_proxy_simple.py +0 -0
  124. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_redis_fixes.py +0 -0
  125. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_redis_memory.py +0 -0
  126. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_server_api_examples.py +0 -0
  127. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_session_continuity.py +0 -0
  128. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_streamable_http_mcp_example.py +0 -0
  129. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_timeout_functionality.py +0 -0
  130. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_tool_integration.py +0 -0
  131. {jaf_py-2.4.8 → jaf_py-2.5.0}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.4.8
3
+ Version: 2.5.0
4
4
  Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
5
5
  Author: JAF Contributors
6
6
  Maintainer: JAF Contributors
@@ -279,8 +279,9 @@ async def _load_conversation_history(state: RunState[Ctx], config: RunConfig[Ctx
279
279
  try:
280
280
  content = json.loads(msg.content)
281
281
  status = content.get('status')
282
- # Filter out ALL halted messages (they're for audit only)
283
- if status == 'halted':
282
+ hitl_status = content.get('hitl_status')
283
+ # Filter out ALL halted/pending approval messages (they're for audit only)
284
+ if status == 'halted' or hitl_status == 'pending_approval':
284
285
  filtered_count += 1
285
286
  continue # Skip this halted message
286
287
  else:
@@ -765,7 +766,7 @@ async def _run_internal(
765
766
  else:
766
767
  try:
767
768
  content = json.loads(msg.content)
768
- if content.get('status') == 'halted':
769
+ if content.get('status') == 'halted' or content.get('hitl_status') == 'pending_approval':
769
770
  # Remove this halted message if we have a new result for the same tool_call_id
770
771
  if not any(result['message'].tool_call_id == msg.tool_call_id for result in tool_results):
771
772
  cleaned_new_messages.append(msg)
@@ -793,7 +794,7 @@ async def _run_internal(
793
794
  else:
794
795
  try:
795
796
  content = json.loads(msg.content)
796
- if content.get('status') == 'halted':
797
+ if content.get('status') == 'halted' or content.get('hitl_status') == 'pending_approval':
797
798
  # Remove this halted message if we have a new result for the same tool_call_id
798
799
  if not any(result['message'].tool_call_id == msg.tool_call_id for result in tool_results):
799
800
  cleaned_new_messages.append(msg)
@@ -1011,7 +1012,7 @@ async def _execute_tool_calls(
1011
1012
 
1012
1013
  if not tool:
1013
1014
  error_result = json.dumps({
1014
- 'status': 'tool_not_found',
1015
+ 'hitl_status': 'tool_not_found', # HITL workflow status
1015
1016
  'message': f'Tool {tool_call.function.name} not found',
1016
1017
  'tool_name': tool_call.function.name,
1017
1018
  })
@@ -1022,7 +1023,7 @@ async def _execute_tool_calls(
1022
1023
  result=error_result,
1023
1024
  trace_id=state.trace_id,
1024
1025
  run_id=state.run_id,
1025
- status='error',
1026
+ execution_status='error', # Tool execution failed
1026
1027
  tool_result={'error': 'tool_not_found'},
1027
1028
  call_id=tool_call.id
1028
1029
  ))))
@@ -1045,7 +1046,7 @@ async def _execute_tool_calls(
1045
1046
  validated_args = raw_args
1046
1047
  except ValidationError as e:
1047
1048
  error_result = json.dumps({
1048
- 'status': 'validation_error',
1049
+ 'hitl_status': 'validation_error', # HITL workflow status
1049
1050
  'message': f'Invalid arguments for {tool_call.function.name}: {e!s}',
1050
1051
  'tool_name': tool_call.function.name,
1051
1052
  'validation_errors': e.errors()
@@ -1057,7 +1058,7 @@ async def _execute_tool_calls(
1057
1058
  result=error_result,
1058
1059
  trace_id=state.trace_id,
1059
1060
  run_id=state.run_id,
1060
- status='error',
1061
+ execution_status='error', # Tool execution failed due to validation
1061
1062
  tool_result={'error': 'validation_error', 'details': e.errors()},
1062
1063
  call_id=tool_call.id
1063
1064
  ))))
@@ -1114,7 +1115,7 @@ async def _execute_tool_calls(
1114
1115
 
1115
1116
  # Return interrupted result with halted message
1116
1117
  halted_result = json.dumps({
1117
- 'status': 'halted',
1118
+ 'hitl_status': 'pending_approval', # HITL workflow status: waiting for approval
1118
1119
  'message': f'Tool {tool_call.function.name} requires approval.',
1119
1120
  })
1120
1121
 
@@ -1131,7 +1132,7 @@ async def _execute_tool_calls(
1131
1132
  if derived_status == 'rejected':
1132
1133
  rejection_reason = approval_status.additional_context.get('rejection_reason', 'User declined the action') if approval_status.additional_context else 'User declined the action'
1133
1134
  rejection_result = json.dumps({
1134
- 'status': 'approval_denied',
1135
+ 'hitl_status': 'rejected', # HITL workflow status: user rejected the action
1135
1136
  'message': f'Action was not approved. {rejection_reason}. Please ask if you can help with something else or suggest an alternative approach.',
1136
1137
  'tool_name': tool_call.function.name,
1137
1138
  'rejection_reason': rejection_reason,
@@ -1184,7 +1185,7 @@ async def _execute_tool_calls(
1184
1185
  )
1185
1186
  except asyncio.TimeoutError:
1186
1187
  timeout_error_result = json.dumps({
1187
- 'error': 'timeout_error',
1188
+ 'hitl_status': 'execution_timeout', # HITL workflow status
1188
1189
  'message': f'Tool {tool_call.function.name} timed out after {timeout} seconds',
1189
1190
  'tool_name': tool_call.function.name,
1190
1191
  'timeout_seconds': timeout
@@ -1196,8 +1197,8 @@ async def _execute_tool_calls(
1196
1197
  result=timeout_error_result,
1197
1198
  trace_id=state.trace_id,
1198
1199
  run_id=state.run_id,
1199
- status='timeout',
1200
- tool_result={'error': 'timeout_error'},
1200
+ execution_status='timeout', # Tool execution timed out
1201
+ tool_result={'error': 'timeout'},
1201
1202
  call_id=tool_call.id
1202
1203
  ))))
1203
1204
 
@@ -1222,7 +1223,7 @@ async def _execute_tool_calls(
1222
1223
  # Wrap tool result with status information for approval context
1223
1224
  if approval_status and approval_status.additional_context:
1224
1225
  final_content = json.dumps({
1225
- 'status': 'approved_and_executed',
1226
+ 'hitl_status': 'approved_and_executed', # HITL workflow status: approved by user and executed
1226
1227
  'result': result_string,
1227
1228
  'tool_name': tool_call.function.name,
1228
1229
  'approval_context': approval_status.additional_context,
@@ -1230,14 +1231,14 @@ async def _execute_tool_calls(
1230
1231
  })
1231
1232
  elif needs_approval:
1232
1233
  final_content = json.dumps({
1233
- 'status': 'approved_and_executed',
1234
+ 'hitl_status': 'approved_and_executed', # HITL workflow status: approved by user and executed
1234
1235
  'result': result_string,
1235
1236
  'tool_name': tool_call.function.name,
1236
1237
  'message': 'Tool was approved and executed successfully.'
1237
1238
  })
1238
1239
  else:
1239
1240
  final_content = json.dumps({
1240
- 'status': 'executed',
1241
+ 'hitl_status': 'executed', # HITL workflow status: executed normally (no approval needed)
1241
1242
  'result': result_string,
1242
1243
  'tool_name': tool_call.function.name,
1243
1244
  'message': 'Tool executed successfully.'
@@ -1250,7 +1251,7 @@ async def _execute_tool_calls(
1250
1251
  trace_id=state.trace_id,
1251
1252
  run_id=state.run_id,
1252
1253
  tool_result=tool_result,
1253
- status='success',
1254
+ execution_status='success', # Tool execution succeeded
1254
1255
  call_id=tool_call.id
1255
1256
  ))))
1256
1257
 
@@ -1277,7 +1278,7 @@ async def _execute_tool_calls(
1277
1278
 
1278
1279
  except Exception as error:
1279
1280
  error_result = json.dumps({
1280
- 'status': 'execution_error',
1281
+ 'hitl_status': 'execution_error', # HITL workflow status
1281
1282
  'message': str(error),
1282
1283
  'tool_name': tool_call.function.name,
1283
1284
  })
@@ -1288,7 +1289,7 @@ async def _execute_tool_calls(
1288
1289
  result=error_result,
1289
1290
  trace_id=state.trace_id,
1290
1291
  run_id=state.run_id,
1291
- status='error',
1292
+ execution_status='error', # Tool execution failed with exception
1292
1293
  tool_result={'error': 'execution_error', 'detail': str(error)},
1293
1294
  call_id=tool_call.id
1294
1295
  ))))
@@ -341,7 +341,7 @@ class LangfuseTraceCollector:
341
341
  public_key=public_key,
342
342
  secret_key=secret_key,
343
343
  host=host,
344
- release="jaf-py-v2.4.8"
344
+ release="jaf-py-v2.5.0"
345
345
  )
346
346
  self.active_spans: Dict[str, Any] = {}
347
347
  self.trace_spans: Dict[TraceId, Any] = {}
@@ -716,7 +716,8 @@ class LangfuseTraceCollector:
716
716
  "result": tool_result,
717
717
  "call_id": call_id,
718
718
  "timestamp": datetime.now().isoformat(),
719
- "status": event.data.get("status", "completed"),
719
+ "execution_status": event.data.get("execution_status", "completed"),
720
+ "status": event.data.get("execution_status", "completed"), # DEPRECATED: backward compatibility
720
721
  "tool_result": event.data.get("tool_result")
721
722
  }
722
723
 
@@ -731,7 +732,8 @@ class LangfuseTraceCollector:
731
732
  "result": tool_result,
732
733
  "call_id": call_id,
733
734
  "timestamp": datetime.now().isoformat(),
734
- "status": event.data.get("status", "completed")
735
+ "execution_status": event.data.get("execution_status", "completed"),
736
+ "status": event.data.get("execution_status", "completed") # DEPRECATED: backward compatibility
735
737
  }
736
738
 
737
739
  # End the span with detailed output
@@ -619,15 +619,43 @@ class ToolCallStartEvent:
619
619
 
620
620
  @dataclass(frozen=True)
621
621
  class ToolCallEndEventData:
622
- """Data for tool call end events."""
622
+ """
623
+ Data for tool call end events.
624
+
625
+ IMPORTANT: There are two different status concepts:
626
+ 1. execution_status (this field): Indicates whether the tool execution itself succeeded or failed
627
+ - 'success': Tool executed without errors
628
+ - 'error': Tool execution failed due to validation, not found, or runtime errors
629
+ - 'timeout': Tool execution timed out
630
+
631
+ 2. hitl_status (in result JSON): Indicates HITL workflow status
632
+ - 'executed': Tool ran normally (no approval needed)
633
+ - 'approved_and_executed': Tool required approval, was approved, and executed
634
+ - 'pending_approval': Tool requires approval and is waiting
635
+ - 'rejected': Tool was rejected by user
636
+ - 'execution_error', 'validation_error', etc.: Various error states
637
+ """
623
638
  tool_name: str
624
639
  result: str
625
640
  trace_id: TraceId
626
641
  run_id: RunId
627
642
  tool_result: Optional[Any] = None
628
- status: Optional[str] = None
643
+ execution_status: Optional[str] = None # success/error/timeout - indicates if tool executed successfully
644
+ status: Optional[str] = None # DEPRECATED: maintained for backward-compatible initialization/serialization
629
645
  call_id: Optional[str] = None
630
646
 
647
+ def __post_init__(self) -> None:
648
+ # Handle backward compatibility with explicit conflict detection
649
+ if self.execution_status is not None and self.status is not None and self.execution_status != self.status:
650
+ raise ValueError(
651
+ f"Conflicting values for execution_status ('{self.execution_status}') and status ('{self.status}'). "
652
+ f"Please use only execution_status for new code."
653
+ )
654
+
655
+ # Prefer execution_status (new field) over status (deprecated field)
656
+ canonical = self.execution_status if self.execution_status is not None else self.status
657
+ object.__setattr__(self, 'execution_status', canonical)
658
+ object.__setattr__(self, 'status', canonical)
631
659
  @dataclass(frozen=True)
632
660
  class ToolCallEndEvent:
633
661
  type: Literal['tool_call_end'] = 'tool_call_end'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.4.8
3
+ Version: 2.5.0
4
4
  Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
5
5
  Author: JAF Contributors
6
6
  Maintainer: JAF Contributors
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jaf-py"
7
- version = "2.4.8"
7
+ version = "2.5.0"
8
8
  description = "A purely functional agent framework with immutable state and composable tools - Python implementation"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes