verifiers 0.3.2.dev126__tar.gz → 0.3.2.dev127__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 (221) hide show
  1. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/PKG-INFO +1 -1
  2. verifiers-0.3.2.dev127/verifiers/v1/configs/runtime.py +126 -0
  3. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/dialects/anthropic.py +72 -145
  4. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/dialects/base.py +121 -69
  5. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/dialects/chat.py +49 -54
  6. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/dialects/responses.py +80 -163
  7. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/modal.py +3 -6
  8. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/task.py +2 -2
  9. verifiers-0.3.2.dev126/verifiers/v1/configs/runtime.py +0 -72
  10. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/.gitignore +0 -0
  11. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/LICENSE +0 -0
  12. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/README.md +0 -0
  13. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/pyproject.toml +0 -0
  14. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/conftest.py +0 -0
  15. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/counter_tool_v1.py +0 -0
  16. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/duet_v1.py +0 -0
  17. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
  18. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
  19. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/echo_tool_v1.py +0 -0
  20. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
  21. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/echo_v1.py +0 -0
  22. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/pyproject.toml +0 -0
  23. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
  24. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_color_codeword_tasks.py +0 -0
  25. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_configs.py +0 -0
  26. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_e2e.py +0 -0
  27. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_envs.py +0 -0
  28. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_graph.py +0 -0
  29. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_judges.py +0 -0
  30. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_platform_run.py +0 -0
  31. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_scoring.py +0 -0
  32. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_serve_delta.py +0 -0
  33. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_taskset.py +0 -0
  34. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/tests/v1/test_trace.py +0 -0
  35. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/__init__.py +0 -0
  36. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/__init__.py +0 -0
  37. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/acp/__init__.py +0 -0
  38. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/acp/runner.py +0 -0
  39. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/agent.py +0 -0
  40. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/__init__.py +0 -0
  41. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  42. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/dashboard/base.py +0 -0
  43. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/dashboard/eval.py +0 -0
  44. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/dashboard/replay.py +0 -0
  45. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/dashboard/validate.py +0 -0
  46. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/debug.py +0 -0
  47. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/eval/__init__.py +0 -0
  48. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/eval/hint.py +0 -0
  49. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/eval/main.py +0 -0
  50. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/eval/resume.py +0 -0
  51. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/eval/runner.py +0 -0
  52. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/gepa.py +0 -0
  53. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/init.py +0 -0
  54. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/output.py +0 -0
  55. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/replay.py +0 -0
  56. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/resolve.py +0 -0
  57. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/resume.py +0 -0
  58. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/cli/validate.py +0 -0
  59. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/clients/__init__.py +0 -0
  60. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/clients/base.py +0 -0
  61. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/clients/client.py +0 -0
  62. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/clients/eval.py +0 -0
  63. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/clients/train.py +0 -0
  64. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/__init__.py +0 -0
  65. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/agent.py +0 -0
  66. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/__init__.py +0 -0
  67. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/debug.py +0 -0
  68. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/env.py +0 -0
  69. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/eval.py +0 -0
  70. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/init.py +0 -0
  71. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/replay.py +0 -0
  72. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/cli/validate.py +0 -0
  73. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/client.py +0 -0
  74. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/env.py +0 -0
  75. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/harness.py +0 -0
  76. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/judge.py +0 -0
  77. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/retries.py +0 -0
  78. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/serve.py +0 -0
  79. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/task.py +0 -0
  80. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/configs/taskset.py +0 -0
  81. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/dialects/__init__.py +0 -0
  82. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/env.py +0 -0
  83. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/__init__.py +0 -0
  84. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  85. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  86. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  87. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/best_of_n/env.py +0 -0
  88. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
  89. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
  90. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
  91. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  92. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/single_agent/env.py +0 -0
  93. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  94. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/envs/user_sim/env.py +0 -0
  95. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/episode.py +0 -0
  96. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/errors.py +0 -0
  97. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/__init__.py +0 -0
  98. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/adapter.py +0 -0
  99. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/config.py +0 -0
  100. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/dataset.py +0 -0
  101. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/reflection.py +0 -0
  102. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/gepa/runner.py +0 -0
  103. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/graph.py +0 -0
  104. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harness.py +0 -0
  105. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/__init__.py +0 -0
  106. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  107. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/bash/harness.py +0 -0
  108. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  109. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  110. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  111. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  112. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  113. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  114. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
  115. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/codex/harness.py +0 -0
  116. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  117. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  118. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  119. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  120. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  121. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  122. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  123. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  124. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/node.py +0 -0
  125. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/null/__init__.py +0 -0
  126. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/null/harness.py +0 -0
  127. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  128. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  129. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  130. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
  131. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/pi/harness.py +0 -0
  132. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  133. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  134. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  135. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  136. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  137. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  138. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  139. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  140. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  141. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/core.py +0 -0
  142. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/install.py +0 -0
  143. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/launch.py +0 -0
  144. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  145. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/__init__.py +0 -0
  146. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/base.py +0 -0
  147. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/pool.py +0 -0
  148. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/server.py +0 -0
  149. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  150. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/tunnel/base.py +0 -0
  151. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/tunnel/custom.py +0 -0
  152. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/interception/tunnel/prime.py +0 -0
  153. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judge.py +0 -0
  154. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judges/__init__.py +0 -0
  155. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judges/reference.py +0 -0
  156. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judges/reference.txt +0 -0
  157. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judges/rubric.py +0 -0
  158. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/judges/rubric.txt +0 -0
  159. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/mcp/__init__.py +0 -0
  160. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/mcp/launch.py +0 -0
  161. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/mcp/server.py +0 -0
  162. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/mcp/toolset.py +0 -0
  163. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/rollout.py +0 -0
  164. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/__init__.py +0 -0
  165. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/apptainer.py +0 -0
  166. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/base.py +0 -0
  167. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/container.py +0 -0
  168. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  169. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/docker/egress.py +0 -0
  170. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/limiters.py +0 -0
  171. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/prime.py +0 -0
  172. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/runtimes/subprocess.py +0 -0
  173. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/semantic.py +0 -0
  174. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/__init__.py +0 -0
  175. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/client.py +0 -0
  176. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/delta.py +0 -0
  177. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/encoding.py +0 -0
  178. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/pool.py +0 -0
  179. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/server.py +0 -0
  180. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/serve/types.py +0 -0
  181. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/session.py +0 -0
  182. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/state.py +0 -0
  183. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/taskset.py +0 -0
  184. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/__init__.py +0 -0
  185. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  186. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/harbor/env.py +0 -0
  187. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  188. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  189. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/lean/__init__.py +0 -0
  190. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/lean/scoring.py +0 -0
  191. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/lean/taskset.py +0 -0
  192. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  193. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  194. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  195. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  196. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  197. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  198. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  199. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  200. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  201. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/trace.py +0 -0
  202. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/types.py +0 -0
  203. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/__init__.py +0 -0
  204. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/aio.py +0 -0
  205. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/artifacts.py +0 -0
  206. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/compile.py +0 -0
  207. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/decorators.py +0 -0
  208. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/format.py +0 -0
  209. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/generic.py +0 -0
  210. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/git.py +0 -0
  211. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/image.py +0 -0
  212. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/interrupt.py +0 -0
  213. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/loaders.py +0 -0
  214. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/logging.py +0 -0
  215. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/memory.py +0 -0
  216. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/paths.py +0 -0
  217. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/platform.py +0 -0
  218. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/prime.py +0 -0
  219. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/retries.py +0 -0
  220. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/verifiers/v1/utils/score.py +0 -0
  221. {verifiers-0.3.2.dev126 → verifiers-0.3.2.dev127}/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.dev126
3
+ Version: 0.3.2.dev127
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
@@ -0,0 +1,126 @@
1
+ """Shared configuration for execution-time network policy."""
2
+
3
+ from fnmatch import fnmatchcase
4
+ from glob import has_magic
5
+ from itertools import product
6
+ from typing import Self
7
+ from urllib.parse import SplitResult, urlsplit
8
+
9
+ from pydantic import Field, model_validator
10
+ from pydantic_config import BaseConfig
11
+
12
+
13
+ def parse_network_rule(rule: str) -> tuple[SplitResult, str, int | None]:
14
+ """Normalize host/port while retaining URL syntax for backend validation."""
15
+ parsed = urlsplit(rule if "://" in rule else f"//{rule}")
16
+ port = parsed.port
17
+ if parsed.scheme and port is None:
18
+ port = 443 if parsed.scheme == "https" else 80
19
+ return parsed, (parsed.hostname or "").lower().rstrip("."), port
20
+
21
+
22
+ def network_rule_matches(rule: str, scheme: str, host: str, port: int) -> bool:
23
+ """Match a network-policy host pattern or URL origin. Paths are ignored."""
24
+ try:
25
+ parsed, pattern, rule_port = parse_network_rule(rule)
26
+ except ValueError:
27
+ return False
28
+ if not pattern or (parsed.scheme and parsed.scheme != scheme):
29
+ return False
30
+ if rule_port is not None and rule_port != port:
31
+ return False
32
+ host = host.lower().rstrip(".")
33
+ return fnmatchcase(host, pattern) or (
34
+ pattern.startswith("*.") and host == pattern[2:]
35
+ )
36
+
37
+
38
+ def intersect_network_hosts(left: str, right: str) -> str:
39
+ """Return the narrower host pattern, rejecting unprovable glob overlaps."""
40
+ for parent, child in ((left, right), (right, left)):
41
+ if parent in ("*", child):
42
+ return child
43
+ # Matching a pattern as text proves containment only for domain suffixes.
44
+ if has_magic(child) and not (
45
+ parent.startswith("*.") and not has_magic(parent[2:])
46
+ ):
47
+ continue
48
+ if fnmatchcase(child, parent) or child == parent.removeprefix("*."):
49
+ return child
50
+ if all(has_magic(host) for host in (left, right)) and any(
51
+ has_magic(host.removeprefix("*.")) for host in (left, right)
52
+ ):
53
+ raise ValueError(f"cannot intersect network hosts {left!r} and {right!r}")
54
+ return ""
55
+
56
+
57
+ class NetworkPolicyConfig(BaseConfig):
58
+ """Shared execution-time policy surface for runtimes that support it."""
59
+
60
+ allow: list[str] = Field(default_factory=lambda: ["*"])
61
+ """Destinations allowed during execution; `*` is unrestricted and `[]` is
62
+ framework-only."""
63
+ block: list[str] = Field(default_factory=list)
64
+ """Destinations denied during execution; any `*` makes the policy framework-only."""
65
+
66
+ @model_validator(mode="after")
67
+ def validate_network_policy(self) -> Self:
68
+ if not self.allow or "*" in self.block:
69
+ # Empty allowlists and wildcard blocks both mean framework-only access.
70
+ self.allow = []
71
+ self.block = ["*"]
72
+ elif self.allow != ["*"] and self.block:
73
+ raise ValueError(
74
+ "non-empty concrete allow and block egress lists are mutually exclusive"
75
+ )
76
+ return self
77
+
78
+ @property
79
+ def network_restricted(self) -> bool:
80
+ return "*" not in self.allow or bool(self.block)
81
+
82
+ def permits(self, scheme: str, host: str, port: int) -> bool:
83
+ """Whether the destination is allowed by the configured egress rules."""
84
+ return not any(
85
+ network_rule_matches(rule, scheme, host, port) for rule in self.block
86
+ ) and any(network_rule_matches(rule, scheme, host, port) for rule in self.allow)
87
+
88
+ def with_task_network_policy(self, allow: list[str], block: list[str]) -> Self:
89
+ values = self.model_dump()
90
+ # Intersection must not erase syntax the runtime cannot enforce.
91
+ task = type(self).model_validate({**values, "allow": allow, "block": block})
92
+ allow, block = task.allow, task.block
93
+ if "*" in allow:
94
+ allow = self.allow
95
+ elif "*" not in self.allow:
96
+ intersection = []
97
+ for left, right in product(allow, self.allow):
98
+ if left == right:
99
+ intersection.append(left)
100
+ continue
101
+ a, a_host, a_port = parse_network_rule(left)
102
+ b, b_host, b_port = parse_network_rule(right)
103
+ # Bare paths can be provider-specific CIDRs, not URL paths to discard.
104
+ if any(
105
+ not url.hostname or (url.path and not url.scheme) for url in (a, b)
106
+ ):
107
+ raise ValueError(
108
+ f"cannot intersect network rules {left!r} and {right!r}"
109
+ )
110
+ if (
111
+ len({a.scheme, b.scheme} - {""}) > 1
112
+ or len({a_port, b_port} - {None}) > 1
113
+ ):
114
+ continue
115
+ host = intersect_network_hosts(a_host, b_host)
116
+ if not host:
117
+ continue
118
+ scheme = a.scheme or b.scheme
119
+ port = a_port if a_port is not None else b_port
120
+ authority = f"[{host}]" if ":" in host else host
121
+ if port is not None:
122
+ authority = f"{authority}:{port}"
123
+ intersection.append(f"{scheme}://{authority}" if scheme else authority)
124
+ allow = list(dict.fromkeys(intersection))
125
+ block = list(dict.fromkeys([*block, *self.block]))
126
+ return type(self).model_validate({**values, "allow": allow, "block": block})
@@ -18,9 +18,9 @@ from verifiers.v1.configs.runtime import NetworkPolicyConfig
18
18
  from verifiers.v1.dialects.base import (
19
19
  Dialect,
20
20
  RawRequest,
21
+ RequestFilter,
21
22
  StreamParser,
22
23
  append_user_notice,
23
- blocked_url,
24
24
  parse_sse_event,
25
25
  provider_domains,
26
26
  )
@@ -129,89 +129,66 @@ def parse_content(content) -> str | list[ContentPart]:
129
129
  return parts
130
130
 
131
131
 
132
- def blocked_content_path(
133
- value, path: str, policy: NetworkPolicyConfig, blocked_urls: list[str]
134
- ) -> str | None:
135
- if isinstance(value, list):
136
- for index, item in enumerate(value):
137
- if blocked := blocked_content_path(
138
- item, f"{path}[{index}]", policy, blocked_urls
139
- ):
140
- return blocked
141
- return None
142
- if not isinstance(value, dict):
143
- return None
132
+ class AnthropicRequestFilter(RequestFilter):
133
+ wrappers = _CONTENT_WRAPPERS
144
134
 
145
- kind = value.get("type")
146
- caller = value.get("caller")
147
- if caller is not None and not (
148
- isinstance(caller, dict) and caller.get("type") == "direct"
149
- ):
150
- return f"{path}.caller.type"
151
- if kind in ("image", "document"):
152
- source_path = f"{path}.source"
153
- source = value.get("source") or {}
154
- if not isinstance(source, dict):
155
- return source_path
156
- source_kind = source.get("type")
157
- if source_kind == "content":
158
- return blocked_content_path(
159
- source.get("content"), f"{source_path}.content", policy, blocked_urls
135
+ def tool(self, tool, path: str) -> dict | None:
136
+ kind = tool.get("type") if isinstance(tool, dict) else None
137
+ if isinstance(kind, str) and _WEB_TOOL_TYPE(kind):
138
+ callers = tool.get("allowed_callers")
139
+ filter_key = "blocked_domains" if self.policy.block else "allowed_domains"
140
+ other_key = "allowed_domains" if self.policy.block else "blocked_domains"
141
+ domains = (
142
+ provider_domains(self.policy, tool.get(filter_key))
143
+ # Anthropic does not support combining allow and block filters.
144
+ if tool.get(other_key) in (None, [])
145
+ and (
146
+ callers is None or isinstance(callers, list) and "direct" in callers
147
+ )
148
+ else []
160
149
  )
161
- if source_kind == "url":
162
- url = source.get("url")
163
- if not isinstance(url, str) or blocked_url(url, policy, blocked_urls):
150
+ if domains:
151
+ web_tool = {**tool, filter_key: domains, "allowed_callers": ["direct"]}
152
+ web_tool.pop(other_key, None)
153
+ return web_tool
154
+ if isinstance(tool, dict) and (
155
+ (isinstance(kind, str) and _CLIENT_TOOL_TYPE(kind))
156
+ or (kind in (None, "custom") and "input_schema" in tool)
157
+ ):
158
+ return tool
159
+ self.capabilities.append(f"{path}.type")
160
+ return None
161
+
162
+ def blocked_part(self, value: dict, path: str) -> str | None:
163
+ kind = value.get("type")
164
+ if kind in ("image", "document"):
165
+ source_path = f"{path}.source"
166
+ source = value.get("source") or {}
167
+ if not isinstance(source, dict):
168
+ return source_path
169
+ source_kind = source.get("type")
170
+ if source_kind == "content":
171
+ return self.blocked(source.get("content"), f"{source_path}.content")
172
+ if source_kind == "url" and self.blocked_url(source.get("url")):
164
173
  return f"{source_path}.url"
165
- if source_kind == "file":
166
- return (
167
- f"{source_path}.file_id"
168
- if source.get("file_id")
169
- else f"{source_path}.type"
170
- )
171
- if source_kind not in ("base64", "text", "url"):
172
- return f"{source_path}.type"
173
-
174
- if kind in (
175
- "container_upload",
176
- "code_execution_output",
177
- "bash_code_execution_output",
178
- ) and value.get("file_id"):
179
- return f"{path}.file_id"
180
- if kind in _CONTENT_WRAPPERS:
181
- return blocked_content_path(
182
- value.get("content"), f"{path}.content", policy, blocked_urls
183
- )
184
- return None if kind in _SAFE_CONTENT_TYPES else f"{path}.type"
185
-
186
-
187
- def mediate_content(
188
- value, path: str, policy: NetworkPolicyConfig, blocked_urls: list[str]
189
- ):
190
- if not isinstance(value, list):
191
- blocked = blocked_content_path(value, path, policy, blocked_urls)
192
- return ("", [blocked]) if blocked else (value, [])
193
-
194
- mediated = []
195
- capabilities = []
196
- for index, block in enumerate(value):
197
- item_path = f"{path}[{index}]"
198
- if isinstance(block, dict) and block.get("type") in _CONTENT_WRAPPERS:
199
- if blocked := blocked_content_path(
200
- {**block, "content": []}, item_path, policy, blocked_urls
201
- ):
202
- capabilities.append(blocked)
203
- continue
204
- content, removed = mediate_content(
205
- block.get("content"), f"{item_path}.content", policy, blocked_urls
206
- )
207
- if removed:
208
- block["content"] = content or ""
209
- capabilities.extend(removed)
210
- elif blocked := blocked_content_path(block, item_path, policy, blocked_urls):
211
- capabilities.append(blocked)
212
- continue
213
- mediated.append(block)
214
- return mediated, capabilities
174
+ if source_kind == "file":
175
+ return (
176
+ f"{source_path}.file_id"
177
+ if source.get("file_id")
178
+ else f"{source_path}.type"
179
+ )
180
+ if source_kind not in ("base64", "text", "url"):
181
+ return f"{source_path}.type"
182
+
183
+ if kind in (
184
+ "container_upload",
185
+ "code_execution_output",
186
+ "bash_code_execution_output",
187
+ ) and value.get("file_id"):
188
+ return f"{path}.file_id"
189
+ if kind in self.wrappers:
190
+ return self.blocked(value.get("content"), f"{path}.content")
191
+ return None if kind in _SAFE_CONTENT_TYPES else f"{path}.type"
215
192
 
216
193
 
217
194
  def content_to_wire(content) -> str | list[dict]:
@@ -464,80 +441,29 @@ class AnthropicDialect(Dialect[AnthropicMessage]):
464
441
  self, body: RawRequest, policy: NetworkPolicyConfig
465
442
  ) -> tuple[RawRequest, list[str]]:
466
443
  mediated = body
467
- capabilities: list[str] = []
468
- blocked_urls: list[str] = []
444
+ request_filter = AnthropicRequestFilter(policy)
445
+ capabilities = request_filter.capabilities
469
446
 
470
447
  for key in ("container", "mcp_servers"):
471
448
  if mediated.pop(key, None):
472
449
  capabilities.append(key)
473
450
 
474
- system, removed = mediate_content(
475
- mediated.get("system"), "system", policy, blocked_urls
476
- )
477
- capabilities.extend(removed)
478
- if removed:
479
- if system:
480
- mediated["system"] = system
481
- else:
482
- mediated.pop("system")
451
+ if (
452
+ request_filter.content(mediated, "system", "system")
453
+ and not mediated["system"]
454
+ ):
455
+ mediated.pop("system")
483
456
 
484
457
  for message_index, message in enumerate(mediated.get("messages") or []):
485
458
  if not isinstance(message, dict):
486
459
  continue
487
- content, removed = mediate_content(
488
- message.get("content"),
460
+ request_filter.content(
461
+ message,
462
+ "content",
489
463
  f"messages[{message_index}].content",
490
- policy,
491
- blocked_urls,
492
464
  )
493
- capabilities.extend(removed)
494
- if removed:
495
- message["content"] = content or ""
496
-
497
- raw_tools = mediated.get("tools")
498
- tool_items = raw_tools if isinstance(raw_tools, list) else []
499
- if raw_tools is not None and not isinstance(raw_tools, list):
500
- capabilities.append("tools")
501
- tools = []
502
- for index, tool in enumerate(tool_items):
503
- kind = tool.get("type") if isinstance(tool, dict) else None
504
- if (
505
- isinstance(tool, dict)
506
- and isinstance(kind, str)
507
- and _WEB_TOOL_TYPE(kind)
508
- ):
509
- callers = tool.get("allowed_callers")
510
- filter_key = "blocked_domains" if policy.block else "allowed_domains"
511
- other_key = "allowed_domains" if policy.block else "blocked_domains"
512
- domains = (
513
- provider_domains(policy, tool.get(filter_key))
514
- # Anthropic does not support combining allow and block filters.
515
- if tool.get(other_key) in (None, [])
516
- and (
517
- callers is None
518
- or isinstance(callers, list)
519
- and "direct" in callers
520
- )
521
- else []
522
- )
523
- if domains:
524
- web_tool = {
525
- **tool,
526
- filter_key: domains,
527
- "allowed_callers": ["direct"],
528
- }
529
- web_tool.pop(other_key, None)
530
- tools.append(web_tool)
531
- continue
532
- capabilities.append(f"tools[{index}].type")
533
- continue
534
- if isinstance(tool, dict) and (
535
- (isinstance(kind, str) and _CLIENT_TOOL_TYPE(kind))
536
- or (kind in (None, "custom") and "input_schema" in tool)
537
- ):
538
- tools.append(tool)
539
- continue
540
- capabilities.append(f"tools[{index}].type")
465
+
466
+ tools = request_filter.tools(mediated.get("tools"))
541
467
  if "tools" in mediated:
542
468
  mediated["tools"] = tools
543
469
 
@@ -562,7 +488,8 @@ class AnthropicDialect(Dialect[AnthropicMessage]):
562
488
 
563
489
  if capabilities:
564
490
  append_user_notice(
565
- mediated.setdefault("messages", []), blocked_urls=blocked_urls
491
+ mediated.setdefault("messages", []),
492
+ blocked_urls=request_filter.blocked_urls,
566
493
  )
567
494
  return mediated, capabilities
568
495
 
@@ -15,13 +15,17 @@ import json
15
15
  import logging
16
16
  from abc import ABC, abstractmethod
17
17
  from collections.abc import Callable, Mapping
18
+ from glob import has_magic
18
19
  from typing import Any, ClassVar, Generic, TypeVar
19
- from urllib.parse import urlsplit
20
20
 
21
21
  from pydantic import AnyHttpUrl, BaseModel, ValidationError
22
22
  from pydantic_core import from_json
23
23
 
24
- from verifiers.v1.configs.runtime import NetworkPolicyConfig
24
+ from verifiers.v1.configs.runtime import (
25
+ NetworkPolicyConfig,
26
+ intersect_network_hosts,
27
+ parse_network_rule,
28
+ )
25
29
  from verifiers.v1.types import Request, Response, Sampling, SamplingConfig
26
30
 
27
31
  RespT = TypeVar("RespT", bound=BaseModel)
@@ -36,21 +40,90 @@ CAPABILITY_NOTICE = (
36
40
  )
37
41
 
38
42
 
39
- def blocked_url(
40
- value: str, policy: NetworkPolicyConfig, blocked_urls: list[str] | None = None
41
- ) -> bool:
42
- """Whether a provider-resolved resource is neither inline nor policy-permitted."""
43
- if value.lower().startswith("data:"):
44
- return False
45
- try:
46
- url = AnyHttpUrl(value)
47
- except ValidationError:
48
- return True
49
- host = url.host.lower().rstrip(".").strip("[]")
50
- blocked = not policy.permits(url.scheme, host, url.port)
51
- if blocked and blocked_urls is not None:
52
- blocked_urls.append(value)
53
- return blocked
43
+ class RequestFilter:
44
+ """One request's omissions and blocked URLs; subclasses define native wire rules."""
45
+
46
+ wrappers: tuple[str, ...] = ()
47
+
48
+ def __init__(self, policy: NetworkPolicyConfig):
49
+ self.policy = policy
50
+ self.blocked_urls: list[str] = []
51
+ self.capabilities: list[str] = []
52
+
53
+ def blocked_url(self, value: object) -> bool:
54
+ if not isinstance(value, str):
55
+ return True
56
+ if value.lower().startswith("data:"):
57
+ return False
58
+ try:
59
+ url = AnyHttpUrl(value)
60
+ except ValidationError:
61
+ return True
62
+ blocked = not self.policy.permits(url.scheme, url.host.strip("[]"), url.port)
63
+ if blocked:
64
+ self.blocked_urls.append(value)
65
+ return blocked
66
+
67
+ def blocked(self, value, path: str) -> str | None:
68
+ """Find the first forbidden part, treating nested content as one unit."""
69
+ if isinstance(value, list):
70
+ for index, item in enumerate(value):
71
+ if blocked := self.blocked(item, f"{path}[{index}]"):
72
+ return blocked
73
+ return None
74
+ if not isinstance(value, dict):
75
+ return None
76
+ caller = value.get("caller")
77
+ if caller is not None and not (
78
+ isinstance(caller, dict) and caller.get("type") == "direct"
79
+ ):
80
+ return f"{path}.caller.type"
81
+ return self.blocked_part(value, path)
82
+
83
+ def blocked_part(self, value: dict, path: str) -> str | None:
84
+ raise NotImplementedError
85
+
86
+ def mediate(self, value, path: str):
87
+ """Remove forbidden parts while retaining supported wrapper blocks."""
88
+ if not isinstance(value, list):
89
+ if blocked := self.blocked(value, path):
90
+ self.capabilities.append(blocked)
91
+ return ""
92
+ return value
93
+ mediated = []
94
+ for index, block in enumerate(value):
95
+ item_path = f"{path}[{index}]"
96
+ wrapper = isinstance(block, dict) and block.get("type") in self.wrappers
97
+ scan = {**block, "content": []} if wrapper else block
98
+ if blocked := self.blocked(scan, item_path):
99
+ self.capabilities.append(blocked)
100
+ continue
101
+ if wrapper:
102
+ self.content(block, "content", f"{item_path}.content")
103
+ mediated.append(block)
104
+ return mediated
105
+
106
+ def content(self, parent: dict, key: str, path: str) -> bool:
107
+ """Rewrite a content field only when filtering removes something."""
108
+ before = len(self.capabilities)
109
+ content = self.mediate(parent.get(key), path)
110
+ changed = len(self.capabilities) != before
111
+ if changed:
112
+ parent[key] = content or ""
113
+ return changed
114
+
115
+ def tools(self, value, path: str = "tools") -> list[dict]:
116
+ if value is not None and not isinstance(value, list):
117
+ self.capabilities.append(path)
118
+ return []
119
+ tools = []
120
+ for index, tool in enumerate(value or []):
121
+ if (filtered := self.tool(tool, f"{path}[{index}]")) is not None:
122
+ tools.append(filtered)
123
+ return tools
124
+
125
+ def tool(self, value, path: str) -> dict | None:
126
+ raise NotImplementedError
54
127
 
55
128
 
56
129
  def provider_domains(
@@ -62,75 +135,54 @@ def provider_domains(
62
135
  Empty results mean the policy cannot be represented; never send an empty filter.
63
136
  """
64
137
  rules = policy.block or policy.allow
65
- if not rules or "*" in rules:
138
+ if requested is not None and not isinstance(requested, list):
66
139
  return []
67
- domains = []
68
- hosts = []
69
- for rule in rules:
70
- try:
71
- url = urlsplit(rule if "://" in rule else f"//{rule}")
72
- port = url.port
73
- except ValueError:
74
- return []
75
- host = (url.hostname or "").lower().rstrip(".")
76
- domain = host.removeprefix("*.")
77
- if (
78
- url.scheme
79
- or port is not None
80
- or not domain
81
- or any(char in domain for char in "*?[]")
82
- or not domain.isascii()
83
- ):
84
- return []
85
- hosts.append(host)
86
- domains.append(domain)
140
+ hosts, requested_domains = [], []
141
+ for entries, output, is_filter in (
142
+ (rules, hosts, False),
143
+ (requested or [], requested_domains, True),
144
+ ):
145
+ for rule in entries:
146
+ if not isinstance(rule, str):
147
+ return []
148
+ try:
149
+ url, host, port = parse_network_rule(rule)
150
+ except ValueError:
151
+ return []
152
+ if is_filter and (
153
+ url.username is not None or url.path or url.query or url.fragment
154
+ ):
155
+ return []
156
+ domain = host if is_filter else host.removeprefix("*.")
157
+ if (
158
+ url.scheme
159
+ or port is not None
160
+ or not domain
161
+ or has_magic(domain)
162
+ or not domain.isascii()
163
+ ):
164
+ return []
165
+ output.append(host)
87
166
  for host in hosts:
88
167
  if not host.startswith("*.") and not (
89
168
  policy.block
90
169
  and any(
91
170
  wildcard.startswith("*.")
92
- and (host == wildcard[2:] or host.endswith(wildcard[1:]))
171
+ and intersect_network_hosts(wildcard, host) == host
93
172
  for wildcard in hosts
94
173
  )
95
174
  ):
96
175
  return []
97
- domains = list(dict.fromkeys(domains))
176
+ domains = list(dict.fromkeys(host.removeprefix("*.") for host in hosts))
98
177
  if requested is None:
99
178
  return domains
100
- if not isinstance(requested, list):
101
- return []
102
- requested_domains = []
103
- for domain in requested:
104
- if not isinstance(domain, str):
105
- return []
106
- try:
107
- url = urlsplit(domain if "://" in domain else f"//{domain}")
108
- port = url.port
109
- except ValueError:
110
- return []
111
- host = (url.hostname or "").lower().rstrip(".")
112
- if (
113
- url.scheme
114
- or port is not None
115
- or url.username is not None
116
- or url.path
117
- or url.query
118
- or url.fragment
119
- or not host
120
- or any(char in host for char in "*?[]")
121
- or not host.isascii()
122
- ):
123
- return []
124
- requested_domains.append(host)
125
179
  if policy.block:
126
180
  return list(dict.fromkeys([*domains, *requested_domains]))
127
181
  intersection = []
128
182
  for allowed in domains:
129
183
  for requested_domain in requested_domains:
130
- if allowed == requested_domain or allowed.endswith(f".{requested_domain}"):
131
- intersection.append(allowed)
132
- elif requested_domain.endswith(f".{allowed}"):
133
- intersection.append(requested_domain)
184
+ if host := intersect_network_hosts(f"*.{allowed}", f"*.{requested_domain}"):
185
+ intersection.append(host.removeprefix("*."))
134
186
  return list(dict.fromkeys(intersection))
135
187
 
136
188