alysis-code 0.13.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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,985 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ from collections.abc import Mapping
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING, Any
7
+ from urllib.parse import urlsplit
8
+
9
+ from .protocols import (
10
+ ANTHROPIC_MESSAGES_PROTOCOL,
11
+ GEMINI_GENERATE_CONTENT_PROTOCOL,
12
+ GEMINI_INTERACTIONS_PROTOCOL,
13
+ OPENAI_COMPAT_PROTOCOL,
14
+ OPENAI_RESPONSES_PROTOCOL,
15
+ ProviderProtocolCapabilities,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from ..profile_presets import ProfilePreset
20
+ from ..profiles import ProfileSpec
21
+
22
+
23
+ CACHE_STRATEGY_INHERIT = "inherit"
24
+ CACHE_STRATEGY_NONE = "none"
25
+ CACHE_STRATEGY_IMPLICIT_PROVIDER = "implicit_provider"
26
+ CACHE_STRATEGY_OPENAI_PROMPT_CACHE = "openai_prompt_cache"
27
+ CACHE_STRATEGY_MISTRAL_PROMPT_CACHE_KEY = "mistral_prompt_cache_key"
28
+ CACHE_STRATEGY_ANTHROPIC_CACHE_CONTROL = "anthropic_cache_control"
29
+ CACHE_STRATEGY_GEMINI_EXPLICIT_CACHED_CONTENT = "gemini_explicit_cached_content"
30
+ CACHE_STRATEGY_GEMINI_IMPLICIT = "gemini_implicit"
31
+ CACHE_STRATEGY_OPENROUTER_STICKY_SESSION = "openrouter_sticky_session"
32
+ CACHE_STRATEGY_XAI_CONVERSATION_HEADER = "xai_conversation_header"
33
+ CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS = "qwen_cache_control_blocks"
34
+
35
+ KNOWN_CACHE_STRATEGIES: frozenset[str] = frozenset(
36
+ {
37
+ CACHE_STRATEGY_NONE,
38
+ CACHE_STRATEGY_IMPLICIT_PROVIDER,
39
+ CACHE_STRATEGY_OPENAI_PROMPT_CACHE,
40
+ CACHE_STRATEGY_MISTRAL_PROMPT_CACHE_KEY,
41
+ CACHE_STRATEGY_ANTHROPIC_CACHE_CONTROL,
42
+ CACHE_STRATEGY_GEMINI_EXPLICIT_CACHED_CONTENT,
43
+ CACHE_STRATEGY_GEMINI_IMPLICIT,
44
+ CACHE_STRATEGY_OPENROUTER_STICKY_SESSION,
45
+ CACHE_STRATEGY_XAI_CONVERSATION_HEADER,
46
+ CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS,
47
+ }
48
+ )
49
+
50
+ PROFILE_CACHE_STRATEGIES: frozenset[str] = frozenset(
51
+ {CACHE_STRATEGY_INHERIT, *KNOWN_CACHE_STRATEGIES}
52
+ )
53
+
54
+ CACHE_USAGE_SCHEMA_NONE = "none"
55
+ CACHE_USAGE_SCHEMA_OPENAI = "openai"
56
+ CACHE_USAGE_SCHEMA_ANTHROPIC = "anthropic"
57
+ CACHE_USAGE_SCHEMA_GEMINI = "gemini"
58
+ CACHE_USAGE_SCHEMA_PROVIDER = "provider"
59
+
60
+ KNOWN_CACHE_USAGE_SCHEMAS: frozenset[str] = frozenset(
61
+ {
62
+ CACHE_USAGE_SCHEMA_NONE,
63
+ CACHE_USAGE_SCHEMA_OPENAI,
64
+ CACHE_USAGE_SCHEMA_ANTHROPIC,
65
+ CACHE_USAGE_SCHEMA_GEMINI,
66
+ CACHE_USAGE_SCHEMA_PROVIDER,
67
+ }
68
+ )
69
+
70
+ PROMPT_CACHE_KEY_FIELD = "prompt_cache_key"
71
+ PROMPT_CACHE_RETENTION_FIELD = "prompt_cache_retention"
72
+ CACHE_CONTROL_FIELD = "cache_control"
73
+ CACHED_CONTENT_FIELD = "cached_content"
74
+ OPENROUTER_SESSION_ID_FIELD = "session_id"
75
+ OPENROUTER_SESSION_ID_HEADER_FIELD = "x-session-id"
76
+ XAI_CONVERSATION_ID_HEADER_FIELD = "x-grok-conv-id"
77
+
78
+ KNOWN_CACHE_REQUEST_FIELDS: frozenset[str] = frozenset(
79
+ {
80
+ PROMPT_CACHE_KEY_FIELD,
81
+ PROMPT_CACHE_RETENTION_FIELD,
82
+ CACHE_CONTROL_FIELD,
83
+ CACHED_CONTENT_FIELD,
84
+ OPENROUTER_SESSION_ID_FIELD,
85
+ OPENROUTER_SESSION_ID_HEADER_FIELD,
86
+ XAI_CONVERSATION_ID_HEADER_FIELD,
87
+ }
88
+ )
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class CacheCapabilitySpec:
93
+ """Declarative cache capability metadata for profiles and presets.
94
+
95
+ ``None`` fields mean "inherit from the lower-precedence capability source".
96
+ Persisted user profiles should store this object under ``cache_capability``.
97
+ """
98
+
99
+ strategy: str = CACHE_STRATEGY_INHERIT
100
+ enabled: bool | None = None
101
+ supports_prompt_cache_key: bool | None = None
102
+ supports_prompt_cache_retention: bool | None = None
103
+ supports_cache_control: bool | None = None
104
+ supports_explicit_cached_content: bool | None = None
105
+ reports_cache_read_tokens: bool | None = None
106
+ reports_cache_write_tokens: bool | None = None
107
+ emits_request_fields: bool | None = None
108
+ request_fields: tuple[str, ...] = field(default_factory=tuple)
109
+ usage_schema: str | None = None
110
+ min_cacheable_tokens: int | None = None
111
+ notes: tuple[str, ...] = field(default_factory=tuple)
112
+ source: str = "profile"
113
+ model_overrides: tuple[tuple[str, CacheCapabilitySpec], ...] = ()
114
+ model_family_overrides: tuple[tuple[str, CacheCapabilitySpec], ...] = ()
115
+ endpoint_overrides: tuple[tuple[str, CacheCapabilitySpec], ...] = ()
116
+
117
+ def __post_init__(self) -> None:
118
+ strategy = normalize_cache_strategy(
119
+ self.strategy,
120
+ allow_inherit=True,
121
+ field_name=f"{self.source} cache strategy",
122
+ )
123
+ usage_schema = (
124
+ normalize_cache_usage_schema(
125
+ self.usage_schema,
126
+ field_name=f"{self.source} cache usage_schema",
127
+ )
128
+ if self.usage_schema is not None
129
+ else None
130
+ )
131
+ min_tokens = self.min_cacheable_tokens
132
+ if min_tokens is not None:
133
+ try:
134
+ min_tokens = int(min_tokens)
135
+ except (TypeError, ValueError):
136
+ raise ValueError(
137
+ f"{self.source} cache min_cacheable_tokens must be an integer."
138
+ ) from None
139
+ if min_tokens < 0:
140
+ raise ValueError(f"{self.source} cache min_cacheable_tokens must be non-negative.")
141
+ object.__setattr__(self, "strategy", strategy)
142
+ object.__setattr__(self, "usage_schema", usage_schema)
143
+ object.__setattr__(self, "min_cacheable_tokens", min_tokens)
144
+ object.__setattr__(
145
+ self,
146
+ "request_fields",
147
+ _request_field_tuple(
148
+ self.request_fields,
149
+ field_name=f"{self.source} cache request_fields",
150
+ ),
151
+ )
152
+ object.__setattr__(
153
+ self,
154
+ "notes",
155
+ tuple(note for note in (str(item).strip() for item in self.notes) if note),
156
+ )
157
+ object.__setattr__(self, "source", str(self.source or "profile").strip() or "profile")
158
+
159
+ @classmethod
160
+ def from_mapping(
161
+ cls,
162
+ value: Mapping[str, Any] | None,
163
+ *,
164
+ source: str = "profile",
165
+ ) -> CacheCapabilitySpec | None:
166
+ if value is None:
167
+ return None
168
+ if not isinstance(value, Mapping):
169
+ raise ValueError(f"{source} cache_capability must be an object.")
170
+ strategy = value.get("strategy", value.get("cache_strategy", CACHE_STRATEGY_INHERIT))
171
+ return cls(
172
+ strategy=str(strategy or CACHE_STRATEGY_INHERIT).strip(),
173
+ enabled=_optional_bool(value.get("enabled"), field_name=f"{source} cache enabled"),
174
+ supports_prompt_cache_key=_optional_bool(
175
+ value.get("supports_prompt_cache_key"),
176
+ field_name=f"{source} cache supports_prompt_cache_key",
177
+ ),
178
+ supports_prompt_cache_retention=_optional_bool(
179
+ value.get("supports_prompt_cache_retention"),
180
+ field_name=f"{source} cache supports_prompt_cache_retention",
181
+ ),
182
+ supports_cache_control=_optional_bool(
183
+ value.get("supports_cache_control"),
184
+ field_name=f"{source} cache supports_cache_control",
185
+ ),
186
+ supports_explicit_cached_content=_optional_bool(
187
+ value.get("supports_explicit_cached_content"),
188
+ field_name=f"{source} cache supports_explicit_cached_content",
189
+ ),
190
+ reports_cache_read_tokens=_optional_bool(
191
+ value.get("reports_cache_read_tokens"),
192
+ field_name=f"{source} cache reports_cache_read_tokens",
193
+ ),
194
+ reports_cache_write_tokens=_optional_bool(
195
+ value.get("reports_cache_write_tokens"),
196
+ field_name=f"{source} cache reports_cache_write_tokens",
197
+ ),
198
+ emits_request_fields=_optional_bool(
199
+ value.get(
200
+ "emits_request_fields",
201
+ value.get("emit_request_fields", value.get("request_fields_enabled")),
202
+ ),
203
+ field_name=f"{source} cache emits_request_fields",
204
+ ),
205
+ request_fields=_request_field_tuple(
206
+ value.get("request_fields"),
207
+ field_name=f"{source} cache request_fields",
208
+ ),
209
+ usage_schema=(
210
+ str(value.get("usage_schema")).strip()
211
+ if value.get("usage_schema") is not None
212
+ else None
213
+ ),
214
+ min_cacheable_tokens=_optional_int(
215
+ value.get("min_cacheable_tokens"),
216
+ field_name=f"{source} cache min_cacheable_tokens",
217
+ ),
218
+ notes=_string_tuple(value.get("notes"), field_name=f"{source} cache notes"),
219
+ source=source,
220
+ model_overrides=_scoped_specs(
221
+ value.get("models"),
222
+ source=f"{source} cache models",
223
+ ),
224
+ model_family_overrides=_scoped_specs(
225
+ value.get("model_families"),
226
+ source=f"{source} cache model_families",
227
+ ),
228
+ endpoint_overrides=_scoped_specs(
229
+ value.get("endpoints"),
230
+ source=f"{source} cache endpoints",
231
+ ),
232
+ )
233
+
234
+ def to_dict(self) -> dict[str, Any]:
235
+ data: dict[str, Any] = {}
236
+ if self.strategy != CACHE_STRATEGY_INHERIT:
237
+ data["strategy"] = self.strategy
238
+ for key in (
239
+ "enabled",
240
+ "supports_prompt_cache_key",
241
+ "supports_prompt_cache_retention",
242
+ "supports_cache_control",
243
+ "supports_explicit_cached_content",
244
+ "reports_cache_read_tokens",
245
+ "reports_cache_write_tokens",
246
+ "emits_request_fields",
247
+ ):
248
+ value = getattr(self, key)
249
+ if value is not None:
250
+ data[key] = bool(value)
251
+ if self.request_fields:
252
+ data["request_fields"] = list(self.request_fields)
253
+ if self.usage_schema is not None:
254
+ data["usage_schema"] = self.usage_schema
255
+ if self.min_cacheable_tokens is not None:
256
+ data["min_cacheable_tokens"] = self.min_cacheable_tokens
257
+ if self.notes:
258
+ data["notes"] = list(self.notes)
259
+ if self.model_overrides:
260
+ data["models"] = {
261
+ key: spec.to_dict() for key, spec in self.model_overrides if spec.has_values()
262
+ }
263
+ if self.model_family_overrides:
264
+ data["model_families"] = {
265
+ key: spec.to_dict()
266
+ for key, spec in self.model_family_overrides
267
+ if spec.has_values()
268
+ }
269
+ if self.endpoint_overrides:
270
+ data["endpoints"] = {
271
+ key: spec.to_dict() for key, spec in self.endpoint_overrides if spec.has_values()
272
+ }
273
+ return data
274
+
275
+ def has_values(self) -> bool:
276
+ return bool(self.to_dict())
277
+
278
+
279
+ @dataclass(frozen=True)
280
+ class EffectiveCacheCapability:
281
+ provider_key: str
282
+ protocol: str
283
+ model: str
284
+ strategy: str = CACHE_STRATEGY_NONE
285
+ enabled: bool = False
286
+ source: str = "default"
287
+ supports_prompt_cache_key: bool = False
288
+ supports_prompt_cache_retention: bool = False
289
+ supports_cache_control: bool = False
290
+ supports_explicit_cached_content: bool = False
291
+ reports_cache_read_tokens: bool = False
292
+ reports_cache_write_tokens: bool = False
293
+ emits_request_fields: bool = False
294
+ usage_schema: str = CACHE_USAGE_SCHEMA_NONE
295
+ min_cacheable_tokens: int | None = None
296
+ emitted_fields: tuple[str, ...] = ()
297
+ trusted_usage_fields: tuple[str, ...] = ()
298
+ notes: tuple[str, ...] = ()
299
+ warnings: tuple[str, ...] = ()
300
+
301
+ @property
302
+ def status(self) -> str:
303
+ if self.enabled and self.emitted_fields:
304
+ return "enabled"
305
+ if self.enabled:
306
+ return "available"
307
+ if self.strategy == CACHE_STRATEGY_NONE:
308
+ return "disabled"
309
+ return "unsupported"
310
+
311
+ def to_dict(self) -> dict[str, Any]:
312
+ payload: dict[str, Any] = {
313
+ "status": self.status,
314
+ "strategy": self.strategy,
315
+ "source": self.source,
316
+ "provider_key": self.provider_key,
317
+ "protocol": self.protocol,
318
+ "model": self.model,
319
+ "emitted_fields": list(self.emitted_fields),
320
+ "trusted_usage_fields": list(self.trusted_usage_fields),
321
+ "usage_schema": self.usage_schema,
322
+ "emits_request_fields": self.emits_request_fields,
323
+ }
324
+ if self.min_cacheable_tokens is not None:
325
+ payload["min_cacheable_tokens"] = self.min_cacheable_tokens
326
+ if self.notes:
327
+ payload["notes"] = list(self.notes)
328
+ if self.warnings:
329
+ payload["warnings"] = list(self.warnings)
330
+ return payload
331
+
332
+ def summary(self) -> str:
333
+ fields = ", ".join(self.emitted_fields) if self.emitted_fields else "none"
334
+ usage = ", ".join(self.trusted_usage_fields) if self.trusted_usage_fields else "none"
335
+ return (
336
+ f"{self.status}; strategy={self.strategy}; source={self.source}; "
337
+ f"emits={fields}; usage={usage}"
338
+ )
339
+
340
+
341
+ @dataclass(frozen=True)
342
+ class _CacheCapabilityState:
343
+ strategy: str
344
+ enabled: bool
345
+ supports_prompt_cache_key: bool
346
+ supports_prompt_cache_retention: bool
347
+ supports_cache_control: bool
348
+ supports_explicit_cached_content: bool
349
+ reports_cache_read_tokens: bool
350
+ reports_cache_write_tokens: bool
351
+ emits_request_fields: bool
352
+ request_fields: tuple[str, ...]
353
+ usage_schema: str
354
+ min_cacheable_tokens: int | None
355
+ source: str
356
+ notes: tuple[str, ...]
357
+
358
+
359
+ def resolve_effective_cache_capability(
360
+ *,
361
+ provider_key: str,
362
+ protocol: str,
363
+ model: str,
364
+ base_url: str | None = None,
365
+ transport_capabilities: ProviderProtocolCapabilities | None,
366
+ preset_cache_capability: CacheCapabilitySpec | Mapping[str, Any] | None = None,
367
+ profile_cache_capability: CacheCapabilitySpec | Mapping[str, Any] | None = None,
368
+ runtime_disabled_fields: tuple[str, ...] = (),
369
+ ) -> EffectiveCacheCapability:
370
+ """Resolve cache behavior from transport, preset, and profile metadata.
371
+
372
+ The resolver deliberately separates "declared support" from "transport projection".
373
+ User/profile overrides may opt into provider features, but emitted request fields are
374
+ still clamped to fields the active client implementation knows how to project.
375
+ """
376
+
377
+ normalized_provider = _safe_text(provider_key).lower()
378
+ normalized_protocol = _safe_text(protocol).lower()
379
+ normalized_model = _safe_text(model)
380
+
381
+ state = _state_from_transport_capabilities(transport_capabilities)
382
+ preset_spec = _coerce_spec(preset_cache_capability, source="preset")
383
+ profile_spec = _coerce_spec(profile_cache_capability, source="profile")
384
+ if preset_spec is not None:
385
+ state = _apply_spec(state, preset_spec, origin="preset")
386
+ state = _apply_scoped_specs(
387
+ state,
388
+ preset_spec,
389
+ model=normalized_model,
390
+ base_url=base_url,
391
+ origin="preset",
392
+ )
393
+ if profile_spec is not None:
394
+ state = _apply_spec(state, profile_spec, origin="profile")
395
+ state = _apply_scoped_specs(
396
+ state,
397
+ profile_spec,
398
+ model=normalized_model,
399
+ base_url=base_url,
400
+ origin="profile",
401
+ )
402
+
403
+ warnings: list[str] = []
404
+ allowed_fields, projection_warnings = _projected_fields(
405
+ state=state,
406
+ protocol=normalized_protocol,
407
+ )
408
+ warnings.extend(projection_warnings)
409
+ disabled_fields = _request_field_tuple(
410
+ runtime_disabled_fields,
411
+ field_name="runtime disabled cache fields",
412
+ )
413
+ if disabled_fields:
414
+ disabled = set(disabled_fields)
415
+ allowed_fields = tuple(field for field in allowed_fields if field not in disabled)
416
+ warnings.append(
417
+ "Session-local cache capability downgrade disabled rejected field(s): "
418
+ + ", ".join(disabled_fields)
419
+ )
420
+
421
+ strategy = normalize_cache_strategy(state.strategy, allow_inherit=False)
422
+ enabled = bool(state.enabled) and strategy != CACHE_STRATEGY_NONE
423
+ if (
424
+ enabled
425
+ and state.emits_request_fields
426
+ and not _strategy_has_effective_surface(strategy, allowed_fields)
427
+ ):
428
+ enabled = False
429
+ warnings.append(
430
+ f"Cache strategy {strategy!r} has no safe emitted field for protocol "
431
+ f"{normalized_protocol!r}."
432
+ )
433
+ if not enabled and strategy == CACHE_STRATEGY_NONE:
434
+ allowed_fields = ()
435
+
436
+ trusted_usage_fields = _trusted_usage_fields(
437
+ read=state.reports_cache_read_tokens,
438
+ write=state.reports_cache_write_tokens,
439
+ )
440
+ usage_schema = state.usage_schema
441
+ if not trusted_usage_fields and usage_schema != CACHE_USAGE_SCHEMA_NONE:
442
+ usage_schema = CACHE_USAGE_SCHEMA_NONE
443
+
444
+ return EffectiveCacheCapability(
445
+ provider_key=normalized_provider,
446
+ protocol=normalized_protocol,
447
+ model=normalized_model,
448
+ strategy=strategy if enabled or strategy != CACHE_STRATEGY_INHERIT else CACHE_STRATEGY_NONE,
449
+ enabled=enabled,
450
+ source=state.source,
451
+ supports_prompt_cache_key=PROMPT_CACHE_KEY_FIELD in allowed_fields,
452
+ supports_prompt_cache_retention=PROMPT_CACHE_RETENTION_FIELD in allowed_fields,
453
+ supports_cache_control=CACHE_CONTROL_FIELD in allowed_fields,
454
+ supports_explicit_cached_content=CACHED_CONTENT_FIELD in allowed_fields,
455
+ reports_cache_read_tokens=state.reports_cache_read_tokens,
456
+ reports_cache_write_tokens=state.reports_cache_write_tokens,
457
+ emits_request_fields=state.emits_request_fields,
458
+ usage_schema=usage_schema,
459
+ min_cacheable_tokens=state.min_cacheable_tokens,
460
+ emitted_fields=allowed_fields if enabled else (),
461
+ trusted_usage_fields=trusted_usage_fields,
462
+ notes=state.notes,
463
+ warnings=tuple(_dedupe(warnings)),
464
+ )
465
+
466
+
467
+ def profile_cache_capability_from_preset(
468
+ preset: ProfilePreset | None,
469
+ ) -> CacheCapabilitySpec | None:
470
+ if preset is None:
471
+ return None
472
+ return getattr(preset, "cache_capability", None)
473
+
474
+
475
+ def profile_cache_capability(profile: ProfileSpec | None) -> CacheCapabilitySpec | None:
476
+ if profile is None:
477
+ return None
478
+ return getattr(profile, "cache_capability", None)
479
+
480
+
481
+ def cache_capability_diagnostic_rows(
482
+ capability: EffectiveCacheCapability,
483
+ ) -> tuple[tuple[str, str], ...]:
484
+ emitted_fields = ", ".join(capability.emitted_fields) if capability.emitted_fields else "none"
485
+ usage_fields = (
486
+ ", ".join(capability.trusted_usage_fields) if capability.trusted_usage_fields else "none"
487
+ )
488
+ warnings = "; ".join(capability.warnings) if capability.warnings else "none"
489
+ return (
490
+ ("cache_status", capability.status),
491
+ ("cache_strategy", capability.strategy),
492
+ ("cache_capability_source", capability.source),
493
+ ("cache_emitted_fields", emitted_fields),
494
+ ("cache_usage_fields", usage_fields),
495
+ ("cache_usage_schema", capability.usage_schema),
496
+ ("cache_warnings", warnings),
497
+ )
498
+
499
+
500
+ def normalize_cache_strategy(
501
+ value: str | None,
502
+ *,
503
+ allow_inherit: bool = False,
504
+ field_name: str = "cache strategy",
505
+ ) -> str:
506
+ normalized = str(value or "").strip().lower()
507
+ if not normalized:
508
+ normalized = CACHE_STRATEGY_INHERIT if allow_inherit else CACHE_STRATEGY_NONE
509
+ allowed = PROFILE_CACHE_STRATEGIES if allow_inherit else KNOWN_CACHE_STRATEGIES
510
+ if normalized not in allowed:
511
+ allowed_text = ", ".join(sorted(allowed))
512
+ raise ValueError(f"{field_name} must be one of: {allowed_text}")
513
+ if normalized == CACHE_STRATEGY_INHERIT and not allow_inherit:
514
+ return CACHE_STRATEGY_NONE
515
+ return normalized
516
+
517
+
518
+ def normalize_cache_usage_schema(
519
+ value: str | None,
520
+ *,
521
+ field_name: str = "cache usage_schema",
522
+ ) -> str:
523
+ normalized = str(value or "").strip().lower()
524
+ if not normalized:
525
+ normalized = CACHE_USAGE_SCHEMA_NONE
526
+ if normalized not in KNOWN_CACHE_USAGE_SCHEMAS:
527
+ allowed_text = ", ".join(sorted(KNOWN_CACHE_USAGE_SCHEMAS))
528
+ raise ValueError(f"{field_name} must be one of: {allowed_text}")
529
+ return normalized
530
+
531
+
532
+ def _coerce_spec(
533
+ value: CacheCapabilitySpec | Mapping[str, Any] | None,
534
+ *,
535
+ source: str,
536
+ ) -> CacheCapabilitySpec | None:
537
+ if value is None:
538
+ return None
539
+ if isinstance(value, CacheCapabilitySpec):
540
+ if value.source == source:
541
+ return value
542
+ return CacheCapabilitySpec(
543
+ strategy=value.strategy,
544
+ enabled=value.enabled,
545
+ supports_prompt_cache_key=value.supports_prompt_cache_key,
546
+ supports_prompt_cache_retention=value.supports_prompt_cache_retention,
547
+ supports_cache_control=value.supports_cache_control,
548
+ supports_explicit_cached_content=value.supports_explicit_cached_content,
549
+ reports_cache_read_tokens=value.reports_cache_read_tokens,
550
+ reports_cache_write_tokens=value.reports_cache_write_tokens,
551
+ emits_request_fields=value.emits_request_fields,
552
+ request_fields=value.request_fields,
553
+ usage_schema=value.usage_schema,
554
+ min_cacheable_tokens=value.min_cacheable_tokens,
555
+ notes=value.notes,
556
+ source=source,
557
+ model_overrides=value.model_overrides,
558
+ model_family_overrides=value.model_family_overrides,
559
+ endpoint_overrides=value.endpoint_overrides,
560
+ )
561
+ return CacheCapabilitySpec.from_mapping(value, source=source)
562
+
563
+
564
+ def _scoped_specs(value: Any, *, source: str) -> tuple[tuple[str, CacheCapabilitySpec], ...]:
565
+ if value is None:
566
+ return ()
567
+ if not isinstance(value, Mapping):
568
+ raise ValueError(f"{source} must be an object.")
569
+ specs: list[tuple[str, CacheCapabilitySpec]] = []
570
+ for key, raw_spec in value.items():
571
+ label = str(key or "").strip()
572
+ if not label:
573
+ continue
574
+ spec = CacheCapabilitySpec.from_mapping(raw_spec, source=f"{source}[{label!r}]")
575
+ if spec is not None:
576
+ specs.append((label, spec))
577
+ return tuple(specs)
578
+
579
+
580
+ def _apply_scoped_specs(
581
+ state: _CacheCapabilityState,
582
+ spec: CacheCapabilitySpec,
583
+ *,
584
+ model: str,
585
+ base_url: str | None,
586
+ origin: str,
587
+ ) -> _CacheCapabilityState:
588
+ resolved = state
589
+ for key, scoped_spec in spec.endpoint_overrides:
590
+ if _endpoint_rule_matches(key, base_url):
591
+ resolved = _apply_spec(resolved, scoped_spec, origin=origin)
592
+ for key, scoped_spec in spec.model_family_overrides:
593
+ if _model_family_rule_matches(key, model):
594
+ resolved = _apply_spec(resolved, scoped_spec, origin=origin)
595
+ for key, scoped_spec in spec.model_overrides:
596
+ if _model_rule_matches(key, model):
597
+ resolved = _apply_spec(resolved, scoped_spec, origin=origin)
598
+ return resolved
599
+
600
+
601
+ def _endpoint_rule_matches(rule: str, base_url: str | None) -> bool:
602
+ normalized_rule = _normalize_endpoint_rule(rule)
603
+ if not normalized_rule:
604
+ return False
605
+ normalized_url = _normalize_endpoint_rule(base_url)
606
+ if normalized_url == normalized_rule:
607
+ return True
608
+ return _endpoint_host(base_url) == normalized_rule
609
+
610
+
611
+ def _normalize_endpoint_rule(value: str | None) -> str:
612
+ text = str(value or "").strip().rstrip("/").lower()
613
+ if not text:
614
+ return ""
615
+ host = _endpoint_host(text)
616
+ if "://" not in text and "/" not in text and host:
617
+ return host
618
+ return text
619
+
620
+
621
+ def _endpoint_host(value: str | None) -> str:
622
+ text = str(value or "").strip()
623
+ if not text:
624
+ return ""
625
+ try:
626
+ parsed = urlsplit(text if "://" in text else f"https://{text}")
627
+ except ValueError:
628
+ return ""
629
+ return (parsed.hostname or "").rstrip(".").lower()
630
+
631
+
632
+ def _model_rule_matches(rule: str, model: str) -> bool:
633
+ normalized_rule = str(rule or "").strip().casefold()
634
+ normalized_model = str(model or "").strip().casefold()
635
+ if not normalized_rule or not normalized_model:
636
+ return False
637
+ return normalized_model == normalized_rule or fnmatch.fnmatchcase(
638
+ normalized_model,
639
+ normalized_rule,
640
+ )
641
+
642
+
643
+ def _model_family_rule_matches(rule: str, model: str) -> bool:
644
+ normalized_rule = str(rule or "").strip().casefold().rstrip("*")
645
+ normalized_model = str(model or "").strip().casefold()
646
+ if not normalized_rule or not normalized_model:
647
+ return False
648
+ return normalized_model.startswith(normalized_rule)
649
+
650
+
651
+ def _state_from_transport_capabilities(
652
+ capabilities: ProviderProtocolCapabilities | None,
653
+ ) -> _CacheCapabilityState:
654
+ if capabilities is None:
655
+ return _CacheCapabilityState(
656
+ strategy=CACHE_STRATEGY_NONE,
657
+ enabled=False,
658
+ supports_prompt_cache_key=False,
659
+ supports_prompt_cache_retention=False,
660
+ supports_cache_control=False,
661
+ supports_explicit_cached_content=False,
662
+ reports_cache_read_tokens=False,
663
+ reports_cache_write_tokens=False,
664
+ emits_request_fields=False,
665
+ request_fields=(),
666
+ usage_schema=CACHE_USAGE_SCHEMA_NONE,
667
+ min_cacheable_tokens=None,
668
+ source="default",
669
+ notes=("No bundled cache capability metadata for this provider/protocol.",),
670
+ )
671
+
672
+ strategy = normalize_cache_strategy(capabilities.cache_strategy, allow_inherit=False)
673
+ enabled = strategy != CACHE_STRATEGY_NONE
674
+ return _CacheCapabilityState(
675
+ strategy=strategy,
676
+ enabled=enabled,
677
+ supports_prompt_cache_key=bool(capabilities.supports_prompt_cache_key),
678
+ supports_prompt_cache_retention=bool(capabilities.supports_prompt_cache_retention),
679
+ supports_cache_control=bool(capabilities.supports_cache_control),
680
+ supports_explicit_cached_content=bool(capabilities.supports_explicit_cached_content),
681
+ reports_cache_read_tokens=bool(capabilities.reports_cache_read_tokens),
682
+ reports_cache_write_tokens=bool(capabilities.reports_cache_write_tokens),
683
+ emits_request_fields=True,
684
+ request_fields=(),
685
+ usage_schema=_usage_schema_for_strategy(strategy),
686
+ min_cacheable_tokens=_min_tokens_for_strategy(strategy),
687
+ source="protocol",
688
+ notes=(),
689
+ )
690
+
691
+
692
+ def _apply_spec(
693
+ state: _CacheCapabilityState,
694
+ spec: CacheCapabilitySpec,
695
+ *,
696
+ origin: str,
697
+ ) -> _CacheCapabilityState:
698
+ if spec.enabled is False:
699
+ return _CacheCapabilityState(
700
+ strategy=CACHE_STRATEGY_NONE,
701
+ enabled=False,
702
+ supports_prompt_cache_key=False,
703
+ supports_prompt_cache_retention=False,
704
+ supports_cache_control=False,
705
+ supports_explicit_cached_content=False,
706
+ reports_cache_read_tokens=False,
707
+ reports_cache_write_tokens=False,
708
+ emits_request_fields=False,
709
+ request_fields=(),
710
+ usage_schema=CACHE_USAGE_SCHEMA_NONE,
711
+ min_cacheable_tokens=spec.min_cacheable_tokens,
712
+ source=spec.source,
713
+ notes=_dedupe((*state.notes, *spec.notes)),
714
+ )
715
+
716
+ strategy = state.strategy
717
+ if spec.strategy != CACHE_STRATEGY_INHERIT:
718
+ strategy = spec.strategy
719
+ enabled = state.enabled if spec.enabled is None else bool(spec.enabled)
720
+ if strategy == CACHE_STRATEGY_NONE:
721
+ enabled = False
722
+ emits_request_fields = state.emits_request_fields
723
+ if spec.emits_request_fields is not None:
724
+ emits_request_fields = bool(spec.emits_request_fields)
725
+ elif origin == "profile" and spec.enabled is True:
726
+ # A profile override is an explicit advanced user opt-in, including scoped
727
+ # models/model_families/endpoints overrides, which inherit the profile
728
+ # origin. Preset metadata can stay diagnostic-only by setting
729
+ # emits_request_fields=False.
730
+ emits_request_fields = True
731
+
732
+ return _CacheCapabilityState(
733
+ strategy=strategy,
734
+ enabled=enabled,
735
+ supports_prompt_cache_key=_merge_bool(
736
+ state.supports_prompt_cache_key,
737
+ spec.supports_prompt_cache_key,
738
+ ),
739
+ supports_prompt_cache_retention=_merge_bool(
740
+ state.supports_prompt_cache_retention,
741
+ spec.supports_prompt_cache_retention,
742
+ ),
743
+ supports_cache_control=_merge_bool(
744
+ state.supports_cache_control,
745
+ spec.supports_cache_control,
746
+ ),
747
+ supports_explicit_cached_content=_merge_bool(
748
+ state.supports_explicit_cached_content,
749
+ spec.supports_explicit_cached_content,
750
+ ),
751
+ reports_cache_read_tokens=_merge_bool(
752
+ state.reports_cache_read_tokens,
753
+ spec.reports_cache_read_tokens,
754
+ ),
755
+ reports_cache_write_tokens=_merge_bool(
756
+ state.reports_cache_write_tokens,
757
+ spec.reports_cache_write_tokens,
758
+ ),
759
+ emits_request_fields=emits_request_fields,
760
+ request_fields=_dedupe((*state.request_fields, *spec.request_fields)),
761
+ usage_schema=spec.usage_schema
762
+ or state.usage_schema
763
+ or _usage_schema_for_strategy(strategy),
764
+ min_cacheable_tokens=(
765
+ spec.min_cacheable_tokens
766
+ if spec.min_cacheable_tokens is not None
767
+ else state.min_cacheable_tokens
768
+ ),
769
+ source=spec.source if spec.has_values() else state.source,
770
+ notes=_dedupe((*state.notes, *spec.notes)),
771
+ )
772
+
773
+
774
+ def _projected_fields(
775
+ *,
776
+ state: _CacheCapabilityState,
777
+ protocol: str,
778
+ ) -> tuple[tuple[str, ...], tuple[str, ...]]:
779
+ physical_fields = _transport_projection_fields(protocol)
780
+ if not state.emits_request_fields:
781
+ return (), ()
782
+ wanted: list[str] = []
783
+ if state.supports_prompt_cache_key:
784
+ wanted.append(PROMPT_CACHE_KEY_FIELD)
785
+ if state.supports_prompt_cache_retention:
786
+ wanted.append(PROMPT_CACHE_RETENTION_FIELD)
787
+ if state.supports_cache_control:
788
+ wanted.append(CACHE_CONTROL_FIELD)
789
+ if state.supports_explicit_cached_content:
790
+ wanted.append(CACHED_CONTENT_FIELD)
791
+ wanted.extend(state.request_fields)
792
+
793
+ strategy_fields = _strategy_projection_fields(state.strategy)
794
+ wanted = [field for field in wanted if field in strategy_fields]
795
+ fields = tuple(field for field in wanted if field in physical_fields)
796
+ dropped = [field for field in wanted if field not in physical_fields]
797
+ warnings = tuple(
798
+ f"Dropped cache field {field!r}; protocol {protocol!r} cannot project it."
799
+ for field in dropped
800
+ )
801
+ return fields, warnings
802
+
803
+
804
+ def _transport_projection_fields(protocol: str) -> tuple[str, ...]:
805
+ normalized = str(protocol or "").strip().lower()
806
+ if normalized in {OPENAI_COMPAT_PROTOCOL, OPENAI_RESPONSES_PROTOCOL}:
807
+ fields = [PROMPT_CACHE_KEY_FIELD, PROMPT_CACHE_RETENTION_FIELD]
808
+ if normalized == OPENAI_COMPAT_PROTOCOL:
809
+ fields.extend(
810
+ (
811
+ CACHE_CONTROL_FIELD,
812
+ OPENROUTER_SESSION_ID_FIELD,
813
+ OPENROUTER_SESSION_ID_HEADER_FIELD,
814
+ XAI_CONVERSATION_ID_HEADER_FIELD,
815
+ )
816
+ )
817
+ return tuple(fields)
818
+ if normalized == ANTHROPIC_MESSAGES_PROTOCOL:
819
+ return (CACHE_CONTROL_FIELD,)
820
+ if normalized == GEMINI_GENERATE_CONTENT_PROTOCOL:
821
+ return (CACHED_CONTENT_FIELD,)
822
+ if normalized == GEMINI_INTERACTIONS_PROTOCOL:
823
+ return ()
824
+ return ()
825
+
826
+
827
+ def _strategy_projection_fields(strategy: str) -> tuple[str, ...]:
828
+ normalized = str(strategy or "").strip().lower()
829
+ if normalized == CACHE_STRATEGY_IMPLICIT_PROVIDER:
830
+ return (PROMPT_CACHE_KEY_FIELD,)
831
+ if normalized in {
832
+ CACHE_STRATEGY_OPENAI_PROMPT_CACHE,
833
+ }:
834
+ return (PROMPT_CACHE_KEY_FIELD, PROMPT_CACHE_RETENTION_FIELD)
835
+ if normalized == CACHE_STRATEGY_MISTRAL_PROMPT_CACHE_KEY:
836
+ return (PROMPT_CACHE_KEY_FIELD,)
837
+ if normalized == CACHE_STRATEGY_OPENROUTER_STICKY_SESSION:
838
+ return (OPENROUTER_SESSION_ID_FIELD, OPENROUTER_SESSION_ID_HEADER_FIELD)
839
+ if normalized == CACHE_STRATEGY_XAI_CONVERSATION_HEADER:
840
+ return (XAI_CONVERSATION_ID_HEADER_FIELD, PROMPT_CACHE_KEY_FIELD)
841
+ if normalized == CACHE_STRATEGY_ANTHROPIC_CACHE_CONTROL:
842
+ return (CACHE_CONTROL_FIELD,)
843
+ if normalized == CACHE_STRATEGY_GEMINI_EXPLICIT_CACHED_CONTENT:
844
+ return (CACHED_CONTENT_FIELD,)
845
+ if normalized == CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS:
846
+ return (CACHE_CONTROL_FIELD,)
847
+ return ()
848
+
849
+
850
+ def _strategy_has_effective_surface(strategy: str, projected_fields: tuple[str, ...]) -> bool:
851
+ if strategy in {
852
+ CACHE_STRATEGY_IMPLICIT_PROVIDER,
853
+ CACHE_STRATEGY_GEMINI_IMPLICIT,
854
+ CACHE_STRATEGY_OPENROUTER_STICKY_SESSION,
855
+ CACHE_STRATEGY_XAI_CONVERSATION_HEADER,
856
+ CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS,
857
+ }:
858
+ return True
859
+ if strategy == CACHE_STRATEGY_NONE:
860
+ return False
861
+ return bool(projected_fields)
862
+
863
+
864
+ def _usage_schema_for_strategy(strategy: str) -> str:
865
+ if strategy in {
866
+ CACHE_STRATEGY_OPENAI_PROMPT_CACHE,
867
+ CACHE_STRATEGY_MISTRAL_PROMPT_CACHE_KEY,
868
+ }:
869
+ return CACHE_USAGE_SCHEMA_OPENAI
870
+ if strategy == CACHE_STRATEGY_ANTHROPIC_CACHE_CONTROL:
871
+ return CACHE_USAGE_SCHEMA_ANTHROPIC
872
+ if strategy in {
873
+ CACHE_STRATEGY_GEMINI_EXPLICIT_CACHED_CONTENT,
874
+ CACHE_STRATEGY_GEMINI_IMPLICIT,
875
+ }:
876
+ return CACHE_USAGE_SCHEMA_GEMINI
877
+ if strategy in {
878
+ CACHE_STRATEGY_IMPLICIT_PROVIDER,
879
+ CACHE_STRATEGY_OPENROUTER_STICKY_SESSION,
880
+ CACHE_STRATEGY_XAI_CONVERSATION_HEADER,
881
+ CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS,
882
+ }:
883
+ return CACHE_USAGE_SCHEMA_PROVIDER
884
+ return CACHE_USAGE_SCHEMA_NONE
885
+
886
+
887
+ def _min_tokens_for_strategy(strategy: str) -> int | None:
888
+ if strategy in {
889
+ CACHE_STRATEGY_OPENAI_PROMPT_CACHE,
890
+ CACHE_STRATEGY_MISTRAL_PROMPT_CACHE_KEY,
891
+ CACHE_STRATEGY_QWEN_CACHE_CONTROL_BLOCKS,
892
+ }:
893
+ return 1024
894
+ if strategy == CACHE_STRATEGY_GEMINI_EXPLICIT_CACHED_CONTENT:
895
+ return 4096
896
+ return None
897
+
898
+
899
+ def _trusted_usage_fields(*, read: bool, write: bool) -> tuple[str, ...]:
900
+ fields: list[str] = []
901
+ if read:
902
+ fields.append("cache_read_input_tokens")
903
+ if write:
904
+ fields.extend(
905
+ [
906
+ "cache_creation_input_tokens",
907
+ "cache_creation_5m_input_tokens",
908
+ "cache_creation_1h_input_tokens",
909
+ ]
910
+ )
911
+ return tuple(fields)
912
+
913
+
914
+ def _merge_bool(current: bool, override: bool | None) -> bool:
915
+ return current if override is None else bool(override)
916
+
917
+
918
+ def _optional_bool(value: Any, *, field_name: str) -> bool | None:
919
+ if value is None:
920
+ return None
921
+ if isinstance(value, bool):
922
+ return value
923
+ normalized = str(value).strip().lower()
924
+ if normalized in {"1", "true", "yes", "on"}:
925
+ return True
926
+ if normalized in {"0", "false", "no", "off"}:
927
+ return False
928
+ raise ValueError(f"{field_name} must be true or false.")
929
+
930
+
931
+ def _optional_int(value: Any, *, field_name: str) -> int | None:
932
+ if value is None or str(value).strip() == "":
933
+ return None
934
+ try:
935
+ number = int(value)
936
+ except (TypeError, ValueError):
937
+ raise ValueError(f"{field_name} must be an integer.") from None
938
+ if number < 0:
939
+ raise ValueError(f"{field_name} must be non-negative.")
940
+ return number
941
+
942
+
943
+ def _string_tuple(value: Any, *, field_name: str) -> tuple[str, ...]:
944
+ if value is None:
945
+ return ()
946
+ if isinstance(value, str):
947
+ text = value.strip()
948
+ return (text,) if text else ()
949
+ if not isinstance(value, (list, tuple)):
950
+ raise ValueError(f"{field_name} must be a string or list of strings.")
951
+ items: list[str] = []
952
+ for item in value:
953
+ text = str(item).strip()
954
+ if text:
955
+ items.append(text)
956
+ return tuple(items)
957
+
958
+
959
+ def _request_field_tuple(value: Any, *, field_name: str) -> tuple[str, ...]:
960
+ fields = _string_tuple(value, field_name=field_name)
961
+ unknown = [field for field in fields if field not in KNOWN_CACHE_REQUEST_FIELDS]
962
+ if unknown:
963
+ allowed_text = ", ".join(sorted(KNOWN_CACHE_REQUEST_FIELDS))
964
+ unknown_text = ", ".join(unknown)
965
+ raise ValueError(
966
+ f"{field_name} contains unknown field(s): {unknown_text}. "
967
+ f"Allowed fields: {allowed_text}."
968
+ )
969
+ return _dedupe(fields)
970
+
971
+
972
+ def _safe_text(value: Any) -> str:
973
+ return str(value or "").strip()
974
+
975
+
976
+ def _dedupe(values: tuple[str, ...]) -> tuple[str, ...]:
977
+ result: list[str] = []
978
+ seen: set[str] = set()
979
+ for value in values:
980
+ text = str(value or "").strip()
981
+ if not text or text in seen:
982
+ continue
983
+ seen.add(text)
984
+ result.append(text)
985
+ return tuple(result)