openclacky 1.5.8 → 1.5.9
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 +38 -0
- data/README.md +1 -1
- data/README_CN.md +1 -1
- data/README_JA.md +1 -1
- data/lib/clacky/brand_config.rb +12 -7
- data/lib/clacky/default_extensions/ext-studio/ext.yml +1 -1
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +4 -4
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +19 -3
- data/lib/clacky/extension/scaffold/templates/hello/test/handler_test.rb.erb +22 -0
- data/lib/clacky/message_format/anthropic.rb +19 -2
- data/lib/clacky/message_format/bedrock.rb +8 -3
- data/lib/clacky/providers.rb +157 -104
- data/lib/clacky/server/channel/adapters/feishu/ws_client.rb +6 -0
- data/lib/clacky/server/http_server.rb +7 -3
- data/lib/clacky/skill_loader.rb +4 -0
- data/lib/clacky/tools/base.rb +11 -1
- data/lib/clacky/tools/edit.rb +28 -0
- data/lib/clacky/tools/terminal.rb +5 -0
- data/lib/clacky/tools/trash_manager.rb +1 -1
- data/lib/clacky/utils/model_pricing.rb +62 -27
- data/lib/clacky/utils/string_matcher.rb +29 -2
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +146 -13
- data/lib/clacky/web/features/extensions/store.js +64 -40
- data/lib/clacky/web/features/extensions/view.js +46 -5
- data/lib/clacky/web/features/version/view.js +2 -2
- data/lib/clacky/web/i18n.js +16 -4
- data/lib/clacky/web/index.html +6 -5
- data/lib/clacky/web/projects.js +10 -3
- data/lib/clacky/web/sessions.js +151 -17
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 565adf42059330fb821c57cb3b1200035fb866a1f2190ca87e093bc34de9edc0
|
|
4
|
+
data.tar.gz: 89db4dd987f39d4b77acd52121aa31bef54858f81d614557ac7b9984a1066ba0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3bbb8988d833e3c6e6ce4cd13da64111e2ee15b400c2eb34b78bca9f51ae80525336baf923ba18cdbf85b5320d542f8050567792e7d5a437b28e2d09d29729d
|
|
7
|
+
data.tar.gz: ae508f3bdb44a35b8edd0c89ebcc53a64d3f7340214ded6c8dd47d0c757c66f5f99700f466c86278e8ee0a38ab838b8509c80ba8af2f253653f967b91f52a814
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
|
|
7
|
+
## [1.5.9] - 2026-08-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- OrcaRouter as a named provider preset (#480 - @Marc-oss-hub)
|
|
11
|
+
- DeepSeek peak/off-peak billing support
|
|
12
|
+
- Unicode homoglyph-tolerant string matching in edit mode
|
|
13
|
+
- Load-more pagination in extension marketplace
|
|
14
|
+
- Remove-from-project action in session menu
|
|
15
|
+
- Move-to-project submenu in session actions menu
|
|
16
|
+
- Changelog link in version badge and upgrade popover
|
|
17
|
+
- ext-studio scaffolding generates a runnable test/handler_test.rb and promotes "write tests" to its own step
|
|
18
|
+
- Lucide SVG icons for cron and extension group icons
|
|
19
|
+
|
|
20
|
+
### Improved
|
|
21
|
+
- Skill "use" button themed with accent color
|
|
22
|
+
- Reordered provider presets list
|
|
23
|
+
- Replaced native confirm() with a Modal component and refreshed aside icons
|
|
24
|
+
- Billing sidebar icon switched to circle-dollar-sign
|
|
25
|
+
- Settings data tab renamed to backup management
|
|
26
|
+
- Refined project session group indentation and hover styling
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Scroll to top when entering a session group sub-view
|
|
30
|
+
- Zhipu AI website_url now points to the console overview page
|
|
31
|
+
- WSL path translation and CRLF line-ending preservation (#454 - @shipinliang)
|
|
32
|
+
- Project sessions re-render on language switch
|
|
33
|
+
- Corrected arrow direction for move-in/move-out project icons
|
|
34
|
+
- Attachment drafts isolated per session (#478)
|
|
35
|
+
- Pinned sessions prioritized in project session lists
|
|
36
|
+
- Projects.all() returns a sorted list
|
|
37
|
+
- Builtin extension skills classified as system and hidden third-party ext skills from the panel
|
|
38
|
+
- Feishu WebSocket TLS SNI (#479 - @ayers-ltd)
|
|
39
|
+
- stop_reason mapping expanded from 3 to 8 cases (#469 - @shipinliang)
|
|
40
|
+
|
|
41
|
+
### More
|
|
42
|
+
- Shortened ext-developer title
|
|
43
|
+
- cmdbar hover and session group indent tweaks
|
|
44
|
+
|
|
7
45
|
## [1.5.8] - 2026-08-12
|
|
8
46
|
|
|
9
47
|
### Added
|
data/README.md
CHANGED
|
@@ -194,7 +194,7 @@ $ openclacky
|
|
|
194
194
|
|
|
195
195
|
Set your **API Key**, **Model**, and **Base URL** (any OpenAI-compatible provider).
|
|
196
196
|
|
|
197
|
-
Supported out of the box: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter** — or any custom endpoint.
|
|
197
|
+
Supported out of the box: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter** — or any custom endpoint.
|
|
198
198
|
|
|
199
199
|
## Coding use case
|
|
200
200
|
|
data/README_CN.md
CHANGED
|
@@ -189,7 +189,7 @@ $ openclacky
|
|
|
189
189
|
|
|
190
190
|
设置你的 **API Key**、**模型**和 **Base URL**(任意 OpenAI 兼容提供商)。
|
|
191
191
|
|
|
192
|
-
开箱即支持:**Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter**,或任意自定义端点。
|
|
192
|
+
开箱即支持:**Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter**,或任意自定义端点。
|
|
193
193
|
|
|
194
194
|
## 代码开发场景
|
|
195
195
|
|
data/README_JA.md
CHANGED
|
@@ -194,7 +194,7 @@ $ openclacky
|
|
|
194
194
|
|
|
195
195
|
**API Key**、**Model**、**Base URL**(任意の OpenAI 互換プロバイダー)を設定します。
|
|
196
196
|
|
|
197
|
-
標準でサポート: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter** — または任意のカスタムエンドポイント。
|
|
197
|
+
標準でサポート: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter** — または任意のカスタムエンドポイント。
|
|
198
198
|
|
|
199
199
|
## コーディングのユースケース
|
|
200
200
|
|
data/lib/clacky/brand_config.rb
CHANGED
|
@@ -777,21 +777,26 @@ module Clacky
|
|
|
777
777
|
end
|
|
778
778
|
|
|
779
779
|
# Search the public extension marketplace. Anonymous — no license required.
|
|
780
|
-
# Uses GET /api/v1/extensions. Returns { success:, extensions: [], error: }.
|
|
781
|
-
|
|
780
|
+
# Uses GET /api/v1/extensions. Returns { success:, extensions: [], meta:, error: }.
|
|
781
|
+
# `meta` carries the platform's pagination info ({ current_page, total_pages,
|
|
782
|
+
# total_count, per_page }) when available.
|
|
783
|
+
def search_extensions!(query: nil, sort: nil, page: nil, per_page: nil)
|
|
782
784
|
params = {}
|
|
783
|
-
params["q"]
|
|
784
|
-
params["sort"]
|
|
785
|
+
params["q"] = query if query && !query.to_s.strip.empty?
|
|
786
|
+
params["sort"] = sort if sort && !sort.to_s.strip.empty?
|
|
787
|
+
params["page"] = page if page && page.to_i > 1
|
|
788
|
+
params["per_page"] = per_page if per_page && per_page.to_i > 0
|
|
785
789
|
qs = params.empty? ? "" : "?#{URI.encode_www_form(params)}"
|
|
786
790
|
response = platform_client.get("/api/v1/extensions#{qs}")
|
|
787
791
|
|
|
788
792
|
if response[:success]
|
|
789
|
-
|
|
793
|
+
data = response[:data] || {}
|
|
794
|
+
{ success: true, extensions: data["extensions"] || [], meta: data["meta"] }
|
|
790
795
|
else
|
|
791
|
-
{ success: false, error: response[:error] || "Search failed", extensions: [] }
|
|
796
|
+
{ success: false, error: response[:error] || "Search failed", extensions: [], meta: nil }
|
|
792
797
|
end
|
|
793
798
|
rescue StandardError => e
|
|
794
|
-
{ success: false, error: "Network error: #{e.message}", extensions: [] }
|
|
799
|
+
{ success: false, error: "Network error: #{e.message}", extensions: [], meta: nil }
|
|
795
800
|
end
|
|
796
801
|
|
|
797
802
|
# Fetch a single public marketplace extension's detail (contributes +
|
|
@@ -1372,7 +1372,7 @@
|
|
|
1372
1372
|
const unpubBtn = el("button", { class: "studio-btn studio-btn-danger studio-btn-sm", text: t("btn.unpublish") });
|
|
1373
1373
|
unpubBtn.style.marginLeft = "auto";
|
|
1374
1374
|
unpubBtn.addEventListener("click", async () => {
|
|
1375
|
-
if (!
|
|
1375
|
+
if (!await Clacky.Modal.confirm(t("published.confirm", { id: e.id }))) return;
|
|
1376
1376
|
unpubBtn.disabled = true;
|
|
1377
1377
|
try { await postJson("/unpublish", { ext_id: e.id }); loadPublished(); }
|
|
1378
1378
|
catch (err) { unpubBtn.disabled = false; feedback(t("err.generic", { msg: err.message }), "error"); }
|
|
@@ -1525,7 +1525,7 @@
|
|
|
1525
1525
|
actions.appendChild(el("button", { class: "studio-btn studio-btn-primary", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
1526
1526
|
const un = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.unpublish") });
|
|
1527
1527
|
un.addEventListener("click", async () => {
|
|
1528
|
-
if (!
|
|
1528
|
+
if (!await Clacky.Modal.confirm(t("extlist.unpublish.confirm", { id: ext.id }))) return;
|
|
1529
1529
|
un.disabled = true;
|
|
1530
1530
|
try { await postJson("/unpublish", { ext_id: ext.id }); await reload(); rebuild(); }
|
|
1531
1531
|
catch (e) { un.disabled = false; alert(t("err.generic", { msg: e.message })); }
|
|
@@ -1599,7 +1599,7 @@
|
|
|
1599
1599
|
}
|
|
1600
1600
|
|
|
1601
1601
|
async function doDelete(ext, btn) {
|
|
1602
|
-
if (!confirm(t("extlist.delete.confirm", { id: ext.id }))) return;
|
|
1602
|
+
if (!await Clacky.Modal.confirm(t("extlist.delete.confirm", { id: ext.id }))) return;
|
|
1603
1603
|
btn.disabled = true;
|
|
1604
1604
|
try {
|
|
1605
1605
|
const res = await fetch("/api/ext/ext-studio/local", {
|
|
@@ -1766,7 +1766,7 @@
|
|
|
1766
1766
|
try {
|
|
1767
1767
|
let result = await publish(name, isUpdate);
|
|
1768
1768
|
if (!result.ok && result.already_exists && !isUpdate) {
|
|
1769
|
-
if (
|
|
1769
|
+
if (await Clacky.Modal.confirm(`"${name}" already exists on the platform. Overwrite?`)) {
|
|
1770
1770
|
result = await publish(name, true);
|
|
1771
1771
|
} else {
|
|
1772
1772
|
btn.disabled = false;
|
|
@@ -280,14 +280,15 @@ This creates `~/.clacky/ext/local/<id>/` with a working hello panel + handler:
|
|
|
280
280
|
- `ext.yml` — the manifest
|
|
281
281
|
- `panels/hello/view.js` — a panel that pings the backend
|
|
282
282
|
- `api/handler.rb` — a `Clacky::ApiExtension` subclass mounted at `/api/ext/<id>/`
|
|
283
|
+
- `test/handler_test.rb` — a runnable minitest example (`ruby test/handler_test.rb`)
|
|
283
284
|
|
|
284
285
|
Use `--full` only when the user needs the kitchen-sink reference exercising all seven
|
|
285
286
|
contributes types — it's a lot to read, so prefer the plain scaffold otherwise.
|
|
286
287
|
|
|
287
288
|
### 3 — Read what was generated
|
|
288
289
|
|
|
289
|
-
Always read the generated `ext.yml`, `view.js`,
|
|
290
|
-
is your starting point; you'll reshape it to match the idea.
|
|
290
|
+
Always read the generated `ext.yml`, `view.js`, `handler.rb`, and `test/handler_test.rb`
|
|
291
|
+
before editing. This is your starting point; you'll reshape it to match the idea.
|
|
291
292
|
|
|
292
293
|
### 4 — Reshape to the idea
|
|
293
294
|
|
|
@@ -366,7 +367,22 @@ Rules while reshaping:
|
|
|
366
367
|
reference `panels: [id]` and `skills: [id]`. Add those blocks to `ext.yml` only if the
|
|
367
368
|
idea needs them.
|
|
368
369
|
|
|
369
|
-
### 5 —
|
|
370
|
+
### 5 — Write tests
|
|
371
|
+
|
|
372
|
+
Test the backend before you call it done. Rewrite the scaffolded `test/handler_test.rb`
|
|
373
|
+
to exercise your real routes (parse the JSON response, assert its fields), then run it
|
|
374
|
+
green:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
ruby test/handler_test.rb
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
- If `test/` is missing from the scaffold, create `test/handler_test.rb` yourself
|
|
381
|
+
(`mkdir -p test` then write it) — do NOT skip tests just because the file is absent.
|
|
382
|
+
- A handler that only returns a static string may keep the scaffold's hello test;
|
|
383
|
+
anything that fetches, transforms, caches, or computes MUST have a real test.
|
|
384
|
+
|
|
385
|
+
### 6 — Confirm it loads
|
|
370
386
|
|
|
371
387
|
Run `clacky ext verify` and confirm the new units resolve with no errors, then have the
|
|
372
388
|
user reload the WebUI page. If verify reports problems, go to **Debug & verify**.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Run from the container root:
|
|
4
|
+
# ruby test/handler_test.rb
|
|
5
|
+
require "minitest/autorun"
|
|
6
|
+
require "clacky"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
load File.expand_path("../api/handler.rb", __dir__)
|
|
10
|
+
|
|
11
|
+
class <%= const_prefix %>ExtTest < Minitest::Test
|
|
12
|
+
def test_root_route_returns_json
|
|
13
|
+
route = <%= const_prefix %>Ext.routes.find { |r| r.method == :get && r.pattern == "/" }
|
|
14
|
+
refute_nil route, "expected a GET / route"
|
|
15
|
+
|
|
16
|
+
handler = <%= const_prefix %>Ext.new(req: nil, res: nil, route: route, params: {}, http_server: nil)
|
|
17
|
+
halt = assert_raises(Clacky::ApiExtension::Halt) { handler.invoke }
|
|
18
|
+
|
|
19
|
+
assert_equal 200, halt.status
|
|
20
|
+
assert_equal "hello from <%= slug %>", JSON.parse(halt.payload)["message"]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -181,10 +181,27 @@ module Clacky
|
|
|
181
181
|
{ id: tc["id"], type: "function", name: tc["name"], arguments: args }
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
+
# Map Anthropic stop_reason → OpenAI-style finish_reason so downstream
|
|
185
|
+
# (llm_caller empty-response detector, agent loop, CostTracker) stays
|
|
186
|
+
# provider-agnostic. Each cluster shares the same semantic class:
|
|
187
|
+
# stop — natural end of turn (end_turn / pause_turn / stop_sequence)
|
|
188
|
+
# length — token or context-window limit hit (max_tokens /
|
|
189
|
+
# model_context_window_exceeded; mapping the latter to
|
|
190
|
+
# "length" also prevents a futile retry in llm_caller's
|
|
191
|
+
# empty-response detector, which exempts "length")
|
|
192
|
+
# content_filter — model declined (refusal)
|
|
193
|
+
# other — context compaction (compaction)
|
|
194
|
+
# Note: `compaction` is a real stop_reason but only emitted under the
|
|
195
|
+
# `compact-2026-01-12` beta header (server-side context compaction),
|
|
196
|
+
# so it is not yet in the stable SDK's StopReason literal list.
|
|
197
|
+
# See: https://platform.claude.com/docs/en/build-with-claude/compaction
|
|
198
|
+
# Unmapped values fall through unchanged.
|
|
184
199
|
finish_reason = case data["stop_reason"]
|
|
185
|
-
when "end_turn"
|
|
200
|
+
when "end_turn", "pause_turn", "stop_sequence" then "stop"
|
|
186
201
|
when "tool_use" then "tool_calls"
|
|
187
|
-
when "max_tokens" then "length"
|
|
202
|
+
when "max_tokens", "model_context_window_exceeded" then "length"
|
|
203
|
+
when "refusal" then "content_filter"
|
|
204
|
+
when "compaction" then "other"
|
|
188
205
|
else data["stop_reason"]
|
|
189
206
|
end
|
|
190
207
|
|
|
@@ -118,11 +118,16 @@ module Clacky
|
|
|
118
118
|
{ id: tc["toolUseId"], type: "function", name: tc["name"], arguments: args }
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
# Map Bedrock stopReason → canonical finish_reason
|
|
121
|
+
# Map Bedrock stopReason → canonical finish_reason (same clusters as
|
|
122
|
+
# the Anthropic direct adapter — see lib/clacky/message_format/anthropic.rb).
|
|
122
123
|
finish_reason = case data["stopReason"]
|
|
123
|
-
when "end_turn"
|
|
124
|
+
when "end_turn", "pause_turn", "stop_sequence" then "stop"
|
|
124
125
|
when "tool_use" then "tool_calls"
|
|
125
|
-
when "max_tokens" then "length"
|
|
126
|
+
when "max_tokens", "model_context_window_exceeded" then "length"
|
|
127
|
+
when "refusal" then "content_filter"
|
|
128
|
+
# `compaction` is a beta stop_reason (compact-2026-01-12);
|
|
129
|
+
# see anthropic.rb for the full note.
|
|
130
|
+
when "compaction" then "other"
|
|
126
131
|
else data["stopReason"]
|
|
127
132
|
end
|
|
128
133
|
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -253,32 +253,34 @@ module Clacky
|
|
|
253
253
|
"website_url" => "https://platform.deepseek.com/api_keys"
|
|
254
254
|
}.freeze,
|
|
255
255
|
|
|
256
|
-
"
|
|
257
|
-
"name" => "
|
|
258
|
-
"base_url" => "https://
|
|
256
|
+
"glm" => {
|
|
257
|
+
"name" => "GLM (Z.ai / Zhipu)",
|
|
258
|
+
"base_url" => "https://open.bigmodel.cn/api/paas/v4",
|
|
259
259
|
"api" => "openai-completions",
|
|
260
|
-
"default_model" => "
|
|
261
|
-
"models" => ["
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
260
|
+
"default_model" => "glm-5.2",
|
|
261
|
+
"models" => ["glm-5.2", "glm-5.1", "glm-5", "glm-5-turbo", "glm-5v-turbo", "glm-4.7"],
|
|
262
|
+
# Zhipu / Z.ai expose four functionally-equivalent endpoints:
|
|
263
|
+
# two regional sites (mainland open.bigmodel.cn + international api.z.ai)
|
|
264
|
+
# each with a general-billing and a Coding-Plan subpath. They share the
|
|
265
|
+
# same model lineup & identical capability profile, so a single preset
|
|
266
|
+
# with endpoint_variants is the right shape — one source of truth for
|
|
267
|
+
# vision/model_capabilities, four URLs recognised by find_by_base_url.
|
|
268
|
+
# Without this, users pointing at api.z.ai or the /coding/ path fell
|
|
269
|
+
# through to the conservative "assume vision=true" default and got
|
|
270
|
+
# hallucinated image descriptions on text-only GLM models (C-5563).
|
|
267
271
|
"endpoint_variants" => [
|
|
268
|
-
{ "label" => "Mainland
|
|
269
|
-
{ "label" => "
|
|
272
|
+
{ "label" => "Mainland · Pay-as-you-go", "label_key" => "settings.models.baseurl.variant.mainland_cn_payg", "base_url" => "https://open.bigmodel.cn/api/paas/v4", "region" => "cn" }.freeze,
|
|
273
|
+
{ "label" => "Mainland · Coding Plan", "label_key" => "settings.models.baseurl.variant.mainland_cn_coding", "base_url" => "https://open.bigmodel.cn/api/coding/paas/v4", "region" => "cn" }.freeze,
|
|
274
|
+
{ "label" => "International · Pay-as-you-go", "label_key" => "settings.models.baseurl.variant.international_payg", "base_url" => "https://api.z.ai/api/paas/v4", "region" => "intl" }.freeze,
|
|
275
|
+
{ "label" => "International · Coding Plan", "label_key" => "settings.models.baseurl.variant.international_coding","base_url" => "https://api.z.ai/api/coding/paas/v4", "region" => "intl" }.freeze
|
|
270
276
|
].freeze,
|
|
271
|
-
#
|
|
272
|
-
# is natively multimodal — it accepts image input via OpenAI-style
|
|
273
|
-
# image_url content parts — so it overrides the provider-level
|
|
274
|
-
# vision=false below. M3 exposes a 1,000,000-token context window
|
|
275
|
-
# (M2.7: 204,800; M2.5: 40,960).
|
|
277
|
+
# GLM models are text-only except glm-5v-turbo which is vision-capable ("v" = visual).
|
|
276
278
|
"capabilities" => { "vision" => false }.freeze,
|
|
277
279
|
"model_capabilities" => {
|
|
278
|
-
"
|
|
280
|
+
"glm-5v-turbo" => { "vision" => true }.freeze
|
|
279
281
|
}.freeze,
|
|
280
|
-
"default_ocr_model" => "
|
|
281
|
-
"website_url" => "https://
|
|
282
|
+
"default_ocr_model" => "glm-5v-turbo",
|
|
283
|
+
"website_url" => "https://open.bigmodel.cn/console/overview"
|
|
282
284
|
}.freeze,
|
|
283
285
|
|
|
284
286
|
"kimi" => {
|
|
@@ -346,6 +348,34 @@ module Clacky
|
|
|
346
348
|
"website_url" => "https://www.kimi.com/code"
|
|
347
349
|
}.freeze,
|
|
348
350
|
|
|
351
|
+
"minimax" => {
|
|
352
|
+
"name" => "Minimax",
|
|
353
|
+
"base_url" => "https://api.minimaxi.com/v1",
|
|
354
|
+
"api" => "openai-completions",
|
|
355
|
+
"default_model" => "MiniMax-M3",
|
|
356
|
+
"models" => ["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.5"],
|
|
357
|
+
# MiniMax operates two regional endpoints with identical APIs & model
|
|
358
|
+
# lineup — mainland China (.com) and international (.io). Listing both
|
|
359
|
+
# lets find_by_base_url identify either one as provider "minimax",
|
|
360
|
+
# so capability checks (vision=false) fire correctly regardless of
|
|
361
|
+
# which endpoint the user configured.
|
|
362
|
+
"endpoint_variants" => [
|
|
363
|
+
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://api.minimaxi.com/v1", "region" => "cn" }.freeze,
|
|
364
|
+
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://api.minimax.io/v1", "region" => "intl" }.freeze
|
|
365
|
+
].freeze,
|
|
366
|
+
# MiniMax M2.5/M2.7 are text-only on this endpoint. M3 (released 2026-06-01)
|
|
367
|
+
# is natively multimodal — it accepts image input via OpenAI-style
|
|
368
|
+
# image_url content parts — so it overrides the provider-level
|
|
369
|
+
# vision=false below. M3 exposes a 1,000,000-token context window
|
|
370
|
+
# (M2.7: 204,800; M2.5: 40,960).
|
|
371
|
+
"capabilities" => { "vision" => false }.freeze,
|
|
372
|
+
"model_capabilities" => {
|
|
373
|
+
"MiniMax-M3" => { "vision" => true }.freeze
|
|
374
|
+
}.freeze,
|
|
375
|
+
"default_ocr_model" => "MiniMax-M3",
|
|
376
|
+
"website_url" => "https://platform.minimax.io/"
|
|
377
|
+
}.freeze,
|
|
378
|
+
|
|
349
379
|
"anthropic" => {
|
|
350
380
|
"name" => "Anthropic (Claude)",
|
|
351
381
|
"base_url" => "https://api.anthropic.com",
|
|
@@ -356,6 +386,70 @@ module Clacky
|
|
|
356
386
|
"website_url" => "https://console.anthropic.com/settings/keys"
|
|
357
387
|
}.freeze,
|
|
358
388
|
|
|
389
|
+
"openai" => {
|
|
390
|
+
"name" => "OpenAI (GPT)",
|
|
391
|
+
"base_url" => "https://api.openai.com/v1",
|
|
392
|
+
"api" => "openai-completions",
|
|
393
|
+
"default_model" => "gpt-5.5",
|
|
394
|
+
"models" => [
|
|
395
|
+
"gpt-5.5",
|
|
396
|
+
"gpt-5.4",
|
|
397
|
+
"gpt-5.4-mini",
|
|
398
|
+
"gpt-5.4-nano",
|
|
399
|
+
"o4-mini",
|
|
400
|
+
"o3"
|
|
401
|
+
],
|
|
402
|
+
# GPT-5.x and o-series models are multimodal (text + image input).
|
|
403
|
+
"capabilities" => { "vision" => true }.freeze,
|
|
404
|
+
# Per-primary lite pairing: subagents use mini/nano for cheap/fast work.
|
|
405
|
+
# o4-mini and o3 are reasoning models without a lite-tier sibling here.
|
|
406
|
+
"lite_models" => {
|
|
407
|
+
"gpt-5.5" => "gpt-5.4-mini",
|
|
408
|
+
"gpt-5.4" => "gpt-5.4-mini"
|
|
409
|
+
},
|
|
410
|
+
# OpenAI's image generation model — same /v1/images/generations
|
|
411
|
+
# endpoint, so the OpenAICompat image provider handles it.
|
|
412
|
+
"image_models" => [
|
|
413
|
+
"gpt-image-2"
|
|
414
|
+
],
|
|
415
|
+
"default_image_model" => "gpt-image-2",
|
|
416
|
+
"default_ocr_model" => "gpt-5.4-mini",
|
|
417
|
+
"website_url" => "https://platform.openai.com/api-keys"
|
|
418
|
+
}.freeze,
|
|
419
|
+
|
|
420
|
+
"qwen" => {
|
|
421
|
+
"name" => "Qwen (Alibaba)",
|
|
422
|
+
"base_url" => "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
423
|
+
"api" => "openai-completions",
|
|
424
|
+
"default_model" => "qwen3.7-max",
|
|
425
|
+
"models" => [
|
|
426
|
+
"qwen3.7-max",
|
|
427
|
+
"qwen3.6-plus",
|
|
428
|
+
"qwen3.6-max",
|
|
429
|
+
"qwen3.6-27b",
|
|
430
|
+
"qwen3.6-flash",
|
|
431
|
+
"qwen-plus-latest",
|
|
432
|
+
],
|
|
433
|
+
"endpoint_variants" => [
|
|
434
|
+
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://dashscope.aliyuncs.com/compatible-mode/v1", "region" => "cn" }.freeze,
|
|
435
|
+
{ "label" => "Singapore", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", "region" => "intl" }.freeze,
|
|
436
|
+
{ "label" => "US (Virginia)", "label_key" => "settings.models.baseurl.variant.us", "base_url" => "https://dashscope-us.aliyuncs.com/compatible-mode/v1", "region" => "us" }.freeze
|
|
437
|
+
].freeze,
|
|
438
|
+
"capabilities" => { "vision" => true }.freeze,
|
|
439
|
+
"model_capabilities" => {
|
|
440
|
+
"qwen3.7-max" => { "vision" => false }.freeze
|
|
441
|
+
}.freeze,
|
|
442
|
+
"default_ocr_model" => "qwen3.6-flash",
|
|
443
|
+
"lite_models" => {
|
|
444
|
+
"qwen3.7-max" => "qwen3.6-flash",
|
|
445
|
+
"qwen3.6-plus" => "qwen3.6-flash",
|
|
446
|
+
"qwen3.6-max" => "qwen3.6-flash",
|
|
447
|
+
"qwen3.6-27b" => "qwen3.6-flash",
|
|
448
|
+
"qwen-plus-latest" => "qwen3.6-flash"
|
|
449
|
+
},
|
|
450
|
+
"website_url" => "https://bailian.console.aliyun.com/?apiKey=1"
|
|
451
|
+
}.freeze,
|
|
452
|
+
|
|
359
453
|
"mimo" => {
|
|
360
454
|
"name" => "MiMo (Xiaomi)",
|
|
361
455
|
"base_url" => "https://api.xiaomimimo.com/v1",
|
|
@@ -388,36 +482,6 @@ module Clacky
|
|
|
388
482
|
"website_url" => "https://platform.xiaomimimo.com/"
|
|
389
483
|
}.freeze,
|
|
390
484
|
|
|
391
|
-
"glm" => {
|
|
392
|
-
"name" => "GLM (Z.ai / Zhipu)",
|
|
393
|
-
"base_url" => "https://open.bigmodel.cn/api/paas/v4",
|
|
394
|
-
"api" => "openai-completions",
|
|
395
|
-
"default_model" => "glm-5.2",
|
|
396
|
-
"models" => ["glm-5.2", "glm-5.1", "glm-5", "glm-5-turbo", "glm-5v-turbo", "glm-4.7"],
|
|
397
|
-
# Zhipu / Z.ai expose four functionally-equivalent endpoints:
|
|
398
|
-
# two regional sites (mainland open.bigmodel.cn + international api.z.ai)
|
|
399
|
-
# each with a general-billing and a Coding-Plan subpath. They share the
|
|
400
|
-
# same model lineup & identical capability profile, so a single preset
|
|
401
|
-
# with endpoint_variants is the right shape — one source of truth for
|
|
402
|
-
# vision/model_capabilities, four URLs recognised by find_by_base_url.
|
|
403
|
-
# Without this, users pointing at api.z.ai or the /coding/ path fell
|
|
404
|
-
# through to the conservative "assume vision=true" default and got
|
|
405
|
-
# hallucinated image descriptions on text-only GLM models (C-5563).
|
|
406
|
-
"endpoint_variants" => [
|
|
407
|
-
{ "label" => "Mainland · Pay-as-you-go", "label_key" => "settings.models.baseurl.variant.mainland_cn_payg", "base_url" => "https://open.bigmodel.cn/api/paas/v4", "region" => "cn" }.freeze,
|
|
408
|
-
{ "label" => "Mainland · Coding Plan", "label_key" => "settings.models.baseurl.variant.mainland_cn_coding", "base_url" => "https://open.bigmodel.cn/api/coding/paas/v4", "region" => "cn" }.freeze,
|
|
409
|
-
{ "label" => "International · Pay-as-you-go", "label_key" => "settings.models.baseurl.variant.international_payg", "base_url" => "https://api.z.ai/api/paas/v4", "region" => "intl" }.freeze,
|
|
410
|
-
{ "label" => "International · Coding Plan", "label_key" => "settings.models.baseurl.variant.international_coding","base_url" => "https://api.z.ai/api/coding/paas/v4", "region" => "intl" }.freeze
|
|
411
|
-
].freeze,
|
|
412
|
-
# GLM models are text-only except glm-5v-turbo which is vision-capable ("v" = visual).
|
|
413
|
-
"capabilities" => { "vision" => false }.freeze,
|
|
414
|
-
"model_capabilities" => {
|
|
415
|
-
"glm-5v-turbo" => { "vision" => true }.freeze
|
|
416
|
-
}.freeze,
|
|
417
|
-
"default_ocr_model" => "glm-5v-turbo",
|
|
418
|
-
"website_url" => "https://open.bigmodel.cn/usercenter/apikeys"
|
|
419
|
-
}.freeze,
|
|
420
|
-
|
|
421
485
|
# Volcengine Ark (Doubao) — ByteDance's model platform, OpenAI-compatible.
|
|
422
486
|
# Exposes three functionally-equivalent endpoints (Pay-as-you-go / Coding
|
|
423
487
|
# Plan / Agent Plan) that share the same model lineup and capability
|
|
@@ -540,68 +604,57 @@ module Clacky
|
|
|
540
604
|
"website_url" => "https://ollama.com/settings/keys"
|
|
541
605
|
}.freeze,
|
|
542
606
|
|
|
543
|
-
"
|
|
544
|
-
"name" => "
|
|
545
|
-
"base_url" => "https://api.
|
|
607
|
+
"orcarouter" => {
|
|
608
|
+
"name" => "OrcaRouter",
|
|
609
|
+
"base_url" => "https://api.orcarouter.ai/v1",
|
|
546
610
|
"api" => "openai-completions",
|
|
547
|
-
"default_model" => "gpt-5.5",
|
|
611
|
+
"default_model" => "openai/gpt-5.5",
|
|
612
|
+
# Curated default lineup. OrcaRouter exposes 190+ models from OpenAI,
|
|
613
|
+
# Anthropic, Google, DeepSeek, Qwen, MiniMax, Zhipu and others behind a
|
|
614
|
+
# single OpenAI-compatible endpoint. Shipping a small list of the
|
|
615
|
+
# mainstream Claude + GPT entries gives users a working dropdown out
|
|
616
|
+
# of the box; users can still type any other OrcaRouter model id
|
|
617
|
+
# manually (e.g. "orcarouter/auto" for request-level auto-routing).
|
|
548
618
|
"models" => [
|
|
549
|
-
"
|
|
550
|
-
"
|
|
551
|
-
"
|
|
552
|
-
"gpt-5.
|
|
553
|
-
"
|
|
554
|
-
"
|
|
619
|
+
"anthropic/claude-sonnet-5",
|
|
620
|
+
"anthropic/claude-opus-4.8",
|
|
621
|
+
"anthropic/claude-haiku-4.5",
|
|
622
|
+
"openai/gpt-5.5",
|
|
623
|
+
"openai/gpt-5.4",
|
|
624
|
+
"openai/gpt-5.4-mini",
|
|
625
|
+
"google/gemini-3.5-flash",
|
|
626
|
+
"deepseek/deepseek-v4-flash",
|
|
627
|
+
"z-ai/glm-5.2",
|
|
628
|
+
"orcarouter/auto"
|
|
555
629
|
],
|
|
556
|
-
#
|
|
557
|
-
|
|
558
|
-
#
|
|
559
|
-
# o4-mini and o3 are reasoning models without a lite-tier sibling here.
|
|
630
|
+
# Per-primary lite pairing — Claude family pairs with Haiku, GPT
|
|
631
|
+
# family pairs with the mini variant. Mirrors the openrouter preset
|
|
632
|
+
# so subagents on OrcaRouter get a sensible cheap/fast sidekick.
|
|
560
633
|
"lite_models" => {
|
|
561
|
-
"
|
|
562
|
-
"
|
|
634
|
+
"anthropic/claude-sonnet-5" => "anthropic/claude-haiku-4.5",
|
|
635
|
+
"anthropic/claude-opus-4.8" => "anthropic/claude-haiku-4.5",
|
|
636
|
+
"openai/gpt-5.5" => "openai/gpt-5.4-mini",
|
|
637
|
+
"openai/gpt-5.4" => "openai/gpt-5.4-mini"
|
|
563
638
|
},
|
|
564
|
-
#
|
|
565
|
-
#
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
"
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
"name" => "Qwen (Alibaba)",
|
|
576
|
-
"base_url" => "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
577
|
-
"api" => "openai-completions",
|
|
578
|
-
"default_model" => "qwen3.7-max",
|
|
579
|
-
"models" => [
|
|
580
|
-
"qwen3.7-max",
|
|
581
|
-
"qwen3.6-plus",
|
|
582
|
-
"qwen3.6-max",
|
|
583
|
-
"qwen3.6-27b",
|
|
584
|
-
"qwen3.6-flash",
|
|
585
|
-
"qwen-plus-latest",
|
|
586
|
-
],
|
|
587
|
-
"endpoint_variants" => [
|
|
588
|
-
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://dashscope.aliyuncs.com/compatible-mode/v1", "region" => "cn" }.freeze,
|
|
589
|
-
{ "label" => "Singapore", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", "region" => "intl" }.freeze,
|
|
590
|
-
{ "label" => "US (Virginia)", "label_key" => "settings.models.baseurl.variant.us", "base_url" => "https://dashscope-us.aliyuncs.com/compatible-mode/v1", "region" => "us" }.freeze
|
|
591
|
-
].freeze,
|
|
639
|
+
# Per-model API type overrides. OrcaRouter proxies Claude through a
|
|
640
|
+
# native Anthropic /v1/messages endpoint (https://api.orcarouter.ai/v1/messages)
|
|
641
|
+
# in addition to its OpenAI-compatible /chat/completions endpoint.
|
|
642
|
+
# Routing "anthropic/*" via the native endpoint preserves cache_control
|
|
643
|
+
# fidelity, matching what Claude Code CLI does internally (same rationale
|
|
644
|
+
# as the openrouter preset). Non-Claude models keep the OpenAI shim.
|
|
645
|
+
"model_api_overrides" => {
|
|
646
|
+
/\Aanthropic\// => "anthropic-messages"
|
|
647
|
+
}.freeze,
|
|
648
|
+
# Most models on OrcaRouter are vision-capable; DeepSeek / GLM-5.2 and
|
|
649
|
+
# the "orcarouter/auto" router are text-only.
|
|
592
650
|
"capabilities" => { "vision" => true }.freeze,
|
|
593
651
|
"model_capabilities" => {
|
|
594
|
-
"
|
|
652
|
+
"deepseek/deepseek-v4-flash" => { "vision" => false }.freeze,
|
|
653
|
+
"z-ai/glm-5.2" => { "vision" => false }.freeze,
|
|
654
|
+
"orcarouter/auto" => { "vision" => false }.freeze
|
|
595
655
|
}.freeze,
|
|
596
|
-
"default_ocr_model" => "
|
|
597
|
-
"
|
|
598
|
-
"qwen3.7-max" => "qwen3.6-flash",
|
|
599
|
-
"qwen3.6-plus" => "qwen3.6-flash",
|
|
600
|
-
"qwen3.6-max" => "qwen3.6-flash",
|
|
601
|
-
"qwen3.6-27b" => "qwen3.6-flash",
|
|
602
|
-
"qwen-plus-latest" => "qwen3.6-flash"
|
|
603
|
-
},
|
|
604
|
-
"website_url" => "https://bailian.console.aliyun.com/?apiKey=1"
|
|
656
|
+
"default_ocr_model" => "google/gemini-3.5-flash",
|
|
657
|
+
"website_url" => "https://www.orcarouter.ai"
|
|
605
658
|
}.freeze
|
|
606
659
|
|
|
607
660
|
}.freeze
|
|
@@ -71,6 +71,12 @@ module Clacky
|
|
|
71
71
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
72
72
|
ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
|
73
73
|
ssl = OpenSSL::SSL::SSLSocket.new(tcp, ssl_context)
|
|
74
|
+
# Feishu/Lark WebSocket endpoints are served behind CDN edges that
|
|
75
|
+
# require SNI to select the correct certificate/backend. Some
|
|
76
|
+
# Ruby/OpenSSL combinations do not infer the hostname for manually
|
|
77
|
+
# created SSLSocket instances, which can make the peer abort the TLS
|
|
78
|
+
# handshake with `tlsv1 alert internal error`.
|
|
79
|
+
ssl.hostname = uri.host if ssl.respond_to?(:hostname=)
|
|
74
80
|
ssl.sync_close = true
|
|
75
81
|
ssl.connect
|
|
76
82
|
ssl
|