verifiers 0.3.2.dev124__tar.gz → 0.3.2.dev126__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.
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/PKG-INFO +1 -1
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_e2e.py +9 -2
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_graph.py +42 -5
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_trace.py +91 -4
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/acp/__init__.py +15 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/acp/runner.py +68 -4
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/train.py +5 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/anthropic.py +27 -12
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/chat.py +24 -16
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/responses.py +33 -11
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/graph.py +26 -2
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harness.py +4 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/claude_code/harness.py +14 -0
- verifiers-0.3.2.dev126/verifiers/v1/harnesses/codex/gate.mjs +34 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/codex/harness.py +46 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/kimi_code/harness.py +14 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/null/harness.py +5 -0
- verifiers-0.3.2.dev126/verifiers/v1/harnesses/pi/gate.mjs +11 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/pi/harness.py +18 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/core.py +20 -28
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/server.py +30 -12
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/rollout.py +1 -0
- verifiers-0.3.2.dev126/verifiers/v1/session.py +605 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/response.py +1 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/trace.py +3 -2
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/types.py +9 -2
- verifiers-0.3.2.dev124/verifiers/v1/interception/tool.py +0 -12
- verifiers-0.3.2.dev124/verifiers/v1/session.py +0 -418
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/.gitignore +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/LICENSE +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/README.md +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/pyproject.toml +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/conftest.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/counter_tool_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/duet_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_tool_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/pyproject.toml +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_color_codeword_tasks.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_configs.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_envs.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_judges.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_platform_run.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_scoring.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_serve_delta.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/agent.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/eval.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/replay.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/validate.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/debug.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/hint.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/main.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/resume.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/runner.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/gepa.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/init.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/output.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/replay.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/resolve.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/resume.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/validate.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/client.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/eval.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/agent.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/debug.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/eval.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/init.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/replay.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/validate.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/client.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/judge.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/retries.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/runtime.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/serve.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/task.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/agentic_judge/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/best_of_n/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/single_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/single_agent/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/user_sim/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/user_sim/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/episode.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/errors.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/adapter.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/config.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/dataset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/reflection.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/runner.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/bash/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/bash/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/program.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/codex/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/node.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/null/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/pi/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/rlm/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/compaction.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/install.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/launch.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/mcp.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/pool.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/custom.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/prime.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judge.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/reference.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/reference.txt +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/rubric.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/rubric.txt +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/launch.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/server.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/toolset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/apptainer.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/base.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/container.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/docker/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/docker/egress.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/limiters.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/modal.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/prime.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/subprocess.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/semantic.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/client.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/delta.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/encoding.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/pool.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/server.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/types.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/state.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/task.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/env.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/scoring.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/aio.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/artifacts.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/compile.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/decorators.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/format.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/generic.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/git.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/image.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/interrupt.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/loaders.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/logging.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/memory.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/paths.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/platform.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/prime.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/retries.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/score.py +0 -0
- {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev126
|
|
4
4
|
Summary: Verifiers: Environments for LLM Reinforcement Learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
|
|
6
6
|
Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
|
|
@@ -373,14 +373,21 @@ async def test_acp_resume_with_tool(run_v1, harness, harness_runtime, tmp_path):
|
|
|
373
373
|
# Kimi Code is broken upstream: its Responses adapter drops message `phase` on replay.
|
|
374
374
|
if harness.id != "kimi-code":
|
|
375
375
|
assert trace.num_branches == 1
|
|
376
|
-
# Native MCP tools need not appear in the intercepted model request that
|
|
377
|
-
# populates trace.tools; the ACP transcript is the source of truth for use.
|
|
378
376
|
assert "tool" in segments[1]["roles"]
|
|
379
377
|
assert segments[1]["tool_outputs"]
|
|
380
378
|
if harness.id == "pi":
|
|
381
379
|
recall = next(tool for tool in trace.tools if tool.name == "resume_recall")
|
|
382
380
|
assert recall.parameters["properties"]["codeword"]["type"] == "string"
|
|
383
381
|
assert "codeword" in recall.parameters["required"]
|
|
382
|
+
if harness.id == "codex":
|
|
383
|
+
recall = next(tool for tool in trace.tools if tool.name.endswith("recall"))
|
|
384
|
+
assert recall.namespace
|
|
385
|
+
assert "codeword" in recall.parameters["properties"]
|
|
386
|
+
assert any(
|
|
387
|
+
call.namespace == recall.namespace and call.name == recall.name
|
|
388
|
+
for message in trace.assistant_messages
|
|
389
|
+
for call in message.tool_calls or []
|
|
390
|
+
)
|
|
384
391
|
if harness.id == "rlm":
|
|
385
392
|
assert "turns_since_last_compaction" in trace.metrics
|
|
386
393
|
assert all(call.acp is not None for call in trace.calls)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
import pytest
|
|
4
5
|
|
|
5
6
|
import verifiers.v1 as vf
|
|
6
7
|
from verifiers.v1 import graph
|
|
@@ -203,6 +204,31 @@ def test_tool_call_hash_matches_v0_content_and_arguments_normalization():
|
|
|
203
204
|
|
|
204
205
|
assert graph.message_hash(left) == graph.message_hash(right)
|
|
205
206
|
|
|
207
|
+
# Identical call IDs and short names can occur in different agent branches.
|
|
208
|
+
trace = vf.Trace(
|
|
209
|
+
agent=vf.AgentInfo(config=vf.AgentConfig()),
|
|
210
|
+
task=vf.TraceTask(type="Task", data=vf.TaskData(idx=0, prompt="lookup")),
|
|
211
|
+
)
|
|
212
|
+
user = vf.UserMessage(content="lookup")
|
|
213
|
+
calls = [
|
|
214
|
+
vf.AssistantMessage(
|
|
215
|
+
tool_calls=[
|
|
216
|
+
vf.ToolCall(id="call_0", name="lookup", namespace=ns, arguments="{}")
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
for ns in (None, "alpha", "beta")
|
|
220
|
+
]
|
|
221
|
+
node_ids = [
|
|
222
|
+
graph.prepare_turn(trace, [user]).commit(_response(message))
|
|
223
|
+
for message in calls
|
|
224
|
+
]
|
|
225
|
+
for message, node_id in zip(calls, node_ids, strict=True):
|
|
226
|
+
turn = graph.prepare_turn(
|
|
227
|
+
trace,
|
|
228
|
+
[user, message, vf.ToolMessage(content="result", tool_call_id="call_0")],
|
|
229
|
+
)
|
|
230
|
+
assert turn.prefix_node_ids == [0, node_id]
|
|
231
|
+
|
|
206
232
|
|
|
207
233
|
def test_reasoning_content_participates_in_graph_prefix_matching():
|
|
208
234
|
task = vf.TaskData(idx=0, prompt="use a tool")
|
|
@@ -243,7 +269,8 @@ def test_reasoning_content_participates_in_graph_prefix_matching():
|
|
|
243
269
|
assert len(tool_call_nodes) == 2
|
|
244
270
|
|
|
245
271
|
|
|
246
|
-
|
|
272
|
+
@pytest.mark.parametrize("same_tools", [False, True])
|
|
273
|
+
def test_parallel_commits_reconcile_shared_prompt_prefix(same_tools):
|
|
247
274
|
"""Two requests prepared from the same graph snapshot share any common prompt prefix that
|
|
248
275
|
the first response commits while the second is in flight. A later turn must keep following
|
|
249
276
|
its original child path rather than re-rooting through the sibling and stranding an orphan."""
|
|
@@ -257,10 +284,13 @@ def test_parallel_commits_reconcile_shared_prompt_prefix():
|
|
|
257
284
|
assistant_a = vf.AssistantMessage(content="A1")
|
|
258
285
|
|
|
259
286
|
# Both model requests leave before either response has committed its prompt.
|
|
260
|
-
|
|
261
|
-
|
|
287
|
+
tool_a = vf.Tool(name="echo", namespace="a")
|
|
288
|
+
tool_b = vf.Tool(name="echo", namespace="a" if same_tools else "b")
|
|
289
|
+
pending_a = graph.prepare_turn(trace, [system, user_a], [tool_a])
|
|
290
|
+
pending_b = graph.prepare_turn(trace, [system, user_b], [tool_b])
|
|
262
291
|
assistant_a_id = pending_a.commit(_response(assistant_a))
|
|
263
292
|
pending_b.commit(_response(vf.AssistantMessage(content="B1")))
|
|
293
|
+
assert trace.tools == ([tool_a] if same_tools else [tool_a, tool_b])
|
|
264
294
|
|
|
265
295
|
graph.prepare_turn(
|
|
266
296
|
trace,
|
|
@@ -270,16 +300,23 @@ def test_parallel_commits_reconcile_shared_prompt_prefix():
|
|
|
270
300
|
assistant_a,
|
|
271
301
|
vf.ToolMessage(content="tool A", tool_call_id="call_a"),
|
|
272
302
|
],
|
|
303
|
+
[tool_a],
|
|
273
304
|
).commit(_response(vf.AssistantMessage(content="A2")))
|
|
274
305
|
|
|
275
306
|
roots = [node for node in trace.nodes if node.parent is None]
|
|
276
307
|
identities = [
|
|
277
|
-
(
|
|
308
|
+
(
|
|
309
|
+
node.parent,
|
|
310
|
+
tuple(tool.namespace for tool in node.tools),
|
|
311
|
+
graph.message_hash(node.message),
|
|
312
|
+
)
|
|
313
|
+
for node in trace.nodes
|
|
278
314
|
]
|
|
279
|
-
assert len(roots) == 1
|
|
315
|
+
assert len(roots) == (1 if same_tools else 2)
|
|
280
316
|
assert len(identities) == len(set(identities))
|
|
281
317
|
assert trace.num_branches == 2
|
|
282
318
|
assert assistant_a_id not in graph.leaves(trace)
|
|
319
|
+
assert [branch.tools for branch in trace.branches] == [[tool_b], [tool_a]]
|
|
283
320
|
|
|
284
321
|
|
|
285
322
|
def test_parallel_commit_reconciles_only_token_identical_prefixes():
|
|
@@ -11,7 +11,9 @@ import pytest
|
|
|
11
11
|
import verifiers.v1 as vf
|
|
12
12
|
from verifiers.v1.agent import Interaction
|
|
13
13
|
from verifiers.v1.cli.output import write_episode
|
|
14
|
-
from verifiers.v1.
|
|
14
|
+
from verifiers.v1.dialects.chat import ChatDialect
|
|
15
|
+
from verifiers.v1.dialects.responses import ResponsesDialect, fold_assistant
|
|
16
|
+
from verifiers.v1.graph import MessageNode, prepare_turn
|
|
15
17
|
from verifiers.v1.harnesses.rlm.harness import (
|
|
16
18
|
RLM_SESSION_METADATA_KEY,
|
|
17
19
|
RLMHarness,
|
|
@@ -174,16 +176,71 @@ def test_custom_task_state_round_trip(tmp_path):
|
|
|
174
176
|
assert tr.state.artifacts["/artifact.bin"] == b"\xff\x00\xfe"
|
|
175
177
|
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
@pytest.mark.parametrize("history_type", ["additional_tools", "tool_search_output"])
|
|
180
|
+
def test_wire_trace_round_trip(history_type):
|
|
181
|
+
mcp = {
|
|
182
|
+
"type": "mcp",
|
|
183
|
+
"server_label": "alpha",
|
|
184
|
+
"server_url": "https://example.invalid/mcp",
|
|
185
|
+
"authorization": "trace-test-oauth-token",
|
|
186
|
+
"headers": {"Authorization": "Bearer trace-test-header-token"},
|
|
187
|
+
"Authorization": "trace-test-case-oauth-token",
|
|
188
|
+
"Headers": {"X-Api-Key": "trace-test-case-header-token"},
|
|
189
|
+
}
|
|
190
|
+
function = {
|
|
191
|
+
"type": "function",
|
|
192
|
+
"name": "echo",
|
|
193
|
+
"description": "Echo the supplied text.",
|
|
194
|
+
"parameters": {"type": "object", "properties": {"text": {"type": "string"}}},
|
|
195
|
+
"strict": False,
|
|
196
|
+
}
|
|
197
|
+
stale = function | {"parameters": {"type": "object"}, "strict": True}
|
|
198
|
+
request = ResponsesDialect().parse_request(
|
|
199
|
+
{
|
|
200
|
+
"input": [
|
|
201
|
+
{
|
|
202
|
+
"type": history_type,
|
|
203
|
+
"call_id": "search",
|
|
204
|
+
"tools": [
|
|
205
|
+
stale,
|
|
206
|
+
{"type": "namespace", "name": "mcp__world", "tools": [stale]},
|
|
207
|
+
],
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"tools": [
|
|
211
|
+
function,
|
|
212
|
+
{"type": "namespace", "name": "mcp__world", "tools": [function]},
|
|
213
|
+
{"type": "namespace", "name": "mcp__other", "tools": [function]},
|
|
214
|
+
{"type": "custom", "name": "patch", "format": {"type": "text"}},
|
|
215
|
+
{"type": "web_search", "search_context_size": "low"},
|
|
216
|
+
mcp,
|
|
217
|
+
mcp | {"server_label": "beta"},
|
|
218
|
+
],
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
assert request.tools is not None and len(request.tools) == 7
|
|
222
|
+
assert all(tool.parameters == function["parameters"] for tool in request.tools[:3])
|
|
223
|
+
assert all(tool.strict is False for tool in request.tools[:3])
|
|
224
|
+
assistant = fold_assistant(
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
"type": "function_call",
|
|
228
|
+
"call_id": "call_0",
|
|
229
|
+
"name": "echo",
|
|
230
|
+
"namespace": "mcp__world",
|
|
231
|
+
"arguments": '{"text":"hello"}',
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
)
|
|
178
235
|
# Two leaves off one root → 2 branches (a compaction-shaped trace), so the round-trip has to
|
|
179
236
|
# carry node `parent` links for `num_branches` to survive.
|
|
180
237
|
tr = vf.Trace[MyTask, vf.State](
|
|
181
238
|
agent=vf.AgentInfo(config=vf.AgentConfig()),
|
|
182
239
|
task=vf.TraceTask(type="MyTask", data=MyTask(idx=0, prompt="q", answer="a")),
|
|
183
|
-
tools=
|
|
240
|
+
tools=request.tools,
|
|
184
241
|
nodes=[
|
|
185
242
|
MessageNode(parent=None, message=UserMessage(content="q"), sampled=False),
|
|
186
|
-
MessageNode(parent=0, message=
|
|
243
|
+
MessageNode(parent=0, message=assistant, sampled=True),
|
|
187
244
|
MessageNode(parent=0, message=AssistantMessage(content="a2"), sampled=True),
|
|
188
245
|
],
|
|
189
246
|
)
|
|
@@ -193,9 +250,26 @@ def test_wire_trace_round_trip():
|
|
|
193
250
|
tr.info = {"build": "ok"}
|
|
194
251
|
tr.root_reply = "root answer"
|
|
195
252
|
tr.stop("done")
|
|
253
|
+
prepare_turn(tr, [], request.tools).commit_prompt()
|
|
196
254
|
|
|
197
255
|
# the dump is plain pydantic — derived values are properties, so they're not serialized
|
|
198
256
|
data = json.loads(tr.model_dump_json(exclude_none=True))
|
|
257
|
+
assert "trace-test-" not in json.dumps(data)
|
|
258
|
+
assert mcp["authorization"] == "trace-test-oauth-token"
|
|
259
|
+
assert mcp["headers"] == {"Authorization": "Bearer trace-test-header-token"}
|
|
260
|
+
assert mcp["Authorization"] == "trace-test-case-oauth-token"
|
|
261
|
+
assert mcp["Headers"] == {"X-Api-Key": "trace-test-case-header-token"}
|
|
262
|
+
chat_request = ChatDialect().parse_request(
|
|
263
|
+
{"tools": [mcp, mcp | {"server_label": "beta"}]}
|
|
264
|
+
)
|
|
265
|
+
chat_trace = tr.model_copy(update={"tools": []})
|
|
266
|
+
prepare_turn(chat_trace, [], chat_request.tools).commit_prompt()
|
|
267
|
+
assert [tool.name for tool in chat_trace.tools] == ["alpha", "beta"]
|
|
268
|
+
assert "trace-test-" not in chat_trace.model_dump_json()
|
|
269
|
+
assert (
|
|
270
|
+
vf.WireTrace.model_validate_json(chat_trace.model_dump_json()).tools
|
|
271
|
+
== chat_request.tools
|
|
272
|
+
)
|
|
199
273
|
assert "reward" not in data and "is_truncated" not in data
|
|
200
274
|
# exclude_none drops None FIELDS, not None dict values — unscored seeds survive
|
|
201
275
|
assert data["rewards"]["solved"] is None and data["metrics"]["acc"] is None
|
|
@@ -214,6 +288,19 @@ def test_wire_trace_round_trip():
|
|
|
214
288
|
assert (
|
|
215
289
|
rt.tools == tr.tools
|
|
216
290
|
) # the advertised tools persist (tool-use SFT reads them)
|
|
291
|
+
assert [tool.namespace for tool in rt.tools[:3]] == [
|
|
292
|
+
None,
|
|
293
|
+
"mcp__world",
|
|
294
|
+
"mcp__other",
|
|
295
|
+
]
|
|
296
|
+
assert all(tool.parameters == function["parameters"] for tool in rt.tools[:3])
|
|
297
|
+
assert all(tool.strict is False for tool in rt.tools[:3])
|
|
298
|
+
assert rt.tools[3].type == "custom"
|
|
299
|
+
assert rt.tools[3].model_extra == {"format": {"type": "text"}}
|
|
300
|
+
assert rt.tools[4].type == "web_search"
|
|
301
|
+
assert rt.tools[4].model_extra == {"search_context_size": "low"}
|
|
302
|
+
assert [tool.name for tool in rt.tools[5:]] == ["alpha", "beta"]
|
|
303
|
+
assert rt.nodes[1].message.tool_calls[0].namespace == "mcp__world"
|
|
217
304
|
assert rt.task.data.model_extra == {
|
|
218
305
|
"answer": "a"
|
|
219
306
|
} # taskset extras preserved on WireTaskData
|
|
@@ -85,6 +85,13 @@ class ACPHarness(Harness[ConfigT]):
|
|
|
85
85
|
)
|
|
86
86
|
trace.add_semantic_edges(edge_set)
|
|
87
87
|
|
|
88
|
+
async def gate_tools(
|
|
89
|
+
self, config: ACPConfig, runtime: Runtime, url: str, secret: str
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Configure the agent to ask before every tool call, so each request reaches the
|
|
92
|
+
runner's gate (`/tool` at `url`, keyed by `secret`). A harness that advertises
|
|
93
|
+
`SUPPORTS_TOOL_INTERCEPTION` implements this with the agent's own config."""
|
|
94
|
+
|
|
88
95
|
@abstractmethod
|
|
89
96
|
async def prepare_acp(
|
|
90
97
|
self,
|
|
@@ -116,6 +123,8 @@ class ACPHarness(Harness[ConfigT]):
|
|
|
116
123
|
config = await self.prepare_acp(
|
|
117
124
|
ctx, trace, runtime, endpoint, secret, mcp_urls, data
|
|
118
125
|
)
|
|
126
|
+
if tool_interception_url is not None:
|
|
127
|
+
await self.gate_tools(config, runtime, tool_interception_url, secret)
|
|
119
128
|
return ACPHarnessSession(
|
|
120
129
|
self,
|
|
121
130
|
ctx,
|
|
@@ -268,6 +277,12 @@ class ACPHarnessSession(HarnessSession):
|
|
|
268
277
|
"system_prompt": self.config.system_prompt or "",
|
|
269
278
|
"session_meta": self.config.session_meta or {},
|
|
270
279
|
"client_capabilities": self.config.client_capabilities or {},
|
|
280
|
+
"tool_interception": {
|
|
281
|
+
"url": self.tool_interception_url,
|
|
282
|
+
"secret": self.secret,
|
|
283
|
+
}
|
|
284
|
+
if self.tool_interception_url
|
|
285
|
+
else None,
|
|
271
286
|
}
|
|
272
287
|
async with self._lock:
|
|
273
288
|
if self._closed:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# /// script
|
|
2
2
|
# requires-python = ">=3.10,<3.15"
|
|
3
|
-
# dependencies = ["agent-client-protocol==0.12.1"]
|
|
3
|
+
# dependencies = ["agent-client-protocol==0.12.1", "httpx"]
|
|
4
4
|
# ///
|
|
5
5
|
"""Run harness segments through an ACP agent."""
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ from contextlib import AsyncExitStack, suppress
|
|
|
14
14
|
from dataclasses import asdict, dataclass
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
+
import httpx
|
|
17
18
|
from acp import (
|
|
18
19
|
PROTOCOL_VERSION,
|
|
19
20
|
Client,
|
|
@@ -45,8 +46,42 @@ class ACPTurn:
|
|
|
45
46
|
update_metadata: list[dict[str, Any]]
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
class ToolGate:
|
|
50
|
+
"""The rollout's `/tool` gate, asked before every tool call the agent wants to run."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, url: str, secret: str) -> None:
|
|
53
|
+
self.url = url
|
|
54
|
+
self.client = httpx.AsyncClient(
|
|
55
|
+
headers={"Authorization": f"Bearer {secret}"},
|
|
56
|
+
timeout=httpx.Timeout(120, connect=5),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def decision(self, tool_call_id: str, arguments: Any) -> str:
|
|
60
|
+
try:
|
|
61
|
+
# pi-acp wraps extension confirmations in a separate UI permission call.
|
|
62
|
+
if (
|
|
63
|
+
tool_call_id.startswith("pi-ui-")
|
|
64
|
+
and isinstance(arguments, dict)
|
|
65
|
+
and arguments.get("method") == "confirm"
|
|
66
|
+
):
|
|
67
|
+
tool_call_id, arguments = (
|
|
68
|
+
arguments["title"],
|
|
69
|
+
json.loads(arguments["message"]),
|
|
70
|
+
)
|
|
71
|
+
response = await self.client.post(
|
|
72
|
+
self.url, json={"tool_call_id": tool_call_id, "arguments": arguments}
|
|
73
|
+
)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
return response.json()["action"]
|
|
76
|
+
except Exception as error: # noqa: BLE001 - an unreachable gate lets nothing run
|
|
77
|
+
print(f"tool gate denied {tool_call_id}: {error}", file=sys.stderr)
|
|
78
|
+
return "deny"
|
|
79
|
+
|
|
80
|
+
|
|
48
81
|
class VerifiersACPClient(Client):
|
|
49
82
|
def __init__(self) -> None:
|
|
83
|
+
self.gate: ToolGate | None = None
|
|
84
|
+
self.prompt_task: asyncio.Task | None = None
|
|
50
85
|
self.visible_reply = ""
|
|
51
86
|
self.message_id: str | None = None
|
|
52
87
|
self.stop_reason: str | None = None
|
|
@@ -90,9 +125,23 @@ class VerifiersACPClient(Client):
|
|
|
90
125
|
options: list[PermissionOption],
|
|
91
126
|
**kwargs: Any,
|
|
92
127
|
) -> RequestPermissionResponse:
|
|
128
|
+
"""Ask the rollout before execution: deny rejects one call; stop cancels the turn."""
|
|
129
|
+
kinds = ("allow_once", "allow_always")
|
|
130
|
+
if self.gate is not None:
|
|
131
|
+
decision = await self.gate.decision(
|
|
132
|
+
tool_call.tool_call_id, tool_call.raw_input
|
|
133
|
+
)
|
|
134
|
+
if decision == "stop":
|
|
135
|
+
self.stop_reason = "cancelled"
|
|
136
|
+
if self.prompt_task is not None:
|
|
137
|
+
self.prompt_task.cancel()
|
|
138
|
+
return RequestPermissionResponse(
|
|
139
|
+
outcome=DeniedOutcome(outcome="cancelled")
|
|
140
|
+
)
|
|
141
|
+
if decision != "allow":
|
|
142
|
+
kinds = ("reject_once", "reject_always")
|
|
93
143
|
option = next(
|
|
94
|
-
(item for item in options if item.kind
|
|
95
|
-
None,
|
|
144
|
+
(item for kind in kinds for item in options if item.kind == kind), None
|
|
96
145
|
)
|
|
97
146
|
outcome = (
|
|
98
147
|
AllowedOutcome(outcome="selected", option_id=option.option_id)
|
|
@@ -165,12 +214,21 @@ async def prompt(
|
|
|
165
214
|
if not blocks:
|
|
166
215
|
raise ValueError("ACP prompt has no content")
|
|
167
216
|
try:
|
|
168
|
-
|
|
217
|
+
client.prompt_task = asyncio.create_task(
|
|
218
|
+
connection.prompt(session_id=session_id, prompt=blocks)
|
|
219
|
+
)
|
|
220
|
+
response = await client.prompt_task
|
|
169
221
|
client.stop_reason = response.stop_reason
|
|
170
222
|
client.response_metadata = dict(response.field_meta or {})
|
|
223
|
+
except asyncio.CancelledError:
|
|
224
|
+
if client.stop_reason != "cancelled":
|
|
225
|
+
raise
|
|
226
|
+
await connection.cancel(session_id=session_id)
|
|
171
227
|
except RequestError as error:
|
|
172
228
|
detail = error.data.get("details") if isinstance(error.data, dict) else None
|
|
173
229
|
raise RuntimeError(detail or str(error)) from error
|
|
230
|
+
finally:
|
|
231
|
+
client.prompt_task = None
|
|
174
232
|
return client.turn_result()
|
|
175
233
|
|
|
176
234
|
|
|
@@ -222,6 +280,9 @@ class ACPSession:
|
|
|
222
280
|
self.is_new = True
|
|
223
281
|
|
|
224
282
|
async def run(self, config: dict) -> ACPTurn:
|
|
283
|
+
gate = config.get("tool_interception")
|
|
284
|
+
if gate and self.client.gate is None:
|
|
285
|
+
self.client.gate = ToolGate(gate["url"], gate["secret"])
|
|
225
286
|
if self.connection is None:
|
|
226
287
|
await self.start(config)
|
|
227
288
|
assert self.session_id is not None
|
|
@@ -253,6 +314,9 @@ class ACPSession:
|
|
|
253
314
|
try:
|
|
254
315
|
await self.stack.aclose()
|
|
255
316
|
finally:
|
|
317
|
+
if self.client.gate is not None:
|
|
318
|
+
await self.client.gate.client.aclose()
|
|
319
|
+
self.client.gate = None
|
|
256
320
|
self._reset()
|
|
257
321
|
return response_metadata
|
|
258
322
|
|
|
@@ -38,6 +38,10 @@ T = TypeVar("T")
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def tool_to_wire(tool: Tool) -> dict:
|
|
41
|
+
if tool.type != "function" or tool.namespace:
|
|
42
|
+
raise NotImplementedError(
|
|
43
|
+
"The renderer client only supports unnamespaced function tools."
|
|
44
|
+
)
|
|
41
45
|
function: dict = {
|
|
42
46
|
"name": tool.name,
|
|
43
47
|
"description": tool.description,
|
|
@@ -61,6 +65,7 @@ def serialize_completion(response: Response, model: str) -> dict:
|
|
|
61
65
|
"type": c.type,
|
|
62
66
|
c.type: {
|
|
63
67
|
"name": c.name,
|
|
68
|
+
**({"namespace": c.namespace} if c.namespace else {}),
|
|
64
69
|
"input" if c.type == "custom" else "arguments": c.arguments,
|
|
65
70
|
},
|
|
66
71
|
}
|
|
@@ -52,7 +52,8 @@ STOP_REASONS: dict[str, FinishReason] = {
|
|
|
52
52
|
"stop_sequence": "stop",
|
|
53
53
|
}
|
|
54
54
|
# Claude may reorder mixed thinking block types between a response and its replay.
|
|
55
|
-
|
|
55
|
+
# Native tool events share the final rank, preserving their relative order.
|
|
56
|
+
THINKING_ORDER = {"redacted_thinking": 0, "thinking": 1}
|
|
56
57
|
# These versioned tool families return calls to the harness; every other typed tool may execute
|
|
57
58
|
# at the provider. Anchoring the pattern keeps new versions client-side without treating an
|
|
58
59
|
# arbitrary dated provider tool as safe.
|
|
@@ -259,8 +260,10 @@ def parse_messages(body: dict) -> Messages:
|
|
|
259
260
|
if isinstance(content, str)
|
|
260
261
|
else content or []
|
|
261
262
|
)
|
|
262
|
-
state = [
|
|
263
|
-
|
|
263
|
+
state = [
|
|
264
|
+
block for block in blocks if block["type"] not in ("text", "tool_use")
|
|
265
|
+
]
|
|
266
|
+
state.sort(key=lambda block: THINKING_ORDER.get(block["type"], 2))
|
|
264
267
|
text = "".join(b.get("text", "") for b in blocks if b.get("type") == "text")
|
|
265
268
|
reasoning = "".join(
|
|
266
269
|
b.get("thinking", "") for b in blocks if b.get("type") == "thinking"
|
|
@@ -269,6 +272,7 @@ def parse_messages(body: dict) -> Messages:
|
|
|
269
272
|
ToolCall(
|
|
270
273
|
id=b.get("id", ""),
|
|
271
274
|
name=b.get("name", ""),
|
|
275
|
+
namespace=b.get("toolset_name"),
|
|
272
276
|
arguments=json.dumps(b.get("input") or {}),
|
|
273
277
|
)
|
|
274
278
|
for b in blocks
|
|
@@ -311,8 +315,9 @@ def response_from_wire(message: AnthropicMessage) -> Response:
|
|
|
311
315
|
reasoning: list[str] = []
|
|
312
316
|
calls: list[ToolCall] = []
|
|
313
317
|
for block in message.content:
|
|
314
|
-
if block.type in
|
|
315
|
-
|
|
318
|
+
if block.type not in ("text", "tool_use"):
|
|
319
|
+
# SDK-inserted defaults are absent when the native response is replayed.
|
|
320
|
+
state.append(block.model_dump(exclude_unset=True))
|
|
316
321
|
if block.type == "text":
|
|
317
322
|
content.append(block.text)
|
|
318
323
|
elif block.type == "thinking":
|
|
@@ -322,10 +327,11 @@ def response_from_wire(message: AnthropicMessage) -> Response:
|
|
|
322
327
|
ToolCall(
|
|
323
328
|
id=block.id,
|
|
324
329
|
name=block.name,
|
|
330
|
+
namespace=block.toolset_name,
|
|
325
331
|
arguments=json.dumps(block.input or {}),
|
|
326
332
|
)
|
|
327
333
|
)
|
|
328
|
-
state.sort(key=lambda block:
|
|
334
|
+
state.sort(key=lambda block: THINKING_ORDER.get(block["type"], 2))
|
|
329
335
|
finish = STOP_REASONS.get(message.stop_reason or "")
|
|
330
336
|
provider_usage = message.usage
|
|
331
337
|
output_details = provider_usage.model_dump().get("output_tokens_details")
|
|
@@ -580,14 +586,23 @@ class AnthropicDialect(Dialect[AnthropicMessage]):
|
|
|
580
586
|
}
|
|
581
587
|
|
|
582
588
|
def parse_request(self, body: RawRequest) -> Request:
|
|
589
|
+
native_tools = body.get("tools") or []
|
|
590
|
+
if not isinstance(native_tools, list) or any(
|
|
591
|
+
not isinstance(tool, dict) for tool in native_tools
|
|
592
|
+
):
|
|
593
|
+
raise ValueError("tools must be an array of objects")
|
|
583
594
|
tools = [
|
|
584
|
-
Tool(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
595
|
+
Tool.model_validate(
|
|
596
|
+
{k: v for k, v in t.items() if k != "input_schema"}
|
|
597
|
+
| {
|
|
598
|
+
"name": t.get("name") or t.get("mcp_server_name") or t.get("type"),
|
|
599
|
+
"type": "function"
|
|
600
|
+
if t.get("type") in (None, "custom")
|
|
601
|
+
else t["type"],
|
|
602
|
+
"parameters": t.get("input_schema") or {},
|
|
603
|
+
}
|
|
588
604
|
)
|
|
589
|
-
for t in
|
|
590
|
-
if "input_schema" in t # skip server tools (web_search etc.)
|
|
605
|
+
for t in native_tools
|
|
591
606
|
] or None
|
|
592
607
|
return Request(messages=parse_messages(body), tools=tools)
|
|
593
608
|
|
|
@@ -122,6 +122,7 @@ def parse_message(raw: dict) -> Message:
|
|
|
122
122
|
id=call["id"],
|
|
123
123
|
type=kind,
|
|
124
124
|
name=native["name"],
|
|
125
|
+
namespace=native.get("namespace"),
|
|
125
126
|
arguments=native["input" if kind == "custom" else "arguments"],
|
|
126
127
|
)
|
|
127
128
|
)
|
|
@@ -135,21 +136,26 @@ def parse_message(raw: dict) -> Message:
|
|
|
135
136
|
|
|
136
137
|
|
|
137
138
|
def parse_tools(raw: list[dict] | None) -> list[Tool] | None:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
tools = []
|
|
140
|
+
for declaration in raw or []:
|
|
141
|
+
kind = declaration.get("type", "function")
|
|
142
|
+
tool = declaration.get(kind, declaration)
|
|
143
|
+
if kind == "mcp":
|
|
144
|
+
tool = {
|
|
145
|
+
key: value
|
|
146
|
+
for key, value in tool.items()
|
|
147
|
+
if key.lower() not in ("authorization", "headers")
|
|
148
|
+
}
|
|
149
|
+
tools.append(
|
|
150
|
+
Tool.model_validate(
|
|
151
|
+
tool
|
|
152
|
+
| {
|
|
153
|
+
"type": kind,
|
|
154
|
+
"name": tool.get("name") or tool.get("server_label") or kind,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
149
157
|
)
|
|
150
|
-
|
|
151
|
-
if t.get("type", "function") == "function"
|
|
152
|
-
] or None
|
|
158
|
+
return tools or None
|
|
153
159
|
|
|
154
160
|
|
|
155
161
|
# --- vf -> chat wire ----------------------------------------------------------
|
|
@@ -184,6 +190,7 @@ def message_to_wire(message: Message) -> dict:
|
|
|
184
190
|
"type": call.type,
|
|
185
191
|
call.type: {
|
|
186
192
|
"name": call.name,
|
|
193
|
+
**({"namespace": call.namespace} if call.namespace else {}),
|
|
187
194
|
"input"
|
|
188
195
|
if call.type == "custom"
|
|
189
196
|
else "arguments": call.arguments,
|
|
@@ -302,8 +309,9 @@ class ChatStreamParser(StreamParser):
|
|
|
302
309
|
slot["type"] = kind
|
|
303
310
|
native = slot.setdefault(kind, {"name": ""})
|
|
304
311
|
delta_native = tool_call.get(kind) or {}
|
|
305
|
-
|
|
306
|
-
|
|
312
|
+
for field in ("name", "namespace"):
|
|
313
|
+
if delta_native.get(field):
|
|
314
|
+
native[field] = delta_native[field]
|
|
307
315
|
input_field = "input" if kind == "custom" else "arguments"
|
|
308
316
|
self.tool_inputs.setdefault(index, []).append(
|
|
309
317
|
delta_native.get(input_field) or ""
|
|
@@ -303,6 +303,7 @@ def fold_assistant(items: list[dict] | None) -> AssistantMessage:
|
|
|
303
303
|
id=item.get("call_id", ""),
|
|
304
304
|
type="custom" if kind == "custom_tool_call" else "function",
|
|
305
305
|
name=item.get("name", ""),
|
|
306
|
+
namespace=item.get("namespace"),
|
|
306
307
|
arguments=item.get("arguments", item.get("input", "")),
|
|
307
308
|
)
|
|
308
309
|
)
|
|
@@ -612,17 +613,38 @@ class ResponsesDialect(Dialect[OpenAIResponse]):
|
|
|
612
613
|
prompt.append(UserMessage(content=parse_content(item.get("content"))))
|
|
613
614
|
if run:
|
|
614
615
|
prompt.append(fold_assistant(run))
|
|
615
|
-
tools = [
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if
|
|
624
|
-
|
|
625
|
-
|
|
616
|
+
tools = []
|
|
617
|
+
declarations = []
|
|
618
|
+
for item in items:
|
|
619
|
+
if item.get("type") in ("additional_tools", "tool_search_output"):
|
|
620
|
+
declarations.extend(item.get("tools") or [])
|
|
621
|
+
# Current declarations take precedence over definitions replayed in history.
|
|
622
|
+
declarations.extend(body.get("tools") or [])
|
|
623
|
+
for group in declarations:
|
|
624
|
+
namespace = group["name"] if group.get("type") == "namespace" else None
|
|
625
|
+
for tool in group["tools"] if namespace else [group]:
|
|
626
|
+
if tool.get("type") == "mcp":
|
|
627
|
+
# Connection credentials belong in the native request, not the trace.
|
|
628
|
+
tool = {
|
|
629
|
+
key: value
|
|
630
|
+
for key, value in tool.items()
|
|
631
|
+
if key.lower() not in ("authorization", "headers")
|
|
632
|
+
}
|
|
633
|
+
tools.append(
|
|
634
|
+
Tool.model_validate(
|
|
635
|
+
tool
|
|
636
|
+
| {
|
|
637
|
+
"name": tool.get("name")
|
|
638
|
+
or tool.get("server_label")
|
|
639
|
+
or tool.get("type"),
|
|
640
|
+
"namespace": namespace,
|
|
641
|
+
"description": tool.get("description") or "",
|
|
642
|
+
"parameters": tool.get("parameters") or {},
|
|
643
|
+
}
|
|
644
|
+
)
|
|
645
|
+
)
|
|
646
|
+
tools = list({(t.namespace, t.name, t.type): t for t in tools}.values())
|
|
647
|
+
return Request(messages=prompt, tools=tools or None)
|
|
626
648
|
|
|
627
649
|
def parse_response(self, response: OpenAIResponse) -> Response:
|
|
628
650
|
return response_from_wire(response)
|