openclacky 1.5.20 → 1.5.21
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 +4 -4
- data/CHANGELOG.md +49 -0
- data/README.md +18 -0
- data/lib/clacky/agent/chunk_index.rb +8 -1
- data/lib/clacky/agent/feedback.rb +30 -0
- data/lib/clacky/agent/history_navigation.rb +17 -3
- data/lib/clacky/agent/hook_manager.rb +6 -1
- data/lib/clacky/agent/llm_caller.rb +1 -0
- data/lib/clacky/agent/message_compressor_helper.rb +28 -2
- data/lib/clacky/agent/session_serializer.rb +20 -2
- data/lib/clacky/agent.rb +56 -43
- data/lib/clacky/agent_config.rb +82 -4
- data/lib/clacky/billing/platform_billing.rb +5 -1
- data/lib/clacky/brand_config.rb +1 -1
- data/lib/clacky/cli.rb +3 -2
- data/lib/clacky/client.rb +5 -5
- data/lib/clacky/deepseek_web_tools.rb +156 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +8 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +21 -1
- data/lib/clacky/extension/loader.rb +5 -3
- data/lib/clacky/locales/en.rb +5 -5
- data/lib/clacky/locales/zh.rb +5 -5
- data/lib/clacky/message_format/anthropic.rb +20 -0
- data/lib/clacky/message_format/open_ai.rb +31 -14
- data/lib/clacky/message_history.rb +21 -3
- data/lib/clacky/platform_http_client.rb +6 -2
- data/lib/clacky/providers.rb +23 -14
- data/lib/clacky/search_config.rb +47 -13
- data/lib/clacky/server/browser_manager.rb +35 -5
- data/lib/clacky/server/channel/channel_manager.rb +6 -6
- data/lib/clacky/server/discover.rb +5 -4
- data/lib/clacky/server/http_server.rb +194 -47
- data/lib/clacky/server/model_prices.rb +5 -3
- data/lib/clacky/server/pid_files.rb +25 -0
- data/lib/clacky/server/server_master.rb +9 -3
- data/lib/clacky/server/session_registry.rb +19 -7
- data/lib/clacky/server/web_ui_controller.rb +61 -38
- data/lib/clacky/session_manager.rb +7 -1
- data/lib/clacky/skill.rb +1 -1
- data/lib/clacky/tools/ask_user.rb +4 -1
- data/lib/clacky/tools/base.rb +3 -2
- data/lib/clacky/tools/browser.rb +18 -2
- data/lib/clacky/tools/file_reader.rb +1 -1
- data/lib/clacky/tools/terminal/session_manager.rb +19 -1
- data/lib/clacky/tools/terminal.rb +65 -23
- data/lib/clacky/tools/trash_manager.rb +5 -3
- data/lib/clacky/tools/web_fetch.rb +81 -22
- data/lib/clacky/tools/web_search.rb +45 -4
- data/lib/clacky/ui2/ui_controller.rb +1 -1
- data/lib/clacky/ui_interface.rb +4 -3
- data/lib/clacky/utils/file_processor.rb +51 -0
- data/lib/clacky/utils/model_pricing.rb +132 -13
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +366 -105
- data/lib/clacky/web/components/chat-navigator.js +8 -1
- data/lib/clacky/web/components/mentions.js +2 -13
- data/lib/clacky/web/components/model-picker.js +3 -1
- data/lib/clacky/web/components/onboard.js +13 -10
- data/lib/clacky/web/features/archive/store.js +30 -16
- data/lib/clacky/web/features/archive/view.js +20 -10
- data/lib/clacky/web/features/backup/view.js +5 -5
- data/lib/clacky/web/features/new-session/store.js +3 -3
- data/lib/clacky/web/features/trash/store.js +10 -7
- data/lib/clacky/web/features/trash/view.js +9 -4
- data/lib/clacky/web/i18n.js +106 -30
- data/lib/clacky/web/index.html +90 -60
- data/lib/clacky/web/projects.js +56 -1
- data/lib/clacky/web/sessions.js +244 -131
- data/lib/clacky/web/settings.js +143 -8
- data/lib/clacky/web/ws-dispatcher.js +9 -13
- data/lib/clacky.rb +1 -0
- metadata +4 -1
|
@@ -6,6 +6,7 @@ require "uri"
|
|
|
6
6
|
require "cgi"
|
|
7
7
|
require "base64"
|
|
8
8
|
require_relative "../utils/encoding"
|
|
9
|
+
require_relative "../deepseek_web_tools"
|
|
9
10
|
|
|
10
11
|
module Clacky
|
|
11
12
|
module Tools
|
|
@@ -37,7 +38,9 @@ module Clacky
|
|
|
37
38
|
SEARCHER_TIMEOUT = 30
|
|
38
39
|
PARALLEL_ENDPOINT = "https://search.parallel.ai/mcp"
|
|
39
40
|
PARALLEL_SNIPPET_MAX_CHARS = 400
|
|
40
|
-
|
|
41
|
+
# Shared by every provider, hosted or free: one failure keeps that
|
|
42
|
+
# provider out of the retry path for the whole window.
|
|
43
|
+
PROVIDER_COOLDOWN_SECONDS = 3600
|
|
41
44
|
|
|
42
45
|
def execute(query:, max_results: 10, working_dir: nil)
|
|
43
46
|
if query.nil? || query.strip.empty?
|
|
@@ -62,10 +65,13 @@ module Clacky
|
|
|
62
65
|
}
|
|
63
66
|
rescue StandardError => e
|
|
64
67
|
# Avoid paying the same network/handshake failure on every search.
|
|
65
|
-
|
|
68
|
+
case provider
|
|
69
|
+
when :deepseek
|
|
70
|
+
Clacky::DeepSeekWebTools.mark_unavailable!
|
|
71
|
+
when :parallel
|
|
66
72
|
reset_parallel_client
|
|
67
73
|
@parallel_unavailable_until = Time.now + PROVIDER_COOLDOWN_SECONDS
|
|
68
|
-
|
|
74
|
+
when :duckduckgo
|
|
69
75
|
@ddg_unavailable_until = Time.now + PROVIDER_COOLDOWN_SECONDS
|
|
70
76
|
end
|
|
71
77
|
last_error = e
|
|
@@ -90,6 +96,10 @@ module Clacky
|
|
|
90
96
|
"Configured search provider '#{name}' failed. #{reason} " \
|
|
91
97
|
"Check the key and the script at #{Clacky::SearchConfig.script_path}, " \
|
|
92
98
|
"or reconfigure it in Settings → Search."
|
|
99
|
+
elsif providers.include?(:deepseek)
|
|
100
|
+
"DeepSeek web search failed and no free engine returned results. #{reason} " \
|
|
101
|
+
"Check the API key and balance in Settings → Web Search; DeepSeek will be " \
|
|
102
|
+
"retried in #{PROVIDER_COOLDOWN_SECONDS / 60} minutes."
|
|
93
103
|
else
|
|
94
104
|
"All search providers failed. #{reason}"
|
|
95
105
|
end
|
|
@@ -102,13 +112,23 @@ module Clacky
|
|
|
102
112
|
# broken key or script look like a low-quality result set.
|
|
103
113
|
return [:custom] if Clacky::SearchConfig.script_path
|
|
104
114
|
|
|
105
|
-
PROVIDERS.reject do |provider|
|
|
115
|
+
free_chain = PROVIDERS.reject do |provider|
|
|
106
116
|
unavailable_until = case provider
|
|
107
117
|
when :parallel then @parallel_unavailable_until
|
|
108
118
|
when :duckduckgo then @ddg_unavailable_until
|
|
109
119
|
end
|
|
110
120
|
unavailable_until && Time.now < unavailable_until
|
|
111
121
|
end
|
|
122
|
+
|
|
123
|
+
return free_chain unless Clacky::SearchConfig.deepseek_web?
|
|
124
|
+
|
|
125
|
+
# DeepSeek leads the chain but does not replace the free engines: a
|
|
126
|
+
# missing key, an exhausted balance or an outage should degrade to a
|
|
127
|
+
# working search rather than return nothing.
|
|
128
|
+
return free_chain if Clacky::DeepSeekWebTools.cooling_down?
|
|
129
|
+
return free_chain unless deepseek_web_available?
|
|
130
|
+
|
|
131
|
+
[:deepseek] + free_chain
|
|
112
132
|
end
|
|
113
133
|
|
|
114
134
|
# ── Parallel Free MCP ──────────────────────────────────────────────────
|
|
@@ -171,6 +191,27 @@ module Clacky
|
|
|
171
191
|
end.join("\n")
|
|
172
192
|
end
|
|
173
193
|
|
|
194
|
+
# ── DeepSeek hosted search (paid, opt-in) ──────────────────────────────
|
|
195
|
+
|
|
196
|
+
private def deepseek_web_available?
|
|
197
|
+
return false unless agent
|
|
198
|
+
|
|
199
|
+
agent.config.deepseek_web_ready?
|
|
200
|
+
rescue StandardError
|
|
201
|
+
false
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private def search_deepseek(query, max_results)
|
|
205
|
+
config = agent.config
|
|
206
|
+
key = config.deepseek_web_key
|
|
207
|
+
Clacky::DeepSeekWebTools.search(
|
|
208
|
+
query: query,
|
|
209
|
+
count: max_results,
|
|
210
|
+
api_key: key,
|
|
211
|
+
base_url: config.deepseek_web_base_url(key)
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
174
215
|
# ── Configured searcher (~/.clacky/searchers/<provider>.rb) ────────────
|
|
175
216
|
|
|
176
217
|
private def search_custom(query, max_results)
|
|
@@ -1396,7 +1396,7 @@ module Clacky
|
|
|
1396
1396
|
# * the countdown reaches zero with no interaction -> returns :timeout
|
|
1397
1397
|
# @param seconds [Integer] Countdown duration
|
|
1398
1398
|
# @return [String, Symbol] feedback text, or :timeout
|
|
1399
|
-
def request_feedback_with_countdown(seconds:
|
|
1399
|
+
def request_feedback_with_countdown(seconds: 20)
|
|
1400
1400
|
theme = ThemeManager.current_theme
|
|
1401
1401
|
|
|
1402
1402
|
queue = Queue.new
|
data/lib/clacky/ui_interface.rb
CHANGED
|
@@ -145,9 +145,10 @@ module Clacky
|
|
|
145
145
|
# Auto-approve countdown for ask_user. Shows a live countdown
|
|
146
146
|
# and lets the user press a key to take over and answer. Returns :timeout
|
|
147
147
|
# when no one intervenes (agent should auto-decide and continue), or a
|
|
148
|
-
# feedback string / "" when the user steps in
|
|
149
|
-
#
|
|
150
|
-
|
|
148
|
+
# feedback string / "" when the user steps in, or :answered when Web has
|
|
149
|
+
# queued a full answer for steering. Non-interactive UIs (json, channel)
|
|
150
|
+
# default to :timeout.
|
|
151
|
+
def request_feedback_with_countdown(seconds: 20)
|
|
151
152
|
:timeout
|
|
152
153
|
end
|
|
153
154
|
|
|
@@ -226,6 +226,57 @@ module Clacky
|
|
|
226
226
|
MIME_TYPES[File.extname(path).downcase] || "application/octet-stream"
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
# Quantize with a frequency-weighted median-cut palette using only chunky_png.
|
|
230
|
+
# Keep the input on failure so the caller retains its existing size handling.
|
|
231
|
+
def self.quantize_png_base64(b64)
|
|
232
|
+
require "base64"
|
|
233
|
+
require "chunky_png"
|
|
234
|
+
|
|
235
|
+
image = ChunkyPNG::Image.from_blob(Base64.strict_decode64(b64))
|
|
236
|
+
counts = Hash.new(0)
|
|
237
|
+
image.pixels.each { |color| counts[color] += 1 }
|
|
238
|
+
if counts.size > 256
|
|
239
|
+
# Cache each box's widest RGBA channel; split until at most 256 remain.
|
|
240
|
+
make_box = lambda do |colors|
|
|
241
|
+
ranges = [24, 16, 8, 0].map do |shift|
|
|
242
|
+
values = colors.map { |color| (color >> shift) & 255 }
|
|
243
|
+
values.max - values.min
|
|
244
|
+
end
|
|
245
|
+
channel = ranges.each_index.max_by { |i| ranges[i] }
|
|
246
|
+
[colors, [24, 16, 8, 0][channel], ranges[channel]]
|
|
247
|
+
end
|
|
248
|
+
boxes = [make_box.call(counts.keys)]
|
|
249
|
+
while boxes.size < 256
|
|
250
|
+
index = boxes.each_index.max_by { |i| boxes[i][2] }
|
|
251
|
+
colors, shift, range = boxes.delete_at(index)
|
|
252
|
+
break if range.zero?
|
|
253
|
+
|
|
254
|
+
colors.sort_by! { |color| (color >> shift) & 255 }
|
|
255
|
+
halfway = colors.sum { |color| counts[color] } / 2.0
|
|
256
|
+
total = 0
|
|
257
|
+
split = colors.index { |color| (total += counts[color]) >= halfway } + 1
|
|
258
|
+
split = [split, colors.size - 1].min
|
|
259
|
+
boxes << make_box.call(colors.take(split))
|
|
260
|
+
boxes << make_box.call(colors.drop(split))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
mapping = {}
|
|
264
|
+
boxes.each do |colors, _shift, _range|
|
|
265
|
+
weight = colors.sum { |color| counts[color] }
|
|
266
|
+
channels = [24, 16, 8, 0].map do |shift|
|
|
267
|
+
(colors.sum { |color| ((color >> shift) & 255) * counts[color] }.to_f / weight).round
|
|
268
|
+
end
|
|
269
|
+
representative = ChunkyPNG::Color.rgba(*channels)
|
|
270
|
+
colors.each { |color| mapping[color] = representative }
|
|
271
|
+
end
|
|
272
|
+
image.pixels.map! { |color| mapping.fetch(color) }
|
|
273
|
+
end
|
|
274
|
+
Base64.strict_encode64(image.to_blob(color_mode: ChunkyPNG::COLOR_INDEXED))
|
|
275
|
+
rescue StandardError => e
|
|
276
|
+
Clacky::Logger.debug("image_quantization_skipped", format: "png", reason: e.message)
|
|
277
|
+
b64
|
|
278
|
+
end
|
|
279
|
+
|
|
229
280
|
# Downscale a base64-encoded image so its width is at most max_width pixels.
|
|
230
281
|
#
|
|
231
282
|
# Strategy:
|
|
@@ -441,6 +441,91 @@ module Clacky
|
|
|
441
441
|
}
|
|
442
442
|
},
|
|
443
443
|
|
|
444
|
+
# GPT-6.1 Sol served through the openclacky gateway (oc- prefix, TokHub
|
|
445
|
+
# gpt group) — the same model as abs-gpt-6.1-sol, which is served by
|
|
446
|
+
# Bedrock; the two aliases exist because the lanes are priced differently.
|
|
447
|
+
# OpenAI's own card, tiered at 272K prompt tokens (the model's breakpoint,
|
|
448
|
+
# not the global 200K): above it every bucket doubles.
|
|
449
|
+
"oc-gpt-6.1-sol" => {
|
|
450
|
+
input: {
|
|
451
|
+
default: 2.00, # $2.00/MTok
|
|
452
|
+
over_272k: 4.00 # $4.00/MTok
|
|
453
|
+
},
|
|
454
|
+
output: {
|
|
455
|
+
default: 10.00, # $10.00/MTok
|
|
456
|
+
over_272k: 20.00 # $20.00/MTok
|
|
457
|
+
},
|
|
458
|
+
cache: {
|
|
459
|
+
write_default: 2.50, # $2.50/MTok cache write
|
|
460
|
+
write_over_272k: 5.00,
|
|
461
|
+
read_default: 0.10, # $0.10/MTok cache hit
|
|
462
|
+
read_over_272k: 0.20
|
|
463
|
+
},
|
|
464
|
+
tier_threshold: 272_000
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
# GPT-6 Astra served through the openclacky gateway (oc- prefix, same
|
|
468
|
+
# ciyuan gpt group as oc-gpt-6.1-sol, hence the same 272K breakpoint and
|
|
469
|
+
# the same series discount). Its card is much higher than Sol's.
|
|
470
|
+
"oc-gpt-6-astra" => {
|
|
471
|
+
input: {
|
|
472
|
+
default: 10.00, # $10.00/MTok
|
|
473
|
+
over_272k: 20.00 # $20.00/MTok
|
|
474
|
+
},
|
|
475
|
+
output: {
|
|
476
|
+
default: 50.00, # $50.00/MTok
|
|
477
|
+
over_272k: 75.00 # $75.00/MTok
|
|
478
|
+
},
|
|
479
|
+
cache: {
|
|
480
|
+
write_default: 12.50, # $12.50/MTok cache write
|
|
481
|
+
write_over_272k: 25.00,
|
|
482
|
+
read_default: 1.00, # $1.00/MTok cache hit
|
|
483
|
+
read_over_272k: 2.00
|
|
484
|
+
},
|
|
485
|
+
tier_threshold: 272_000
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
# GPT-6 Sol rides the same ciyuan gpt group and 272K breakpoint, and opens
|
|
489
|
+
# at the same $2 / $10 as 6.1 Sol, but its long-context card is its own:
|
|
490
|
+
# output goes to $15 (1.5x, not 2x) while cache reads jump 4x to $0.40.
|
|
491
|
+
"oc-gpt-6-sol" => {
|
|
492
|
+
input: {
|
|
493
|
+
default: 2.00, # $2.00/MTok
|
|
494
|
+
over_272k: 4.00 # $4.00/MTok
|
|
495
|
+
},
|
|
496
|
+
output: {
|
|
497
|
+
default: 10.00, # $10.00/MTok
|
|
498
|
+
over_272k: 15.00 # $15.00/MTok
|
|
499
|
+
},
|
|
500
|
+
cache: {
|
|
501
|
+
write_default: 2.50, # $2.50/MTok cache write
|
|
502
|
+
write_over_272k: 5.00,
|
|
503
|
+
read_default: 0.10, # $0.10/MTok cache hit
|
|
504
|
+
read_over_272k: 0.40
|
|
505
|
+
},
|
|
506
|
+
tier_threshold: 272_000
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
# GPT-6 Luna rides the same ciyuan gpt group and 272K breakpoint as
|
|
510
|
+
# Sol/Astra, but sits at the cheap end: $0.10 in / $0.50 out.
|
|
511
|
+
"oc-gpt-6-luna" => {
|
|
512
|
+
input: {
|
|
513
|
+
default: 0.10, # $0.10/MTok
|
|
514
|
+
over_272k: 0.20 # $0.20/MTok
|
|
515
|
+
},
|
|
516
|
+
output: {
|
|
517
|
+
default: 0.50, # $0.50/MTok
|
|
518
|
+
over_272k: 0.75 # $0.75/MTok
|
|
519
|
+
},
|
|
520
|
+
cache: {
|
|
521
|
+
write_default: 0.125, # $0.125/MTok cache write
|
|
522
|
+
write_over_272k: 0.25,
|
|
523
|
+
read_default: 0.01, # $0.01/MTok cache hit
|
|
524
|
+
read_over_272k: 0.02
|
|
525
|
+
},
|
|
526
|
+
tier_threshold: 272_000
|
|
527
|
+
},
|
|
528
|
+
|
|
444
529
|
# Grok 4.7 served through the openclacky gateway (oc- prefix, TokHub
|
|
445
530
|
# upstream). Official xAI list price; the long-context surcharge xAI
|
|
446
531
|
# applies above 200K prompt tokens is not passed through, so both tiers
|
|
@@ -970,6 +1055,7 @@ module Clacky
|
|
|
970
1055
|
# rest of this file; normalize_model_name() lowercases incoming model
|
|
971
1056
|
# names before lookup.
|
|
972
1057
|
|
|
1058
|
+
# M2.x is retired on MiniMax's own API but still served via OpenRouter/Ollama — keep these rows.
|
|
973
1059
|
# M2.5 — high-throughput text model, optimised for coding and agent tasks
|
|
974
1060
|
# (40,960-token context window). Listed at Pay-as-You-Go prices.
|
|
975
1061
|
# Source: https://www.minimax.io/models/text (MiniMax-M2.5 product page).
|
|
@@ -979,19 +1065,25 @@ module Clacky
|
|
|
979
1065
|
cache: { write: 0.30, read: 0.03 }
|
|
980
1066
|
},
|
|
981
1067
|
|
|
982
|
-
# M3 (released 2026-
|
|
983
|
-
#
|
|
984
|
-
#
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1068
|
+
# M3.1-Flash (released 2026-10-19) — flat pricing, 1M context. Source:
|
|
1069
|
+
# https://www.minimax.io/blog/minimax-m31f. No cache-write price is
|
|
1070
|
+
# published; billed at the input rate per this table's convention.
|
|
1071
|
+
"minimax-m3.1-flash" => {
|
|
1072
|
+
input: { default: 0.15, over_200k: 0.15 },
|
|
1073
|
+
output: { default: 0.60, over_200k: 0.60 },
|
|
1074
|
+
cache: { write: 0.15, read: 0.015 }
|
|
1075
|
+
},
|
|
1076
|
+
|
|
1077
|
+
# M3 — tiered at 512K input tokens; cache write billed at the input rate.
|
|
1078
|
+
# Source: https://platform.minimax.io/docs/guides/pricing-paygo (Standard).
|
|
991
1079
|
"minimax-m3" => {
|
|
992
|
-
input: { default: 0.
|
|
993
|
-
output: { default:
|
|
994
|
-
cache: {
|
|
1080
|
+
input: { default: 0.30, over_512k: 0.60 },
|
|
1081
|
+
output: { default: 1.20, over_512k: 2.40 },
|
|
1082
|
+
cache: {
|
|
1083
|
+
write_default: 0.30, write_over_512k: 0.60,
|
|
1084
|
+
read_default: 0.06, read_over_512k: 0.12
|
|
1085
|
+
},
|
|
1086
|
+
tier_threshold: 512_000
|
|
995
1087
|
},
|
|
996
1088
|
|
|
997
1089
|
# M2.7 — text-only model (204,800-token context window). Listed at
|
|
@@ -1126,9 +1218,20 @@ module Clacky
|
|
|
1126
1218
|
{ match: /\Aoc-glm-/, rate: 0.95, reason: "channel" }, # GLM via ciyuan glm - 5% off
|
|
1127
1219
|
{ match: /\Aoc-kimi-/, rate: 0.9, reason: "channel" }, # Kimi K3 via ciyuan - 10% off
|
|
1128
1220
|
{ match: /\Aoc-grok-/, rate: 0.3, reason: "channel" }, # Grok 4.7 via ciyuan grok - 70% off
|
|
1221
|
+
{ match: /\Aoc-gpt-/, rate: 0.3, reason: "channel" }, # GPT-6.1 Sol via ciyuan gpt - 70% off
|
|
1129
1222
|
{ match: /\Aor-(gemini|veo)-/, rate: 0.8, reason: "promo" } # Vertex Gemini/Veo launch promo - 20% off
|
|
1130
1223
|
].freeze
|
|
1131
1224
|
|
|
1225
|
+
# Lanes sold above the rates in PRICING_TABLE. Bedrock GPT is the only
|
|
1226
|
+
# one: it bills at OpenAI list (Global CRIS carries no premium), so its
|
|
1227
|
+
# 20% is a pricing decision on top of the standard 5% processing fee -
|
|
1228
|
+
# mirrored by bedrockOpenAIMarkup in llm_proxy. BYOK callers hit Bedrock
|
|
1229
|
+
# under their own account and keep the list price, so only the "abs-"
|
|
1230
|
+
# alias matches.
|
|
1231
|
+
MARKUP_RULES = [
|
|
1232
|
+
{ match: /\Aabs-gpt-/, rate: 1.2 }
|
|
1233
|
+
].freeze
|
|
1234
|
+
|
|
1132
1235
|
class << self
|
|
1133
1236
|
# Matching promotion for a model alias, or nil when none applies.
|
|
1134
1237
|
# rate - multiplier applied to the list price
|
|
@@ -1149,6 +1252,20 @@ module Clacky
|
|
|
1149
1252
|
info ? info[:rate] : 1.0
|
|
1150
1253
|
end
|
|
1151
1254
|
|
|
1255
|
+
# Multiplier applied to the list price by the lane serving this alias.
|
|
1256
|
+
def markup_rate(model)
|
|
1257
|
+
name = model.to_s.downcase
|
|
1258
|
+
return 1.0 if name.empty?
|
|
1259
|
+
|
|
1260
|
+
rule = MARKUP_RULES.find { |r| r[:match].match?(name) }
|
|
1261
|
+
rule ? rule[:rate] : 1.0
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
# What a token actually costs us: promotion and lane markup combined.
|
|
1265
|
+
def effective_rate(model)
|
|
1266
|
+
discount_rate(model) * markup_rate(model)
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1152
1269
|
# Calculate cost for the given model and usage
|
|
1153
1270
|
#
|
|
1154
1271
|
# @param model [String] Model identifier
|
|
@@ -1213,7 +1330,7 @@ module Clacky
|
|
|
1213
1330
|
# Promotions are keyed on the requested alias, so apply them last: the
|
|
1214
1331
|
# table above holds list prices shared with BYOK callers.
|
|
1215
1332
|
{
|
|
1216
|
-
cost: (input_cost + output_cost + cache_cost) *
|
|
1333
|
+
cost: (input_cost + output_cost + cache_cost) * effective_rate(model),
|
|
1217
1334
|
source: source
|
|
1218
1335
|
}
|
|
1219
1336
|
end
|
|
@@ -1378,6 +1495,8 @@ module Clacky
|
|
|
1378
1495
|
"glm-4.7"
|
|
1379
1496
|
# MiniMax — model ids in providers.rb use capitalised "MiniMax-M*"
|
|
1380
1497
|
# but we match case-insensitively and map to the lowercased table key.
|
|
1498
|
+
when /^minimax-m3[.]1-flash$/i
|
|
1499
|
+
"minimax-m3.1-flash"
|
|
1381
1500
|
when /^minimax-m3$/i
|
|
1382
1501
|
"minimax-m3"
|
|
1383
1502
|
when /^minimax-m2\.7$/i
|
data/lib/clacky/version.rb
CHANGED