agent-learning-kit 0.1.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 (642) hide show
  1. agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
  2. agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
  3. agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
  4. agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
  5. agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
  6. agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
  7. fi/__init__.py +5 -0
  8. fi/alk/__init__.py +57 -0
  9. fi/alk/_facade.py +31 -0
  10. fi/alk/_module_alias.py +68 -0
  11. fi/alk/_paths.py +14 -0
  12. fi/alk/_schema.py +522 -0
  13. fi/alk/actions.py +727 -0
  14. fi/alk/bench/__init__.py +517 -0
  15. fi/alk/bench/_codeexec.py +213 -0
  16. fi/alk/bench/_coding.py +215 -0
  17. fi/alk/bench/_docker.py +237 -0
  18. fi/alk/bench/_grader.py +286 -0
  19. fi/alk/bench/_pull.py +212 -0
  20. fi/alk/bench/_voice.py +147 -0
  21. fi/alk/capabilities.py +627 -0
  22. fi/alk/cli.py +6396 -0
  23. fi/alk/config.py +130 -0
  24. fi/alk/cua_loop.py +562 -0
  25. fi/alk/evals.py +2351 -0
  26. fi/alk/extensions.py +163 -0
  27. fi/alk/harness/ARCHITECTURE.md +231 -0
  28. fi/alk/harness/DESIGN.md +246 -0
  29. fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
  30. fi/alk/harness/HOW-IT-WORKS.md +297 -0
  31. fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
  32. fi/alk/harness/README.md +417 -0
  33. fi/alk/harness/__init__.py +77 -0
  34. fi/alk/harness/__main__.py +3 -0
  35. fi/alk/harness/amend.py +312 -0
  36. fi/alk/harness/artifacts.py +319 -0
  37. fi/alk/harness/authoring_entrypoint.py +189 -0
  38. fi/alk/harness/authoring_runtime_validation.py +267 -0
  39. fi/alk/harness/backends/README.md +43 -0
  40. fi/alk/harness/backends/__init__.py +122 -0
  41. fi/alk/harness/backends/base.py +241 -0
  42. fi/alk/harness/backends/claude.py +211 -0
  43. fi/alk/harness/backends/files.py +182 -0
  44. fi/alk/harness/backends/vertex_gemini.py +457 -0
  45. fi/alk/harness/background_noise.py +95 -0
  46. fi/alk/harness/build.py +385 -0
  47. fi/alk/harness/bundle.py +593 -0
  48. fi/alk/harness/bundle_author_v2.py +1831 -0
  49. fi/alk/harness/bundle_v2.py +719 -0
  50. fi/alk/harness/call_runner.py +1440 -0
  51. fi/alk/harness/callback_http_adapter.py +111 -0
  52. fi/alk/harness/catalogue.py +287 -0
  53. fi/alk/harness/chat.py +428 -0
  54. fi/alk/harness/chat_call_runner.py +506 -0
  55. fi/alk/harness/checks.py +136 -0
  56. fi/alk/harness/cli.py +1354 -0
  57. fi/alk/harness/config.py +338 -0
  58. fi/alk/harness/contract.py +718 -0
  59. fi/alk/harness/credentials.py +674 -0
  60. fi/alk/harness/data/persona_vocabulary.json +111 -0
  61. fi/alk/harness/environment.py +99 -0
  62. fi/alk/harness/environment_plan.py +168 -0
  63. fi/alk/harness/events.py +125 -0
  64. fi/alk/harness/executor.py +304 -0
  65. fi/alk/harness/folder.py +234 -0
  66. fi/alk/harness/generated_runtime.py +815 -0
  67. fi/alk/harness/github.py +72 -0
  68. fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
  69. fi/alk/harness/hosted_entrypoint.py +2402 -0
  70. fi/alk/harness/hosted_scheduler.py +2218 -0
  71. fi/alk/harness/job.py +426 -0
  72. fi/alk/harness/judge.py +184 -0
  73. fi/alk/harness/livekit_source.py +50 -0
  74. fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
  75. fi/alk/harness/observability.py +208 -0
  76. fi/alk/harness/outbound.py +3252 -0
  77. fi/alk/harness/packaging.py +515 -0
  78. fi/alk/harness/persona_guides.py +157 -0
  79. fi/alk/harness/platform.py +692 -0
  80. fi/alk/harness/process_preflight.py +764 -0
  81. fi/alk/harness/process_runtime.py +5670 -0
  82. fi/alk/harness/prove.py +425 -0
  83. fi/alk/harness/provider_import.py +703 -0
  84. fi/alk/harness/provider_lifecycle.py +392 -0
  85. fi/alk/harness/provision.py +2896 -0
  86. fi/alk/harness/reception.py +147 -0
  87. fi/alk/harness/retell_chat_call_runner.py +373 -0
  88. fi/alk/harness/run/__init__.py +296 -0
  89. fi/alk/harness/run/alk.py +184 -0
  90. fi/alk/harness/run/call.py +162 -0
  91. fi/alk/harness/run/conversation.py +264 -0
  92. fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
  93. fi/alk/harness/run/evidence.py +195 -0
  94. fi/alk/harness/run/grade.py +598 -0
  95. fi/alk/harness/run/live.py +297 -0
  96. fi/alk/harness/run/models.py +56 -0
  97. fi/alk/harness/run/platform_evals.py +227 -0
  98. fi/alk/harness/run/sdk_voice.py +130 -0
  99. fi/alk/harness/run/simulation.py +1209 -0
  100. fi/alk/harness/run/stage.py +91 -0
  101. fi/alk/harness/run/targets.py +508 -0
  102. fi/alk/harness/run/tools.py +601 -0
  103. fi/alk/harness/run/voice.py +340 -0
  104. fi/alk/harness/runtime.py +172 -0
  105. fi/alk/harness/sandbox_server.py +2011 -0
  106. fi/alk/harness/sandbox_worker.py +44 -0
  107. fi/alk/harness/scenario.py +1048 -0
  108. fi/alk/harness/scenario_source.py +879 -0
  109. fi/alk/harness/scenario_tools.py +1143 -0
  110. fi/alk/harness/scenarios.py +915 -0
  111. fi/alk/harness/secrets.py +168 -0
  112. fi/alk/harness/service_catalog.py +97 -0
  113. fi/alk/harness/session.py +391 -0
  114. fi/alk/harness/sessions.py +372 -0
  115. fi/alk/harness/simulator.py +76 -0
  116. fi/alk/harness/simulator_voice.py +928 -0
  117. fi/alk/harness/skills/build-environment/SKILL.md +538 -0
  118. fi/alk/harness/skills/harness.md +131 -0
  119. fi/alk/harness/skills/kinds/chat.md +48 -0
  120. fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
  121. fi/alk/harness/skills/kinds/voice.md +59 -0
  122. fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
  123. fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
  124. fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
  125. fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
  126. fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
  127. fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
  128. fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
  129. fi/alk/harness/source_data_invariants.py +444 -0
  130. fi/alk/harness/source_tool_evidence.py +79 -0
  131. fi/alk/harness/sources.py +253 -0
  132. fi/alk/harness/spend.py +140 -0
  133. fi/alk/harness/tool_trace_proxy.py +104 -0
  134. fi/alk/harness/tools.py +1018 -0
  135. fi/alk/harness/understand.py +169 -0
  136. fi/alk/harness/voicemail_audio.py +74 -0
  137. fi/alk/harness/world/__init__.py +33 -0
  138. fi/alk/harness/world/errors.py +68 -0
  139. fi/alk/harness/world/expectations.py +91 -0
  140. fi/alk/harness/world/handle.py +538 -0
  141. fi/alk/harness/world/kinds.py +196 -0
  142. fi/alk/harness/world/mutate.py +186 -0
  143. fi/alk/harness/world/probe.py +413 -0
  144. fi/alk/harness/world/provision.py +511 -0
  145. fi/alk/harness/world/provisioned.py +191 -0
  146. fi/alk/harness/world/runtime.py +616 -0
  147. fi/alk/harness/world/snapshot.py +288 -0
  148. fi/alk/harness/world/stores/__init__.py +305 -0
  149. fi/alk/harness/world/stores/container.py +215 -0
  150. fi/alk/harness/world/stores/inprocess.py +346 -0
  151. fi/alk/harness/world/stores/postgres.py +481 -0
  152. fi/alk/harness/world/stores/prove.py +202 -0
  153. fi/alk/harness/world/stores/sqlite.py +245 -0
  154. fi/alk/harness/world/stores/written.py +182 -0
  155. fi/alk/harness/world/tools.py +1516 -0
  156. fi/alk/harness/world/workspace.py +144 -0
  157. fi/alk/image_loop.py +453 -0
  158. fi/alk/image_perturb.py +241 -0
  159. fi/alk/improve.py +274 -0
  160. fi/alk/live/__init__.py +154 -0
  161. fi/alk/live/_attribution.py +184 -0
  162. fi/alk/live/_capture.py +264 -0
  163. fi/alk/live/_codec.py +391 -0
  164. fi/alk/live/_contract.py +134 -0
  165. fi/alk/live/_loopback.py +316 -0
  166. fi/alk/live/_perturb.py +449 -0
  167. fi/alk/live/_runner.py +386 -0
  168. fi/alk/live/_stats.py +561 -0
  169. fi/alk/live/_transcript.py +240 -0
  170. fi/alk/live/_workers/__init__.py +9 -0
  171. fi/alk/live/_workers/a2a_worker.py +316 -0
  172. fi/alk/live/_workers/langgraph_worker.py +217 -0
  173. fi/alk/live/_workers/livekit_worker.py +207 -0
  174. fi/alk/live/_workers/mcp_loopback_server.py +46 -0
  175. fi/alk/live/_workers/mcp_worker.py +158 -0
  176. fi/alk/live/_workers/pipecat_worker.py +189 -0
  177. fi/alk/live/a2a_lane.py +138 -0
  178. fi/alk/live/langgraph_lane.py +339 -0
  179. fi/alk/live/livekit_lane.py +376 -0
  180. fi/alk/live/mcp_lane.py +172 -0
  181. fi/alk/live/pipecat_lane.py +341 -0
  182. fi/alk/live/voice_redteam.py +494 -0
  183. fi/alk/loss.py +306 -0
  184. fi/alk/optimize.py +36260 -0
  185. fi/alk/practice/__init__.py +51 -0
  186. fi/alk/practice/_assess.py +103 -0
  187. fi/alk/practice/_budget.py +81 -0
  188. fi/alk/practice/_calibrate.py +69 -0
  189. fi/alk/practice/_capstone.py +86 -0
  190. fi/alk/practice/_contract.py +91 -0
  191. fi/alk/practice/_diagnose.py +79 -0
  192. fi/alk/practice/_drill.py +196 -0
  193. fi/alk/practice/_experiment.py +720 -0
  194. fi/alk/practice/_schedule.py +102 -0
  195. fi/alk/practice/_store.py +194 -0
  196. fi/alk/practice/_trainer.py +245 -0
  197. fi/alk/practice/_update.py +125 -0
  198. fi/alk/redteam.py +2621 -0
  199. fi/alk/rewardhack.py +237 -0
  200. fi/alk/simulate.py +10351 -0
  201. fi/alk/studio/__init__.py +82 -0
  202. fi/alk/studio/_bias.py +314 -0
  203. fi/alk/studio/_calibration.py +522 -0
  204. fi/alk/studio/_coverage.py +262 -0
  205. fi/alk/studio/_download.py +665 -0
  206. fi/alk/studio/_fidelity_attack.py +114 -0
  207. fi/alk/studio/_generate.py +652 -0
  208. fi/alk/studio/_library.py +370 -0
  209. fi/alk/studio/_scan.py +134 -0
  210. fi/alk/studio/_upgrade.py +42 -0
  211. fi/alk/studio/_vendor.py +172 -0
  212. fi/alk/suite.py +4200 -0
  213. fi/alk/tasks.py +828 -0
  214. fi/alk/telemetry/__init__.py +149 -0
  215. fi/alk/telemetry/_contract.py +141 -0
  216. fi/alk/telemetry/_emit.py +182 -0
  217. fi/alk/telemetry/_ledger.py +296 -0
  218. fi/alk/telemetry/_queue.py +127 -0
  219. fi/alk/telemetry/_row.py +294 -0
  220. fi/alk/telemetry/_run.py +233 -0
  221. fi/alk/telemetry/_sync.py +193 -0
  222. fi/alk/telemetry/_url.py +119 -0
  223. fi/alk/trinity.py +49397 -0
  224. fi/alk/voice_loop.py +174 -0
  225. fi/api/__init__.py +1 -0
  226. fi/api/auth.py +137 -0
  227. fi/api/types.py +29 -0
  228. fi/cli/__init__.py +9 -0
  229. fi/cli/assertions/__init__.py +25 -0
  230. fi/cli/assertions/conditions.py +76 -0
  231. fi/cli/assertions/evaluator.py +286 -0
  232. fi/cli/assertions/exit_codes.py +20 -0
  233. fi/cli/assertions/parser.py +131 -0
  234. fi/cli/assertions/reporter.py +194 -0
  235. fi/cli/commands/__init__.py +9 -0
  236. fi/cli/commands/config.py +165 -0
  237. fi/cli/commands/export.py +208 -0
  238. fi/cli/commands/init.py +112 -0
  239. fi/cli/commands/list_cmd.py +213 -0
  240. fi/cli/commands/run.py +486 -0
  241. fi/cli/commands/validate.py +173 -0
  242. fi/cli/commands/view.py +424 -0
  243. fi/cli/config/__init__.py +6 -0
  244. fi/cli/config/defaults.py +206 -0
  245. fi/cli/config/loader.py +155 -0
  246. fi/cli/config/schema.py +174 -0
  247. fi/cli/main.py +78 -0
  248. fi/cli/output/__init__.py +6 -0
  249. fi/cli/output/formatters.py +106 -0
  250. fi/cli/output/reporters.py +46 -0
  251. fi/cli/storage/__init__.py +5 -0
  252. fi/cli/storage/run_history.py +249 -0
  253. fi/cli/utils/__init__.py +5 -0
  254. fi/cli/utils/console.py +44 -0
  255. fi/evals/__init__.py +131 -0
  256. fi/evals/autoeval/__init__.py +137 -0
  257. fi/evals/autoeval/analyzer.py +211 -0
  258. fi/evals/autoeval/config.py +244 -0
  259. fi/evals/autoeval/export.py +213 -0
  260. fi/evals/autoeval/interactive.py +283 -0
  261. fi/evals/autoeval/pipeline.py +625 -0
  262. fi/evals/autoeval/prompts.py +139 -0
  263. fi/evals/autoeval/recommender.py +242 -0
  264. fi/evals/autoeval/rules.py +589 -0
  265. fi/evals/autoeval/templates.py +299 -0
  266. fi/evals/autoeval/types.py +232 -0
  267. fi/evals/core/__init__.py +16 -0
  268. fi/evals/core/cloud_registry.py +184 -0
  269. fi/evals/core/engines.py +368 -0
  270. fi/evals/core/evaluate.py +319 -0
  271. fi/evals/core/judge_prompt.py +90 -0
  272. fi/evals/core/prompt_generator.py +83 -0
  273. fi/evals/core/registry.py +57 -0
  274. fi/evals/core/result.py +55 -0
  275. fi/evals/evaluator.py +721 -0
  276. fi/evals/execution.py +168 -0
  277. fi/evals/feedback/__init__.py +32 -0
  278. fi/evals/feedback/calibrator.py +160 -0
  279. fi/evals/feedback/collector.py +214 -0
  280. fi/evals/feedback/hooks.py +81 -0
  281. fi/evals/feedback/retriever.py +128 -0
  282. fi/evals/feedback/store.py +272 -0
  283. fi/evals/feedback/types.py +99 -0
  284. fi/evals/framework/README.md +79 -0
  285. fi/evals/framework/__init__.py +267 -0
  286. fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
  287. fi/evals/framework/backends/__init__.py +99 -0
  288. fi/evals/framework/backends/_container.py +141 -0
  289. fi/evals/framework/backends/_utils.py +145 -0
  290. fi/evals/framework/backends/base.py +223 -0
  291. fi/evals/framework/backends/celery_backend.py +417 -0
  292. fi/evals/framework/backends/celery_worker.py +78 -0
  293. fi/evals/framework/backends/kubernetes_backend.py +665 -0
  294. fi/evals/framework/backends/ray_backend.py +521 -0
  295. fi/evals/framework/backends/temporal.py +350 -0
  296. fi/evals/framework/backends/temporal_worker.py +126 -0
  297. fi/evals/framework/backends/thread_pool.py +286 -0
  298. fi/evals/framework/context.py +258 -0
  299. fi/evals/framework/enrichment.py +306 -0
  300. fi/evals/framework/evals/__init__.py +68 -0
  301. fi/evals/framework/evals/agentic.py +399 -0
  302. fi/evals/framework/evals/builder.py +609 -0
  303. fi/evals/framework/evals/semantic.py +142 -0
  304. fi/evals/framework/evaluator.py +647 -0
  305. fi/evals/framework/evaluators/__init__.py +22 -0
  306. fi/evals/framework/evaluators/blocking.py +347 -0
  307. fi/evals/framework/evaluators/non_blocking.py +577 -0
  308. fi/evals/framework/propagation.py +421 -0
  309. fi/evals/framework/protocols.py +385 -0
  310. fi/evals/framework/registry.py +370 -0
  311. fi/evals/framework/resilience/__init__.py +150 -0
  312. fi/evals/framework/resilience/circuit_breaker.py +309 -0
  313. fi/evals/framework/resilience/degradation.py +355 -0
  314. fi/evals/framework/resilience/health.py +505 -0
  315. fi/evals/framework/resilience/rate_limiter.py +228 -0
  316. fi/evals/framework/resilience/retry.py +274 -0
  317. fi/evals/framework/resilience/types.py +288 -0
  318. fi/evals/framework/resilience/wrapper.py +433 -0
  319. fi/evals/framework/types.py +218 -0
  320. fi/evals/guardrails/README.md +915 -0
  321. fi/evals/guardrails/__init__.py +96 -0
  322. fi/evals/guardrails/backends/__init__.py +43 -0
  323. fi/evals/guardrails/backends/azure.py +361 -0
  324. fi/evals/guardrails/backends/base.py +88 -0
  325. fi/evals/guardrails/backends/generic_llm.py +163 -0
  326. fi/evals/guardrails/backends/granite.py +216 -0
  327. fi/evals/guardrails/backends/llamaguard.py +221 -0
  328. fi/evals/guardrails/backends/local_base.py +479 -0
  329. fi/evals/guardrails/backends/openai.py +365 -0
  330. fi/evals/guardrails/backends/qwen.py +170 -0
  331. fi/evals/guardrails/backends/shieldgemma.py +154 -0
  332. fi/evals/guardrails/backends/turing.py +235 -0
  333. fi/evals/guardrails/backends/vllm_client.py +321 -0
  334. fi/evals/guardrails/backends/wildguard.py +188 -0
  335. fi/evals/guardrails/base.py +888 -0
  336. fi/evals/guardrails/config.py +221 -0
  337. fi/evals/guardrails/discovery.py +243 -0
  338. fi/evals/guardrails/gateway.py +437 -0
  339. fi/evals/guardrails/registry.py +231 -0
  340. fi/evals/guardrails/scanners/__init__.py +127 -0
  341. fi/evals/guardrails/scanners/base.py +191 -0
  342. fi/evals/guardrails/scanners/code_injection.py +243 -0
  343. fi/evals/guardrails/scanners/eval_delegate.py +574 -0
  344. fi/evals/guardrails/scanners/invisible_chars.py +351 -0
  345. fi/evals/guardrails/scanners/jailbreak.py +412 -0
  346. fi/evals/guardrails/scanners/language.py +288 -0
  347. fi/evals/guardrails/scanners/pipeline.py +260 -0
  348. fi/evals/guardrails/scanners/regex.py +311 -0
  349. fi/evals/guardrails/scanners/secrets.py +274 -0
  350. fi/evals/guardrails/scanners/topics.py +649 -0
  351. fi/evals/guardrails/scanners/urls.py +341 -0
  352. fi/evals/guardrails/types.py +96 -0
  353. fi/evals/llm/__init__.py +3 -0
  354. fi/evals/llm/base_llm_provider.py +35 -0
  355. fi/evals/llm/providers/litellm.py +70 -0
  356. fi/evals/local/__init__.py +90 -0
  357. fi/evals/local/evaluator.py +690 -0
  358. fi/evals/local/execution_mode.py +121 -0
  359. fi/evals/local/llm.py +489 -0
  360. fi/evals/local/metrics/__init__.py +19 -0
  361. fi/evals/local/registry.py +360 -0
  362. fi/evals/manager.py +1018 -0
  363. fi/evals/manager_types.py +362 -0
  364. fi/evals/metrics/__init__.py +185 -0
  365. fi/evals/metrics/agents/__init__.py +74 -0
  366. fi/evals/metrics/agents/metrics.py +693 -0
  367. fi/evals/metrics/agents/report.py +36463 -0
  368. fi/evals/metrics/agents/types.py +160 -0
  369. fi/evals/metrics/base_llm_metric.py +111 -0
  370. fi/evals/metrics/base_metric.py +138 -0
  371. fi/evals/metrics/code_security/__init__.py +305 -0
  372. fi/evals/metrics/code_security/analyzer.py +985 -0
  373. fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
  374. fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
  375. fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
  376. fi/evals/metrics/code_security/benchmarks/types.py +308 -0
  377. fi/evals/metrics/code_security/detectors/__init__.py +186 -0
  378. fi/evals/metrics/code_security/detectors/base.py +394 -0
  379. fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
  380. fi/evals/metrics/code_security/detectors/injection.py +744 -0
  381. fi/evals/metrics/code_security/detectors/secrets.py +287 -0
  382. fi/evals/metrics/code_security/detectors/serialization.py +192 -0
  383. fi/evals/metrics/code_security/joint_metrics.py +588 -0
  384. fi/evals/metrics/code_security/judges/__init__.py +83 -0
  385. fi/evals/metrics/code_security/judges/base.py +238 -0
  386. fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
  387. fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
  388. fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
  389. fi/evals/metrics/code_security/metrics.py +388 -0
  390. fi/evals/metrics/code_security/modes/__init__.py +63 -0
  391. fi/evals/metrics/code_security/modes/adversarial.py +284 -0
  392. fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
  393. fi/evals/metrics/code_security/modes/base.py +283 -0
  394. fi/evals/metrics/code_security/modes/instruct.py +253 -0
  395. fi/evals/metrics/code_security/modes/repair.py +230 -0
  396. fi/evals/metrics/code_security/reports/__init__.py +57 -0
  397. fi/evals/metrics/code_security/reports/generator.py +404 -0
  398. fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
  399. fi/evals/metrics/code_security/types.py +534 -0
  400. fi/evals/metrics/function_calling/__init__.py +34 -0
  401. fi/evals/metrics/function_calling/metrics.py +573 -0
  402. fi/evals/metrics/function_calling/types.py +87 -0
  403. fi/evals/metrics/hallucination/__init__.py +54 -0
  404. fi/evals/metrics/hallucination/detector.py +149 -0
  405. fi/evals/metrics/hallucination/metrics.py +390 -0
  406. fi/evals/metrics/hallucination/nli.py +253 -0
  407. fi/evals/metrics/hallucination/sentinel.py +106 -0
  408. fi/evals/metrics/hallucination/types.py +132 -0
  409. fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
  410. fi/evals/metrics/heuristics/json_metrics.py +87 -0
  411. fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
  412. fi/evals/metrics/heuristics/string_metrics.py +391 -0
  413. fi/evals/metrics/llm_as_judges/__init__.py +17 -0
  414. fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
  415. fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
  416. fi/evals/metrics/llm_as_judges/types.py +48 -0
  417. fi/evals/metrics/rag/__init__.py +111 -0
  418. fi/evals/metrics/rag/advanced/__init__.py +14 -0
  419. fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
  420. fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
  421. fi/evals/metrics/rag/generation/__init__.py +17 -0
  422. fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
  423. fi/evals/metrics/rag/generation/context_utilization.py +245 -0
  424. fi/evals/metrics/rag/generation/faithfulness.py +241 -0
  425. fi/evals/metrics/rag/generation/groundedness.py +131 -0
  426. fi/evals/metrics/rag/rag_score.py +277 -0
  427. fi/evals/metrics/rag/retrieval/__init__.py +20 -0
  428. fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
  429. fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
  430. fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
  431. fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
  432. fi/evals/metrics/rag/retrieval/ranking.py +261 -0
  433. fi/evals/metrics/rag/types.py +100 -0
  434. fi/evals/metrics/rag/utils/__init__.py +62 -0
  435. fi/evals/metrics/rag/utils/claims.py +189 -0
  436. fi/evals/metrics/rag/utils/entities.py +244 -0
  437. fi/evals/metrics/rag/utils/nli.py +92 -0
  438. fi/evals/metrics/rag/utils/similarity.py +345 -0
  439. fi/evals/metrics/structured/__init__.py +114 -0
  440. fi/evals/metrics/structured/field_completeness.py +313 -0
  441. fi/evals/metrics/structured/hierarchy_score.py +366 -0
  442. fi/evals/metrics/structured/json_validation.py +190 -0
  443. fi/evals/metrics/structured/schema_compliance.py +280 -0
  444. fi/evals/metrics/structured/structured_output_score.py +298 -0
  445. fi/evals/metrics/structured/types.py +108 -0
  446. fi/evals/metrics/structured/validators/__init__.py +30 -0
  447. fi/evals/metrics/structured/validators/base.py +189 -0
  448. fi/evals/metrics/structured/validators/json_validator.py +196 -0
  449. fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
  450. fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
  451. fi/evals/otel/__init__.py +266 -0
  452. fi/evals/otel/config.py +400 -0
  453. fi/evals/otel/conventions.py +463 -0
  454. fi/evals/otel/enrichment.py +371 -0
  455. fi/evals/otel/instrumentors/__init__.py +140 -0
  456. fi/evals/otel/instrumentors/anthropic.py +517 -0
  457. fi/evals/otel/instrumentors/base.py +382 -0
  458. fi/evals/otel/instrumentors/openai.py +673 -0
  459. fi/evals/otel/processors/__init__.py +36 -0
  460. fi/evals/otel/processors/base.py +473 -0
  461. fi/evals/otel/processors/cost.py +445 -0
  462. fi/evals/otel/processors/evaluation.py +559 -0
  463. fi/evals/otel/processors/llm.py +462 -0
  464. fi/evals/otel/tracer.py +506 -0
  465. fi/evals/otel/types.py +232 -0
  466. fi/evals/otel_utils.py +23 -0
  467. fi/evals/protect.py +671 -0
  468. fi/evals/protect_input_adapter.py +154 -0
  469. fi/evals/streaming/__init__.py +88 -0
  470. fi/evals/streaming/buffer.py +213 -0
  471. fi/evals/streaming/evaluator.py +551 -0
  472. fi/evals/streaming/policy.py +307 -0
  473. fi/evals/streaming/scorers.py +368 -0
  474. fi/evals/streaming/types.py +238 -0
  475. fi/evals/templates.py +472 -0
  476. fi/evals/types.py +156 -0
  477. fi/opt/__init__.py +221 -0
  478. fi/opt/_objective_scoring.py +85 -0
  479. fi/opt/base/__init__.py +11 -0
  480. fi/opt/base/base_generator.py +33 -0
  481. fi/opt/base/base_mapper.py +26 -0
  482. fi/opt/base/base_optimizer.py +45 -0
  483. fi/opt/base/evaluator.py +211 -0
  484. fi/opt/components.py +3095 -0
  485. fi/opt/datamappers/__init__.py +3 -0
  486. fi/opt/datamappers/basic_mapper.py +40 -0
  487. fi/opt/deployment.py +1021 -0
  488. fi/opt/evidence.py +4332 -0
  489. fi/opt/generators/__init__.py +3 -0
  490. fi/opt/generators/litellm.py +66 -0
  491. fi/opt/integrations/__init__.py +23 -0
  492. fi/opt/integrations/generative_suite.py +410 -0
  493. fi/opt/integrations/simulate.py +1313 -0
  494. fi/opt/mutations.py +771 -0
  495. fi/opt/observability.py +4639 -0
  496. fi/opt/optimizer_trace.py +889 -0
  497. fi/opt/optimizers/__init__.py +80 -0
  498. fi/opt/optimizers/agent.py +331 -0
  499. fi/opt/optimizers/agent_bandit.py +392 -0
  500. fi/opt/optimizers/agent_curriculum.py +635 -0
  501. fi/opt/optimizers/agent_evolution.py +894 -0
  502. fi/opt/optimizers/agent_feedback.py +1863 -0
  503. fi/opt/optimizers/agent_pareto.py +547 -0
  504. fi/opt/optimizers/agent_social_memory.py +1113 -0
  505. fi/opt/optimizers/agent_tpe.py +321 -0
  506. fi/opt/optimizers/bayesian_search.py +449 -0
  507. fi/opt/optimizers/council.py +2075 -0
  508. fi/opt/optimizers/futureagi_replay.py +799 -0
  509. fi/opt/optimizers/gepa.py +322 -0
  510. fi/opt/optimizers/metaprompt.py +243 -0
  511. fi/opt/optimizers/promptwizard.py +417 -0
  512. fi/opt/optimizers/protegi.py +329 -0
  513. fi/opt/optimizers/random_search.py +224 -0
  514. fi/opt/research.py +518 -0
  515. fi/opt/simulation.py +260 -0
  516. fi/opt/targets.py +232 -0
  517. fi/opt/types.py +66 -0
  518. fi/opt/utils/__init__.py +4 -0
  519. fi/opt/utils/early_stopping.py +266 -0
  520. fi/opt/utils/setup_logging.py +82 -0
  521. fi/simulate/__init__.py +540 -0
  522. fi/simulate/_hashing.py +35 -0
  523. fi/simulate/_logging.py +10 -0
  524. fi/simulate/adapters.py +87 -0
  525. fi/simulate/agent/__init__.py +120 -0
  526. fi/simulate/agent/browser.py +658 -0
  527. fi/simulate/agent/definition.py +587 -0
  528. fi/simulate/agent/frameworks.py +3528 -0
  529. fi/simulate/agent/generic.py +8286 -0
  530. fi/simulate/agent/import_probe.py +227 -0
  531. fi/simulate/agent/memory.py +905 -0
  532. fi/simulate/agent/mocks.py +101 -0
  533. fi/simulate/agent/multi_agent.py +361 -0
  534. fi/simulate/agent/orchestration.py +903 -0
  535. fi/simulate/agent/realtime.py +665 -0
  536. fi/simulate/agent/wrapper.py +99 -0
  537. fi/simulate/agent/wrappers/__init__.py +18 -0
  538. fi/simulate/agent/wrappers/anthropic.py +62 -0
  539. fi/simulate/agent/wrappers/gemini.py +65 -0
  540. fi/simulate/agent/wrappers/http.py +404 -0
  541. fi/simulate/agent/wrappers/langchain.py +80 -0
  542. fi/simulate/agent/wrappers/openai.py +75 -0
  543. fi/simulate/agent/wrappers/websocket.py +326 -0
  544. fi/simulate/artifacts/__init__.py +11 -0
  545. fi/simulate/artifacts/manifest.py +62 -0
  546. fi/simulate/cli.py +20560 -0
  547. fi/simulate/endpoints/__init__.py +45 -0
  548. fi/simulate/endpoints/_http_actor.py +73 -0
  549. fi/simulate/endpoints/actor_sources.py +243 -0
  550. fi/simulate/endpoints/base.py +107 -0
  551. fi/simulate/endpoints/builtins.py +10 -0
  552. fi/simulate/endpoints/callable.py +95 -0
  553. fi/simulate/endpoints/http.py +76 -0
  554. fi/simulate/endpoints/livekit.py +138 -0
  555. fi/simulate/endpoints/originators.py +132 -0
  556. fi/simulate/endpoints/profiles.py +348 -0
  557. fi/simulate/endpoints/retell.py +633 -0
  558. fi/simulate/endpoints/vapi.py +205 -0
  559. fi/simulate/endpoints/websocket.py +76 -0
  560. fi/simulate/environment.py +33026 -0
  561. fi/simulate/environments/__init__.py +11 -0
  562. fi/simulate/environments/base.py +73 -0
  563. fi/simulate/environments/chat.py +697 -0
  564. fi/simulate/environments/voice.py +212 -0
  565. fi/simulate/evaluation/__init__.py +4 -0
  566. fi/simulate/evaluation/ai_eval.py +227 -0
  567. fi/simulate/evidence/__init__.py +35 -0
  568. fi/simulate/evidence/base.py +59 -0
  569. fi/simulate/evidence/caller_observed.py +50 -0
  570. fi/simulate/evidence/livekit_instrumentation.py +51 -0
  571. fi/simulate/evidence/livekit_room.py +50 -0
  572. fi/simulate/evidence/otel.py +49 -0
  573. fi/simulate/evidence/providers/__init__.py +24 -0
  574. fi/simulate/evidence/providers/base.py +61 -0
  575. fi/simulate/evidence/providers/retell.py +376 -0
  576. fi/simulate/evidence/providers/vapi.py +426 -0
  577. fi/simulate/hosted/__init__.py +32 -0
  578. fi/simulate/hosted/child_entrypoint.py +306 -0
  579. fi/simulate/hosted/job.py +150 -0
  580. fi/simulate/hosted/targets.py +53 -0
  581. fi/simulate/instrumentation/__init__.py +5 -0
  582. fi/simulate/instrumentation/livekit/__init__.py +122 -0
  583. fi/simulate/manifest.py +1033 -0
  584. fi/simulate/matrix_cli.py +165 -0
  585. fi/simulate/realtime/__init__.py +40 -0
  586. fi/simulate/realtime/events.py +107 -0
  587. fi/simulate/realtime/media.py +61 -0
  588. fi/simulate/realtime/session.py +91 -0
  589. fi/simulate/recording/__init__.py +5 -0
  590. fi/simulate/recording/room_recorder.py +326 -0
  591. fi/simulate/registry.py +185 -0
  592. fi/simulate/results/__init__.py +9 -0
  593. fi/simulate/results/base.py +18 -0
  594. fi/simulate/results/filesystem.py +71 -0
  595. fi/simulate/results/futureagi.py +1340 -0
  596. fi/simulate/runtime/__init__.py +85 -0
  597. fi/simulate/runtime/capabilities.py +40 -0
  598. fi/simulate/runtime/events.py +63 -0
  599. fi/simulate/runtime/failures.py +25 -0
  600. fi/simulate/runtime/ids.py +34 -0
  601. fi/simulate/runtime/plan.py +70 -0
  602. fi/simulate/runtime/planner.py +102 -0
  603. fi/simulate/runtime/report.py +174 -0
  604. fi/simulate/runtime/run.py +75 -0
  605. fi/simulate/runtime/runner.py +333 -0
  606. fi/simulate/runtime/spec.py +186 -0
  607. fi/simulate/simulation/__init__.py +30 -0
  608. fi/simulate/simulation/behavior_policy.py +425 -0
  609. fi/simulate/simulation/bridge/__init__.py +9 -0
  610. fi/simulate/simulation/bridge/audio.py +29 -0
  611. fi/simulate/simulation/bridge/connector.py +46 -0
  612. fi/simulate/simulation/bridge/livekit.py +252 -0
  613. fi/simulate/simulation/bridge/retell.py +188 -0
  614. fi/simulate/simulation/bridge/vapi.py +177 -0
  615. fi/simulate/simulation/contract.py +419 -0
  616. fi/simulate/simulation/engines/__init__.py +12 -0
  617. fi/simulate/simulation/engines/base.py +21 -0
  618. fi/simulate/simulation/engines/cloud.py +517 -0
  619. fi/simulate/simulation/engines/livekit.py +4167 -0
  620. fi/simulate/simulation/engines/local_text.py +89 -0
  621. fi/simulate/simulation/fidelity.py +374 -0
  622. fi/simulate/simulation/gemini_tts_stream.py +110 -0
  623. fi/simulate/simulation/generator.py +91 -0
  624. fi/simulate/simulation/goal_machine.py +185 -0
  625. fi/simulate/simulation/livekit_models.py +467 -0
  626. fi/simulate/simulation/matrix.py +170 -0
  627. fi/simulate/simulation/models.py +279 -0
  628. fi/simulate/simulation/runner.py +153 -0
  629. fi/simulate/simulation/synthetic.py +880 -0
  630. fi/simulate/simulation/voice_prompt.py +502 -0
  631. fi/simulate/simulator/__init__.py +55 -0
  632. fi/simulate/simulator/builtins.py +53 -0
  633. fi/simulate/suite.py +1288 -0
  634. fi/simulate/utils/routes.py +164 -0
  635. fi/simulate/voice.py +225 -0
  636. fi/simulate/voice_cli.py +182 -0
  637. fi/utils/__init__.py +1 -0
  638. fi/utils/constants.py +14 -0
  639. fi/utils/errors.py +200 -0
  640. fi/utils/executor.py +26 -0
  641. fi/utils/routes.py +119 -0
  642. fi/utils/utils.py +17 -0
@@ -0,0 +1,815 @@
1
+ """Deterministic container packaging for repositories that ship no container metadata.
2
+
3
+ The adapter packages a submitted process; it never generates application behavior. It accepts
4
+ only dependency and entrypoint conventions that can be proven from files in the repository, and
5
+ stages a sanitized build context outside the submitted checkout so generated files and secret
6
+ configuration never mutate or enter the customer's source tree.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import re
14
+ import shlex
15
+ import shutil
16
+ import stat
17
+ from dataclasses import asdict, dataclass
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+
22
+ GENERATED_DOCKERFILE = ".alk-generated.Dockerfile"
23
+ GENERATED_PLAN = "generated-runtime.json"
24
+ _MAX_FILES = 20_000
25
+ _MAX_FILE_BYTES = 64 * 1024 * 1024
26
+ _MAX_CONTEXT_BYTES = 512 * 1024 * 1024
27
+ _IGNORED_DIRECTORIES = {
28
+ ".git",
29
+ ".hg",
30
+ ".mypy_cache",
31
+ ".pytest_cache",
32
+ ".ruff_cache",
33
+ ".svn",
34
+ ".tox",
35
+ ".venv",
36
+ "__pycache__",
37
+ "artifacts",
38
+ "build",
39
+ "coverage",
40
+ "dist",
41
+ "node_modules",
42
+ "target",
43
+ "vendor",
44
+ }
45
+ _SECRET_NAMES = {
46
+ ".env",
47
+ ".npmrc",
48
+ ".pypirc",
49
+ "credentials",
50
+ "credentials.json",
51
+ "id_rsa",
52
+ "id_ed25519",
53
+ }
54
+ _SECRET_SUFFIXES = {".key", ".p12", ".pem", ".pfx"}
55
+ _SECRET_CONTENT = (
56
+ re.compile(rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
57
+ re.compile(rb"\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b"),
58
+ re.compile(rb"\bAKIA[0-9A-Z]{16}\b"),
59
+ re.compile(rb"\bsk-[A-Za-z0-9_-]{20,}\b"),
60
+ )
61
+ _PYTHON_ENTRYPOINTS = ("agent.py", "main.py", "app.py", "bot.py", "server.py")
62
+ _NODE_ENTRYPOINTS = ("agent.js", "index.js", "main.js", "server.js")
63
+
64
+
65
+ class GeneratedRuntimeError(ValueError):
66
+ """The repository cannot be packaged without unsafe or ambiguous guessing."""
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class GeneratedRuntimePlan:
71
+ language: str
72
+ component: str
73
+ version: str
74
+ dependency_file: str
75
+ install_strategy: str
76
+ extras: tuple[str, ...]
77
+ command: tuple[str, ...]
78
+ context_excludes: tuple[str, ...]
79
+ dockerfile: str
80
+ context_directory: str = ""
81
+
82
+ @property
83
+ def fingerprint(self) -> str:
84
+ payload = asdict(self)
85
+ payload.pop("context_directory", None)
86
+ return hashlib.sha256(
87
+ json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
88
+ ).hexdigest()
89
+
90
+
91
+ def can_generate_runtime(source: str | Path, runtime: Any | None = None) -> bool:
92
+ """Whether a deterministic language manifest exists; this performs no writes."""
93
+ try:
94
+ detect_generated_runtime(source, runtime)
95
+ except GeneratedRuntimeError:
96
+ return False
97
+ return True
98
+
99
+
100
+ def detect_generated_runtime(
101
+ source: str | Path, runtime: Any | None = None
102
+ ) -> GeneratedRuntimePlan:
103
+ """Return an auditable runtime plan, or one actionable admission error."""
104
+ root = Path(source).expanduser().resolve()
105
+ component = _component_root(root, str(getattr(runtime, "workdir", "") or ""))
106
+ language = str(getattr(runtime, "language", "") or "").strip().lower()
107
+ python_markers = _python_markers(component)
108
+ node_marker = component / "package.json"
109
+
110
+ if language in {"python", "py"}:
111
+ if not python_markers:
112
+ raise GeneratedRuntimeError(
113
+ "generated_runtime_dependency_manifest_missing: expected pyproject.toml, "
114
+ "requirements.txt, or a Python lock file"
115
+ )
116
+ language = "python"
117
+ elif language in {"node", "nodejs", "javascript", "typescript", "js", "ts"}:
118
+ if not node_marker.is_file():
119
+ raise GeneratedRuntimeError(
120
+ "generated_runtime_dependency_manifest_missing: expected package.json"
121
+ )
122
+ language = "node"
123
+ elif python_markers and not node_marker.is_file():
124
+ language = "python"
125
+ elif node_marker.is_file() and not python_markers:
126
+ language = "node"
127
+ elif python_markers and node_marker.is_file():
128
+ raise GeneratedRuntimeError(
129
+ "generated_runtime_language_ambiguous: both Python and Node manifests exist; "
130
+ "set runtime.language and runtime.workdir to the intended component"
131
+ )
132
+ else:
133
+ nested = _nested_components(component)
134
+ if len(nested) == 1 and component == root:
135
+ component = nested[0]
136
+ return detect_generated_runtime(
137
+ root,
138
+ _runtime_override(
139
+ runtime, workdir=component.relative_to(root).as_posix()
140
+ ),
141
+ )
142
+ if len(nested) > 1:
143
+ choices = ", ".join(
144
+ path.relative_to(root).as_posix() for path in nested[:12]
145
+ )
146
+ raise GeneratedRuntimeError(
147
+ "generated_runtime_component_ambiguous: multiple runnable components found: "
148
+ f"{choices}; set runtime.workdir"
149
+ )
150
+ raise GeneratedRuntimeError(
151
+ "generated_runtime_dependency_manifest_missing: no supported Python or Node "
152
+ "dependency manifest was found"
153
+ )
154
+
155
+ explicit_command = _explicit_command(runtime)
156
+ relative_component = component.relative_to(root).as_posix()
157
+ if relative_component == ".":
158
+ relative_component = ""
159
+ if language == "python":
160
+ version = _python_language_version(
161
+ component, str(getattr(runtime, "version", "") or "")
162
+ )
163
+ command = explicit_command or _python_command(component)
164
+ dependency, strategy, install, extras = _python_install(
165
+ component, runtime, command
166
+ )
167
+ dockerfile = _python_dockerfile(version, dependency, strategy, install, command)
168
+ else:
169
+ version = _language_version(str(getattr(runtime, "version", "") or ""), "22")
170
+ dependency, strategy, install, runner = _node_install(component)
171
+ command = explicit_command or _node_command(component, runner)
172
+ install.extend(_node_build_steps(component, runner, command))
173
+ dockerfile = _node_dockerfile(version, dependency, install, command)
174
+ extras = []
175
+ return GeneratedRuntimePlan(
176
+ language=language,
177
+ component=relative_component,
178
+ version=version,
179
+ dependency_file=dependency,
180
+ install_strategy=strategy,
181
+ extras=tuple(extras),
182
+ command=tuple(command),
183
+ context_excludes=_context_excludes(component, runtime, dependency, command),
184
+ dockerfile=dockerfile,
185
+ )
186
+
187
+
188
+ def prepare_generated_runtime(
189
+ source: str | Path, destination: str | Path, runtime: Any | None = None
190
+ ) -> GeneratedRuntimePlan:
191
+ """Detect the runtime and create its sanitized, session-owned build context."""
192
+ source_root = Path(source).expanduser().resolve()
193
+ destination_root = Path(destination).expanduser().resolve()
194
+ try:
195
+ destination_root.relative_to(source_root)
196
+ except ValueError:
197
+ pass
198
+ else:
199
+ raise GeneratedRuntimeError(
200
+ "generated_runtime_destination_inside_source: session artifacts must be outside "
201
+ "the submitted repository"
202
+ )
203
+ plan = detect_generated_runtime(source_root, runtime)
204
+ component = source_root / plan.component if plan.component else source_root
205
+ context = destination_root / "generated-runtime-context"
206
+ if context.exists():
207
+ shutil.rmtree(context)
208
+ context.mkdir(parents=True)
209
+ _copy_sanitized_context(component, context, plan.context_excludes)
210
+ context.joinpath(GENERATED_DOCKERFILE).write_text(plan.dockerfile, encoding="utf-8")
211
+ prepared = GeneratedRuntimePlan(
212
+ **{**asdict(plan), "context_directory": str(context)}
213
+ )
214
+ destination_root.mkdir(parents=True, exist_ok=True)
215
+ destination_root.joinpath(GENERATED_PLAN).write_text(
216
+ json.dumps(
217
+ {
218
+ **asdict(prepared),
219
+ "fingerprint": prepared.fingerprint,
220
+ "generated_files": [
221
+ f"generated-runtime-context/{GENERATED_DOCKERFILE}",
222
+ GENERATED_PLAN,
223
+ ],
224
+ },
225
+ indent=2,
226
+ ),
227
+ encoding="utf-8",
228
+ )
229
+ return prepared
230
+
231
+
232
+ def _runtime_override(runtime: Any | None, **values: str) -> Any:
233
+ payload = {
234
+ "language": str(getattr(runtime, "language", "") or ""),
235
+ "version": str(getattr(runtime, "version", "") or ""),
236
+ "workdir": str(getattr(runtime, "workdir", "") or ""),
237
+ "command": list(getattr(runtime, "command", None) or []),
238
+ "extras": list(getattr(runtime, "extras", None) or []),
239
+ "context_excludes": list(
240
+ getattr(runtime, "context_excludes", None) or []
241
+ ),
242
+ }
243
+ payload.update(values)
244
+ return type("RuntimeHints", (), payload)()
245
+
246
+
247
+ def _component_root(root: Path, configured: str) -> Path:
248
+ # Understanding occasionally records ``/`` to mean "the repository root". A generated
249
+ # runtime can never use the sandbox filesystem root as its source component; its hard
250
+ # boundary is the submitted checkout. Normalize only this root sentinel while continuing
251
+ # to reject every unrelated absolute path below.
252
+ if configured.strip() == "/":
253
+ return root
254
+ # Contracts produced for a selected monorepo component sometimes retain the
255
+ # repository-relative path with a leading slash (for example
256
+ # ``/examples/drive_thru``). The submitted ``root`` is already the hard
257
+ # filesystem boundary and the selected component, so an absolute hint whose
258
+ # final component names that root means "the selected root". Never resolve
259
+ # or read the absolute path itself.
260
+ configured_path = Path(configured) if configured else None
261
+ if (
262
+ configured_path is not None
263
+ and configured_path.is_absolute()
264
+ and configured_path.name == root.name
265
+ ):
266
+ return root
267
+ # The same thing is common without a leading slash: understanding runs at
268
+ # a selected component but records its path relative to the original
269
+ # monorepo (``examples/hotel_receptionist``). If the submitted root already
270
+ # ends in that exact path, it is provenance for the selected root rather
271
+ # than a request to descend into it a second time.
272
+ if configured_path is not None and not configured_path.is_absolute():
273
+ configured_parts = tuple(part for part in configured_path.parts if part != ".")
274
+ if (
275
+ configured_parts
276
+ and tuple(root.parts[-len(configured_parts) :]) == configured_parts
277
+ ):
278
+ return root
279
+ component = (root / configured).resolve() if configured else root
280
+ try:
281
+ component.relative_to(root)
282
+ except ValueError as exc:
283
+ raise GeneratedRuntimeError(
284
+ "generated_runtime_workdir_escapes_repository"
285
+ ) from exc
286
+ if not component.is_dir():
287
+ raise GeneratedRuntimeError(
288
+ f"generated_runtime_workdir_missing: {configured or '.'}"
289
+ )
290
+ return component
291
+
292
+
293
+ def _python_markers(root: Path) -> list[Path]:
294
+ return [
295
+ root / name
296
+ for name in (
297
+ "pyproject.toml",
298
+ "requirements.txt",
299
+ "uv.lock",
300
+ "poetry.lock",
301
+ "setup.py",
302
+ )
303
+ if root.joinpath(name).is_file()
304
+ ]
305
+
306
+
307
+ def _nested_components(root: Path) -> list[Path]:
308
+ found: set[Path] = set()
309
+ for marker in ("pyproject.toml", "requirements.txt", "setup.py", "package.json"):
310
+ for path in root.glob(f"*/*/{marker}"):
311
+ if not any(
312
+ part in _IGNORED_DIRECTORIES for part in path.relative_to(root).parts
313
+ ):
314
+ found.add(path.parent)
315
+ for path in root.glob(f"*/{marker}"):
316
+ if not any(
317
+ part in _IGNORED_DIRECTORIES for part in path.relative_to(root).parts
318
+ ):
319
+ found.add(path.parent)
320
+ return sorted(found)
321
+
322
+
323
+ def _explicit_command(runtime: Any | None) -> list[str]:
324
+ value = getattr(runtime, "command", None)
325
+ if not value:
326
+ return []
327
+ if isinstance(value, str):
328
+ try:
329
+ command = shlex.split(value)
330
+ except ValueError as exc:
331
+ raise GeneratedRuntimeError("generated_runtime_command_invalid") from exc
332
+ else:
333
+ command = [str(item) for item in value]
334
+ if not command or any(not item or "\x00" in item for item in command):
335
+ raise GeneratedRuntimeError("generated_runtime_command_invalid")
336
+ return command
337
+
338
+
339
+ def _language_version(value: str, default: str) -> str:
340
+ if not value:
341
+ return default
342
+ match = re.search(r"(?<!\d)(\d{1,2}(?:\.\d{1,2})?)(?!\d)", value)
343
+ return match.group(1) if match else default
344
+
345
+
346
+ def _python_language_version(root: Path, configured: str) -> str:
347
+ """Choose a concrete image version that satisfies a declared Python range.
348
+
349
+ ``>=3.10`` is a compatibility floor, not an instruction to use 3.10. Selecting the first
350
+ number from it makes otherwise reproducible projects fail as transitive dependencies retire
351
+ old interpreter wheels. Exact values remain exact; ranges prefer the harness' stable default.
352
+ """
353
+ declared = configured.strip()
354
+ if not declared:
355
+ requires = (
356
+ _pyproject_document(root / "pyproject.toml")
357
+ .get("project", {})
358
+ .get("requires-python", "")
359
+ )
360
+ declared = str(requires or "").strip()
361
+ if not declared:
362
+ return "3.12"
363
+ if not any(marker in declared for marker in "<>=!~,*"):
364
+ return _language_version(declared, "3.12")
365
+ try:
366
+ from packaging.specifiers import InvalidSpecifier, SpecifierSet
367
+ from packaging.version import Version
368
+
369
+ specifier = SpecifierSet(declared)
370
+ for candidate in ("3.12", "3.13", "3.11", "3.10", "3.14", "3.9"):
371
+ if Version(candidate) in specifier:
372
+ return candidate
373
+ except (InvalidSpecifier, ValueError):
374
+ pass
375
+ raise GeneratedRuntimeError(
376
+ f"generated_runtime_python_version_unsupported: {declared}"
377
+ )
378
+
379
+
380
+ def _python_install(
381
+ root: Path, runtime: Any | None, command: list[str]
382
+ ) -> tuple[str, str, list[str], list[str]]:
383
+ extras = _python_extras(root, runtime, command)
384
+ uv_extras = [value for extra in extras for value in ("--extra", extra)]
385
+ uv_script_project = _uv_script_project(root / "pyproject.toml")
386
+ if root.joinpath("uv.lock").is_file() and root.joinpath("pyproject.toml").is_file():
387
+ return (
388
+ "uv.lock",
389
+ "uv-frozen",
390
+ [
391
+ "RUN pip install --no-cache-dir uv==0.8.13",
392
+ "RUN " + shlex.join(["uv", "sync", "--frozen", *uv_extras]),
393
+ 'ENV PATH="/app/.venv/bin:$PATH"',
394
+ ],
395
+ extras,
396
+ )
397
+ if root.joinpath("requirements.txt").is_file():
398
+ return (
399
+ "requirements.txt",
400
+ "pip-requirements",
401
+ ["RUN pip install --no-cache-dir -r requirements.txt"],
402
+ [],
403
+ )
404
+ if uv_script_project:
405
+ install = [
406
+ "RUN pip install --no-cache-dir uv==0.8.13",
407
+ "RUN " + shlex.join(["uv", "sync", "--no-cache", *uv_extras]),
408
+ 'ENV PATH="/app/.venv/bin:$PATH"',
409
+ ]
410
+ if _needs_livekit_model_download(root / "pyproject.toml"):
411
+ install.append("RUN python -m livekit.agents download-files")
412
+ return (
413
+ "pyproject.toml",
414
+ "uv-script-project-unlocked",
415
+ install,
416
+ extras,
417
+ )
418
+ if (
419
+ root.joinpath("poetry.lock").is_file()
420
+ and root.joinpath("pyproject.toml").is_file()
421
+ ):
422
+ return (
423
+ "poetry.lock",
424
+ "poetry-locked",
425
+ [
426
+ "RUN pip install --no-cache-dir poetry==2.1.3",
427
+ "RUN poetry config virtualenvs.create false && "
428
+ + shlex.join(
429
+ [
430
+ "poetry",
431
+ "install",
432
+ "--only",
433
+ "main",
434
+ "--no-interaction",
435
+ *(["--extras", " ".join(extras)] if extras else []),
436
+ ]
437
+ ),
438
+ ],
439
+ extras,
440
+ )
441
+ if root.joinpath("pyproject.toml").is_file():
442
+ target = ".[" + ",".join(extras) + "]" if extras else "."
443
+ return (
444
+ "pyproject.toml",
445
+ "pip-project",
446
+ ["RUN " + shlex.join(["pip", "install", "--no-cache-dir", target])],
447
+ extras,
448
+ )
449
+ if root.joinpath("setup.py").is_file():
450
+ return (
451
+ "setup.py",
452
+ "pip-setup-project-unlocked",
453
+ ["RUN pip install --no-cache-dir ."],
454
+ [],
455
+ )
456
+ raise GeneratedRuntimeError("generated_runtime_python_dependencies_unsupported")
457
+
458
+
459
+ def _pyproject_document(path: Path) -> dict[str, Any]:
460
+ if not path.is_file():
461
+ return {}
462
+ try:
463
+ import tomllib
464
+
465
+ value = tomllib.loads(path.read_text(encoding="utf-8"))
466
+ except (OSError, ValueError):
467
+ return {}
468
+ return value if isinstance(value, dict) else {}
469
+
470
+
471
+ def _uv_script_project(path: Path) -> bool:
472
+ uv = _pyproject_document(path).get("tool", {}).get("uv", {})
473
+ return isinstance(uv, dict) and uv.get("package") is False
474
+
475
+
476
+ def _needs_livekit_model_download(path: Path) -> bool:
477
+ dependencies = _pyproject_document(path).get("project", {}).get("dependencies", [])
478
+ if not isinstance(dependencies, list):
479
+ return False
480
+ normalized = [str(value).lower().replace("_", "-") for value in dependencies]
481
+ return any(value.startswith("livekit-agents") for value in normalized) and any(
482
+ value.startswith(("livekit-plugins-silero", "livekit-plugins-turn-detector"))
483
+ for value in normalized
484
+ )
485
+
486
+
487
+ def _python_extras(root: Path, runtime: Any | None, command: list[str]) -> list[str]:
488
+ path = root / "pyproject.toml"
489
+ if not path.is_file():
490
+ return []
491
+ try:
492
+ import tomllib
493
+
494
+ document = tomllib.loads(path.read_text(encoding="utf-8"))
495
+ except (OSError, ValueError):
496
+ return []
497
+ declared = document.get("project", {}).get("optional-dependencies", {})
498
+ available = (
499
+ sorted(str(name) for name in declared) if isinstance(declared, dict) else []
500
+ )
501
+ requested = [str(item) for item in (getattr(runtime, "extras", None) or [])]
502
+ if requested:
503
+ unknown = sorted(set(requested) - set(available))
504
+ if unknown:
505
+ raise GeneratedRuntimeError(
506
+ "generated_runtime_extra_unknown: " + ", ".join(unknown)
507
+ )
508
+ return sorted(set(requested))
509
+ tokens = re.split(r"[^a-z0-9]+", " ".join(command).lower())
510
+ return [
511
+ extra
512
+ for extra in available
513
+ if extra.lower() not in {"all", "dev", "test", "tests"}
514
+ and extra.lower() in tokens
515
+ ]
516
+
517
+
518
+ def _python_command(root: Path) -> list[str]:
519
+ scripts = _pyproject_scripts(root / "pyproject.toml")
520
+ if len(scripts) == 1:
521
+ return [scripts[0]]
522
+ if len(scripts) > 1:
523
+ raise GeneratedRuntimeError(
524
+ "generated_runtime_entrypoint_ambiguous: multiple pyproject scripts exist: "
525
+ + ", ".join(scripts)
526
+ )
527
+ candidates = [name for name in _PYTHON_ENTRYPOINTS if root.joinpath(name).is_file()]
528
+ runnable = [
529
+ name
530
+ for name in candidates
531
+ if "__main__"
532
+ in root.joinpath(name).read_text(encoding="utf-8", errors="replace")
533
+ or "cli.run_app"
534
+ in root.joinpath(name).read_text(encoding="utf-8", errors="replace")
535
+ ]
536
+ choices = runnable or candidates
537
+ if len(choices) != 1:
538
+ detail = ", ".join(choices) if choices else "none"
539
+ raise GeneratedRuntimeError(
540
+ "generated_runtime_entrypoint_ambiguous: expected exactly one runnable Python "
541
+ f"entrypoint, found {detail}; set runtime.command"
542
+ )
543
+ command = ["python", choices[0]]
544
+ content = root.joinpath(choices[0]).read_text(encoding="utf-8", errors="replace")
545
+ if "cli.run_app" in content:
546
+ command.append("start")
547
+ return command
548
+
549
+
550
+ def _pyproject_scripts(path: Path) -> list[str]:
551
+ if not path.is_file():
552
+ return []
553
+ try:
554
+ import tomllib
555
+
556
+ document = tomllib.loads(path.read_text(encoding="utf-8"))
557
+ except (OSError, ValueError):
558
+ return []
559
+ scripts = document.get("project", {}).get("scripts", {})
560
+ return sorted(str(name) for name in scripts) if isinstance(scripts, dict) else []
561
+
562
+
563
+ def _node_install(root: Path) -> tuple[str, str, list[str], str]:
564
+ if root.joinpath("pnpm-lock.yaml").is_file():
565
+ return (
566
+ "pnpm-lock.yaml",
567
+ "pnpm-frozen",
568
+ ["RUN corepack enable && pnpm install --frozen-lockfile"],
569
+ "pnpm",
570
+ )
571
+ if root.joinpath("package-lock.json").is_file():
572
+ return "package-lock.json", "npm-ci", ["RUN npm ci"], "npm"
573
+ if root.joinpath("yarn.lock").is_file():
574
+ return (
575
+ "yarn.lock",
576
+ "yarn-frozen",
577
+ ["RUN corepack enable && yarn install --immutable"],
578
+ "yarn",
579
+ )
580
+ return "package.json", "npm-install-unlocked", ["RUN npm install"], "npm"
581
+
582
+
583
+ def _node_command(root: Path, runner: str) -> list[str]:
584
+ try:
585
+ package = json.loads(root.joinpath("package.json").read_text(encoding="utf-8"))
586
+ except (OSError, json.JSONDecodeError) as exc:
587
+ raise GeneratedRuntimeError("generated_runtime_package_json_invalid") from exc
588
+ scripts = package.get("scripts", {})
589
+ if isinstance(scripts, dict) and scripts.get("start"):
590
+ return [runner, "run", "start"] if runner == "npm" else [runner, "start"]
591
+ main = package.get("main")
592
+ if isinstance(main, str) and main and root.joinpath(main).is_file():
593
+ return ["node", main]
594
+ candidates = [name for name in _NODE_ENTRYPOINTS if root.joinpath(name).is_file()]
595
+ if len(candidates) != 1:
596
+ detail = ", ".join(candidates) if candidates else "none"
597
+ raise GeneratedRuntimeError(
598
+ "generated_runtime_entrypoint_ambiguous: package.json needs a start script or "
599
+ f"one existing main file, found {detail}; set runtime.command"
600
+ )
601
+ return ["node", candidates[0]]
602
+
603
+
604
+ def _node_build_steps(root: Path, runner: str, command: list[str]) -> list[str]:
605
+ """Build a submitted Node application when its selected start script needs that phase."""
606
+ try:
607
+ package = json.loads(root.joinpath("package.json").read_text(encoding="utf-8"))
608
+ except (OSError, json.JSONDecodeError):
609
+ return []
610
+ scripts = package.get("scripts", {})
611
+ if not isinstance(scripts, dict) or not scripts.get("build"):
612
+ return []
613
+ selected_start = command in (["npm", "run", "start"], [runner, "start"])
614
+ if not selected_start:
615
+ return []
616
+ invocation = [runner, "run", "build"] if runner == "npm" else [runner, "build"]
617
+ return ["RUN " + shlex.join(invocation)]
618
+
619
+
620
+ def _python_dockerfile(
621
+ version: str,
622
+ dependency: str,
623
+ strategy: str,
624
+ install: list[str],
625
+ command: list[str],
626
+ ) -> str:
627
+ del dependency, strategy
628
+ return "\n".join(
629
+ [
630
+ f"FROM python:{version}-slim",
631
+ "ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1",
632
+ # Poorly packaged repositories commonly declare Python extensions that
633
+ # only publish wheels for a subset of Python/CPU combinations. A slim
634
+ # image has no compiler, so otherwise a valid manifest fails depending
635
+ # on the runner architecture (for example webrtcvad on arm64). This is
636
+ # runtime infrastructure, not a rewrite of the submitted application.
637
+ "RUN apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*",
638
+ "WORKDIR /app",
639
+ "COPY . /app",
640
+ *install,
641
+ "RUN groupadd --gid 10001 alk && useradd --uid 10001 --gid 10001 --no-create-home alk && chown -R alk:alk /app",
642
+ "USER 10001:10001",
643
+ "CMD " + json.dumps(command),
644
+ "",
645
+ ]
646
+ )
647
+
648
+
649
+ def _node_dockerfile(
650
+ version: str, dependency: str, install: list[str], command: list[str]
651
+ ) -> str:
652
+ del dependency
653
+ return "\n".join(
654
+ [
655
+ f"FROM node:{version}-slim",
656
+ "WORKDIR /app",
657
+ "COPY --chown=node:node . /app",
658
+ *install,
659
+ "USER node",
660
+ "CMD " + json.dumps(command),
661
+ "",
662
+ ]
663
+ )
664
+
665
+
666
+ def _context_excludes(
667
+ component: Path, runtime: Any | None, dependency: str, command: list[str]
668
+ ) -> tuple[str, ...]:
669
+ required = {
670
+ Path(name)
671
+ for name in (dependency, "pyproject.toml", "package.json")
672
+ if name and component.joinpath(name).is_file()
673
+ }
674
+ required.update(
675
+ Path(token)
676
+ for token in command
677
+ if token
678
+ and not Path(token).is_absolute()
679
+ and ".." not in Path(token).parts
680
+ and component.joinpath(token).is_file()
681
+ )
682
+ values: set[str] = set()
683
+ for raw in getattr(runtime, "context_excludes", None) or []:
684
+ relative = Path(str(raw))
685
+ if relative.is_absolute() or not relative.parts or ".." in relative.parts:
686
+ raise GeneratedRuntimeError(
687
+ f"generated_runtime_context_exclude_invalid: {raw}"
688
+ )
689
+ normalized = relative.as_posix().removeprefix("./")
690
+ target = (component / normalized).resolve()
691
+ try:
692
+ target.relative_to(component.resolve())
693
+ except ValueError as exc:
694
+ raise GeneratedRuntimeError(
695
+ f"generated_runtime_context_exclude_invalid: {raw}"
696
+ ) from exc
697
+ if not target.exists():
698
+ # Generated/local state files are commonly listed even before the
699
+ # first run creates them. A missing exclusion is already excluded
700
+ # from the immutable build context and is therefore safe to ignore.
701
+ continue
702
+ excluded_path = Path(normalized)
703
+ if any(
704
+ excluded_path == required_path or excluded_path in required_path.parents
705
+ for required_path in required
706
+ ):
707
+ raise GeneratedRuntimeError(
708
+ f"generated_runtime_context_exclude_required: {normalized}"
709
+ )
710
+ values.add(normalized)
711
+ return tuple(sorted(values))
712
+
713
+
714
+ def _copy_sanitized_context(
715
+ source: Path, destination: Path, exclusions: tuple[str, ...] = ()
716
+ ) -> None:
717
+ count = 0
718
+ total = 0
719
+ source = source.resolve()
720
+ excluded = tuple(Path(value) for value in exclusions)
721
+
722
+ def is_excluded(relative: Path) -> bool:
723
+ return any(relative == value or value in relative.parents for value in excluded)
724
+
725
+ def copy_file(path: Path, relative: Path) -> None:
726
+ nonlocal count, total
727
+ if _sensitive_file(relative):
728
+ return
729
+ info = path.stat()
730
+ if not stat.S_ISREG(info.st_mode):
731
+ raise GeneratedRuntimeError(
732
+ f"generated_runtime_file_type_unsupported: {relative.as_posix()}"
733
+ )
734
+ if info.st_size > _MAX_FILE_BYTES:
735
+ raise GeneratedRuntimeError(
736
+ f"generated_runtime_file_too_large: {relative.as_posix()}"
737
+ )
738
+ count += 1
739
+ total += info.st_size
740
+ if count > _MAX_FILES or total > _MAX_CONTEXT_BYTES:
741
+ raise GeneratedRuntimeError("generated_runtime_context_limit_exceeded")
742
+ with path.open("rb") as stream:
743
+ while chunk := stream.read(1024 * 1024):
744
+ if any(pattern.search(chunk) for pattern in _SECRET_CONTENT):
745
+ raise GeneratedRuntimeError(
746
+ "generated_runtime_secret_material_detected: "
747
+ + relative.as_posix()
748
+ )
749
+ target = destination / relative
750
+ target.parent.mkdir(parents=True, exist_ok=True)
751
+ shutil.copy2(path, target, follow_symlinks=True)
752
+
753
+ def copy_directory(
754
+ directory: Path, relative_directory: Path, ancestors: frozenset[Path]
755
+ ) -> None:
756
+ resolved_directory = directory.resolve()
757
+ try:
758
+ resolved_directory.relative_to(source)
759
+ except ValueError as exc:
760
+ raise GeneratedRuntimeError(
761
+ f"generated_runtime_symlink_forbidden: {relative_directory.as_posix()}"
762
+ ) from exc
763
+ if resolved_directory in ancestors:
764
+ raise GeneratedRuntimeError(
765
+ f"generated_runtime_symlink_cycle: {relative_directory.as_posix()}"
766
+ )
767
+ next_ancestors = ancestors | {resolved_directory}
768
+ for path in sorted(directory.iterdir(), key=lambda item: item.name):
769
+ relative = relative_directory / path.name
770
+ if is_excluded(relative):
771
+ continue
772
+ if path.name in _IGNORED_DIRECTORIES and path.is_dir():
773
+ continue
774
+ if path.is_symlink():
775
+ try:
776
+ resolved = path.resolve(strict=True)
777
+ resolved.relative_to(source)
778
+ except (OSError, ValueError) as exc:
779
+ raise GeneratedRuntimeError(
780
+ f"generated_runtime_symlink_forbidden: {relative.as_posix()}"
781
+ ) from exc
782
+ if resolved.is_dir():
783
+ copy_directory(resolved, relative, next_ancestors)
784
+ else:
785
+ copy_file(resolved, relative)
786
+ elif path.is_dir():
787
+ copy_directory(path, relative, next_ancestors)
788
+ elif path.is_file():
789
+ copy_file(path, relative)
790
+ else:
791
+ raise GeneratedRuntimeError(
792
+ f"generated_runtime_file_type_unsupported: {relative.as_posix()}"
793
+ )
794
+
795
+ copy_directory(source, Path(), frozenset())
796
+
797
+
798
+ def _sensitive_file(relative: Path) -> bool:
799
+ name = relative.name.lower()
800
+ return (
801
+ name in _SECRET_NAMES
802
+ or name.startswith(".env.")
803
+ or relative.suffix.lower() in _SECRET_SUFFIXES
804
+ )
805
+
806
+
807
+ __all__ = [
808
+ "GENERATED_DOCKERFILE",
809
+ "GENERATED_PLAN",
810
+ "GeneratedRuntimeError",
811
+ "GeneratedRuntimePlan",
812
+ "can_generate_runtime",
813
+ "detect_generated_runtime",
814
+ "prepare_generated_runtime",
815
+ ]