agentinputlab 0.1.0a1__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 (469) hide show
  1. agentinputlab/__init__.py +28 -0
  2. agentinputlab/algorithms/__init__.py +6 -0
  3. agentinputlab/algorithms/acquire/__init__.py +145 -0
  4. agentinputlab/algorithms/acquire/adaptive_rag/__init__.py +43 -0
  5. agentinputlab/algorithms/acquire/adaptive_rag/replay.py +367 -0
  6. agentinputlab/algorithms/acquire/adaptive_rag/runtime.py +1202 -0
  7. agentinputlab/algorithms/acquire/adaptive_rag_common_support.py +542 -0
  8. agentinputlab/algorithms/acquire/hyde.py +296 -0
  9. agentinputlab/algorithms/acquire/hyde_controlled.py +200 -0
  10. agentinputlab/algorithms/acquire/hyde_external.py +148 -0
  11. agentinputlab/algorithms/acquire/ircot.py +28 -0
  12. agentinputlab/algorithms/acquire/ircot_common_support.py +478 -0
  13. agentinputlab/algorithms/acquire/ircot_native.py +355 -0
  14. agentinputlab/algorithms/acquire/ircot_replay.py +327 -0
  15. agentinputlab/algorithms/acquire/qrag/__init__.py +79 -0
  16. agentinputlab/algorithms/acquire/qrag/bridge.py +828 -0
  17. agentinputlab/algorithms/acquire/qrag/common_support.py +891 -0
  18. agentinputlab/algorithms/acquire/qrag/native_axis.py +71 -0
  19. agentinputlab/algorithms/acquire/qrag/reader_adapter.py +139 -0
  20. agentinputlab/algorithms/acquire/r2ag/__init__.py +57 -0
  21. agentinputlab/algorithms/acquire/r2ag/bridge.py +1312 -0
  22. agentinputlab/algorithms/acquire/r2ag/common_support.py +1029 -0
  23. agentinputlab/algorithms/acquire/r2ag/socket_runtime.py +255 -0
  24. agentinputlab/algorithms/clarify/__init__.py +61 -0
  25. agentinputlab/algorithms/clarify/_structured.py +64 -0
  26. agentinputlab/algorithms/clarify/active_task_disambiguation.py +371 -0
  27. agentinputlab/algorithms/clarify/ask_or_assume/__init__.py +17 -0
  28. agentinputlab/algorithms/clarify/ask_or_assume/monitor.py +410 -0
  29. agentinputlab/algorithms/clarify/future_turns.py +379 -0
  30. agentinputlab/algorithms/clarify/udcs.py +255 -0
  31. agentinputlab/algorithms/clarify/udcs_deterministic.py +196 -0
  32. agentinputlab/algorithms/curate/__init__.py +65 -0
  33. agentinputlab/algorithms/curate/acon/__init__.py +27 -0
  34. agentinputlab/algorithms/curate/acon/common.py +284 -0
  35. agentinputlab/algorithms/curate/acon/common_support.py +136 -0
  36. agentinputlab/algorithms/curate/acon/history_bridge.py +309 -0
  37. agentinputlab/algorithms/curate/acon/observation_bridge.py +290 -0
  38. agentinputlab/algorithms/curate/llmlingua/__init__.py +30 -0
  39. agentinputlab/algorithms/curate/llmlingua/adapters.py +537 -0
  40. agentinputlab/algorithms/curate/llmlingua/budget.py +48 -0
  41. agentinputlab/algorithms/curate/llmlingua/controlled.py +175 -0
  42. agentinputlab/algorithms/curate/llmlingua/multilingual.py +105 -0
  43. agentinputlab/algorithms/curate/llmlingua/span_map.py +167 -0
  44. agentinputlab/algorithms/curate/recomp/__init__.py +47 -0
  45. agentinputlab/algorithms/curate/recomp/external.py +1129 -0
  46. agentinputlab/algorithms/curate/recomp/selective.py +304 -0
  47. agentinputlab/algorithms/decompose/__init__.py +107 -0
  48. agentinputlab/algorithms/decompose/decomp/__init__.py +53 -0
  49. agentinputlab/algorithms/decompose/decomp/common_support.py +478 -0
  50. agentinputlab/algorithms/decompose/decomp/planner_component.py +199 -0
  51. agentinputlab/algorithms/decompose/decomp/worker_bridge.py +416 -0
  52. agentinputlab/algorithms/decompose/llm_compiler/__init__.py +35 -0
  53. agentinputlab/algorithms/decompose/llm_compiler/bridge.py +520 -0
  54. agentinputlab/algorithms/decompose/llm_compiler/common_support.py +504 -0
  55. agentinputlab/algorithms/decompose/plan_and_budget/__init__.py +43 -0
  56. agentinputlab/algorithms/decompose/plan_and_budget/cached_soft_budget.py +362 -0
  57. agentinputlab/algorithms/decompose/plan_and_budget/decomposer_component.py +107 -0
  58. agentinputlab/algorithms/decompose/plan_and_budget/full.py +205 -0
  59. agentinputlab/algorithms/decompose/plan_and_budget/hard_cap.py +246 -0
  60. agentinputlab/algorithms/decompose/plan_and_budget/regenerated_soft_budget.py +448 -0
  61. agentinputlab/algorithms/decompose/plan_and_solve/__init__.py +33 -0
  62. agentinputlab/algorithms/decompose/plan_and_solve/plan_component.py +192 -0
  63. agentinputlab/algorithms/decompose/plan_and_solve/ps_prompt.py +127 -0
  64. agentinputlab/algorithms/decompose/self_ask/__init__.py +27 -0
  65. agentinputlab/algorithms/decompose/self_ask/loop.py +362 -0
  66. agentinputlab/algorithms/decompose/self_ask/planner_component.py +117 -0
  67. agentinputlab/algorithms/reformulate/__init__.py +85 -0
  68. agentinputlab/algorithms/reformulate/infocqr/__init__.py +40 -0
  69. agentinputlab/algorithms/reformulate/infocqr/cache_replay.py +362 -0
  70. agentinputlab/algorithms/reformulate/infocqr/external.py +675 -0
  71. agentinputlab/algorithms/reformulate/infocqr/student.py +189 -0
  72. agentinputlab/algorithms/reformulate/query2doc/__init__.py +42 -0
  73. agentinputlab/algorithms/reformulate/query2doc/controlled_live.py +190 -0
  74. agentinputlab/algorithms/reformulate/query2doc/models.py +95 -0
  75. agentinputlab/algorithms/reformulate/query2doc/official_cached.py +335 -0
  76. agentinputlab/algorithms/reformulate/query2doc/querygym_bridge.py +223 -0
  77. agentinputlab/algorithms/reformulate/query2doc/querygym_runtime.py +326 -0
  78. agentinputlab/algorithms/reformulate/reformer/__init__.py +23 -0
  79. agentinputlab/algorithms/reformulate/reformer/external.py +417 -0
  80. agentinputlab/algorithms/reformulate/rrr.py +696 -0
  81. agentinputlab/annotation/__init__.py +5 -0
  82. agentinputlab/annotation/independent.py +428 -0
  83. agentinputlab/annotation/server.py +233 -0
  84. agentinputlab/annotation/static/app.js +121 -0
  85. agentinputlab/annotation/static/index.html +60 -0
  86. agentinputlab/annotation/static/review.css +2 -0
  87. agentinputlab/annotation/static/review.html +10 -0
  88. agentinputlab/annotation/static/review.js +11 -0
  89. agentinputlab/annotation/static/styles.css +55 -0
  90. agentinputlab/annotation/store.py +444 -0
  91. agentinputlab/api.py +368 -0
  92. agentinputlab/artifacts/__init__.py +15 -0
  93. agentinputlab/artifacts/json_file.py +104 -0
  94. agentinputlab/artifacts/manifest.py +98 -0
  95. agentinputlab/artifacts/store.py +157 -0
  96. agentinputlab/backends/__init__.py +98 -0
  97. agentinputlab/backends/embedding/__init__.py +25 -0
  98. agentinputlab/backends/embedding/base.py +145 -0
  99. agentinputlab/backends/embedding/callable.py +75 -0
  100. agentinputlab/backends/embedding/replay.py +129 -0
  101. agentinputlab/backends/llm/__init__.py +31 -0
  102. agentinputlab/backends/llm/base.py +149 -0
  103. agentinputlab/backends/llm/callable.py +108 -0
  104. agentinputlab/backends/llm/decomp_completion.py +88 -0
  105. agentinputlab/backends/llm/final_content_stop.py +72 -0
  106. agentinputlab/backends/llm/gpt_oss_final_channel.py +84 -0
  107. agentinputlab/backends/llm/openai_compatible.py +332 -0
  108. agentinputlab/backends/llm/transformers.py +171 -0
  109. agentinputlab/backends/retrieval/__init__.py +35 -0
  110. agentinputlab/backends/retrieval/base.py +50 -0
  111. agentinputlab/backends/retrieval/dense.py +777 -0
  112. agentinputlab/backends/retrieval/memory.py +119 -0
  113. agentinputlab/backends/retrieval/sqlite_fts.py +155 -0
  114. agentinputlab/backends/user/__init__.py +15 -0
  115. agentinputlab/backends/user/base.py +66 -0
  116. agentinputlab/backends/user/callable.py +54 -0
  117. agentinputlab/backends/user/replay.py +89 -0
  118. agentinputlab/backends/vector.py +38 -0
  119. agentinputlab/benchmarks/__init__.py +246 -0
  120. agentinputlab/benchmarks/acquisition.py +106 -0
  121. agentinputlab/benchmarks/ale.py +540 -0
  122. agentinputlab/benchmarks/appworld_ul.py +669 -0
  123. agentinputlab/benchmarks/assets.py +226 -0
  124. agentinputlab/benchmarks/base.py +68 -0
  125. agentinputlab/benchmarks/beir_archive.py +235 -0
  126. agentinputlab/benchmarks/beir_sample.py +269 -0
  127. agentinputlab/benchmarks/bfcl_native.py +245 -0
  128. agentinputlab/benchmarks/bfcl_v3.py +607 -0
  129. agentinputlab/benchmarks/cache.py +218 -0
  130. agentinputlab/benchmarks/clariq.py +203 -0
  131. agentinputlab/benchmarks/data/registry.json +321 -0
  132. agentinputlab/benchmarks/data/smoke_assets.json +511 -0
  133. agentinputlab/benchmarks/data/statebench_v0_8_1_task_ids.json +475 -0
  134. agentinputlab/benchmarks/data/toolsandbox_165848b_scenario_names.json +1 -0
  135. agentinputlab/benchmarks/execution.py +1111 -0
  136. agentinputlab/benchmarks/interactive.py +257 -0
  137. agentinputlab/benchmarks/ir_query.py +204 -0
  138. agentinputlab/benchmarks/longbench.py +203 -0
  139. agentinputlab/benchmarks/paired_replay.py +479 -0
  140. agentinputlab/benchmarks/preprocess.py +2313 -0
  141. agentinputlab/benchmarks/production.py +236 -0
  142. agentinputlab/benchmarks/qa.py +963 -0
  143. agentinputlab/benchmarks/registry.py +110 -0
  144. agentinputlab/benchmarks/retrieval.py +459 -0
  145. agentinputlab/benchmarks/runtime.py +509 -0
  146. agentinputlab/benchmarks/statebench.py +938 -0
  147. agentinputlab/benchmarks/tau3.py +725 -0
  148. agentinputlab/benchmarks/toolsandbox.py +626 -0
  149. agentinputlab/benchmarks/trec_dl_sample.py +263 -0
  150. agentinputlab/campaign.py +654 -0
  151. agentinputlab/campaign_runner.py +953 -0
  152. agentinputlab/cli/__init__.py +6 -0
  153. agentinputlab/cli/main.py +933 -0
  154. agentinputlab/connectors/__init__.py +46 -0
  155. agentinputlab/connectors/knowledge/__init__.py +4 -0
  156. agentinputlab/connectors/tools/__init__.py +30 -0
  157. agentinputlab/connectors/tools/base.py +57 -0
  158. agentinputlab/connectors/tools/mcp.py +222 -0
  159. agentinputlab/connectors/tools/memory.py +41 -0
  160. agentinputlab/connectors/tools/openapi.py +695 -0
  161. agentinputlab/connectors/tools/schema.py +102 -0
  162. agentinputlab/contracts/__init__.py +91 -0
  163. agentinputlab/contracts/availability.py +24 -0
  164. agentinputlab/contracts/dependency.py +58 -0
  165. agentinputlab/contracts/enums.py +82 -0
  166. agentinputlab/contracts/errors.py +63 -0
  167. agentinputlab/contracts/evidence.py +43 -0
  168. agentinputlab/contracts/intent.py +110 -0
  169. agentinputlab/contracts/lifecycle.py +164 -0
  170. agentinputlab/contracts/patch.py +167 -0
  171. agentinputlab/contracts/serialization.py +93 -0
  172. agentinputlab/contracts/state.py +127 -0
  173. agentinputlab/contracts/trace.py +59 -0
  174. agentinputlab/contracts/worker.py +42 -0
  175. agentinputlab/core/__init__.py +7 -0
  176. agentinputlab/data/__init__.py +32 -0
  177. agentinputlab/data/checksums.py +9 -0
  178. agentinputlab/data/downloader.py +235 -0
  179. agentinputlab/data/http.py +171 -0
  180. agentinputlab/data/index.py +21 -0
  181. agentinputlab/data/overlap.py +28 -0
  182. agentinputlab/data/preprocess.py +32 -0
  183. agentinputlab/data/storage.py +57 -0
  184. agentinputlab/data/trec_sample.py +427 -0
  185. agentinputlab/evaluation/__init__.py +5 -0
  186. agentinputlab/evaluation/outcomes.py +32 -0
  187. agentinputlab/experiments/__init__.py +278 -0
  188. agentinputlab/experiments/analysis.py +981 -0
  189. agentinputlab/experiments/artifacts.py +687 -0
  190. agentinputlab/experiments/automatic_measurement.py +678 -0
  191. agentinputlab/experiments/auxiliary_matrix.py +203 -0
  192. agentinputlab/experiments/bindings.py +430 -0
  193. agentinputlab/experiments/blueprints.py +147 -0
  194. agentinputlab/experiments/builders/__init__.py +45 -0
  195. agentinputlab/experiments/builders/executable_external_payload.py +423 -0
  196. agentinputlab/experiments/builders/external_process_arena.py +1018 -0
  197. agentinputlab/experiments/builders/registry_intervention.py +463 -0
  198. agentinputlab/experiments/cli.py +158 -0
  199. agentinputlab/experiments/controls.py +340 -0
  200. agentinputlab/experiments/driver_guard.py +205 -0
  201. agentinputlab/experiments/drivers/__init__.py +89 -0
  202. agentinputlab/experiments/drivers/external_arena.py +2908 -0
  203. agentinputlab/experiments/drivers/method_process.py +662 -0
  204. agentinputlab/experiments/drivers/parity_process.py +1484 -0
  205. agentinputlab/experiments/drivers/static_production.py +1574 -0
  206. agentinputlab/experiments/executor.py +448 -0
  207. agentinputlab/experiments/loader.py +605 -0
  208. agentinputlab/experiments/measurement.py +675 -0
  209. agentinputlab/experiments/paper_analysis.py +1818 -0
  210. agentinputlab/experiments/parity_identity.py +605 -0
  211. agentinputlab/experiments/plan.py +277 -0
  212. agentinputlab/experiments/production_builders.py +3739 -0
  213. agentinputlab/experiments/readiness.py +432 -0
  214. agentinputlab/experiments/schema.py +514 -0
  215. agentinputlab/experiments/secrets.py +177 -0
  216. agentinputlab/experiments/stateful_analysis.py +1738 -0
  217. agentinputlab/experiments/transfer_analysis.py +1316 -0
  218. agentinputlab/experiments/unseen_analysis.py +1025 -0
  219. agentinputlab/integrations/__init__.py +1 -0
  220. agentinputlab/integrations/action_space/__init__.py +1 -0
  221. agentinputlab/integrations/external_workers/__init__.py +76 -0
  222. agentinputlab/integrations/external_workers/ace.py +431 -0
  223. agentinputlab/integrations/external_workers/base.py +181 -0
  224. agentinputlab/integrations/external_workers/decomp_live.py +249 -0
  225. agentinputlab/integrations/external_workers/decomp_planner.py +30 -0
  226. agentinputlab/integrations/external_workers/infocqr_student.py +280 -0
  227. agentinputlab/integrations/external_workers/ircot.py +576 -0
  228. agentinputlab/integrations/external_workers/llmcompiler_live.py +360 -0
  229. agentinputlab/integrations/external_workers/longllmlingua.py +162 -0
  230. agentinputlab/integrations/external_workers/method.py +314 -0
  231. agentinputlab/integrations/external_workers/process.py +678 -0
  232. agentinputlab/integrations/external_workers/supervisor.py +1071 -0
  233. agentinputlab/integrations/full_architecture/__init__.py +1 -0
  234. agentinputlab/integrations/full_architecture/hyde.py +212 -0
  235. agentinputlab/integrations/full_architecture/plan_and_budget.py +188 -0
  236. agentinputlab/integrations/live_history.py +222 -0
  237. agentinputlab/integrations/prompt_optimization/__init__.py +1 -0
  238. agentinputlab/integrations/training/__init__.py +1 -0
  239. agentinputlab/metrics/__init__.py +50 -0
  240. agentinputlab/metrics/catalog.py +313 -0
  241. agentinputlab/metrics/harm.py +95 -0
  242. agentinputlab/metrics/paired.py +40 -0
  243. agentinputlab/metrics/qa.py +76 -0
  244. agentinputlab/metrics/text.py +33 -0
  245. agentinputlab/metrics/third_party.py +161 -0
  246. agentinputlab/models/__init__.py +4 -0
  247. agentinputlab/observability/__init__.py +56 -0
  248. agentinputlab/observability/budget.py +316 -0
  249. agentinputlab/observability/instrumentation.py +754 -0
  250. agentinputlab/observability/models.py +403 -0
  251. agentinputlab/observability/monitor.py +380 -0
  252. agentinputlab/optimizers/__init__.py +1 -0
  253. agentinputlab/optimizers/clarify/__init__.py +12 -0
  254. agentinputlab/optimizers/context/__init__.py +22 -0
  255. agentinputlab/optimizers/decompose/__init__.py +64 -0
  256. agentinputlab/optimizers/evidence/__init__.py +22 -0
  257. agentinputlab/optimizers/rewrite/__init__.py +30 -0
  258. agentinputlab/parity/__init__.py +5 -0
  259. agentinputlab/parity/base.py +206 -0
  260. agentinputlab/production_campaign.py +763 -0
  261. agentinputlab/py.typed +1 -0
  262. agentinputlab/recipes/__init__.py +333 -0
  263. agentinputlab/recipes/ace.py +418 -0
  264. agentinputlab/recipes/ace_bfcl.py +273 -0
  265. agentinputlab/recipes/ace_common_support.py +169 -0
  266. agentinputlab/recipes/ace_native_plan.py +322 -0
  267. agentinputlab/recipes/ace_online.py +711 -0
  268. agentinputlab/recipes/ace_retrieval.py +330 -0
  269. agentinputlab/recipes/ace_statebench.py +382 -0
  270. agentinputlab/recipes/ace_tau.py +463 -0
  271. agentinputlab/recipes/ace_toolsandbox.py +432 -0
  272. agentinputlab/recipes/acon_guideline.py +22 -0
  273. agentinputlab/recipes/adaptive_rag.py +822 -0
  274. agentinputlab/recipes/llmlingua2_inference.py +146 -0
  275. agentinputlab/recipes/llmlingua2_training.py +796 -0
  276. agentinputlab/recipes/mce.py +1486 -0
  277. agentinputlab/recipes/mce_final.py +335 -0
  278. agentinputlab/recipes/mce_text_fit.py +499 -0
  279. agentinputlab/recipes/mce_text_only.py +298 -0
  280. agentinputlab/recipes/qrag.py +729 -0
  281. agentinputlab/recipes/qrag_inference.py +364 -0
  282. agentinputlab/recipes/r2ag_features.py +236 -0
  283. agentinputlab/recipes/r2ag_inference.py +503 -0
  284. agentinputlab/recipes/r2ag_training.py +948 -0
  285. agentinputlab/recipes/recomp_abstractive.py +592 -0
  286. agentinputlab/recipes/recomp_extractive.py +525 -0
  287. agentinputlab/recipes/reformer.py +17 -0
  288. agentinputlab/recipes/rqrag.py +1516 -0
  289. agentinputlab/recipes/rqrag_common_support.py +193 -0
  290. agentinputlab/recipes/rqrag_completion.py +147 -0
  291. agentinputlab/recipes/skillopt.py +462 -0
  292. agentinputlab/recipes/skillopt_common_support.py +154 -0
  293. agentinputlab/recipes/skillopt_environment.py +165 -0
  294. agentinputlab/recipes/skillopt_environment_cli.py +5 -0
  295. agentinputlab/recipes/skillopt_retrieval.py +188 -0
  296. agentinputlab/recipes/smartsearch.py +897 -0
  297. agentinputlab/recipes/smartsearch_curriculum.py +283 -0
  298. agentinputlab/recipes/smartsearch_grpo_contracts.py +58 -0
  299. agentinputlab/recipes/smartsearch_grpo_data.py +314 -0
  300. agentinputlab/recipes/smartsearch_grpo_meter.py +324 -0
  301. agentinputlab/recipes/smartsearch_grpo_prefix.py +144 -0
  302. agentinputlab/recipes/smartsearch_grpo_rollout.py +119 -0
  303. agentinputlab/recipes/smartsearch_grpo_tools.py +364 -0
  304. agentinputlab/recipes/smartsearch_grpo_training.py +305 -0
  305. agentinputlab/recipes/smartsearch_grpo_transport.py +248 -0
  306. agentinputlab/recipes/smartsearch_grpo_worker.py +364 -0
  307. agentinputlab/recipes/smartsearch_inference.py +524 -0
  308. agentinputlab/recipes/smartsearch_native_data.py +172 -0
  309. agentinputlab/recipes/smartsearch_policy.py +616 -0
  310. agentinputlab/recipes/smartsearch_policy_worker.py +9 -0
  311. agentinputlab/recipes/smartsearch_sampling.py +232 -0
  312. agentinputlab/recipes/smartsearch_teacher.py +203 -0
  313. agentinputlab/recipes/smartsearch_training.py +265 -0
  314. agentinputlab/registry/__init__.py +46 -0
  315. agentinputlab/registry/beta.py +727 -0
  316. agentinputlab/registry/data/algorithms.json +1959 -0
  317. agentinputlab/registry/data/auxiliary_method_profiles.json +34 -0
  318. agentinputlab/registry/loader.py +100 -0
  319. agentinputlab/registry/result_manifest.py +184 -0
  320. agentinputlab/registry/runtime.py +195 -0
  321. agentinputlab/registry/schema.py +449 -0
  322. agentinputlab/runner/__init__.py +30 -0
  323. agentinputlab/runner/balance.py +248 -0
  324. agentinputlab/runner/cache.py +135 -0
  325. agentinputlab/runner/composite.py +130 -0
  326. agentinputlab/runner/engine.py +285 -0
  327. agentinputlab/runner/runtime_guard.py +268 -0
  328. agentinputlab/runner/worker.py +80 -0
  329. agentinputlab/runtime_bootstrap.py +288 -0
  330. agentinputlab/selectors/__init__.py +196 -0
  331. agentinputlab/selectors/advanced.py +1514 -0
  332. agentinputlab/selectors/artifacts.py +1087 -0
  333. agentinputlab/selectors/base.py +112 -0
  334. agentinputlab/selectors/campaigns.py +1893 -0
  335. agentinputlab/selectors/catalog.py +86 -0
  336. agentinputlab/selectors/discovery.py +1860 -0
  337. agentinputlab/selectors/evaluation.py +1348 -0
  338. agentinputlab/selectors/features.py +383 -0
  339. agentinputlab/selectors/learned.py +502 -0
  340. agentinputlab/selectors/no_op.py +28 -0
  341. agentinputlab/selectors/outcome_table.py +475 -0
  342. agentinputlab/selectors/producer.py +853 -0
  343. agentinputlab/selectors/rules.py +109 -0
  344. agentinputlab/selectors/runtime.py +420 -0
  345. agentinputlab/studies/__init__.py +16 -0
  346. agentinputlab/studies/design.py +165 -0
  347. agentinputlab/studies/paired.py +322 -0
  348. agentinputlab/studio/__init__.py +6 -0
  349. agentinputlab/studio/campaigns.py +819 -0
  350. agentinputlab/studio/engine.py +1408 -0
  351. agentinputlab/studio/model_backends.py +425 -0
  352. agentinputlab/studio/prepared.py +410 -0
  353. agentinputlab/studio/server.py +613 -0
  354. agentinputlab/studio/static/app.js +397 -0
  355. agentinputlab/studio/static/campaigns.html +175 -0
  356. agentinputlab/studio/static/campaigns.js +426 -0
  357. agentinputlab/studio/static/index.html +192 -0
  358. agentinputlab/studio/static/result.html +103 -0
  359. agentinputlab/studio/static/result.js +273 -0
  360. agentinputlab/studio/static/styles.css +467 -0
  361. agentinputlab-0.1.0a1.data/data/share/agentinputlab/CITATION.cff +22 -0
  362. agentinputlab-0.1.0a1.dist-info/METADATA +392 -0
  363. agentinputlab-0.1.0a1.dist-info/RECORD +469 -0
  364. agentinputlab-0.1.0a1.dist-info/WHEEL +5 -0
  365. agentinputlab-0.1.0a1.dist-info/entry_points.txt +6 -0
  366. agentinputlab-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  367. agentinputlab-0.1.0a1.dist-info/licenses/NOTICE +31 -0
  368. agentinputlab-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +94 -0
  369. agentinputlab-0.1.0a1.dist-info/top_level.txt +2 -0
  370. workers/README.md +94 -0
  371. workers/ace/README.md +261 -0
  372. workers/ace/__init__.py +1 -0
  373. workers/ace/compatibility_manifest.json +39 -0
  374. workers/ace/driver.py +1682 -0
  375. workers/acon/README.md +215 -0
  376. workers/acon/__init__.py +5 -0
  377. workers/acon/compatibility_manifest.json +93 -0
  378. workers/acon/driver.py +1859 -0
  379. workers/acon/fitted.py +395 -0
  380. workers/acon/guideline.py +470 -0
  381. workers/acon/guideline_source_manifest.json +20 -0
  382. workers/adaptive_rag/README.md +96 -0
  383. workers/adaptive_rag/__init__.py +15 -0
  384. workers/adaptive_rag/compatibility_manifest.json +61 -0
  385. workers/adaptive_rag/driver.py +1470 -0
  386. workers/adaptive_rag/fixture_runtime.py +163 -0
  387. workers/adaptive_rag/runtime.py +252 -0
  388. workers/ask_or_assume/README.md +124 -0
  389. workers/ask_or_assume/__init__.py +1 -0
  390. workers/ask_or_assume/compatibility_manifest.json +36 -0
  391. workers/ask_or_assume/driver.py +1227 -0
  392. workers/decomp/README.md +133 -0
  393. workers/decomp/UPSTREAM_LICENSE.Apache-2.0.txt +201 -0
  394. workers/decomp/__init__.py +5 -0
  395. workers/decomp/compatibility_manifest.json +205 -0
  396. workers/decomp/driver.py +1015 -0
  397. workers/decomp/live.py +137 -0
  398. workers/decomp/runtime.py +231 -0
  399. workers/infocqr/README.md +106 -0
  400. workers/infocqr/__init__.py +1 -0
  401. workers/infocqr/compatibility_manifest.json +84 -0
  402. workers/infocqr/driver.py +1766 -0
  403. workers/ircot/README.md +167 -0
  404. workers/ircot/__init__.py +7 -0
  405. workers/ircot/compatibility_manifest.json +121 -0
  406. workers/ircot/controlled.py +457 -0
  407. workers/ircot/driver.py +1373 -0
  408. workers/ircot/fixture_bindings.json +39 -0
  409. workers/ircot/fixture_config.json +48 -0
  410. workers/ircot/fixture_prompt.txt +2 -0
  411. workers/ircot/native_qa.py +398 -0
  412. workers/ircot/qa_compatibility_manifest.json +57 -0
  413. workers/ircot/runtime.py +97 -0
  414. workers/llmcompiler/README.md +160 -0
  415. workers/llmcompiler/__init__.py +1 -0
  416. workers/llmcompiler/compatibility_manifest.json +45 -0
  417. workers/llmcompiler/driver.py +1514 -0
  418. workers/llmcompiler/export_prompts.py +74 -0
  419. workers/llmcompiler/live.py +174 -0
  420. workers/llmcompiler/patch_manifest.json +147 -0
  421. workers/mce/README.md +198 -0
  422. workers/mce/__init__.py +29 -0
  423. workers/mce/compatibility_manifest.json +115 -0
  424. workers/mce/driver.py +2585 -0
  425. workers/mce/final_eval.py +173 -0
  426. workers/official_arenas/README.md +25 -0
  427. workers/official_arenas/__init__.py +1 -0
  428. workers/official_arenas/capture_records.py +110 -0
  429. workers/official_arenas/driver.py +619 -0
  430. workers/official_arenas/protocol.py +334 -0
  431. workers/official_arenas/providers/__init__.py +1 -0
  432. workers/official_arenas/providers/_live_history.py +228 -0
  433. workers/official_arenas/providers/_message_compat.py +77 -0
  434. workers/official_arenas/providers/appworld_ul.py +196 -0
  435. workers/official_arenas/providers/fixture.py +112 -0
  436. workers/official_arenas/providers/statebench.py +960 -0
  437. workers/official_arenas/providers/tau3.py +1672 -0
  438. workers/official_arenas/providers/tau_retail.py +586 -0
  439. workers/official_arenas/providers/toolsandbox.py +1321 -0
  440. workers/official_arenas/providers/tooltalk.py +699 -0
  441. workers/official_arenas/retail_capture.py +188 -0
  442. workers/official_arenas/retail_driver.py +275 -0
  443. workers/qrag/README.md +172 -0
  444. workers/qrag/__init__.py +1 -0
  445. workers/qrag/compatibility_manifest.json +64 -0
  446. workers/qrag/driver.py +673 -0
  447. workers/r2ag/README.md +154 -0
  448. workers/r2ag/__init__.py +5 -0
  449. workers/r2ag/compatibility_manifest.json +55 -0
  450. workers/r2ag/driver.py +1287 -0
  451. workers/r2ag/feature_schema.json +8 -0
  452. workers/r2ag/runtime.py +589 -0
  453. workers/recomp/README.md +120 -0
  454. workers/recomp/__init__.py +1 -0
  455. workers/recomp/compatibility_manifest.json +43 -0
  456. workers/recomp/driver.py +1653 -0
  457. workers/reformer/README.md +125 -0
  458. workers/reformer/__init__.py +1 -0
  459. workers/reformer/compatibility_manifest.json +68 -0
  460. workers/reformer/driver.py +1459 -0
  461. workers/reformer/induction.py +457 -0
  462. workers/rrr/README.md +126 -0
  463. workers/rrr/__init__.py +1 -0
  464. workers/rrr/compatibility_manifest.json +57 -0
  465. workers/rrr/driver.py +1133 -0
  466. workers/smartsearch/README.md +331 -0
  467. workers/smartsearch/__init__.py +1 -0
  468. workers/smartsearch/compatibility_manifest.json +41 -0
  469. workers/smartsearch/driver.py +1643 -0
@@ -0,0 +1,695 @@
1
+ """OpenAPI and Python-callable tool normalization with injected transport.
2
+
3
+ No HTTP implementation is bundled here. Calling an OpenAPI operation requires
4
+ an explicit transport object, which keeps imports, construction, schema
5
+ inspection, and fingerprinting offline by default.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import dataclasses
11
+ import inspect
12
+ import math
13
+ import re
14
+ import types
15
+ from collections import Counter
16
+ from collections.abc import Callable, Iterable, Mapping, Sequence
17
+ from collections.abc import Mapping as ABCMapping
18
+ from contextlib import suppress
19
+ from dataclasses import dataclass, field
20
+ from enum import Enum
21
+ from typing import (
22
+ Any,
23
+ Literal,
24
+ Protocol,
25
+ Union,
26
+ get_args,
27
+ get_origin,
28
+ get_type_hints,
29
+ runtime_checkable,
30
+ )
31
+ from urllib.parse import quote, urlsplit
32
+
33
+ from agentinputlab.contracts.errors import ValidationError
34
+ from agentinputlab.contracts.serialization import (
35
+ canonical_json,
36
+ content_hash,
37
+ freeze_mapping,
38
+ to_jsonable,
39
+ )
40
+
41
+ from .base import ToolCall, ToolConnector, ToolResult, ToolSpec
42
+
43
+ _HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
44
+ _SAFE_NAME = re.compile(r"[^a-zA-Z0-9_.-]+")
45
+ _SENSITIVE_HEADERS = frozenset(
46
+ {
47
+ "authorization",
48
+ "connection",
49
+ "cookie",
50
+ "host",
51
+ "proxy-authorization",
52
+ "set-cookie",
53
+ "transfer-encoding",
54
+ "x-api-key",
55
+ }
56
+ )
57
+
58
+
59
+ def _json_pointer(document: Mapping[str, Any], ref: str) -> Any:
60
+ if not ref.startswith("#/"):
61
+ raise ValidationError(f"only local OpenAPI references are supported: {ref}")
62
+ value: Any = document
63
+ for raw_segment in ref[2:].split("/"):
64
+ segment = raw_segment.replace("~1", "/").replace("~0", "~")
65
+ if not isinstance(value, Mapping) or segment not in value:
66
+ raise ValidationError(f"unresolved OpenAPI reference: {ref}")
67
+ value = value[segment]
68
+ return value
69
+
70
+
71
+ def _normalize_schema_node(
72
+ value: Any,
73
+ *,
74
+ document: Mapping[str, Any] | None,
75
+ resolving: frozenset[str],
76
+ ) -> Any:
77
+ if isinstance(value, bool) or value is None:
78
+ return value
79
+ if isinstance(value, (list, tuple)):
80
+ return [
81
+ _normalize_schema_node(item, document=document, resolving=resolving) for item in value
82
+ ]
83
+ if not isinstance(value, Mapping):
84
+ return to_jsonable(value)
85
+
86
+ if "$ref" in value:
87
+ ref = value["$ref"]
88
+ if not isinstance(ref, str):
89
+ raise ValidationError("JSON Schema $ref must be a string")
90
+ if document is None or not ref.startswith("#/") or ref in resolving:
91
+ return {"$ref": ref}
92
+ resolved = _json_pointer(document, ref)
93
+ normalized = _normalize_schema_node(
94
+ resolved,
95
+ document=document,
96
+ resolving=resolving | {ref},
97
+ )
98
+ siblings = {key: item for key, item in value.items() if key != "$ref"}
99
+ if not siblings:
100
+ return normalized
101
+ return {
102
+ "allOf": [
103
+ normalized,
104
+ _normalize_schema_node(siblings, document=document, resolving=resolving),
105
+ ]
106
+ }
107
+
108
+ result: dict[str, Any] = {}
109
+ nullable = value.get("nullable") is True
110
+ for key in sorted(value):
111
+ if key in {"nullable", "readOnly", "writeOnly", "xml", "externalDocs"}:
112
+ continue
113
+ item = value[key]
114
+ if key == "properties":
115
+ if not isinstance(item, Mapping):
116
+ raise ValidationError("JSON Schema properties must be an object")
117
+ result[key] = {
118
+ name: _normalize_schema_node(
119
+ item[name],
120
+ document=document,
121
+ resolving=resolving,
122
+ )
123
+ for name in sorted(item)
124
+ }
125
+ elif key == "required":
126
+ if not isinstance(item, (list, tuple)) or not all(
127
+ isinstance(name, str) for name in item
128
+ ):
129
+ raise ValidationError("JSON Schema required must be an array of strings")
130
+ result[key] = sorted(set(item))
131
+ elif key == "enum":
132
+ if not isinstance(item, (list, tuple)):
133
+ raise ValidationError("JSON Schema enum must be an array")
134
+ result[key] = sorted(
135
+ (to_jsonable(member) for member in item),
136
+ key=canonical_json,
137
+ )
138
+ else:
139
+ result[key] = _normalize_schema_node(
140
+ item,
141
+ document=document,
142
+ resolving=resolving,
143
+ )
144
+
145
+ if "properties" in result and "type" not in result:
146
+ result["type"] = "object"
147
+ if nullable:
148
+ existing = result.get("type")
149
+ if isinstance(existing, str):
150
+ result["type"] = [existing, "null"]
151
+ elif isinstance(existing, list):
152
+ result["type"] = sorted(set(existing) | {"null"})
153
+ elif "anyOf" in result:
154
+ result["anyOf"] = [*result["anyOf"], {"type": "null"}]
155
+ else:
156
+ result["anyOf"] = [{"type": "null"}, {}]
157
+ return result
158
+
159
+
160
+ def normalize_json_schema(
161
+ schema: Mapping[str, Any] | None,
162
+ *,
163
+ document: Mapping[str, Any] | None = None,
164
+ ) -> Mapping[str, Any]:
165
+ """Return a stable, local-ref-resolved JSON Schema mapping."""
166
+
167
+ if schema is None:
168
+ return {"type": "object", "properties": {}, "additionalProperties": False}
169
+ if not isinstance(schema, Mapping):
170
+ raise ValidationError("tool input schema must be an object")
171
+ normalized = _normalize_schema_node(schema, document=document, resolving=frozenset())
172
+ if not isinstance(normalized, Mapping):
173
+ raise ValidationError("normalized tool input schema must be an object")
174
+ if not normalized:
175
+ return {"type": "object", "properties": {}, "additionalProperties": False}
176
+ return normalized
177
+
178
+
179
+ def validate_json_value(value: Any, schema: Mapping[str, Any], *, path: str = "value") -> None:
180
+ """Validate the conservative JSON-Schema subset exposed by connectors."""
181
+
182
+ if "allOf" in schema:
183
+ variants = schema["allOf"]
184
+ if not isinstance(variants, (list, tuple)) or not all(
185
+ isinstance(variant, Mapping) for variant in variants
186
+ ):
187
+ raise ValidationError(f"{path}: allOf must be an array of schemas")
188
+ for variant in variants:
189
+ validate_json_value(value, variant, path=path)
190
+ if "oneOf" in schema:
191
+ variants = schema["oneOf"]
192
+ if not isinstance(variants, (list, tuple)):
193
+ raise ValidationError(f"{path}: oneOf must be an array")
194
+ matches = 0
195
+ for variant in variants:
196
+ if not isinstance(variant, Mapping):
197
+ continue
198
+ try:
199
+ validate_json_value(value, variant, path=path)
200
+ matches += 1
201
+ except ValidationError:
202
+ continue
203
+ if matches != 1:
204
+ raise ValidationError(f"{path}: value must match exactly one allowed schema")
205
+ if "anyOf" in schema:
206
+ variants = schema["anyOf"]
207
+ if not isinstance(variants, (list, tuple)):
208
+ raise ValidationError(f"{path}: anyOf must be an array")
209
+ for variant in variants:
210
+ if not isinstance(variant, Mapping):
211
+ continue
212
+ try:
213
+ validate_json_value(value, variant, path=path)
214
+ break
215
+ except ValidationError:
216
+ continue
217
+ else:
218
+ raise ValidationError(f"{path}: value does not match any allowed schema")
219
+ if "enum" in schema and value not in schema["enum"]:
220
+ raise ValidationError(f"{path}: value is not in the allowed enum")
221
+ expected = schema.get("type")
222
+ expected_types = {expected} if isinstance(expected, str) else set(expected or ())
223
+ if expected_types and not _matches_json_type(value, expected_types):
224
+ raise ValidationError(f"{path}: expected JSON type {'|'.join(sorted(expected_types))}")
225
+ if isinstance(value, Mapping):
226
+ properties = schema.get("properties", {})
227
+ if isinstance(properties, Mapping):
228
+ required = set(schema.get("required", ()))
229
+ missing = sorted(required - set(value))
230
+ if missing:
231
+ raise ValidationError(f"{path}: missing required keys {', '.join(missing)}")
232
+ if schema.get("additionalProperties") is False:
233
+ unknown = sorted(set(value) - set(properties))
234
+ if unknown:
235
+ raise ValidationError(f"{path}: unknown keys {', '.join(unknown)}")
236
+ for key, item in value.items():
237
+ child = properties.get(key)
238
+ if isinstance(child, Mapping):
239
+ validate_json_value(item, child, path=f"{path}.{key}")
240
+ elif key not in properties and isinstance(
241
+ schema.get("additionalProperties"), Mapping
242
+ ):
243
+ validate_json_value(
244
+ item,
245
+ schema["additionalProperties"],
246
+ path=f"{path}.{key}",
247
+ )
248
+ if isinstance(value, (list, tuple)):
249
+ item_schema = schema.get("items")
250
+ if isinstance(item_schema, Mapping):
251
+ for index, item in enumerate(value):
252
+ validate_json_value(item, item_schema, path=f"{path}[{index}]")
253
+ if "minItems" in schema and len(value) < int(schema["minItems"]):
254
+ raise ValidationError(f"{path}: array is shorter than minItems")
255
+ if "maxItems" in schema and len(value) > int(schema["maxItems"]):
256
+ raise ValidationError(f"{path}: array is longer than maxItems")
257
+ if isinstance(value, str):
258
+ if "minLength" in schema and len(value) < int(schema["minLength"]):
259
+ raise ValidationError(f"{path}: string is shorter than minLength")
260
+ if "maxLength" in schema and len(value) > int(schema["maxLength"]):
261
+ raise ValidationError(f"{path}: string is longer than maxLength")
262
+ if "pattern" in schema and re.search(str(schema["pattern"]), value) is None:
263
+ raise ValidationError(f"{path}: string does not match pattern")
264
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
265
+ if not math.isfinite(float(value)):
266
+ raise ValidationError(f"{path}: number must be finite")
267
+ if "minimum" in schema and value < schema["minimum"]:
268
+ raise ValidationError(f"{path}: number is below minimum")
269
+ if "maximum" in schema and value > schema["maximum"]:
270
+ raise ValidationError(f"{path}: number is above maximum")
271
+
272
+
273
+ def _matches_json_type(value: Any, expected: set[str]) -> bool:
274
+ return any(
275
+ (
276
+ (kind == "null" and value is None)
277
+ or (kind == "boolean" and isinstance(value, bool))
278
+ or (kind == "integer" and isinstance(value, int) and not isinstance(value, bool))
279
+ or (
280
+ kind == "number" and isinstance(value, (int, float)) and not isinstance(value, bool)
281
+ )
282
+ or (kind == "string" and isinstance(value, str))
283
+ or (kind == "array" and isinstance(value, (list, tuple)))
284
+ or (kind == "object" and isinstance(value, Mapping))
285
+ )
286
+ for kind in expected
287
+ )
288
+
289
+
290
+ def _annotation_schema(annotation: Any) -> Mapping[str, Any]:
291
+ if annotation in {Any, inspect.Signature.empty}:
292
+ return {}
293
+ if annotation is None or annotation is type(None):
294
+ return {"type": "null"}
295
+ if annotation is str:
296
+ return {"type": "string"}
297
+ if annotation is bool:
298
+ return {"type": "boolean"}
299
+ if annotation is int:
300
+ return {"type": "integer"}
301
+ if annotation is float:
302
+ return {"type": "number"}
303
+
304
+ origin = get_origin(annotation)
305
+ arguments = get_args(annotation)
306
+ if origin is Literal:
307
+ values = [to_jsonable(item) for item in arguments]
308
+ return {"enum": sorted(values, key=canonical_json)}
309
+ if origin in {Union, types.UnionType}:
310
+ variants = [_annotation_schema(item) for item in arguments]
311
+ return {"anyOf": variants}
312
+ if origin in {list, tuple, set, frozenset, Sequence, Iterable}:
313
+ item_schema = _annotation_schema(arguments[0]) if arguments else {}
314
+ return {"type": "array", "items": item_schema}
315
+ if origin in {dict, Mapping, ABCMapping}:
316
+ value_schema = _annotation_schema(arguments[1]) if len(arguments) > 1 else {}
317
+ return {"type": "object", "additionalProperties": value_schema}
318
+ if inspect.isclass(annotation) and issubclass(annotation, Enum):
319
+ values = [to_jsonable(item.value) for item in annotation]
320
+ return {"enum": sorted(values, key=canonical_json)}
321
+ if inspect.isclass(annotation) and dataclasses.is_dataclass(annotation):
322
+ properties: dict[str, Any] = {}
323
+ required: list[str] = []
324
+ for item in dataclasses.fields(annotation):
325
+ properties[item.name] = _annotation_schema(item.type)
326
+ if item.default is dataclasses.MISSING and item.default_factory is dataclasses.MISSING:
327
+ required.append(item.name)
328
+ result: dict[str, Any] = {
329
+ "type": "object",
330
+ "properties": properties,
331
+ "additionalProperties": False,
332
+ }
333
+ if required:
334
+ result["required"] = sorted(required)
335
+ return result
336
+ return {"type": "string", "x-python-type": getattr(annotation, "__name__", str(annotation))}
337
+
338
+
339
+ def python_tool_spec(
340
+ function: Callable[..., Any],
341
+ *,
342
+ name: str | None = None,
343
+ description: str | None = None,
344
+ read_only: bool = False,
345
+ network: bool = False,
346
+ ) -> ToolSpec:
347
+ """Derive a deterministic tool schema from a typed Python callable."""
348
+
349
+ signature = inspect.signature(function)
350
+ try:
351
+ hints = get_type_hints(function)
352
+ except (NameError, TypeError):
353
+ hints = dict(getattr(function, "__annotations__", {}))
354
+
355
+ properties: dict[str, Any] = {}
356
+ required: list[str] = []
357
+ for parameter_name, parameter in signature.parameters.items():
358
+ if parameter.kind in {
359
+ inspect.Parameter.VAR_POSITIONAL,
360
+ inspect.Parameter.VAR_KEYWORD,
361
+ }:
362
+ raise ValidationError("variadic Python tools require an explicit ToolSpec")
363
+ schema = dict(_annotation_schema(hints.get(parameter_name, parameter.annotation)))
364
+ if parameter.default is inspect.Signature.empty:
365
+ required.append(parameter_name)
366
+ else:
367
+ with suppress(TypeError):
368
+ schema["default"] = to_jsonable(parameter.default)
369
+ properties[parameter_name] = schema
370
+ input_schema: dict[str, Any] = {
371
+ "type": "object",
372
+ "properties": properties,
373
+ "additionalProperties": False,
374
+ }
375
+ if required:
376
+ input_schema["required"] = sorted(required)
377
+ tool_name = name or function.__name__
378
+ if not tool_name or not isinstance(tool_name, str):
379
+ raise ValidationError("Python tool name must be a non-empty string")
380
+ return ToolSpec(
381
+ name=tool_name,
382
+ description=description if description is not None else (inspect.getdoc(function) or ""),
383
+ input_schema=normalize_json_schema(input_schema),
384
+ read_only=read_only,
385
+ network=network,
386
+ )
387
+
388
+
389
+ @runtime_checkable
390
+ class OpenAPITransport(Protocol):
391
+ """Injected request executor; implementations may be HTTP, mock, or replay."""
392
+
393
+ def request(
394
+ self,
395
+ method: str,
396
+ path: str,
397
+ *,
398
+ query: Mapping[str, Any],
399
+ headers: Mapping[str, str],
400
+ body: Any,
401
+ ) -> Any: ...
402
+
403
+
404
+ @dataclass(frozen=True, slots=True)
405
+ class _Operation:
406
+ tool: ToolSpec
407
+ method: str
408
+ path: str
409
+ argument_locations: Mapping[str, tuple[str, str]]
410
+ body_argument: str | None
411
+
412
+ def fingerprint(self) -> Mapping[str, Any]:
413
+ return {
414
+ "tool": self.tool,
415
+ "method": self.method,
416
+ "path": self.path,
417
+ "argument_locations": self.argument_locations,
418
+ "body_argument": self.body_argument,
419
+ }
420
+
421
+
422
+ def _resolve_object(value: Any, document: Mapping[str, Any]) -> Mapping[str, Any]:
423
+ if not isinstance(value, Mapping):
424
+ raise ValidationError("OpenAPI component must be an object")
425
+ if "$ref" in value:
426
+ ref = value["$ref"]
427
+ if not isinstance(ref, str):
428
+ raise ValidationError("OpenAPI $ref must be a string")
429
+ resolved = _json_pointer(document, ref)
430
+ if not isinstance(resolved, Mapping):
431
+ raise ValidationError(f"OpenAPI reference does not resolve to an object: {ref}")
432
+ return resolved
433
+ return value
434
+
435
+
436
+ def _operation_name(method: str, path: str, operation: Mapping[str, Any]) -> str:
437
+ operation_id = operation.get("operationId")
438
+ if isinstance(operation_id, str) and operation_id.strip():
439
+ return _SAFE_NAME.sub("_", operation_id.strip()).strip("_")
440
+ stem = _SAFE_NAME.sub("_", path.strip("/")).strip("_") or "root"
441
+ return f"{method.lower()}_{stem}"
442
+
443
+
444
+ def _operation_description(operation: Mapping[str, Any]) -> str:
445
+ summary = operation.get("summary")
446
+ description = operation.get("description")
447
+ parts = [
448
+ item.strip() for item in (summary, description) if isinstance(item, str) and item.strip()
449
+ ]
450
+ return "\n\n".join(parts)
451
+
452
+
453
+ @dataclass(slots=True)
454
+ class OpenAPIToolConnector(ToolConnector):
455
+ """Expose selected OpenAPI operations through the common tool contract."""
456
+
457
+ document: Mapping[str, Any]
458
+ transport: OpenAPITransport
459
+ connector_id: str = "openapi"
460
+ base_url_identity: str = ""
461
+ allow_operations: tuple[str, ...] = ()
462
+ allow_mutating: bool = False
463
+ allow_network_calls: bool = False
464
+ network: bool = True
465
+ _operations: Mapping[str, _Operation] = field(init=False, repr=False)
466
+
467
+ def __post_init__(self) -> None:
468
+ self.allow_operations = tuple(self.allow_operations)
469
+ self._operations = self._build_operations()
470
+ self.document = freeze_mapping(self.document)
471
+
472
+ def _build_operations(self) -> Mapping[str, _Operation]:
473
+ paths = self.document.get("paths", {})
474
+ if not isinstance(paths, Mapping):
475
+ raise ValidationError("OpenAPI document.paths must be an object")
476
+ operations: dict[str, _Operation] = {}
477
+ for path in sorted(paths):
478
+ if (
479
+ not isinstance(path, str)
480
+ or not path.startswith("/")
481
+ or path.startswith("//")
482
+ or "\\" in path
483
+ or urlsplit(path).scheme
484
+ or urlsplit(path).netloc
485
+ or urlsplit(path).query
486
+ or urlsplit(path).fragment
487
+ ):
488
+ raise ValidationError(f"OpenAPI paths must be root-relative: {path!r}")
489
+ path_item = _resolve_object(paths[path], self.document)
490
+ inherited = path_item.get("parameters", [])
491
+ if not isinstance(inherited, list):
492
+ raise ValidationError(f"OpenAPI path parameters must be an array: {path}")
493
+ for method in sorted(key for key in path_item if key.lower() in _HTTP_METHODS):
494
+ raw_operation = _resolve_object(path_item[method], self.document)
495
+ tool_name = _operation_name(method, path, raw_operation)
496
+ if "*" not in self.allow_operations and tool_name not in self.allow_operations:
497
+ continue
498
+ if tool_name in operations:
499
+ raise ValidationError(
500
+ f"duplicate normalized OpenAPI operation name: {tool_name}"
501
+ )
502
+ own_parameters = raw_operation.get("parameters", [])
503
+ if not isinstance(own_parameters, list):
504
+ raise ValidationError(
505
+ f"OpenAPI operation parameters must be an array: {tool_name}"
506
+ )
507
+ # OpenAPI operation parameters override path-level parameters
508
+ # with the same ``(name, in)`` identity.
509
+ merged_parameters: dict[tuple[Any, Any], Mapping[str, Any]] = {}
510
+ for item in [*inherited, *own_parameters]:
511
+ parameter = _resolve_object(item, self.document)
512
+ merged_parameters[(parameter.get("name"), parameter.get("in"))] = parameter
513
+ parameters = list(merged_parameters.values())
514
+ name_counts = Counter(
515
+ item.get("name") for item in parameters if isinstance(item.get("name"), str)
516
+ )
517
+ properties: dict[str, Any] = {}
518
+ required: list[str] = []
519
+ locations: dict[str, tuple[str, str]] = {}
520
+ for parameter in parameters:
521
+ original_name = parameter.get("name")
522
+ location = parameter.get("in")
523
+ if not isinstance(original_name, str) or location not in {
524
+ "path",
525
+ "query",
526
+ "header",
527
+ "cookie",
528
+ }:
529
+ raise ValidationError(f"invalid OpenAPI parameter in operation {tool_name}")
530
+ if location == "cookie" or (
531
+ location == "header" and original_name.lower() in _SENSITIVE_HEADERS
532
+ ):
533
+ raise ValidationError(
534
+ f"sensitive OpenAPI parameter is not model-exposed: {original_name}"
535
+ )
536
+ argument_name = (
537
+ original_name
538
+ if name_counts[original_name] == 1
539
+ else f"{location}__{original_name}"
540
+ )
541
+ properties[argument_name] = normalize_json_schema(
542
+ parameter.get("schema", {}),
543
+ document=self.document,
544
+ )
545
+ locations[argument_name] = (location, original_name)
546
+ if parameter.get("required") is True or location == "path":
547
+ required.append(argument_name)
548
+
549
+ body_argument: str | None = None
550
+ request_body = raw_operation.get("requestBody")
551
+ if request_body is not None:
552
+ request_body = _resolve_object(request_body, self.document)
553
+ content = request_body.get("content", {})
554
+ if not isinstance(content, Mapping) or not content:
555
+ raise ValidationError(f"requestBody has no content schemas: {tool_name}")
556
+ media_type = (
557
+ "application/json" if "application/json" in content else sorted(content)[0]
558
+ )
559
+ media = _resolve_object(content[media_type], self.document)
560
+ body_argument = "body" if "body" not in properties else "request_body"
561
+ properties[body_argument] = normalize_json_schema(
562
+ media.get("schema", {}),
563
+ document=self.document,
564
+ )
565
+ if request_body.get("required") is True:
566
+ required.append(body_argument)
567
+
568
+ input_schema: dict[str, Any] = {
569
+ "type": "object",
570
+ "properties": properties,
571
+ "additionalProperties": False,
572
+ }
573
+ if required:
574
+ input_schema["required"] = sorted(set(required))
575
+ tool = ToolSpec(
576
+ name=tool_name,
577
+ description=_operation_description(raw_operation),
578
+ input_schema=normalize_json_schema(input_schema),
579
+ read_only=method.lower() in {"get", "head", "options"},
580
+ network=self.network,
581
+ )
582
+ operations[tool_name] = _Operation(
583
+ tool=tool,
584
+ method=method.upper(),
585
+ path=path,
586
+ argument_locations=locations,
587
+ body_argument=body_argument,
588
+ )
589
+ return operations
590
+
591
+ def list_tools(self) -> tuple[ToolSpec, ...]:
592
+ return tuple(self._operations[name].tool for name in sorted(self._operations))
593
+
594
+ def call(self, request: ToolCall) -> ToolResult:
595
+ operation = self._operations.get(request.name)
596
+ if operation is None:
597
+ return ToolResult(call_id=request.call_id, error=f"unknown tool: {request.name}")
598
+ if not operation.tool.read_only and not self.allow_mutating:
599
+ return ToolResult(
600
+ call_id=request.call_id,
601
+ error=f"mutating tool is disabled by policy: {request.name}",
602
+ )
603
+ if operation.tool.network and not self.allow_network_calls:
604
+ return ToolResult(
605
+ call_id=request.call_id,
606
+ error=f"network tool is disabled by policy: {request.name}",
607
+ )
608
+ arguments = dict(request.arguments)
609
+ properties = operation.tool.input_schema.get("properties", {})
610
+ required = operation.tool.input_schema.get("required", [])
611
+ unknown = sorted(set(arguments) - set(properties))
612
+ missing = sorted(set(required) - set(arguments))
613
+ if unknown:
614
+ return ToolResult(
615
+ call_id=request.call_id,
616
+ error=f"unknown arguments for {request.name}: {', '.join(unknown)}",
617
+ )
618
+ if missing:
619
+ return ToolResult(
620
+ call_id=request.call_id,
621
+ error=f"missing required arguments for {request.name}: {', '.join(missing)}",
622
+ )
623
+ try:
624
+ for name, value in arguments.items():
625
+ schema = properties.get(name, {})
626
+ if isinstance(schema, Mapping):
627
+ validate_json_value(value, schema, path=name)
628
+ except ValidationError as exc:
629
+ return ToolResult(call_id=request.call_id, error=str(exc))
630
+
631
+ path = operation.path
632
+ query: dict[str, Any] = {}
633
+ headers: dict[str, str] = {}
634
+ cookies: dict[str, str] = {}
635
+ for argument_name, (location, original_name) in operation.argument_locations.items():
636
+ if argument_name not in arguments:
637
+ continue
638
+ value = arguments[argument_name]
639
+ if location == "path":
640
+ path = path.replace("{" + original_name + "}", quote(str(value), safe=""))
641
+ elif location == "query":
642
+ query[original_name] = value
643
+ elif location == "header":
644
+ headers[original_name] = str(value)
645
+ elif location == "cookie":
646
+ cookies[original_name] = str(value)
647
+ if cookies:
648
+ headers["Cookie"] = "; ".join(f"{name}={cookies[name]}" for name in sorted(cookies))
649
+ body = arguments.get(operation.body_argument) if operation.body_argument else None
650
+ try:
651
+ output = self.transport.request(
652
+ operation.method,
653
+ path,
654
+ query=query,
655
+ headers=headers,
656
+ body=body,
657
+ )
658
+ return ToolResult(
659
+ call_id=request.call_id,
660
+ output=output,
661
+ metadata={"connector": "openapi", "operation": request.name},
662
+ )
663
+ except Exception as exc:
664
+ return ToolResult(
665
+ call_id=request.call_id,
666
+ error=f"{type(exc).__name__}: {exc}",
667
+ metadata={"connector": "openapi", "operation": request.name},
668
+ )
669
+
670
+ def fingerprint(self) -> Mapping[str, Any]:
671
+ operation_fingerprints = [
672
+ self._operations[name].fingerprint() for name in sorted(self._operations)
673
+ ]
674
+ return {
675
+ "connector_id": self.connector_id,
676
+ "protocol": "openapi",
677
+ "implementation": "agentinputlab.openapi.v1",
678
+ "document_hash": content_hash(self.document),
679
+ "operation_schema_hash": content_hash(operation_fingerprints),
680
+ "operation_count": len(operation_fingerprints),
681
+ "base_url_identity_hash": content_hash(self.base_url_identity),
682
+ "network": self.network,
683
+ "allow_operations": sorted(self.allow_operations),
684
+ "allow_mutating": self.allow_mutating,
685
+ "allow_network_calls": self.allow_network_calls,
686
+ }
687
+
688
+
689
+ __all__ = [
690
+ "OpenAPIToolConnector",
691
+ "OpenAPITransport",
692
+ "normalize_json_schema",
693
+ "python_tool_spec",
694
+ "validate_json_value",
695
+ ]