reflex-agentkit 0.1.0rc1__tar.gz → 0.2.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 (158) hide show
  1. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/CHANGELOG.md +33 -4
  2. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/PKG-INFO +14 -8
  3. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/README.md +12 -7
  4. reflex_agentkit-0.2.0/changes/README.md +11 -0
  5. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/pyproject.toml +1 -0
  6. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/__init__.py +1 -1
  7. reflex_agentkit-0.2.0/src/reflex_agentkit/adapters/_edits.py +35 -0
  8. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/adapters/langgraph.py +19 -3
  9. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/adapters/pydantic_ai.py +62 -3
  10. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/commands.py +4 -3
  11. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/reducer.py +14 -1
  12. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/contract/test_pydantic_ai_facts.py +14 -0
  13. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_langgraph_hitl.py +2 -2
  14. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_pydantic_ai_hitl.py +46 -3
  15. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/test_smoke.py +1 -1
  16. reflex_agentkit-0.2.0/tests/unit/adapters/test_edits.py +44 -0
  17. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/adapters/test_langgraph_resume.py +33 -1
  18. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_commands.py +9 -0
  19. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_state.py +38 -2
  20. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/.gitignore +0 -0
  21. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/LICENSE +0 -0
  22. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/examples/_shared/README.md +0 -0
  23. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/examples/langgraph_dojo/README.md +0 -0
  24. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/examples/pydantic_ai_dojo/README.md +0 -0
  25. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/adapters/__init__.py +0 -0
  26. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/adapters/_require.py +0 -0
  27. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/__init__.py +0 -0
  28. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/approval.py +0 -0
  29. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/chat.py +0 -0
  30. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/composer.py +0 -0
  31. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/messages.py +0 -0
  32. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/shared_state.py +0 -0
  33. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/status.py +0 -0
  34. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/components/tools.py +0 -0
  35. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/__init__.py +0 -0
  36. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/_log.py +0 -0
  37. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/_patch.py +0 -0
  38. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/clients/__init__.py +0 -0
  39. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/clients/base.py +0 -0
  40. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/clients/http.py +0 -0
  41. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/clients/in_process.py +0 -0
  42. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/clients/sse.py +0 -0
  43. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/conversation.py +0 -0
  44. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/errors.py +0 -0
  45. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/models.py +0 -0
  46. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/run_input.py +0 -0
  47. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/core/tools.py +0 -0
  48. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/py.typed +0 -0
  49. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/state/__init__.py +0 -0
  50. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/state/_driver.py +0 -0
  51. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/state/_projection.py +0 -0
  52. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/state/_registry.py +0 -0
  53. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/src/reflex_agentkit/state/agent_state.py +0 -0
  54. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/bench/conftest.py +0 -0
  55. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/bench/test_bench_flush.py +0 -0
  56. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/bench/test_bench_reducer.py +0 -0
  57. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/conftest.py +0 -0
  58. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/contract/test_agui_protocol.py +0 -0
  59. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/contract/test_langgraph_facts.py +0 -0
  60. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/contract/test_reflex_facts.py +0 -0
  61. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/client_tool_marked.jsonl +0 -0
  62. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/custom_ring.jsonl +0 -0
  63. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_cancelled_outcome.jsonl +0 -0
  64. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_interrupt_generic_multi.jsonl +0 -0
  65. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_interrupt_tool_call.jsonl +0 -0
  66. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_mixed_interrupts.jsonl +0 -0
  67. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_success.jsonl +0 -0
  68. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/finish_with_pending_client_tool.jsonl +0 -0
  69. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/hitl_approve.jsonl +0 -0
  70. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/hitl_approve_edited.jsonl +0 -0
  71. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/hitl_cancel_while_paused.jsonl +0 -0
  72. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/hitl_generic_queue.jsonl +0 -0
  73. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/hitl_reject.jsonl +0 -0
  74. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/ignored_families.jsonl +0 -0
  75. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/invalid_event.jsonl +0 -0
  76. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/lifecycle_happy.jsonl +0 -0
  77. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/messages_snapshot.jsonl +0 -0
  78. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/pydantic_ai_approval_recorded.jsonl +0 -0
  79. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/reasoning_encrypted.jsonl +0 -0
  80. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/reasoning_stream.jsonl +0 -0
  81. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/run_error.jsonl +0 -0
  82. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/stale_run_event.jsonl +0 -0
  83. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/state_delta_fails.jsonl +0 -0
  84. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/state_snapshot_delta.jsonl +0 -0
  85. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/steps.jsonl +0 -0
  86. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/text_chunks.jsonl +0 -0
  87. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/text_stream.jsonl +0 -0
  88. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/text_unclosed_at_finish.jsonl +0 -0
  89. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/text_unknown_id.jsonl +0 -0
  90. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/tool_call_args_too_large.jsonl +0 -0
  91. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/tool_call_chunks.jsonl +0 -0
  92. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/tool_call_happy_path.jsonl +0 -0
  93. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/tool_call_invalid_args.jsonl +0 -0
  94. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/tool_call_unknown_id.jsonl +0 -0
  95. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/fixtures/events/unknown_type.jsonl +0 -0
  96. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/__init__.py +0 -0
  97. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/dojo.py +0 -0
  98. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/fake_chat_model.py +0 -0
  99. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/fake_clients.py +0 -0
  100. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/fake_server.py +0 -0
  101. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/fake_state_access.py +0 -0
  102. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/fixtures.py +0 -0
  103. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/invariants.py +0 -0
  104. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/reflex_harness.py +0 -0
  105. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/strategies.py +0 -0
  106. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/helpers/ui_state.py +0 -0
  107. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/conftest.py +0 -0
  108. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_concurrent_clients.py +0 -0
  109. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_fake_server.py +0 -0
  110. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_http_client.py +0 -0
  111. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_multiworker_cancel.py +0 -0
  112. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_responsiveness.py +0 -0
  113. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_secret_hygiene.py +0 -0
  114. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/integration/test_security_vectors.py +0 -0
  115. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/smoke/conftest.py +0 -0
  116. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/smoke/test_dojo_langgraph.py +0 -0
  117. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/smoke/test_dojo_pydantic_ai.py +0 -0
  118. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/smoke/test_quickstart.py +0 -0
  119. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/adapters/test_langgraph_legacy.py +0 -0
  120. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/adapters/test_langgraph_unit.py +0 -0
  121. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/adapters/test_pydantic_ai_unit.py +0 -0
  122. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/adapters/test_require.py +0 -0
  123. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_approval.py +0 -0
  124. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_chat.py +0 -0
  125. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_composer.py +0 -0
  126. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_messages.py +0 -0
  127. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_shared_state.py +0 -0
  128. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_status.py +0 -0
  129. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/components/test_tool_card.py +0 -0
  130. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/clients/test_base.py +0 -0
  131. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/clients/test_in_process.py +0 -0
  132. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/clients/test_sse.py +0 -0
  133. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_conversation.py +0 -0
  134. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_errors.py +0 -0
  135. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_fixtures.py +0 -0
  136. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_isolation.py +0 -0
  137. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_models.py +0 -0
  138. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_patch.py +0 -0
  139. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_finish.py +0 -0
  140. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_lifecycle.py +0 -0
  141. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_properties.py +0 -0
  142. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_text.py +0 -0
  143. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_reducer_tools.py +0 -0
  144. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_run_input.py +0 -0
  145. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/core/test_tools.py +0 -0
  146. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/examples/test_example_models.py +0 -0
  147. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/conftest.py +0 -0
  148. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_agent_state_config.py +0 -0
  149. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_agent_state_handlers.py +0 -0
  150. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_driver_cancel.py +0 -0
  151. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_driver_client_tools.py +0 -0
  152. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_driver_streaming.py +0 -0
  153. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_projection.py +0 -0
  154. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_reflex_harness.py +0 -0
  155. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/state/test_registry.py +0 -0
  156. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/test_deps_latest_workflow.py +0 -0
  157. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/test_public_api.py +0 -0
  158. {reflex_agentkit-0.1.0rc1 → reflex_agentkit-0.2.0}/tests/unit/test_release.py +0 -0
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-31
11
+
12
+ ### Added
13
+
14
+ - The documentation site is published to GitHub Pages from `main`
15
+ (<https://blog.seraph.to/reflex-agentkit/>).
16
+ - The model is told when the user approves a tool call with edited arguments (REQ-035).
17
+ Without this, models tended to report the edited result as a mistake or call the tool
18
+ again with their own arguments.
19
+ - `PydanticAIAgent` appends a notice to the edited call's result. Turn it off with
20
+ `notify_edited_args=False`.
21
+ - For LangGraph, `ApprovalDecision` gains `edited` and `tool_result(result)`. Build the
22
+ `ToolMessage` from `decision.tool_result(...)`.
23
+
24
+ ### Changed
25
+
26
+ - Approving with "edited" arguments identical to the proposed ones now records a plain
27
+ approval, without `editedArgs` (REQ-013.3).
28
+ - `astral-sh/setup-uv` is pinned to a commit SHA in every workflow.
29
+ - Markdown-image exfiltration is an accepted residual risk; markdown images stay
30
+ supported (TD v1.15).
31
+
32
+ ## [0.1.0] - 2026-10-31
33
+
34
+ The first release. `0.1.0rc1` was published on 2026-10-31 as its release candidate.
35
+
10
36
  ### Added
11
37
 
12
38
  - Project skeleton: uv/hatchling packaging, lint, type and test configuration, and CI.
@@ -92,12 +118,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
92
118
  - A weekly workflow that tests against the newest allowed dependency versions and opens
93
119
  an issue when they fail.
94
120
 
95
- ### Changed
96
-
97
- - The package version is `0.1.0rc1`, the release candidate for `0.1.0`.
98
-
99
121
  ### Fixed
100
122
 
123
+ - A tool call that a model sends in one chunk (Ollama through LangGraph) now shows its
124
+ arguments in the approval dialog: a messages snapshot fills in the arguments of a call
125
+ that streamed none. Found in the `0.1.0rc1` walkthrough.
101
126
  - A `TOOL_CALL_RESULT` no longer moves a rejected or failed tool call back to `done`.
102
127
  - A `MESSAGES_SNAPSHOT` now fills in the result of a tool call it keeps, so LangGraph tool
103
128
  calls end as `done` after their tool node runs.
@@ -113,3 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113
138
  registered as Reflex event handlers, so the browser cannot call them.
114
139
  - The latest-dependencies workflow runs the newest, unvetted versions without a token or
115
140
  stored git credentials; a separate job reports failures.
141
+
142
+ [Unreleased]: https://github.com/ecrespo/reflex-agentkit/compare/v0.2.0...HEAD
143
+ [0.2.0]: https://github.com/ecrespo/reflex-agentkit/compare/v0.1.0...v0.2.0
144
+ [0.1.0]: https://github.com/ecrespo/reflex-agentkit/releases/tag/v0.1.0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: reflex-agentkit
3
- Version: 0.1.0rc1
3
+ Version: 0.2.0
4
4
  Summary: The production UI layer for Python AI agents, built on Reflex and the AG-UI protocol.
5
5
  Project-URL: Homepage, https://github.com/ecrespo/reflex-agentkit
6
+ Project-URL: Documentation, https://blog.seraph.to/reflex-agentkit/
6
7
  Project-URL: Repository, https://github.com/ecrespo/reflex-agentkit
7
8
  Project-URL: Issues, https://github.com/ecrespo/reflex-agentkit/issues
8
9
  Project-URL: Changelog, https://github.com/ecrespo/reflex-agentkit/blob/main/CHANGELOG.md
@@ -44,10 +45,11 @@ chat, tool-call cards and human-in-the-loop approval for LangGraph, PydanticAI a
44
45
  AG-UI-compatible agent. The production UI layer your agent was missing — no TypeScript,
45
46
  no Streamlit.
46
47
 
47
- > **Status: pre-release.** Everything in v0.1 is implemented and tested: the pure core,
48
+ > **Status: `0.2.0`.** Everything in v0.1 is implemented and tested: the pure core,
48
49
  > the agent clients, the Reflex state engine (`AgentState`), the chat components, both
49
- > adapters (PydanticAI and LangGraph) and the AG-UI Dojo examples. The release candidate
50
- > `0.1.0rc1` is ready for PyPI. Documentation: [`docs/`](docs/) (`uv run mkdocs serve`).
50
+ > adapters (PydanticAI and LangGraph) and the AG-UI Dojo examples. `0.2.0` tells the model
51
+ > when the user edits a tool call's arguments before approving it.
52
+ > `pip install "reflex-agentkit[pydantic-ai]"` or `uv add "reflex-agentkit[pydantic-ai]"`. Documentation: https://blog.seraph.to/reflex-agentkit/
51
53
 
52
54
  ## Why
53
55
 
@@ -161,7 +163,8 @@ an accessible label.
161
163
 
162
164
  The MkDocs site in [`docs/`](docs/) covers getting started (uv and pip), the concepts,
163
165
  guides (your own UI, human in the loop, sidecar deployment, local models with Ollama), the
164
- API reference, the security model and troubleshooting. Preview it with
166
+ API reference, the security model and troubleshooting. It is published at
167
+ <https://blog.seraph.to/reflex-agentkit/> from `main`; preview it locally with
165
168
  `uv run mkdocs serve`.
166
169
 
167
170
  ## Progress
@@ -176,10 +179,11 @@ Work follows the approved spec chain in [`specs/`](specs/) and the task list in
176
179
  | WP2 — Clients | `AgentClient`/`AGUIAgent` protocols, SSE parser, `HttpAGUIClient`, `InProcessAGUIClient` | ✅ done |
177
180
  | WP3 — Reflex state engine | `AgentState` mixin, streaming driver, cancellation, client-tool rounds | ✅ done |
178
181
  | WP4 — PydanticAI adapter | `PydanticAIAgent`: a PydanticAI agent as an `AGUIAgent`, tool approvals, client tools | ✅ done |
179
- | WP5 — Components and quickstart | `agent_chat` and its parts, quickstart example, release workflow | ✅ done (PyPI publish pending the release setup) |
182
+ | WP5 — Components and quickstart | `agent_chat` and its parts, quickstart example, release workflow | ✅ done |
180
183
  | WP6 — LangGraph adapter | `LangGraphAGUIAgent`, `request_tool_approval`, legacy interrupts | ✅ done |
181
184
  | WP7 — Dojo examples | The five AG-UI Dojo demos for PydanticAI and LangGraph | ✅ done |
182
- | WP8 — Hardening, docs, release | Security review, benchmarks, documentation site, `0.1.0` on PyPI | 🚧 in progress (security, benchmarks and docs done) |
185
+ | WP8 — Hardening, docs, release | Security review, benchmarks, documentation site, `0.1.0` on PyPI | ✅ done |
186
+ | Change: edited-args notice | REQ-035: the model learns about edited tool arguments (`changes/_archive/`) | ✅ `0.2.0` |
183
187
 
184
188
  ## What works today: `reflex_agentkit.core`
185
189
 
@@ -333,7 +337,8 @@ app.add_page(index)
333
337
 
334
338
  Install the extra (`reflex-agentkit[pydantic-ai]`) and wrap the agent. Tools declared with
335
339
  `requires_approval=True` pause the run for `approve_tool` / `reject_tool` with no extra
336
- configuration; edited arguments reach the tool.
340
+ configuration; edited arguments reach the tool, and the tool's result tells the model the
341
+ user changed them (`notify_edited_args=False` turns that off).
337
342
 
338
343
  ```python
339
344
  from typing import ClassVar
@@ -383,6 +388,7 @@ def tools(state: MessagesState) -> dict[str, Any]:
383
388
  call = last.tool_calls[0]
384
389
  decision = request_tool_approval(str(call["id"]), call["name"], call["args"])
385
390
  content = f"Booked {decision.args['city']}" if decision.approved else "Not booked"
391
+ content = decision.tool_result(content) # tells the model about edited arguments
386
392
  return {"messages": [ToolMessage(content=content, tool_call_id=call["id"])]}
387
393
 
388
394
 
@@ -5,10 +5,11 @@ chat, tool-call cards and human-in-the-loop approval for LangGraph, PydanticAI a
5
5
  AG-UI-compatible agent. The production UI layer your agent was missing — no TypeScript,
6
6
  no Streamlit.
7
7
 
8
- > **Status: pre-release.** Everything in v0.1 is implemented and tested: the pure core,
8
+ > **Status: `0.2.0`.** Everything in v0.1 is implemented and tested: the pure core,
9
9
  > the agent clients, the Reflex state engine (`AgentState`), the chat components, both
10
- > adapters (PydanticAI and LangGraph) and the AG-UI Dojo examples. The release candidate
11
- > `0.1.0rc1` is ready for PyPI. Documentation: [`docs/`](docs/) (`uv run mkdocs serve`).
10
+ > adapters (PydanticAI and LangGraph) and the AG-UI Dojo examples. `0.2.0` tells the model
11
+ > when the user edits a tool call's arguments before approving it.
12
+ > `pip install "reflex-agentkit[pydantic-ai]"` or `uv add "reflex-agentkit[pydantic-ai]"`. Documentation: https://blog.seraph.to/reflex-agentkit/
12
13
 
13
14
  ## Why
14
15
 
@@ -122,7 +123,8 @@ an accessible label.
122
123
 
123
124
  The MkDocs site in [`docs/`](docs/) covers getting started (uv and pip), the concepts,
124
125
  guides (your own UI, human in the loop, sidecar deployment, local models with Ollama), the
125
- API reference, the security model and troubleshooting. Preview it with
126
+ API reference, the security model and troubleshooting. It is published at
127
+ <https://blog.seraph.to/reflex-agentkit/> from `main`; preview it locally with
126
128
  `uv run mkdocs serve`.
127
129
 
128
130
  ## Progress
@@ -137,10 +139,11 @@ Work follows the approved spec chain in [`specs/`](specs/) and the task list in
137
139
  | WP2 — Clients | `AgentClient`/`AGUIAgent` protocols, SSE parser, `HttpAGUIClient`, `InProcessAGUIClient` | ✅ done |
138
140
  | WP3 — Reflex state engine | `AgentState` mixin, streaming driver, cancellation, client-tool rounds | ✅ done |
139
141
  | WP4 — PydanticAI adapter | `PydanticAIAgent`: a PydanticAI agent as an `AGUIAgent`, tool approvals, client tools | ✅ done |
140
- | WP5 — Components and quickstart | `agent_chat` and its parts, quickstart example, release workflow | ✅ done (PyPI publish pending the release setup) |
142
+ | WP5 — Components and quickstart | `agent_chat` and its parts, quickstart example, release workflow | ✅ done |
141
143
  | WP6 — LangGraph adapter | `LangGraphAGUIAgent`, `request_tool_approval`, legacy interrupts | ✅ done |
142
144
  | WP7 — Dojo examples | The five AG-UI Dojo demos for PydanticAI and LangGraph | ✅ done |
143
- | WP8 — Hardening, docs, release | Security review, benchmarks, documentation site, `0.1.0` on PyPI | 🚧 in progress (security, benchmarks and docs done) |
145
+ | WP8 — Hardening, docs, release | Security review, benchmarks, documentation site, `0.1.0` on PyPI | ✅ done |
146
+ | Change: edited-args notice | REQ-035: the model learns about edited tool arguments (`changes/_archive/`) | ✅ `0.2.0` |
144
147
 
145
148
  ## What works today: `reflex_agentkit.core`
146
149
 
@@ -294,7 +297,8 @@ app.add_page(index)
294
297
 
295
298
  Install the extra (`reflex-agentkit[pydantic-ai]`) and wrap the agent. Tools declared with
296
299
  `requires_approval=True` pause the run for `approve_tool` / `reject_tool` with no extra
297
- configuration; edited arguments reach the tool.
300
+ configuration; edited arguments reach the tool, and the tool's result tells the model the
301
+ user changed them (`notify_edited_args=False` turns that off).
298
302
 
299
303
  ```python
300
304
  from typing import ClassVar
@@ -344,6 +348,7 @@ def tools(state: MessagesState) -> dict[str, Any]:
344
348
  call = last.tool_calls[0]
345
349
  decision = request_tool_approval(str(call["id"]), call["name"], call["args"])
346
350
  content = f"Booked {decision.args['city']}" if decision.approved else "Not booked"
351
+ content = decision.tool_result(content) # tells the model about edited arguments
347
352
  return {"messages": [ToolMessage(content=content, tool_call_id=call["id"])]}
348
353
 
349
354
 
@@ -0,0 +1,11 @@
1
+ # Changes
2
+
3
+ Proposals that change the observable behavior or the public API of a released
4
+ reflex-agentkit (CLAUDE.md, spec-anchored process). Each change lives in
5
+ `changes/<yyyy-mm>-<slug>/` with `proposal.md`, `delta-spec.md` and `tasks.md`. Once the
6
+ maintainer approves it and it ships, the delta is folded into `specs/` in the same PR and
7
+ the folder moves to `changes/_archive/`.
8
+
9
+ | Change | Status |
10
+ |---|---|
11
+ | [`2026-11-edited-args-notice`](_archive/2026-11-edited-args-notice/proposal.md) | Released in `0.2.0`, folded into `specs/`, archived |
@@ -36,6 +36,7 @@ all = ["reflex-agentkit[pydantic-ai,langgraph]"]
36
36
 
37
37
  [project.urls]
38
38
  Homepage = "https://github.com/ecrespo/reflex-agentkit"
39
+ Documentation = "https://blog.seraph.to/reflex-agentkit/"
39
40
  Repository = "https://github.com/ecrespo/reflex-agentkit"
40
41
  Issues = "https://github.com/ecrespo/reflex-agentkit/issues"
41
42
  Changelog = "https://github.com/ecrespo/reflex-agentkit/blob/main/CHANGELOG.md"
@@ -29,7 +29,7 @@ if TYPE_CHECKING: # type checkers see the lazy names; runtime imports them on f
29
29
  from reflex_agentkit.components.chat import agent_chat as agent_chat
30
30
  from reflex_agentkit.state.agent_state import AgentState as AgentState
31
31
 
32
- __version__ = "0.1.0rc1"
32
+ __version__ = "0.2.0"
33
33
 
34
34
  # Public name -> module that defines it (API §1, §3).
35
35
  _LAZY_EXPORTS: dict[str, str] = {
@@ -0,0 +1,35 @@
1
+ """The edited-arguments notice both adapters give the model (REQ-035, DD-016).
2
+
3
+ When the user approves a tool call with edited arguments, the model only sees a result
4
+ that does not match what it asked for, and may "correct" the user by calling the tool
5
+ again. The notice tells it the edit was deliberate.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from collections.abc import Mapping
12
+ from typing import Any
13
+
14
+ import pydantic_core
15
+
16
+ __all__ = ["EDITED_ARGS_NOTICE", "with_edited_args_notice"]
17
+
18
+ EDITED_ARGS_NOTICE = (
19
+ "Note: the user edited this tool call's arguments before approving it, so the tool "
20
+ "ran with: {args}. These are the arguments the user wants: do not call the tool "
21
+ "again to restore the earlier ones, and do not report the change as an error."
22
+ )
23
+ """Normative text (REQ-035); tests pin it, so a change here is a spec change."""
24
+
25
+
26
+ def with_edited_args_notice(result: Any, args: Mapping[str, Any]) -> str:
27
+ """``result`` followed by a blank line and the notice naming ``args``.
28
+
29
+ A non-string result is serialized to JSON first, as PydanticAI does for tool returns.
30
+ """
31
+ text = (
32
+ result if isinstance(result, str) else pydantic_core.to_json(result, fallback=str).decode()
33
+ )
34
+ shown = json.dumps(dict(args), ensure_ascii=False, default=str)
35
+ return f"{text}\n\n{EDITED_ARGS_NOTICE.format(args=shown)}"
@@ -37,6 +37,7 @@ from langgraph.graph.state import CompiledStateGraph # noqa: E402
37
37
  from langgraph.types import Command, interrupt # noqa: E402
38
38
  from langgraph.types import Interrupt as LangGraphInterrupt # noqa: E402
39
39
 
40
+ from reflex_agentkit.adapters._edits import with_edited_args_notice # noqa: E402
40
41
  from reflex_agentkit.core.errors import ConfigurationError # noqa: E402
41
42
 
42
43
  __all__ = ["ApprovalDecision", "LangGraphAGUIAgent", "request_tool_approval"]
@@ -76,11 +77,26 @@ class ApprovalDecision:
76
77
  otherwise the original ones.
77
78
  reason: The rejection reason, ``"cancelled"`` when the run was cancelled while
78
79
  waiting, or ``None``.
80
+ edited: Whether the user approved with arguments that differ from the proposed
81
+ ones (REQ-035.2, REQ-035.5).
79
82
  """
80
83
 
81
84
  approved: bool
82
85
  args: dict[str, Any]
83
86
  reason: str | None = None
87
+ edited: bool = False
88
+
89
+ def tool_result(self, result: Any) -> Any:
90
+ """The result to give the model: with the edited-arguments notice when ``edited``.
91
+
92
+ Use it to build the ``ToolMessage`` so the model knows the user changed the
93
+ arguments on purpose (REQ-035.2). Without edits ``result`` comes back unchanged.
94
+ """
95
+ if not self.edited:
96
+ return result
97
+ text = with_edited_args_notice(result, self.args)
98
+ _log.debug("edited-arguments notice: %s", text)
99
+ return text
84
100
 
85
101
 
86
102
  def request_tool_approval(
@@ -120,9 +136,9 @@ def _decision(answer: Any, args: dict[str, Any]) -> ApprovalDecision:
120
136
  if answer.get("approved") is not True:
121
137
  return ApprovalDecision(approved=False, args=args, reason=reason)
122
138
  edited = answer.get("editedArgs")
123
- return ApprovalDecision(
124
- approved=True, args=dict(edited) if isinstance(edited, Mapping) else args
125
- )
139
+ if not isinstance(edited, Mapping) or dict(edited) == args:
140
+ return ApprovalDecision(approved=True, args=args)
141
+ return ApprovalDecision(approved=True, args=dict(edited), edited=True)
126
142
 
127
143
 
128
144
  class LangGraphAGUIAgent(LangGraphAgent): # type: ignore[misc] # untyped upstream base
@@ -6,21 +6,31 @@ from reflex_agentkit.adapters._require import require
6
6
 
7
7
  require("pydantic-ai", "pydantic_ai", "pydantic_ai.ui.ag_ui")
8
8
 
9
- import inspect # noqa: E402 - the guard must run before the optional imports
10
- from collections.abc import AsyncIterator, Awaitable, Callable, Sequence # noqa: E402
9
+ import dataclasses # noqa: E402 - the guard must run before the optional imports
10
+ import inspect # noqa: E402
11
+ import logging # noqa: E402
12
+ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, Sequence # noqa: E402
11
13
  from typing import Any # noqa: E402
12
14
 
13
15
  from ag_ui.core import BaseEvent, RunAgentInput # noqa: E402
14
- from pydantic_ai import DeferredToolRequests # noqa: E402
16
+ from pydantic_ai import DeferredToolRequests, RunContext # noqa: E402
15
17
  from pydantic_ai.agent import AbstractAgent # noqa: E402
18
+ from pydantic_ai.capabilities import AbstractCapability # noqa: E402
19
+ from pydantic_ai.exceptions import UserError # noqa: E402
20
+ from pydantic_ai.messages import ToolCallPart # noqa: E402
21
+ from pydantic_ai.tools import ToolDefinition # noqa: E402
16
22
  from pydantic_ai.ui.ag_ui import AGUIAdapter # noqa: E402
23
+ from pydantic_ai.ui.ag_ui._interrupt import interrupt_id_to_tool_call_id # noqa: E402
17
24
 
25
+ from reflex_agentkit.adapters._edits import with_edited_args_notice # noqa: E402
18
26
  from reflex_agentkit.core.errors import ConfigurationError # noqa: E402
19
27
 
20
28
  __all__ = ["PydanticAIAgent"]
21
29
 
22
30
  DepsFactory = Callable[[RunAgentInput], Any | Awaitable[Any]]
23
31
 
32
+ _log = logging.getLogger(__name__)
33
+
24
34
 
25
35
  class PydanticAIAgent:
26
36
  """Run a PydanticAI agent as an ``AGUIAgent`` (REQ-022.1).
@@ -34,6 +44,9 @@ class PydanticAIAgent:
34
44
  deps: Dependencies passed to every run.
35
45
  deps_factory: Builds the dependencies of one run from its input (sync or async).
36
46
  Mutually exclusive with ``deps``.
47
+ notify_edited_args: Tell the model when the user approved a tool call with
48
+ edited arguments, by appending a notice to the tool's result (REQ-035.1,
49
+ REQ-035.4).
37
50
  run_kwargs: Forwarded to ``AGUIAdapter.run_stream`` (``model_settings``,
38
51
  ``usage_limits``, ``model``, ...).
39
52
  """
@@ -44,6 +57,7 @@ class PydanticAIAgent:
44
57
  *,
45
58
  deps: Any = None,
46
59
  deps_factory: DepsFactory | None = None,
60
+ notify_edited_args: bool = True,
47
61
  **run_kwargs: Any,
48
62
  ) -> None:
49
63
  if not isinstance(agent, AbstractAgent):
@@ -56,6 +70,7 @@ class PydanticAIAgent:
56
70
  self._deps = deps
57
71
  self._deps_factory = deps_factory
58
72
  self._run_kwargs = run_kwargs
73
+ self._notify_edited_args = notify_edited_args
59
74
  self._output_type = _with_deferred(agent.output_type)
60
75
 
61
76
  def __repr__(self) -> str:
@@ -71,6 +86,9 @@ class PydanticAIAgent:
71
86
  kwargs = dict(self._run_kwargs)
72
87
  if self._output_type is not None:
73
88
  kwargs.setdefault("output_type", self._output_type)
89
+ edits = _edited_calls(run_input) if self._notify_edited_args else {}
90
+ if edits:
91
+ kwargs["capabilities"] = [*(kwargs.get("capabilities") or ()), _EditNotice(edits)]
74
92
  adapter = AGUIAdapter(agent=self._agent, run_input=run_input)
75
93
  stream = adapter.run_stream(deps=deps, **kwargs)
76
94
  try:
@@ -91,3 +109,44 @@ def _with_deferred(output_type: Any) -> list[Any] | None:
91
109
  if any(t is DeferredToolRequests for t in types):
92
110
  return None
93
111
  return [*types, DeferredToolRequests]
112
+
113
+
114
+ def _edited_calls(run_input: RunAgentInput) -> dict[str, dict[str, Any]]:
115
+ """Tool call ID → edited arguments, for every approval resumed with ``editedArgs``."""
116
+ edits: dict[str, dict[str, Any]] = {}
117
+ for entry in run_input.resume or ():
118
+ payload = entry.payload
119
+ if entry.status != "resolved" or not isinstance(payload, Mapping):
120
+ continue
121
+ edited = payload.get("editedArgs")
122
+ if payload.get("approved") is not True or not isinstance(edited, Mapping):
123
+ continue
124
+ try:
125
+ call_id = interrupt_id_to_tool_call_id(entry.interrupt_id)
126
+ except UserError: # not one of PydanticAI's approval interrupts
127
+ continue
128
+ edits[call_id] = dict(edited)
129
+ return edits
130
+
131
+
132
+ @dataclasses.dataclass
133
+ class _EditNotice(AbstractCapability[Any]):
134
+ """Append the edited-arguments notice to the results of edited calls (DD-016)."""
135
+
136
+ edits: dict[str, dict[str, Any]]
137
+
138
+ async def after_tool_execute(
139
+ self,
140
+ ctx: RunContext[Any],
141
+ *,
142
+ call: ToolCallPart,
143
+ tool_def: ToolDefinition,
144
+ args: Any,
145
+ result: Any,
146
+ ) -> Any:
147
+ edited = self.edits.get(call.tool_call_id)
148
+ if edited is None:
149
+ return result
150
+ text = with_edited_args_notice(result, edited)
151
+ _log.debug("edited-arguments notice: %s", text)
152
+ return text
@@ -121,7 +121,7 @@ def _with_call(conv: ConversationState, call: ToolCall) -> tuple[ToolCall, ...]:
121
121
  def apply_approval(
122
122
  conv: ConversationState, call_id: str, edited_args: object = None
123
123
  ) -> ConversationState:
124
- """Approve a pending tool call, optionally with edited arguments (REQ-013.2-3, 013.7).
124
+ """Approve a pending tool call, optionally with edited arguments (REQ-013.2-3, 013.7, 035.5).
125
125
 
126
126
  A ``str`` ``edited_args`` is parsed as JSON. Edits that are not a JSON object or that
127
127
  exceed :data:`MAX_PAYLOAD_BYTES` serialized set ``INVALID_EDITED_ARGS`` and leave the
@@ -146,8 +146,9 @@ def apply_approval(
146
146
  return _invalid_edit(conv, "the edited arguments are not serializable as JSON")
147
147
  if too_large:
148
148
  return _invalid_edit(conv, f"the edited arguments exceed {MAX_PAYLOAD_BYTES} bytes")
149
- args = edited_args
150
- payload["editedArgs"] = edited_args
149
+ if edited_args != call.args: # an unchanged "edit" is a plain approval (REQ-035.5)
150
+ args = edited_args
151
+ payload["editedArgs"] = edited_args
151
152
  running = replace(call, status=ToolCallStatus.RUNNING, args=args)
152
153
  answer = InterruptAnswer(interrupt_id=interrupt.id, status="resolved", payload=payload)
153
154
  return _record(conv, answer, tool_calls=_with_call(conv, running))
@@ -816,6 +816,16 @@ def _reasoning_encrypted(
816
816
  # --- MESSAGES_SNAPSHOT ---------------------------------------------------------------------
817
817
 
818
818
 
819
+ def _with_snapshot_args(call: ToolCall, arguments: str) -> ToolCall:
820
+ """``call`` with the snapshot's arguments if it streamed none and they are an object."""
821
+ if call.args_text.strip() or call.args or not arguments.strip():
822
+ return call # streamed or edited arguments win
823
+ args = _parse_args(arguments)
824
+ if args is None:
825
+ return call
826
+ return dataclasses.replace(call, args_text=arguments, args=args)
827
+
828
+
819
829
  def _snapshot_call(
820
830
  conv: ConversationState, tool_call: Any, parent_id: str, results: dict[str, Any]
821
831
  ) -> ToolCall:
@@ -823,11 +833,14 @@ def _snapshot_call(
823
833
 
824
834
  A kept record with no result yet takes the one from the snapshot's ``tool`` message,
825
835
  as ``TOOL_CALL_RESULT`` would: ag-ui-langgraph sends a tool node's result only here
826
- (DM v1.10). ``rejected`` and ``error`` stay terminal.
836
+ (DM v1.10). ``rejected`` and ``error`` stay terminal. A kept record that streamed no
837
+ arguments takes the snapshot's, when they parse to an object (DM v1.11): a tool call
838
+ sent in one chunk reaches ag-ui-langgraph's stream with no ``TOOL_CALL_ARGS``.
827
839
  """
828
840
  existing = _get_call(conv, tool_call.id)
829
841
  result = results.get(tool_call.id)
830
842
  if existing is not None:
843
+ existing = _with_snapshot_args(existing, tool_call.function.arguments)
831
844
  if result is None or existing.result is not None:
832
845
  return existing
833
846
  status: ToolCallStatus
@@ -132,3 +132,17 @@ async def test_TD_8_4_2_frontend_tool_call_ends_with_success_not_interrupt() ->
132
132
  assert last.outcome is None or last.outcome.type == "success"
133
133
  pending = getattr(last.outcome, "pending_tool_call_ids", None)
134
134
  assert pending in (None, [starts[0].tool_call_id])
135
+
136
+
137
+ def test_TD_DD_016_interrupt_ids_map_back_to_tool_call_ids() -> None:
138
+ """DD-016: the private helper the adapter uses to find the edited call still exists."""
139
+ from pydantic_ai.ui.ag_ui._interrupt import interrupt_id_to_tool_call_id
140
+
141
+ assert interrupt_id_to_tool_call_id("int-call_1") == "call_1"
142
+
143
+
144
+ def test_TD_DD_016_run_stream_accepts_run_level_capabilities() -> None:
145
+ """DD-016: the notice rides on a per-run capability, merged with the agent's own."""
146
+ import inspect
147
+
148
+ assert "capabilities" in inspect.signature(AGUIAdapter.run_stream).parameters
@@ -128,14 +128,14 @@ async def _pause_for_approval(rx_: ReflexHarness, token: str) -> HotelGraphState
128
128
  async def test_REQ_034_3_approval_with_edits_reaches_the_tool(
129
129
  reflex_harness: ReflexHarness,
130
130
  ) -> None:
131
- """REQ-034.3: the helper returns the decision, edited args included."""
131
+ """REQ-034.3, REQ-035.2: the helper returns the decision, edits flagged."""
132
132
  await _pause_for_approval(reflex_harness, "edit")
133
133
  await reflex_harness.call(
134
134
  "edit", HotelGraphState, "approve_tool", call_id="c1", edited_args={"city": "Porto"}
135
135
  )
136
136
  state = await reflex_harness.state("edit", HotelGraphState)
137
137
  assert state.run_status == RunStatus.IDLE, state.error
138
- assert decisions == [ApprovalDecision(approved=True, args={"city": "Porto"})]
138
+ assert decisions == [ApprovalDecision(approved=True, args={"city": "Porto"}, edited=True)]
139
139
  call = next(c for c in state.tool_calls if c.id == "c1")
140
140
  assert call.status == ToolCallStatus.DONE
141
141
  assert call.result == "Booked Porto"
@@ -6,6 +6,7 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import logging
9
10
  from typing import ClassVar
10
11
 
11
12
  import pytest
@@ -61,12 +62,19 @@ async def test_REQ_022_2_approve_runs_the_tool(reflex_harness: ReflexHarness) ->
61
62
  assert state.run_status == RunStatus.IDLE
62
63
  assert booked == ["a"] # TestModel's generated argument
63
64
  call = state.tool_calls[0]
64
- assert (call.status, call.result) == (ToolCallStatus.DONE, "booked a")
65
+ assert (call.status, call.result) == (ToolCallStatus.DONE, "booked a") # REQ-035.3
65
66
  assert state.messages[-1].role == "assistant"
66
67
 
67
68
 
69
+ NOTICE = (
70
+ "booked Porto\n\nNote: the user edited this tool call's arguments before approving it, "
71
+ 'so the tool ran with: {"city": "Porto"}. These are the arguments the user wants: do not '
72
+ "call the tool again to restore the earlier ones, and do not report the change as an error."
73
+ )
74
+
75
+
68
76
  async def test_REQ_022_3_approve_with_edited_args(reflex_harness: ReflexHarness) -> None:
69
- """REQ-022.3: the tool receives the edited arguments."""
77
+ """REQ-022.3, REQ-035.1: the tool gets the edits and the model is told about them."""
70
78
  state = await _pause(reflex_harness, "edit")
71
79
  call_id = state.pending_approvals[0].id
72
80
  await reflex_harness.call(
@@ -74,10 +82,45 @@ async def test_REQ_022_3_approve_with_edited_args(reflex_harness: ReflexHarness)
74
82
  )
75
83
  state = await reflex_harness.state("edit", HotelState)
76
84
  assert booked == ["Porto"]
77
- assert state.tool_calls[0].result == "booked Porto"
85
+ assert state.tool_calls[0].result == NOTICE
78
86
  assert state.run_status == RunStatus.IDLE
79
87
 
80
88
 
89
+ class QuietHotelState(AgentState, rx.State):
90
+ agent: ClassVar = PydanticAIAgent(_hotel_agent(), notify_edited_args=False)
91
+
92
+
93
+ async def test_REQ_035_4_notice_can_be_turned_off(reflex_harness: ReflexHarness) -> None:
94
+ """REQ-035.4: ``notify_edited_args=False`` leaves the tool result as the tool returned it."""
95
+ await reflex_harness.call("quiet", QuietHotelState, "send_message", text="book a hotel")
96
+ state = await reflex_harness.state("quiet", QuietHotelState)
97
+ call_id = state.pending_approvals[0].id
98
+ await reflex_harness.call(
99
+ "quiet", QuietHotelState, "approve_tool", call_id=call_id, edited_args={"city": "Porto"}
100
+ )
101
+ state = await reflex_harness.state("quiet", QuietHotelState)
102
+ assert booked == ["Porto"]
103
+ assert state.tool_calls[0].result == "booked Porto"
104
+
105
+
106
+ async def test_REQ_035_6_notice_is_logged_only_at_debug(
107
+ reflex_harness: ReflexHarness, caplog: pytest.LogCaptureFixture
108
+ ) -> None:
109
+ """REQ-035.6: the notice holds user content, so nothing about it is logged above DEBUG."""
110
+ caplog.set_level(logging.DEBUG, logger="reflex_agentkit")
111
+ state = await _pause(reflex_harness, "log")
112
+ await reflex_harness.call(
113
+ "log",
114
+ HotelState,
115
+ "approve_tool",
116
+ call_id=state.pending_approvals[0].id,
117
+ edited_args={"city": "Porto"},
118
+ )
119
+ porto = [r for r in caplog.records if "Porto" in r.getMessage()]
120
+ assert porto, "the notice is logged at DEBUG"
121
+ assert all(r.levelno == logging.DEBUG for r in porto)
122
+
123
+
81
124
  async def test_REQ_022_2_reject_does_not_run_the_tool(reflex_harness: ReflexHarness) -> None:
82
125
  """REQ-022.2: rejection resumes without running the tool; the model sees the denial."""
83
126
  state = await _pause(reflex_harness, "reject")
@@ -9,7 +9,7 @@ def test_package_imports() -> None:
9
9
  """REQ-031.1: the package and its pure core import cleanly."""
10
10
  import reflex_agentkit.core
11
11
 
12
- assert reflex_agentkit.__version__ == "0.1.0rc1"
12
+ assert reflex_agentkit.__version__ == "0.2.0"
13
13
 
14
14
 
15
15
  def test_unknown_attribute_raises_attribute_error() -> None:
@@ -0,0 +1,44 @@
1
+ """The edited-arguments notice shared by both adapters (REQ-035, DD-016)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from reflex_agentkit.adapters._edits import EDITED_ARGS_NOTICE, with_edited_args_notice
6
+
7
+
8
+ def test_REQ_035_1_notice_text_is_pinned() -> None:
9
+ """REQ-035: the normative notice; changing it changes what every model reads."""
10
+ assert EDITED_ARGS_NOTICE == (
11
+ "Note: the user edited this tool call's arguments before approving it, so the tool "
12
+ "ran with: {args}. These are the arguments the user wants: do not call the tool "
13
+ "again to restore the earlier ones, and do not report the change as an error."
14
+ )
15
+
16
+
17
+ def test_REQ_035_1_string_results_get_the_notice_after_a_blank_line() -> None:
18
+ text = with_edited_args_notice("Booked 3 nights in Porto.", {"nights": 3, "city": "Porto"})
19
+ assert text == (
20
+ "Booked 3 nights in Porto.\n\nNote: the user edited this tool call's arguments "
21
+ 'before approving it, so the tool ran with: {"nights": 3, "city": "Porto"}. '
22
+ "These are the arguments the user wants: do not call the tool again to restore the "
23
+ "earlier ones, and do not report the change as an error."
24
+ )
25
+
26
+
27
+ def test_REQ_035_1_other_results_are_serialized_as_json_first() -> None:
28
+ """Non-string results are serialized the way PydanticAI serializes tool returns."""
29
+ text = with_edited_args_notice({"booked": True, "city": "Porto"}, {"city": "Porto"})
30
+ assert text.startswith('{"booked":true,"city":"Porto"}\n\nNote: ')
31
+
32
+
33
+ def test_REQ_035_1_non_json_values_fall_back_to_str() -> None:
34
+ class Ticket:
35
+ def __str__(self) -> str:
36
+ return "ticket-7"
37
+
38
+ text = with_edited_args_notice(Ticket(), {"when": Ticket()})
39
+ assert text.startswith('"ticket-7"\n\nNote: ')
40
+ assert 'the tool ran with: {"when": "ticket-7"}. ' in text
41
+
42
+
43
+ def test_REQ_035_1_unicode_stays_readable() -> None:
44
+ assert '{"city": "Málaga"}' in with_edited_args_notice("ok", {"city": "Málaga"})