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.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- 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
|