flowra 0.0.91__tar.gz → 0.0.92.dev108__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 (357) hide show
  1. {flowra-0.0.91 → flowra-0.0.92.dev108}/CHANGELOG.md +19 -0
  2. {flowra-0.0.91 → flowra-0.0.92.dev108}/PKG-INFO +1 -1
  3. {flowra-0.0.91 → flowra-0.0.92.dev108}/context7.json +2 -2
  4. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/agents.md +5 -2
  5. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/observability.md +33 -2
  6. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/__init__.py +2 -0
  7. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/agent.py +52 -5
  8. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/hook_events.py +24 -2
  9. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/version.py +1 -1
  10. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_turn_agent.py +381 -2
  11. {flowra-0.0.91 → flowra-0.0.92.dev108}/.agents/skills/source-command-update-pricing/SKILL.md +0 -0
  12. {flowra-0.0.91 → flowra-0.0.92.dev108}/.claude/commands/update-pricing.md +0 -0
  13. {flowra-0.0.91 → flowra-0.0.92.dev108}/.env.example +0 -0
  14. {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/CODEOWNERS +0 -0
  15. {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/master.yml +0 -0
  16. {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/publish.yml +0 -0
  17. {flowra-0.0.91 → flowra-0.0.92.dev108}/.github/workflows/pull_request.yml +0 -0
  18. {flowra-0.0.91 → flowra-0.0.92.dev108}/.gitignore +0 -0
  19. {flowra-0.0.91 → flowra-0.0.92.dev108}/.python-version +0 -0
  20. {flowra-0.0.91 → flowra-0.0.92.dev108}/AGENTS.md +0 -0
  21. {flowra-0.0.91 → flowra-0.0.92.dev108}/CLAUDE.md +0 -0
  22. {flowra-0.0.91 → flowra-0.0.92.dev108}/LICENSE +0 -0
  23. {flowra-0.0.91 → flowra-0.0.92.dev108}/Makefile +0 -0
  24. {flowra-0.0.91 → flowra-0.0.92.dev108}/README.md +0 -0
  25. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/architecture.md +0 -0
  26. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/getting-started.md +0 -0
  27. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/llm.md +0 -0
  28. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/patterns.md +0 -0
  29. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/agent_async_init.md +0 -0
  30. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/agent_tool_errors.md +0 -0
  31. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/anthropic_web_search.md +0 -0
  32. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/e2e_security.md +0 -0
  33. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/enum_slot_values.md +0 -0
  34. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/final_tools.md +0 -0
  35. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/langchain_comparison.md +0 -0
  36. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/lifecycle_hooks_and_conversation.md +0 -0
  37. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/llm_retry_backoff.md +0 -0
  38. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scope_lazy_hydration.md +0 -0
  39. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scope_type_registry.md +0 -0
  40. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/named_scopes.md +0 -0
  41. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/pricing_complexity.md +0 -0
  42. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/pydantic_ai_comparison.md +0 -0
  43. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/strands_comparison.md +0 -0
  44. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_error_signals.md +0 -0
  45. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_errors_vs_failures.md +0 -0
  46. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_registry_redesign.md +0 -0
  47. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/tool_search_tool.md +0 -0
  48. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/research/voice_stt.md +0 -0
  49. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step1_structure.md +0 -0
  50. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step2_code_style.md +0 -0
  51. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step3_documentation.md +0 -0
  52. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step4_doc_readability.md +0 -0
  53. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step5_doc_audit.md +0 -0
  54. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/review_prompts/step6_tests.md +0 -0
  55. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/todo.md +0 -0
  56. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/tools.md +0 -0
  57. {flowra-0.0.91 → flowra-0.0.92.dev108}/docs/video_script.md +0 -0
  58. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/TRACING_COMBINATIONS.md +0 -0
  59. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/__init__.py +0 -0
  60. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/agent_as_tool.py +0 -0
  61. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/app_agent.py +0 -0
  62. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/console_chat.py +0 -0
  63. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/__init__.py +0 -0
  64. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/agents_custom.py +0 -0
  65. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/agents_parallel.py +0 -0
  66. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_chat.py +0 -0
  67. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_streaming.py +0 -0
  68. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/getting_started_tools.py +0 -0
  69. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/llm_streaming.py +0 -0
  70. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/llm_structured_output.py +0 -0
  71. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/docs/tools_service_injection.py +0 -0
  72. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/escalation.py +0 -0
  73. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/llm_logging.py +0 -0
  74. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/llm_routing.py +0 -0
  75. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/menu_agent.py +0 -0
  76. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/menu_agent_class.py +0 -0
  77. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_demo.py +0 -0
  78. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_dual_export_demo.py +0 -0
  79. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_nested_demo.py +0 -0
  80. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_otel_both_demo.py +0 -0
  81. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_otel_nested_demo.py +0 -0
  82. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/mlflow_parallel_demo.py +0 -0
  83. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/model_registry.py +0 -0
  84. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_demo.py +0 -0
  85. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_jaeger_demo.py +0 -0
  86. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_nested_demo.py +0 -0
  87. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/otel_visualize.py +0 -0
  88. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/race.py +0 -0
  89. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/span_crash_demo.py +0 -0
  90. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/span_demo.py +0 -0
  91. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/system_prompt.txt +0 -0
  92. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/__init__.py +0 -0
  93. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/calculator.py +0 -0
  94. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/random_numbers.py +0 -0
  95. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tools/switch_model.py +0 -0
  96. {flowra-0.0.91 → flowra-0.0.92.dev108}/examples/tui_chat.py +0 -0
  97. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/__init__.py +0 -0
  98. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/_sentinel.py +0 -0
  99. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/_typing.py +0 -0
  100. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/__init__.py +0 -0
  101. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/__init__.py +0 -0
  102. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/_path.py +0 -0
  103. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/actions.py +0 -0
  104. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/agent.py +0 -0
  105. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/agent_arg.py +0 -0
  106. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/__init__.py +0 -0
  107. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/compiler.py +0 -0
  108. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/contract.py +0 -0
  109. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/init_params.py +0 -0
  110. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/instance.py +0 -0
  111. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/step_params.py +0 -0
  112. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/step_validation.py +0 -0
  113. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/steps.py +0 -0
  114. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/compile/type_registry.py +0 -0
  115. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/model.py +0 -0
  116. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/registry.py +0 -0
  117. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/shared_persistent.py +0 -0
  118. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/spawn.py +0 -0
  119. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/step.py +0 -0
  120. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/definition/step_helpers.py +0 -0
  121. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/__init__.py +0 -0
  122. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/context.py +0 -0
  123. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/flowing_registry.py +0 -0
  124. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/hooks.py +0 -0
  125. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/interrupt.py +0 -0
  126. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/interrupt_helpers.py +0 -0
  127. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/flow/timeout.py +0 -0
  128. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/__init__.py +0 -0
  129. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/engine.py +0 -0
  130. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/execution.py +0 -0
  131. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/instance_factory.py +0 -0
  132. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/key_locks.py +0 -0
  133. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/runtime.py +0 -0
  134. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/scope.py +0 -0
  135. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/serialization.py +0 -0
  136. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spans.py +0 -0
  137. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spawn_satisfaction.py +0 -0
  138. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/spawn_tree.py +0 -0
  139. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/runtime/visibility.py +0 -0
  140. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/services.py +0 -0
  141. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/session_state.py +0 -0
  142. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/__init__.py +0 -0
  143. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/markers.py +0 -0
  144. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/persistent.py +0 -0
  145. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/store.py +0 -0
  146. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/state/values.py +0 -0
  147. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/__init__.py +0 -0
  148. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/file.py +0 -0
  149. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/in_memory.py +0 -0
  150. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/agent/storage/session_storage.py +0 -0
  151. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/__init__.py +0 -0
  152. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/_session.py +0 -0
  153. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/mlflow.py +0 -0
  154. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/ext/otel.py +0 -0
  155. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/__init__.py +0 -0
  156. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/__init__.py +0 -0
  157. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/agent.py +0 -0
  158. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/config.py +0 -0
  159. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/conversation.py +0 -0
  160. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/hook_events.py +0 -0
  161. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/chat/spec.py +0 -0
  162. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/config_value.py +0 -0
  163. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/__init__.py +0 -0
  164. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/agent.py +0 -0
  165. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/config.py +0 -0
  166. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_call/spec.py +0 -0
  167. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_caller.py +0 -0
  168. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/llm_config.py +0 -0
  169. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/observability/__init__.py +0 -0
  170. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/observability/llm_hooks.py +0 -0
  171. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/config.py +0 -0
  172. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/context.py +0 -0
  173. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/spec.py +0 -0
  174. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/__init__.py +0 -0
  175. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/agent.py +0 -0
  176. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/agent_tool.py +0 -0
  177. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/lib/turn/tool_call/context.py +0 -0
  178. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/__init__.py +0 -0
  179. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/_schema_nodes.py +0 -0
  180. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/base.py +0 -0
  181. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/blocks.py +0 -0
  182. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/errors.py +0 -0
  183. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/__init__.py +0 -0
  184. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/__init__.py +0 -0
  185. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/cache.py +0 -0
  186. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/presets.py +0 -0
  187. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/tool_search.py +0 -0
  188. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/anthropic/web_search.py +0 -0
  189. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/openai/__init__.py +0 -0
  190. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/ext/openai/tool_search.py +0 -0
  191. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/gemini_schema.py +0 -0
  192. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/messages.py +0 -0
  193. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/openai_schema.py +0 -0
  194. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/__init__.py +0 -0
  195. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/bundled.py +0 -0
  196. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/custom/anthropic.json +0 -0
  197. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/custom/vertex.json +0 -0
  198. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/anthropic.json +0 -0
  199. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/azure_openai.json +0 -0
  200. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/bedrock.json +0 -0
  201. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/gemini_api.json +0 -0
  202. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/inception.json +0 -0
  203. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/openai.json +0 -0
  204. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/data/generated/vertex.json +0 -0
  205. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/model_pricing.py +0 -0
  206. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/pricing/price_list.py +0 -0
  207. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/provider.py +0 -0
  208. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/__init__.py +0 -0
  209. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/_openai_common.py +0 -0
  210. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic.py +0 -0
  211. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_bedrock.py +0 -0
  212. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_foundry.py +0 -0
  213. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/anthropic_vertex.py +0 -0
  214. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/azure_openai.py +0 -0
  215. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/google_vertex.py +0 -0
  216. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/openai.py +0 -0
  217. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/providers/openai_responses.py +0 -0
  218. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/request.py +0 -0
  219. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/response.py +0 -0
  220. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/schema_formatting.py +0 -0
  221. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/schema_validation.py +0 -0
  222. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/stream.py +0 -0
  223. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/llm/tools.py +0 -0
  224. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/py.typed +0 -0
  225. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/__init__.py +0 -0
  226. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/_mcp_content.py +0 -0
  227. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/_mcp_headers.py +0 -0
  228. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/local_tool.py +0 -0
  229. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/mcp_connection.py +0 -0
  230. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/result_format.py +0 -0
  231. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_arg.py +0 -0
  232. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_filter.py +0 -0
  233. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/tool_registry.py +0 -0
  234. {flowra-0.0.91 → flowra-0.0.92.dev108}/flowra/tools/types.py +0 -0
  235. {flowra-0.0.91 → flowra-0.0.92.dev108}/pyproject.toml +0 -0
  236. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/__init__.py +0 -0
  237. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/__init__.py +0 -0
  238. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/__init__.py +0 -0
  239. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/compile/__init__.py +0 -0
  240. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/compile/test_compile.py +0 -0
  241. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_actions.py +0 -0
  242. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_agent.py +0 -0
  243. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_registry.py +0 -0
  244. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_spawn.py +0 -0
  245. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/definition/test_step_helpers.py +0 -0
  246. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/__init__.py +0 -0
  247. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_context.py +0 -0
  248. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_registry.py +0 -0
  249. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_registry_tasks.py +0 -0
  250. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_flowing_sync.py +0 -0
  251. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_hooks.py +0 -0
  252. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_interrupt.py +0 -0
  253. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_interruptible.py +0 -0
  254. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_spans.py +0 -0
  255. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_timeout.py +0 -0
  256. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/flow/test_with_interrupt.py +0 -0
  257. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/__init__.py +0 -0
  258. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_engine.py +0 -0
  259. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_engine_spans.py +0 -0
  260. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_ephemeral_isolation.py +0 -0
  261. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_hook_context.py +0 -0
  262. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scope_persistence.py +0 -0
  263. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scope_registry.py +0 -0
  264. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_named_scopes.py +0 -0
  265. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_parallel_failure.py +0 -0
  266. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_persistence.py +0 -0
  267. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_primitive_join.py +0 -0
  268. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_runtime.py +0 -0
  269. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_scope.py +0 -0
  270. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_serialization.py +0 -0
  271. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/runtime/test_spec_in_constructor.py +0 -0
  272. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/state/__init__.py +0 -0
  273. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/state/test_values.py +0 -0
  274. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/__init__.py +0 -0
  275. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/test_file.py +0 -0
  276. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/storage/test_in_memory.py +0 -0
  277. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/test_missing_scenarios.py +0 -0
  278. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/agent/test_session_state.py +0 -0
  279. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/examples/__init__.py +0 -0
  280. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/examples/test_menu_agent.py +0 -0
  281. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/__init__.py +0 -0
  282. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/_mlflow.py +0 -0
  283. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_mlflow.py +0 -0
  284. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_mlflow_cross_task.py +0 -0
  285. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/ext/test_otel.py +0 -0
  286. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/__init__.py +0 -0
  287. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/_helpers.py +0 -0
  288. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/__init__.py +0 -0
  289. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/anthropic/__init__.py +0 -0
  290. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/anthropic/test_anthropic.py +0 -0
  291. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/openai/__init__.py +0 -0
  292. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/ext/openai/test_openai_tool_search.py +0 -0
  293. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_chat_agent.py +0 -0
  294. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_chat_spec.py +0 -0
  295. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_config_value.py +0 -0
  296. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_conversation.py +0 -0
  297. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_llm_call_agent.py +0 -0
  298. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_llm_caller.py +0 -0
  299. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_matches_tool_filter.py +0 -0
  300. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_tool_call_agent.py +0 -0
  301. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/test_tool_call_agent_call_agent.py +0 -0
  302. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/__init__.py +0 -0
  303. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_per_call_headers.py +0 -0
  304. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_render.py +0 -0
  305. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/lib/turn/test_tool_errors.py +0 -0
  306. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/__init__.py +0 -0
  307. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/__init__.py +0 -0
  308. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_bundled.py +0 -0
  309. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_model_pricing.py +0 -0
  310. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/pricing/test_price_list.py +0 -0
  311. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/__init__.py +0 -0
  312. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_mock_http.py +0 -0
  313. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_raw_body.py +0 -0
  314. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/_tool_result_blocks.py +0 -0
  315. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic.py +0 -0
  316. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_bedrock.py +0 -0
  317. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_bedrock_e2e.py +0 -0
  318. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_e2e.py +0 -0
  319. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_foundry.py +0 -0
  320. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_foundry_e2e.py +0 -0
  321. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_vertex.py +0 -0
  322. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_anthropic_vertex_locations_e2e.py +0 -0
  323. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_azure_openai_e2e.py +0 -0
  324. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_cross_provider_e2e.py +0 -0
  325. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_errors.py +0 -0
  326. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_fallback_e2e.py +0 -0
  327. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_google_vertex.py +0 -0
  328. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_google_vertex_e2e.py +0 -0
  329. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_history_across_models.py +0 -0
  330. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_e2e.py +0 -0
  331. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_provider.py +0 -0
  332. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_responses.py +0 -0
  333. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_openai_responses_e2e.py +0 -0
  334. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_refusals.py +0 -0
  335. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/providers/test_schemas_e2e.py +0 -0
  336. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_blocks.py +0 -0
  337. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_cost_breakdown.py +0 -0
  338. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_gemini_schema.py +0 -0
  339. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_metadata.py +0 -0
  340. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_openai_schema.py +0 -0
  341. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_response.py +0 -0
  342. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_formatting.py +0 -0
  343. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_nodes.py +0 -0
  344. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_schema_validation.py +0 -0
  345. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/llm/test_stream.py +0 -0
  346. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/scripts/__init__.py +0 -0
  347. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/scripts/test_sync_pricing.py +0 -0
  348. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/test_typing.py +0 -0
  349. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/__init__.py +0 -0
  350. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_local_tool.py +0 -0
  351. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_mcp_connection.py +0 -0
  352. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_mcp_headers.py +0 -0
  353. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_result_format.py +0 -0
  354. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_tool_registry.py +0 -0
  355. {flowra-0.0.91 → flowra-0.0.92.dev108}/tests/tools/test_types.py +0 -0
  356. {flowra-0.0.91 → flowra-0.0.92.dev108}/tools/sync_pricing.py +0 -0
  357. {flowra-0.0.91 → flowra-0.0.92.dev108}/uv.lock +0 -0
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - **`BeforeToolCallsEvent`: answer a model message's tool calls before any
12
+ runs.** Emitted once per model message with tool calls, a single call
13
+ included, with every call of the message in `tool_uses`, a call the response cut off
14
+ included (an answer under its id is not used). A handler puts a
15
+ `ToolResult` into `results` under a call's id to answer that call itself:
16
+ it does not run, the other calls run as usual, and its result reaches the
17
+ model in the message's order. An answered call gets no
18
+ `BeforeToolCallEvent` and no `ToolCallSpan`; it goes through
19
+ `AfterToolCallEvent` and counts toward `max_consecutive_errors` like any
20
+ result. An error answered without an `error_kind` gets
21
+ `ToolErrorKind.EXECUTION`; a key that is not the id of a call in the message
22
+ raises `ValueError`. Answered calls are kept in the turn's state, so they
23
+ survive a resume before the results are collected.
24
+ - **`AfterToolCallEvent.prefilled`**: true for a call answered in
25
+ `BeforeToolCallsEvent`, so a handler can tell that the tool did not run.
26
+
8
27
  ## [0.0.91] - 2026-10-20
9
28
 
10
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowra
3
- Version: 0.0.91
3
+ Version: 0.0.92.dev108
4
4
  Summary: Flowra — flow infrastructure for building stateful LLM agents
5
5
  Project-URL: Repository, https://github.com/anna-money/flowra
6
6
  Project-URL: Changelog, https://github.com/anna-money/flowra/blob/master/CHANGELOG.md
@@ -129,12 +129,12 @@
129
129
 
130
130
  "PRE-BUILT AGENTS — TurnAgent: single-turn LLM tool loop with hooks and caching",
131
131
  "TurnAgent sends messages to LLM, executes tool calls, feeds results back, repeats until done",
132
- "TurnAgent stop handling: a response without tool calls ends the turn with TurnStatus.COMPLETED and TurnResult.stop_reason (END_TURN, MAX_TOKENS, STOP_SEQUENCE, OTHER, SCHEMA_VALIDATION_FAILED, REFUSAL). On REFUSAL TurnResult.refusal is set, result_message is None and messages keep the turn so far (tool calls included) with no empty assistant message. If a response stopped with anything but END_TURN/TOOL_USE ends on a tool call, that call was cut off: it is not run and gets an error ToolResultBlock (no Before/AfterToolCallEvent, no ToolCallSpan, not counted in max_consecutive_errors), the complete calls before it run, and the loop continues so the model can retry (bounded by max_llm_calls). History never holds an unanswered tool_use",
132
+ "TurnAgent stop handling: a response without tool calls ends the turn with TurnStatus.COMPLETED and TurnResult.stop_reason (END_TURN, MAX_TOKENS, STOP_SEQUENCE, OTHER, SCHEMA_VALIDATION_FAILED, REFUSAL). On REFUSAL TurnResult.refusal is set, result_message is None and messages keep the turn so far (tool calls included) with no empty assistant message. If a response stopped with anything but END_TURN/TOOL_USE ends on a tool call, that call was cut off: it is not run and gets an error ToolResultBlock (it appears in BeforeToolCallsEvent.tool_uses; no BeforeToolCallEvent/AfterToolCallEvent, no ToolCallSpan, not counted in max_consecutive_errors), the complete calls before it run, and the loop continues so the model can retry (bounded by max_llm_calls). History never holds an unanswered tool_use",
133
133
  "TurnConfig configures TurnAgent: TurnConfig(llm_config=LLMConfig(model='...'), system=[...], history=[...])",
134
134
  "RENDER: ChatConfig.render / TurnConfig.render (PromptRender = Callable[[Prompt], Prompt | Awaitable[Prompt]]; Prompt from flowra.lib.turn has system and messages) is called once per LLM call of the turn (not per fallback attempt), before LLMCaller, with the system messages and the conversation (history, then turn messages). What it returns is sent to every fallback link; each link's request_extensions apply after it, and each attempt's LLMCallSpan carries the request with them applied. Stored history and turn messages are not changed, so a marker block can be rendered from live state on each call; build new messages, never edit the given ones. Messages left with no blocks are not sent, none left at all is a ValueError before the call; a tool call must still be followed straight away by its results (Claude and OpenAI Chat Completions reject anything in between); an error in render is raised from the turn as is; an interrupt stops an async render and ends the turn INTERRUPTED",
135
135
  "TurnAgent lifecycle: __post_init__ emits TurnReadyEvent(context, turn_messages) — fires on both creation and crash recovery. ChatAgent subscribes and emits ConversationReadyEvent(conversation, context) with a Conversation view combining history + turn messages",
136
136
  "Conversation class (from flowra.lib.chat): read-only Sequence view over history + turn_messages. Supports len(), indexing, slicing, forward/reverse iteration. Live — reflects changes to underlying lists. Properties: history_len, turn_len. Method without_pending_tool_use() -> list[UserMessage | AssistantMessage] returns messages with ToolUseBlocks stripped from the last AssistantMessage (drops the message entirely if it becomes empty) — use it inside an agent-as-tool to feed the parent conversation to a child LLM call without the unresolved tool-use that spawned it",
137
- "TurnAgent hook events: StartTurnEvent (messages, metadata — mutate messages to augment input), MessageAcceptedEvent, StartIterationEvent, TextReasoningEvent (each TextBlock with text in response), ThinkingEvent (each ThinkingBlock with text; not redacted or signature-only thoughts), ServerToolCallEvent (server-side tool call from intermediate messages, e.g. tool search), ServerToolResultEvent (server-side tool result), ResultMessageEvent, BeforeToolCallEvent (has tool_definition: ToolDefinition | None; mutable tool_use, headers, meta — see ToolCallInfo), AfterToolCallEvent (has error_kind; result is a single ToolResultBlock; append TextBlock/MediaBlock to additional_blocks to attach them to this call's result — they go at the end of its content, also when max_consecutive_errors replaces it, so they stay with their call and never land between the results of parallel calls; mark one transient to keep it out of saved history; being part of the result, a MediaBlock there takes the providers' rules for media in a tool result (Chat Completions raises, Gemini before 3 does not see it) and a transient block ends the non-transient cache prefix before the result). Delta events: TextDeltaEvent, ThinkingDeltaEvent (from flowra.lib.observability — shared with LLMCallAgent). UsageEvent emitted after each LLM call + subscribed to accumulate usage from child agents (e.g. agent_as_tool). Span-hooks: LLMCallSpan, ToolCallSpan. Request transforms (prompt caching, tool search) are NOT hooks — they live on LLMConfig.request_extensions. A conversation transform for every fallback link goes in ChatConfig.render / TurnConfig.render (see RENDER)",
137
+ "TurnAgent hook events: StartTurnEvent (messages, metadata — mutate messages to augment input), MessageAcceptedEvent, StartIterationEvent, TextReasoningEvent (each TextBlock with text in response), ThinkingEvent (each ThinkingBlock with text; not redacted or signature-only thoughts), ServerToolCallEvent (server-side tool call from intermediate messages, e.g. tool search), ServerToolResultEvent (server-side tool result), ResultMessageEvent, BeforeToolCallsEvent (once per model message with tool calls, before any runs; tool_uses is every call of the message, a cut-off call included (an answer under its id is not used); put a ToolResult into results under a call's id to answer it without running it — that call gets no BeforeToolCallEvent and no ToolCallSpan, its result goes through AfterToolCallEvent with prefilled=True and max_consecutive_errors in message order, an error without error_kind gets EXECUTION; a key not among the message's call ids raises ValueError), BeforeToolCallEvent (has tool_definition: ToolDefinition | None; mutable tool_use, headers, meta — see ToolCallInfo; not emitted for a call answered in BeforeToolCallsEvent), AfterToolCallEvent (fires after execution, and for a call answered in BeforeToolCallsEvent; has error_kind; prefilled is true for a call answered in BeforeToolCallsEvent; result is a single ToolResultBlock; append TextBlock/MediaBlock to additional_blocks to attach them to this call's result — they go at the end of its content, also when max_consecutive_errors replaces it, so they stay with their call and never land between the results of parallel calls; mark one transient to keep it out of saved history; being part of the result, a MediaBlock there takes the providers' rules for media in a tool result (Chat Completions raises, Gemini before 3 does not see it) and a transient block ends the non-transient cache prefix before the result). Delta events: TextDeltaEvent, ThinkingDeltaEvent (from flowra.lib.observability — shared with LLMCallAgent). UsageEvent emitted after each LLM call + subscribed to accumulate usage from child agents (e.g. agent_as_tool). Span-hooks: LLMCallSpan, ToolCallSpan. Request transforms (prompt caching, tool search) are NOT hooks — they live on LLMConfig.request_extensions. A conversation transform for every fallback link goes in ChatConfig.render / TurnConfig.render (see RENDER)",
138
138
  "When TextDeltaEvent or ThinkingDeltaEvent handlers are registered, TurnAgent automatically uses provider.stream() instead of provider.call()",
139
139
 
140
140
  "AGENTS AS TOOLS: Tools can delegate to sub-agents via ToolCallAgentContext.call_agent(). make_agent_tool(AgentClass, name, description) creates an ExecutableTool with input_schema from spec type and output_schema from result type (ToolResult excluded). @agent_tool decorator marks an agent class as a tool, get_agent_tool() extracts the ExecutableTool. ToolCallAgent spawns the sub-agent and serializes its result as a ToolResultBlock. Agent tools can return ToolResult directly to signal errors to the parent LLM or answer with blocks (e.g. return ToolResult(content='...', is_error=True)) — the agent completes normally; with is_error=True the parent LLM sees a tool error and can retry. Compile-time validation: step return types must be a subset of Agent's generic result type parameter. The sub-agent must be declared by the agent running the tool or one of its registry ancestors (top level works); it receives services of its own registry ancestors, not the calling chat's (unless the chat is one of them)",
@@ -802,11 +802,14 @@ Lifecycle events (from `flowra.lib.turn`):
802
802
  | `ThinkingEvent` | Non-empty thinking block in LLM response | — |
803
803
  | `ServerToolCallEvent` | Server-side tool call (e.g. tool search) | — |
804
804
  | `ServerToolResultEvent` | Server-side tool result | — |
805
+ | `BeforeToolCallsEvent` | Message with tool calls, before any runs | `results` |
805
806
  | `BeforeToolCallEvent` | Before tool execution | `tool_use`, `headers`, `meta` |
806
- | `AfterToolCallEvent` | After tool execution | `result`, `additional_blocks`, `additional_messages` |
807
+ | `AfterToolCallEvent` | After tool execution or answer | `result`, `additional_blocks`, `additional_messages` |
807
808
  | `ResultMessageEvent` | LLM finished (END_TURN) | `continue_messages` |
808
809
 
809
- Tool calls cut off by the output limit fire no tool events; see
810
+ A tool call cut off by the output limit appears in `BeforeToolCallsEvent` but
811
+ fires no `BeforeToolCallEvent` or `AfterToolCallEvent`, and a call answered in
812
+ `BeforeToolCallsEvent` does not run; see
810
813
  [Pre-built events](observability.md#pre-built-events) in Observability.
811
814
 
812
815
  Streaming events (from `flowra.lib.observability`):
@@ -35,8 +35,9 @@ Events from `TurnAgent` (and `ChatAgent`, which uses it internally):
35
35
  | `ThinkingEvent` | Non-empty thinking block in LLM response | — |
36
36
  | `ServerToolCallEvent` | Server-side tool call (e.g. tool search) | — |
37
37
  | `ServerToolResultEvent` | Server-side tool result | — |
38
+ | `BeforeToolCallsEvent` | Message with tool calls, before any runs | `results` |
38
39
  | `BeforeToolCallEvent` | Before tool execution | `tool_use`, `headers`, `meta` |
39
- | `AfterToolCallEvent` | After tool execution | `result`, `additional_blocks`, `additional_messages` |
40
+ | `AfterToolCallEvent` | After tool execution or answer | `result`, `additional_blocks`, `additional_messages` |
40
41
  | `ResultMessageEvent` | LLM finished (END_TURN) | `continue_messages` |
41
42
 
42
43
  `AfterToolCallEvent.additional_blocks` go at the end of that call's result, so a
@@ -71,7 +72,37 @@ runtime.hooks.on(AfterToolCallEvent, add_hint)
71
72
  A tool call cut off by the output limit (see [TurnAgent](agents.md#turnagent)) is
72
73
  never executed. It gets an error result directly, with no `BeforeToolCallEvent`,
73
74
  `AfterToolCallEvent` or `ToolCallSpan`, so hooks that audit or rewrite tool
74
- results don't see it.
75
+ results don't see it; it does appear in `BeforeToolCallsEvent.tool_uses`, and an
76
+ answer put under its id is not used.
77
+
78
+ `BeforeToolCallsEvent` fires once per model message with tool calls, a single
79
+ call included, before any of them runs. A handler answers a call itself by
80
+ putting a `ToolResult` into `results` under the call's id: that call does not
81
+ run, and the others run as usual. An answered call gets no `BeforeToolCallEvent`
82
+ and no `ToolCallSpan`; its result goes through `AfterToolCallEvent` (with
83
+ `prefilled=True`) and `max_consecutive_errors` like any other, in the message's
84
+ order. An error answered without an `error_kind` arrives with
85
+ `ToolErrorKind.EXECUTION`. A key that is not the id of a call in the message
86
+ fails the step with `ValueError`.
87
+
88
+ ```python
89
+ from flowra.agent import Event
90
+ from flowra.lib.turn import BeforeToolCallsEvent
91
+ from flowra.tools import ToolResult
92
+
93
+
94
+ def reply_alone(event: Event[BeforeToolCallsEvent]) -> None:
95
+ if len(event.data.tool_uses) < 2:
96
+ return
97
+ for tool_use in event.data.tool_uses:
98
+ if tool_use.name == "send_reply":
99
+ event.data.results[tool_use.id] = ToolResult(
100
+ content="Call send_reply on its own, in a message with no other tool calls.", is_error=True
101
+ )
102
+
103
+
104
+ runtime.hooks.on(BeforeToolCallsEvent, reply_alone)
105
+ ```
75
106
 
76
107
  A tool call that fails the run (an exception raised around a tool's executor,
77
108
  see [Error handling](tools.md#what-fails-the-run)) has no result:
@@ -4,6 +4,7 @@ from .context import TurnAgentContext
4
4
  from .hook_events import (
5
5
  AfterToolCallEvent,
6
6
  BeforeToolCallEvent,
7
+ BeforeToolCallsEvent,
7
8
  MessageAcceptedEvent,
8
9
  ResultMessageEvent,
9
10
  ServerToolCallEvent,
@@ -20,6 +21,7 @@ from .tool_call import ToolCallAgentContext, agent_tool, get_agent_tool, make_ag
20
21
  __all__ = [
21
22
  "AfterToolCallEvent",
22
23
  "BeforeToolCallEvent",
24
+ "BeforeToolCallsEvent",
23
25
  "MessageAcceptedEvent",
24
26
  "Prompt",
25
27
  "PromptRender",
@@ -36,7 +36,7 @@ from ...llm import (
36
36
  Usage,
37
37
  UserMessage,
38
38
  )
39
- from ...tools import ToolRegistry
39
+ from ...tools import ToolErrorKind, ToolRegistry, ToolResult
40
40
  from ..config_value import resolve_config_value
41
41
  from ..llm_caller import DEFAULT_LLM_CALLER, LLMCaller
42
42
  from ..observability import UsageEvent
@@ -45,6 +45,7 @@ from .context import TurnAgentContext
45
45
  from .hook_events import (
46
46
  AfterToolCallEvent,
47
47
  BeforeToolCallEvent,
48
+ BeforeToolCallsEvent,
48
49
  MessageAcceptedEvent,
49
50
  ResultMessageEvent,
50
51
  ServerToolCallEvent,
@@ -65,6 +66,7 @@ logger = logging.getLogger(__name__)
65
66
 
66
67
  class TurnAgent(Agent[TurnSpec, TurnResult]):
67
68
  __slots__ = (
69
+ "__answered_tool_results",
68
70
  "__caller",
69
71
  "__config",
70
72
  "__consecutive_errors",
@@ -97,6 +99,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
97
99
  self.__usage = store.ephemeral.slot(Scalar[Usage | None], "usage")
98
100
  self.__consecutive_errors = store.ephemeral.slot(Scalar[dict[str, int]], "consecutive_errors")
99
101
  self.__incomplete_tool_result = store.ephemeral.slot(Scalar[ToolResultBlock | None], "incomplete_tool_result")
102
+ self.__answered_tool_results = store.ephemeral.slot(Scalar[list[ToolCallResult]], "answered_tool_results")
100
103
  self.__interrupt = interrupt
101
104
  self.__context = TurnAgentContext()
102
105
  self.__config = config
@@ -246,7 +249,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
246
249
  )
247
250
 
248
251
  case StopReason.TOOL_USE:
249
- return await self.__call_tools(response, tool_uses, registry, allowed_tools, denied_tools)
252
+ return await self.__call_tools(response, tool_uses, None, registry, allowed_tools, denied_tools)
250
253
 
251
254
  case StopReason.REFUSAL:
252
255
  # The provider declined: no output to keep, and an empty assistant message would make
@@ -268,7 +271,7 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
268
271
  # A response cut off mid tool call ends on that call, and only that call is incomplete
269
272
  cut_off = response.message.blocks[-1] is tool_uses[-1]
270
273
  if not cut_off:
271
- return await self.__call_tools(response, tool_uses, registry, allowed_tools, denied_tools)
274
+ return await self.__call_tools(response, tool_uses, None, registry, allowed_tools, denied_tools)
272
275
 
273
276
  # Answer the cut-off call with an error so history stays valid and the model can retry
274
277
  self.__incomplete_tool_result.set(
@@ -278,7 +281,9 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
278
281
  is_error=True,
279
282
  )
280
283
  )
281
- return await self.__call_tools(response, tool_uses[:-1], registry, allowed_tools, denied_tools)
284
+ return await self.__call_tools(
285
+ response, tool_uses[:-1], tool_uses[-1], registry, allowed_tools, denied_tools
286
+ )
282
287
 
283
288
  @step("collect_results")
284
289
  async def collect_results(
@@ -289,13 +294,17 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
289
294
  consecutive_errors = dict(self.__consecutive_errors.get({}))
290
295
  max_consecutive = await resolve_config_value(self.__config.max_consecutive_errors)
291
296
 
292
- for tc_result in results:
297
+ answered = self.__answered_tool_results.get([])
298
+ answered_ids = {tc_result.tool_use.id for tc_result in answered}
299
+ collected = self.__in_message_order([*answered, *results]) if answered else results
300
+ for tc_result in collected:
293
301
  after_event = await self.__hooks.emit(
294
302
  AfterToolCallEvent(
295
303
  tool_use=tc_result.tool_use,
296
304
  result=tc_result.result,
297
305
  error_kind=tc_result.error_kind,
298
306
  context=self.__context,
307
+ prefilled=tc_result.tool_use.id in answered_ids,
299
308
  )
300
309
  )
301
310
 
@@ -332,6 +341,8 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
332
341
  if incomplete_result is not None:
333
342
  result_blocks.append(incomplete_result)
334
343
  self.__incomplete_tool_result.set(None)
344
+ if answered:
345
+ self.__answered_tool_results.set([])
335
346
 
336
347
  self.__consecutive_errors.set(consecutive_errors)
337
348
  user_message = UserMessage(blocks=list(result_blocks))
@@ -370,13 +381,32 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
370
381
  self,
371
382
  response: LLMResponse,
372
383
  tool_uses: list[ToolUseBlock],
384
+ cut_off: ToolUseBlock | None,
373
385
  registry: ToolRegistry | None,
374
386
  allowed_tools: set[str] | None,
375
387
  denied_tools: set[str] | None,
376
388
  ) -> Spawn:
389
+ message_calls = [*tool_uses, cut_off] if cut_off is not None else list(tool_uses)
390
+ # Taken before the emit, so a handler changing the event's list cannot widen what may be answered
391
+ call_ids = {tool_use.id for tool_use in message_calls}
392
+ calls_event = await self.__hooks.emit(BeforeToolCallsEvent(tool_uses=message_calls, context=self.__context))
393
+ for tool_use_id in calls_event.results:
394
+ if tool_use_id not in call_ids:
395
+ raise ValueError(f"No tool call {tool_use_id!r} in this message")
396
+ # Answers are taken from the calls that can run, so the cut-off call keeps its own error result
397
+ answered = [
398
+ self.__answered_result(tool_use, calls_event.results[tool_use.id])
399
+ for tool_use in tool_uses
400
+ if tool_use.id in calls_event.results
401
+ ]
402
+ if answered:
403
+ self.__answered_tool_results.set(answered)
404
+
377
405
  amendments: dict[str, ToolUseBlock] = {}
378
406
  final_calls: list[BeforeToolCallEvent] = []
379
407
  for tool_use in tool_uses:
408
+ if tool_use.id in calls_event.results:
409
+ continue
380
410
  before_event = await self.__hooks.emit(
381
411
  BeforeToolCallEvent(
382
412
  tool_use=tool_use,
@@ -413,6 +443,23 @@ class TurnAgent(Agent[TurnSpec, TurnResult]):
413
443
  ]
414
444
  return Spawn(children=children, then=self.collect_results)
415
445
 
446
+ def __in_message_order(self, results: list[ToolCallResult]) -> list[ToolCallResult]:
447
+ # Every collected call, answered or run, is one of the last assistant message in the turn
448
+ message = next(m for m in reversed(self.__turn_messages.get_all()) if isinstance(m, AssistantMessage))
449
+ position = {block.id: i for i, block in enumerate(message.blocks) if isinstance(block, ToolUseBlock)}
450
+ return sorted(results, key=lambda tc_result: position[tc_result.tool_use.id])
451
+
452
+ @staticmethod
453
+ def __answered_result(tool_use: ToolUseBlock, result: ToolResult) -> ToolCallResult:
454
+ # Consumers tell an error by its kind: an error answered without one gets the default ToolError uses,
455
+ # and an answer that is not an error carries none
456
+ error_kind = (result.error_kind or ToolErrorKind.EXECUTION) if result.is_error else None
457
+ return ToolCallResult(
458
+ tool_use=tool_use,
459
+ result=ToolResultBlock(tool_use_id=tool_use.id, content=result.blocks, is_error=result.is_error),
460
+ error_kind=error_kind,
461
+ )
462
+
416
463
  @staticmethod
417
464
  def __incomplete_tool_use_message(stop_reason: StopReason) -> str:
418
465
  if stop_reason == StopReason.MAX_TOKENS:
@@ -14,12 +14,13 @@ from ...llm import (
14
14
  ToolUseBlock,
15
15
  UserMessage,
16
16
  )
17
- from ...tools import ToolDefinition, ToolErrorKind
17
+ from ...tools import ToolDefinition, ToolErrorKind, ToolResult
18
18
  from .context import TurnAgentContext
19
19
 
20
20
  __all__ = [
21
21
  "AfterToolCallEvent",
22
22
  "BeforeToolCallEvent",
23
+ "BeforeToolCallsEvent",
23
24
  "MessageAcceptedEvent",
24
25
  "ResultMessageEvent",
25
26
  "ServerToolCallEvent",
@@ -96,6 +97,21 @@ class ResultMessageEvent:
96
97
  continue_messages: list[UserMessage] = dataclasses.field(default_factory=list)
97
98
 
98
99
 
100
+ @dataclasses.dataclass(slots=True, kw_only=True)
101
+ class BeforeToolCallsEvent:
102
+ """Emitted once per model message with tool calls, before any of them runs.
103
+
104
+ ``tool_uses`` is every call of the message in its order, a call the response cut off included.
105
+ An answer under the cut-off call's id is not used: that call always gets its own error result.
106
+ Put a ``ToolResult`` into ``results`` under a call's id to answer it without running it: the
107
+ other calls run as usual, and the result reaches the model as that call's answer.
108
+ """
109
+
110
+ tool_uses: list[ToolUseBlock]
111
+ context: TurnAgentContext
112
+ results: dict[str, ToolResult] = dataclasses.field(default_factory=dict)
113
+
114
+
99
115
  @dataclasses.dataclass(slots=True, kw_only=True)
100
116
  class BeforeToolCallEvent:
101
117
  """Emitted before each tool execution.
@@ -112,6 +128,9 @@ class BeforeToolCallEvent:
112
128
 
113
129
  Neither this nor ``AfterToolCallEvent`` fires for a call the response cut
114
130
  off (e.g. at ``MAX_TOKENS``): that call is not run.
131
+
132
+ Not emitted for a call answered in ``BeforeToolCallsEvent``; that call still gets
133
+ ``AfterToolCallEvent``, with ``prefilled=True``.
115
134
  """
116
135
 
117
136
  tool_use: ToolUseBlock
@@ -123,7 +142,7 @@ class BeforeToolCallEvent:
123
142
 
124
143
  @dataclasses.dataclass(slots=True, kw_only=True)
125
144
  class AfterToolCallEvent:
126
- """Emitted after each tool execution.
145
+ """Emitted after each tool execution, and for a call answered in ``BeforeToolCallsEvent``.
127
146
 
128
147
  Replace ``result`` to modify tool output (e.g. sanitize errors, redact PII).
129
148
  Append to ``additional_blocks`` to attach blocks (e.g. a hint as a ``TextBlock``)
@@ -136,12 +155,15 @@ class AfterToolCallEvent:
136
155
  this result.
137
156
  Append to ``additional_messages`` to inject standalone messages after the
138
157
  message with the tool results.
158
+
159
+ ``prefilled`` is true for a call answered in ``BeforeToolCallsEvent``: the tool did not run.
139
160
  """
140
161
 
141
162
  tool_use: ToolUseBlock
142
163
  result: ToolResultBlock
143
164
  error_kind: ToolErrorKind | None
144
165
  context: TurnAgentContext
166
+ prefilled: bool = False
145
167
  additional_blocks: list[ToolResultContentBlock] = dataclasses.field(default_factory=list)
146
168
  additional_messages: list[UserMessage] = dataclasses.field(default_factory=list)
147
169
 
@@ -1,4 +1,4 @@
1
1
  # Updated by CI/CD pipeline (the __version__ line only)
2
2
  __all__ = ["__version__"]
3
3
 
4
- __version__ = "0.0.91"
4
+ __version__ = "0.0.92.dev108"