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,108 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Iterable, Iterator
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from .types import LLMError
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class SSEFrame:
13
+ """A parsed Server-Sent Events frame.
14
+
15
+ The parser intentionally keeps this structure provider-neutral so native
16
+ Responses, Messages, and GenerateContent streaming paths can share it.
17
+ """
18
+
19
+ event: str
20
+ data: str
21
+ event_id: str | None = None
22
+ retry: int | None = None
23
+
24
+
25
+ def iter_sse_frames(
26
+ lines: Iterable[str | bytes],
27
+ *,
28
+ done_sentinels: tuple[str, ...] = ("[DONE]",),
29
+ include_done: bool = False,
30
+ ) -> Iterator[SSEFrame]:
31
+ """Yield SSE frames from an iterable of response lines.
32
+
33
+ Handles `event:`, multi-line `data:`, comments, blank-line frame
34
+ boundaries, optional `id:` / `retry:`, and `[DONE]`-style sentinels.
35
+ """
36
+
37
+ event = "message"
38
+ event_id: str | None = None
39
+ retry: int | None = None
40
+ data_lines: list[str] = []
41
+
42
+ def _flush() -> SSEFrame | None:
43
+ nonlocal event, event_id, retry, data_lines
44
+ if not data_lines and event == "message" and event_id is None and retry is None:
45
+ return None
46
+ frame = SSEFrame(
47
+ event=event or "message",
48
+ data="\n".join(data_lines),
49
+ event_id=event_id,
50
+ retry=retry,
51
+ )
52
+ event = "message"
53
+ event_id = None
54
+ retry = None
55
+ data_lines = []
56
+ return frame
57
+
58
+ for raw_line in lines:
59
+ if isinstance(raw_line, bytes):
60
+ line = raw_line.decode("utf-8", errors="replace")
61
+ else:
62
+ line = raw_line
63
+ line = line.rstrip("\r\n")
64
+ if line == "":
65
+ frame = _flush()
66
+ if frame is None:
67
+ continue
68
+ if frame.data.strip() in done_sentinels and not include_done:
69
+ return
70
+ yield frame
71
+ continue
72
+ if line.startswith(":"):
73
+ continue
74
+
75
+ if ":" in line:
76
+ field, value = line.split(":", 1)
77
+ if value.startswith(" "):
78
+ value = value[1:]
79
+ else:
80
+ field, value = line, ""
81
+
82
+ if field == "event":
83
+ event = value
84
+ elif field == "data":
85
+ data_lines.append(value)
86
+ elif field == "id":
87
+ event_id = value
88
+ elif field == "retry":
89
+ try:
90
+ retry = int(value)
91
+ except ValueError:
92
+ retry = None
93
+
94
+ frame = _flush()
95
+ if frame is not None:
96
+ if frame.data.strip() in done_sentinels and not include_done:
97
+ return
98
+ yield frame
99
+
100
+
101
+ def parse_sse_json_frame(frame: SSEFrame, *, stream_name: str) -> Any:
102
+ """Parse a frame's JSON payload with a deterministic provider error."""
103
+
104
+ try:
105
+ return json.loads(frame.data)
106
+ except json.JSONDecodeError as exc:
107
+ event_suffix = f" event={frame.event!r}" if frame.event else ""
108
+ raise LLMError(f"{stream_name} emitted malformed JSON{event_suffix}: {exc.msg}") from exc
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS = "anthropic_sampling_parameters_deprecated"
6
+ GEMINI_3_DEFAULT_TEMPERATURE = "gemini_3_default_temperature"
7
+ DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED = "deepseek_thinking_temperature_unsupported"
8
+ QWEN_QVQ_DEFAULT_TEMPERATURE = "qwen_qvq_default_temperature"
9
+ KIMI_FIXED_TEMPERATURE = "kimi_fixed_temperature"
10
+
11
+ _CLAUDE_OPUS_VERSION_RE = re.compile(r"claude[-_.]opus[-_.](?P<major>\d+)(?:[-_.](?P<minor>\d+))?")
12
+ _CLAUDE_SONNET_VERSION_RE = re.compile(
13
+ r"claude[-_.]sonnet[-_.](?P<major>\d+)(?:[-_.](?P<minor>\d+))?"
14
+ )
15
+ _GEMINI_3_RE = re.compile(r"(?:^|[/.:_-])gemini[-_.]?3(?:[-_.]|$)")
16
+ _DEEPSEEK_V4_RE = re.compile(r"(?:^|[/.:_-])deepseek[-_.]v?4(?:[-_.]|$)")
17
+ _QWEN_QVQ_RE = re.compile(r"(?:^|[/.:_-])qvq(?:[-_.]|$)")
18
+ _CURRENT_KIMI_RE = re.compile(r"(?:^|[/.:_-])kimi[-_.]?k(?:3|2[-_.]?(?:6|7))(?:[-_.]|$)")
19
+
20
+
21
+ def documented_temperature_omit_reason(
22
+ model: str,
23
+ *,
24
+ provider_key: str | None = None,
25
+ thinking_enabled: bool | None = None,
26
+ ) -> str | None:
27
+ """Return the documented reason an optional temperature must be omitted.
28
+
29
+ The checks deliberately use model-family patterns rather than a closed list of
30
+ snapshots so provider aliases, gateway-qualified names, and later compatible
31
+ releases receive the same safe request shape.
32
+ """
33
+
34
+ normalized_model = str(model or "").strip().casefold()
35
+ if not normalized_model:
36
+ return None
37
+
38
+ opus = _CLAUDE_OPUS_VERSION_RE.search(normalized_model)
39
+ if opus is not None:
40
+ major = int(opus.group("major"))
41
+ minor_text = opus.group("minor")
42
+ minor = int(minor_text) if minor_text is not None else None
43
+ # Pre-4.6 snapshot IDs use a date after the major version (for example
44
+ # claude-opus-4-20250514). Do not mistake that date for a minor release.
45
+ if major > 4 or (major == 4 and minor is not None and 7 <= minor < 100):
46
+ return ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS
47
+
48
+ sonnet = _CLAUDE_SONNET_VERSION_RE.search(normalized_model)
49
+ if sonnet is not None and int(sonnet.group("major")) >= 5:
50
+ return ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS
51
+
52
+ if _GEMINI_3_RE.search(normalized_model):
53
+ return GEMINI_3_DEFAULT_TEMPERATURE
54
+
55
+ if _QWEN_QVQ_RE.search(normalized_model):
56
+ return QWEN_QVQ_DEFAULT_TEMPERATURE
57
+
58
+ normalized_provider = str(provider_key or "").strip().casefold()
59
+ if normalized_provider == "moonshot" and _CURRENT_KIMI_RE.search(normalized_model):
60
+ return KIMI_FIXED_TEMPERATURE
61
+ if (
62
+ normalized_provider == "deepseek"
63
+ and thinking_enabled is not False
64
+ and _DEEPSEEK_V4_RE.search(normalized_model)
65
+ ):
66
+ return DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED
67
+
68
+ return None
69
+
70
+
71
+ __all__ = [
72
+ "ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS",
73
+ "DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED",
74
+ "GEMINI_3_DEFAULT_TEMPERATURE",
75
+ "KIMI_FIXED_TEMPERATURE",
76
+ "QWEN_QVQ_DEFAULT_TEMPERATURE",
77
+ "documented_temperature_omit_reason",
78
+ ]
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, replace
4
+ from enum import StrEnum
5
+ from typing import Any
6
+
7
+ from ..error_text import sanitize_error_text_for_output
8
+
9
+
10
+ class LLMError(RuntimeError):
11
+ """Provider/model error whose public message is safe to persist and display."""
12
+
13
+ def __init__(self, message: object = "") -> None:
14
+ super().__init__(sanitize_error_text_for_output(message))
15
+
16
+
17
+ class LLMStreamNoProgressError(LLMError):
18
+ """A streaming attempt produced no meaningful provider payload in time."""
19
+
20
+
21
+ class UsageConfidence(StrEnum):
22
+ AUTHORITATIVE = "authoritative"
23
+ REPORTED = "reported"
24
+ ESTIMATED = "estimated"
25
+
26
+
27
+ class UsageSource(StrEnum):
28
+ PROVIDER_RESPONSE = "provider_response"
29
+ PROVIDER_COUNT = "provider_count"
30
+ LOCAL_ESTIMATE = "local_estimate"
31
+ MIXED = "mixed"
32
+
33
+
34
+ class BillingMode(StrEnum):
35
+ """How the selected route is paid for from the user's perspective."""
36
+
37
+ METERED_API = "metered_api"
38
+ SUBSCRIPTION = "subscription"
39
+ INCLUDED = "included"
40
+ LOCAL = "local"
41
+ UNKNOWN = "unknown"
42
+
43
+
44
+ class CostSource(StrEnum):
45
+ """Provenance of a per-call dollar amount."""
46
+
47
+ PROVIDER_REPORTED = "provider_reported"
48
+ CATALOG_ESTIMATE = "catalog_estimate"
49
+ SUBSCRIPTION = "subscription"
50
+ INCLUDED = "included"
51
+ LOCAL = "local"
52
+ UNKNOWN = "unknown"
53
+
54
+
55
+ class ReasoningOutputKind(StrEnum):
56
+ """Visibility class for provider-produced reasoning output.
57
+
58
+ ``SUMMARY`` is safe, provider-generated explanatory text intended for
59
+ display. ``PROVIDER_REASONING`` is a provider-visible reasoning channel
60
+ that may contain a much more detailed chain and must not be rendered by
61
+ the default chat trace.
62
+ """
63
+
64
+ SUMMARY = "summary"
65
+ PROVIDER_REASONING = "provider_reasoning"
66
+
67
+
68
+ class AssistantResponsePhase(StrEnum):
69
+ """Provider-declared lifecycle phase for assistant-visible output."""
70
+
71
+ COMMENTARY = "commentary"
72
+ FINAL_ANSWER = "final_answer"
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class ReasoningOutput:
77
+ """Normalized reasoning material returned alongside an assistant answer."""
78
+
79
+ text: str
80
+ kind: ReasoningOutputKind
81
+ provider: str | None = None
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class UsageContract:
86
+ """Protocol-level guarantees after provider usage has been normalized."""
87
+
88
+ response_usage_confidence: UsageConfidence = UsageConfidence.REPORTED
89
+ normalized_output_includes_reasoning: bool = True
90
+ input_token_count_strategy: str = "none"
91
+ billing_mode: BillingMode = BillingMode.METERED_API
92
+
93
+ @property
94
+ def response_usage_authoritative(self) -> bool:
95
+ return self.response_usage_confidence == UsageConfidence.AUTHORITATIVE
96
+
97
+ @property
98
+ def supports_input_token_count(self) -> bool:
99
+ return self.input_token_count_strategy != "none"
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class InputTokenCount:
104
+ input_tokens: int
105
+ source: UsageSource = UsageSource.PROVIDER_COUNT
106
+ confidence: UsageConfidence = UsageConfidence.AUTHORITATIVE
107
+ raw_provider_usage: dict[str, Any] | None = None
108
+
109
+ def __post_init__(self) -> None:
110
+ if (
111
+ isinstance(self.input_tokens, bool)
112
+ or not isinstance(self.input_tokens, int)
113
+ or self.input_tokens < 0
114
+ ):
115
+ raise ValueError("input token count must be a non-negative integer")
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class ToolCall:
120
+ id: str
121
+ name: str
122
+ arguments: dict[str, Any]
123
+ provider_metadata: dict[str, Any] | None = None
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class LLMUsage:
128
+ prompt_tokens: int | None
129
+ completion_tokens: int | None
130
+ total_tokens: int | None
131
+ cached_prompt_tokens: int | None = None
132
+ input_tokens_uncached: int | None = None
133
+ # True when the uncached figure was computed from other reported counts
134
+ # rather than sent by the provider, so display can avoid claiming it as
135
+ # provider-reported.
136
+ input_tokens_uncached_derived: bool = False
137
+ cache_read_input_tokens: int | None = None
138
+ cache_creation_input_tokens: int | None = None
139
+ cache_creation_5m_input_tokens: int | None = None
140
+ cache_creation_1h_input_tokens: int | None = None
141
+ reasoning_tokens: int | None = None
142
+ provider_cost_usd: float | None = None
143
+ provider_cost_currency: str | None = None
144
+ raw_provider_usage: dict[str, Any] | None = None
145
+ source: UsageSource = UsageSource.PROVIDER_RESPONSE
146
+ confidence: UsageConfidence = UsageConfidence.REPORTED
147
+ output_includes_reasoning: bool = True
148
+ total_includes_reasoning: bool = True
149
+
150
+ def normalized(self) -> LLMUsage:
151
+ """Return the provider-independent invariant used by accounting.
152
+
153
+ After normalization, completion/output and total include reasoning,
154
+ while ``reasoning_tokens`` remains an informational subset.
155
+ """
156
+ reasoning = self.reasoning_tokens
157
+ if reasoning is None or reasoning <= 0:
158
+ return replace(
159
+ self,
160
+ output_includes_reasoning=True,
161
+ total_includes_reasoning=True,
162
+ )
163
+ completion = self.completion_tokens
164
+ total = self.total_tokens
165
+ if completion is not None and not self.output_includes_reasoning:
166
+ completion += reasoning
167
+ if total is not None and not self.total_includes_reasoning:
168
+ total += reasoning
169
+ return replace(
170
+ self,
171
+ completion_tokens=completion,
172
+ total_tokens=total,
173
+ output_includes_reasoning=True,
174
+ total_includes_reasoning=True,
175
+ )
176
+
177
+ @property
178
+ def cache_read_tokens(self) -> int | None:
179
+ return (
180
+ self.cache_read_input_tokens
181
+ if self.cache_read_input_tokens is not None
182
+ else self.cached_prompt_tokens
183
+ )
184
+
185
+
186
+ @dataclass(frozen=True)
187
+ class LLMResponse:
188
+ content: str
189
+ tool_calls: list[ToolCall]
190
+ raw: dict[str, Any]
191
+ response_model: str | None = None
192
+ usage: LLMUsage | None = None
193
+ provider_metadata: dict[str, Any] | None = None
194
+ reasoning: tuple[ReasoningOutput, ...] = ()
195
+ assistant_phase: AssistantResponsePhase | None = None
@@ -0,0 +1,222 @@
1
+ """Normalize usage payloads shared by OpenAI-compatible provider routes.
2
+
3
+ Compatibility describes the transport envelope, not the provider's usage
4
+ schema. This module deliberately decodes documented *shapes* instead of
5
+ maintaining a provider-name switch: gateways and first-party endpoints often
6
+ surface the same cache fields, while a single gateway can return several
7
+ upstream schemas.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import copy
13
+ from collections.abc import Mapping
14
+ from typing import Any
15
+
16
+ from .types import LLMUsage
17
+
18
+
19
+ def _non_negative_int(value: Any) -> int | None:
20
+ if isinstance(value, bool):
21
+ return None
22
+ try:
23
+ parsed = int(value) if value is not None else None
24
+ except (TypeError, ValueError):
25
+ return None
26
+ return parsed if parsed is None or parsed >= 0 else None
27
+
28
+
29
+ def _non_negative_float(value: Any) -> float | None:
30
+ if isinstance(value, bool):
31
+ return None
32
+ try:
33
+ parsed = float(value) if value is not None else None
34
+ except (TypeError, ValueError):
35
+ return None
36
+ return parsed if parsed is None or parsed >= 0 else None
37
+
38
+
39
+ def _mapping(value: Any) -> Mapping[str, Any]:
40
+ return value if isinstance(value, Mapping) else {}
41
+
42
+
43
+ def _first_int(*values: Any) -> int | None:
44
+ for value in values:
45
+ parsed = _non_negative_int(value)
46
+ if parsed is not None:
47
+ return parsed
48
+ return None
49
+
50
+
51
+ def _provider_cost_usd(
52
+ raw: Mapping[str, Any], *, provider_key: str | None
53
+ ) -> tuple[float | None, str | None]:
54
+ """Return only provider amounts whose unit is explicitly USD-compatible.
55
+
56
+ Perplexity-style payloads identify ``total_cost`` and may name the
57
+ currency. OpenRouter documents its numeric ``usage.cost`` as the charged
58
+ account-credit amount; OpenRouter credits are USD-denominated. A generic
59
+ numeric ``cost`` is otherwise ambiguous and is intentionally ignored.
60
+ """
61
+
62
+ cost = raw.get("cost")
63
+ if isinstance(cost, Mapping):
64
+ currency = str(cost.get("currency") or "USD").strip().upper()
65
+ if currency != "USD":
66
+ return None, currency or None
67
+ total = _non_negative_float(cost.get("total_cost"))
68
+ if total is None:
69
+ total = _non_negative_float(cost.get("total"))
70
+ return total, "USD" if total is not None else None
71
+
72
+ normalized_provider = str(provider_key or "").strip().lower()
73
+ if normalized_provider == "openrouter":
74
+ amount = _non_negative_float(cost)
75
+ if amount is not None:
76
+ return amount, "USD"
77
+
78
+ currency = str(raw.get("currency") or "").strip().upper()
79
+ if currency in {"", "USD"}:
80
+ amount = _non_negative_float(raw.get("total_cost"))
81
+ if amount is not None:
82
+ return amount, "USD"
83
+ return None, currency or None
84
+
85
+
86
+ def parse_compatible_usage(
87
+ raw: Any,
88
+ *,
89
+ provider_key: str | None = None,
90
+ responses_shape: bool = False,
91
+ ) -> LLMUsage | None:
92
+ """Decode common provider usage variants without inventing absent values."""
93
+
94
+ if not isinstance(raw, Mapping):
95
+ return None
96
+
97
+ prompt_details = _mapping(raw.get("prompt_tokens_details"))
98
+ input_details = _mapping(raw.get("input_tokens_details"))
99
+ completion_details = _mapping(raw.get("completion_tokens_details"))
100
+ output_details = _mapping(raw.get("output_tokens_details"))
101
+ token_group = _mapping(raw.get("tokens"))
102
+ billed_units = _mapping(raw.get("billed_units"))
103
+
104
+ if responses_shape:
105
+ prompt = _first_int(
106
+ raw.get("input_tokens"), raw.get("prompt_tokens"), token_group.get("input_tokens")
107
+ )
108
+ completion = _first_int(
109
+ raw.get("output_tokens"),
110
+ raw.get("completion_tokens"),
111
+ token_group.get("output_tokens"),
112
+ )
113
+ else:
114
+ prompt = _first_int(
115
+ raw.get("prompt_tokens"), raw.get("input_tokens"), token_group.get("input_tokens")
116
+ )
117
+ completion = _first_int(
118
+ raw.get("completion_tokens"),
119
+ raw.get("output_tokens"),
120
+ token_group.get("output_tokens"),
121
+ )
122
+
123
+ cache_read = _first_int(
124
+ prompt_details.get("cached_tokens"),
125
+ input_details.get("cached_tokens"),
126
+ prompt_details.get("cache_read_input_tokens"),
127
+ input_details.get("cache_read_input_tokens"),
128
+ raw.get("cache_read_input_tokens"),
129
+ raw.get("cached_prompt_tokens"),
130
+ raw.get("cached_tokens"),
131
+ raw.get("prompt_cache_hit_tokens"),
132
+ raw.get("cached_prompt_text_tokens"),
133
+ )
134
+ cache_write = _first_int(
135
+ prompt_details.get("cache_write_tokens"),
136
+ input_details.get("cache_write_tokens"),
137
+ prompt_details.get("cache_creation_input_tokens"),
138
+ input_details.get("cache_creation_input_tokens"),
139
+ raw.get("cache_creation_input_tokens"),
140
+ raw.get("cache_write_tokens"),
141
+ )
142
+ cache_creation = _mapping(raw.get("cache_creation"))
143
+ cache_creation_5m = _first_int(
144
+ cache_creation.get("ephemeral_5m_input_tokens"),
145
+ prompt_details.get("cache_creation_5m_input_tokens"),
146
+ input_details.get("cache_creation_5m_input_tokens"),
147
+ )
148
+ cache_creation_1h = _first_int(
149
+ cache_creation.get("ephemeral_1h_input_tokens"),
150
+ prompt_details.get("cache_creation_1h_input_tokens"),
151
+ input_details.get("cache_creation_1h_input_tokens"),
152
+ )
153
+ if cache_write is None:
154
+ write_parts = [
155
+ value for value in (cache_creation_5m, cache_creation_1h) if value is not None
156
+ ]
157
+ if write_parts:
158
+ cache_write = sum(write_parts)
159
+
160
+ cache_miss = _first_int(
161
+ raw.get("prompt_cache_miss_tokens"),
162
+ raw.get("input_tokens_uncached"),
163
+ raw.get("uncached_prompt_tokens"),
164
+ prompt_details.get("uncached_tokens"),
165
+ input_details.get("uncached_tokens"),
166
+ )
167
+ cache_miss_derived = False
168
+ if prompt is None and cache_read is not None and cache_miss is not None:
169
+ prompt = cache_read + cache_miss + (cache_write or 0)
170
+ if (
171
+ cache_miss is None
172
+ and prompt is not None
173
+ and (cache_read is not None or cache_write is not None)
174
+ ):
175
+ # Implied by the counts the provider did send — usable, but not itself
176
+ # a reported figure.
177
+ cache_miss = max(0, prompt - (cache_read or 0) - (cache_write or 0))
178
+ cache_miss_derived = True
179
+
180
+ reasoning = _first_int(
181
+ output_details.get("reasoning_tokens"),
182
+ completion_details.get("reasoning_tokens"),
183
+ raw.get("reasoning_tokens"),
184
+ )
185
+ total = _first_int(raw.get("total_tokens"))
186
+ provider_cost_usd, provider_cost_currency = _provider_cost_usd(raw, provider_key=provider_key)
187
+
188
+ # Cohere's native schema distinguishes context tokens from billed units.
189
+ # Preserve the provider's context counts above and retain billed units only
190
+ # in raw_provider_usage; substituting them would corrupt context accounting.
191
+ has_billed_units = bool(billed_units)
192
+ if (
193
+ prompt is None
194
+ and completion is None
195
+ and total is None
196
+ and cache_read is None
197
+ and cache_write is None
198
+ and reasoning is None
199
+ and provider_cost_usd is None
200
+ and not has_billed_units
201
+ ):
202
+ return None
203
+
204
+ return LLMUsage(
205
+ prompt_tokens=prompt,
206
+ completion_tokens=completion,
207
+ total_tokens=total,
208
+ cached_prompt_tokens=cache_read,
209
+ input_tokens_uncached=cache_miss,
210
+ input_tokens_uncached_derived=cache_miss_derived,
211
+ cache_read_input_tokens=cache_read,
212
+ cache_creation_input_tokens=cache_write,
213
+ cache_creation_5m_input_tokens=cache_creation_5m,
214
+ cache_creation_1h_input_tokens=cache_creation_1h,
215
+ reasoning_tokens=reasoning,
216
+ provider_cost_usd=provider_cost_usd,
217
+ provider_cost_currency=provider_cost_currency,
218
+ raw_provider_usage=copy.deepcopy(dict(raw)),
219
+ )
220
+
221
+
222
+ __all__ = ["parse_compatible_usage"]