reactifact 0.13.0__tar.gz → 0.14.1__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 (316) hide show
  1. {reactifact-0.13.0/reactifact.egg-info → reactifact-0.14.1}/PKG-INFO +13 -4
  2. {reactifact-0.13.0 → reactifact-0.14.1}/README.md +10 -3
  3. {reactifact-0.13.0 → reactifact-0.14.1}/pyproject.toml +7 -1
  4. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/__init__.py +7 -2
  5. reactifact-0.14.1/reactifact/a2a/__init__.py +77 -0
  6. reactifact-0.14.1/reactifact/a2a/client.py +282 -0
  7. reactifact-0.14.1/reactifact/a2a/models.py +294 -0
  8. reactifact-0.14.1/reactifact/a2a/remote.py +178 -0
  9. reactifact-0.14.1/reactifact/a2a/server.py +381 -0
  10. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/branching.py +29 -2
  11. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/budget.py +4 -0
  12. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/chat.py +14 -1
  13. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/replay.py +10 -0
  14. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/context.py +62 -1
  15. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/effects.py +27 -0
  16. reactifact-0.14.1/reactifact/interrupt.py +54 -0
  17. reactifact-0.14.1/reactifact/logging.py +222 -0
  18. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/chat.py +5 -0
  19. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/replay.py +7 -0
  20. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/runtime.py +318 -16
  21. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/SKILL.md +1 -0
  22. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/references/effects-and-state.md +19 -0
  23. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/references/effects.md +17 -0
  24. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-observability/SKILL.md +21 -1
  25. {reactifact-0.13.0 → reactifact-0.14.1/reactifact.egg-info}/PKG-INFO +13 -4
  26. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/SOURCES.txt +14 -0
  27. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/requires.txt +3 -0
  28. reactifact-0.14.1/tests/test_a2a.py +347 -0
  29. reactifact-0.14.1/tests/test_a2a_example.py +16 -0
  30. reactifact-0.14.1/tests/test_a2a_interop.py +94 -0
  31. reactifact-0.14.1/tests/test_chat_outbox.py +70 -0
  32. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_consumes_produces.py +1 -1
  33. reactifact-0.14.1/tests/test_logging.py +147 -0
  34. reactifact-0.14.1/tests/test_outbox.py +65 -0
  35. reactifact-0.14.1/tests/test_pending_actions.py +256 -0
  36. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_public_api.py +4 -0
  37. reactifact-0.14.1/tests/test_shutdown.py +200 -0
  38. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_starter_app.py +5 -0
  39. reactifact-0.13.0/reactifact/interrupt.py +0 -22
  40. {reactifact-0.13.0 → reactifact-0.14.1}/LICENSE +0 -0
  41. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/__main__.py +0 -0
  42. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/_extras.py +0 -0
  43. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/_httpx.py +0 -0
  44. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/agent_tool.py +0 -0
  45. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/agents.py +0 -0
  46. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/artifacts.py +0 -0
  47. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/audit.py +0 -0
  48. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/authz.py +0 -0
  49. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cache.py +0 -0
  50. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/checkpoints.py +0 -0
  51. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/__init__.py +0 -0
  52. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/branch.py +0 -0
  53. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/common.py +0 -0
  54. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/context.py +0 -0
  55. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/eval.py +0 -0
  56. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/graph.py +0 -0
  57. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/scenario.py +0 -0
  58. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/skills.py +0 -0
  59. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/trace.py +0 -0
  60. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/commit.py +0 -0
  61. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/commit_log.py +0 -0
  62. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/consume.py +0 -0
  63. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/context_builder.py +0 -0
  64. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/__init__.py +0 -0
  65. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/dataset.py +0 -0
  66. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/evaluators.py +0 -0
  67. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/judge.py +0 -0
  68. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/online.py +0 -0
  69. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/runner.py +0 -0
  70. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/scoring.py +0 -0
  71. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/events.py +0 -0
  72. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/guardrails.py +0 -0
  73. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/llm_agent.py +0 -0
  74. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/__init__.py +0 -0
  75. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/client.py +0 -0
  76. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/oauth.py +0 -0
  77. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/server.py +0 -0
  78. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/metrics.py +0 -0
  79. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/native_tool_use.py +0 -0
  80. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/operations.py +0 -0
  81. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/patches.py +0 -0
  82. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/pricing.py +0 -0
  83. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/produce.py +0 -0
  84. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/prompts.py +0 -0
  85. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/__init__.py +0 -0
  86. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/_retry.py +0 -0
  87. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/anthropic.py +0 -0
  88. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/azure.py +0 -0
  89. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/cerebras.py +0 -0
  90. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/chat.py +0 -0
  91. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/contracts.py +0 -0
  92. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/deepseek.py +0 -0
  93. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/fake.py +0 -0
  94. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/fireworks.py +0 -0
  95. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/gemini.py +0 -0
  96. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/github_models.py +0 -0
  97. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/groq.py +0 -0
  98. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/image.py +0 -0
  99. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/mistral.py +0 -0
  100. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/nvidia.py +0 -0
  101. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/ollama.py +0 -0
  102. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/openai.py +0 -0
  103. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/openrouter.py +0 -0
  104. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/perplexity.py +0 -0
  105. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/qwen.py +0 -0
  106. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/speech.py +0 -0
  107. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/together.py +0 -0
  108. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/video.py +0 -0
  109. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/xai.py +0 -0
  110. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/zai.py +0 -0
  111. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/py.typed +0 -0
  112. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/__init__.py +0 -0
  113. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/_shared.py +0 -0
  114. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/agent.py +0 -0
  115. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/models.py +0 -0
  116. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/rag.py +0 -0
  117. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/tools_agent.py +0 -0
  118. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quota.py +0 -0
  119. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/__init__.py +0 -0
  120. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/cleanup.py +0 -0
  121. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/conversation.py +0 -0
  122. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/identity.py +0 -0
  123. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/inputs.py +0 -0
  124. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/memory.py +0 -0
  125. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/plan_execute.py +0 -0
  126. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/reflection.py +0 -0
  127. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/resolve.py +0 -0
  128. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/rollback.py +0 -0
  129. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/search.py +0 -0
  130. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/skills.py +0 -0
  131. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/status.py +0 -0
  132. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/supervisor.py +0 -0
  133. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/text.py +0 -0
  134. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/tool_loop.py +0 -0
  135. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/redaction.py +0 -0
  136. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/relations.py +0 -0
  137. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/request.py +0 -0
  138. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/resources.py +0 -0
  139. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/routing.py +0 -0
  140. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/scheduler.py +0 -0
  141. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/session.py +0 -0
  142. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
  143. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
  144. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
  145. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
  146. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-eval/references/online.md +0 -0
  147. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
  148. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
  149. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
  150. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
  151. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
  152. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
  153. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
  154. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/sources.py +0 -0
  155. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/streaming.py +0 -0
  156. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/structured.py +0 -0
  157. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/__init__.py +0 -0
  158. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/assertions.py +0 -0
  159. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/exceptions.py +0 -0
  160. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/fault.py +0 -0
  161. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/golden.py +0 -0
  162. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/lab.py +0 -0
  163. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/mock.py +0 -0
  164. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/pytest_plugin.py +0 -0
  165. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/record.py +0 -0
  166. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/registry.py +0 -0
  167. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tool_use.py +0 -0
  168. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tools.py +0 -0
  169. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/__init__.py +0 -0
  170. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/_otlp.py +0 -0
  171. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/columns.py +0 -0
  172. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/langfuse.py +0 -0
  173. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/models.py +0 -0
  174. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/otlp.py +0 -0
  175. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/postgres.py +0 -0
  176. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/store.py +0 -0
  177. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/app.css +0 -0
  178. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/sessions.html +0 -0
  179. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/ui.html +0 -0
  180. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/ui_run.html +0 -0
  181. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/tracer.py +0 -0
  182. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/web.py +0 -0
  183. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/triggers.py +0 -0
  184. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/types.py +0 -0
  185. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/verify.py +0 -0
  186. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/viz.py +0 -0
  187. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/web.py +0 -0
  188. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/dependency_links.txt +0 -0
  189. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/entry_points.txt +0 -0
  190. {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/top_level.txt +0 -0
  191. {reactifact-0.13.0 → reactifact-0.14.1}/setup.cfg +0 -0
  192. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_absent_consume.py +0 -0
  193. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_adaptive.py +0 -0
  194. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_agent_tool.py +0 -0
  195. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_anthropic_provider.py +0 -0
  196. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_artifacts.py +0 -0
  197. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_audit.py +0 -0
  198. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_authz.py +0 -0
  199. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_backbone.py +0 -0
  200. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_branching.py +0 -0
  201. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_budget.py +0 -0
  202. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cache.py +0 -0
  203. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_canonical_ports.py +0 -0
  204. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_example.py +0 -0
  205. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_memory.py +0 -0
  206. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_web.py +0 -0
  207. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_checkpoint.py +0 -0
  208. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_checkpoints_concurrency.py +0 -0
  209. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cli.py +0 -0
  210. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cli_scenario.py +0 -0
  211. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_commit_log.py +0 -0
  212. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_compaction.py +0 -0
  213. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_concurrency.py +0 -0
  214. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_context_builder.py +0 -0
  215. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_correlated_consume.py +0 -0
  216. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_debounce.py +0 -0
  217. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_determinism.py +0 -0
  218. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops.py +0 -0
  219. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops_online.py +0 -0
  220. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops_web.py +0 -0
  221. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_effects.py +0 -0
  222. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval.py +0 -0
  223. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_dataset.py +0 -0
  224. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_judge.py +0 -0
  225. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_online.py +0 -0
  226. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_trajectory.py +0 -0
  227. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_fintech_audit.py +0 -0
  228. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_forklab.py +0 -0
  229. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_forklab_web.py +0 -0
  230. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_friendly_api.py +0 -0
  231. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_gemini_provider.py +0 -0
  232. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_golden.py +0 -0
  233. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_guardrails.py +0 -0
  234. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_hitl.py +0 -0
  235. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_image_provider.py +0 -0
  236. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_incident_commander.py +0 -0
  237. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_invalidation.py +0 -0
  238. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_join_consume.py +0 -0
  239. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_knowledge.py +0 -0
  240. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_knowledge_web.py +0 -0
  241. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_llm_ladder.py +0 -0
  242. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_loop_bound_client.py +0 -0
  243. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_mcp.py +0 -0
  244. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_medic_lab.py +0 -0
  245. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_medic_lab_web.py +0 -0
  246. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_metrics.py +0 -0
  247. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_multisource.py +0 -0
  248. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_native_tool_use.py +0 -0
  249. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_openai_provider.py +0 -0
  250. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_patches.py +0 -0
  251. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_ports.py +0 -0
  252. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_produce_styles.py +0 -0
  253. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_produce_trigger.py +0 -0
  254. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_prompts.py +0 -0
  255. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_provider_auth.py +0 -0
  256. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_provider_retry.py +0 -0
  257. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_providers_integration.py +0 -0
  258. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_quick.py +0 -0
  259. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_quota.py +0 -0
  260. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_reacts_to.py +0 -0
  261. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_conversation.py +0 -0
  262. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_plan_execute.py +0 -0
  263. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_reflection.py +0 -0
  264. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_supervisor.py +0 -0
  265. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_tool_loop.py +0 -0
  266. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes.py +0 -0
  267. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_inputs.py +0 -0
  268. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_memory.py +0 -0
  269. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_skills.py +0 -0
  270. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_redaction.py +0 -0
  271. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_relation_graph.py +0 -0
  272. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_relations.py +0 -0
  273. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repair.py +0 -0
  274. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repair_web.py +0 -0
  275. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_replay.py +0 -0
  276. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repo_agent.py +0 -0
  277. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_request.py +0 -0
  278. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_research.py +0 -0
  279. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_resources.py +0 -0
  280. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_retry.py +0 -0
  281. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_routing.py +0 -0
  282. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_runtime.py +0 -0
  283. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_runtime_errors.py +0 -0
  284. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_scheduler_semantics.py +0 -0
  285. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sessions.py +0 -0
  286. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_skills.py +0 -0
  287. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources.py +0 -0
  288. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources_search.py +0 -0
  289. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources_vector.py +0 -0
  290. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_speech_provider.py +0 -0
  291. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_streaming.py +0 -0
  292. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_structured.py +0 -0
  293. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_support_copilot.py +0 -0
  294. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_assertions.py +0 -0
  295. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_golden.py +0 -0
  296. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_lab.py +0 -0
  297. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_mock.py +0 -0
  298. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_provenance.py +0 -0
  299. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_pytest_plugin.py +0 -0
  300. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_registry.py +0 -0
  301. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_token_budget.py +0 -0
  302. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_tools.py +0 -0
  303. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_trace_columns.py +0 -0
  304. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_trace_tags.py +0 -0
  305. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_tracing.py +0 -0
  306. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_type_registry.py +0 -0
  307. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_vendor_factories.py +0 -0
  308. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_vendor_multimodal_factories.py +0 -0
  309. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_verify.py +0 -0
  310. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_video_provider.py +0 -0
  311. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_view.py +0 -0
  312. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_viz.py +0 -0
  313. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_web_source.py +0 -0
  314. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_workspace.py +0 -0
  315. {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_workspace_with_sources.py +0 -0
  316. {reactifact-0.13.0 → reactifact-0.14.1}/tests/tests_checkpoints_sqlite.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactifact
3
- Version: 0.13.0
3
+ Version: 0.14.1
4
4
  Summary: Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/bzdvdn/reactifact
@@ -36,6 +36,8 @@ Provides-Extra: pg
36
36
  Requires-Dist: psycopg[binary]>=3.2; extra == "pg"
37
37
  Provides-Extra: mcp
38
38
  Requires-Dist: mcp>=2.2; extra == "mcp"
39
+ Provides-Extra: a2a
40
+ Requires-Dist: a2a-sdk>=1.0; extra == "a2a"
39
41
  Provides-Extra: docs
40
42
  Requires-Dist: mkdocs>=1.6; extra == "docs"
41
43
  Requires-Dist: mkdocs-material>=9.5; extra == "docs"
@@ -289,13 +291,19 @@ by hand. Run it yourself: `uv run python -m examples.ledger.main`.
289
291
  - **Provenance** — every derived artifact links to what produced it
290
292
  (`Answer —supported_by→ Claim —derived_from→ Evidence —extracted_from→ Doc`).
291
293
  - **HITL** — humans as `effects.ask(...)` → `PendingQuestion`, answered via `effects.resume(...)` like any agent.
294
+ - **Outbox** — outbound side effects as `effects.act(...)` → `PendingAction`, performed once *after* the commit by `Runtime(dispatcher=...)`; safe across replay, retries and merged branches.
292
295
 
293
296
  ## In the box
294
297
 
295
298
  - **Deterministic by design** — calculations over structured data, honest `None`
296
299
  fallbacks instead of hallucinated answers; the model reasons, never "knows".
297
300
  - **Observability** — every run traces agent spans, reads/writes, LLM calls,
298
- tokens: SQLite store + web dashboard, exportable to Langfuse/Postgres (async sinks).
301
+ tokens: SQLite store + web dashboard, exportable to Langfuse/Postgres (async sinks);
302
+ **correlated logs** (`configure_logging()`, silent until called) carry
303
+ `run_id`/`session_id`/`agent` on every line.
304
+ - **Lifecycle** — `Budget.max_seconds` is a hard deadline (a hung LLM call is
305
+ cancelled); `request_stop()`/`ashutdown()` stop at a generation boundary
306
+ (`RunOutcome.STOPPED`); `active_runs()`/`in_flight` give a readiness view.
299
307
  - **MCP, both ways** — call any MCP server's tools as a `Tool`
300
308
  (`mcp_stdio_tools`/`mcp_http_tools`), or expose your own `Tool`s and a live
301
309
  `Context` as an MCP server (`create_mcp_server`) for Claude Desktop, Claude
@@ -326,7 +334,7 @@ uv run python ./examples/devops/web.py # HITL ops assistant + trace dash
326
334
  ```
327
335
 
328
336
  Classic-pattern ports run as one-liners too:
329
- `python -m examples.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute,adaptive,ledger}.main`.
337
+ `python -m examples.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute,adaptive,ledger,outbox}.main`.
330
338
 
331
339
  Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/bzdvdn/reactifact/blob/master/examples/quickstart.ipynb)
332
340
  — `examples/quickstart.ipynb` covers the four `quick` cases (`agent`/`rag`/`tools_agent`/`chat_agent`), offline by default.
@@ -341,6 +349,7 @@ Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/co
341
349
  - `chat` — minimal offline single-model multi-turn conversation (`Conversation` + bounded `ChatMemory`).
342
350
  - `forklab` — deterministic branch & merge: two strategies on their own forks, three-way merge.
343
351
  - `ledger` — offline proof of reactive recompute: edit one fact, only its real `Consume`rs re-run.
352
+ - `outbox` — one intent, one send: `effects.act(...)` + `Runtime(dispatcher=...)`, safe across replays, retries and merged branches (§42).
344
353
  - `llm_ladder` — the workflow from one LLM call to state-changing patches (3 levels).
345
354
  - `adaptive` — hybrid scheduler: rule filters + deterministic rank + LLM tie-break + `rank_limit`.
346
355
  - `{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute}` — canonical ports (see [port-matrix](docs/en/port-matrix.md)).
@@ -371,7 +380,7 @@ uv sync --extra dev --extra web
371
380
 
372
381
  ## Status & contributing
373
382
 
374
- Active, `0.13.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
383
+ Active, `0.14.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
375
384
  (see the [roadmap](docs/roadmap.md)); releases follow
376
385
  [Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
377
386
  [GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
@@ -245,13 +245,19 @@ by hand. Run it yourself: `uv run python -m examples.ledger.main`.
245
245
  - **Provenance** — every derived artifact links to what produced it
246
246
  (`Answer —supported_by→ Claim —derived_from→ Evidence —extracted_from→ Doc`).
247
247
  - **HITL** — humans as `effects.ask(...)` → `PendingQuestion`, answered via `effects.resume(...)` like any agent.
248
+ - **Outbox** — outbound side effects as `effects.act(...)` → `PendingAction`, performed once *after* the commit by `Runtime(dispatcher=...)`; safe across replay, retries and merged branches.
248
249
 
249
250
  ## In the box
250
251
 
251
252
  - **Deterministic by design** — calculations over structured data, honest `None`
252
253
  fallbacks instead of hallucinated answers; the model reasons, never "knows".
253
254
  - **Observability** — every run traces agent spans, reads/writes, LLM calls,
254
- tokens: SQLite store + web dashboard, exportable to Langfuse/Postgres (async sinks).
255
+ tokens: SQLite store + web dashboard, exportable to Langfuse/Postgres (async sinks);
256
+ **correlated logs** (`configure_logging()`, silent until called) carry
257
+ `run_id`/`session_id`/`agent` on every line.
258
+ - **Lifecycle** — `Budget.max_seconds` is a hard deadline (a hung LLM call is
259
+ cancelled); `request_stop()`/`ashutdown()` stop at a generation boundary
260
+ (`RunOutcome.STOPPED`); `active_runs()`/`in_flight` give a readiness view.
255
261
  - **MCP, both ways** — call any MCP server's tools as a `Tool`
256
262
  (`mcp_stdio_tools`/`mcp_http_tools`), or expose your own `Tool`s and a live
257
263
  `Context` as an MCP server (`create_mcp_server`) for Claude Desktop, Claude
@@ -282,7 +288,7 @@ uv run python ./examples/devops/web.py # HITL ops assistant + trace dash
282
288
  ```
283
289
 
284
290
  Classic-pattern ports run as one-liners too:
285
- `python -m examples.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute,adaptive,ledger}.main`.
291
+ `python -m examples.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute,adaptive,ledger,outbox}.main`.
286
292
 
287
293
  Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/bzdvdn/reactifact/blob/master/examples/quickstart.ipynb)
288
294
  — `examples/quickstart.ipynb` covers the four `quick` cases (`agent`/`rag`/`tools_agent`/`chat_agent`), offline by default.
@@ -297,6 +303,7 @@ Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/co
297
303
  - `chat` — minimal offline single-model multi-turn conversation (`Conversation` + bounded `ChatMemory`).
298
304
  - `forklab` — deterministic branch & merge: two strategies on their own forks, three-way merge.
299
305
  - `ledger` — offline proof of reactive recompute: edit one fact, only its real `Consume`rs re-run.
306
+ - `outbox` — one intent, one send: `effects.act(...)` + `Runtime(dispatcher=...)`, safe across replays, retries and merged branches (§42).
300
307
  - `llm_ladder` — the workflow from one LLM call to state-changing patches (3 levels).
301
308
  - `adaptive` — hybrid scheduler: rule filters + deterministic rank + LLM tie-break + `rank_limit`.
302
309
  - `{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute}` — canonical ports (see [port-matrix](docs/en/port-matrix.md)).
@@ -327,7 +334,7 @@ uv sync --extra dev --extra web
327
334
 
328
335
  ## Status & contributing
329
336
 
330
- Active, `0.13.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
337
+ Active, `0.14.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
331
338
  (see the [roadmap](docs/roadmap.md)); releases follow
332
339
  [Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
333
340
  [GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactifact"
7
- version = "0.13.0"
7
+ version = "0.14.1"
8
8
  description = "Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -52,6 +52,9 @@ pg = [
52
52
  mcp = [
53
53
  "mcp>=2.2",
54
54
  ]
55
+ a2a = [
56
+ "a2a-sdk>=1.0",
57
+ ]
55
58
  docs = [
56
59
  "mkdocs>=1.6",
57
60
  "mkdocs-material>=9.5",
@@ -81,6 +84,9 @@ pg = [
81
84
  mcp = [
82
85
  "mcp>=2.2",
83
86
  ]
87
+ a2a = [
88
+ "a2a-sdk>=1.0",
89
+ ]
84
90
  docs = [
85
91
  "mkdocs>=1.6",
86
92
  "mkdocs-material>=9.5",
@@ -29,7 +29,8 @@ from .consume import Consume, consume
29
29
  from .context import Context, View
30
30
  from .effects import Effects, Handle
31
31
  from .events import Event, EventType
32
- from .interrupt import PendingQuestion
32
+ from .interrupt import PendingAction, PendingQuestion
33
+ from .logging import bind, configure_logging, get_logger
33
34
  from .patches import Create, Delete, Link, Patch, Relation, Unlink, Update
34
35
  from .produce import Produce, ProduceCall, produce
35
36
  from .providers import (
@@ -50,7 +51,7 @@ from .tools import FunctionTool, Tool, ToolOutput, tool
50
51
  from .triggers import Trigger
51
52
  from .types import register as register_type
52
53
 
53
- __version__ = "0.13.0"
54
+ __version__ = "0.14.1"
54
55
 
55
56
  __all__ = [
56
57
  "Agent",
@@ -73,6 +74,7 @@ __all__ = [
73
74
  "Message",
74
75
  "MergeConflict",
75
76
  "Patch",
77
+ "PendingAction",
76
78
  "PendingQuestion",
77
79
  "Produce",
78
80
  "ProduceCall",
@@ -95,9 +97,12 @@ __all__ = [
95
97
  "Unlink",
96
98
  "Update",
97
99
  "View",
100
+ "bind",
101
+ "configure_logging",
98
102
  "consume",
99
103
  "create_agent",
100
104
  "current_request",
105
+ "get_logger",
101
106
  "produce",
102
107
  "register_type",
103
108
  "tool",
@@ -0,0 +1,77 @@
1
+ """A2A (Agent2Agent) integration: call remote agents, or serve reactifact ones.
2
+
3
+ No extra dependency: the client uses `httpx` (core), the server uses FastAPI
4
+ from the existing `web` extra and imports it lazily — so importing this package
5
+ never needs FastAPI.
6
+
7
+ - Client (`reactifact.a2a.client`): `A2AClient` (Agent Card + `message/send`,
8
+ `message/stream`, `tasks/get`, `tasks/cancel`) and `A2AAgentTool`/`a2a_tool`,
9
+ a reactifact `Tool` that delegates to a remote agent.
10
+ - Server (`reactifact.a2a.server`): `create_a2a_router(agents, ...)` — an Agent
11
+ Card plus a JSON-RPC endpoint where a `Task` is a conversation and a HITL
12
+ `PendingQuestion` maps to `input-required`.
13
+
14
+ The JSON-RPC binding implements the A2A `0.3` wire shape (camelCase fields,
15
+ `kind`-discriminated parts).
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from .client import (
21
+ AGENT_CARD_PATH,
22
+ LEGACY_AGENT_CARD_PATH,
23
+ A2AAgentTool,
24
+ A2AClient,
25
+ A2AError,
26
+ a2a_tool,
27
+ )
28
+ from .models import (
29
+ AgentCapabilities,
30
+ AgentCard,
31
+ AgentProvider,
32
+ AgentSkill,
33
+ Artifact,
34
+ ErrorCode,
35
+ JSONRPCError,
36
+ JSONRPCRequest,
37
+ JSONRPCResponse,
38
+ Message,
39
+ MessageSendParams,
40
+ Part,
41
+ Task,
42
+ TaskState,
43
+ TaskStatus,
44
+ UserMessage,
45
+ )
46
+ from .remote import A2ARemoteProduce, remote_agent
47
+ from .server import create_a2a_router, default_create_message, default_reply
48
+
49
+ __all__ = [
50
+ "AGENT_CARD_PATH",
51
+ "LEGACY_AGENT_CARD_PATH",
52
+ "A2AAgentTool",
53
+ "A2AClient",
54
+ "A2AError",
55
+ "A2ARemoteProduce",
56
+ "AgentCapabilities",
57
+ "AgentCard",
58
+ "AgentProvider",
59
+ "AgentSkill",
60
+ "Artifact",
61
+ "ErrorCode",
62
+ "JSONRPCError",
63
+ "JSONRPCRequest",
64
+ "JSONRPCResponse",
65
+ "Message",
66
+ "MessageSendParams",
67
+ "Part",
68
+ "Task",
69
+ "TaskState",
70
+ "TaskStatus",
71
+ "UserMessage",
72
+ "a2a_tool",
73
+ "create_a2a_router",
74
+ "default_create_message",
75
+ "default_reply",
76
+ "remote_agent",
77
+ ]
@@ -0,0 +1,282 @@
1
+ """A2A client: call a remote Agent2Agent server, and expose one as a `Tool`.
2
+
3
+ Speaks the JSON-RPC binding (v0.3 shape) over `httpx` — no extra dependency.
4
+ Two ways to consume a remote agent:
5
+
6
+ - `A2AClient` — the low-level protocol surface (`fetch_agent_card`, `send`,
7
+ `stream`, `get_task`, `cancel_task`), returning the A2A `Task`/`Message`.
8
+ - `A2AAgentTool` / `a2a_tool` — a reactifact `Tool` wrapping a remote agent, so
9
+ an `LLMAgent` can delegate to it: `message/send`, then the reply's text (or
10
+ the task's artifact) becomes the tool result.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import AsyncIterator
16
+ from typing import Any
17
+ from urllib.parse import urlsplit
18
+ from uuid import uuid4
19
+
20
+ import httpx
21
+
22
+ from .._httpx import LoopBoundClient
23
+ from ..tools import Tool, ToolOutput
24
+ from .models import (
25
+ AgentCard,
26
+ ErrorCode,
27
+ JSONRPCRequest,
28
+ JSONRPCResponse,
29
+ Message,
30
+ MessageSendParams,
31
+ Task,
32
+ TaskIdParams,
33
+ TaskQueryParams,
34
+ )
35
+
36
+ #: Well-known Agent Card paths (v0.3 recommends `agent-card.json`; v0.2 used
37
+ #: `agent.json` — the server serves both).
38
+ AGENT_CARD_PATH = "/.well-known/agent-card.json"
39
+ LEGACY_AGENT_CARD_PATH = "/.well-known/agent.json"
40
+
41
+
42
+ class A2AError(Exception):
43
+ """A remote A2A server returned a JSON-RPC error."""
44
+
45
+ def __init__(self, code: int, message: str, data: Any = None) -> None:
46
+ self.code = code
47
+ self.message = message
48
+ self.data = data
49
+ super().__init__(f"A2A error {code}: {message}")
50
+
51
+
52
+ def _default_card_url(url: str) -> str:
53
+ parts = urlsplit(url)
54
+ origin = f"{parts.scheme}://{parts.netloc}"
55
+ return origin + AGENT_CARD_PATH
56
+
57
+
58
+ def _as_message(
59
+ value: str | Message, *, task_id: str | None, context_id: str | None
60
+ ) -> Message:
61
+ if isinstance(value, Message):
62
+ message = value.model_copy()
63
+ if task_id is not None:
64
+ message.taskId = task_id
65
+ if context_id is not None:
66
+ message.contextId = context_id
67
+ return message
68
+ return Message.user(value, taskId=task_id, contextId=context_id)
69
+
70
+
71
+ def _parse_result(result: Any) -> Task | Message | dict[str, Any]:
72
+ """Reads a `message/send`/`stream` result into its typed model."""
73
+ if isinstance(result, dict):
74
+ kind = result.get("kind")
75
+ if kind == "task":
76
+ return Task.model_validate(result)
77
+ if kind == "message":
78
+ return Message.model_validate(result)
79
+ return result
80
+ return {}
81
+
82
+
83
+ class A2AClient:
84
+ """A JSON-RPC A2A client for one server `url`.
85
+
86
+ `url` is the JSON-RPC endpoint from the server's Agent Card; the card is
87
+ fetched from the well-known path on the same origin unless `card_url` is
88
+ given. `headers` are sent on every request (auth lives here, per the spec:
89
+ credentials travel in HTTP headers, not in A2A messages). Pass `client=` to
90
+ inject an `httpx.AsyncClient` (tests, a custom transport).
91
+ """
92
+
93
+ def __init__(
94
+ self,
95
+ url: str,
96
+ *,
97
+ card_url: str | None = None,
98
+ headers: dict[str, str] | None = None,
99
+ timeout: float = 30.0,
100
+ client: Any | None = None,
101
+ ) -> None:
102
+ self.url = url.rstrip("/")
103
+ self.card_url = card_url or _default_card_url(self.url)
104
+ self._headers = {"Content-Type": "application/json", **(headers or {})}
105
+ self._http = LoopBoundClient(
106
+ lambda: httpx.AsyncClient(timeout=timeout, headers=self._headers),
107
+ client=client,
108
+ )
109
+
110
+ async def fetch_agent_card(self) -> AgentCard:
111
+ response = await self._http.get().get(self.card_url)
112
+ response.raise_for_status()
113
+ return AgentCard.model_validate(response.json())
114
+
115
+ async def _rpc(self, method: str, params: dict[str, Any]) -> Any:
116
+ payload = JSONRPCRequest(
117
+ id=uuid4().hex, method=method, params=params
118
+ ).model_dump(mode="json")
119
+ response = await self._http.get().post(self.url, json=payload)
120
+ response.raise_for_status()
121
+ envelope = JSONRPCResponse.model_validate(response.json())
122
+ if envelope.error is not None:
123
+ raise A2AError(
124
+ envelope.error.code, envelope.error.message, envelope.error.data
125
+ )
126
+ return envelope.result
127
+
128
+ async def send(
129
+ self,
130
+ message: str | Message,
131
+ *,
132
+ task_id: str | None = None,
133
+ context_id: str | None = None,
134
+ blocking: bool = True,
135
+ ) -> Task | Message:
136
+ """`message/send` → the task state (or a direct reply `Message`)."""
137
+ params = MessageSendParams(
138
+ message=_as_message(message, task_id=task_id, context_id=context_id),
139
+ configuration=None if blocking else {"blocking": False},
140
+ )
141
+ result = _parse_result(
142
+ await self._rpc("message/send", params.model_dump(mode="json"))
143
+ )
144
+ if not isinstance(result, (Task, Message)):
145
+ raise A2AError(ErrorCode.INTERNAL_ERROR, "unexpected message/send result")
146
+ return result
147
+
148
+ async def stream(
149
+ self,
150
+ message: str | Message,
151
+ *,
152
+ task_id: str | None = None,
153
+ context_id: str | None = None,
154
+ ) -> AsyncIterator[Task | Message | dict[str, Any]]:
155
+ """`message/stream` → an async iterator of task/message/update events."""
156
+ params = MessageSendParams(
157
+ message=_as_message(message, task_id=task_id, context_id=context_id)
158
+ )
159
+ payload = JSONRPCRequest(
160
+ id=uuid4().hex,
161
+ method="message/stream",
162
+ params=params.model_dump(mode="json"),
163
+ ).model_dump(mode="json")
164
+ client = self._http.get()
165
+ async with client.stream("POST", self.url, json=payload) as response:
166
+ response.raise_for_status()
167
+ async for line in response.aiter_lines():
168
+ if not line.startswith("data:"):
169
+ continue
170
+ data = line[len("data:") :].strip()
171
+ if not data or data == "[DONE]":
172
+ continue
173
+ envelope = JSONRPCResponse.model_validate_json(data)
174
+ if envelope.error is not None:
175
+ raise A2AError(
176
+ envelope.error.code,
177
+ envelope.error.message,
178
+ envelope.error.data,
179
+ )
180
+ yield _parse_result(envelope.result)
181
+
182
+ async def get_task(
183
+ self, task_id: str, *, history_length: int | None = None
184
+ ) -> Task:
185
+ params = TaskQueryParams(id=task_id, historyLength=history_length)
186
+ return Task.model_validate(
187
+ await self._rpc("tasks/get", params.model_dump(mode="json"))
188
+ )
189
+
190
+ async def cancel_task(self, task_id: str) -> Task:
191
+ params = TaskIdParams(id=task_id)
192
+ return Task.model_validate(
193
+ await self._rpc("tasks/cancel", params.model_dump(mode="json"))
194
+ )
195
+
196
+ async def aclose(self) -> None:
197
+ await self._http.aclose()
198
+
199
+
200
+ def _task_reply_text(task: Task) -> str:
201
+ if task.status.message is not None and task.status.message.text:
202
+ return task.status.message.text
203
+ return "\n".join(
204
+ part.as_text() for artifact in task.artifacts for part in artifact.parts
205
+ )
206
+
207
+
208
+ class A2AAgentTool(Tool):
209
+ """A reactifact `Tool` that delegates to a remote A2A agent.
210
+
211
+ The tool takes a single `message` string, sends it with `message/send`, and
212
+ returns the reply text (the task's status message, else its artifacts) — so
213
+ an `LLMAgent` can call another agent the way it calls any tool.
214
+ """
215
+
216
+ def __init__(
217
+ self,
218
+ target: str | A2AClient,
219
+ *,
220
+ name: str = "a2a_agent",
221
+ description: str | None = None,
222
+ card_url: str | None = None,
223
+ headers: dict[str, str] | None = None,
224
+ client: Any | None = None,
225
+ ) -> None:
226
+ self.client = (
227
+ target
228
+ if isinstance(target, A2AClient)
229
+ else A2AClient(target, card_url=card_url, headers=headers, client=client)
230
+ )
231
+ self.name = name
232
+ self.description = description or (
233
+ f"Send a request to the A2A agent at {self.client.url} and get its reply."
234
+ )
235
+ self.schema = {
236
+ "type": "object",
237
+ "properties": {
238
+ "message": {
239
+ "type": "string",
240
+ "description": "The request to send to the remote agent.",
241
+ }
242
+ },
243
+ "required": ["message"],
244
+ }
245
+
246
+ async def execute(self, args: dict[str, Any]) -> ToolOutput:
247
+ text = str(args.get("message", "")).strip()
248
+ if not text:
249
+ return ToolOutput(error="'message' is required")
250
+ try:
251
+ result = await self.client.send(text)
252
+ except A2AError as exc:
253
+ return ToolOutput(error=str(exc))
254
+ if isinstance(result, Task):
255
+ return ToolOutput(text=_task_reply_text(result), data=result.model_dump())
256
+ if isinstance(result, Message):
257
+ return ToolOutput(text=result.text, data=result.model_dump())
258
+ return ToolOutput(text="", data=result)
259
+
260
+
261
+ def a2a_tool(
262
+ url: str,
263
+ *,
264
+ name: str = "a2a_agent",
265
+ description: str | None = None,
266
+ card_url: str | None = None,
267
+ headers: dict[str, str] | None = None,
268
+ ) -> A2AAgentTool:
269
+ """Builds an `A2AAgentTool` for the server at `url` (convenience wrapper)."""
270
+ return A2AAgentTool(
271
+ url, name=name, description=description, card_url=card_url, headers=headers
272
+ )
273
+
274
+
275
+ __all__ = [
276
+ "AGENT_CARD_PATH",
277
+ "LEGACY_AGENT_CARD_PATH",
278
+ "A2AAgentTool",
279
+ "A2AClient",
280
+ "A2AError",
281
+ "a2a_tool",
282
+ ]