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.
- {reactifact-0.13.0/reactifact.egg-info → reactifact-0.14.1}/PKG-INFO +13 -4
- {reactifact-0.13.0 → reactifact-0.14.1}/README.md +10 -3
- {reactifact-0.13.0 → reactifact-0.14.1}/pyproject.toml +7 -1
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/__init__.py +7 -2
- reactifact-0.14.1/reactifact/a2a/__init__.py +77 -0
- reactifact-0.14.1/reactifact/a2a/client.py +282 -0
- reactifact-0.14.1/reactifact/a2a/models.py +294 -0
- reactifact-0.14.1/reactifact/a2a/remote.py +178 -0
- reactifact-0.14.1/reactifact/a2a/server.py +381 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/branching.py +29 -2
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/budget.py +4 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/chat.py +14 -1
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/replay.py +10 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/context.py +62 -1
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/effects.py +27 -0
- reactifact-0.14.1/reactifact/interrupt.py +54 -0
- reactifact-0.14.1/reactifact/logging.py +222 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/chat.py +5 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/replay.py +7 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/runtime.py +318 -16
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/SKILL.md +1 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/references/effects-and-state.md +19 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/references/effects.md +17 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-observability/SKILL.md +21 -1
- {reactifact-0.13.0 → reactifact-0.14.1/reactifact.egg-info}/PKG-INFO +13 -4
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/SOURCES.txt +14 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/requires.txt +3 -0
- reactifact-0.14.1/tests/test_a2a.py +347 -0
- reactifact-0.14.1/tests/test_a2a_example.py +16 -0
- reactifact-0.14.1/tests/test_a2a_interop.py +94 -0
- reactifact-0.14.1/tests/test_chat_outbox.py +70 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_consumes_produces.py +1 -1
- reactifact-0.14.1/tests/test_logging.py +147 -0
- reactifact-0.14.1/tests/test_outbox.py +65 -0
- reactifact-0.14.1/tests/test_pending_actions.py +256 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_public_api.py +4 -0
- reactifact-0.14.1/tests/test_shutdown.py +200 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_starter_app.py +5 -0
- reactifact-0.13.0/reactifact/interrupt.py +0 -22
- {reactifact-0.13.0 → reactifact-0.14.1}/LICENSE +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/__main__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/_extras.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/_httpx.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/agent_tool.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/agents.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/artifacts.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/audit.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/authz.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cache.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/checkpoints.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/branch.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/common.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/context.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/eval.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/graph.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/scenario.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/skills.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/cli/trace.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/commit.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/commit_log.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/consume.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/context_builder.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/dataset.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/evaluators.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/judge.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/online.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/runner.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/eval/scoring.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/events.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/guardrails.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/llm_agent.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/client.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/oauth.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/mcp/server.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/metrics.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/native_tool_use.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/operations.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/patches.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/pricing.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/produce.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/prompts.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/_retry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/anthropic.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/azure.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/cerebras.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/chat.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/contracts.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/deepseek.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/fake.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/fireworks.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/gemini.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/github_models.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/groq.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/image.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/mistral.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/nvidia.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/ollama.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/openai.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/openrouter.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/perplexity.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/qwen.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/speech.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/together.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/video.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/xai.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/providers/zai.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/py.typed +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/_shared.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/agent.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/models.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/rag.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quick/tools_agent.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/quota.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/cleanup.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/conversation.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/identity.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/inputs.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/memory.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/plan_execute.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/reflection.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/resolve.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/rollback.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/search.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/skills.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/status.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/supervisor.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/text.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/recipes/tool_loop.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/redaction.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/relations.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/request.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/resources.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/routing.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/scheduler.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/session.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-eval/references/online.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/sources.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/streaming.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/structured.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/assertions.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/exceptions.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/fault.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/golden.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/lab.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/mock.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/pytest_plugin.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/record.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/testing/registry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tool_use.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tools.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/__init__.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/_otlp.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/columns.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/langfuse.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/models.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/otlp.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/postgres.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/store.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/app.css +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/sessions.html +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/ui.html +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/templates/ui_run.html +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/tracer.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/tracing/web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/triggers.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/types.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/verify.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/viz.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact/web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/dependency_links.txt +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/entry_points.txt +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/reactifact.egg-info/top_level.txt +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/setup.cfg +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_absent_consume.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_adaptive.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_agent_tool.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_anthropic_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_artifacts.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_audit.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_authz.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_backbone.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_branching.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_budget.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cache.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_canonical_ports.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_example.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_memory.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_chat_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_checkpoint.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_checkpoints_concurrency.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cli.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_cli_scenario.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_commit_log.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_compaction.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_concurrency.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_context_builder.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_correlated_consume.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_debounce.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_determinism.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops_online.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_devops_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_effects.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_dataset.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_judge.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_online.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_eval_trajectory.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_fintech_audit.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_forklab.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_forklab_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_friendly_api.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_gemini_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_golden.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_guardrails.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_hitl.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_image_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_incident_commander.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_invalidation.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_join_consume.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_knowledge.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_knowledge_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_llm_ladder.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_loop_bound_client.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_mcp.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_medic_lab.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_medic_lab_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_metrics.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_multisource.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_native_tool_use.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_openai_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_patches.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_ports.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_produce_styles.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_produce_trigger.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_prompts.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_provider_auth.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_provider_retry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_providers_integration.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_quick.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_quota.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_reacts_to.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_conversation.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_plan_execute.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_reflection.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_supervisor.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipe_tool_loop.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_inputs.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_memory.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_recipes_skills.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_redaction.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_relation_graph.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_relations.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repair.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repair_web.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_replay.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_repo_agent.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_request.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_research.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_resources.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_retry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_routing.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_runtime.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_runtime_errors.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_scheduler_semantics.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sessions.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_skills.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources_search.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_sources_vector.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_speech_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_streaming.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_structured.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_support_copilot.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_assertions.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_golden.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_lab.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_mock.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_provenance.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_pytest_plugin.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_testing_registry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_token_budget.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_tools.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_trace_columns.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_trace_tags.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_tracing.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_type_registry.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_vendor_factories.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_vendor_multimodal_factories.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_verify.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_video_provider.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_view.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_viz.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_web_source.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_workspace.py +0 -0
- {reactifact-0.13.0 → reactifact-0.14.1}/tests/test_workspace_with_sources.py +0 -0
- {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.
|
|
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? [](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? [.
|
|
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.
|
|
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? [](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? [.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
]
|