openclacky 1.5.3 → 1.5.5
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/.dockerignore +13 -0
- data/CHANGELOG.md +57 -0
- data/Dockerfile +21 -1
- data/README.md +26 -7
- data/README_CN.md +26 -7
- data/README_JA.md +26 -7
- data/lib/clacky/agent/message_compressor.rb +4 -29
- data/lib/clacky/agent/message_compressor_helper.rb +17 -16
- data/lib/clacky/agent/session_serializer.rb +7 -0
- data/lib/clacky/agent.rb +14 -2
- data/lib/clacky/agent_config.rb +10 -5
- data/lib/clacky/agent_profile.rb +25 -3
- data/lib/clacky/brand_config.rb +32 -12
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +15 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +1 -5
- data/lib/clacky/default_extensions/ext-studio/ext.yml +2 -2
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +43 -34
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +9 -3
- data/lib/clacky/default_extensions/git/ext.yml +3 -0
- data/lib/clacky/default_extensions/meeting/ext.yml +3 -0
- data/lib/clacky/default_extensions/time_machine/ext.yml +4 -0
- data/lib/clacky/default_parsers/xlsx_parser.py +66 -0
- data/lib/clacky/default_skills/new/SKILL.md +17 -12
- data/lib/clacky/extension/api_extension.rb +5 -2
- data/lib/clacky/extension/loader.rb +1 -0
- data/lib/clacky/extension/packager.rb +60 -15
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/message_format/anthropic.rb +1 -1
- data/lib/clacky/message_format/bedrock.rb +1 -1
- data/lib/clacky/message_format/open_ai.rb +34 -3
- data/lib/clacky/providers.rb +77 -8
- data/lib/clacky/server/http_server.rb +495 -39
- data/lib/clacky/server/project_manager.rb +150 -0
- data/lib/clacky/server/server_master.rb +7 -0
- data/lib/clacky/server/session_registry.rb +14 -1
- data/lib/clacky/server/web_ui_controller.rb +12 -2
- data/lib/clacky/session_manager.rb +6 -4
- data/lib/clacky/tools/file_reader.rb +2 -1
- data/lib/clacky/tools/terminal.rb +30 -3
- data/lib/clacky/utils/model_pricing.rb +69 -18
- data/lib/clacky/utils/parser_manager.rb +115 -7
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1216 -318
- data/lib/clacky/web/app.js +27 -4
- data/lib/clacky/web/components/notify.js +139 -22
- data/lib/clacky/web/core/aside.js +21 -0
- data/lib/clacky/web/features/brand/store.js +3 -1
- data/lib/clacky/web/features/brand/view.js +29 -5
- data/lib/clacky/web/features/extensions/store.js +214 -9
- data/lib/clacky/web/features/extensions/view.js +156 -11
- data/lib/clacky/web/features/new-session/store.js +25 -3
- data/lib/clacky/web/features/new-session/view.js +339 -142
- data/lib/clacky/web/features/skills/view.js +1 -1
- data/lib/clacky/web/features/trash/store.js +23 -9
- data/lib/clacky/web/features/trash/view.js +51 -0
- data/lib/clacky/web/i18n.js +124 -15
- data/lib/clacky/web/index.html +105 -79
- data/lib/clacky/web/projects.js +1143 -0
- data/lib/clacky/web/sessions.js +320 -538
- data/lib/clacky/web/settings.js +138 -5
- data/lib/clacky/web/utils.js +1 -0
- data/lib/clacky/web/ws-dispatcher.js +98 -4
- data/scripts/build/src/install_system_deps.sh.cc +11 -3
- data/scripts/install_system_deps.sh +11 -3
- metadata +5 -2
- data/lib/clacky/default_parsers/xlsx_parser.rb +0 -121
|
@@ -18,6 +18,14 @@ module Clacky
|
|
|
18
18
|
MANIFEST = "ext.yml"
|
|
19
19
|
MAX_ZIP_SIZE = 50 * 1024 * 1024
|
|
20
20
|
|
|
21
|
+
# Network timeouts for the download step. `read_timeout` is an *idle*
|
|
22
|
+
# timeout (resets on every chunk received), so a stalled connection with
|
|
23
|
+
# no new bytes flowing for this long is treated as dead — not a "download
|
|
24
|
+
# still going, just slow" situation, which large files on a slow link
|
|
25
|
+
# would otherwise trigger.
|
|
26
|
+
OPEN_TIMEOUT = 15 # seconds to establish the connection
|
|
27
|
+
READ_TIMEOUT = 60 # seconds of silence before giving up mid-download
|
|
28
|
+
|
|
21
29
|
# Platform metadata that leaks in from the developer's OS; never ship it.
|
|
22
30
|
SYSTEM_METADATA = [".DS_Store", "__MACOSX", "Thumbs.db", "desktop.ini"].freeze
|
|
23
31
|
|
|
@@ -56,12 +64,19 @@ module Clacky
|
|
|
56
64
|
# local zip path or an http(s) URL. Validates the archive (single top
|
|
57
65
|
# container with an ext.yml, no path traversal), extracts it, then runs
|
|
58
66
|
# verify on the resolved installed layer.
|
|
59
|
-
|
|
67
|
+
# on_progress is an optional callable that receives a stage hash, e.g.:
|
|
68
|
+
# { stage: "downloading", progress: 45 }
|
|
69
|
+
# { stage: "extracting" }
|
|
70
|
+
# { stage: "verifying" }
|
|
71
|
+
# Caller can use this for progress reporting without coupling to internals.
|
|
72
|
+
def install(source, installed_dir: Clacky::ExtensionLoader::INSTALLED_DIR, force: false, on_progress: nil)
|
|
60
73
|
Dir.mktmpdir("clacky-ext-install") do |tmp|
|
|
61
|
-
|
|
74
|
+
on_progress&.call({ stage: "downloading", progress: 0 })
|
|
75
|
+
zip_path = local_zip_for(source, tmp, on_progress: on_progress)
|
|
76
|
+
|
|
77
|
+
on_progress&.call({ stage: "extracting" })
|
|
62
78
|
extract_root = File.join(tmp, "unpacked")
|
|
63
79
|
FileUtils.mkdir_p(extract_root)
|
|
64
|
-
|
|
65
80
|
extract_zip(zip_path, extract_root)
|
|
66
81
|
|
|
67
82
|
ext_id, container_src = locate_container(extract_root)
|
|
@@ -74,6 +89,7 @@ module Clacky
|
|
|
74
89
|
FileUtils.mkdir_p(target)
|
|
75
90
|
FileUtils.cp_r(Dir.glob("#{container_src}/*"), target)
|
|
76
91
|
|
|
92
|
+
on_progress&.call({ stage: "verifying" })
|
|
77
93
|
Clacky::ExtensionLoader.invalidate_cache!
|
|
78
94
|
units = verify_installed(ext_id, installed_dir)
|
|
79
95
|
|
|
@@ -149,11 +165,11 @@ module Clacky
|
|
|
149
165
|
rel.split(File::SEPARATOR).any? { |seg| SYSTEM_METADATA.include?(seg) }
|
|
150
166
|
end
|
|
151
167
|
|
|
152
|
-
private def local_zip_for(source, tmp)
|
|
168
|
+
private def local_zip_for(source, tmp, on_progress: nil)
|
|
153
169
|
src = source.to_s
|
|
154
170
|
if src.match?(%r{\Ahttps?://})
|
|
155
171
|
dest = File.join(tmp, "download.zip")
|
|
156
|
-
download(src, dest)
|
|
172
|
+
download(src, dest, on_progress: on_progress)
|
|
157
173
|
dest
|
|
158
174
|
else
|
|
159
175
|
path = File.expand_path(src)
|
|
@@ -162,19 +178,48 @@ module Clacky
|
|
|
162
178
|
end
|
|
163
179
|
end
|
|
164
180
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
# Download url → dest file.
|
|
182
|
+
# IMPORTANT: OpenURI's block form (`URI.open(url) { |io| ... }`) buffers
|
|
183
|
+
# the *entire* response into a StringIO/Tempfile via Net::HTTP's
|
|
184
|
+
# read_body before ever yielding `io` — so reading `io` in chunks after
|
|
185
|
+
# the fact reports progress only after the download has already
|
|
186
|
+
# finished. To get progress callbacks that fire while bytes are still
|
|
187
|
+
# arriving on the socket, we hook OpenURI's own `progress_proc` /
|
|
188
|
+
# `content_length_proc`, which run inside read_body as each chunk lands.
|
|
189
|
+
# Respects whatever proxy env vars the user has configured (some CDN
|
|
190
|
+
# domains are only reachable at usable speed through a proxy).
|
|
191
|
+
private def download(url, dest, on_progress: nil)
|
|
192
|
+
content_length = nil
|
|
193
|
+
|
|
194
|
+
content_length_proc = lambda do |len|
|
|
195
|
+
content_length = len if len&.positive?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
progress_proc = lambda do |size|
|
|
199
|
+
raise Error, "download exceeds #{MAX_ZIP_SIZE} bytes" if size > MAX_ZIP_SIZE
|
|
200
|
+
next unless on_progress
|
|
201
|
+
|
|
202
|
+
pct = if content_length&.positive?
|
|
203
|
+
[(size * 100 / content_length).floor, 99].min
|
|
204
|
+
else
|
|
205
|
+
(90 - 90 * Math.exp(-size.to_f / (1024 * 1024))).floor
|
|
206
|
+
end
|
|
207
|
+
on_progress.call({ stage: "downloading", progress: pct })
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
URI.open(
|
|
211
|
+
url, "rb",
|
|
212
|
+
open_timeout: OPEN_TIMEOUT,
|
|
213
|
+
read_timeout: READ_TIMEOUT,
|
|
214
|
+
content_length_proc: content_length_proc,
|
|
215
|
+
progress_proc: progress_proc
|
|
216
|
+
) do |io| # rubocop:disable Security/Open
|
|
217
|
+
File.open(dest, "wb") { |out| out.write(io.read) }
|
|
175
218
|
end
|
|
176
219
|
rescue OpenURI::HTTPError, SocketError => e
|
|
177
220
|
raise Error, "failed to download #{url}: #{e.message}"
|
|
221
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
222
|
+
raise Error, "download timed out: #{e.message}"
|
|
178
223
|
end
|
|
179
224
|
|
|
180
225
|
private def extract_zip(zip_path, dest_root)
|
|
@@ -22,7 +22,7 @@ module Clacky
|
|
|
22
22
|
PANEL_KEYS = %w[id title title_zh description description_zh view order attach entry_points].freeze
|
|
23
23
|
API_KEYS = %w[id handler].freeze
|
|
24
24
|
SKILL_KEYS = %w[id dir protected].freeze
|
|
25
|
-
AGENT_KEYS = %w[id title title_zh description description_zh order prompt panels skills avatar].freeze
|
|
25
|
+
AGENT_KEYS = %w[id title title_zh description description_zh order prompt panels skills avatar hidden].freeze
|
|
26
26
|
CHANNEL_KEYS = %w[id platform adapter].freeze
|
|
27
27
|
PATCH_KEYS = %w[target file fingerprint on_mismatch].freeze
|
|
28
28
|
HOOK_KEYS = %w[event file].freeze
|
|
@@ -114,7 +114,7 @@ module Clacky
|
|
|
114
114
|
private_class_method def self.normalized_effort(effort)
|
|
115
115
|
return nil if effort.nil? || effort.to_s.empty?
|
|
116
116
|
s = effort.to_s
|
|
117
|
-
%w[low medium high].include?(s) ? s : nil
|
|
117
|
+
%w[low medium high xhigh max].include?(s) ? s : nil
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
# Merge consecutive tool_result user messages into a single user message.
|
|
@@ -91,7 +91,7 @@ module Clacky
|
|
|
91
91
|
|
|
92
92
|
private_class_method def self.additional_fields_for_effort(effort)
|
|
93
93
|
return nil if effort.nil? || effort.to_s.empty?
|
|
94
|
-
return nil unless %w[low medium high].include?(effort.to_s)
|
|
94
|
+
return nil unless %w[low medium high xhigh max].include?(effort.to_s)
|
|
95
95
|
{
|
|
96
96
|
thinking: { type: "adaptive" },
|
|
97
97
|
output_config: { effort: effort.to_s }
|
|
@@ -195,10 +195,16 @@ module Clacky
|
|
|
195
195
|
# ── Private helpers ───────────────────────────────────────────────────────
|
|
196
196
|
|
|
197
197
|
# Returns the token-limit field name for the given model.
|
|
198
|
-
# Most OpenAI-compatible APIs use :max_tokens; MiMo-V2.5 (Xiaomi)
|
|
199
|
-
# :max_completion_tokens to cap the combined thinking +
|
|
198
|
+
# Most OpenAI-compatible APIs use :max_tokens; MiMo-V2.5 (Xiaomi) and
|
|
199
|
+
# MiniMax-M3 use :max_completion_tokens to cap the combined thinking +
|
|
200
|
+
# answer length (M3 documents max_completion_tokens over the legacy
|
|
201
|
+
# max_tokens field).
|
|
200
202
|
private_class_method def self.token_field_for(model)
|
|
201
|
-
model.to_s.match?(/^mimo-v2/i) ?
|
|
203
|
+
if model.to_s.match?(/^mimo-v2/i) || model.to_s.match?(/^minimax-m3$/i)
|
|
204
|
+
:max_completion_tokens
|
|
205
|
+
else
|
|
206
|
+
:max_tokens
|
|
207
|
+
end
|
|
202
208
|
end
|
|
203
209
|
|
|
204
210
|
# Apply model-specific reasoning / thinking parameters to the request body.
|
|
@@ -256,6 +262,31 @@ module Clacky
|
|
|
256
262
|
elsif !effort_str.empty?
|
|
257
263
|
body[:thinking] = { type: "enabled" }
|
|
258
264
|
end
|
|
265
|
+
elsif model.to_s.match?(/^minimax-m3$/i)
|
|
266
|
+
# MiniMax-M3 controls thinking via a top-level "thinking" field with
|
|
267
|
+
# { type: "adaptive" | "disabled" } — the same envelope shape MiMo
|
|
268
|
+
# uses, but with MiniMax-specific values. Adaptive thinking is the
|
|
269
|
+
# server default when the field is omitted, so we only send the
|
|
270
|
+
# field to explicitly switch modes:
|
|
271
|
+
# - reasoning_effort "off"/"disabled" → thinking disabled
|
|
272
|
+
# (M3 answers directly; M2.7 keeps thinking on regardless).
|
|
273
|
+
# - any other effort → adaptive thinking (M3
|
|
274
|
+
# decides per request); we let the server pick the intensity.
|
|
275
|
+
# M2.7 cannot disable thinking and ignores this field, so sending
|
|
276
|
+
# it is harmless for the rest of the MiniMax lineup.
|
|
277
|
+
if %w[off nothink disabled].include?(effort_str)
|
|
278
|
+
body[:thinking] = { type: "disabled" }
|
|
279
|
+
elsif !effort_str.empty?
|
|
280
|
+
body[:thinking] = { type: "adaptive" }
|
|
281
|
+
end
|
|
282
|
+
elsif model.to_s.match?(/deepseek/i)
|
|
283
|
+
# DeepSeek V4 reasoning_effort only accepts low/high/max.
|
|
284
|
+
# xhigh is not recognized — map it to max.
|
|
285
|
+
effort = case effort_str
|
|
286
|
+
when "xhigh" then "max"
|
|
287
|
+
else effort_str
|
|
288
|
+
end
|
|
289
|
+
body[:reasoning_effort] = effort unless effort.empty?
|
|
259
290
|
elsif reasoning_effort && !effort_str.empty?
|
|
260
291
|
body[:reasoning_effort] = effort_str
|
|
261
292
|
end
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -29,12 +29,14 @@ module Clacky
|
|
|
29
29
|
"name" => "OpenClacky",
|
|
30
30
|
"base_url" => "https://api.openclacky.com",
|
|
31
31
|
"api" => "bedrock",
|
|
32
|
-
"default_model" => "abs-claude-sonnet-
|
|
32
|
+
"default_model" => "abs-claude-sonnet-5",
|
|
33
33
|
"models" => [
|
|
34
34
|
"abs-claude-fable-5",
|
|
35
|
+
"abs-claude-opus-5",
|
|
35
36
|
"abs-claude-opus-4-8",
|
|
36
37
|
"abs-claude-opus-4-7",
|
|
37
38
|
"abs-claude-opus-4-6",
|
|
39
|
+
"abs-claude-sonnet-5",
|
|
38
40
|
"abs-claude-sonnet-4-6",
|
|
39
41
|
"abs-claude-sonnet-4-5",
|
|
40
42
|
"abs-claude-haiku-4-5",
|
|
@@ -134,9 +136,11 @@ module Clacky
|
|
|
134
136
|
# no injection happens when the default model is already lite-class.
|
|
135
137
|
"lite_models" => {
|
|
136
138
|
"abs-claude-fable-5" => "abs-claude-haiku-4-5",
|
|
139
|
+
"abs-claude-opus-5" => "abs-claude-haiku-4-5",
|
|
137
140
|
"abs-claude-opus-4-8" => "abs-claude-haiku-4-5",
|
|
138
141
|
"abs-claude-opus-4-7" => "abs-claude-haiku-4-5",
|
|
139
142
|
"abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
|
|
143
|
+
"abs-claude-sonnet-5" => "abs-claude-haiku-4-5",
|
|
140
144
|
"abs-claude-sonnet-4-6" => "abs-claude-haiku-4-5",
|
|
141
145
|
"abs-claude-sonnet-4-5" => "abs-claude-haiku-4-5",
|
|
142
146
|
"dsk-deepseek-v4-pro" => "dsk-deepseek-v4-flash",
|
|
@@ -145,7 +149,9 @@ module Clacky
|
|
|
145
149
|
# Fallback chain: if a model is unavailable, try the next one in order.
|
|
146
150
|
# Keys are primary model names; values are the fallback model to use instead.
|
|
147
151
|
"fallback_models" => {
|
|
148
|
-
"abs-claude-fable-5" => "abs-claude-opus-
|
|
152
|
+
"abs-claude-fable-5" => "abs-claude-opus-5",
|
|
153
|
+
"abs-claude-opus-5" => "abs-claude-opus-4-8",
|
|
154
|
+
"abs-claude-sonnet-5" => "abs-claude-sonnet-4-6",
|
|
149
155
|
"abs-claude-sonnet-4-6" => "abs-claude-sonnet-4-5"
|
|
150
156
|
},
|
|
151
157
|
# Secondary gateway URL used when the primary base_url is unreachable
|
|
@@ -254,18 +260,20 @@ module Clacky
|
|
|
254
260
|
# so capability checks (vision=false) fire correctly regardless of
|
|
255
261
|
# which endpoint the user configured.
|
|
256
262
|
"endpoint_variants" => [
|
|
257
|
-
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn",
|
|
258
|
-
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international",
|
|
263
|
+
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://api.minimaxi.com/v1", "region" => "cn" }.freeze,
|
|
264
|
+
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://api.minimax.io/v1", "region" => "intl" }.freeze
|
|
259
265
|
].freeze,
|
|
260
|
-
# MiniMax M2.
|
|
261
|
-
#
|
|
262
|
-
#
|
|
266
|
+
# MiniMax M2.5/M2.7 are text-only on this endpoint. M3 (released 2026-06-01)
|
|
267
|
+
# is natively multimodal — it accepts image input via OpenAI-style
|
|
268
|
+
# image_url content parts — so it overrides the provider-level
|
|
269
|
+
# vision=false below. M3 exposes a 1,000,000-token context window
|
|
270
|
+
# (M2.7: 204,800; M2.5: 40,960).
|
|
263
271
|
"capabilities" => { "vision" => false }.freeze,
|
|
264
272
|
"model_capabilities" => {
|
|
265
273
|
"MiniMax-M3" => { "vision" => true }.freeze
|
|
266
274
|
}.freeze,
|
|
267
275
|
"default_ocr_model" => "MiniMax-M3",
|
|
268
|
-
"website_url" => "https://
|
|
276
|
+
"website_url" => "https://platform.minimax.io/"
|
|
269
277
|
}.freeze,
|
|
270
278
|
|
|
271
279
|
"kimi" => {
|
|
@@ -469,6 +477,67 @@ module Clacky
|
|
|
469
477
|
"website_url" => "https://console.volcengine.com/ark/region:cn-beijing/overview"
|
|
470
478
|
}.freeze,
|
|
471
479
|
|
|
480
|
+
"ollama" => {
|
|
481
|
+
"name" => "Ollama (Cloud)",
|
|
482
|
+
# Ollama Cloud API — OpenAI-compatible endpoint serving both local and
|
|
483
|
+
# cloud models. Cloud models are available immediately without pulling.
|
|
484
|
+
# Authentication: API key via Authorization: Bearer <key> header.
|
|
485
|
+
# Create API keys at: https://ollama.com/settings/keys
|
|
486
|
+
"base_url" => "https://ollama.com",
|
|
487
|
+
"api" => "openai-completions",
|
|
488
|
+
"default_model" => "deepseek-v4-flash",
|
|
489
|
+
# Curated list of cloud-enabled models from the Ollama library.
|
|
490
|
+
# Users can type any Ollama model name manually; this list seeds the picker.
|
|
491
|
+
"models" => [
|
|
492
|
+
"glm-5.2",
|
|
493
|
+
"kimi-k3",
|
|
494
|
+
"gemma4",
|
|
495
|
+
"qwen3.5",
|
|
496
|
+
"glm-5.1",
|
|
497
|
+
"minimax-m2.7",
|
|
498
|
+
"nemotron-3-super",
|
|
499
|
+
"minimax-m3",
|
|
500
|
+
"kimi-k2.7-code",
|
|
501
|
+
"kimi-k2.6",
|
|
502
|
+
"deepseek-v4-flash",
|
|
503
|
+
"deepseek-v4-pro",
|
|
504
|
+
"nemotron-3-ultra",
|
|
505
|
+
"gemini-3-flash-preview",
|
|
506
|
+
"nemotron-3-nano",
|
|
507
|
+
"mistral-large-3",
|
|
508
|
+
"gpt-oss"
|
|
509
|
+
],
|
|
510
|
+
# Provider-level default: many Ollama cloud models are vision-capable.
|
|
511
|
+
"capabilities" => { "vision" => true }.freeze,
|
|
512
|
+
# Model-level overrides: text-only models that don't accept image input.
|
|
513
|
+
"model_capabilities" => {
|
|
514
|
+
"glm-5.2" => { "vision" => false }.freeze,
|
|
515
|
+
"glm-5.1" => { "vision" => false }.freeze,
|
|
516
|
+
"minimax-m2.7" => { "vision" => false }.freeze,
|
|
517
|
+
"deepseek-v4-pro" => { "vision" => false }.freeze,
|
|
518
|
+
"deepseek-v4-flash" => { "vision" => false }.freeze,
|
|
519
|
+
"nemotron-3-super" => { "vision" => false }.freeze,
|
|
520
|
+
"nemotron-3-ultra" => { "vision" => false }.freeze,
|
|
521
|
+
"nemotron-3-nano" => { "vision" => false }.freeze,
|
|
522
|
+
"gpt-oss" => { "vision" => false }.freeze
|
|
523
|
+
}.freeze,
|
|
524
|
+
# Per-primary lite pairing: subagents use smaller models for cheap/fast work.
|
|
525
|
+
"lite_models" => {
|
|
526
|
+
"glm-5.2" => "glm-5.1",
|
|
527
|
+
"kimi-k3" => "kimi-k2.6",
|
|
528
|
+
"gemma4" => "nemotron-3-nano",
|
|
529
|
+
"qwen3.5" => "gemini-3-flash-preview",
|
|
530
|
+
"minimax-m3" => "minimax-m2.7",
|
|
531
|
+
"kimi-k2.7-code" => "kimi-k2.6",
|
|
532
|
+
"deepseek-v4-pro" => "deepseek-v4-flash",
|
|
533
|
+
"mistral-large-3" => "nemotron-3-nano",
|
|
534
|
+
"gpt-oss" => "nemotron-3-nano",
|
|
535
|
+
"gemini-3-flash-preview" => "nemotron-3-nano"
|
|
536
|
+
},
|
|
537
|
+
"default_ocr_model" => "kimi-k2.7-code",
|
|
538
|
+
"website_url" => "https://ollama.com/settings/keys"
|
|
539
|
+
}.freeze,
|
|
540
|
+
|
|
472
541
|
"openai" => {
|
|
473
542
|
"name" => "OpenAI (GPT)",
|
|
474
543
|
"base_url" => "https://api.openai.com/v1",
|