verifiers 0.3.2.dev65__tar.gz → 0.3.2.dev66__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.

Potentially problematic release.


This version of verifiers might be problematic. Click here for more details.

Files changed (216) hide show
  1. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/PKG-INFO +1 -1
  2. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/bash/harness.py +0 -2
  3. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/browser_use/program.py +53 -55
  4. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/null/harness.py +0 -2
  5. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/utils/core.py +31 -28
  6. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/utils/launch.py +1 -4
  7. verifiers-0.3.2.dev66/verifiers/v1/harnesses/utils/mcp.py +201 -0
  8. verifiers-0.3.2.dev65/verifiers/v1/harnesses/utils/mcp.py +0 -127
  9. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/.gitignore +0 -0
  10. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/LICENSE +0 -0
  11. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/README.md +0 -0
  12. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/pyproject.toml +0 -0
  13. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/conftest.py +0 -0
  14. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/counter_tool_v1.py +0 -0
  15. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/duet_v1.py +0 -0
  16. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
  17. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
  18. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/echo_tool_v1.py +0 -0
  19. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
  20. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/echo_v1.py +0 -0
  21. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/pyproject.toml +0 -0
  22. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
  23. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_color_codeword_tasks.py +0 -0
  24. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_configs.py +0 -0
  25. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_e2e.py +0 -0
  26. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_envs.py +0 -0
  27. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_graph.py +0 -0
  28. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_judges.py +0 -0
  29. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_scoring.py +0 -0
  30. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_taskset.py +0 -0
  31. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/tests/v1/test_trace.py +0 -0
  32. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/__init__.py +0 -0
  33. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/cli/commands/init.py +0 -0
  34. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/__init__.py +0 -0
  35. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/acp/__init__.py +0 -0
  36. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/acp/runner.py +0 -0
  37. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/agent.py +0 -0
  38. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/__init__.py +0 -0
  39. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  40. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/dashboard/base.py +0 -0
  41. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/dashboard/eval.py +0 -0
  42. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/dashboard/replay.py +0 -0
  43. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/dashboard/validate.py +0 -0
  44. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/debug.py +0 -0
  45. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/eval/__init__.py +0 -0
  46. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/eval/main.py +0 -0
  47. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/eval/resume.py +0 -0
  48. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/eval/runner.py +0 -0
  49. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/gepa.py +0 -0
  50. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/init.py +0 -0
  51. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/output.py +0 -0
  52. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/replay.py +0 -0
  53. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/resolve.py +0 -0
  54. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/resume.py +0 -0
  55. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/cli/validate.py +0 -0
  56. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/clients/__init__.py +0 -0
  57. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/clients/base.py +0 -0
  58. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/clients/client.py +0 -0
  59. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/clients/eval.py +0 -0
  60. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/clients/train.py +0 -0
  61. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/__init__.py +0 -0
  62. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/agent.py +0 -0
  63. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/__init__.py +0 -0
  64. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/debug.py +0 -0
  65. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/env.py +0 -0
  66. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/eval.py +0 -0
  67. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/init.py +0 -0
  68. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/replay.py +0 -0
  69. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/cli/validate.py +0 -0
  70. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/client.py +0 -0
  71. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/env.py +0 -0
  72. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/harness.py +0 -0
  73. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/judge.py +0 -0
  74. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/retries.py +0 -0
  75. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/runtime.py +0 -0
  76. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/serve.py +0 -0
  77. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/task.py +0 -0
  78. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/configs/taskset.py +0 -0
  79. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/dialects/__init__.py +0 -0
  80. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/dialects/anthropic.py +0 -0
  81. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/dialects/base.py +0 -0
  82. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/dialects/chat.py +0 -0
  83. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/dialects/responses.py +0 -0
  84. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/env.py +0 -0
  85. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/__init__.py +0 -0
  86. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  87. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  88. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  89. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/best_of_n/env.py +0 -0
  90. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
  91. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
  92. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
  93. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  94. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/single_agent/env.py +0 -0
  95. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  96. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/envs/user_sim/env.py +0 -0
  97. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/episode.py +0 -0
  98. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/errors.py +0 -0
  99. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/__init__.py +0 -0
  100. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/adapter.py +0 -0
  101. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/config.py +0 -0
  102. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/dataset.py +0 -0
  103. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/reflection.py +0 -0
  104. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/gepa/runner.py +0 -0
  105. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/graph.py +0 -0
  106. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harness.py +0 -0
  107. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/__init__.py +0 -0
  108. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  109. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  110. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  111. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  112. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  113. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  114. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/codex/harness.py +0 -0
  115. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  116. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  117. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  118. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  119. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  120. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  121. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  122. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  123. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/node.py +0 -0
  124. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/null/__init__.py +0 -0
  125. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  126. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  127. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  128. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/pi/harness.py +0 -0
  129. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/pool/__init__.py +0 -0
  130. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/pool/harness.py +0 -0
  131. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  132. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  133. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  134. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  135. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  136. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  137. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  138. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  139. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  140. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/harnesses/utils/install.py +0 -0
  141. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/__init__.py +0 -0
  142. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/base.py +0 -0
  143. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/pool.py +0 -0
  144. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/server.py +0 -0
  145. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/tool.py +0 -0
  146. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  147. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/tunnel/base.py +0 -0
  148. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/tunnel/custom.py +0 -0
  149. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/interception/tunnel/prime.py +0 -0
  150. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judge.py +0 -0
  151. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judges/__init__.py +0 -0
  152. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judges/reference.py +0 -0
  153. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judges/reference.txt +0 -0
  154. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judges/rubric.py +0 -0
  155. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/judges/rubric.txt +0 -0
  156. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/mcp/__init__.py +0 -0
  157. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/mcp/launch.py +0 -0
  158. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/mcp/server.py +0 -0
  159. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/mcp/toolset.py +0 -0
  160. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/rollout.py +0 -0
  161. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/__init__.py +0 -0
  162. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/base.py +0 -0
  163. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  164. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/docker/egress.py +0 -0
  165. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/limiters.py +0 -0
  166. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/modal.py +0 -0
  167. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/prime.py +0 -0
  168. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/runtimes/subprocess.py +0 -0
  169. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/semantic.py +0 -0
  170. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/__init__.py +0 -0
  171. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/client.py +0 -0
  172. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/encoding.py +0 -0
  173. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/pool.py +0 -0
  174. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/server.py +0 -0
  175. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/serve/types.py +0 -0
  176. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/session.py +0 -0
  177. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/state.py +0 -0
  178. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/task.py +0 -0
  179. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/taskset.py +0 -0
  180. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/__init__.py +0 -0
  181. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  182. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/harbor/env.py +0 -0
  183. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  184. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/lean/__init__.py +0 -0
  185. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/lean/scoring.py +0 -0
  186. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/lean/taskset.py +0 -0
  187. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  188. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  189. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  190. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  191. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  192. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  193. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  194. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  195. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  196. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/trace.py +0 -0
  197. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/types.py +0 -0
  198. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/__init__.py +0 -0
  199. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/aio.py +0 -0
  200. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/artifacts.py +0 -0
  201. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/compile.py +0 -0
  202. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/decorators.py +0 -0
  203. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/format.py +0 -0
  204. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/generic.py +0 -0
  205. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/git.py +0 -0
  206. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/image.py +0 -0
  207. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/interrupt.py +0 -0
  208. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/loaders.py +0 -0
  209. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/logging.py +0 -0
  210. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/memory.py +0 -0
  211. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/paths.py +0 -0
  212. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/platform.py +0 -0
  213. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/prime.py +0 -0
  214. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/retries.py +0 -0
  215. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/verifiers/v1/utils/score.py +0 -0
  216. {verifiers-0.3.2.dev65 → verifiers-0.3.2.dev66}/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.dev65
3
+ Version: 0.3.2.dev66
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
@@ -8,7 +8,6 @@ from verifiers.v1.configs.harness import HarnessConfig
8
8
  from verifiers.v1.harness import Harness
9
9
  from verifiers.v1.harnesses.utils.launch import (
10
10
  CHAT_PROGRAM_SOURCE,
11
- MCP_CHAT_PROGRAM_SOURCE,
12
11
  launch_chat_program,
13
12
  )
14
13
  from verifiers.v1.runtimes import ProgramResult, Runtime
@@ -122,7 +121,6 @@ class BashHarness(Harness[BashHarnessConfig]):
122
121
  mcp_urls,
123
122
  system_prompt,
124
123
  prompt,
125
- source_with_mcp=MCP_CHAT_PROGRAM_SOURCE,
126
124
  extra_args=args,
127
125
  env=env,
128
126
  activate=False,
@@ -27,6 +27,7 @@ import subprocess
27
27
  import sys
28
28
  import time
29
29
  import urllib.request
30
+ from contextlib import AsyncExitStack
30
31
  from pathlib import Path
31
32
  from typing import TYPE_CHECKING, Any, cast
32
33
  from urllib.parse import urlsplit
@@ -226,63 +227,60 @@ async def main() -> None:
226
227
  config = json.loads(args.mcp_config or "{}")
227
228
  tools = [BROWSER_TOOL]
228
229
  reserved = {"browser"}
229
- mcp_tools, dispatch, servers = (
230
- await connect_mcp(config, reserved)
231
- if config.get("mcpServers")
232
- else ([], {}, {})
233
- )
234
- tools += mcp_tools
235
- messages = (
236
- [{"role": "system", "content": args.system_prompt}]
237
- if args.system_prompt
238
- else []
239
- )
240
- if initial:
241
- messages.extend(initial)
242
- elif args.prompt:
243
- messages.append({"role": "user", "content": args.prompt})
244
- while True:
245
- message = await chat(client, args.model, messages, tools)
246
- messages.append(message.model_dump(exclude_none=True))
247
- if not message.tool_calls:
248
- break
249
- for call in message.tool_calls:
250
- name = call.function.name
251
- try:
252
- tool_args = json.loads(call.function.arguments or "{}")
253
- except json.JSONDecodeError as e:
254
- messages.append(
255
- {
256
- "role": "tool",
257
- "tool_call_id": call.id,
258
- "content": f"error: invalid JSON in tool arguments ({e}); resend the call with valid JSON",
259
- }
260
- )
261
- continue
262
- # Valid JSON can still be a non-object (`[]`, `42`, `null`); the `.get(...)` calls
263
- # below assume a dict, so reject anything else as a tool error rather than crashing.
264
- if not isinstance(tool_args, dict):
230
+ async with AsyncExitStack() as mcp_stack:
231
+ mcp_tools, dispatch, servers = await connect_mcp(config, mcp_stack, reserved)
232
+ tools += mcp_tools
233
+ messages = (
234
+ [{"role": "system", "content": args.system_prompt}]
235
+ if args.system_prompt
236
+ else []
237
+ )
238
+ if initial:
239
+ messages.extend(initial)
240
+ elif args.prompt:
241
+ messages.append({"role": "user", "content": args.prompt})
242
+ while True:
243
+ message = await chat(client, args.model, messages, tools)
244
+ messages.append(message.model_dump(exclude_none=True))
245
+ if not message.tool_calls:
246
+ break
247
+ for call in message.tool_calls:
248
+ name = call.function.name
249
+ try:
250
+ tool_args = json.loads(call.function.arguments or "{}")
251
+ except json.JSONDecodeError as e:
252
+ messages.append(
253
+ {
254
+ "role": "tool",
255
+ "tool_call_id": call.id,
256
+ "content": f"error: invalid JSON in tool arguments ({e}); resend the call with valid JSON",
257
+ }
258
+ )
259
+ continue
260
+ # Valid JSON can still be a non-object (`[]`, `42`, `null`); the `.get(...)` calls
261
+ # below assume a dict, so reject anything else as a tool error rather than crashing.
262
+ if not isinstance(tool_args, dict):
263
+ messages.append(
264
+ {
265
+ "role": "tool",
266
+ "tool_call_id": call.id,
267
+ "content": f"error: tool arguments must be a JSON object, got {type(tool_args).__name__}; resend as an object",
268
+ }
269
+ )
270
+ continue
271
+ if name in dispatch:
272
+ content = await call_mcp(servers, dispatch, name, tool_args)
273
+ elif name == "browser":
274
+ content = await asyncio.to_thread(
275
+ run_browser,
276
+ tool_args.get("code", ""),
277
+ browser_env,
278
+ )
279
+ else:
280
+ content = f"error: unknown tool {name!r}"
265
281
  messages.append(
266
- {
267
- "role": "tool",
268
- "tool_call_id": call.id,
269
- "content": f"error: tool arguments must be a JSON object, got {type(tool_args).__name__}; resend as an object",
270
- }
271
- )
272
- continue
273
- if name in dispatch:
274
- content = await call_mcp(servers, dispatch, name, tool_args)
275
- elif name == "browser":
276
- content = await asyncio.to_thread(
277
- run_browser,
278
- tool_args.get("code", ""),
279
- browser_env,
282
+ {"role": "tool", "tool_call_id": call.id, "content": content}
280
283
  )
281
- else:
282
- content = f"error: unknown tool {name!r}"
283
- messages.append(
284
- {"role": "tool", "tool_call_id": call.id, "content": content}
285
- )
286
284
 
287
285
 
288
286
  if __name__ == "__main__":
@@ -3,7 +3,6 @@ from verifiers.v1.configs.harness import HarnessConfig
3
3
  from verifiers.v1.harness import Harness
4
4
  from verifiers.v1.harnesses.utils.launch import (
5
5
  CHAT_PROGRAM_SOURCE,
6
- MCP_CHAT_PROGRAM_SOURCE,
7
6
  launch_chat_program,
8
7
  )
9
8
  from verifiers.v1.runtimes import ProgramResult, Runtime
@@ -47,5 +46,4 @@ class NullHarness(Harness[NullHarnessConfig]):
47
46
  mcp_urls,
48
47
  system_prompt,
49
48
  prompt,
50
- source_with_mcp=MCP_CHAT_PROGRAM_SOURCE,
51
49
  )
@@ -4,6 +4,7 @@ import argparse
4
4
  import asyncio
5
5
  import json
6
6
  import subprocess
7
+ from contextlib import AsyncExitStack
7
8
  from pathlib import Path
8
9
  from typing import TYPE_CHECKING
9
10
 
@@ -361,35 +362,37 @@ async def main() -> None:
361
362
  if args.search:
362
363
  tools.append(SEARCH_TOOL)
363
364
  reserved.add("search")
364
- if config.get("mcpServers"):
365
- mcp_tools, dispatch, servers = await asyncio.wait_for(
366
- connect_mcp(config, reserved), timeout=None if args.bash else 60
365
+ async with AsyncExitStack() as mcp_stack:
366
+ if config.get("mcpServers"):
367
+ mcp_tools, dispatch, servers = await asyncio.wait_for(
368
+ connect_mcp(config, mcp_stack, reserved),
369
+ timeout=None if args.bash else 60,
370
+ )
371
+ else:
372
+ mcp_tools, dispatch, servers = [], {}, {}
373
+ tools += mcp_tools
374
+ messages = (
375
+ [{"role": "system", "content": args.system_prompt}]
376
+ if args.system_prompt
377
+ else []
367
378
  )
368
- else:
369
- mcp_tools, dispatch, servers = [], {}, {}
370
- tools += mcp_tools
371
- messages = (
372
- [{"role": "system", "content": args.system_prompt}]
373
- if args.system_prompt
374
- else []
375
- )
376
- if initial:
377
- messages.extend(initial)
378
- elif args.prompt:
379
- messages.append({"role": "user", "content": args.prompt})
380
- compactor = Compactor(
381
- client,
382
- args.model,
383
- tools,
384
- args.compaction,
385
- args.summarize_at_tokens,
386
- )
387
- if compactor.enabled and compactor.threshold is None:
388
- compactor.threshold = await discover_threshold(client, args.model)
389
- # The initial conversation is the floor for checkpoint fallbacks: a first-turn
390
- # checkpoint must never retry from an empty base.
391
- compactor.note_good(messages)
392
- await run_chat_loop(args, compactor, messages, dispatch, servers, tool_client)
379
+ if initial:
380
+ messages.extend(initial)
381
+ elif args.prompt:
382
+ messages.append({"role": "user", "content": args.prompt})
383
+ compactor = Compactor(
384
+ client,
385
+ args.model,
386
+ tools,
387
+ args.compaction,
388
+ args.summarize_at_tokens,
389
+ )
390
+ if compactor.enabled and compactor.threshold is None:
391
+ compactor.threshold = await discover_threshold(client, args.model)
392
+ # The initial conversation is the floor for checkpoint fallbacks: a first-turn
393
+ # checkpoint must never retry from an empty base.
394
+ compactor.note_good(messages)
395
+ await run_chat_loop(args, compactor, messages, dispatch, servers, tool_client)
393
396
  if tool_client is not None:
394
397
  await tool_client.aclose()
395
398
 
@@ -29,8 +29,7 @@ CHAT_PROGRAM = (
29
29
  '# dependencies = ["openai", "mcp==2.0.0", "httpx", "httpx2", "tenacity"]\n'
30
30
  "# ///\n"
31
31
  )
32
- CHAT_PROGRAM_SOURCE = bundle_program(CHAT_PROGRAM, compaction, core)
33
- MCP_CHAT_PROGRAM_SOURCE = bundle_program(CHAT_PROGRAM, mcp, compaction, core)
32
+ CHAT_PROGRAM_SOURCE = bundle_program(CHAT_PROGRAM, mcp, compaction, core)
34
33
 
35
34
 
36
35
  async def launch_chat_program(
@@ -45,7 +44,6 @@ async def launch_chat_program(
45
44
  system_prompt: str | None,
46
45
  prompt: str | Messages | None,
47
46
  *,
48
- source_with_mcp: str | None = None,
49
47
  extra_args: Sequence[str] = (),
50
48
  env: dict[str, str] | None = None,
51
49
  activate: bool = True,
@@ -60,7 +58,6 @@ async def launch_chat_program(
60
58
  if system_prompt:
61
59
  args.append(f"--system-prompt={system_prompt}")
62
60
  if mcp_urls:
63
- source = source_with_mcp or source
64
61
  args.append(
65
62
  "--mcp-config="
66
63
  + json.dumps(
@@ -0,0 +1,201 @@
1
+ import asyncio
2
+ from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
3
+ from contextlib import AsyncExitStack, asynccontextmanager, suppress
4
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
5
+
6
+ from anyio import CancelScope
7
+
8
+ if TYPE_CHECKING:
9
+ from mcp import Client
10
+
11
+ MCP_CALL_ATTEMPTS = 6
12
+ MCP_TIMEOUT = 600.0
13
+
14
+ T = TypeVar("T")
15
+
16
+
17
+ @asynccontextmanager
18
+ async def mcp_client(spec: dict[str, Any]) -> AsyncIterator["Client"]:
19
+ """Open one fresh MCP client in the caller's task.
20
+
21
+ The client negotiates the newest protocol and falls back for older servers.
22
+ Teardown failures after the body completes are suppressed so closing noise cannot
23
+ fail or replay a call whose result is already available.
24
+ """
25
+ # Bundled chat programs also run without tools; load MCP only when it is used.
26
+ import httpx2
27
+ from mcp import Client
28
+ from mcp.client.streamable_http import (
29
+ create_mcp_http_client,
30
+ streamable_http_client,
31
+ )
32
+
33
+ stack = AsyncExitStack()
34
+ try:
35
+ http_client = await stack.enter_async_context(
36
+ create_mcp_http_client(
37
+ headers=spec.get("headers") or None,
38
+ timeout=httpx2.Timeout(
39
+ spec.get("timeout", MCP_TIMEOUT),
40
+ connect=spec.get("connect_timeout", 5.0),
41
+ ),
42
+ )
43
+ )
44
+ transport = streamable_http_client(spec["url"], http_client=http_client)
45
+ yield await stack.enter_async_context(Client(transport))
46
+ finally:
47
+ with suppress(Exception):
48
+ await stack.aclose()
49
+
50
+
51
+ class MCPConnection:
52
+ """One rollout-owned client, with operations serialized across reconnects.
53
+
54
+ The SDK's AnyIO contexts enter and exit in the owner task, even when discovery
55
+ runs under wait_for or a tool call is cancelled by another task.
56
+ """
57
+
58
+ def __init__(self, spec: dict[str, Any]):
59
+ # Bundled programs without MCP should not import the retry machinery.
60
+ from tenacity import AsyncRetrying, stop_after_attempt, wait_exponential_jitter
61
+
62
+ self.spec = spec
63
+ self.task: asyncio.Task[None] | None = None
64
+ self.ready: asyncio.Future[Client] | None = None
65
+ self.cancel_scope: CancelScope | None = None
66
+ self.lock = asyncio.Lock()
67
+ self.run = AsyncRetrying(
68
+ stop=stop_after_attempt(MCP_CALL_ATTEMPTS),
69
+ wait=wait_exponential_jitter(initial=0.5, max=30),
70
+ reraise=True,
71
+ ).wraps(self._run)
72
+
73
+ async def serve(
74
+ self, ready: asyncio.Future["Client"], cancel_scope: CancelScope
75
+ ) -> None:
76
+ stack = AsyncExitStack()
77
+ try:
78
+ stack.enter_context(cancel_scope)
79
+ client = await stack.enter_async_context(mcp_client(self.spec))
80
+ ready.set_result(client)
81
+ await asyncio.Future()
82
+ except asyncio.CancelledError:
83
+ pass
84
+ except Exception as error: # noqa: BLE001 - deliver owner failures to the caller
85
+ if not ready.done():
86
+ ready.set_exception(error)
87
+ finally:
88
+ if not ready.done():
89
+ ready.cancel()
90
+ # Owner teardown must not replace the caller's error with cancellation.
91
+ with suppress(asyncio.CancelledError):
92
+ await stack.aclose()
93
+
94
+ async def _run(self, operation: Callable[["Client"], Awaitable[T]]) -> T:
95
+ await self.lock.acquire()
96
+ try:
97
+ if self.task is None or self.task.done():
98
+ self.ready = asyncio.get_running_loop().create_future()
99
+ self.cancel_scope = CancelScope()
100
+ self.task = asyncio.create_task(
101
+ self.serve(self.ready, self.cancel_scope)
102
+ )
103
+ assert self.ready is not None
104
+ client = await self.ready
105
+ return await operation(client)
106
+ except BaseException as error:
107
+ await self.aclose(abort=isinstance(error, asyncio.CancelledError))
108
+ raise
109
+ finally:
110
+ self.lock.release()
111
+
112
+ async def aclose(self, *, abort: bool = False) -> None:
113
+ if self.task is None:
114
+ return
115
+ task, self.task = self.task, None
116
+ cancel_scope = self.cancel_scope
117
+ assert cancel_scope is not None
118
+ caller = asyncio.current_task()
119
+ assert caller is not None
120
+ # Stack cleanup can follow cancellation while awaiting a model or local tool.
121
+ if abort or caller.cancelling():
122
+ cancel_scope.cancel()
123
+ else:
124
+ task.cancel()
125
+ try:
126
+ await asyncio.shield(task)
127
+ except asyncio.CancelledError:
128
+ cancel_scope.cancel()
129
+ await asyncio.shield(task)
130
+ raise
131
+
132
+
133
+ async def connect_mcp(
134
+ config: dict[str, Any], stack: AsyncExitStack, reserved: set[str] | None = None
135
+ ) -> tuple[
136
+ list[dict[str, Any]],
137
+ dict[str, tuple[str, str]],
138
+ dict[str, MCPConnection],
139
+ ]:
140
+ """Enumerate MCP tools and return their schemas, dispatch map, and servers."""
141
+ tool_schemas: list[dict[str, Any]] = []
142
+ dispatch: dict[str, tuple[str, str]] = {}
143
+ servers: dict[str, MCPConnection] = {}
144
+ reserved = reserved or set()
145
+ for name, spec in config.get("mcpServers", {}).items():
146
+ server = servers[name] = MCPConnection(spec)
147
+ stack.push_async_callback(server.aclose)
148
+ result = await server.run(lambda client: client.list_tools())
149
+ for tool in result.tools:
150
+ full = f"{name}_{tool.name}" if name else tool.name
151
+ if full in reserved or full in dispatch:
152
+ raise ValueError(
153
+ f"duplicate tool name {full!r}; keep MCP tool names qualified"
154
+ )
155
+ tool_schemas.append(
156
+ {
157
+ "type": "function",
158
+ "function": {
159
+ "name": full,
160
+ "description": tool.description or "",
161
+ "parameters": tool.input_schema,
162
+ },
163
+ }
164
+ )
165
+ dispatch[full] = (name, tool.name)
166
+ return tool_schemas, dispatch, servers
167
+
168
+
169
+ def mcp_content_to_chat_content(
170
+ blocks: Sequence[Any],
171
+ ) -> str | list[dict[str, Any]]:
172
+ """Convert MCP content blocks to OpenAI chat tool-result content."""
173
+ parts = []
174
+ for block in blocks:
175
+ if block.type == "text":
176
+ parts.append({"type": "text", "text": block.text})
177
+ elif block.type == "image":
178
+ url = f"data:{block.mime_type};base64,{block.data}"
179
+ parts.append({"type": "image_url", "image_url": {"url": url}})
180
+ else:
181
+ parts.append({"type": "text", "text": str(block)})
182
+ if not parts:
183
+ return str(blocks)
184
+ if all(part["type"] == "text" for part in parts):
185
+ return "\n".join(cast(str, part["text"]) for part in parts)
186
+ return parts
187
+
188
+
189
+ async def call_mcp(
190
+ servers: dict[str, MCPConnection],
191
+ dispatch: dict[str, tuple[str, str]],
192
+ name: str,
193
+ arguments: dict[str, Any],
194
+ ) -> str | list[dict[str, Any]]:
195
+ """Reuse the rollout's client, reconnecting before retrying a failed call."""
196
+ server_name, raw = dispatch[name]
197
+
198
+ result = await servers[server_name].run(
199
+ lambda client: client.call_tool(raw, arguments)
200
+ )
201
+ return mcp_content_to_chat_content(result.content)
@@ -1,127 +0,0 @@
1
- from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
2
- from contextlib import AsyncExitStack, asynccontextmanager, suppress
3
- from typing import Any, TypeVar, cast
4
-
5
- import httpx2
6
- from mcp import Client
7
- from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client
8
- from tenacity import AsyncRetrying, stop_after_attempt, wait_exponential_jitter
9
-
10
- MCP_CALL_ATTEMPTS = 6
11
- MCP_TIMEOUT = 600.0
12
-
13
- T = TypeVar("T")
14
-
15
-
16
- @asynccontextmanager
17
- async def mcp_client(spec: dict[str, Any]) -> AsyncIterator[Client]:
18
- """Open one fresh MCP client in the caller's task.
19
-
20
- The client negotiates the newest protocol and falls back for older servers.
21
- Teardown failures after the body completes are suppressed so closing noise cannot
22
- fail or replay a call whose result is already available.
23
- """
24
- stack = AsyncExitStack()
25
- try:
26
- http_client = await stack.enter_async_context(
27
- create_mcp_http_client(
28
- headers=spec.get("headers") or None,
29
- timeout=httpx2.Timeout(
30
- spec.get("timeout", MCP_TIMEOUT),
31
- connect=spec.get("connect_timeout", 5.0),
32
- ),
33
- )
34
- )
35
- transport = streamable_http_client(spec["url"], http_client=http_client)
36
- yield await stack.enter_async_context(Client(transport))
37
- finally:
38
- with suppress(Exception):
39
- await stack.aclose()
40
-
41
-
42
- async def with_retry(call: Callable[[], Awaitable[T]]) -> T:
43
- """Run one client operation with the existing at-least-once retries."""
44
- async for attempt in AsyncRetrying(
45
- stop=stop_after_attempt(MCP_CALL_ATTEMPTS),
46
- wait=wait_exponential_jitter(initial=0.5, max=30),
47
- reraise=True,
48
- ):
49
- with attempt:
50
- return await call()
51
- raise RuntimeError("retrying stopped without returning or raising")
52
-
53
-
54
- async def connect_mcp(
55
- config: dict[str, Any], reserved: set[str] | None = None
56
- ) -> tuple[
57
- list[dict[str, Any]],
58
- dict[str, tuple[str, str]],
59
- dict[str, dict[str, Any]],
60
- ]:
61
- """Enumerate MCP tools and return their schemas, dispatch map, and servers."""
62
- tool_schemas: list[dict[str, Any]] = []
63
- dispatch: dict[str, tuple[str, str]] = {}
64
- servers: dict[str, dict[str, Any]] = {}
65
- reserved = reserved or set()
66
- for name, spec in config.get("mcpServers", {}).items():
67
- servers[name] = spec
68
-
69
- async def list_tools(spec: dict[str, Any] = spec):
70
- async with mcp_client(spec) as client:
71
- return (await client.list_tools()).tools
72
-
73
- for tool in await with_retry(list_tools):
74
- full = f"{name}_{tool.name}" if name else tool.name
75
- if full in reserved or full in dispatch:
76
- raise ValueError(
77
- f"duplicate tool name {full!r}; keep MCP tool names qualified"
78
- )
79
- tool_schemas.append(
80
- {
81
- "type": "function",
82
- "function": {
83
- "name": full,
84
- "description": tool.description or "",
85
- "parameters": tool.input_schema,
86
- },
87
- }
88
- )
89
- dispatch[full] = (name, tool.name)
90
- return tool_schemas, dispatch, servers
91
-
92
-
93
- def mcp_content_to_chat_content(
94
- blocks: Sequence[Any],
95
- ) -> str | list[dict[str, Any]]:
96
- """Convert MCP content blocks to OpenAI chat tool-result content."""
97
- parts = []
98
- for block in blocks:
99
- if block.type == "text":
100
- parts.append({"type": "text", "text": block.text})
101
- elif block.type == "image":
102
- url = f"data:{block.mime_type};base64,{block.data}"
103
- parts.append({"type": "image_url", "image_url": {"url": url}})
104
- else:
105
- parts.append({"type": "text", "text": str(block)})
106
- if not parts:
107
- return str(blocks)
108
- if all(part["type"] == "text" for part in parts):
109
- return "\n".join(cast(str, part["text"]) for part in parts)
110
- return parts
111
-
112
-
113
- async def call_mcp(
114
- servers: dict[str, dict[str, Any]],
115
- dispatch: dict[str, tuple[str, str]],
116
- name: str,
117
- arguments: dict[str, Any],
118
- ) -> str | list[dict[str, Any]]:
119
- """Call one MCP tool with a fresh client per retry attempt."""
120
- server_name, raw = dispatch[name]
121
-
122
- async def call():
123
- async with mcp_client(servers[server_name]) as client:
124
- return await client.call_tool(raw, arguments)
125
-
126
- result = await with_retry(call)
127
- return mcp_content_to_chat_content(result.content)
File without changes