reactifact 0.14.1__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reactifact-0.14.1/reactifact.egg-info → reactifact-0.15.0}/PKG-INFO +2 -2
- {reactifact-0.14.1 → reactifact-0.15.0}/README.md +1 -1
- {reactifact-0.14.1 → reactifact-0.15.0}/pyproject.toml +1 -1
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/__init__.py +1 -1
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/templates/sessions.html +6 -6
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/templates/ui.html +14 -14
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/templates/ui_run.html +10 -10
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/web.py +49 -13
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/web.py +19 -4
- {reactifact-0.14.1 → reactifact-0.15.0/reactifact.egg-info}/PKG-INFO +2 -2
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_chat_web.py +72 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_tracing.py +84 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/LICENSE +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/__main__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/_extras.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/_httpx.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/a2a/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/a2a/client.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/a2a/models.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/a2a/remote.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/a2a/server.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/agent_tool.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/agents.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/artifacts.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/audit.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/authz.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/branching.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/budget.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cache.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/chat.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/checkpoints.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/branch.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/common.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/context.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/eval.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/graph.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/replay.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/scenario.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/skills.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/cli/trace.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/commit.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/commit_log.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/consume.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/context.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/context_builder.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/effects.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/dataset.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/evaluators.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/judge.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/online.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/runner.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/eval/scoring.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/events.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/guardrails.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/interrupt.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/llm_agent.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/logging.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/mcp/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/mcp/client.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/mcp/oauth.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/mcp/server.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/metrics.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/native_tool_use.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/operations.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/patches.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/pricing.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/produce.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/prompts.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/_retry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/anthropic.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/azure.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/cerebras.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/chat.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/contracts.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/deepseek.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/fake.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/fireworks.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/gemini.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/github_models.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/groq.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/image.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/mistral.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/nvidia.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/ollama.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/openai.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/openrouter.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/perplexity.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/qwen.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/speech.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/together.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/video.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/xai.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/providers/zai.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/py.typed +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/_shared.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/agent.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/chat.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/models.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/rag.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quick/tools_agent.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/quota.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/cleanup.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/conversation.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/identity.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/inputs.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/memory.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/plan_execute.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/reflection.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/resolve.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/rollback.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/search.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/skills.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/status.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/supervisor.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/text.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/recipes/tool_loop.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/redaction.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/relations.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/replay.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/request.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/resources.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/routing.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/runtime.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/scheduler.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/session.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact/references/effects-and-state.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-agents/references/effects.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-eval/references/online.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-observability/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/sources.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/streaming.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/structured.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/assertions.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/exceptions.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/fault.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/golden.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/lab.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/mock.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/pytest_plugin.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/record.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/testing/registry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tool_use.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tools.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/__init__.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/_otlp.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/columns.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/langfuse.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/models.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/otlp.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/postgres.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/store.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/templates/app.css +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/tracing/tracer.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/triggers.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/types.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/verify.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact/viz.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact.egg-info/SOURCES.txt +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact.egg-info/dependency_links.txt +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact.egg-info/entry_points.txt +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact.egg-info/requires.txt +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/reactifact.egg-info/top_level.txt +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/setup.cfg +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_a2a.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_a2a_example.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_a2a_interop.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_absent_consume.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_adaptive.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_agent_tool.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_anthropic_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_artifacts.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_audit.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_authz.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_backbone.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_branching.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_budget.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_cache.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_canonical_ports.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_chat_example.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_chat_memory.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_chat_outbox.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_checkpoint.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_checkpoints_concurrency.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_cli.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_cli_scenario.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_commit_log.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_compaction.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_concurrency.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_consumes_produces.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_context_builder.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_correlated_consume.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_debounce.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_determinism.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_devops.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_devops_online.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_devops_web.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_effects.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_eval.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_eval_dataset.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_eval_judge.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_eval_online.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_eval_trajectory.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_fintech_audit.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_forklab.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_forklab_web.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_friendly_api.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_gemini_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_golden.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_guardrails.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_hitl.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_image_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_incident_commander.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_invalidation.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_join_consume.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_knowledge.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_knowledge_web.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_llm_ladder.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_logging.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_loop_bound_client.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_mcp.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_medic_lab.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_medic_lab_web.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_metrics.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_multisource.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_native_tool_use.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_openai_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_outbox.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_patches.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_pending_actions.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_ports.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_produce_styles.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_produce_trigger.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_prompts.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_provider_auth.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_provider_retry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_providers_integration.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_public_api.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_quick.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_quota.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_reacts_to.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipe_conversation.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipe_plan_execute.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipe_reflection.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipe_supervisor.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipe_tool_loop.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipes.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipes_inputs.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipes_memory.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_recipes_skills.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_redaction.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_relation_graph.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_relations.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_repair.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_repair_web.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_replay.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_repo_agent.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_request.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_research.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_resources.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_retry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_routing.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_runtime.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_runtime_errors.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_scheduler_semantics.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_sessions.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_shutdown.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_skills.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_sources.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_sources_search.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_sources_vector.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_speech_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_starter_app.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_streaming.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_structured.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_support_copilot.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_assertions.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_golden.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_lab.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_mock.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_provenance.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_pytest_plugin.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_testing_registry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_token_budget.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_tools.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_trace_columns.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_trace_tags.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_type_registry.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_vendor_factories.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_vendor_multimodal_factories.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_verify.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_video_provider.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_view.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_viz.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_web_source.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_workspace.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/tests/test_workspace_with_sources.py +0 -0
- {reactifact-0.14.1 → reactifact-0.15.0}/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.15.0
|
|
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
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
[](https://github.com/bzdvdn/reactifact)
|
|
56
56
|
[](https://pypi.org/project/reactifact/)
|
|
57
57
|
[](LICENSE)
|
|
58
|
-
[](https://deepwiki.com/bzdvdn/reactifact)
|
|
59
59
|
[](https://bzdvdn.github.io/reactifact/)
|
|
60
60
|
|
|
61
61
|
> **Is:** a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://github.com/bzdvdn/reactifact)
|
|
10
10
|
[](https://pypi.org/project/reactifact/)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](https://deepwiki.com/bzdvdn/reactifact)
|
|
13
13
|
[](https://bzdvdn.github.io/reactifact/)
|
|
14
14
|
|
|
15
15
|
> **Is:** a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reactifact"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
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"
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
<meta charset="utf-8"/>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
6
|
<title>reactifact sessions</title>
|
|
7
|
-
<link rel="stylesheet" href="/traces/assets/app.css"/>
|
|
7
|
+
<link rel="stylesheet" href="__BASE__/traces/assets/app.css"/>
|
|
8
8
|
<script>(function(){try{var s=localStorage.getItem('reactifact-theme');var d=s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',d);}catch(e){document.documentElement.classList.add('dark');}})();</script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div class="app" id="app">
|
|
12
12
|
<aside class="sidebar">
|
|
13
|
-
<a class="brand" href="/traces">
|
|
13
|
+
<a class="brand" href="__BASE__/traces">
|
|
14
14
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="5" cy="12" r="2.6" fill="currentColor"/><circle cx="19" cy="5" r="2.6" fill="currentColor"/><circle cx="19" cy="19" r="2.6" fill="currentColor"/><path d="M7.4 11 16.6 6M7.4 13l9.2 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
15
15
|
<span class="name">reactifact <em>traces</em></span>
|
|
16
16
|
</a>
|
|
17
17
|
<nav class="nav">
|
|
18
|
-
<a class="nav-item" href="/traces">
|
|
18
|
+
<a class="nav-item" href="__BASE__/traces">
|
|
19
19
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="5" cy="12" r="2.2"/><circle cx="19" cy="5" r="2.2"/><circle cx="19" cy="19" r="2.2"/><path d="M7.2 11 16.8 6M7.2 13l9.6 5"/></svg>
|
|
20
20
|
<span class="nav-label">Traces</span>
|
|
21
21
|
</a>
|
|
22
|
-
<a class="nav-item active" href="/sessions">
|
|
22
|
+
<a class="nav-item active" href="__BASE__/sessions">
|
|
23
23
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v11H8l-4 4z"/></svg>
|
|
24
24
|
<span class="nav-label">Sessions</span>
|
|
25
25
|
</a>
|
|
@@ -97,7 +97,7 @@ async function load(){
|
|
|
97
97
|
const p=new URLSearchParams({limit:state.limit,offset:state.offset});
|
|
98
98
|
if(state.q)p.set('q',state.q);
|
|
99
99
|
try{
|
|
100
|
-
const res=await fetch('/api/sessions?'+p);
|
|
100
|
+
const res=await fetch('__BASE__/api/sessions?'+p);
|
|
101
101
|
if(!res.ok)throw new Error('HTTP '+res.status);
|
|
102
102
|
const data=await res.json();
|
|
103
103
|
state.items=data.items||[];state.total=data.total||0;render();
|
|
@@ -133,7 +133,7 @@ function updatePager(){
|
|
|
133
133
|
}
|
|
134
134
|
document.getElementById('rows').addEventListener('click',(e)=>{
|
|
135
135
|
const row=e.target.closest('tr.row');if(!row)return;
|
|
136
|
-
window.location.href='/traces?session_id='+encodeURIComponent(row.dataset.id);
|
|
136
|
+
window.location.href='__BASE__/traces?session_id='+encodeURIComponent(row.dataset.id);
|
|
137
137
|
});
|
|
138
138
|
document.getElementById('f-q').addEventListener('input',debounce((e)=>{state.q=e.target.value.trim();state.offset=0;load();}));
|
|
139
139
|
document.getElementById('prev').addEventListener('click',()=>{state.offset=Math.max(0,state.offset-state.limit);load();});
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
<meta charset="utf-8"/>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
6
|
<title>reactifact traces</title>
|
|
7
|
-
<link rel="stylesheet" href="/traces/assets/app.css"/>
|
|
7
|
+
<link rel="stylesheet" href="__BASE__/traces/assets/app.css"/>
|
|
8
8
|
<script>(function(){try{var s=localStorage.getItem('reactifact-theme');var d=s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',d);}catch(e){document.documentElement.classList.add('dark');}})();</script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div class="app" id="app">
|
|
12
12
|
<aside class="sidebar">
|
|
13
|
-
<a class="brand" href="/traces">
|
|
13
|
+
<a class="brand" href="__BASE__/traces">
|
|
14
14
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="5" cy="12" r="2.6" fill="currentColor"/><circle cx="19" cy="5" r="2.6" fill="currentColor"/><circle cx="19" cy="19" r="2.6" fill="currentColor"/><path d="M7.4 11 16.6 6M7.4 13l9.2 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
15
15
|
<span class="name">reactifact <em>traces</em></span>
|
|
16
16
|
</a>
|
|
17
17
|
<nav class="nav">
|
|
18
|
-
<a class="nav-item active" href="/traces">
|
|
18
|
+
<a class="nav-item active" href="__BASE__/traces">
|
|
19
19
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="5" cy="12" r="2.2"/><circle cx="19" cy="5" r="2.2"/><circle cx="19" cy="19" r="2.2"/><path d="M7.2 11 16.8 6M7.2 13l9.6 5"/></svg>
|
|
20
20
|
<span class="nav-label">Traces</span>
|
|
21
21
|
</a>
|
|
22
|
-
<a class="nav-item" href="/sessions">
|
|
22
|
+
<a class="nav-item" href="__BASE__/sessions">
|
|
23
23
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v11H8l-4 4z"/></svg>
|
|
24
24
|
<span class="nav-label">Sessions</span>
|
|
25
25
|
</a>
|
|
@@ -188,7 +188,7 @@ function loadColumnPref(){try{const raw=localStorage.getItem(COLS_KEY);VISIBLE_C
|
|
|
188
188
|
function saveColumnPref(){try{localStorage.setItem(COLS_KEY,JSON.stringify(VISIBLE_COLS||[]));}catch(_){}}
|
|
189
189
|
function visibleColumns(){const all=state.columns.map(c=>c.label);if(VISIBLE_COLS===null)return all;return all.filter(l=>VISIBLE_COLS.includes(l));}
|
|
190
190
|
async function loadColumns(){
|
|
191
|
-
try{const r=await fetch('/api/columns');state.columns=(await r.json()).items||[];}
|
|
191
|
+
try{const r=await fetch('__BASE__/api/columns');state.columns=(await r.json()).items||[];}
|
|
192
192
|
catch(_){state.columns=[];}
|
|
193
193
|
render();
|
|
194
194
|
}
|
|
@@ -287,7 +287,7 @@ function filters(){
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
async function loadFacets(){
|
|
290
|
-
try{const r=await fetch('/api/tags');const d=await r.json();state.facets=d.items||[];}
|
|
290
|
+
try{const r=await fetch('__BASE__/api/tags');const d=await r.json();state.facets=d.items||[];}
|
|
291
291
|
catch(_){state.facets=[];}
|
|
292
292
|
renderFacets();
|
|
293
293
|
}
|
|
@@ -308,7 +308,7 @@ function renderFacets(){
|
|
|
308
308
|
async function load(){
|
|
309
309
|
const q=filters();q.set('limit',state.limit);q.set('offset',state.offset);
|
|
310
310
|
try{
|
|
311
|
-
const res=await fetch('/api/traces?'+q);
|
|
311
|
+
const res=await fetch('__BASE__/api/traces?'+q);
|
|
312
312
|
if(!res.ok)throw new Error('HTTP '+res.status);
|
|
313
313
|
const data=await res.json();
|
|
314
314
|
state.runs=data.items||[];state.total=data.total||0;state.stats=data.stats||null;
|
|
@@ -454,7 +454,7 @@ document.getElementById('view-del').addEventListener('click',()=>{
|
|
|
454
454
|
const sel=document.getElementById('views');if(sel.value==='')return;
|
|
455
455
|
const views=getViews();views.splice(Number(sel.value),1);setViews(views);renderViews();toast('View deleted');
|
|
456
456
|
});
|
|
457
|
-
document.getElementById('export').addEventListener('click',()=>{const q=filters();q.set('limit',500);window.location.href='/api/traces/export?'+q;});
|
|
457
|
+
document.getElementById('export').addEventListener('click',()=>{const q=filters();q.set('limit',500);window.location.href='__BASE__/api/traces/export?'+q;});
|
|
458
458
|
document.getElementById('live').addEventListener('click',(e)=>{
|
|
459
459
|
state.live=!state.live;e.currentTarget.classList.toggle('on',state.live);
|
|
460
460
|
if(state.live){timer=setInterval(()=>{if(!document.hidden)load();},3000);}else{clearInterval(timer);}
|
|
@@ -462,7 +462,7 @@ document.getElementById('live').addEventListener('click',(e)=>{
|
|
|
462
462
|
document.getElementById('runs').addEventListener('click',(e)=>{
|
|
463
463
|
if(e.target.closest('.rowcheck'))return;
|
|
464
464
|
const row=e.target.closest('tr.row');if(!row)return;
|
|
465
|
-
window.location.href='/traces/'+row.dataset.id;
|
|
465
|
+
window.location.href='__BASE__/traces/'+row.dataset.id;
|
|
466
466
|
});
|
|
467
467
|
document.getElementById('runs').addEventListener('change',(e)=>{
|
|
468
468
|
const c=e.target.closest('.rowcheck');if(!c)return;
|
|
@@ -476,13 +476,13 @@ document.getElementById('bulk-clear').addEventListener('click',()=>{state.select
|
|
|
476
476
|
document.getElementById('bulk-apply').addEventListener('click',async()=>{
|
|
477
477
|
const tag=document.getElementById('bulk-tag').value.trim();if(!tag)return;
|
|
478
478
|
const note=document.getElementById('bulk-note').value;
|
|
479
|
-
await fetch('/api/traces/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({run_ids:Array.from(state.selected),tag:tag,note:note})});
|
|
479
|
+
await fetch('__BASE__/api/traces/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({run_ids:Array.from(state.selected),tag:tag,note:note})});
|
|
480
480
|
document.getElementById('bulk-tag').value='';document.getElementById('bulk-note').value='';
|
|
481
481
|
state.selected.clear();toast('Tagged');await refresh();
|
|
482
482
|
});
|
|
483
483
|
document.getElementById('bulk-remove').addEventListener('click',async()=>{
|
|
484
484
|
const tag=document.getElementById('bulk-remove-sel').value;if(!tag)return;
|
|
485
|
-
await fetch('/api/traces/tags/remove',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({run_ids:Array.from(state.selected),tag:tag})});
|
|
485
|
+
await fetch('__BASE__/api/traces/tags/remove',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({run_ids:Array.from(state.selected),tag:tag})});
|
|
486
486
|
state.selected.clear();toast('Tag removed');await refresh();
|
|
487
487
|
});
|
|
488
488
|
|
|
@@ -507,18 +507,18 @@ document.getElementById('tagrows').addEventListener('click',async(e)=>{
|
|
|
507
507
|
if(e.target.closest('.row-save')){
|
|
508
508
|
const name=row.querySelector('.row-name').value.trim();
|
|
509
509
|
const color=row.querySelector('.row-color').value;
|
|
510
|
-
await fetch('/api/tags/'+encodeURIComponent(old),{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({new_name:name,color:color})});
|
|
510
|
+
await fetch('__BASE__/api/tags/'+encodeURIComponent(old),{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({new_name:name,color:color})});
|
|
511
511
|
await refresh();renderTagRows();toast('Tag updated');
|
|
512
512
|
}else if(e.target.closest('.row-del')){
|
|
513
513
|
if(!confirm('Delete tag "'+old+'" from every run?'))return;
|
|
514
|
-
await fetch('/api/tags/'+encodeURIComponent(old),{method:'DELETE'});
|
|
514
|
+
await fetch('__BASE__/api/tags/'+encodeURIComponent(old),{method:'DELETE'});
|
|
515
515
|
await refresh();renderTagRows();toast('Tag deleted');
|
|
516
516
|
}
|
|
517
517
|
});
|
|
518
518
|
document.getElementById('new-tag-add').addEventListener('click',async()=>{
|
|
519
519
|
const name=document.getElementById('new-tag-name').value.trim();if(!name)return;
|
|
520
520
|
const color=document.getElementById('new-tag-color').value;
|
|
521
|
-
await fetch('/api/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:name,color:color})});
|
|
521
|
+
await fetch('__BASE__/api/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:name,color:color})});
|
|
522
522
|
document.getElementById('new-tag-name').value='';await refresh();renderTagRows();toast('Tag added');
|
|
523
523
|
});
|
|
524
524
|
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
<meta charset="utf-8"/>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
6
|
<title>reactifact trace</title>
|
|
7
|
-
<link rel="stylesheet" href="/traces/assets/app.css"/>
|
|
7
|
+
<link rel="stylesheet" href="__BASE__/traces/assets/app.css"/>
|
|
8
8
|
<script>(function(){try{var s=localStorage.getItem('reactifact-theme');var d=s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',d);}catch(e){document.documentElement.classList.add('dark');}})();</script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div class="app" id="app">
|
|
12
12
|
<aside class="sidebar">
|
|
13
|
-
<a class="brand" href="/traces">
|
|
13
|
+
<a class="brand" href="__BASE__/traces">
|
|
14
14
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="5" cy="12" r="2.6" fill="currentColor"/><circle cx="19" cy="5" r="2.6" fill="currentColor"/><circle cx="19" cy="19" r="2.6" fill="currentColor"/><path d="M7.4 11 16.6 6M7.4 13l9.2 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
15
15
|
<span class="name">reactifact <em>traces</em></span>
|
|
16
16
|
</a>
|
|
17
17
|
<nav class="nav">
|
|
18
|
-
<a class="nav-item active" href="/traces">
|
|
18
|
+
<a class="nav-item active" href="__BASE__/traces">
|
|
19
19
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="5" cy="12" r="2.2"/><circle cx="19" cy="5" r="2.2"/><circle cx="19" cy="19" r="2.2"/><path d="M7.2 11 16.8 6M7.2 13l9.6 5"/></svg>
|
|
20
20
|
<span class="nav-label">Traces</span>
|
|
21
21
|
</a>
|
|
22
|
-
<a class="nav-item" href="/sessions">
|
|
22
|
+
<a class="nav-item" href="__BASE__/sessions">
|
|
23
23
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v11H8l-4 4z"/></svg>
|
|
24
24
|
<span class="nav-label">Sessions</span>
|
|
25
25
|
</a>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
<button class="icon-btn" id="menu-btn" aria-label="open navigation">
|
|
43
43
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
|
|
44
44
|
</button>
|
|
45
|
-
<div class="crumb"><a href="/traces">traces</a><span class="sep">/</span><b id="cid">#…</b><span id="head"></span></div>
|
|
45
|
+
<div class="crumb"><a href="__BASE__/traces">traces</a><span class="sep">/</span><b id="cid">#…</b><span id="head"></span></div>
|
|
46
46
|
<div class="spacer"></div>
|
|
47
47
|
<button class="btn sm" id="copy-link"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.1 0l3-3a5 5 0 0 0-7.1-7.1L11 4.9"/><path d="M14 11a5 5 0 0 0-7.1 0l-3 3A5 5 0 0 0 11 21.1l1.9-1.9"/></svg><span data-label>link</span></button>
|
|
48
48
|
<button class="btn sm" id="copy-json"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span data-label>json</span></button>
|
|
@@ -104,7 +104,7 @@ function toast(msg){let t=document.getElementById('toast');if(!t){t=document.cre
|
|
|
104
104
|
|
|
105
105
|
async function load(){
|
|
106
106
|
try{
|
|
107
|
-
const res=await fetch('/api/traces/'+RUN_ID);
|
|
107
|
+
const res=await fetch('__BASE__/api/traces/'+RUN_ID);
|
|
108
108
|
if(res.status===404){document.getElementById('cid').textContent='#'+RUN_ID+' — not found';return;}
|
|
109
109
|
if(!res.ok)throw new Error('HTTP '+res.status);
|
|
110
110
|
render(await res.json());
|
|
@@ -122,7 +122,7 @@ function hex(c){return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(
|
|
|
122
122
|
let VOCAB=[];
|
|
123
123
|
let CURRENT=[];
|
|
124
124
|
async function loadVocab(){
|
|
125
|
-
try{const r=await fetch('/api/tags');const d=await r.json();VOCAB=(d.items||[]).map(x=>x.name);}
|
|
125
|
+
try{const r=await fetch('__BASE__/api/tags');const d=await r.json();VOCAB=(d.items||[]).map(x=>x.name);}
|
|
126
126
|
catch(_){VOCAB=[];}
|
|
127
127
|
const dl=document.getElementById('taglist');
|
|
128
128
|
if(dl)dl.innerHTML=VOCAB.map(n=>'<option value="'+escapeHtml(n)+'">').join('');
|
|
@@ -152,18 +152,18 @@ function renderAnnot(list){
|
|
|
152
152
|
document.getElementById('annot-note').addEventListener('keydown',(e)=>{if(e.key==='Enter'){e.preventDefault();addTag();}});
|
|
153
153
|
}
|
|
154
154
|
async function reloadTrace(){
|
|
155
|
-
const res=await fetch('/api/traces/'+RUN_ID);
|
|
155
|
+
const res=await fetch('__BASE__/api/traces/'+RUN_ID);
|
|
156
156
|
if(res.ok)renderAnnot((await res.json()).annotations||[]);
|
|
157
157
|
await loadVocab();
|
|
158
158
|
}
|
|
159
159
|
async function addTag(){
|
|
160
160
|
const tag=document.getElementById('annot-tag').value.trim();if(!tag)return;
|
|
161
161
|
const note=document.getElementById('annot-note').value;
|
|
162
|
-
await fetch('/api/traces/'+RUN_ID+'/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tag:tag,note:note})});
|
|
162
|
+
await fetch('__BASE__/api/traces/'+RUN_ID+'/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tag:tag,note:note})});
|
|
163
163
|
toast('Tag added');await reloadTrace();
|
|
164
164
|
}
|
|
165
165
|
async function removeTag(tag){
|
|
166
|
-
await fetch('/api/traces/'+RUN_ID+'/tags/'+encodeURIComponent(tag),{method:'DELETE'});
|
|
166
|
+
await fetch('__BASE__/api/traces/'+RUN_ID+'/tags/'+encodeURIComponent(tag),{method:'DELETE'});
|
|
167
167
|
toast('Tag removed');await reloadTrace();
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -33,11 +33,41 @@ from .columns import TraceColumn
|
|
|
33
33
|
from .store import TraceStoreProtocol
|
|
34
34
|
|
|
35
35
|
if TYPE_CHECKING:
|
|
36
|
-
from fastapi import APIRouter
|
|
36
|
+
from fastapi import APIRouter, Request
|
|
37
37
|
|
|
38
38
|
_TEMPLATES = Path(__file__).parent / "templates"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def _mount_base(request: Request, logical_path: str) -> str:
|
|
42
|
+
"""Path prefix this router is mounted under — ``include_router(prefix=…)``
|
|
43
|
+
and/or the ASGI ``root_path`` — so templates can emit prefix-aware
|
|
44
|
+
absolute URLs (``/*`` links and ``fetch`` paths) instead of assuming the
|
|
45
|
+
service sits at the origin root.
|
|
46
|
+
|
|
47
|
+
The prefix is everything before the *last* occurrence of this route's first
|
|
48
|
+
logical segment (``/traces`` / ``/sessions``) in the request path. The last
|
|
49
|
+
occurrence — not the first — because the mount prefix may itself contain
|
|
50
|
+
that segment (``prefix="/api/traces"`` → ``/api/traces/traces``), while the
|
|
51
|
+
route's own segment is always the one nearest the path parameters.
|
|
52
|
+
"""
|
|
53
|
+
root_path = request.scope.get("root_path", "") or ""
|
|
54
|
+
marker = "/" + logical_path.strip("/").split("/")[0]
|
|
55
|
+
index = request.url.path.rfind(marker)
|
|
56
|
+
return root_path + (request.url.path[:index] if index > 0 else "")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _render_page(
|
|
60
|
+
request: Request, filename: str, logical_path: str, **replacements: str
|
|
61
|
+
) -> str:
|
|
62
|
+
"""Read a UI template and fill ``__BASE__`` (mount prefix) plus any
|
|
63
|
+
caller placeholders (e.g. ``__RUN_ID__``)."""
|
|
64
|
+
text = (_TEMPLATES / filename).read_text(encoding="utf-8")
|
|
65
|
+
text = text.replace("__BASE__", _mount_base(request, logical_path))
|
|
66
|
+
for key, value in replacements.items():
|
|
67
|
+
text = text.replace(key, value)
|
|
68
|
+
return text
|
|
69
|
+
|
|
70
|
+
|
|
41
71
|
class TagAssign(BaseModel):
|
|
42
72
|
tag: str = Field(min_length=1, max_length=64)
|
|
43
73
|
note: str = ""
|
|
@@ -93,8 +123,13 @@ def create_trace_router(
|
|
|
93
123
|
|
|
94
124
|
require_extra("tracing.web.create_trace_router", "fastapi", "web")
|
|
95
125
|
|
|
96
|
-
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
126
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
97
127
|
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
|
128
|
+
|
|
129
|
+
# `from __future__ import annotations` keeps every hint a string, resolved
|
|
130
|
+
# against this module's globals at decoration time; fastapi is imported
|
|
131
|
+
# lazily here, so publish the names the page handlers annotate with.
|
|
132
|
+
globals()["Request"] = Request
|
|
98
133
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
|
99
134
|
|
|
100
135
|
dependencies = []
|
|
@@ -286,26 +321,27 @@ def create_trace_router(
|
|
|
286
321
|
)
|
|
287
322
|
|
|
288
323
|
@router.get("/traces", response_class=HTMLResponse)
|
|
289
|
-
async def traces_list_page() -> str:
|
|
290
|
-
return (
|
|
324
|
+
async def traces_list_page(request: Request) -> str:
|
|
325
|
+
return _render_page(request, "ui.html", "/traces")
|
|
291
326
|
|
|
292
327
|
@router.get("/sessions", response_class=HTMLResponse)
|
|
293
|
-
async def sessions_list_page() -> str:
|
|
294
|
-
return (
|
|
328
|
+
async def sessions_list_page(request: Request) -> str:
|
|
329
|
+
return _render_page(request, "sessions.html", "/sessions")
|
|
295
330
|
|
|
296
331
|
@router.get("/traces/{trace_id}", response_class=HTMLResponse)
|
|
297
|
-
async def traces_run_page(trace_id: str) -> str:
|
|
332
|
+
async def traces_run_page(request: Request, trace_id: str) -> str:
|
|
298
333
|
from ..viz import trace_provenance_to_mermaid, trace_to_mermaid
|
|
299
334
|
|
|
300
335
|
trace = await store.get(trace_id)
|
|
301
336
|
mermaid = trace_to_mermaid(trace) if trace is not None else ""
|
|
302
337
|
provenance = trace_provenance_to_mermaid(trace) if trace is not None else ""
|
|
303
|
-
return (
|
|
304
|
-
|
|
305
|
-
.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
338
|
+
return _render_page(
|
|
339
|
+
request,
|
|
340
|
+
"ui_run.html",
|
|
341
|
+
"/traces/{trace_id}",
|
|
342
|
+
__RUN_ID__=json.dumps(trace_id),
|
|
343
|
+
__MERMAID__=json.dumps(mermaid),
|
|
344
|
+
__MERMAID_GRAPH__=json.dumps(provenance),
|
|
309
345
|
)
|
|
310
346
|
|
|
311
347
|
return router
|
|
@@ -41,7 +41,7 @@ from ._extras import require_extra
|
|
|
41
41
|
from .chat import ChatAssistant, ChatEvent, ChatEventKind
|
|
42
42
|
|
|
43
43
|
if TYPE_CHECKING:
|
|
44
|
-
from fastapi import APIRouter
|
|
44
|
+
from fastapi import APIRouter, Request
|
|
45
45
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
46
46
|
|
|
47
47
|
|
|
@@ -83,6 +83,7 @@ def create_chat_router(
|
|
|
83
83
|
forward_kinds: Sequence[str] | None = None,
|
|
84
84
|
payload_shaper: Callable[[ChatEvent], Mapping[str, Any]] | None = None,
|
|
85
85
|
done_event: str | None = None,
|
|
86
|
+
request_factory: Callable[[Request], Mapping[str, Any]] | None = None,
|
|
86
87
|
) -> APIRouter:
|
|
87
88
|
"""Builds the chat router on top of a `ChatAssistant`.
|
|
88
89
|
|
|
@@ -100,6 +101,10 @@ def create_chat_router(
|
|
|
100
101
|
- `payload_shaper(event) -> mapping` — reshape a frame's payload.
|
|
101
102
|
- `done_event` — emit one extra terminal frame (e.g. `"done"`) when the
|
|
102
103
|
stream ends.
|
|
104
|
+
- `request_factory(request) -> mapping` — build the per-turn `request=`
|
|
105
|
+
mapping (the same one `ChatAssistant.stream` takes) from the incoming
|
|
106
|
+
FastAPI `Request`, e.g. the authenticated user for ACL/identity-aware
|
|
107
|
+
runs. Without it the turn runs with `request=None`.
|
|
103
108
|
|
|
104
109
|
Defaults reproduce the canonical contract exactly, so existing clients are
|
|
105
110
|
unaffected. The event schema (`ChatEvent`) and the effective names are
|
|
@@ -108,9 +113,15 @@ def create_chat_router(
|
|
|
108
113
|
# Readable error when the `web` extra is missing — then a regular
|
|
109
114
|
# (mypy-visible) import for the real types.
|
|
110
115
|
require_extra("web.create_chat_router", "fastapi", "web")
|
|
111
|
-
from fastapi import APIRouter
|
|
116
|
+
from fastapi import APIRouter, Request
|
|
112
117
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
113
118
|
|
|
119
|
+
# `Request` is only referenced from the endpoint's *string* annotation
|
|
120
|
+
# (`from __future__ import annotations`); FastAPI resolves those against
|
|
121
|
+
# the module globals, and fastapi may be absent at import time — so publish
|
|
122
|
+
# it lazily here, right before the endpoint that uses it is defined.
|
|
123
|
+
globals().setdefault("Request", Request)
|
|
124
|
+
|
|
114
125
|
names: dict[str, str] = {k: v for k, v in DEFAULT_EVENT_NAMES.items()}
|
|
115
126
|
names.update(event_names or {})
|
|
116
127
|
forward = set(forward_kinds) if forward_kinds is not None else None
|
|
@@ -140,9 +151,13 @@ def create_chat_router(
|
|
|
140
151
|
}
|
|
141
152
|
},
|
|
142
153
|
)
|
|
143
|
-
async def chat_stream(req: ChatMessage) -> StreamingResponse:
|
|
154
|
+
async def chat_stream(req: ChatMessage, request: Request) -> StreamingResponse:
|
|
155
|
+
turn_request = request_factory(request) if request_factory else None
|
|
156
|
+
|
|
144
157
|
async def stream() -> AsyncIterator[str]:
|
|
145
|
-
async for event in assistant.stream(
|
|
158
|
+
async for event in assistant.stream(
|
|
159
|
+
req.message, req.session_id, request=turn_request
|
|
160
|
+
):
|
|
146
161
|
if forward is not None and event.kind not in forward:
|
|
147
162
|
continue
|
|
148
163
|
yield sse(names.get(event.kind, event.kind), dict(shaper(event)))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reactifact
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
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
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
[](https://github.com/bzdvdn/reactifact)
|
|
56
56
|
[](https://pypi.org/project/reactifact/)
|
|
57
57
|
[](LICENSE)
|
|
58
|
-
[](https://deepwiki.com/bzdvdn/reactifact)
|
|
59
59
|
[](https://bzdvdn.github.io/reactifact/)
|
|
60
60
|
|
|
61
61
|
> **Is:** a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key
|
|
@@ -157,6 +157,78 @@ def test_web_router_openapi_publishes_event_schema(tmp_path):
|
|
|
157
157
|
assert "content" in responses["200"]["description"]
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
def test_web_router_request_factory_forwards_per_turn_context(tmp_path):
|
|
161
|
+
"""`request_factory` maps the FastAPI `Request` into the turn's `request=`."""
|
|
162
|
+
seen: dict[str, object] = {}
|
|
163
|
+
|
|
164
|
+
async def capture(call):
|
|
165
|
+
seen["user"] = call.request.get("user")
|
|
166
|
+
call.effects.create(A(text="answer"))
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
prod = produce(A)(capture)
|
|
170
|
+
agent = create_agent("cap", consumes=[Consume(Q)], produces=[prod])
|
|
171
|
+
store = SessionStore(FileKVBackend(os.path.join(tmp_path, "sess")))
|
|
172
|
+
assistant = ChatAssistant(
|
|
173
|
+
store=store,
|
|
174
|
+
agents=[agent],
|
|
175
|
+
user_message=Q,
|
|
176
|
+
reply=lambda ctx, msg_id: {"reply": "ok", "waiting": False},
|
|
177
|
+
max_concurrency=1,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
app = FastAPI()
|
|
181
|
+
app.include_router(
|
|
182
|
+
create_chat_router(
|
|
183
|
+
assistant,
|
|
184
|
+
request_factory=lambda request: {"user": request.headers.get("x-user", "")},
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
client = TestClient(app)
|
|
188
|
+
|
|
189
|
+
with client.stream(
|
|
190
|
+
"POST",
|
|
191
|
+
"/api/chat/stream",
|
|
192
|
+
json={"message": "hi", "session_id": "s1"},
|
|
193
|
+
headers={"X-User": "alice"},
|
|
194
|
+
) as response:
|
|
195
|
+
"".join(response.iter_text())
|
|
196
|
+
|
|
197
|
+
assert seen["user"] == "alice"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_web_router_without_request_factory_has_no_request(tmp_path):
|
|
201
|
+
"""Default stays `request=None`: no behavior change for existing callers."""
|
|
202
|
+
seen: dict[str, object] = {}
|
|
203
|
+
|
|
204
|
+
async def capture(call):
|
|
205
|
+
seen["request"] = dict(call.request)
|
|
206
|
+
call.effects.create(A(text="answer"))
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
prod = produce(A)(capture)
|
|
210
|
+
agent = create_agent("cap", consumes=[Consume(Q)], produces=[prod])
|
|
211
|
+
store = SessionStore(FileKVBackend(os.path.join(tmp_path, "sess")))
|
|
212
|
+
assistant = ChatAssistant(
|
|
213
|
+
store=store,
|
|
214
|
+
agents=[agent],
|
|
215
|
+
user_message=Q,
|
|
216
|
+
reply=lambda ctx, msg_id: {"reply": "ok", "waiting": False},
|
|
217
|
+
max_concurrency=1,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
app = FastAPI()
|
|
221
|
+
app.include_router(create_chat_router(assistant))
|
|
222
|
+
client = TestClient(app)
|
|
223
|
+
|
|
224
|
+
with client.stream(
|
|
225
|
+
"POST", "/api/chat/stream", json={"message": "hi", "session_id": "s1"}
|
|
226
|
+
) as response:
|
|
227
|
+
"".join(response.iter_text())
|
|
228
|
+
|
|
229
|
+
assert seen["request"] == {}
|
|
230
|
+
|
|
231
|
+
|
|
160
232
|
def test_disconnect_cancels_the_running_turn(tmp_path):
|
|
161
233
|
"""A dropped SSE client must cancel in-flight work, not leave it burning.
|
|
162
234
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import re
|
|
3
4
|
|
|
4
5
|
from pydantic import BaseModel
|
|
5
6
|
from reactifact import Agent, Consume, Context, Patch, Runtime, RuntimeResources
|
|
@@ -868,3 +869,86 @@ def test_recording_llm_prompt_hash_defaults_empty():
|
|
|
868
869
|
llm = RecordingLLM(FakeLLM("x"), on_call=recorded.append, agent_of=lambda: "a")
|
|
869
870
|
asyncio.run(llm.complete(LLMRequest(messages=[Message.user("hi")])))
|
|
870
871
|
assert recorded[0].prompt_hash == ""
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def test_trace_pages_are_prefix_aware(tmp_path):
|
|
875
|
+
from fastapi import FastAPI
|
|
876
|
+
from fastapi.testclient import TestClient
|
|
877
|
+
from reactifact.tracing.web import create_trace_router
|
|
878
|
+
|
|
879
|
+
store = TraceStore(str(tmp_path / "prefix.db"))
|
|
880
|
+
run(store.export(RunTrace(id="r1", session_id="s", outcome="completed")))
|
|
881
|
+
app = FastAPI()
|
|
882
|
+
app.include_router(create_trace_router(store), prefix="/ai")
|
|
883
|
+
client = TestClient(app)
|
|
884
|
+
|
|
885
|
+
listing = client.get("/ai/traces")
|
|
886
|
+
assert listing.status_code == 200
|
|
887
|
+
html = listing.text
|
|
888
|
+
assert "__BASE__" not in html
|
|
889
|
+
assert 'href="/ai/traces/assets/app.css"' in html
|
|
890
|
+
assert 'href="/ai/traces"' in html
|
|
891
|
+
assert 'href="/ai/sessions"' in html
|
|
892
|
+
assert "'/ai/api/traces" in html
|
|
893
|
+
assert "'/ai/api/columns" in html
|
|
894
|
+
assert "'/ai/traces/'" in html
|
|
895
|
+
|
|
896
|
+
sessions = client.get("/ai/sessions").text
|
|
897
|
+
assert "__BASE__" not in sessions
|
|
898
|
+
assert "'/ai/api/sessions" in sessions
|
|
899
|
+
assert 'href="/ai/traces"' in sessions
|
|
900
|
+
|
|
901
|
+
run_page = client.get("/ai/traces/r1").text
|
|
902
|
+
assert "__BASE__" not in run_page
|
|
903
|
+
assert "'/ai/api/traces/" in run_page
|
|
904
|
+
assert 'href="/ai/traces/assets/app.css"' in run_page
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def test_trace_pages_without_prefix_keep_root_paths(tmp_path):
|
|
908
|
+
from fastapi import FastAPI
|
|
909
|
+
from fastapi.testclient import TestClient
|
|
910
|
+
from reactifact.tracing.web import create_trace_router
|
|
911
|
+
|
|
912
|
+
store = TraceStore(str(tmp_path / "root.db"))
|
|
913
|
+
app = FastAPI()
|
|
914
|
+
app.include_router(create_trace_router(store))
|
|
915
|
+
client = TestClient(app)
|
|
916
|
+
html = client.get("/traces").text
|
|
917
|
+
assert "__BASE__" not in html
|
|
918
|
+
assert 'href="/traces/assets/app.css"' in html
|
|
919
|
+
assert "'/api/traces" in html
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def test_trace_pages_honor_asgi_root_path(tmp_path):
|
|
923
|
+
from fastapi import FastAPI
|
|
924
|
+
from fastapi.testclient import TestClient
|
|
925
|
+
from reactifact.tracing.web import create_trace_router
|
|
926
|
+
|
|
927
|
+
store = TraceStore(str(tmp_path / "rootpath.db"))
|
|
928
|
+
app = FastAPI(root_path="/ai")
|
|
929
|
+
app.include_router(create_trace_router(store))
|
|
930
|
+
client = TestClient(app)
|
|
931
|
+
html = client.get("/traces").text
|
|
932
|
+
assert "__BASE__" not in html
|
|
933
|
+
assert 'href="/ai/traces/assets/app.css"' in html
|
|
934
|
+
assert "'/ai/api/traces" in html
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def test_trace_pages_stay_prefixed_when_prefix_contains_a_route_segment(tmp_path):
|
|
938
|
+
"""A mount prefix that itself contains ``/traces`` (or ``/sessions``) must
|
|
939
|
+
not confuse the base: every absolute ref stays inside the prefix."""
|
|
940
|
+
from fastapi import FastAPI
|
|
941
|
+
from fastapi.testclient import TestClient
|
|
942
|
+
from reactifact.tracing.web import create_trace_router
|
|
943
|
+
|
|
944
|
+
store = TraceStore(str(tmp_path / "tricky.db"))
|
|
945
|
+
run(store.export(RunTrace(id="r1", session_id="s", outcome="completed")))
|
|
946
|
+
for prefix in ("/api/traces", "/traces-app", "/sessions", "/traces"):
|
|
947
|
+
app = FastAPI()
|
|
948
|
+
app.include_router(create_trace_router(store), prefix=prefix)
|
|
949
|
+
client = TestClient(app)
|
|
950
|
+
for logical in ("/traces", "/sessions", "/traces/r1"):
|
|
951
|
+
html = client.get(prefix + logical).text
|
|
952
|
+
assert "__BASE__" not in html
|
|
953
|
+
refs = re.findall(r'(?:href="|fetch\(\')(/[^"\'?]*)', html)
|
|
954
|
+
assert all(u.startswith(prefix) for u in refs), (prefix, logical, refs[:5])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|