agentevolve-optimizer 0.5.0__py3-none-any.whl

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 (414) hide show
  1. agent_evolve/__init__.py +722 -0
  2. agent_evolve/agentic.py +2800 -0
  3. agent_evolve/api.py +767 -0
  4. agent_evolve/application/__init__.py +1580 -0
  5. agent_evolve/application/action_allocation.py +744 -0
  6. agent_evolve/application/action_allocation_frame.py +347 -0
  7. agent_evolve/application/action_allocation_frame_commit.py +185 -0
  8. agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
  9. agent_evolve/application/action_allocation_frame_v3.py +338 -0
  10. agent_evolve/application/action_archive_value.py +497 -0
  11. agent_evolve/application/action_evidence_consistency.py +455 -0
  12. agent_evolve/application/action_forecast_partitioning.py +1471 -0
  13. agent_evolve/application/action_metric_projection.py +211 -0
  14. agent_evolve/application/action_role_value.py +680 -0
  15. agent_evolve/application/action_score_authorities.py +363 -0
  16. agent_evolve/application/action_structural_signature.py +116 -0
  17. agent_evolve/application/action_target_realization.py +402 -0
  18. agent_evolve/application/agentic_evolution.py +7734 -0
  19. agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
  20. agent_evolve/application/anchor_residual_identification.py +463 -0
  21. agent_evolve/application/archive_conditioned_action_target.py +208 -0
  22. agent_evolve/application/artifact_journal.py +246 -0
  23. agent_evolve/application/artifact_replay.py +347 -0
  24. agent_evolve/application/budgeted_optimizer.py +1828 -0
  25. agent_evolve/application/calibrated_campaign.py +485 -0
  26. agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
  27. agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
  28. agent_evolve/application/campaign_capacity_recourse.py +254 -0
  29. agent_evolve/application/campaign_contextual_outcomes.py +119 -0
  30. agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
  31. agent_evolve/application/campaign_evidence_registry.py +262 -0
  32. agent_evolve/application/campaign_execution.py +2537 -0
  33. agent_evolve/application/campaign_generation_audit.py +942 -0
  34. agent_evolve/application/campaign_learning.py +1812 -0
  35. agent_evolve/application/campaign_learning_runtime.py +1977 -0
  36. agent_evolve/application/campaign_search_phase.py +227 -0
  37. agent_evolve/application/campaign_selector_context_extension.py +220 -0
  38. agent_evolve/application/campaign_variation_envelope.py +649 -0
  39. agent_evolve/application/campaign_variation_trace.py +451 -0
  40. agent_evolve/application/candidate_archive_consequence.py +128 -0
  41. agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
  42. agent_evolve/application/composite_outcome_updater.py +145 -0
  43. agent_evolve/application/composition_portfolio_selection.py +363 -0
  44. agent_evolve/application/concurrent_stage.py +144 -0
  45. agent_evolve/application/contextual_action_allocation.py +181 -0
  46. agent_evolve/application/contextual_campaign_outcomes.py +267 -0
  47. agent_evolve/application/contextual_campaign_planning.py +1366 -0
  48. agent_evolve/application/contextual_delayed_credit.py +651 -0
  49. agent_evolve/application/contextual_search_controller.py +2374 -0
  50. agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
  51. agent_evolve/application/decision_metric_projection.py +112 -0
  52. agent_evolve/application/derived_action_semantics.py +129 -0
  53. agent_evolve/application/detailed_evaluation.py +449 -0
  54. agent_evolve/application/earned_lineage.py +1011 -0
  55. agent_evolve/application/effective_choice_audit.py +484 -0
  56. agent_evolve/application/empirical_consequence_calibration.py +908 -0
  57. agent_evolve/application/evaluation_accounting.py +325 -0
  58. agent_evolve/application/evaluation_cache.py +199 -0
  59. agent_evolve/application/evaluation_escrow.py +547 -0
  60. agent_evolve/application/evaluation_recourse.py +253 -0
  61. agent_evolve/application/event_recorder.py +151 -0
  62. agent_evolve/application/evolution_campaign.py +1840 -0
  63. agent_evolve/application/executable_hypothesis.py +323 -0
  64. agent_evolve/application/factorial_branch_pilot.py +772 -0
  65. agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
  66. agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
  67. agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
  68. agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
  69. agent_evolve/application/finite_action_selection.py +188 -0
  70. agent_evolve/application/finite_action_set.py +306 -0
  71. agent_evolve/application/finite_action_transition.py +537 -0
  72. agent_evolve/application/finite_variation_eligibility.py +296 -0
  73. agent_evolve/application/forecast_geometry_portfolio.py +799 -0
  74. agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
  75. agent_evolve/application/front_proximity_admission.py +311 -0
  76. agent_evolve/application/front_proximity_parent_basis.py +458 -0
  77. agent_evolve/application/frozen_hurdle_score.py +659 -0
  78. agent_evolve/application/g3_causal_screen.py +2257 -0
  79. agent_evolve/application/g3_causal_validation.py +1046 -0
  80. agent_evolve/application/g3_postseal_curation.py +818 -0
  81. agent_evolve/application/gated_agentic_generator.py +205 -0
  82. agent_evolve/application/generation_feedback.py +293 -0
  83. agent_evolve/application/generative_proposal_journal.py +185 -0
  84. agent_evolve/application/geometry_conditional_elasticity.py +453 -0
  85. agent_evolve/application/global_wave_action_allocation.py +1151 -0
  86. agent_evolve/application/head_mass_conditional_seat.py +268 -0
  87. agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
  88. agent_evolve/application/identifiable_reflection_learning.py +395 -0
  89. agent_evolve/application/identifiable_reflection_request.py +364 -0
  90. agent_evolve/application/in_memory_residual_archive.py +341 -0
  91. agent_evolve/application/insight_memory.py +1804 -0
  92. agent_evolve/application/live_runtime_manifest.py +758 -0
  93. agent_evolve/application/llm_task_queue.py +769 -0
  94. agent_evolve/application/matched_finite_action_block.py +409 -0
  95. agent_evolve/application/materialized_action_broker.py +2328 -0
  96. agent_evolve/application/materialized_action_constraints.py +83 -0
  97. agent_evolve/application/materialized_variation.py +211 -0
  98. agent_evolve/application/multi_option_evolution.py +1536 -0
  99. agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
  100. agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
  101. agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
  102. agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
  103. agent_evolve/application/outcome_relation.py +193 -0
  104. agent_evolve/application/paired_allocation_comparison.py +241 -0
  105. agent_evolve/application/paired_block_schedule.py +127 -0
  106. agent_evolve/application/parent_measurement.py +226 -0
  107. agent_evolve/application/pareto_archive.py +811 -0
  108. agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
  109. agent_evolve/application/portfolio_evolution.py +2950 -0
  110. agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
  111. agent_evolve/application/portfolio_memory_attribution.py +581 -0
  112. agent_evolve/application/portfolio_memory_dose.py +788 -0
  113. agent_evolve/application/portfolio_memory_matched_control.py +938 -0
  114. agent_evolve/application/portfolio_memory_transfer.py +297 -0
  115. agent_evolve/application/portfolio_optimization_memory.py +363 -0
  116. agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
  117. agent_evolve/application/portfolio_projection.py +335 -0
  118. agent_evolve/application/portfolio_recombination.py +2032 -0
  119. agent_evolve/application/post_evolution_reflection.py +834 -0
  120. agent_evolve/application/postcommit_rank_authority.py +245 -0
  121. agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
  122. agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
  123. agent_evolve/application/prequential_residual_exploration.py +343 -0
  124. agent_evolve/application/prequential_score_portfolio.py +954 -0
  125. agent_evolve/application/projections.py +292 -0
  126. agent_evolve/application/protected_action_committee.py +1027 -0
  127. agent_evolve/application/protected_branch_pilot.py +376 -0
  128. agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
  129. agent_evolve/application/provider_replay.py +910 -0
  130. agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
  131. agent_evolve/application/recombination_residual_expert.py +403 -0
  132. agent_evolve/application/reflection_workflow.py +571 -0
  133. agent_evolve/application/region_conditional_credit.py +911 -0
  134. agent_evolve/application/residual_campaign_runtime.py +531 -0
  135. agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
  136. agent_evolve/application/residual_headroom_ledger.py +1544 -0
  137. agent_evolve/application/residual_learning_transaction.py +396 -0
  138. agent_evolve/application/residual_portfolio_evolution.py +1228 -0
  139. agent_evolve/application/residual_reachability.py +749 -0
  140. agent_evolve/application/residual_stage_credit.py +499 -0
  141. agent_evolve/application/same_prefix_paired_audit.py +1580 -0
  142. agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
  143. agent_evolve/application/sequential_lineage_allocation.py +1017 -0
  144. agent_evolve/application/sequential_market_replay.py +1395 -0
  145. agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
  146. agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
  147. agent_evolve/application/single_score_action_allocation.py +299 -0
  148. agent_evolve/application/source_exposure_allocation.py +906 -0
  149. agent_evolve/application/staged_memory.py +210 -0
  150. agent_evolve/application/stratified_cold_start_allocation.py +732 -0
  151. agent_evolve/application/support_guarded_hurdle_score.py +549 -0
  152. agent_evolve/application/target_conditioned_action_forecast.py +595 -0
  153. agent_evolve/application/target_conditioned_campaign.py +566 -0
  154. agent_evolve/application/treatment_assignment.py +201 -0
  155. agent_evolve/application/trusted_objective_evidence.py +217 -0
  156. agent_evolve/application/two_stage_action_evolution.py +1131 -0
  157. agent_evolve/application/v8lite_allocation_policy.py +1083 -0
  158. agent_evolve/application/v9_candidate_policy.py +1303 -0
  159. agent_evolve/bootstrap.py +108 -0
  160. agent_evolve/campaign_presets.py +517 -0
  161. agent_evolve/campaign_profiles.py +452 -0
  162. agent_evolve/campaign_variation_topology.py +288 -0
  163. agent_evolve/campaign_workload.py +950 -0
  164. agent_evolve/cli.py +797 -0
  165. agent_evolve/contract.py +241 -0
  166. agent_evolve/core/__init__.py +91 -0
  167. agent_evolve/core/action_semantics.py +411 -0
  168. agent_evolve/core/authored.py +105 -0
  169. agent_evolve/core/formatting.py +286 -0
  170. agent_evolve/core/optimization_semantics.py +324 -0
  171. agent_evolve/core/problem.py +167 -0
  172. agent_evolve/core/results.py +323 -0
  173. agent_evolve/core/stats.py +70 -0
  174. agent_evolve/core/telemetry.py +100 -0
  175. agent_evolve/domain/__init__.py +89 -0
  176. agent_evolve/domain/artifact.py +162 -0
  177. agent_evolve/domain/durable_text.py +68 -0
  178. agent_evolve/domain/event.py +1454 -0
  179. agent_evolve/domain/finite_action_set.py +426 -0
  180. agent_evolve/domain/finite_variation.py +526 -0
  181. agent_evolve/domain/generative_emission.py +559 -0
  182. agent_evolve/domain/ids.py +163 -0
  183. agent_evolve/domain/inline_text.py +106 -0
  184. agent_evolve/domain/insight.py +27 -0
  185. agent_evolve/domain/lineage.py +737 -0
  186. agent_evolve/domain/llm_task_queue.py +960 -0
  187. agent_evolve/domain/outcome.py +96 -0
  188. agent_evolve/domain/patch.py +854 -0
  189. agent_evolve/domain/typed_json.py +542 -0
  190. agent_evolve/domain/variation_space.py +158 -0
  191. agent_evolve/driver.py +1014 -0
  192. agent_evolve/harness/__init__.py +29 -0
  193. agent_evolve/harness/base.py +242 -0
  194. agent_evolve/harness/directives.py +163 -0
  195. agent_evolve/harness/generative_seal.py +479 -0
  196. agent_evolve/harness/registry.py +41 -0
  197. agent_evolve/infrastructure/__init__.py +39 -0
  198. agent_evolve/infrastructure/artifacts/__init__.py +6 -0
  199. agent_evolve/infrastructure/artifacts/_verification.py +67 -0
  200. agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
  201. agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
  202. agent_evolve/infrastructure/asyncio_runtime.py +109 -0
  203. agent_evolve/infrastructure/authored_runtime.py +188 -0
  204. agent_evolve/infrastructure/authored_worker.py +171 -0
  205. agent_evolve/infrastructure/clock.py +53 -0
  206. agent_evolve/infrastructure/events/__init__.py +6 -0
  207. agent_evolve/infrastructure/events/_validation.py +89 -0
  208. agent_evolve/infrastructure/events/in_memory.py +56 -0
  209. agent_evolve/infrastructure/events/jsonl.py +193 -0
  210. agent_evolve/infrastructure/exception_provenance.py +215 -0
  211. agent_evolve/infrastructure/ids.py +118 -0
  212. agent_evolve/infrastructure/lineage_codec.py +1836 -0
  213. agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
  214. agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
  215. agent_evolve/infrastructure/resource_lease.py +370 -0
  216. agent_evolve/infrastructure/sanitization/__init__.py +8 -0
  217. agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
  218. agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
  219. agent_evolve/infrastructure/stream_liveness.py +383 -0
  220. agent_evolve/infrastructure/subprocess_boundary.py +136 -0
  221. agent_evolve/integrations/__init__.py +1 -0
  222. agent_evolve/integrations/botorch/__init__.py +28 -0
  223. agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
  224. agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
  225. agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
  226. agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
  227. agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
  228. agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
  229. agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
  230. agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
  231. agent_evolve/integrations/completion.py +242 -0
  232. agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
  233. agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
  234. agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
  235. agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
  236. agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
  237. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
  238. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
  239. agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
  240. agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
  241. agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
  242. agent_evolve/integrations/pydantic_ai/harness.py +159 -0
  243. agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
  244. agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
  245. agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
  246. agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
  247. agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
  248. agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
  249. agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
  250. agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
  251. agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
  252. agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
  253. agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
  254. agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
  255. agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
  256. agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
  257. agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
  258. agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
  259. agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
  260. agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
  261. agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
  262. agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
  263. agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
  264. agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
  265. agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
  266. agent_evolve/integrations/pymoo_adapter.py +242 -0
  267. agent_evolve/policies/__init__.py +17 -0
  268. agent_evolve/policies/check.py +469 -0
  269. agent_evolve/policies/emit_scaffold.py +451 -0
  270. agent_evolve/policies/feedback/__init__.py +37 -0
  271. agent_evolve/policies/feedback/held_out_asn.py +1325 -0
  272. agent_evolve/policies/genetic.py +607 -0
  273. agent_evolve/policies/llm_backoff.py +183 -0
  274. agent_evolve/policies/llm_chooser.py +226 -0
  275. agent_evolve/policies/llm_generator.py +1760 -0
  276. agent_evolve/policies/llm_init.py +267 -0
  277. agent_evolve/policies/llm_operator.py +109 -0
  278. agent_evolve/policies/llm_prior.py +194 -0
  279. agent_evolve/policies/llm_surrogate.py +334 -0
  280. agent_evolve/policies/measurement_evidence.py +704 -0
  281. agent_evolve/policies/memory/__init__.py +223 -0
  282. agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
  283. agent_evolve/policies/memory/compatibility_matching.py +593 -0
  284. agent_evolve/policies/memory/global_falsification.py +1841 -0
  285. agent_evolve/policies/memory/prompt_shape.py +503 -0
  286. agent_evolve/policies/memory/randomized_subset.py +714 -0
  287. agent_evolve/policies/memory/staged_causal.py +1270 -0
  288. agent_evolve/policies/memory/treatment_compliance.py +759 -0
  289. agent_evolve/policies/objective_resolution/__init__.py +17 -0
  290. agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
  291. agent_evolve/policies/operator_portfolio.py +407 -0
  292. agent_evolve/policies/reguidance.py +1133 -0
  293. agent_evolve/policies/reward/__init__.py +83 -0
  294. agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
  295. agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
  296. agent_evolve/policies/reward/affine_hypervolume.py +490 -0
  297. agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
  298. agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
  299. agent_evolve/policies/reward/frozen_archive.py +360 -0
  300. agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
  301. agent_evolve/policies/search_state.py +208 -0
  302. agent_evolve/policies/selection/__init__.py +345 -0
  303. agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
  304. agent_evolve/policies/selection/affine_frontier_context.py +330 -0
  305. agent_evolve/policies/selection/affine_frontier_target.py +473 -0
  306. agent_evolve/policies/selection/archive_elite.py +1346 -0
  307. agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
  308. agent_evolve/policies/selection/calibrated_slate.py +1394 -0
  309. agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
  310. agent_evolve/policies/selection/common_candidate_pool.py +685 -0
  311. agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
  312. agent_evolve/policies/selection/disjoint_pairs.py +479 -0
  313. agent_evolve/policies/selection/elite_explorer.py +719 -0
  314. agent_evolve/policies/selection/finite_action.py +187 -0
  315. agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
  316. agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
  317. agent_evolve/policies/selection/forecast_calibration.py +922 -0
  318. agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
  319. agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
  320. agent_evolve/policies/selection/full_support_slate.py +91 -0
  321. agent_evolve/policies/selection/meaningful_direction.py +240 -0
  322. agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
  323. agent_evolve/policies/selection/model_anchored_slate.py +826 -0
  324. agent_evolve/policies/selection/phenotype_recourse.py +979 -0
  325. agent_evolve/policies/selection/proposal_support.py +368 -0
  326. agent_evolve/policies/selection/random_portfolio.py +254 -0
  327. agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
  328. agent_evolve/policies/selection/residual_frontier.py +463 -0
  329. agent_evolve/policies/selection/residual_frontier_target.py +605 -0
  330. agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
  331. agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
  332. agent_evolve/policies/selection/target_conditioned_features.py +812 -0
  333. agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
  334. agent_evolve/policies/selection/task_keyed_palette.py +906 -0
  335. agent_evolve/policies/semantics.py +147 -0
  336. agent_evolve/policies/structure.py +362 -0
  337. agent_evolve/policies/structured_output_budget.py +62 -0
  338. agent_evolve/policies/surrogate.py +696 -0
  339. agent_evolve/policies/variation/__init__.py +1 -0
  340. agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
  341. agent_evolve/policies/variation/crossover_inheritance.py +575 -0
  342. agent_evolve/policies/variation/disjoint_recombination.py +611 -0
  343. agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
  344. agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
  345. agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
  346. agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
  347. agent_evolve/policies/variation/typed_patch.py +1981 -0
  348. agent_evolve/policies/weighted_prior.py +394 -0
  349. agent_evolve/ports/__init__.py +383 -0
  350. agent_evolve/ports/action_allocation.py +733 -0
  351. agent_evolve/ports/action_allocation_frame.py +1153 -0
  352. agent_evolve/ports/action_allocation_frame_commit.py +294 -0
  353. agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
  354. agent_evolve/ports/action_allocation_frame_v3.py +995 -0
  355. agent_evolve/ports/action_forecast.py +1568 -0
  356. agent_evolve/ports/action_metric_projection.py +165 -0
  357. agent_evolve/ports/agentic_generator.py +1561 -0
  358. agent_evolve/ports/archive_context.py +136 -0
  359. agent_evolve/ports/artifact_sanitizer.py +44 -0
  360. agent_evolve/ports/artifact_store.py +225 -0
  361. agent_evolve/ports/clock.py +13 -0
  362. agent_evolve/ports/contextual_search_allocation.py +827 -0
  363. agent_evolve/ports/decision_metric_projection.py +258 -0
  364. agent_evolve/ports/event_store.py +55 -0
  365. agent_evolve/ports/executable_hypothesis.py +557 -0
  366. agent_evolve/ports/finite_acquisition.py +377 -0
  367. agent_evolve/ports/finite_acquisition_batch.py +296 -0
  368. agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
  369. agent_evolve/ports/finite_acquisition_json.py +247 -0
  370. agent_evolve/ports/finite_acquisition_space.py +168 -0
  371. agent_evolve/ports/finite_action_selection.py +348 -0
  372. agent_evolve/ports/finite_action_set.py +256 -0
  373. agent_evolve/ports/frontier_target.py +396 -0
  374. agent_evolve/ports/generation_failure.py +43 -0
  375. agent_evolve/ports/hard_feasibility.py +233 -0
  376. agent_evolve/ports/id_factory.py +34 -0
  377. agent_evolve/ports/llm_task_queue.py +93 -0
  378. agent_evolve/ports/objective_resolution.py +419 -0
  379. agent_evolve/ports/paired_allocation_comparison.py +401 -0
  380. agent_evolve/ports/paired_block_schedule.py +475 -0
  381. agent_evolve/ports/parent_measurement.py +336 -0
  382. agent_evolve/ports/portfolio_memory_dose.py +643 -0
  383. agent_evolve/ports/portfolio_selection.py +3169 -0
  384. agent_evolve/ports/postcommit_rank_authority.py +467 -0
  385. agent_evolve/ports/presented_action_evidence.py +794 -0
  386. agent_evolve/ports/resource_lease.py +162 -0
  387. agent_evolve/ports/structured_generator.py +734 -0
  388. agent_evolve/ports/structured_output_budget.py +120 -0
  389. agent_evolve/ports/subprocess_boundary.py +138 -0
  390. agent_evolve/ports/treatment_assignment.py +466 -0
  391. agent_evolve/ports/variation_catalog.py +76 -0
  392. agent_evolve/ports/variation_source.py +226 -0
  393. agent_evolve/proposal_mode.py +157 -0
  394. agent_evolve/proposers/__init__.py +10 -0
  395. agent_evolve/proposers/random_proposer.py +188 -0
  396. agent_evolve/provider_accounting.py +163 -0
  397. agent_evolve/py.typed +0 -0
  398. agent_evolve/reference_method.py +1570 -0
  399. agent_evolve/session/__init__.py +11 -0
  400. agent_evolve/session/authorship.py +864 -0
  401. agent_evolve/session/evaluate.py +236 -0
  402. agent_evolve/session/fidelity.py +237 -0
  403. agent_evolve/session/genetic_loop.py +742 -0
  404. agent_evolve/session/loop.py +803 -0
  405. agent_evolve/session/screening.py +671 -0
  406. agent_evolve/settings.py +376 -0
  407. agent_evolve/workload_kit.py +368 -0
  408. agent_evolve/workload_prompt.py +398 -0
  409. agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
  410. agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
  411. agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
  412. agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
  413. agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
  414. agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1170 @@
1
+ """Content-free evidence for exact OpenRouter requests at the HTTP boundary.
2
+
3
+ Logical request evidence is not enough to diagnose provider rejections: both
4
+ Pydantic-AI and the OpenAI SDK transform model settings, messages, and tool
5
+ schemas before HTTP transport. This module observes the fully serialized JSON
6
+ body in an HTTPX request hook, verifies that it still represents the bound
7
+ ``StructuredGenerationRequest``, and synchronously publishes a redacted record
8
+ before the transport is allowed to send any bytes.
9
+
10
+ The record deliberately contains no headers, query text, prompt, tool
11
+ description, JSON schema, or raw body. Exact byte and canonical semantic
12
+ commitments make those omitted values auditable without retaining them.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ import math
20
+ import re
21
+ import threading
22
+ from collections.abc import Callable, Iterator, Mapping
23
+ from contextlib import contextmanager
24
+ from contextvars import ContextVar
25
+ from copy import deepcopy
26
+ from dataclasses import dataclass
27
+ from importlib.metadata import version
28
+ from typing import Any
29
+
30
+ from agent_evolve.domain.ids import LLMCallId, ProviderAttemptId
31
+ from agent_evolve.ports.structured_generator import (
32
+ MAX_OUTPUT_TOKENS,
33
+ MAX_PROMPT_UTF8_BYTES,
34
+ StructuredGenerationRequest,
35
+ )
36
+
37
+
38
+ OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION = 3
39
+ OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID = (
40
+ "openrouter_chat_completions_httpx_pre_transport_v3"
41
+ )
42
+ OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM = (
43
+ "sha256_domain_and_canonical_redacted_projection_v3"
44
+ )
45
+ OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM = (
46
+ "sha256_domain_and_canonical_public_projection_v3"
47
+ )
48
+ OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM = (
49
+ "sha256_domain_and_canonical_transport_contract_v3"
50
+ )
51
+
52
+ _FRAMEWORK_PACKAGES = ("httpx", "openai", "pydantic", "pydantic-ai")
53
+ _TRANSPORT_CONTRACT = {
54
+ "contract_version": 3,
55
+ "hook_phase": "httpx_request_event_before_transport",
56
+ "method": "POST",
57
+ "scheme": "https",
58
+ "host": "openrouter.ai",
59
+ "port": None,
60
+ "path": "/api/v1/chat/completions",
61
+ "query_required_absent": True,
62
+ "openai_sdk_retries": 0,
63
+ "pydantic_ai_agent_retries": 0,
64
+ "pydantic_ai_output_retries": 0,
65
+ "pydantic_ai_request_limit": 1,
66
+ "output_mode": "profile_selected_tool_or_native_json_schema",
67
+ "sink_timing": "synchronous_before_transport_send",
68
+ "raw_sensitive_values_persisted": False,
69
+ }
70
+ _FORBIDDEN_TOP_LEVEL_FIELDS = (
71
+ "extra_body",
72
+ "mode",
73
+ "models",
74
+ "plugins",
75
+ "preset",
76
+ "pro",
77
+ "reasoning_effort",
78
+ "reasoning_mode",
79
+ "transforms",
80
+ "web_search_options",
81
+ )
82
+ _FORBIDDEN_REASONING_FIELDS = (
83
+ "enabled",
84
+ "exclude",
85
+ "mode",
86
+ "pro",
87
+ )
88
+ _BASE_BODY_FIELDS = frozenset(
89
+ {
90
+ "max_completion_tokens",
91
+ "messages",
92
+ "model",
93
+ "provider",
94
+ "stream",
95
+ "usage",
96
+ }
97
+ )
98
+ _STRUCTURED_OUTPUT_MODES = frozenset({"tool", "native_json_schema"})
99
+ _LOWER_SHA256 = re.compile(r"^[0-9a-f]{64}$")
100
+ _OPERATION_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
101
+ _TOOL_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{0,63}$")
102
+ _PROVIDER_SLUG = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
103
+ _MODEL_SLUG = re.compile(
104
+ r"^[a-z0-9][a-z0-9_.-]{0,95}/[A-Za-z0-9][A-Za-z0-9_.:-]{0,191}$"
105
+ )
106
+ _VERSION_TOKEN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$")
107
+ _REASONING_EFFORTS = frozenset(
108
+ {"xhigh", "high", "medium", "low", "minimal", "none"}
109
+ )
110
+ _MAX_WIRE_BODY_BYTES = 8 * 1_048_576
111
+ _MANIFEST_DOMAIN = b"agent-evolve:openrouter-outbound-manifest:v3\x00"
112
+ _PUBLIC_PROJECTION_DOMAIN = (
113
+ b"agent-evolve:openrouter-outbound-public-projection:v3\x00"
114
+ )
115
+ _TRANSPORT_CONTRACT_DOMAIN = (
116
+ b"agent-evolve:openrouter-outbound-transport-contract:v3\x00"
117
+ )
118
+
119
+
120
+ OpenRouterOutboundRequestManifestSink = Callable[[dict[str, object]], None]
121
+
122
+
123
+ class OpenRouterOutboundRequestManifestError(RuntimeError):
124
+ """The exact outbound request could not be safely authenticated/published."""
125
+
126
+
127
+ def _canonical_bytes(value: object) -> bytes:
128
+ return json.dumps(
129
+ value,
130
+ ensure_ascii=True,
131
+ allow_nan=False,
132
+ separators=(",", ":"),
133
+ sort_keys=True,
134
+ ).encode("ascii")
135
+
136
+
137
+ def _sha256(value: bytes) -> str:
138
+ return hashlib.sha256(value).hexdigest()
139
+
140
+
141
+ def _domain_sha256(domain: bytes, value: object) -> str:
142
+ return hashlib.sha256(domain + _canonical_bytes(value)).hexdigest()
143
+
144
+
145
+ def _canonical_json_copy(value: object, *, label: str) -> object:
146
+ try:
147
+ encoded = _canonical_bytes(value)
148
+ return json.loads(encoded)
149
+ except (TypeError, ValueError) as exc:
150
+ raise OpenRouterOutboundRequestManifestError(
151
+ f"{label} is not finite canonical JSON"
152
+ ) from exc
153
+
154
+
155
+ def _reject_duplicate_json_keys(
156
+ pairs: list[tuple[str, object]],
157
+ ) -> dict[str, object]:
158
+ result: dict[str, object] = {}
159
+ for key, value in pairs:
160
+ if key in result:
161
+ raise ValueError("duplicate JSON object key")
162
+ result[key] = value
163
+ return result
164
+
165
+
166
+ def _framework_versions() -> dict[str, str]:
167
+ """Project the closed dependency set; launch qualification binds exact values."""
168
+
169
+ observed = {name: version(name) for name in _FRAMEWORK_PACKAGES}
170
+ if any(
171
+ type(value) is not str or _VERSION_TOKEN.fullmatch(value) is None
172
+ for value in observed.values()
173
+ ):
174
+ raise OpenRouterOutboundRequestManifestError(
175
+ "outbound framework version is outside the closed token grammar"
176
+ )
177
+ return observed
178
+
179
+
180
+ def _closed_provider(value: object) -> dict[str, object]:
181
+ if type(value) is not dict:
182
+ raise ValueError("provider routing must be an exact object")
183
+ fields = frozenset(value)
184
+ if fields not in (
185
+ frozenset({"only", "allow_fallbacks"}),
186
+ frozenset({"only", "allow_fallbacks", "require_parameters"}),
187
+ ):
188
+ raise ValueError("provider routing has unexpected fields")
189
+ only = value["only"]
190
+ if (
191
+ type(only) is not list
192
+ or not 1 <= len(only) <= 16
193
+ or any(
194
+ type(item) is not str or _PROVIDER_SLUG.fullmatch(item) is None
195
+ for item in only
196
+ )
197
+ or len(set(only)) != len(only)
198
+ or value["allow_fallbacks"] is not False
199
+ or (
200
+ "require_parameters" in value
201
+ and value["require_parameters"] is not True
202
+ )
203
+ ):
204
+ raise ValueError("provider routing violates the closed no-fallback policy")
205
+ return dict(value)
206
+
207
+
208
+ def _closed_reasoning(value: object) -> dict[str, object] | None:
209
+ if value is None:
210
+ return None
211
+ if type(value) is not dict or len(value) != 1:
212
+ raise ValueError("reasoning must contain exactly one closed control")
213
+ if frozenset(value) == {"effort"}:
214
+ effort = value["effort"]
215
+ if type(effort) is not str or effort not in _REASONING_EFFORTS:
216
+ raise ValueError("reasoning effort is outside the closed enum")
217
+ elif frozenset(value) == {"max_tokens"}:
218
+ max_tokens = value["max_tokens"]
219
+ if (
220
+ type(max_tokens) is not int
221
+ or not 1 <= max_tokens <= MAX_OUTPUT_TOKENS
222
+ ):
223
+ raise ValueError("reasoning max_tokens is outside the generic bound")
224
+ else:
225
+ raise ValueError("reasoning contains an unsupported control")
226
+ return dict(value)
227
+
228
+
229
+ def _transport_contract_sha256() -> str:
230
+ return _domain_sha256(_TRANSPORT_CONTRACT_DOMAIN, _TRANSPORT_CONTRACT)
231
+
232
+
233
+ def _wire_output_schema(
234
+ output_type: type[Any],
235
+ *,
236
+ json_schema_transformer: type[Any] | None = None,
237
+ strict: bool = False,
238
+ ) -> tuple[int, str, int, str]:
239
+ """Return logical and pinned OpenAI-wire schema commitments.
240
+
241
+ ``OpenRouterModel`` delegates schema projection to its resolved model
242
+ profile. Different model families can therefore serialize the same
243
+ logical Pydantic type differently (for example, OpenAI-style references
244
+ versus inline definitions). Invoking the exact profile transformer is
245
+ intentional: a dependency/source/model-profile change must be qualified
246
+ rather than approximated by an ad-hoc title stripper.
247
+
248
+ The default preserves the historical helper contract for callers that do
249
+ not own a resolved model profile. Production publishers always inject the
250
+ selected OpenRouter profile's transformer.
251
+ """
252
+
253
+ try:
254
+ from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer
255
+ from pydantic_ai.tools import GenerateToolJsonSchema
256
+
257
+ transformer = (
258
+ OpenAIJsonSchemaTransformer
259
+ if json_schema_transformer is None
260
+ else json_schema_transformer
261
+ )
262
+ if not isinstance(transformer, type):
263
+ raise TypeError("json schema transformer must be a class")
264
+ # The logical commitment must be byte-identical to the prequeue
265
+ # request-evidence boundary. That boundary deliberately records
266
+ # Pydantic's ordinary validation schema, before any transport-specific
267
+ # projection.
268
+ logical = output_type.model_json_schema(
269
+ mode="validation",
270
+ )
271
+ if type(logical) is not dict:
272
+ raise TypeError("logical schema is not an exact object")
273
+ logical_bytes = _canonical_bytes(logical)
274
+ # ``ObjectOutputProcessor`` independently uses Pydantic-AI's
275
+ # tool-specific generator before the model-profile transformer. In
276
+ # particular it removes property titles. Reproduce that maintained
277
+ # path for the wire commitment without conflating it with the logical
278
+ # request schema above.
279
+ transport_logical = output_type.model_json_schema(
280
+ mode="validation",
281
+ schema_generator=GenerateToolJsonSchema,
282
+ )
283
+ if type(transport_logical) is not dict:
284
+ raise TypeError("transport logical schema is not an exact object")
285
+ # Detach because schema transformers are allowed to mutate their input.
286
+ # Preserve insertion order: strict transformers derive ordered
287
+ # ``required`` arrays from property order, whereas replaying canonical
288
+ # (key-sorted) JSON would authenticate a different wire schema.
289
+ detached = deepcopy(transport_logical)
290
+ # ObjectOutputProcessor promotes the root schema description (normally
291
+ # the model docstring) to the function-tool description before the
292
+ # model-specific schema transformer runs.
293
+ detached.pop("description", None)
294
+ if type(strict) is not bool:
295
+ raise TypeError("strict must be an exact bool")
296
+ wire = transformer(detached, strict=strict).walk()
297
+ if type(wire) is not dict:
298
+ raise TypeError("wire schema is not an exact object")
299
+ wire_bytes = _canonical_bytes(wire)
300
+ except Exception as exc:
301
+ raise OpenRouterOutboundRequestManifestError(
302
+ "structured output type cannot produce the pinned wire schema"
303
+ ) from exc
304
+ return (
305
+ len(logical_bytes),
306
+ _sha256(logical_bytes),
307
+ len(wire_bytes),
308
+ _sha256(wire_bytes),
309
+ )
310
+
311
+
312
+ @dataclass(frozen=True, slots=True)
313
+ class _OutboundExpectation:
314
+ publisher_identity: object
315
+ call_id: str
316
+ operation: str
317
+ provider_attempt_id: str
318
+ requested_model: str
319
+ prompt_utf8_bytes: int
320
+ prompt_sha256: str
321
+ output_tool_name: str
322
+ logical_schema_utf8_bytes: int
323
+ logical_schema_sha256: str
324
+ wire_schema_utf8_bytes: int
325
+ wire_schema_sha256: str
326
+ max_completion_tokens: int
327
+ requested_temperature_hex: str | None
328
+ provider: dict[str, object]
329
+ reasoning: dict[str, object] | None
330
+ stream: bool
331
+ output_mode: str
332
+ output_strict: bool
333
+ expected_tool_choice: str
334
+
335
+
336
+ _BOUND_OUTBOUND_EXPECTATION: ContextVar[_OutboundExpectation | None] = ContextVar(
337
+ "agent_evolve_openrouter_outbound_expectation",
338
+ default=None,
339
+ )
340
+
341
+
342
+ class OpenRouterOutboundRequestManifestPublisher:
343
+ """Concurrency-safe HTTPX hook and task-local request expectation owner."""
344
+
345
+ def __init__(
346
+ self,
347
+ sink: OpenRouterOutboundRequestManifestSink,
348
+ *,
349
+ json_schema_transformer: type[Any] | None = None,
350
+ ) -> None:
351
+ if not callable(sink):
352
+ raise TypeError("outbound request manifest sink must be callable")
353
+ if json_schema_transformer is not None and not isinstance(
354
+ json_schema_transformer, type
355
+ ):
356
+ raise TypeError("json_schema_transformer must be a class or None")
357
+ self._sink = sink
358
+ self._json_schema_transformer = json_schema_transformer
359
+ self._identity = object()
360
+ self._publication_lock = threading.Lock()
361
+ self._published_provider_attempt_ids: set[str] = set()
362
+
363
+ @contextmanager
364
+ def bind(
365
+ self,
366
+ request: StructuredGenerationRequest[Any],
367
+ *,
368
+ requested_model: str,
369
+ provider: Mapping[str, object],
370
+ reasoning: Mapping[str, object] | None,
371
+ stream: bool,
372
+ output_mode: str = "tool",
373
+ output_strict: bool = False,
374
+ expected_tool_choice: str = "required",
375
+ ) -> Iterator[None]:
376
+ """Bind one exact physical-attempt identity to the current async task."""
377
+
378
+ if type(request) is not StructuredGenerationRequest:
379
+ raise TypeError("request must be an exact StructuredGenerationRequest")
380
+ StructuredGenerationRequest.__post_init__(request)
381
+ if request.provider_attempt_id is None:
382
+ raise OpenRouterOutboundRequestManifestError(
383
+ "outbound evidence requires a physical provider_attempt_id"
384
+ )
385
+ if (
386
+ type(requested_model) is not str
387
+ or _MODEL_SLUG.fullmatch(requested_model) is None
388
+ ):
389
+ raise ValueError("requested_model must be an OpenRouter model slug")
390
+ if not isinstance(provider, Mapping):
391
+ raise TypeError("provider must be a mapping")
392
+ if reasoning is not None and not isinstance(reasoning, Mapping):
393
+ raise TypeError("reasoning must be a mapping or None")
394
+ if type(stream) is not bool:
395
+ raise TypeError("stream must be an exact bool")
396
+ if type(output_strict) is not bool:
397
+ raise TypeError("output_strict must be an exact bool")
398
+ if (
399
+ type(expected_tool_choice) is not str
400
+ or expected_tool_choice not in {"required", "auto"}
401
+ ):
402
+ raise ValueError("expected_tool_choice must be required or auto")
403
+ if (
404
+ type(output_mode) is not str
405
+ or output_mode not in _STRUCTURED_OUTPUT_MODES
406
+ ):
407
+ raise ValueError("output_mode is outside the closed transport modes")
408
+ if _BOUND_OUTBOUND_EXPECTATION.get() is not None:
409
+ raise OpenRouterOutboundRequestManifestError(
410
+ "nested outbound request evidence contexts are forbidden"
411
+ )
412
+
413
+ try:
414
+ provider_json = _canonical_json_copy(dict(provider), label="provider")
415
+ reasoning_json = (
416
+ None
417
+ if reasoning is None
418
+ else _canonical_json_copy(dict(reasoning), label="reasoning")
419
+ )
420
+ provider_copy = _closed_provider(provider_json)
421
+ reasoning_copy = _closed_reasoning(reasoning_json)
422
+ except (TypeError, ValueError) as exc:
423
+ raise OpenRouterOutboundRequestManifestError(
424
+ "provider/reasoning settings violate the closed safe projection"
425
+ ) from exc
426
+ prompt_bytes = request.prompt.encode("utf-8", errors="strict")
427
+ (
428
+ logical_schema_utf8_bytes,
429
+ logical_schema_sha256,
430
+ wire_schema_utf8_bytes,
431
+ wire_schema_sha256,
432
+ ) = _wire_output_schema(
433
+ request.output_type,
434
+ json_schema_transformer=self._json_schema_transformer,
435
+ strict=output_strict,
436
+ )
437
+ expectation = _OutboundExpectation(
438
+ publisher_identity=self._identity,
439
+ call_id=request.call_id.value,
440
+ operation=request.operation,
441
+ provider_attempt_id=request.provider_attempt_id.value,
442
+ requested_model=requested_model,
443
+ prompt_utf8_bytes=len(prompt_bytes),
444
+ prompt_sha256=_sha256(prompt_bytes),
445
+ output_tool_name=request.output_tool_name,
446
+ logical_schema_utf8_bytes=logical_schema_utf8_bytes,
447
+ logical_schema_sha256=logical_schema_sha256,
448
+ wire_schema_utf8_bytes=wire_schema_utf8_bytes,
449
+ wire_schema_sha256=wire_schema_sha256,
450
+ max_completion_tokens=request.max_output_tokens,
451
+ requested_temperature_hex=(
452
+ None
453
+ if request.temperature is None
454
+ else float(request.temperature).hex()
455
+ ),
456
+ provider=provider_copy,
457
+ reasoning=reasoning_copy,
458
+ stream=stream,
459
+ output_mode=output_mode,
460
+ output_strict=output_strict,
461
+ expected_tool_choice=expected_tool_choice,
462
+ )
463
+ token = _BOUND_OUTBOUND_EXPECTATION.set(expectation)
464
+ try:
465
+ yield
466
+ finally:
467
+ _BOUND_OUTBOUND_EXPECTATION.reset(token)
468
+
469
+ async def httpx_request_hook(self, request: Any) -> None:
470
+ """Validate and publish the final serialized body before HTTP transport."""
471
+
472
+ expectation = _BOUND_OUTBOUND_EXPECTATION.get()
473
+ if expectation is None or expectation.publisher_identity is not self._identity:
474
+ raise OpenRouterOutboundRequestManifestError(
475
+ "OpenRouter dispatch has no matching task-local attempt context"
476
+ )
477
+ record = await _manifest_record_from_httpx_request(request, expectation)
478
+ provider_attempt_id = expectation.provider_attempt_id
479
+ with self._publication_lock:
480
+ if provider_attempt_id in self._published_provider_attempt_ids:
481
+ raise OpenRouterOutboundRequestManifestError(
482
+ "duplicate transport dispatch for one provider_attempt_id"
483
+ )
484
+ # Reserve before publication. A sink that writes and then raises must
485
+ # never cause the same physical identity to be published/sent again.
486
+ self._published_provider_attempt_ids.add(provider_attempt_id)
487
+ self._sink(record)
488
+
489
+
490
+ async def _manifest_record_from_httpx_request(
491
+ request: Any,
492
+ expectation: _OutboundExpectation,
493
+ ) -> dict[str, object]:
494
+ if getattr(request, "method", None) != _TRANSPORT_CONTRACT["method"]:
495
+ raise OpenRouterOutboundRequestManifestError(
496
+ "outbound request method violates the transport contract"
497
+ )
498
+ url = getattr(request, "url", None)
499
+ if (
500
+ url is None
501
+ or getattr(url, "scheme", None) != _TRANSPORT_CONTRACT["scheme"]
502
+ or getattr(url, "host", None) != _TRANSPORT_CONTRACT["host"]
503
+ or getattr(url, "port", None) is not None
504
+ or getattr(url, "path", None) != _TRANSPORT_CONTRACT["path"]
505
+ or getattr(url, "query", b"") not in (b"", "")
506
+ ):
507
+ raise OpenRouterOutboundRequestManifestError(
508
+ "outbound URL violates the closed OpenRouter transport contract"
509
+ )
510
+ try:
511
+ wire_body = await request.aread()
512
+ except Exception as exc:
513
+ raise OpenRouterOutboundRequestManifestError(
514
+ "outbound JSON body could not be read before transport"
515
+ ) from exc
516
+ if (
517
+ type(wire_body) is not bytes
518
+ or not wire_body
519
+ or len(wire_body) > _MAX_WIRE_BODY_BYTES
520
+ ):
521
+ raise OpenRouterOutboundRequestManifestError(
522
+ "outbound JSON body is empty or outside its evidence bound"
523
+ )
524
+ try:
525
+ body = json.loads(
526
+ wire_body,
527
+ object_pairs_hook=_reject_duplicate_json_keys,
528
+ )
529
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
530
+ raise OpenRouterOutboundRequestManifestError(
531
+ "outbound body is not one UTF-8 JSON object"
532
+ ) from exc
533
+ if type(body) is not dict:
534
+ raise OpenRouterOutboundRequestManifestError(
535
+ "outbound body must be one exact JSON object"
536
+ )
537
+
538
+ expected_fields = set(_BASE_BODY_FIELDS)
539
+ if expectation.output_mode == "tool":
540
+ expected_fields.update(("tools", "tool_choice"))
541
+ else:
542
+ expected_fields.add("response_format")
543
+ if expectation.reasoning is not None:
544
+ expected_fields.add("reasoning")
545
+ if expectation.requested_temperature_hex is not None:
546
+ # Pydantic-AI intentionally drops sampling parameters for model
547
+ # profiles that identify an active reasoning model. Absence is therefore
548
+ # admitted and recorded; any present value must still match exactly.
549
+ if "temperature" in body:
550
+ expected_fields.add("temperature")
551
+ if expectation.stream:
552
+ expected_fields.add("stream_options")
553
+ if frozenset(body) != frozenset(expected_fields):
554
+ raise OpenRouterOutboundRequestManifestError(
555
+ "outbound body has missing or unexpected top-level fields"
556
+ )
557
+ if any(name in body for name in _FORBIDDEN_TOP_LEVEL_FIELDS):
558
+ raise OpenRouterOutboundRequestManifestError(
559
+ "outbound body contains a forbidden routing/behavior field"
560
+ )
561
+
562
+ model = body["model"]
563
+ if type(model) is not str or model != expectation.requested_model:
564
+ raise OpenRouterOutboundRequestManifestError(
565
+ "outbound model does not match the bound request"
566
+ )
567
+ max_completion_tokens = body["max_completion_tokens"]
568
+ if (
569
+ type(max_completion_tokens) is not int
570
+ or max_completion_tokens != expectation.max_completion_tokens
571
+ ):
572
+ raise OpenRouterOutboundRequestManifestError(
573
+ "outbound completion budget does not match the bound request"
574
+ )
575
+
576
+ messages = body["messages"]
577
+ if (
578
+ type(messages) is not list
579
+ or len(messages) != 1
580
+ or type(messages[0]) is not dict
581
+ or frozenset(messages[0]) != {"content", "role"}
582
+ or messages[0]["role"] != "user"
583
+ or type(messages[0]["content"]) is not str
584
+ ):
585
+ raise OpenRouterOutboundRequestManifestError(
586
+ "outbound messages violate the single-prompt contract"
587
+ )
588
+ prompt_bytes = messages[0]["content"].encode("utf-8", errors="strict")
589
+ if (
590
+ len(prompt_bytes) != expectation.prompt_utf8_bytes
591
+ or _sha256(prompt_bytes) != expectation.prompt_sha256
592
+ ):
593
+ raise OpenRouterOutboundRequestManifestError(
594
+ "outbound prompt does not match the bound request"
595
+ )
596
+
597
+ output_count: int
598
+ output_kind: str
599
+ output_name: str
600
+ output_description: str
601
+ wire_strict: bool | None
602
+ tool_choice: str | None
603
+ response_format_kind: str | None
604
+ if expectation.output_mode == "tool":
605
+ tools = body["tools"]
606
+ if (
607
+ type(tools) is not list
608
+ or len(tools) != 1
609
+ or type(tools[0]) is not dict
610
+ or frozenset(tools[0]) != {"function", "type"}
611
+ or tools[0]["type"] != "function"
612
+ or type(tools[0]["function"]) is not dict
613
+ ):
614
+ raise OpenRouterOutboundRequestManifestError(
615
+ "outbound tools violate the single-function contract"
616
+ )
617
+ function = tools[0]["function"]
618
+ expected_function_fields = {"description", "name", "parameters"}
619
+ if expectation.output_strict:
620
+ expected_function_fields.add("strict")
621
+ if frozenset(function) != expected_function_fields:
622
+ raise OpenRouterOutboundRequestManifestError(
623
+ "outbound function tool has unexpected fields"
624
+ )
625
+ if (
626
+ type(function["name"]) is not str
627
+ or function["name"] != expectation.output_tool_name
628
+ or type(function["description"]) is not str
629
+ or type(function["parameters"]) is not dict
630
+ ):
631
+ raise OpenRouterOutboundRequestManifestError(
632
+ "outbound function tool does not match the bound contract"
633
+ )
634
+ output_count = 1
635
+ output_kind = "function"
636
+ output_name = function["name"]
637
+ output_description = function["description"]
638
+ schema = function["parameters"]
639
+ wire_strict = function.get("strict")
640
+ tool_choice = body["tool_choice"]
641
+ response_format_kind = None
642
+ if tool_choice != expectation.expected_tool_choice:
643
+ raise OpenRouterOutboundRequestManifestError(
644
+ "outbound tool choice differs from the bound route capability"
645
+ )
646
+ if wire_strict != (True if expectation.output_strict else None):
647
+ raise OpenRouterOutboundRequestManifestError(
648
+ "outbound function strictness differs from the bound contract"
649
+ )
650
+ else:
651
+ response_format = body["response_format"]
652
+ if (
653
+ type(response_format) is not dict
654
+ or frozenset(response_format) != {"json_schema", "type"}
655
+ or response_format["type"] != "json_schema"
656
+ or type(response_format["json_schema"]) is not dict
657
+ ):
658
+ raise OpenRouterOutboundRequestManifestError(
659
+ "outbound native response format violates the JSON-schema contract"
660
+ )
661
+ native = response_format["json_schema"]
662
+ if frozenset(native) not in (
663
+ frozenset({"name", "schema", "strict"}),
664
+ frozenset({"description", "name", "schema", "strict"}),
665
+ ):
666
+ raise OpenRouterOutboundRequestManifestError(
667
+ "outbound native JSON schema has unexpected fields"
668
+ )
669
+ if (
670
+ type(native["name"]) is not str
671
+ or native["name"] != expectation.output_tool_name
672
+ or type(native["schema"]) is not dict
673
+ or native["strict"] is not expectation.output_strict
674
+ or (
675
+ "description" in native
676
+ and type(native["description"]) is not str
677
+ )
678
+ ):
679
+ raise OpenRouterOutboundRequestManifestError(
680
+ "outbound native JSON schema does not match the bound contract"
681
+ )
682
+ output_count = 0
683
+ output_kind = "native_json_schema"
684
+ output_name = native["name"]
685
+ output_description = native.get("description", "")
686
+ schema = native["schema"]
687
+ wire_strict = expectation.output_strict
688
+ tool_choice = None
689
+ response_format_kind = "json_schema"
690
+
691
+ description_bytes = output_description.encode("utf-8", errors="strict")
692
+ schema_bytes = _canonical_bytes(schema)
693
+ if (
694
+ len(schema_bytes) != expectation.wire_schema_utf8_bytes
695
+ or _sha256(schema_bytes) != expectation.wire_schema_sha256
696
+ ):
697
+ raise OpenRouterOutboundRequestManifestError(
698
+ "outbound structured schema does not match the bound output type: "
699
+ f"expected={expectation.wire_schema_sha256}/"
700
+ f"{expectation.wire_schema_utf8_bytes}, "
701
+ f"observed={_sha256(schema_bytes)}/{len(schema_bytes)}"
702
+ )
703
+
704
+ if type(body["provider"]) is not dict or body["provider"] != expectation.provider:
705
+ raise OpenRouterOutboundRequestManifestError(
706
+ "outbound provider routing differs from the bound route"
707
+ )
708
+ reasoning = body.get("reasoning")
709
+ if reasoning != expectation.reasoning or (
710
+ reasoning is not None and type(reasoning) is not dict
711
+ ):
712
+ raise OpenRouterOutboundRequestManifestError(
713
+ "outbound reasoning differs from the bound configuration"
714
+ )
715
+ if reasoning is not None:
716
+ expected_reasoning_fields = (
717
+ {"effort"} if "effort" in reasoning else {"max_tokens"}
718
+ )
719
+ if (
720
+ frozenset(reasoning) != expected_reasoning_fields
721
+ or any(name in reasoning for name in _FORBIDDEN_REASONING_FIELDS)
722
+ ):
723
+ raise OpenRouterOutboundRequestManifestError(
724
+ "outbound reasoning has unexpected or forbidden fields"
725
+ )
726
+ if body["usage"] != {"include": True}:
727
+ raise OpenRouterOutboundRequestManifestError(
728
+ "outbound usage accounting is not explicitly enabled"
729
+ )
730
+ if type(body["stream"]) is not bool or body["stream"] is not expectation.stream:
731
+ raise OpenRouterOutboundRequestManifestError(
732
+ "outbound stream mode differs from the bound execution path"
733
+ )
734
+ stream_options = body.get("stream_options")
735
+ if stream_options != ({"include_usage": True} if expectation.stream else None):
736
+ raise OpenRouterOutboundRequestManifestError(
737
+ "outbound stream usage options violate the closed contract"
738
+ )
739
+
740
+ temperature = body.get("temperature")
741
+ temperature_hex: str | None = None
742
+ if temperature is not None:
743
+ if (
744
+ isinstance(temperature, bool)
745
+ or not isinstance(temperature, (int, float))
746
+ or not math.isfinite(float(temperature))
747
+ ):
748
+ raise OpenRouterOutboundRequestManifestError(
749
+ "outbound temperature is not a finite number"
750
+ )
751
+ temperature_hex = float(temperature).hex()
752
+ if temperature_hex != expectation.requested_temperature_hex:
753
+ raise OpenRouterOutboundRequestManifestError(
754
+ "outbound temperature differs from the bound request"
755
+ )
756
+
757
+ canonical_body = _canonical_bytes(body)
758
+ record: dict[str, object] = {
759
+ "schema_version": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION,
760
+ "boundary_id": OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID,
761
+ "call_id": expectation.call_id,
762
+ "operation": expectation.operation,
763
+ "provider_attempt_id": expectation.provider_attempt_id,
764
+ "transport": dict(_TRANSPORT_CONTRACT),
765
+ "transport_contract_sha256": _transport_contract_sha256(),
766
+ "framework_versions": _framework_versions(),
767
+ "body": {
768
+ "wire_utf8_bytes": len(wire_body),
769
+ "wire_sha256": _sha256(wire_body),
770
+ "canonical_utf8_bytes": len(canonical_body),
771
+ "canonical_sha256": _sha256(canonical_body),
772
+ "top_level_keys": sorted(body),
773
+ },
774
+ "message": {
775
+ "count": 1,
776
+ "role": "user",
777
+ "content_utf8_bytes": len(prompt_bytes),
778
+ "content_sha256": _sha256(prompt_bytes),
779
+ },
780
+ "tool": {
781
+ "count": output_count,
782
+ "type": output_kind,
783
+ "name": output_name,
784
+ "description_utf8_bytes": len(description_bytes),
785
+ "description_sha256": _sha256(description_bytes),
786
+ "schema_utf8_bytes": len(schema_bytes),
787
+ "schema_sha256": _sha256(schema_bytes),
788
+ "requested_strict": expectation.output_strict,
789
+ "wire_strict": wire_strict,
790
+ },
791
+ "request_contract": {
792
+ "logical_output_schema_utf8_bytes": (
793
+ expectation.logical_schema_utf8_bytes
794
+ ),
795
+ "logical_output_schema_sha256": expectation.logical_schema_sha256,
796
+ "wire_output_schema_utf8_bytes": expectation.wire_schema_utf8_bytes,
797
+ "wire_output_schema_sha256": expectation.wire_schema_sha256,
798
+ "requested_temperature_hex": expectation.requested_temperature_hex,
799
+ },
800
+ "settings": {
801
+ "model": model,
802
+ "provider": body["provider"],
803
+ "reasoning": reasoning,
804
+ "usage": body["usage"],
805
+ "stream": body["stream"],
806
+ "stream_options": stream_options,
807
+ "output_mode": expectation.output_mode,
808
+ "tool_choice": tool_choice,
809
+ "max_completion_tokens": max_completion_tokens,
810
+ "temperature_hex": temperature_hex,
811
+ "response_format": response_format_kind,
812
+ },
813
+ "forbidden_fields_absent": {
814
+ **{name: name not in body for name in _FORBIDDEN_TOP_LEVEL_FIELDS},
815
+ **{
816
+ f"reasoning.{name}": reasoning is None or name not in reasoning
817
+ for name in _FORBIDDEN_REASONING_FIELDS
818
+ },
819
+ },
820
+ "hash_algorithms": {
821
+ "manifest": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM,
822
+ "public_projection": (
823
+ OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM
824
+ ),
825
+ "transport_contract": (
826
+ OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM
827
+ ),
828
+ "content": "sha256_exact_bytes_or_canonical_json_v1",
829
+ },
830
+ }
831
+ record["public_projection_sha256"] = _domain_sha256(
832
+ _PUBLIC_PROJECTION_DOMAIN,
833
+ record,
834
+ )
835
+ record["outbound_request_manifest_sha256"] = _domain_sha256(
836
+ _MANIFEST_DOMAIN,
837
+ record,
838
+ )
839
+ return validate_openrouter_outbound_request_manifest_record(record)
840
+
841
+
842
+ _MANIFEST_FIELDS = frozenset(
843
+ {
844
+ "schema_version",
845
+ "boundary_id",
846
+ "call_id",
847
+ "operation",
848
+ "provider_attempt_id",
849
+ "transport",
850
+ "transport_contract_sha256",
851
+ "framework_versions",
852
+ "body",
853
+ "message",
854
+ "tool",
855
+ "request_contract",
856
+ "settings",
857
+ "forbidden_fields_absent",
858
+ "hash_algorithms",
859
+ "public_projection_sha256",
860
+ "outbound_request_manifest_sha256",
861
+ }
862
+ )
863
+
864
+
865
+ def _require_sha256(value: object, *, label: str) -> str:
866
+ if type(value) is not str or _LOWER_SHA256.fullmatch(value) is None:
867
+ raise ValueError(f"{label} must be a lowercase SHA-256 digest")
868
+ return value
869
+
870
+
871
+ def _validated_temperature_hex(value: object, *, label: str) -> str | None:
872
+ if value is None:
873
+ return None
874
+ if type(value) is not str:
875
+ raise ValueError(f"{label} must be a hexadecimal float or None")
876
+ try:
877
+ decoded = float.fromhex(value)
878
+ except ValueError as exc:
879
+ raise ValueError(f"{label} is not a hexadecimal float") from exc
880
+ if (
881
+ not math.isfinite(decoded)
882
+ or not 0 <= decoded <= 2
883
+ or decoded.hex() != value
884
+ ):
885
+ raise ValueError(f"{label} is outside the canonical temperature range")
886
+ return value
887
+
888
+
889
+ def validate_openrouter_outbound_request_manifest_record(
890
+ record: Mapping[str, object],
891
+ ) -> dict[str, object]:
892
+ """Strictly validate, authenticate, and detach one persisted manifest row."""
893
+
894
+ if not isinstance(record, Mapping):
895
+ raise TypeError("outbound request manifest must be a mapping")
896
+ try:
897
+ canonical = json.loads(_canonical_bytes(dict(record)))
898
+ except (TypeError, ValueError) as exc:
899
+ raise ValueError("outbound request manifest is not canonical JSON") from exc
900
+ if type(canonical) is not dict or frozenset(canonical) != _MANIFEST_FIELDS:
901
+ raise ValueError("outbound request manifest has unexpected fields")
902
+ if (
903
+ type(canonical["schema_version"]) is not int
904
+ or canonical["schema_version"]
905
+ != OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION
906
+ or canonical["boundary_id"] != OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID
907
+ ):
908
+ raise ValueError("unsupported outbound request manifest contract")
909
+ LLMCallId(canonical["call_id"])
910
+ ProviderAttemptId(canonical["provider_attempt_id"])
911
+ if (
912
+ type(canonical["operation"]) is not str
913
+ or _OPERATION_TOKEN.fullmatch(canonical["operation"]) is None
914
+ ):
915
+ raise ValueError("operation is outside the closed token grammar")
916
+ if canonical["transport"] != _TRANSPORT_CONTRACT:
917
+ raise ValueError("transport contract projection drifted")
918
+ frameworks = canonical["framework_versions"]
919
+ if (
920
+ type(frameworks) is not dict
921
+ or tuple(sorted(frameworks)) != tuple(sorted(_FRAMEWORK_PACKAGES))
922
+ or any(
923
+ type(value) is not str or _VERSION_TOKEN.fullmatch(value) is None
924
+ for value in frameworks.values()
925
+ )
926
+ ):
927
+ raise ValueError("framework version projection is outside its closed schema")
928
+ if (
929
+ _require_sha256(
930
+ canonical["transport_contract_sha256"],
931
+ label="transport_contract_sha256",
932
+ )
933
+ != _transport_contract_sha256()
934
+ ):
935
+ raise ValueError("transport_contract_sha256 does not authenticate contract")
936
+
937
+ exact_nested_fields = {
938
+ "body": {
939
+ "wire_utf8_bytes",
940
+ "wire_sha256",
941
+ "canonical_utf8_bytes",
942
+ "canonical_sha256",
943
+ "top_level_keys",
944
+ },
945
+ "message": {
946
+ "count",
947
+ "role",
948
+ "content_utf8_bytes",
949
+ "content_sha256",
950
+ },
951
+ "tool": {
952
+ "count",
953
+ "type",
954
+ "name",
955
+ "description_utf8_bytes",
956
+ "description_sha256",
957
+ "schema_utf8_bytes",
958
+ "schema_sha256",
959
+ "requested_strict",
960
+ "wire_strict",
961
+ },
962
+ "request_contract": {
963
+ "logical_output_schema_utf8_bytes",
964
+ "logical_output_schema_sha256",
965
+ "wire_output_schema_utf8_bytes",
966
+ "wire_output_schema_sha256",
967
+ "requested_temperature_hex",
968
+ },
969
+ "settings": {
970
+ "model",
971
+ "provider",
972
+ "reasoning",
973
+ "usage",
974
+ "stream",
975
+ "stream_options",
976
+ "output_mode",
977
+ "tool_choice",
978
+ "max_completion_tokens",
979
+ "temperature_hex",
980
+ "response_format",
981
+ },
982
+ "hash_algorithms": {
983
+ "manifest",
984
+ "public_projection",
985
+ "transport_contract",
986
+ "content",
987
+ },
988
+ }
989
+ for name, fields in exact_nested_fields.items():
990
+ value = canonical[name]
991
+ if type(value) is not dict or frozenset(value) != fields:
992
+ raise ValueError(f"{name} projection has unexpected fields")
993
+ for container, names in (
994
+ (canonical["body"], ("wire_sha256", "canonical_sha256")),
995
+ (canonical["message"], ("content_sha256",)),
996
+ (canonical["tool"], ("description_sha256", "schema_sha256")),
997
+ (
998
+ canonical["request_contract"],
999
+ ("logical_output_schema_sha256", "wire_output_schema_sha256"),
1000
+ ),
1001
+ ):
1002
+ for name in names:
1003
+ _require_sha256(container[name], label=name)
1004
+ for container, names in (
1005
+ (canonical["body"], ("wire_utf8_bytes", "canonical_utf8_bytes")),
1006
+ (canonical["message"], ("content_utf8_bytes",)),
1007
+ (
1008
+ canonical["tool"],
1009
+ ("description_utf8_bytes", "schema_utf8_bytes"),
1010
+ ),
1011
+ (
1012
+ canonical["request_contract"],
1013
+ ("logical_output_schema_utf8_bytes", "wire_output_schema_utf8_bytes"),
1014
+ ),
1015
+ ):
1016
+ if any(type(container[name]) is not int or container[name] < 0 for name in names):
1017
+ raise ValueError("manifest byte lengths must be non-negative integers")
1018
+ body = canonical["body"]
1019
+ message = canonical["message"]
1020
+ tool = canonical["tool"]
1021
+ request_contract = canonical["request_contract"]
1022
+ settings = canonical["settings"]
1023
+ if (
1024
+ not 1 <= body["wire_utf8_bytes"] <= _MAX_WIRE_BODY_BYTES
1025
+ or not 1 <= body["canonical_utf8_bytes"] <= _MAX_WIRE_BODY_BYTES
1026
+ or not 1 <= message["content_utf8_bytes"] <= MAX_PROMPT_UTF8_BYTES
1027
+ or tool["description_utf8_bytes"] > MAX_PROMPT_UTF8_BYTES
1028
+ or not 1 <= tool["schema_utf8_bytes"] <= 1_048_576
1029
+ or not 1 <= request_contract["logical_output_schema_utf8_bytes"] <= 1_048_576
1030
+ or not 1 <= request_contract["wire_output_schema_utf8_bytes"] <= 1_048_576
1031
+ ):
1032
+ raise ValueError("manifest byte length exceeds its generic bound")
1033
+ output_mode = settings["output_mode"]
1034
+ if type(output_mode) is not str or output_mode not in _STRUCTURED_OUTPUT_MODES:
1035
+ raise ValueError("manifest output mode is outside the closed transport modes")
1036
+ tool_mode_valid = (
1037
+ output_mode == "tool"
1038
+ and tool["count"] == 1
1039
+ and tool["type"] == "function"
1040
+ and tool["wire_strict"]
1041
+ == (True if tool["requested_strict"] else None)
1042
+ and settings["tool_choice"] in {"required", "auto"}
1043
+ and settings["response_format"] is None
1044
+ )
1045
+ native_mode_valid = (
1046
+ output_mode == "native_json_schema"
1047
+ and tool["count"] == 0
1048
+ and tool["type"] == "native_json_schema"
1049
+ and tool["wire_strict"] is tool["requested_strict"]
1050
+ and settings["tool_choice"] is None
1051
+ and settings["response_format"] == "json_schema"
1052
+ )
1053
+ if (
1054
+ message["count"] != 1
1055
+ or message["role"] != "user"
1056
+ or not (tool_mode_valid or native_mode_valid)
1057
+ or type(tool["name"]) is not str
1058
+ or _TOOL_TOKEN.fullmatch(tool["name"]) is None
1059
+ or type(tool["requested_strict"]) is not bool
1060
+ or settings["usage"] != {"include": True}
1061
+ ):
1062
+ raise ValueError("manifest projections violate the structured request contract")
1063
+ if (
1064
+ tool["schema_sha256"] != request_contract["wire_output_schema_sha256"]
1065
+ or tool["schema_utf8_bytes"]
1066
+ != request_contract["wire_output_schema_utf8_bytes"]
1067
+ ):
1068
+ raise ValueError("tool schema projection does not join its request contract")
1069
+
1070
+ model = settings["model"]
1071
+ if type(model) is not str or _MODEL_SLUG.fullmatch(model) is None:
1072
+ raise ValueError("model is outside the closed OpenRouter slug grammar")
1073
+ try:
1074
+ provider = _closed_provider(settings["provider"])
1075
+ reasoning = _closed_reasoning(settings["reasoning"])
1076
+ except ValueError as exc:
1077
+ raise ValueError("settings contain unsafe provider/reasoning values") from exc
1078
+ if provider != settings["provider"] or reasoning != settings["reasoning"]:
1079
+ raise ValueError("provider/reasoning settings are not canonical")
1080
+ if (
1081
+ type(settings["stream"]) is not bool
1082
+ or settings["stream_options"]
1083
+ != ({"include_usage": True} if settings["stream"] else None)
1084
+ or type(settings["max_completion_tokens"]) is not int
1085
+ or not 1 <= settings["max_completion_tokens"] <= MAX_OUTPUT_TOKENS
1086
+ ):
1087
+ raise ValueError("stream or completion settings are outside the closed contract")
1088
+ requested_temperature = _validated_temperature_hex(
1089
+ request_contract["requested_temperature_hex"],
1090
+ label="requested_temperature_hex",
1091
+ )
1092
+ wire_temperature = _validated_temperature_hex(
1093
+ settings["temperature_hex"],
1094
+ label="temperature_hex",
1095
+ )
1096
+ if wire_temperature is not None and wire_temperature != requested_temperature:
1097
+ raise ValueError("wire temperature differs from requested temperature")
1098
+
1099
+ expected_top_level_fields = set(_BASE_BODY_FIELDS)
1100
+ if output_mode == "tool":
1101
+ expected_top_level_fields.update(("tools", "tool_choice"))
1102
+ else:
1103
+ expected_top_level_fields.add("response_format")
1104
+ if reasoning is not None:
1105
+ expected_top_level_fields.add("reasoning")
1106
+ if wire_temperature is not None:
1107
+ expected_top_level_fields.add("temperature")
1108
+ if settings["stream"]:
1109
+ expected_top_level_fields.add("stream_options")
1110
+ top_level_keys = body["top_level_keys"]
1111
+ if (
1112
+ type(top_level_keys) is not list
1113
+ or any(type(name) is not str for name in top_level_keys)
1114
+ or top_level_keys != sorted(expected_top_level_fields)
1115
+ ):
1116
+ raise ValueError("top-level key projection disagrees with settings")
1117
+ forbidden = canonical["forbidden_fields_absent"]
1118
+ expected_forbidden_keys = {
1119
+ *_FORBIDDEN_TOP_LEVEL_FIELDS,
1120
+ *(f"reasoning.{name}" for name in _FORBIDDEN_REASONING_FIELDS),
1121
+ }
1122
+ if (
1123
+ type(forbidden) is not dict
1124
+ or frozenset(forbidden) != expected_forbidden_keys
1125
+ or any(value is not True for value in forbidden.values())
1126
+ ):
1127
+ raise ValueError("forbidden-field absence projection is invalid")
1128
+ if canonical["hash_algorithms"] != {
1129
+ "manifest": OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM,
1130
+ "public_projection": OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM,
1131
+ "transport_contract": (
1132
+ OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM
1133
+ ),
1134
+ "content": "sha256_exact_bytes_or_canonical_json_v1",
1135
+ }:
1136
+ raise ValueError("manifest hash algorithm projection drifted")
1137
+
1138
+ supplied_manifest = _require_sha256(
1139
+ canonical["outbound_request_manifest_sha256"],
1140
+ label="outbound_request_manifest_sha256",
1141
+ )
1142
+ without_manifest = dict(canonical)
1143
+ del without_manifest["outbound_request_manifest_sha256"]
1144
+ if supplied_manifest != _domain_sha256(_MANIFEST_DOMAIN, without_manifest):
1145
+ raise ValueError("outbound manifest self hash does not authenticate record")
1146
+ supplied_public = _require_sha256(
1147
+ canonical["public_projection_sha256"],
1148
+ label="public_projection_sha256",
1149
+ )
1150
+ public_projection = dict(without_manifest)
1151
+ del public_projection["public_projection_sha256"]
1152
+ if supplied_public != _domain_sha256(
1153
+ _PUBLIC_PROJECTION_DOMAIN,
1154
+ public_projection,
1155
+ ):
1156
+ raise ValueError("public projection hash does not authenticate record")
1157
+ return canonical
1158
+
1159
+
1160
+ __all__ = [
1161
+ "OPENROUTER_OUTBOUND_PUBLIC_PROJECTION_HASH_ALGORITHM",
1162
+ "OPENROUTER_OUTBOUND_REQUEST_BOUNDARY_ID",
1163
+ "OPENROUTER_OUTBOUND_REQUEST_MANIFEST_HASH_ALGORITHM",
1164
+ "OPENROUTER_OUTBOUND_REQUEST_MANIFEST_SCHEMA_VERSION",
1165
+ "OPENROUTER_OUTBOUND_TRANSPORT_CONTRACT_HASH_ALGORITHM",
1166
+ "OpenRouterOutboundRequestManifestError",
1167
+ "OpenRouterOutboundRequestManifestPublisher",
1168
+ "OpenRouterOutboundRequestManifestSink",
1169
+ "validate_openrouter_outbound_request_manifest_record",
1170
+ ]