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.
Files changed (222) hide show
  1. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/PKG-INFO +1 -1
  2. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_e2e.py +9 -2
  3. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_graph.py +42 -5
  4. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_trace.py +91 -4
  5. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/acp/__init__.py +15 -0
  6. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/acp/runner.py +68 -4
  7. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/train.py +5 -0
  8. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/anthropic.py +27 -12
  9. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/chat.py +24 -16
  10. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/responses.py +33 -11
  11. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/graph.py +26 -2
  12. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harness.py +4 -0
  13. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/claude_code/harness.py +14 -0
  14. verifiers-0.3.2.dev126/verifiers/v1/harnesses/codex/gate.mjs +34 -0
  15. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/codex/harness.py +46 -0
  16. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/kimi_code/harness.py +14 -0
  17. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/null/harness.py +5 -0
  18. verifiers-0.3.2.dev126/verifiers/v1/harnesses/pi/gate.mjs +11 -0
  19. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/pi/harness.py +18 -0
  20. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/core.py +20 -28
  21. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/server.py +30 -12
  22. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/rollout.py +1 -0
  23. verifiers-0.3.2.dev126/verifiers/v1/session.py +605 -0
  24. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/response.py +1 -0
  25. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/trace.py +3 -2
  26. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/types.py +9 -2
  27. verifiers-0.3.2.dev124/verifiers/v1/interception/tool.py +0 -12
  28. verifiers-0.3.2.dev124/verifiers/v1/session.py +0 -418
  29. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/.gitignore +0 -0
  30. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/LICENSE +0 -0
  31. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/README.md +0 -0
  32. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/pyproject.toml +0 -0
  33. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/conftest.py +0 -0
  34. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/counter_tool_v1.py +0 -0
  35. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/duet_v1.py +0 -0
  36. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
  37. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
  38. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_tool_v1.py +0 -0
  39. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
  40. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/echo_v1.py +0 -0
  41. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/pyproject.toml +0 -0
  42. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
  43. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_color_codeword_tasks.py +0 -0
  44. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_configs.py +0 -0
  45. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_envs.py +0 -0
  46. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_judges.py +0 -0
  47. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_platform_run.py +0 -0
  48. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_scoring.py +0 -0
  49. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_serve_delta.py +0 -0
  50. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/tests/v1/test_taskset.py +0 -0
  51. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/__init__.py +0 -0
  52. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/__init__.py +0 -0
  53. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/agent.py +0 -0
  54. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/__init__.py +0 -0
  55. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  56. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/base.py +0 -0
  57. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/eval.py +0 -0
  58. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/replay.py +0 -0
  59. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/dashboard/validate.py +0 -0
  60. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/debug.py +0 -0
  61. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/__init__.py +0 -0
  62. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/hint.py +0 -0
  63. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/main.py +0 -0
  64. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/resume.py +0 -0
  65. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/eval/runner.py +0 -0
  66. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/gepa.py +0 -0
  67. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/init.py +0 -0
  68. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/output.py +0 -0
  69. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/replay.py +0 -0
  70. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/resolve.py +0 -0
  71. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/resume.py +0 -0
  72. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/cli/validate.py +0 -0
  73. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/__init__.py +0 -0
  74. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/base.py +0 -0
  75. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/client.py +0 -0
  76. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/clients/eval.py +0 -0
  77. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/__init__.py +0 -0
  78. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/agent.py +0 -0
  79. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/__init__.py +0 -0
  80. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/debug.py +0 -0
  81. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/env.py +0 -0
  82. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/eval.py +0 -0
  83. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/init.py +0 -0
  84. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/replay.py +0 -0
  85. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/cli/validate.py +0 -0
  86. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/client.py +0 -0
  87. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/env.py +0 -0
  88. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/harness.py +0 -0
  89. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/judge.py +0 -0
  90. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/retries.py +0 -0
  91. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/runtime.py +0 -0
  92. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/serve.py +0 -0
  93. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/task.py +0 -0
  94. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/configs/taskset.py +0 -0
  95. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/__init__.py +0 -0
  96. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/dialects/base.py +0 -0
  97. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/env.py +0 -0
  98. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/__init__.py +0 -0
  99. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  100. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  101. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  102. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/best_of_n/env.py +0 -0
  103. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
  104. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
  105. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
  106. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  107. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/single_agent/env.py +0 -0
  108. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  109. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/envs/user_sim/env.py +0 -0
  110. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/episode.py +0 -0
  111. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/errors.py +0 -0
  112. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/__init__.py +0 -0
  113. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/adapter.py +0 -0
  114. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/config.py +0 -0
  115. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/dataset.py +0 -0
  116. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/reflection.py +0 -0
  117. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/gepa/runner.py +0 -0
  118. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/__init__.py +0 -0
  119. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  120. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/bash/harness.py +0 -0
  121. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  122. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  123. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  124. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  125. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  126. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  127. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  128. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  129. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  130. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  131. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  132. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  133. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/node.py +0 -0
  134. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/null/__init__.py +0 -0
  135. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  136. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  137. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  138. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  139. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  140. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  141. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  142. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  143. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  144. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  145. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  146. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  147. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/install.py +0 -0
  148. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/launch.py +0 -0
  149. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  150. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/__init__.py +0 -0
  151. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/base.py +0 -0
  152. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/pool.py +0 -0
  153. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  154. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/base.py +0 -0
  155. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/custom.py +0 -0
  156. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/interception/tunnel/prime.py +0 -0
  157. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judge.py +0 -0
  158. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/__init__.py +0 -0
  159. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/reference.py +0 -0
  160. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/reference.txt +0 -0
  161. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/rubric.py +0 -0
  162. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/judges/rubric.txt +0 -0
  163. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/__init__.py +0 -0
  164. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/launch.py +0 -0
  165. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/server.py +0 -0
  166. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/mcp/toolset.py +0 -0
  167. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/__init__.py +0 -0
  168. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/apptainer.py +0 -0
  169. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/base.py +0 -0
  170. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/container.py +0 -0
  171. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  172. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/docker/egress.py +0 -0
  173. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/limiters.py +0 -0
  174. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/modal.py +0 -0
  175. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/prime.py +0 -0
  176. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/runtimes/subprocess.py +0 -0
  177. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/semantic.py +0 -0
  178. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/__init__.py +0 -0
  179. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/client.py +0 -0
  180. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/delta.py +0 -0
  181. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/encoding.py +0 -0
  182. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/pool.py +0 -0
  183. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/server.py +0 -0
  184. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/serve/types.py +0 -0
  185. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/state.py +0 -0
  186. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/task.py +0 -0
  187. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/taskset.py +0 -0
  188. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/__init__.py +0 -0
  189. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  190. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/env.py +0 -0
  191. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  192. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  193. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/__init__.py +0 -0
  194. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/scoring.py +0 -0
  195. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/lean/taskset.py +0 -0
  196. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  197. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  198. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  199. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  200. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  201. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  202. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  203. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  204. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/__init__.py +0 -0
  205. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/aio.py +0 -0
  206. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/artifacts.py +0 -0
  207. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/compile.py +0 -0
  208. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/decorators.py +0 -0
  209. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/format.py +0 -0
  210. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/generic.py +0 -0
  211. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/git.py +0 -0
  212. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/image.py +0 -0
  213. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/interrupt.py +0 -0
  214. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/loaders.py +0 -0
  215. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/logging.py +0 -0
  216. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/memory.py +0 -0
  217. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/paths.py +0 -0
  218. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/platform.py +0 -0
  219. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/prime.py +0 -0
  220. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/retries.py +0 -0
  221. {verifiers-0.3.2.dev124 → verifiers-0.3.2.dev126}/verifiers/v1/utils/score.py +0 -0
  222. {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.dev124
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
- def test_parallel_commits_reconcile_shared_prompt_prefix():
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
- pending_a = graph.prepare_turn(trace, [system, user_a])
261
- pending_b = graph.prepare_turn(trace, [system, user_b])
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
- (node.parent, graph.message_hash(node.message)) for node in trace.nodes
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.graph import MessageNode
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
- def test_wire_trace_round_trip():
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=[vf.Tool(name="echo", description="", parameters={"type": "object"})],
240
+ tools=request.tools,
184
241
  nodes=[
185
242
  MessageNode(parent=None, message=UserMessage(content="q"), sampled=False),
186
- MessageNode(parent=0, message=AssistantMessage(content="a1"), sampled=True),
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 in ("allow_once", "allow_always")),
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
- response = await connection.prompt(session_id=session_id, prompt=blocks)
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
- THINKING = ("redacted_thinking", "thinking")
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 = [block for block in blocks if block["type"] in THINKING]
263
- state.sort(key=lambda block: THINKING.index(block["type"]))
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 THINKING:
315
- state.append(block.model_dump())
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: THINKING.index(block["type"]))
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
- name=t["name"],
586
- description=t.get("description", ""),
587
- parameters=t.get("input_schema", {}),
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 body.get("tools") or []
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
- # `or None` so a tools array with no function entries (e.g. only `custom`/built-in
139
- # tools) parses to None, not [] — the same contract as the anthropic/responses
140
- # dialects, and what keeps an empty parse from clearing `Trace.tools`.
141
- if not raw:
142
- return None
143
- return [
144
- Tool(
145
- name=t["function"]["name"],
146
- description=t["function"].get("description", ""),
147
- parameters=t["function"].get("parameters", {}),
148
- strict=t["function"].get("strict"),
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
- for t in raw
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
- if delta_native.get("name"):
306
- native["name"] = delta_native["name"]
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
- Tool(
617
- name=t["name"],
618
- description=t.get("description") or "",
619
- parameters=t.get("parameters") or {},
620
- strict=t.get("strict"),
621
- )
622
- for t in body.get("tools") or []
623
- if t.get("type") == "function"
624
- ] or None
625
- return Request(messages=prompt, tools=tools)
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)