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,244 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from collections.abc import Iterator, Mapping
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from ..request_estimation import estimate_message_tokens
9
+ from .cache_capabilities import CACHE_CONTROL_FIELD
10
+
11
+ _TEXT_BLOCK_TYPES = {"text", "input_text", "output_text"}
12
+ _DEFAULT_CACHE_CONTROL_MIN_TOKENS = 0
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class CacheControlApplication:
17
+ messages: list[dict[str, Any]]
18
+ applied: bool
19
+ eligible: bool
20
+ prefix_message_count: int
21
+ prefix_estimated_tokens: int
22
+ min_tokens: int
23
+ reason: str
24
+
25
+ def policy_metadata(self) -> dict[str, Any]:
26
+ return {
27
+ "eligible": self.eligible,
28
+ "used": self.applied,
29
+ "cacheable_prefix_estimated_tokens": self.prefix_estimated_tokens,
30
+ }
31
+
32
+
33
+ def apply_openai_compatible_cache_control_breakpoint(
34
+ messages: list[dict[str, Any]],
35
+ *,
36
+ cache_policy: Mapping[str, Any] | None,
37
+ ) -> CacheControlApplication:
38
+ copied = copy.deepcopy([message for message in messages if isinstance(message, dict)])
39
+ min_tokens = _cache_control_min_tokens(cache_policy)
40
+ prefix_count = cacheable_prefix_message_count(copied)
41
+ prefix_tokens = estimate_message_tokens(copied[:prefix_count])
42
+ if CACHE_CONTROL_FIELD not in _policy_emitted_fields(cache_policy):
43
+ return CacheControlApplication(
44
+ messages=copied,
45
+ applied=False,
46
+ eligible=False,
47
+ prefix_message_count=prefix_count,
48
+ prefix_estimated_tokens=prefix_tokens,
49
+ min_tokens=min_tokens,
50
+ reason="cache_control_not_emitted",
51
+ )
52
+ if count_cache_control_blocks(copied) > 0:
53
+ return CacheControlApplication(
54
+ messages=copied,
55
+ applied=True,
56
+ eligible=True,
57
+ prefix_message_count=prefix_count,
58
+ prefix_estimated_tokens=prefix_tokens,
59
+ min_tokens=min_tokens,
60
+ reason="existing_cache_control_block",
61
+ )
62
+ if prefix_count <= 0:
63
+ return CacheControlApplication(
64
+ messages=copied,
65
+ applied=False,
66
+ eligible=False,
67
+ prefix_message_count=prefix_count,
68
+ prefix_estimated_tokens=prefix_tokens,
69
+ min_tokens=min_tokens,
70
+ reason="no_cacheable_prefix",
71
+ )
72
+ if prefix_tokens < min_tokens:
73
+ return CacheControlApplication(
74
+ messages=copied,
75
+ applied=False,
76
+ eligible=False,
77
+ prefix_message_count=prefix_count,
78
+ prefix_estimated_tokens=prefix_tokens,
79
+ min_tokens=min_tokens,
80
+ reason="prefix_below_min_tokens",
81
+ )
82
+ for index in range(prefix_count - 1, -1, -1):
83
+ if _add_cache_control_to_message(copied[index]):
84
+ return CacheControlApplication(
85
+ messages=copied,
86
+ applied=True,
87
+ eligible=True,
88
+ prefix_message_count=prefix_count,
89
+ prefix_estimated_tokens=prefix_tokens,
90
+ min_tokens=min_tokens,
91
+ reason="cache_control_block_added",
92
+ )
93
+ return CacheControlApplication(
94
+ messages=copied,
95
+ applied=False,
96
+ eligible=False,
97
+ prefix_message_count=prefix_count,
98
+ prefix_estimated_tokens=prefix_tokens,
99
+ min_tokens=min_tokens,
100
+ reason="no_text_block_in_prefix",
101
+ )
102
+
103
+
104
+ def cacheable_prefix_message_count(messages: list[dict[str, Any]]) -> int:
105
+ for index in range(len(messages) - 1, -1, -1):
106
+ if str(messages[index].get("role") or "").strip().lower() == "user":
107
+ return max(0, index)
108
+ return max(0, len(messages) - 1)
109
+
110
+
111
+ def _is_request_payload(value: Mapping[str, Any]) -> bool:
112
+ return isinstance(value.get("messages"), (list, tuple))
113
+
114
+
115
+ def _iter_block_carriers(blocks: Any) -> Iterator[Mapping[str, Any]]:
116
+ if not isinstance(blocks, (list, tuple)):
117
+ return
118
+ for block in blocks:
119
+ if not isinstance(block, Mapping):
120
+ continue
121
+ yield block
122
+ yield from _iter_block_carriers(block.get("content"))
123
+
124
+
125
+ def _iter_cache_control_carriers(
126
+ value: Any,
127
+ *,
128
+ skip_root: bool = False,
129
+ ) -> Iterator[Mapping[str, Any]]:
130
+ # Only positions where providers accept cache_control: the payload top
131
+ # level, messages and their content blocks, system blocks, and the top
132
+ # level of each tools[] entry. Never nested tool input schemas or other
133
+ # structured data, where a field merely named "cache_control" is data.
134
+ if isinstance(value, Mapping):
135
+ if _is_request_payload(value):
136
+ if not skip_root:
137
+ yield value
138
+ yield from _iter_block_carriers(value.get("messages"))
139
+ system = value.get("system")
140
+ if isinstance(system, (list, tuple)):
141
+ yield from _iter_block_carriers(system)
142
+ tools = value.get("tools")
143
+ if isinstance(tools, (list, tuple)):
144
+ for tool in tools:
145
+ if isinstance(tool, Mapping):
146
+ yield tool
147
+ return
148
+ if not skip_root:
149
+ yield value
150
+ yield from _iter_block_carriers(value.get("content"))
151
+ return
152
+ if isinstance(value, (list, tuple)):
153
+ yield from _iter_block_carriers(value)
154
+
155
+
156
+ def count_cache_control_blocks(value: Any) -> int:
157
+ return sum(
158
+ 1 for carrier in _iter_cache_control_carriers(value) if CACHE_CONTROL_FIELD in carrier
159
+ )
160
+
161
+
162
+ def count_explicit_cache_control_blocks(value: Any) -> int:
163
+ return sum(
164
+ 1
165
+ for carrier in _iter_cache_control_carriers(value, skip_root=True)
166
+ if CACHE_CONTROL_FIELD in carrier
167
+ )
168
+
169
+
170
+ def explicit_cache_control_payloads(value: Any) -> list[dict[str, Any]]:
171
+ payloads: list[dict[str, Any]] = []
172
+ for carrier in _iter_cache_control_carriers(value, skip_root=True):
173
+ cache_control = carrier.get(CACHE_CONTROL_FIELD)
174
+ if isinstance(cache_control, Mapping):
175
+ payloads.append(copy.deepcopy(dict(cache_control)))
176
+ return payloads
177
+
178
+
179
+ def strip_cache_control_blocks(value: Any) -> bool:
180
+ stripped = False
181
+ for carrier in _iter_cache_control_carriers(value):
182
+ if isinstance(carrier, dict) and CACHE_CONTROL_FIELD in carrier:
183
+ carrier.pop(CACHE_CONTROL_FIELD, None)
184
+ stripped = True
185
+ return stripped
186
+
187
+
188
+ def _policy_emitted_fields(policy: Mapping[str, Any] | None) -> tuple[str, ...]:
189
+ if not isinstance(policy, Mapping):
190
+ return ()
191
+ raw = policy.get("emitted_fields")
192
+ if not isinstance(raw, (list, tuple)):
193
+ return ()
194
+ return tuple(str(item).strip() for item in raw if str(item).strip())
195
+
196
+
197
+ def _cache_control_min_tokens(policy: Mapping[str, Any] | None) -> int:
198
+ if not isinstance(policy, Mapping):
199
+ return _DEFAULT_CACHE_CONTROL_MIN_TOKENS
200
+ for key in ("min_tokens", "min_cacheable_tokens"):
201
+ try:
202
+ value = int(policy.get(key))
203
+ except (TypeError, ValueError):
204
+ continue
205
+ if value >= 0:
206
+ return value
207
+ return _DEFAULT_CACHE_CONTROL_MIN_TOKENS
208
+
209
+
210
+ def _add_cache_control_to_message(message: dict[str, Any]) -> bool:
211
+ role = str(message.get("role") or "").strip().lower()
212
+ if role == "tool":
213
+ return False
214
+ content = message.get("content")
215
+ if isinstance(content, list):
216
+ for index in range(len(content) - 1, -1, -1):
217
+ item = content[index]
218
+ if not _is_text_block(item):
219
+ continue
220
+ copied_item = dict(item)
221
+ copied_item[CACHE_CONTROL_FIELD] = {"type": "ephemeral"}
222
+ content[index] = copied_item
223
+ return True
224
+ return False
225
+ if isinstance(content, str) and content.strip():
226
+ message["content"] = [
227
+ {
228
+ "type": "text",
229
+ "text": content,
230
+ CACHE_CONTROL_FIELD: {"type": "ephemeral"},
231
+ }
232
+ ]
233
+ return True
234
+ return False
235
+
236
+
237
+ def _is_text_block(value: Any) -> bool:
238
+ if not isinstance(value, Mapping) or CACHE_CONTROL_FIELD in value:
239
+ return False
240
+ block_type = str(value.get("type") or "text").strip().lower()
241
+ if block_type not in _TEXT_BLOCK_TYPES:
242
+ return False
243
+ text = value.get("text")
244
+ return isinstance(text, str) and bool(text.strip())
@@ -0,0 +1,388 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import re
7
+ from collections.abc import Mapping
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from ..config import (
13
+ resolve_anthropic_prompt_cache_enabled,
14
+ resolve_anthropic_prompt_cache_ttl,
15
+ resolve_prompt_cache_mode,
16
+ )
17
+ from .cache_capabilities import (
18
+ CACHE_CONTROL_FIELD,
19
+ CACHED_CONTENT_FIELD,
20
+ OPENROUTER_SESSION_ID_FIELD,
21
+ OPENROUTER_SESSION_ID_HEADER_FIELD,
22
+ PROMPT_CACHE_KEY_FIELD,
23
+ PROMPT_CACHE_RETENTION_FIELD,
24
+ XAI_CONVERSATION_ID_HEADER_FIELD,
25
+ EffectiveCacheCapability,
26
+ resolve_effective_cache_capability,
27
+ )
28
+ from .protocols import ProviderProtocolCapabilities
29
+
30
+ if TYPE_CHECKING:
31
+ from ..config import AppConfig
32
+
33
+
34
+ _CACHE_KEY_LABEL_RE = re.compile(r"[^a-z0-9_.:-]+")
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ResolvedPromptCachePolicy:
39
+ prompt_cache_key: str | None = None
40
+ prompt_cache_retention: str | None = None
41
+ anthropic_cache_control_enabled: bool = False
42
+ anthropic_cache_control_ttl: str = "5m"
43
+ gemini_explicit_cached_content_enabled: bool = False
44
+ gemini_cached_content_ttl: str | None = None
45
+ mode: str = "manual"
46
+ strategy: str = "none"
47
+ capability_source: str = "default"
48
+ allowed_fields: tuple[str, ...] = ()
49
+ emitted_fields: tuple[str, ...] = ()
50
+ trusted_usage_fields: tuple[str, ...] = ()
51
+ usage_schema: str = "none"
52
+ min_cacheable_tokens: int | None = None
53
+ request_field_values: tuple[tuple[str, str], ...] = ()
54
+ warnings: tuple[str, ...] = ()
55
+
56
+ @property
57
+ def status(self) -> str:
58
+ if self.mode == "off":
59
+ return "disabled"
60
+ if self.emitted_fields:
61
+ return "enabled"
62
+ if self.strategy == "none":
63
+ return "unsupported"
64
+ if not self.allowed_fields and self.warnings:
65
+ return "unsupported"
66
+ return "available"
67
+
68
+ def telemetry_metadata(self) -> dict[str, Any]:
69
+ metadata: dict[str, Any] = {
70
+ "status": self.status,
71
+ "strategy": self.strategy,
72
+ "mode": "automatic" if self.mode == "auto" else self.mode,
73
+ "enabled": self.status == "enabled",
74
+ "capability_source": self.capability_source,
75
+ "source": self.capability_source,
76
+ "allowed_fields": list(self.allowed_fields),
77
+ "emitted_fields": list(self.emitted_fields),
78
+ "trusted_usage_fields": list(self.trusted_usage_fields),
79
+ "usage_schema": self.usage_schema,
80
+ "emits_request_fields": bool(self.allowed_fields),
81
+ }
82
+ if self.prompt_cache_retention:
83
+ metadata["retention"] = self.prompt_cache_retention
84
+ if self.prompt_cache_key and PROMPT_CACHE_KEY_FIELD in self.emitted_fields:
85
+ metadata["prompt_cache_key_hash"] = hashlib.sha256(
86
+ self.prompt_cache_key.encode("utf-8")
87
+ ).hexdigest()
88
+ if self.anthropic_cache_control_enabled:
89
+ metadata["ttl"] = self.anthropic_cache_control_ttl
90
+ elif self.gemini_explicit_cached_content_enabled and self.gemini_cached_content_ttl:
91
+ metadata["ttl"] = self.gemini_cached_content_ttl
92
+ if self.min_cacheable_tokens is not None:
93
+ metadata["min_tokens"] = self.min_cacheable_tokens
94
+ if self.warnings:
95
+ metadata["warnings"] = list(self.warnings)
96
+ return metadata
97
+
98
+
99
+ def build_prompt_cache_namespace(
100
+ *,
101
+ workspace_root: str | os.PathLike[str] | None = None,
102
+ role: str | None = None,
103
+ profile_name: str | None = None,
104
+ session_id: str | None = None,
105
+ ) -> str | None:
106
+ parts: list[tuple[str, str]] = []
107
+ if workspace_root is not None:
108
+ parts.append(("workspace", _normalize_workspace_root(workspace_root)))
109
+ normalized_role = str(role or "").strip().lower()
110
+ if normalized_role:
111
+ parts.append(("role", normalized_role))
112
+ normalized_profile = str(profile_name or "").strip().lower()
113
+ if normalized_profile:
114
+ parts.append(("profile", normalized_profile))
115
+ normalized_session = str(session_id or "").strip().lower()
116
+ if normalized_session:
117
+ parts.append(("session", normalized_session))
118
+ if not parts:
119
+ return None
120
+ return json.dumps(parts, ensure_ascii=False, separators=(",", ":"))
121
+
122
+
123
+ def derive_prompt_cache_stream_key(
124
+ *,
125
+ session_id: str,
126
+ parent_session_id: str | None = None,
127
+ ) -> str | None:
128
+ """Return one stable cache-affinity key for a parent or its child stream."""
129
+ normalized_parent = str(parent_session_id or "").strip()
130
+ if normalized_parent:
131
+ digest = hashlib.blake2s(
132
+ f"{normalized_parent}:children".encode(),
133
+ digest_size=12,
134
+ ).hexdigest()
135
+ return f"alysis:children:{digest}"
136
+ normalized_session = str(session_id or "").strip()
137
+ return normalized_session or None
138
+
139
+
140
+ def resolve_prompt_cache_policy(
141
+ *,
142
+ cfg: AppConfig | None,
143
+ capabilities: ProviderProtocolCapabilities | None,
144
+ provider_key: str,
145
+ protocol: str,
146
+ model: str,
147
+ prompt_cache_key: str | None,
148
+ prompt_cache_retention: str | None,
149
+ prompt_cache_namespace: str | None = None,
150
+ cache_capability: EffectiveCacheCapability | None = None,
151
+ ) -> ResolvedPromptCachePolicy:
152
+ mode = resolve_prompt_cache_mode(cfg)
153
+ ttl = resolve_anthropic_prompt_cache_ttl(cfg)
154
+ effective_capability = cache_capability or resolve_effective_cache_capability(
155
+ provider_key=provider_key,
156
+ protocol=protocol,
157
+ model=model,
158
+ transport_capabilities=capabilities,
159
+ )
160
+ if mode == "off" or not effective_capability.enabled:
161
+ return ResolvedPromptCachePolicy(
162
+ mode=mode,
163
+ anthropic_cache_control_ttl=ttl,
164
+ strategy=effective_capability.strategy,
165
+ capability_source=effective_capability.source,
166
+ allowed_fields=effective_capability.emitted_fields,
167
+ emitted_fields=(),
168
+ trusted_usage_fields=effective_capability.trusted_usage_fields,
169
+ usage_schema=effective_capability.usage_schema,
170
+ min_cacheable_tokens=effective_capability.min_cacheable_tokens,
171
+ warnings=effective_capability.warnings,
172
+ )
173
+
174
+ strategy = effective_capability.strategy
175
+ cache_config = getattr(cfg, "cache", None)
176
+ prompt_cache_key_enabled = cache_config is None or bool(
177
+ getattr(cache_config, "prompt_cache_key_enabled", True)
178
+ )
179
+ explicit_key = _clean_optional(prompt_cache_key) if prompt_cache_key_enabled else None
180
+ explicit_retention = _clean_optional(prompt_cache_retention)
181
+ effective_key = explicit_key
182
+ needs_affinity_key = any(
183
+ field
184
+ in {
185
+ PROMPT_CACHE_KEY_FIELD,
186
+ OPENROUTER_SESSION_ID_FIELD,
187
+ OPENROUTER_SESSION_ID_HEADER_FIELD,
188
+ XAI_CONVERSATION_ID_HEADER_FIELD,
189
+ }
190
+ for field in effective_capability.emitted_fields
191
+ )
192
+ if prompt_cache_key_enabled and mode == "auto" and effective_key is None and needs_affinity_key:
193
+ effective_key = _auto_prompt_cache_key(
194
+ provider_key=provider_key,
195
+ protocol=protocol,
196
+ model=model,
197
+ namespace=prompt_cache_namespace,
198
+ )
199
+
200
+ anthropic_enabled = False
201
+ if effective_capability.supports_cache_control:
202
+ anthropic_enabled = mode == "auto" or resolve_anthropic_prompt_cache_enabled(cfg)
203
+ gemini_explicit_enabled = (
204
+ effective_capability.supports_explicit_cached_content and mode == "auto"
205
+ )
206
+ warnings = effective_capability.warnings
207
+ gemini_ttl: str | None = None
208
+ if gemini_explicit_enabled:
209
+ gemini_ttl, gemini_ttl_fallback = _google_duration_from_retention(explicit_retention)
210
+ if gemini_ttl_fallback:
211
+ warnings = (
212
+ *warnings,
213
+ f"gemini_cached_content_ttl_fallback_{gemini_ttl}_for_unparseable_retention",
214
+ )
215
+ request_field_values = _policy_request_field_values(
216
+ emitted_fields=effective_capability.emitted_fields,
217
+ prompt_cache_key=(
218
+ effective_key
219
+ if effective_capability.supports_prompt_cache_key and effective_key
220
+ else None
221
+ ),
222
+ prompt_cache_retention=(
223
+ explicit_retention
224
+ if effective_capability.supports_prompt_cache_retention and explicit_retention
225
+ else None
226
+ ),
227
+ affinity_key=effective_key,
228
+ anthropic_cache_control_enabled=anthropic_enabled,
229
+ gemini_explicit_cached_content_enabled=gemini_explicit_enabled,
230
+ )
231
+ emitted_fields = _policy_emitted_fields(
232
+ request_field_values=request_field_values,
233
+ )
234
+
235
+ return ResolvedPromptCachePolicy(
236
+ prompt_cache_key=(
237
+ effective_key
238
+ if effective_capability.supports_prompt_cache_key and effective_key
239
+ else None
240
+ ),
241
+ prompt_cache_retention=(
242
+ explicit_retention
243
+ if effective_capability.supports_prompt_cache_retention and explicit_retention
244
+ else None
245
+ ),
246
+ anthropic_cache_control_enabled=anthropic_enabled,
247
+ anthropic_cache_control_ttl=ttl,
248
+ gemini_explicit_cached_content_enabled=gemini_explicit_enabled,
249
+ gemini_cached_content_ttl=gemini_ttl,
250
+ mode=mode,
251
+ strategy=strategy,
252
+ capability_source=effective_capability.source,
253
+ allowed_fields=effective_capability.emitted_fields,
254
+ emitted_fields=emitted_fields,
255
+ trusted_usage_fields=effective_capability.trusted_usage_fields,
256
+ usage_schema=effective_capability.usage_schema,
257
+ min_cacheable_tokens=effective_capability.min_cacheable_tokens,
258
+ request_field_values=request_field_values,
259
+ warnings=warnings,
260
+ )
261
+
262
+
263
+ def prompt_cache_policy_diagnostic_rows(
264
+ policy: ResolvedPromptCachePolicy,
265
+ ) -> tuple[tuple[str, str], ...]:
266
+ allowed_fields = ", ".join(policy.allowed_fields) if policy.allowed_fields else "none"
267
+ emitted_fields = ", ".join(policy.emitted_fields) if policy.emitted_fields else "none"
268
+ usage_fields = ", ".join(policy.trusted_usage_fields) if policy.trusted_usage_fields else "none"
269
+ warnings = "; ".join(policy.warnings) if policy.warnings else "none"
270
+ return (
271
+ ("cache_status", policy.status),
272
+ ("cache_strategy", policy.strategy),
273
+ ("cache_capability_source", policy.capability_source),
274
+ ("cache_allowed_fields", allowed_fields),
275
+ ("cache_emitted_fields", emitted_fields),
276
+ ("cache_usage_fields", usage_fields),
277
+ ("cache_usage_schema", policy.usage_schema),
278
+ ("cache_warnings", warnings),
279
+ )
280
+
281
+
282
+ def merge_cache_policy_metadata(
283
+ base: Mapping[str, Any] | None,
284
+ active: Mapping[str, Any] | None,
285
+ ) -> dict[str, Any] | None:
286
+ merged: dict[str, Any] = {}
287
+ if isinstance(base, Mapping):
288
+ merged.update(dict(base))
289
+ if isinstance(active, Mapping):
290
+ merged.update(dict(active))
291
+ if active.get("enabled") is True:
292
+ merged["status"] = "enabled"
293
+ merged["enabled"] = True
294
+ return merged or None
295
+
296
+
297
+ def _policy_emitted_fields(
298
+ *,
299
+ request_field_values: tuple[tuple[str, str], ...],
300
+ ) -> tuple[str, ...]:
301
+ return tuple(field for field, _value in request_field_values)
302
+
303
+
304
+ def _policy_request_field_values(
305
+ *,
306
+ emitted_fields: tuple[str, ...],
307
+ prompt_cache_key: str | None,
308
+ prompt_cache_retention: str | None,
309
+ affinity_key: str | None,
310
+ anthropic_cache_control_enabled: bool,
311
+ gemini_explicit_cached_content_enabled: bool,
312
+ ) -> tuple[tuple[str, str], ...]:
313
+ values: list[tuple[str, str]] = []
314
+ if prompt_cache_key and PROMPT_CACHE_KEY_FIELD in emitted_fields:
315
+ values.append((PROMPT_CACHE_KEY_FIELD, prompt_cache_key))
316
+ if prompt_cache_retention and PROMPT_CACHE_RETENTION_FIELD in emitted_fields:
317
+ values.append((PROMPT_CACHE_RETENTION_FIELD, prompt_cache_retention))
318
+ if affinity_key:
319
+ for field in (
320
+ OPENROUTER_SESSION_ID_FIELD,
321
+ OPENROUTER_SESSION_ID_HEADER_FIELD,
322
+ XAI_CONVERSATION_ID_HEADER_FIELD,
323
+ ):
324
+ if field in emitted_fields:
325
+ values.append((field, affinity_key))
326
+ if anthropic_cache_control_enabled and CACHE_CONTROL_FIELD in emitted_fields:
327
+ values.append((CACHE_CONTROL_FIELD, "ephemeral"))
328
+ if gemini_explicit_cached_content_enabled and CACHED_CONTENT_FIELD in emitted_fields:
329
+ values.append((CACHED_CONTENT_FIELD, "enabled"))
330
+ return tuple(values)
331
+
332
+
333
+ def _clean_optional(value: str | None) -> str | None:
334
+ cleaned = str(value or "").strip()
335
+ return cleaned or None
336
+
337
+
338
+ def _normalize_workspace_root(root: str | os.PathLike[str]) -> str:
339
+ try:
340
+ return os.fspath(Path(root).expanduser().resolve(strict=False))
341
+ except (OSError, RuntimeError, ValueError):
342
+ return os.fspath(root)
343
+
344
+
345
+ def _auto_prompt_cache_key(
346
+ *,
347
+ provider_key: str,
348
+ protocol: str,
349
+ model: str,
350
+ namespace: str | None,
351
+ ) -> str | None:
352
+ normalized_namespace = str(namespace or "").strip()
353
+ if not normalized_namespace:
354
+ return None
355
+ provider_label = _safe_cache_label(provider_key or "provider")
356
+ payload = {
357
+ "version": 1,
358
+ "provider": str(provider_key or "").strip().lower(),
359
+ "protocol": str(protocol or "").strip().lower(),
360
+ "model": str(model or "").strip().lower(),
361
+ "namespace": normalized_namespace,
362
+ }
363
+ digest = hashlib.blake2s(
364
+ json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8"),
365
+ digest_size=10,
366
+ ).hexdigest()
367
+ return f"alysis:{provider_label}:{digest}"
368
+
369
+
370
+ def _safe_cache_label(value: str) -> str:
371
+ normalized = _CACHE_KEY_LABEL_RE.sub("-", str(value or "").strip().lower()).strip("-")
372
+ return normalized[:32] or "provider"
373
+
374
+
375
+ def _google_duration_from_retention(value: str | None) -> tuple[str, bool]:
376
+ cleaned = _clean_optional(value)
377
+ if cleaned is None:
378
+ return "3600s", False
379
+ normalized = cleaned.lower()
380
+ if normalized.endswith("s") and normalized[:-1].isdigit():
381
+ return normalized, False
382
+ if normalized.endswith("m") and normalized[:-1].isdigit():
383
+ return f"{int(normalized[:-1]) * 60}s", False
384
+ if normalized.endswith("h") and normalized[:-1].isdigit():
385
+ return f"{int(normalized[:-1]) * 3600}s", False
386
+ # Retention values from other providers (e.g. "in-memory") are not valid
387
+ # Google durations and would 400 every cachedContents create.
388
+ return "3600s", True