octo-agent 0.11.2

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 (319) hide show
  1. checksums.yaml +7 -0
  2. data/.clacky/skills/commit/SKILL.md +423 -0
  3. data/.clacky/skills/gem-release/SKILL.md +199 -0
  4. data/.clacky/skills/gem-release/scripts/release.sh +304 -0
  5. data/.clacky/skills/oss-upload/SKILL.md +47 -0
  6. data/.octorules +106 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +8 -0
  9. data/CHANGELOG.md +76 -0
  10. data/CODE_OF_CONDUCT.md +132 -0
  11. data/CONTRIBUTING.md +92 -0
  12. data/Dockerfile +28 -0
  13. data/LICENSE.txt +22 -0
  14. data/POSITIONING.md +46 -0
  15. data/README.md +134 -0
  16. data/README_CN.md +134 -0
  17. data/Rakefile +34 -0
  18. data/benchmark/fixtures/sample_project/Gemfile +3 -0
  19. data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
  20. data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
  21. data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
  22. data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
  23. data/benchmark/results/EVALUATION_REPORT.md +165 -0
  24. data/benchmark/results/baseline_20260511_174424.json +128 -0
  25. data/benchmark/results/report_20260511_175256.json +271 -0
  26. data/benchmark/results/report_20260511_175444.json +271 -0
  27. data/benchmark/results/treatment_20260511_175103.json +130 -0
  28. data/benchmark/runner.rb +441 -0
  29. data/bin/octo +7 -0
  30. data/docs/agent-first-ui-design.md +77 -0
  31. data/docs/billing-system.md +318 -0
  32. data/docs/channel-architecture.md +235 -0
  33. data/docs/engineering-article.md +343 -0
  34. data/docs/session-skill-invocation.md +69 -0
  35. data/docs/time_machine_design.md +247 -0
  36. data/docs/ui2-architecture.md +124 -0
  37. data/homebrew/README.md +96 -0
  38. data/homebrew/openocto.rb +24 -0
  39. data/lib/octo/agent/hook_manager.rb +61 -0
  40. data/lib/octo/agent/llm_caller.rb +800 -0
  41. data/lib/octo/agent/memory_updater.rb +246 -0
  42. data/lib/octo/agent/message_compressor.rb +225 -0
  43. data/lib/octo/agent/message_compressor_helper.rb +869 -0
  44. data/lib/octo/agent/next_message_suggester.rb +215 -0
  45. data/lib/octo/agent/session_serializer.rb +685 -0
  46. data/lib/octo/agent/skill_auto_creator.rb +114 -0
  47. data/lib/octo/agent/skill_evolution.rb +61 -0
  48. data/lib/octo/agent/skill_manager.rb +466 -0
  49. data/lib/octo/agent/skill_reflector.rb +89 -0
  50. data/lib/octo/agent/system_prompt_builder.rb +101 -0
  51. data/lib/octo/agent/time_machine.rb +214 -0
  52. data/lib/octo/agent/tool_executor.rb +454 -0
  53. data/lib/octo/agent/tool_registry.rb +150 -0
  54. data/lib/octo/agent.rb +2180 -0
  55. data/lib/octo/agent_config.rb +989 -0
  56. data/lib/octo/agent_profile.rb +112 -0
  57. data/lib/octo/anthropic_stream_aggregator.rb +137 -0
  58. data/lib/octo/background_task_registry.rb +324 -0
  59. data/lib/octo/banner.rb +34 -0
  60. data/lib/octo/bedrock_stream_aggregator.rb +137 -0
  61. data/lib/octo/block_font.rb +331 -0
  62. data/lib/octo/cli.rb +968 -0
  63. data/lib/octo/client.rb +623 -0
  64. data/lib/octo/default_agents/SOUL.md +3 -0
  65. data/lib/octo/default_agents/USER.md +1 -0
  66. data/lib/octo/default_agents/base_prompt.md +66 -0
  67. data/lib/octo/default_agents/coding/profile.yml +2 -0
  68. data/lib/octo/default_agents/coding/system_prompt.md +67 -0
  69. data/lib/octo/default_agents/general/profile.yml +2 -0
  70. data/lib/octo/default_agents/general/system_prompt.md +16 -0
  71. data/lib/octo/default_parsers/doc_parser.rb +69 -0
  72. data/lib/octo/default_parsers/docx_parser.rb +188 -0
  73. data/lib/octo/default_parsers/pdf_parser.rb +120 -0
  74. data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
  75. data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
  76. data/lib/octo/default_parsers/pptx_parser.rb +140 -0
  77. data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
  78. data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
  79. data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
  80. data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
  81. data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
  82. data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
  83. data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
  84. data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
  85. data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
  86. data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
  87. data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
  88. data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
  89. data/lib/octo/default_skills/onboard/SKILL.md +578 -0
  90. data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
  91. data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
  92. data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
  93. data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
  94. data/lib/octo/default_skills/personal-website/publish.rb +235 -0
  95. data/lib/octo/default_skills/product-help/SKILL.md +123 -0
  96. data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
  97. data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
  98. data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
  99. data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
  100. data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
  101. data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
  102. data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
  103. data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
  104. data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
  105. data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
  106. data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
  107. data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
  108. data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
  109. data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
  110. data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
  111. data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
  112. data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
  113. data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
  114. data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
  115. data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
  116. data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
  117. data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
  118. data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
  119. data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
  120. data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
  121. data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
  122. data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
  123. data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
  124. data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
  125. data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  126. data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
  127. data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
  128. data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
  129. data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
  130. data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
  131. data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
  132. data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
  133. data/lib/octo/idle_compression_timer.rb +115 -0
  134. data/lib/octo/json_ui_controller.rb +204 -0
  135. data/lib/octo/message_format/anthropic.rb +409 -0
  136. data/lib/octo/message_format/bedrock.rb +361 -0
  137. data/lib/octo/message_format/open_ai.rb +222 -0
  138. data/lib/octo/message_history.rb +373 -0
  139. data/lib/octo/openai_stream_aggregator.rb +130 -0
  140. data/lib/octo/plain_ui_controller.rb +166 -0
  141. data/lib/octo/providers.rb +534 -0
  142. data/lib/octo/server/browser_manager.rb +397 -0
  143. data/lib/octo/server/channel/adapters/base.rb +82 -0
  144. data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
  145. data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
  146. data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
  147. data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
  148. data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
  149. data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
  150. data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
  151. data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
  152. data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
  153. data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
  154. data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
  155. data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
  156. data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
  157. data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
  158. data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
  159. data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
  160. data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
  161. data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
  162. data/lib/octo/server/channel/channel_config.rb +178 -0
  163. data/lib/octo/server/channel/channel_manager.rb +468 -0
  164. data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
  165. data/lib/octo/server/channel.rb +33 -0
  166. data/lib/octo/server/discover.rb +77 -0
  167. data/lib/octo/server/epipe_safe_io.rb +105 -0
  168. data/lib/octo/server/http_server.rb +3554 -0
  169. data/lib/octo/server/scheduler.rb +317 -0
  170. data/lib/octo/server/server_master.rb +325 -0
  171. data/lib/octo/server/session_registry.rb +431 -0
  172. data/lib/octo/server/web_ui_controller.rb +487 -0
  173. data/lib/octo/session_manager.rb +385 -0
  174. data/lib/octo/skill.rb +466 -0
  175. data/lib/octo/skill_loader.rb +328 -0
  176. data/lib/octo/tools/base.rb +118 -0
  177. data/lib/octo/tools/browser.rb +625 -0
  178. data/lib/octo/tools/edit.rb +165 -0
  179. data/lib/octo/tools/file_reader.rb +549 -0
  180. data/lib/octo/tools/glob.rb +162 -0
  181. data/lib/octo/tools/grep.rb +356 -0
  182. data/lib/octo/tools/invoke_skill.rb +96 -0
  183. data/lib/octo/tools/list_tasks.rb +54 -0
  184. data/lib/octo/tools/redo_task.rb +41 -0
  185. data/lib/octo/tools/request_user_feedback.rb +84 -0
  186. data/lib/octo/tools/security.rb +333 -0
  187. data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
  188. data/lib/octo/tools/terminal/persistent_session.rb +268 -0
  189. data/lib/octo/tools/terminal/safe_rm.sh +106 -0
  190. data/lib/octo/tools/terminal/session_manager.rb +213 -0
  191. data/lib/octo/tools/terminal.rb +1828 -0
  192. data/lib/octo/tools/todo_manager.rb +374 -0
  193. data/lib/octo/tools/trash_manager.rb +388 -0
  194. data/lib/octo/tools/undo_task.rb +35 -0
  195. data/lib/octo/tools/web_fetch.rb +242 -0
  196. data/lib/octo/tools/web_search.rb +260 -0
  197. data/lib/octo/tools/write.rb +77 -0
  198. data/lib/octo/ui2/block_font.rb +10 -0
  199. data/lib/octo/ui2/components/base_component.rb +163 -0
  200. data/lib/octo/ui2/components/command_suggestions.rb +290 -0
  201. data/lib/octo/ui2/components/common_component.rb +96 -0
  202. data/lib/octo/ui2/components/inline_input.rb +226 -0
  203. data/lib/octo/ui2/components/input_area.rb +1338 -0
  204. data/lib/octo/ui2/components/message_component.rb +99 -0
  205. data/lib/octo/ui2/components/modal_component.rb +419 -0
  206. data/lib/octo/ui2/components/todo_area.rb +149 -0
  207. data/lib/octo/ui2/components/tool_component.rb +107 -0
  208. data/lib/octo/ui2/components/welcome_banner.rb +139 -0
  209. data/lib/octo/ui2/layout_manager.rb +807 -0
  210. data/lib/octo/ui2/line_editor.rb +363 -0
  211. data/lib/octo/ui2/markdown_renderer.rb +100 -0
  212. data/lib/octo/ui2/output_buffer.rb +370 -0
  213. data/lib/octo/ui2/progress_handle.rb +362 -0
  214. data/lib/octo/ui2/progress_indicator.rb +55 -0
  215. data/lib/octo/ui2/screen_buffer.rb +273 -0
  216. data/lib/octo/ui2/terminal_detector.rb +119 -0
  217. data/lib/octo/ui2/theme_manager.rb +85 -0
  218. data/lib/octo/ui2/themes/base_theme.rb +105 -0
  219. data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
  220. data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
  221. data/lib/octo/ui2/thinking_verbs.rb +26 -0
  222. data/lib/octo/ui2/ui_controller.rb +1625 -0
  223. data/lib/octo/ui2/view_renderer.rb +177 -0
  224. data/lib/octo/ui2.rb +40 -0
  225. data/lib/octo/ui_interface.rb +154 -0
  226. data/lib/octo/utils/arguments_parser.rb +191 -0
  227. data/lib/octo/utils/browser_detector.rb +195 -0
  228. data/lib/octo/utils/encoding.rb +92 -0
  229. data/lib/octo/utils/environment_detector.rb +140 -0
  230. data/lib/octo/utils/file_ignore_helper.rb +170 -0
  231. data/lib/octo/utils/file_processor.rb +601 -0
  232. data/lib/octo/utils/gitignore_parser.rb +154 -0
  233. data/lib/octo/utils/limit_stack.rb +152 -0
  234. data/lib/octo/utils/logger.rb +124 -0
  235. data/lib/octo/utils/login_shell.rb +72 -0
  236. data/lib/octo/utils/model_pricing.rb +646 -0
  237. data/lib/octo/utils/parser_manager.rb +165 -0
  238. data/lib/octo/utils/path_helper.rb +15 -0
  239. data/lib/octo/utils/scripts_manager.rb +59 -0
  240. data/lib/octo/utils/string_matcher.rb +158 -0
  241. data/lib/octo/utils/trash_directory.rb +112 -0
  242. data/lib/octo/utils/workspace_rules.rb +46 -0
  243. data/lib/octo/version.rb +5 -0
  244. data/lib/octo/web/app.css +7141 -0
  245. data/lib/octo/web/app.js +543 -0
  246. data/lib/octo/web/apple-touch-icon.png +0 -0
  247. data/lib/octo/web/auth.js +150 -0
  248. data/lib/octo/web/channels.js +276 -0
  249. data/lib/octo/web/datepicker.js +205 -0
  250. data/lib/octo/web/favicon.png +0 -0
  251. data/lib/octo/web/i18n.js +1073 -0
  252. data/lib/octo/web/icon-512.png +0 -0
  253. data/lib/octo/web/icon-dark.svg +25 -0
  254. data/lib/octo/web/icon.svg +29 -0
  255. data/lib/octo/web/index.html +871 -0
  256. data/lib/octo/web/marked.min.js +69 -0
  257. data/lib/octo/web/onboard.js +491 -0
  258. data/lib/octo/web/profile.js +442 -0
  259. data/lib/octo/web/sessions.js +4421 -0
  260. data/lib/octo/web/settings.js +913 -0
  261. data/lib/octo/web/sidebar.js +32 -0
  262. data/lib/octo/web/skills.js +885 -0
  263. data/lib/octo/web/tasks.js +297 -0
  264. data/lib/octo/web/theme.js +105 -0
  265. data/lib/octo/web/trash.js +343 -0
  266. data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
  267. data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
  268. data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
  269. data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  270. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  271. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  272. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  273. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  274. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  275. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  276. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  277. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  278. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  279. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  280. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  281. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  282. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  283. data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  284. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  285. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  286. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  287. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  288. data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  289. data/lib/octo/web/vendor/katex/katex.min.css +1 -0
  290. data/lib/octo/web/vendor/katex/katex.min.js +1 -0
  291. data/lib/octo/web/version.js +449 -0
  292. data/lib/octo/web/weixin-qr.html +209 -0
  293. data/lib/octo/web/ws-dispatcher.js +357 -0
  294. data/lib/octo/web/ws.js +128 -0
  295. data/lib/octo.rb +145 -0
  296. data/scripts/build/build.sh +329 -0
  297. data/scripts/build/lib/apt.sh +56 -0
  298. data/scripts/build/lib/brew.sh +89 -0
  299. data/scripts/build/lib/colors.sh +17 -0
  300. data/scripts/build/lib/gem.sh +95 -0
  301. data/scripts/build/lib/mise.sh +125 -0
  302. data/scripts/build/lib/network.sh +157 -0
  303. data/scripts/build/lib/os.sh +57 -0
  304. data/scripts/build/lib/shell.sh +37 -0
  305. data/scripts/build/src/install.sh.cc +174 -0
  306. data/scripts/build/src/install_browser.sh.cc +101 -0
  307. data/scripts/build/src/install_full.sh.cc +290 -0
  308. data/scripts/build/src/install_rails_deps.sh.cc +145 -0
  309. data/scripts/build/src/install_system_deps.sh.cc +123 -0
  310. data/scripts/build/src/uninstall.sh.cc +101 -0
  311. data/scripts/install.ps1 +532 -0
  312. data/scripts/install.sh +567 -0
  313. data/scripts/install_browser.sh +479 -0
  314. data/scripts/install_full.sh +838 -0
  315. data/scripts/install_rails_deps.sh +746 -0
  316. data/scripts/install_system_deps.sh +518 -0
  317. data/scripts/uninstall.sh +287 -0
  318. data/sig/octo.rbs +4 -0
  319. metadata +614 -0
@@ -0,0 +1,646 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Octo
4
+ # Module for handling AI model pricing
5
+ # Supports different pricing tiers and prompt caching
6
+ module ModelPricing
7
+ # Pricing per 1M tokens (MTok) in USD
8
+ # All pricing is based on official API documentation
9
+ PRICING_TABLE = {
10
+ # Claude 4.5 models - tiered pricing based on prompt length
11
+ "claude-opus-4.5" => {
12
+ input: {
13
+ default: 5.00, # $5/MTok for prompts ≤ 200K tokens
14
+ over_200k: 5.00 # same for all tiers
15
+ },
16
+ output: {
17
+ default: 25.00, # $25/MTok for prompts ≤ 200K tokens
18
+ over_200k: 25.00 # same for all tiers
19
+ },
20
+ cache: {
21
+ write: 6.25, # $6.25/MTok cache write
22
+ read: 0.50 # $0.50/MTok cache read
23
+ }
24
+ },
25
+
26
+ "claude-sonnet-4.5" => {
27
+ input: {
28
+ default: 3.00, # $3/MTok for prompts ≤ 200K tokens
29
+ over_200k: 6.00 # $6/MTok for prompts > 200K tokens
30
+ },
31
+ output: {
32
+ default: 15.00, # $15/MTok for prompts ≤ 200K tokens
33
+ over_200k: 22.50 # $22.50/MTok for prompts > 200K tokens
34
+ },
35
+ cache: {
36
+ write_default: 3.75, # $3.75/MTok cache write (≤ 200K)
37
+ write_over_200k: 7.50, # $7.50/MTok cache write (> 200K)
38
+ read_default: 0.30, # $0.30/MTok cache read (≤ 200K)
39
+ read_over_200k: 0.60 # $0.60/MTok cache read (> 200K)
40
+ }
41
+ },
42
+
43
+ "claude-haiku-4.5" => {
44
+ input: {
45
+ default: 1.00, # $1/MTok
46
+ over_200k: 1.00 # same for all tiers
47
+ },
48
+ output: {
49
+ default: 5.00, # $5/MTok
50
+ over_200k: 5.00 # same for all tiers
51
+ },
52
+ cache: {
53
+ write: 1.25, # $1.25/MTok cache write
54
+ read: 0.10 # $0.10/MTok cache read
55
+ }
56
+ },
57
+
58
+ # Claude 3.5 models (for backwards compatibility)
59
+ "claude-3-5-sonnet-20241022" => {
60
+ input: {
61
+ default: 3.00,
62
+ over_200k: 6.00
63
+ },
64
+ output: {
65
+ default: 15.00,
66
+ over_200k: 22.50
67
+ },
68
+ cache: {
69
+ write_default: 3.75,
70
+ write_over_200k: 7.50,
71
+ read_default: 0.30,
72
+ read_over_200k: 0.60
73
+ }
74
+ },
75
+
76
+ "claude-3-5-sonnet-20240620" => {
77
+ input: {
78
+ default: 3.00,
79
+ over_200k: 6.00
80
+ },
81
+ output: {
82
+ default: 15.00,
83
+ over_200k: 22.50
84
+ },
85
+ cache: {
86
+ write_default: 3.75,
87
+ write_over_200k: 7.50,
88
+ read_default: 0.30,
89
+ read_over_200k: 0.60
90
+ }
91
+ },
92
+
93
+ "claude-3-5-haiku-20241022" => {
94
+ input: {
95
+ default: 1.00,
96
+ over_200k: 1.00
97
+ },
98
+ output: {
99
+ default: 5.00,
100
+ over_200k: 5.00
101
+ },
102
+ cache: {
103
+ write: 1.25,
104
+ read: 0.10
105
+ }
106
+ },
107
+
108
+ # DeepSeek V4 models
109
+ # Source: https://api-docs.deepseek.com/quick_start/pricing (USD / 1M tokens)
110
+ # DeepSeek billing model:
111
+ # - "cache miss input" = regular prompt_tokens rate
112
+ # - "cache hit input" = cache_read rate (DeepSeek has no separate cache-write charge)
113
+ # - No tiered pricing (single rate regardless of context length)
114
+ "deepseek-v4-flash" => {
115
+ input: {
116
+ default: 0.14, # $0.14/MTok cache miss
117
+ over_200k: 0.14 # no tiered pricing
118
+ },
119
+ output: {
120
+ default: 0.28, # $0.28/MTok
121
+ over_200k: 0.28
122
+ },
123
+ cache: {
124
+ write: 0.14, # DeepSeek doesn't charge extra for writes; bill at miss rate
125
+ read: 0.0028 # $0.0028/MTok cache hit
126
+ }
127
+ },
128
+
129
+ "deepseek-v4-pro" => {
130
+ input: {
131
+ default: 1.74, # $1.74/MTok cache miss
132
+ over_200k: 1.74
133
+ },
134
+ output: {
135
+ default: 3.48, # $3.48/MTok
136
+ over_200k: 3.48
137
+ },
138
+ cache: {
139
+ write: 1.74, # no separate write charge; bill at miss rate
140
+ read: 0.0145 # $0.0145/MTok cache hit
141
+ }
142
+ },
143
+
144
+ # Kimi K2.5 / K2.6 multimodal models
145
+ # Source: https://platform.moonshot.cn (USD / 1M tokens)
146
+ # Kimi billing model (same shape as DeepSeek):
147
+ # - "cache miss input" = regular prompt_tokens rate
148
+ # - "cache hit input" = cache_read rate (no separate cache-write charge)
149
+ # - No tiered pricing (single rate regardless of context length)
150
+ "kimi-k2.5" => {
151
+ input: {
152
+ default: 0.60, # $0.60/MTok cache miss
153
+ over_200k: 0.60 # no tiered pricing
154
+ },
155
+ output: {
156
+ default: 3.00, # $3.00/MTok
157
+ over_200k: 3.00
158
+ },
159
+ cache: {
160
+ write: 0.60, # Kimi doesn't charge extra for writes; bill at miss rate
161
+ read: 0.10 # $0.10/MTok cache hit
162
+ }
163
+ },
164
+
165
+ "kimi-k2.6" => {
166
+ input: {
167
+ default: 0.95, # $0.95/MTok cache miss
168
+ over_200k: 0.95
169
+ },
170
+ output: {
171
+ default: 4.00, # $4.00/MTok
172
+ over_200k: 4.00
173
+ },
174
+ cache: {
175
+ write: 0.95, # no separate write charge; bill at miss rate
176
+ read: 0.16 # $0.16/MTok cache hit
177
+ }
178
+ },
179
+
180
+ # OpenAI GPT-5.5 / GPT-5.4 — breakpoint at 272K input tokens
181
+ # Source: https://openai.com/api/pricing/ (USD / 1M tokens)
182
+ # Note: OpenAI's actual tiered-pricing threshold is 272K, not the
183
+ # global 200K below. Prompts between 200K–272K will slightly
184
+ # over-estimate costs until a per-model threshold is implemented.
185
+ "gpt-5.5" => {
186
+ input: {
187
+ default: 5.00, # $5/MTok for prompts ≤ 272K tokens
188
+ over_200k: 10.00 # $10/MTok for prompts > 272K tokens
189
+ },
190
+ output: {
191
+ default: 30.00, # $30/MTok for prompts ≤ 272K tokens
192
+ over_200k: 45.00 # $45/MTok for prompts > 272K tokens
193
+ },
194
+ cache: {
195
+ write_default: 5.00, # $5/MTok cache write (≤ 272K)
196
+ write_over_200k: 10.00, # $10/MTok cache write (> 272K)
197
+ read_default: 0.50, # $0.50/MTok cache read (≤ 272K)
198
+ read_over_200k: 1.00 # $1.00/MTok cache read (> 272K)
199
+ }
200
+ },
201
+
202
+ "gpt-5.4" => {
203
+ input: {
204
+ default: 2.50, # $2.50/MTok for prompts ≤ 272K tokens
205
+ over_200k: 5.00 # $5/MTok for prompts > 272K tokens
206
+ },
207
+ output: {
208
+ default: 15.00, # $15/MTok for prompts ≤ 272K tokens
209
+ over_200k: 22.50 # $22.50/MTok for prompts > 272K tokens
210
+ },
211
+ cache: {
212
+ write_default: 2.50, # $2.50/MTok cache write (≤ 272K)
213
+ write_over_200k: 5.00, # $5/MTok cache write (> 272K)
214
+ read_default: 0.25, # $0.25/MTok cache read (≤ 272K)
215
+ read_over_200k: 0.50 # $0.50/MTok cache read (> 272K)
216
+ }
217
+ },
218
+
219
+ # GPT-5.4 flat-rate models (no breakpoint, single rate regardless of context)
220
+ "gpt-5.4-mini" => {
221
+ input: {
222
+ default: 0.75, # $0.75/MTok
223
+ over_200k: 0.75
224
+ },
225
+ output: {
226
+ default: 4.50, # $4.50/MTok
227
+ over_200k: 4.50
228
+ },
229
+ cache: {
230
+ write: 0.75, # $0.75/MTok cache write
231
+ read: 0.075 # $0.075/MTok cache read (10% of input)
232
+ }
233
+ },
234
+
235
+ "gpt-5.4-nano" => {
236
+ input: {
237
+ default: 0.20, # $0.20/MTok
238
+ over_200k: 0.20
239
+ },
240
+ output: {
241
+ default: 1.25, # $1.25/MTok
242
+ over_200k: 1.25
243
+ },
244
+ cache: {
245
+ write: 0.20, # $0.20/MTok cache write
246
+ read: 0.02 # $0.02/MTok cache read (10% of input)
247
+ }
248
+ },
249
+
250
+ # O-series reasoning models — flat-rate (200K context window)
251
+ # Source: https://openai.com/api/pricing/
252
+ "o3" => {
253
+ input: {
254
+ default: 2.00, # $2/MTok
255
+ over_200k: 2.00 # flat rate
256
+ },
257
+ output: {
258
+ default: 8.00, # $8/MTok
259
+ over_200k: 8.00
260
+ },
261
+ cache: {
262
+ write: 2.00, # $2/MTok cache write (same as input)
263
+ read: 0.50 # $0.50/MTok cache read (25% of input)
264
+ }
265
+ },
266
+
267
+ "o4-mini" => {
268
+ input: {
269
+ default: 1.10, # $1.10/MTok
270
+ over_200k: 1.10 # flat rate
271
+ },
272
+ output: {
273
+ default: 4.40, # $4.40/MTok
274
+ over_200k: 4.40
275
+ },
276
+ cache: {
277
+ write: 1.10, # $1.10/MTok cache write (same as input)
278
+ read: 0.275 # $0.275/MTok cache read (25% of input)
279
+ }
280
+ },
281
+
282
+ # GLM (Zhipu / Z.ai) — USD per 1M tokens.
283
+ # Source: https://docs.z.ai/guides/overview/pricing (Z.ai international).
284
+ # Pricing policy: we always bill at the Z.ai international flat rate,
285
+ # regardless of which endpoint (mainland bigmodel.cn vs intl z.ai) the
286
+ # user configured. Rationale:
287
+ # 1. Mainland GLM uses tiered pricing (≤32K / >32K / >128K) where the
288
+ # >32K tier is hit by the vast majority of real requests, and is
289
+ # actually a few RMB cheaper than Z.ai's flat rate — displaying the
290
+ # (slightly higher) Z.ai rate gives users a "displayed ≤ actual"
291
+ # experience which is psychologically safer than the reverse.
292
+ # 2. Single flat rate keeps the table shape consistent with every
293
+ # other provider here (no special-case tier logic for just GLM).
294
+ # Cache-write: same convention as DeepSeek/Kimi — OpenAI-compatible
295
+ # endpoints don't charge separately for cache writes (Z.ai's page lists
296
+ # "Cached Input Storage: Limited-time Free"), so bill writes at the
297
+ # regular input miss rate for safe "displayed ≤ actual" behaviour.
298
+ "glm-5.1" => {
299
+ input: { default: 1.40, over_200k: 1.40 },
300
+ output: { default: 4.40, over_200k: 4.40 },
301
+ cache: { write: 1.40, read: 0.26 }
302
+ },
303
+
304
+ "glm-5" => {
305
+ input: { default: 1.00, over_200k: 1.00 },
306
+ output: { default: 3.20, over_200k: 3.20 },
307
+ cache: { write: 1.00, read: 0.20 }
308
+ },
309
+
310
+ "glm-5-turbo" => {
311
+ input: { default: 1.20, over_200k: 1.20 },
312
+ output: { default: 4.00, over_200k: 4.00 },
313
+ cache: { write: 1.20, read: 0.24 }
314
+ },
315
+
316
+ # GLM-5V-Turbo is the multimodal sibling of GLM-5-Turbo (vision capable,
317
+ # see providers.rb model_capabilities override). Same input/output rate
318
+ # as 5-Turbo per Z.ai's Vision Models table.
319
+ "glm-5v-turbo" => {
320
+ input: { default: 1.20, over_200k: 1.20 },
321
+ output: { default: 4.00, over_200k: 4.00 },
322
+ cache: { write: 1.20, read: 0.24 }
323
+ },
324
+
325
+ "glm-4.7" => {
326
+ input: { default: 0.60, over_200k: 0.60 },
327
+ output: { default: 2.20, over_200k: 2.20 },
328
+ cache: { write: 0.60, read: 0.11 }
329
+ },
330
+
331
+ # MiniMax — USD per 1M tokens.
332
+ # Source: https://platform.minimaxi.com (Pay-as-You-Go).
333
+ # MiniMax pricing is identical across mainland (.com) and international
334
+ # (.io) endpoints, verified by the team. Same cache-write convention as
335
+ # DeepSeek/Kimi/GLM: bill writes at the input miss rate (OpenAI-compatible
336
+ # usage responses from MiniMax don't reliably carry a separate
337
+ # cache_creation_input_tokens field, so a distinct write rate would be
338
+ # dead code in practice).
339
+ # Note: providers.rb uses the capitalised "MiniMax-M2.x" model id, but
340
+ # the pricing table keys are lowercased to stay consistent with the
341
+ # rest of this file; normalize_model_name() lowercases incoming model
342
+ # names before lookup.
343
+ "minimax-m2.5" => {
344
+ input: { default: 0.30, over_200k: 0.30 },
345
+ output: { default: 1.20, over_200k: 1.20 },
346
+ cache: { write: 0.30, read: 0.03 }
347
+ },
348
+
349
+ "minimax-m2.7" => {
350
+ input: { default: 0.30, over_200k: 0.30 },
351
+ output: { default: 1.20, over_200k: 1.20 },
352
+ cache: { write: 0.30, read: 0.06 }
353
+ },
354
+
355
+ # Qwen (Alibaba DashScope) — USD per 1M tokens, Singapore region list price.
356
+ # Source: Alibaba Cloud Model Studio international pricing.
357
+ # Cache convention (mirrors DeepSeek/Kimi/GLM "displayed ≤ actual"):
358
+ # - DashScope has two cache modes; implicit is auto-on, explicit is opt-in.
359
+ # Implicit: write @ 100% input, read @ 20% input (no setup, no guarantee)
360
+ # Explicit: write @ 125% input, read @ 10% input (cache_control marker)
361
+ # - We bill writes at the regular input rate (matches implicit, and avoids
362
+ # surprising users with the explicit 25% surcharge).
363
+ # - We bill reads at 20% (implicit rate) — the conservative side; users on
364
+ # explicit caching will see real bills slightly *lower* than displayed.
365
+ "qwen3.6-plus" => {
366
+ input: { default: 0.40, over_200k: 0.40 },
367
+ output: { default: 2.40, over_200k: 2.40 },
368
+ cache: { write: 0.40, read: 0.08 }
369
+ },
370
+
371
+ "qwen3.6-max" => {
372
+ input: { default: 1.20, over_200k: 1.20 },
373
+ output: { default: 6.00, over_200k: 6.00 },
374
+ cache: { write: 1.20, read: 0.24 }
375
+ },
376
+
377
+ "qwen3.6-27b" => {
378
+ input: { default: 0.20, over_200k: 0.20 },
379
+ output: { default: 0.80, over_200k: 0.80 },
380
+ cache: { write: 0.20, read: 0.04 }
381
+ },
382
+
383
+ "qwen3.6-flash" => {
384
+ input: { default: 0.15, over_200k: 0.15 },
385
+ output: { default: 0.90, over_200k: 0.90 },
386
+ cache: { write: 0.15, read: 0.03 }
387
+ },
388
+
389
+ "qwen-plus-latest" => {
390
+ input: { default: 0.40, over_200k: 0.40 },
391
+ output: { default: 1.20, over_200k: 1.20 },
392
+ cache: { write: 0.40, read: 0.08 }
393
+ },
394
+
395
+ "qwen-vl-plus" => {
396
+ input: { default: 0.14, over_200k: 0.14 },
397
+ output: { default: 0.41, over_200k: 0.41 },
398
+ cache: { write: 0.14, read: 0.028 }
399
+ },
400
+
401
+ "qwen-vl-max" => {
402
+ input: { default: 0.52, over_200k: 0.52 },
403
+ output: { default: 2.08, over_200k: 2.08 },
404
+ cache: { write: 0.52, read: 0.104 }
405
+ },
406
+
407
+ }.freeze
408
+
409
+ # Threshold for tiered pricing (200K tokens)
410
+ # NOTE: OpenAI GPT-5.5/GPT-5.4 use a 272K breakpoint, not 200K.
411
+ # Costs for prompts between 200K–272K will be slightly over-estimated.
412
+ TIERED_PRICING_THRESHOLD = 200_000
413
+
414
+ class << self
415
+ # Calculate cost for the given model and usage
416
+ #
417
+ # @param model [String] Model identifier
418
+ # @param usage [Hash] Usage statistics containing:
419
+ # - prompt_tokens: number of input tokens
420
+ # - completion_tokens: number of output tokens
421
+ # - cache_creation_input_tokens: tokens written to cache (optional)
422
+ # - cache_read_input_tokens: tokens read from cache (optional)
423
+ # @return [Hash] Hash containing:
424
+ # - cost: Cost in USD (Float) or nil if model pricing is unknown
425
+ # - source: Cost source (:price) or nil if unknown (Symbol or nil)
426
+ def calculate_cost(model:, usage:)
427
+ pricing_result = get_pricing_with_source(model)
428
+ pricing = pricing_result[:pricing]
429
+ source = pricing_result[:source]
430
+
431
+ # If no pricing table matches this model, return nil cost.
432
+ # Unknown models should display as N/A, never fall back to guesses.
433
+ return { cost: nil, source: nil } unless pricing
434
+
435
+ prompt_tokens = usage[:prompt_tokens] || 0
436
+ completion_tokens = usage[:completion_tokens] || 0
437
+ cache_write_tokens = usage[:cache_creation_input_tokens] || 0
438
+ cache_read_tokens = usage[:cache_read_input_tokens] || 0
439
+
440
+ # Determine if we're in the over_200k tier
441
+ # Note: prompt_tokens includes cache_read_tokens but NOT cache_write_tokens
442
+ # cache_write_tokens are additional tokens that were written to cache
443
+ total_input_tokens = prompt_tokens + cache_write_tokens
444
+ over_threshold = total_input_tokens > TIERED_PRICING_THRESHOLD
445
+
446
+ # Calculate regular input cost (non-cached tokens)
447
+ # prompt_tokens already includes cache_read_tokens, so we need to subtract them
448
+ # cache_write_tokens are not part of prompt_tokens, so they're handled separately in cache_cost
449
+ regular_input_tokens = prompt_tokens - cache_read_tokens
450
+ input_rate = over_threshold ? pricing[:input][:over_200k] : pricing[:input][:default]
451
+ input_cost = (regular_input_tokens / 1_000_000.0) * input_rate
452
+
453
+ # Calculate output cost
454
+ output_rate = over_threshold ? pricing[:output][:over_200k] : pricing[:output][:default]
455
+ output_cost = (completion_tokens / 1_000_000.0) * output_rate
456
+
457
+ # Calculate cache costs
458
+ cache_cost = calculate_cache_cost(
459
+ pricing: pricing,
460
+ cache_write_tokens: cache_write_tokens,
461
+ cache_read_tokens: cache_read_tokens,
462
+ over_threshold: over_threshold
463
+ )
464
+
465
+ {
466
+ cost: input_cost + output_cost + cache_cost,
467
+ source: source
468
+ }
469
+ end
470
+
471
+ # Get pricing for a specific model
472
+ # Falls back to default pricing if model not found
473
+ #
474
+ # @param model [String] Model identifier
475
+ # @return [Hash] Pricing structure for the model
476
+ def get_pricing(model)
477
+ get_pricing_with_source(model)[:pricing]
478
+ end
479
+
480
+ # Get pricing with source information
481
+ #
482
+ # @param model [String] Model identifier
483
+ # @return [Hash] Hash containing:
484
+ # - pricing: Pricing structure or nil if model is unknown
485
+ # - source: :price (matched) or nil (unknown)
486
+ def get_pricing_with_source(model)
487
+ # Normalize model name (remove version suffixes, handle variations)
488
+ normalized_model = normalize_model_name(model)
489
+
490
+ if normalized_model
491
+ # Found specific pricing for this model
492
+ {
493
+ pricing: PRICING_TABLE[normalized_model],
494
+ source: :price
495
+ }
496
+ else
497
+ # No matching pricing table entry — cost is unknown
498
+ { pricing: nil, source: nil }
499
+ end
500
+ end
501
+
502
+
503
+ # Normalize model name to match pricing table keys.
504
+ # Returns the canonical key on match, or nil when no pricing is available.
505
+ def normalize_model_name(model)
506
+ return nil if model.nil? || model.empty?
507
+
508
+ model = model.downcase.strip
509
+
510
+ # Direct match
511
+ return model if PRICING_TABLE.key?(model)
512
+
513
+ # Check for Claude model variations
514
+ # Support both dot and dash separators (e.g., "4.5", "4-5", "4-6")
515
+ # Also handles Bedrock cross-region prefixes (e.g. "jp.anthropic.claude-sonnet-4-6")
516
+ case model
517
+ when /claude.*opus.*4[.-]?[5-9]/i
518
+ "claude-opus-4.5"
519
+ when /claude.*sonnet.*4[.-]?[5-9]/i
520
+ "claude-sonnet-4.5"
521
+ when /claude.*haiku.*4[.-]?[5-9]/i
522
+ "claude-haiku-4.5"
523
+ when /claude-3-5-sonnet-20241022/i
524
+ "claude-3-5-sonnet-20241022"
525
+ when /claude-3-5-sonnet-20240620/i
526
+ "claude-3-5-sonnet-20240620"
527
+ when /claude-3-5-haiku-20241022/i
528
+ "claude-3-5-haiku-20241022"
529
+ when /deepseek-v4-pro/i, /deepseek.*v4.*pro/i
530
+ "deepseek-v4-pro"
531
+ when /deepseek-v4-flash/i, /deepseek.*v4.*flash/i
532
+ "deepseek-v4-flash"
533
+ # Legacy aliases: deepseek-chat and deepseek-reasoner are being
534
+ # deprecated on 2026-07-24 and map to deepseek-v4-flash's
535
+ # non-thinking / thinking modes respectively. Bill at flash rates.
536
+ when /^deepseek-chat$/i, /^deepseek-reasoner$/i
537
+ "deepseek-v4-flash"
538
+ # Kimi K2.5 / K2.6 — strict match only. K2 text-only models
539
+ # (kimi-k2-0905-preview, kimi-k2-thinking, etc.) are not yet
540
+ # registered in providers.rb and will be added in a follow-up
541
+ # issue together with their model_capabilities overrides.
542
+ when /^kimi-k2\.?5$/i
543
+ "kimi-k2.5"
544
+ when /^kimi-k2\.?6$/i
545
+ "kimi-k2.6"
546
+ # GLM (Zhipu / Z.ai) — the five models registered in providers.rb.
547
+ # GLM-5V-Turbo is the vision variant; all five share the same Z.ai
548
+ # international flat-rate pricing regardless of which endpoint
549
+ # (mainland bigmodel.cn vs intl z.ai) the user configured.
550
+ # Strict anchored match so unrelated strings like "glm-5-x-foo"
551
+ # don't silently borrow a nearby model's rate.
552
+ when /^glm-5\.1$/i
553
+ "glm-5.1"
554
+ when /^glm-5v-turbo$/i
555
+ "glm-5v-turbo"
556
+ when /^glm-5-turbo$/i
557
+ "glm-5-turbo"
558
+ when /^glm-5$/i
559
+ "glm-5"
560
+ when /^glm-4\.7$/i
561
+ "glm-4.7"
562
+ # MiniMax — model ids in providers.rb use capitalised "MiniMax-M2.x"
563
+ # but we match case-insensitively and map to the lowercased table key.
564
+ when /^minimax-m2\.5$/i
565
+ "minimax-m2.5"
566
+ when /^minimax-m2\.7$/i
567
+ "minimax-m2.7"
568
+
569
+ # Qwen (Alibaba DashScope) — strict anchored match per registered
570
+ # model id in providers.rb. qwen3.6-* are the new flagship line;
571
+ # qwen-plus-latest is the rolling alias for the latest Qwen-Plus
572
+ # release; qwen-vl-* are the multimodal SKUs.
573
+ when /^qwen3\.6-plus$/i
574
+ "qwen3.6-plus"
575
+ when /^qwen3\.6-max$/i
576
+ "qwen3.6-max"
577
+ when /^qwen3\.6-27b$/i
578
+ "qwen3.6-27b"
579
+ when /^qwen3\.6-flash$/i
580
+ "qwen3.6-flash"
581
+ when /^qwen-plus-latest$/i
582
+ "qwen-plus-latest"
583
+ when /^qwen-vl-plus$/i
584
+ "qwen-vl-plus"
585
+ when /^qwen-vl-max$/i
586
+ "qwen-vl-max"
587
+
588
+ # OpenAI GPT-5.x models — match various dashed/dotted/compact forms
589
+ # (e.g. "gpt-5.5", "gpt-5-5", "gpt5.5", "gpt55")
590
+ when /^gpt-?5\.?5$/i, /^gpt-?5[\.-]?5$/i
591
+ "gpt-5.5"
592
+ when /^gpt-?5\.?4[^.]*mini$/i, /^gpt-?5\.?4[\.-]?mini$/i
593
+ "gpt-5.4-mini"
594
+ when /^gpt-?5\.?4[^.]*nano$/i, /^gpt-?5\.?4[\.-]?nano$/i
595
+ "gpt-5.4-nano"
596
+ when /^gpt-?5\.?4$/i, /^gpt-?5[\.-]?4$/i
597
+ "gpt-5.4"
598
+ # O-series reasoning models
599
+ when /^o4[\.-]?mini$/i
600
+ "o4-mini"
601
+ when /^o3$/i
602
+ "o3"
603
+ else
604
+ nil # No pricing available for this model — cost will show as N/A
605
+ end
606
+ end
607
+
608
+ # Calculate cache-related costs
609
+ def calculate_cache_cost(pricing:, cache_write_tokens:, cache_read_tokens:, over_threshold:)
610
+ cache_cost = 0.0
611
+
612
+ # Cache write cost
613
+ if cache_write_tokens > 0
614
+ write_rate = if pricing[:cache].key?(:write)
615
+ # Simple pricing (Opus 4.5, Haiku 4.5)
616
+ pricing[:cache][:write]
617
+ elsif over_threshold
618
+ # Tiered pricing (Sonnet 4.5)
619
+ pricing[:cache][:write_over_200k]
620
+ else
621
+ pricing[:cache][:write_default]
622
+ end
623
+
624
+ cache_cost += (cache_write_tokens / 1_000_000.0) * write_rate
625
+ end
626
+
627
+ # Cache read cost
628
+ if cache_read_tokens > 0
629
+ read_rate = if pricing[:cache].key?(:read)
630
+ # Simple pricing (Opus 4.5, Haiku 4.5)
631
+ pricing[:cache][:read]
632
+ elsif over_threshold
633
+ # Tiered pricing (Sonnet 4.5)
634
+ pricing[:cache][:read_over_200k]
635
+ else
636
+ pricing[:cache][:read_default]
637
+ end
638
+
639
+ cache_cost += (cache_read_tokens / 1_000_000.0) * read_rate
640
+ end
641
+
642
+ cache_cost
643
+ end
644
+ end
645
+ end
646
+ end