@herouucn/opencode-commandcode 0.1.6 → 0.1.8
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.
- package/LICENSE +1 -1
- package/README.md +57 -139
- package/package.json +1 -1
- package/plugin.ts +5 -7
- package/src/catalog.ts +15 -26
- package/src/costs-models-dev.ts +1 -1
- package/src/manifest.ts +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -5,116 +5,78 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@herouucn/opencode-commandcode)
|
|
6
6
|
[](https://www.npmjs.com/package/@herouucn/opencode-commandcode)
|
|
7
7
|
[](https://bundlephobia.com/package/@herouucn/opencode-commandcode)
|
|
8
|
-
[](https://www.npmjs.com/package/@herouucn/opencode-commandcode)
|
|
9
8
|
[](LICENSE)
|
|
10
9
|
|
|
11
|
-
[Command Code](https://commandcode.ai) API
|
|
10
|
+
[Command Code](https://commandcode.ai)(统一多模型 API)的 [opencode](https://opencode.ai) provider。一个 key 即可在 opencode 中使用 Claude、GPT、Gemini、DeepSeek、Qwen、Kimi、GLM、MiniMax、Step 等 **70+ 模型**。
|
|
11
|
+
|
|
12
|
+
安装即用:插件启动时自动注入 provider 配置、API key 读取与最新模型目录,无需任何手写配置;模型目录随上游发布自动同步,新模型即时可用。
|
|
12
13
|
|
|
13
14
|
## 特性
|
|
14
15
|
|
|
15
16
|
| 特性 | 说明 |
|
|
16
17
|
|---|---|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
## 演示
|
|
24
|
-
|
|
25
|
-
安装插件并查看模型列表:
|
|
26
|
-
|
|
27
|
-
[](https://asciinema.org/a/ePh5yVrpNatwEQKT)
|
|
28
|
-
|
|
29
|
-
安装后运行 opencode,输入 `/models` 即可看到 Command Code 提供的 70+ 模型:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
commandcode/claude-fable-5
|
|
33
|
-
commandcode/claude-fable-5-1
|
|
34
|
-
commandcode/claude-haiku-4-5-20251001
|
|
35
|
-
commandcode/claude-opus-4-7
|
|
36
|
-
commandcode/claude-opus-4-8
|
|
37
|
-
commandcode/claude-opus-5
|
|
38
|
-
commandcode/claude-sonnet-4-6
|
|
39
|
-
commandcode/claude-sonnet-5
|
|
40
|
-
commandcode/deepseek-v4-flash
|
|
41
|
-
commandcode/deepseek-v4-flash-fast
|
|
42
|
-
commandcode/deepseek-v4-pro
|
|
43
|
-
commandcode/gemini-3.5-flash
|
|
44
|
-
commandcode/gemini-3.6-flash
|
|
45
|
-
commandcode/gemini-3.7-flash
|
|
46
|
-
commandcode/gemini-3.8-flash
|
|
47
|
-
commandcode/gpt-5.4
|
|
48
|
-
commandcode/gpt-5.4-mini
|
|
49
|
-
commandcode/gpt-5.5
|
|
50
|
-
commandcode/gpt-5.6-luna
|
|
51
|
-
commandcode/gpt-5.6-sol
|
|
52
|
-
commandcode/gpt-5.6-terra
|
|
53
|
-
commandcode/qwen3.7-max
|
|
54
|
-
commandcode/qwen3.7-plus
|
|
55
|
-
commandcode/qwen3.8-max
|
|
56
|
-
... 共 70+ 模型
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
选择模型后直接对话,无需额外配置。
|
|
18
|
+
| 零配置接入 | 安装后重启 opencode 即可使用,provider 配置与模型列表自动注入 |
|
|
19
|
+
| 单 key 多模型 | 一个 Command Code API key 聚合 70+ 模型(Claude、GPT、Gemini、DeepSeek 等) |
|
|
20
|
+
| 运行时目录同步 | 每次启动拉取最新 `models.json`,上游新模型即时生效 |
|
|
21
|
+
| 离线兜底 | 拉取失败自动回退包内静态目录 → 本地缓存,模型不缺失 |
|
|
22
|
+
| 目录自同步 | CI 每 6 小时检测上游 `command-code` 新版本并直推 `main` |
|
|
23
|
+
| 安全发布 | 基于 GitHub OIDC Trusted Publishing 发布 npm,无需 long-lived token |
|
|
60
24
|
|
|
61
25
|
## 安装
|
|
62
26
|
|
|
63
|
-
|
|
27
|
+
安装插件:
|
|
64
28
|
|
|
65
29
|
```bash
|
|
66
30
|
opencode plugin @herouucn/opencode-commandcode
|
|
67
31
|
```
|
|
68
32
|
|
|
69
|
-
opencode
|
|
70
|
-
1. 安装 npm 包到缓存目录
|
|
71
|
-
2. 更新 `~/.config/opencode/opencode.json`,追加 plugin 和 provider 配置
|
|
72
|
-
|
|
73
|
-
重启 opencode 即可使用。
|
|
74
|
-
|
|
75
|
-
### 方式二:npm 包
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
npm install @herouucn/opencode-commandcode
|
|
79
|
-
# 或
|
|
80
|
-
bun add @herouucn/opencode-commandcode
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
然后在 `opencode.json` 中声明:
|
|
33
|
+
或手动在 `opencode.json` 声明:
|
|
84
34
|
|
|
85
35
|
```jsonc
|
|
86
36
|
// opencode.json
|
|
87
37
|
{
|
|
88
|
-
"
|
|
89
|
-
"plugin": ["@herouucn/opencode-commandcode"],
|
|
90
|
-
"provider": {
|
|
91
|
-
"commandcode": {
|
|
92
|
-
"npm": "@ai-sdk/openai-compatible",
|
|
93
|
-
"name": "commandcode",
|
|
94
|
-
"env": ["COMMANDCODE_API_KEY"],
|
|
95
|
-
"options": {
|
|
96
|
-
"baseURL": "https://api.commandcode.ai/provider/v1/"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
38
|
+
"plugin": ["@herouucn/opencode-commandcode"]
|
|
100
39
|
}
|
|
101
40
|
```
|
|
102
41
|
|
|
103
|
-
|
|
42
|
+
本地开发可直接用路径:`"plugin": ["file:///absolute/path/to/opencode-commandcode"]`。
|
|
43
|
+
|
|
44
|
+
重启 opencode 后在 `/models` 中选择 Command Code 模型即可对话。
|
|
45
|
+
|
|
46
|
+
## 配置
|
|
47
|
+
|
|
48
|
+
API key 任选一种方式提供:
|
|
104
49
|
|
|
105
50
|
```bash
|
|
106
|
-
|
|
51
|
+
export COMMANDCODE_API_KEY="你的 key" # 方式一:环境变量
|
|
107
52
|
```
|
|
108
53
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
"plugin": ["file:///absolute/path/to/opencode-commandcode"]
|
|
113
|
-
}
|
|
54
|
+
```bash
|
|
55
|
+
opencode auth login --provider commandcode # 方式二:交互式(/connect 搜 Command Code)
|
|
114
56
|
```
|
|
115
57
|
|
|
58
|
+
方式三:`~/.commandcode/auth.json`(若已用官方 CLI 登录则自动复用)。
|
|
59
|
+
|
|
60
|
+
> 插件自动注入 provider 配置(`npm`、baseURL、模型列表)且不覆盖已存在的手写配置。手动配置 `provider.commandcode.options.baseURL` 时请保留 `npm: "@ai-sdk/openai-compatible"` 或指定其他兼容 SDK,否则 opencode 无法解析 provider。
|
|
61
|
+
|
|
62
|
+
### 目录源(可选覆盖)
|
|
63
|
+
|
|
64
|
+
默认拉取本仓库 `main` 分支的 `models.json`,一般无需配置。可用环境变量 `COMMANDCODE_CATALOG_URL` 或配置 `catalogUrl` 覆盖:
|
|
65
|
+
|
|
66
|
+
| 取值 | 行为 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| URL | 每次启动拉取该地址(8s 超时),成功后写本地缓存 |
|
|
69
|
+
| `disabled` | 关闭远程拉取,仅用包内静态 `models.json` |
|
|
70
|
+
|
|
116
71
|
## 工作原理
|
|
117
72
|
|
|
73
|
+
插件采用 opencode 的 **config hook**:opencode 每次启动时执行插件导出的 `config` 函数,并向其传入待解析的全局配置。插件在 config hook 中完成两件事:
|
|
74
|
+
|
|
75
|
+
1. **注入 provider 配置**:通过 `??=` 确保 `provider.commandcode` 块存在,并补齐 `npm: "@ai-sdk/openai-compatible"`、name、`COMMANDCODE_API_KEY` env 与默认 `baseURL`,实现安装即用、零手写配置。若用户已显式书写该块,插件不会覆盖已存在字段。
|
|
76
|
+
2. **注入模型目录**:按 `远程 models.json → opt-in 本地包 → 包内静态 → 本地缓存` 顺序加载模型列表,写入 `provider.commandcode.models`。
|
|
77
|
+
|
|
78
|
+
配合 opencode 的 `auth` hook 声明 API Key 认证方式,`/connect` 与 `opencode auth login --provider commandcode` 可直接完成登录。
|
|
79
|
+
|
|
118
80
|
```mermaid
|
|
119
81
|
sequenceDiagram
|
|
120
82
|
autonumber
|
|
@@ -152,66 +114,19 @@ sequenceDiagram
|
|
|
152
114
|
|
|
153
115
|
三处关键设计:
|
|
154
116
|
|
|
155
|
-
1. **目录自更新**:`.github/workflows/catalog-sync.yml` 每 6 小时比对上游 `command-code` npm 版本与 `_version.txt`,有新版本则重新提取模型并**直接推送到 main
|
|
156
|
-
2. **运行时解耦**:插件每次启动 fetch 本仓库 raw `models.json
|
|
117
|
+
1. **目录自更新**:`.github/workflows/catalog-sync.yml` 每 6 小时比对上游 `command-code` npm 版本与 `_version.txt`,有新版本则重新提取模型并**直接推送到 main**。
|
|
118
|
+
2. **运行时解耦**:插件每次启动 fetch 本仓库 raw `models.json`,模型更新不依赖 npm 发版。
|
|
157
119
|
3. **质量护栏**:模型数跌破保护线时触发 `catalog-break` issue 并回滚,坏数据不落库。
|
|
158
120
|
|
|
159
|
-
## 配置
|
|
160
|
-
|
|
161
|
-
### API key(三选一)
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
export COMMANDCODE_API_KEY="你的 key" # 方式一:环境变量
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
opencode auth login --provider commandcode # 方式二:交互式(/connect 搜 Command Code)
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
方式三:`~/.commandcode/auth.json`(若已用官方 CLI 登录则自动复用)。
|
|
172
|
-
|
|
173
|
-
### 目录源(可选覆盖)
|
|
174
|
-
|
|
175
|
-
默认拉取本仓库 `main` 分支的 `models.json`,一般无需配置。需要自定义时可覆盖:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
export COMMANDCODE_CATALOG_URL="https://raw.githubusercontent.com/herouu/opencode-commandcode/main/models.json"
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
或写入 `~/.config/opencode/opencode-commandcode.json`:
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{ "catalogUrl": "https://raw.githubusercontent.com/herouu/opencode-commandcode/main/models.json" }
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
| 取值 | 行为 |
|
|
188
|
-
|---|---|
|
|
189
|
-
| URL | 每次启动拉取该地址(8s 超时),成功后写本地缓存 |
|
|
190
|
-
| `disabled` | 关闭远程拉取,仅用包内静态 `models.json` |
|
|
191
|
-
|
|
192
|
-
### 选择模型
|
|
193
|
-
|
|
194
|
-
opencode 内运行 `/models` 选择(如 `commandcode/deepseek-v4-flash`)。
|
|
195
|
-
|
|
196
121
|
## 发布
|
|
197
122
|
|
|
198
|
-
本仓库使用 **GitHub OIDC Trusted Publishing**,无需 long-lived npm token。
|
|
199
|
-
|
|
200
|
-
**触发方式**:
|
|
123
|
+
本仓库使用 **GitHub OIDC Trusted Publishing**,无需 long-lived npm token。push `v*` tag 触发 `release.yml`:先跑 check(lint + format + typecheck + unit test),通过后 `npm publish --provenance`。
|
|
201
124
|
|
|
202
125
|
```bash
|
|
203
|
-
# 1. 修改版本号
|
|
204
126
|
npm version patch # 或 minor / major
|
|
205
|
-
|
|
206
|
-
# 2. 推送 tag 触发发布
|
|
207
127
|
git push origin main --tags
|
|
208
128
|
```
|
|
209
129
|
|
|
210
|
-
发布流程:
|
|
211
|
-
1. push `v*` tag → 触发 `release.yml`
|
|
212
|
-
2. `check` job:lint + format + typecheck + unit test
|
|
213
|
-
3. `publish` job:`npm publish --provenance`(OIDC 自动认证,生成 provenance 签名)
|
|
214
|
-
|
|
215
130
|
**npm 包**:[@herouucn/opencode-commandcode](https://www.npmjs.com/package/@herouucn/opencode-commandcode)
|
|
216
131
|
|
|
217
132
|
## 开发与维护
|
|
@@ -219,7 +134,6 @@ git push origin main --tags
|
|
|
219
134
|
```bash
|
|
220
135
|
bun install
|
|
221
136
|
bun run check # CI 门槛:oxlint + oxfmt --check + bun test + tsc
|
|
222
|
-
|
|
223
137
|
bun run sync -- --remote # 本地手动刷新 models.json / manifest.json / _version.txt
|
|
224
138
|
```
|
|
225
139
|
|
|
@@ -236,19 +150,23 @@ CI 一览:
|
|
|
236
150
|
**模型列表不更新?**
|
|
237
151
|
先确认能访问 `https://raw.githubusercontent.com/herouu/opencode-commandcode/main/models.json`;再查本机状态 `~/.local/state/opencode/commandcode-provider/startup.json` 里的 `catalogSource` 字段(应为 `remote`)。
|
|
238
152
|
|
|
239
|
-
|
|
240
|
-
|
|
153
|
+
**升级插件后模型列表还是旧的 / 行为异常?**
|
|
154
|
+
opencode 将插件缓存于 `~/.cache/opencode/packages/@herouucn/`。删除该目录后重跑 `opencode models` 强制重拉最新版:
|
|
241
155
|
|
|
242
|
-
|
|
243
|
-
|
|
156
|
+
```bash
|
|
157
|
+
Remove-Item -Recurse -Force "$HOME\.cache\opencode\packages\@herouucn"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**`opencode models` 报 `undefined is not an object (evaluating '$.models')`?**
|
|
161
|
+
确认已升级到 **v0.1.7 及以上**。v0.1.6 及更早版本在全局配置 `provider: {}`(空对象)时,config hook 会跳过 commandcode 注入,导致 opencode 内部崩溃。v0.1.7 起改用 `??=` 确保 commandcode 块始终存在。
|
|
244
162
|
|
|
245
|
-
|
|
246
|
-
|
|
163
|
+
**离线环境能用吗?**
|
|
164
|
+
能。首次成功后模型已写入本地缓存;离线启动时走 `bundled → cache` 回退链,模型不缺失。
|
|
247
165
|
|
|
248
166
|
## 致谢
|
|
249
167
|
|
|
250
|
-
|
|
168
|
+
本项目由 [herouu](https://github.com/herouuu) 独立维护。初始灵感来自 [BrainerVirus/opencode-commandcode](https://github.com/BrainerVirus/opencode-commandcode)([Brent Weatherall](https://github.com/brent-weatherall) 原始实现),现已完全独立开发。
|
|
251
169
|
|
|
252
170
|
## 许可证
|
|
253
171
|
|
|
254
|
-
MIT — 见 [LICENSE](LICENSE)。
|
|
172
|
+
MIT — 见 [LICENSE](LICENSE)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herouucn/opencode-commandcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Inject Command Code API provider into opencode — access Claude, GPT, Gemini, DeepSeek, Qwen, Kimi, GLM, MiniMax & Step via unified Command Code API. / 将 Command Code API provider 注入 opencode —— 通过统一的 Command Code API 使用 Claude、GPT、Gemini、DeepSeek、Qwen、Kimi、GLM、MiniMax 和 Step 模型。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
package/plugin.ts
CHANGED
|
@@ -108,13 +108,11 @@ function readBundledVersion(): string | null {
|
|
|
108
108
|
export default async function commandcodePlugin() {
|
|
109
109
|
return {
|
|
110
110
|
config: async (config: Record<string, unknown>) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)?.commandcode as Record<string, unknown> | undefined;
|
|
117
|
-
if (!cc) return;
|
|
111
|
+
const providers = ((config as Record<string, unknown>).provider ??= {}) as Record<
|
|
112
|
+
string,
|
|
113
|
+
Record<string, unknown>
|
|
114
|
+
>;
|
|
115
|
+
const cc = (providers.commandcode ??= {}) as Record<string, unknown>;
|
|
118
116
|
|
|
119
117
|
const pluginCfg = loadPluginConfig();
|
|
120
118
|
const debug = pluginCfg.debugStartupLogs === true;
|
package/src/catalog.ts
CHANGED
|
@@ -43,20 +43,20 @@ export interface SnEntry {
|
|
|
43
43
|
maxOutputTokens?: number;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
interface ResolvedCommandCodePackage {
|
|
47
47
|
root: string;
|
|
48
48
|
bundlePath: string;
|
|
49
49
|
version: string;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
interface LocalCatalogResult {
|
|
53
53
|
models: ModelEntry[];
|
|
54
54
|
version: string;
|
|
55
55
|
root: string;
|
|
56
56
|
bundleSource: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
const FALLBACK_LIMITS: Record<string, { context: number; output: number }> = {
|
|
60
60
|
"claude-haiku-4-5-20251001": { context: 200000, output: 8192 },
|
|
61
61
|
"claude-opus-4-6": { context: 200000, output: 32000 },
|
|
62
62
|
"claude-opus-4-7": { context: 200000, output: 32000 },
|
|
@@ -81,7 +81,7 @@ export const FALLBACK_LIMITS: Record<string, { context: number; output: number }
|
|
|
81
81
|
"google/gemini-3.1-flash-lite": { context: 1000000, output: 65536 },
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
const HARDCODED_EXTRAS: SnEntry[] = [
|
|
85
85
|
{
|
|
86
86
|
id: "Qwen/Qwen3.7-Max",
|
|
87
87
|
provider: "vercel-ai-gateway",
|
|
@@ -93,7 +93,7 @@ export const HARDCODED_EXTRAS: SnEntry[] = [
|
|
|
93
93
|
},
|
|
94
94
|
];
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
const TIER_MAP: Record<string, "premium" | "open-source"> = {
|
|
97
97
|
anthropic: "premium",
|
|
98
98
|
openai: "premium",
|
|
99
99
|
baseten: "open-source",
|
|
@@ -110,7 +110,7 @@ const COST_ANCHORS = [
|
|
|
110
110
|
] as const;
|
|
111
111
|
|
|
112
112
|
/** Object after `(` (legacy IIFE / enum form): `(Wt={...})` */
|
|
113
|
-
|
|
113
|
+
function findBalancedObject(source: string, anchor: string): string {
|
|
114
114
|
const anchorIdx = source.indexOf(anchor);
|
|
115
115
|
if (anchorIdx < 0) throw new Error(`Anchor not found: ${anchor}`);
|
|
116
116
|
|
|
@@ -135,11 +135,7 @@ export function findBalancedObject(source: string, anchor: string): string {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/** All balanced `{...}` spans that contain the anchor, innermost → outermost. */
|
|
138
|
-
|
|
139
|
-
source: string,
|
|
140
|
-
anchor: string,
|
|
141
|
-
fromIndex = 0,
|
|
142
|
-
): string[] {
|
|
138
|
+
function findEnclosingObjectCandidates(source: string, anchor: string, fromIndex = 0): string[] {
|
|
143
139
|
const anchorIdx = source.indexOf(anchor, fromIndex);
|
|
144
140
|
if (anchorIdx < 0) throw new Error(`Anchor not found: ${anchor}`);
|
|
145
141
|
|
|
@@ -174,21 +170,14 @@ export function findEnclosingObjectCandidates(
|
|
|
174
170
|
return out;
|
|
175
171
|
}
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
export function findEnclosingObject(source: string, anchor: string, fromIndex = 0): string {
|
|
179
|
-
const first = findEnclosingObjectCandidates(source, anchor, fromIndex)[0];
|
|
180
|
-
if (!first) throw new Error(`Could not find enclosing object for anchor: ${anchor}`);
|
|
181
|
-
return first;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function evaluateWithContext(code: string, context: Record<string, unknown>): any {
|
|
173
|
+
function evaluateWithContext(code: string, context: Record<string, unknown>): any {
|
|
185
174
|
const keys = Object.keys(context);
|
|
186
175
|
const values = keys.map((k) => context[k]);
|
|
187
176
|
const fn = Function(...keys, `"use strict"; return (${code})`);
|
|
188
177
|
return fn(...values);
|
|
189
178
|
}
|
|
190
179
|
|
|
191
|
-
|
|
180
|
+
function normalizeForEval(code: string): string {
|
|
192
181
|
return code
|
|
193
182
|
.replace(/!0/g, "true")
|
|
194
183
|
.replace(/!1/g, "false")
|
|
@@ -207,7 +196,7 @@ function getVarNameBefore(source: string, anchorIdx: number): string | null {
|
|
|
207
196
|
}
|
|
208
197
|
|
|
209
198
|
/** Prefer provider enums that include gateway/openai keys over UI-only enums. */
|
|
210
|
-
|
|
199
|
+
function extractBestProviderEnum(
|
|
211
200
|
source: string,
|
|
212
201
|
): { name: string; value: Record<string, string> } | null {
|
|
213
202
|
const anchor = 'ANTHROPIC:"anthropic"';
|
|
@@ -259,7 +248,7 @@ export function extractBestProviderEnum(
|
|
|
259
248
|
}
|
|
260
249
|
|
|
261
250
|
/** Collect nearby `name="value"` bindings and simple aliases (`tI=Qx`). */
|
|
262
|
-
|
|
251
|
+
function extractStringBindings(
|
|
263
252
|
source: string,
|
|
264
253
|
endIdx: number,
|
|
265
254
|
window = 12000,
|
|
@@ -288,7 +277,7 @@ export function extractStringBindings(
|
|
|
288
277
|
return bindings;
|
|
289
278
|
}
|
|
290
279
|
|
|
291
|
-
|
|
280
|
+
function extractSpecConstants(source: string): {
|
|
292
281
|
chatComplete: string;
|
|
293
282
|
responses: string;
|
|
294
283
|
qt: string;
|
|
@@ -353,7 +342,7 @@ function isModelCatalog(value: unknown): value is Record<string, SnEntry> {
|
|
|
353
342
|
return modelish >= 2;
|
|
354
343
|
}
|
|
355
344
|
|
|
356
|
-
|
|
345
|
+
function extractModelCatalog(
|
|
357
346
|
source: string,
|
|
358
347
|
wt?: Record<string, string>,
|
|
359
348
|
wtName?: string,
|
|
@@ -534,7 +523,7 @@ export function disambiguateModelNames(entries: ModelEntry[]): ModelEntry[] {
|
|
|
534
523
|
return entries;
|
|
535
524
|
}
|
|
536
525
|
|
|
537
|
-
|
|
526
|
+
function sortModelEntries(entries: ModelEntry[]): ModelEntry[] {
|
|
538
527
|
disambiguateModelNames(entries);
|
|
539
528
|
return entries.sort((a, b) => {
|
|
540
529
|
if (a.tier !== b.tier) return a.tier === "premium" ? -1 : 1;
|
|
@@ -729,7 +718,7 @@ export function loadCatalogFromLocalCommandCode(options?: {
|
|
|
729
718
|
}
|
|
730
719
|
}
|
|
731
720
|
|
|
732
|
-
|
|
721
|
+
function toConfigKey(id: string): string {
|
|
733
722
|
const slashIdx = id.indexOf("/");
|
|
734
723
|
const short = slashIdx >= 0 ? id.slice(slashIdx + 1) : id;
|
|
735
724
|
return short.toLowerCase();
|
package/src/costs-models-dev.ts
CHANGED
|
@@ -164,7 +164,7 @@ export async function fetchModelsDevJson(): Promise<string> {
|
|
|
164
164
|
headers: {
|
|
165
165
|
// ponytail: models.dev returns 403 without a browser-like UA; upgrade if they add a real API token
|
|
166
166
|
"User-Agent":
|
|
167
|
-
"Mozilla/5.0 (compatible; opencode-commandcode/0.
|
|
167
|
+
"Mozilla/5.0 (compatible; opencode-commandcode/0.7; +https://github.com/herouu/opencode-commandcode)",
|
|
168
168
|
Accept: "application/json",
|
|
169
169
|
},
|
|
170
170
|
});
|
package/src/manifest.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function meetsModelCountFloor(modelCount: number, lastSuccessful: number
|
|
|
55
55
|
return modelCount >= (lastSuccessful === null ? 20 : floor);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
function bestCostCatalog(sources: CostSources): CostCatalogBest {
|
|
59
59
|
if (sources.cli > 0) return "cli";
|
|
60
60
|
if (sources.officialDocs > 0) return "docs";
|
|
61
61
|
if (sources.thirdParty > 0) return "thirdParty";
|
|
@@ -64,7 +64,7 @@ export function bestCostCatalog(sources: CostSources): CostCatalogBest {
|
|
|
64
64
|
return "missing";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
function catalogStatus(modelCatalogOk: boolean, sources: CostSources): CatalogStatus {
|
|
68
68
|
if (!modelCatalogOk) return "broken";
|
|
69
69
|
if (sources.unmatched > 0) return "degraded";
|
|
70
70
|
return "healthy";
|