trinity-config 1.1.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -48
- package/dist/index.js +403 -70
- package/dist/merge-openclaw-config-BNKTK35F.js +128 -0
- package/dist/merge-opencode-config-24P7MV7L.js +109 -0
- package/dist/merge-zcode-config-U4V7XCYT.js +114 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,58 +1,21 @@
|
|
|
1
1
|
# trinity-config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一键接入 Trinity:Claude Code / Codex / OpenCode / OpenClaw / ZCode。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
### 交互式
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx -y trinity-config
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
按提示选择 **工具类型**、粘贴 Key、确认 **model_code**。
|
|
14
|
-
|
|
15
|
-
### 一行命令(推荐)
|
|
5
|
+
协议按 `GET /v1/models/{code}` 的 `capabilities.text.interfaces` **自动择优**(Anthropic → Responses → Completions),再与工具能力求交。
|
|
16
6
|
|
|
17
7
|
```bash
|
|
18
|
-
|
|
19
|
-
npx -y trinity-config --type claude -k xh-你的key
|
|
20
|
-
|
|
21
|
-
# Codex CLI
|
|
22
|
-
npx -y trinity-config --type codex -k xh-你的key --model gpt-5.4
|
|
8
|
+
npx -y trinity-config --type zcode -k xh-你的key --model gpt-5.4
|
|
23
9
|
```
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## 命令行参数
|
|
28
|
-
|
|
29
|
-
| 参数 | 简写 | 说明 |
|
|
30
|
-
| --- | --- | --- |
|
|
31
|
-
| `--type` | `-t` | **必填(非交互)**:`claude` \| `codex` |
|
|
32
|
-
| `--api-key` | `-k` | Trinity API Key |
|
|
33
|
-
| `--model` | `-M` | 默认 model_code(省略则按 type 内置推荐) |
|
|
34
|
-
| `--base-url` | | 默认 `https://api.trinitydesk.ai` |
|
|
35
|
-
| `--skip-verify` | | 跳过连通性验证 |
|
|
36
|
-
| `--verbose` | `-v` | 显示 zcf 详细输出 |
|
|
37
|
-
|
|
38
|
-
### 按 type 的默认模型
|
|
39
|
-
|
|
40
|
-
| `--type` | 默认 `--model` |
|
|
11
|
+
| type | 配置文件 |
|
|
41
12
|
| --- | --- |
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
后续增加 Cursor、OpenCode 等时,只需新增 `type` 枚举与对应写入逻辑,**不再**为每个工具增加 `--xxx-model` 参数。
|
|
48
|
-
|
|
49
|
-
## 注意
|
|
50
|
-
|
|
51
|
-
- 配置后**所有模型调用走 Trinity 计费**。
|
|
52
|
-
- 非交互模式必须同时提供 `--type` 与 `-k`。
|
|
53
|
-
- **Claude Code**:经 zcf 写入 `~/.claude/settings.json`
|
|
54
|
-
- **Codex CLI**:**直接** merge `~/.codex/config.toml` + `auth.json`(不调用 zcf;zcf cx 会重置为 ChatGPT 官方登录)
|
|
13
|
+
| claude | `~/.claude/settings.json` |
|
|
14
|
+
| codex | `~/.codex/config.toml` |
|
|
15
|
+
| opencode | `~/.config/opencode/opencode.json` |
|
|
16
|
+
| openclaw | `~/.openclaw/openclaw.json` |
|
|
17
|
+
| zcode | `~/.zcode/v2/config.json` |
|
|
55
18
|
|
|
56
|
-
|
|
19
|
+
Anthropic Base **不带** `/v1`;Responses / Completions **带** `/v1`。
|
|
57
20
|
|
|
58
|
-
MIT
|
|
21
|
+
License: MIT
|
package/dist/index.js
CHANGED
|
@@ -5,53 +5,91 @@ import pc from "picocolors";
|
|
|
5
5
|
|
|
6
6
|
// src/constants.ts
|
|
7
7
|
var PACKAGE_NAME = "trinity-config";
|
|
8
|
-
var PACKAGE_VERSION = "1.
|
|
8
|
+
var PACKAGE_VERSION = "1.2.2";
|
|
9
9
|
var TRINITY_BASE_URL = "https://api.trinitydesk.ai";
|
|
10
10
|
var DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
11
11
|
var DEFAULT_CLAUDE_FAST_MODEL = "claude-sonnet-4-6";
|
|
12
12
|
var DEFAULT_CODEX_MODEL = "gpt-5.4";
|
|
13
|
+
var DEFAULT_OPENCODE_MODEL = "gpt-5.4";
|
|
14
|
+
var DEFAULT_OPENCLAW_MODEL = "gpt-5.4";
|
|
15
|
+
var DEFAULT_ZCODE_MODEL = "gpt-5.4";
|
|
13
16
|
var VERIFY_TIMEOUT_MS = 15e3;
|
|
14
17
|
|
|
15
18
|
// src/model.ts
|
|
16
19
|
function defaultModelForType(type) {
|
|
17
|
-
|
|
20
|
+
switch (type) {
|
|
21
|
+
case "claude":
|
|
22
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
23
|
+
case "codex":
|
|
24
|
+
return DEFAULT_CODEX_MODEL;
|
|
25
|
+
case "opencode":
|
|
26
|
+
return DEFAULT_OPENCODE_MODEL;
|
|
27
|
+
case "openclaw":
|
|
28
|
+
return DEFAULT_OPENCLAW_MODEL;
|
|
29
|
+
case "zcode":
|
|
30
|
+
return DEFAULT_ZCODE_MODEL;
|
|
31
|
+
}
|
|
18
32
|
}
|
|
19
33
|
function resolveModel(type, model) {
|
|
20
34
|
return model?.trim() || defaultModelForType(type);
|
|
21
35
|
}
|
|
22
36
|
function parseToolType(raw) {
|
|
23
37
|
const value = raw.trim().toLowerCase();
|
|
24
|
-
if (value === "claude" || value === "codex") {
|
|
38
|
+
if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw" || value === "zcode") {
|
|
25
39
|
return value;
|
|
26
40
|
}
|
|
27
|
-
throw new Error("--type \u987B\u4E3A claude \u6216
|
|
41
|
+
throw new Error("--type \u987B\u4E3A claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
|
|
28
42
|
}
|
|
29
43
|
function toolLabel(type) {
|
|
30
|
-
|
|
44
|
+
switch (type) {
|
|
45
|
+
case "claude":
|
|
46
|
+
return "Claude Code";
|
|
47
|
+
case "codex":
|
|
48
|
+
return "Codex CLI";
|
|
49
|
+
case "opencode":
|
|
50
|
+
return "OpenCode";
|
|
51
|
+
case "openclaw":
|
|
52
|
+
return "OpenClaw";
|
|
53
|
+
case "zcode":
|
|
54
|
+
return "ZCode";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function launchCommand(type) {
|
|
58
|
+
switch (type) {
|
|
59
|
+
case "claude":
|
|
60
|
+
return "claude";
|
|
61
|
+
case "codex":
|
|
62
|
+
return "codex";
|
|
63
|
+
case "opencode":
|
|
64
|
+
return "opencode";
|
|
65
|
+
case "openclaw":
|
|
66
|
+
return "openclaw dashboard";
|
|
67
|
+
case "zcode":
|
|
68
|
+
return "ZCode \u684C\u9762\u5E94\u7528";
|
|
69
|
+
}
|
|
31
70
|
}
|
|
32
71
|
|
|
33
72
|
// src/args.ts
|
|
34
73
|
function printHelp() {
|
|
35
74
|
console.log(`
|
|
36
|
-
trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF\u7F16\u7A0B\u52A9\u624B
|
|
75
|
+
trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF / Agent / \u684C\u9762\u7F16\u7A0B\u52A9\u624B
|
|
37
76
|
|
|
38
77
|
\u7528\u6CD5:
|
|
39
|
-
npx -y trinity-config --type <claude|codex> [\u9009\u9879]
|
|
78
|
+
npx -y trinity-config --type <claude|codex|opencode|openclaw|zcode> [\u9009\u9879]
|
|
40
79
|
|
|
41
80
|
\u9009\u9879:
|
|
42
|
-
-t, --type
|
|
43
|
-
-k, --api-key <key>
|
|
44
|
-
-M, --model <
|
|
45
|
-
--base-url <url>
|
|
46
|
-
--skip-verify
|
|
47
|
-
-v, --verbose
|
|
48
|
-
-h, --help
|
|
49
|
-
-V, --version
|
|
81
|
+
-t, --type <\u2026> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
|
|
82
|
+
-k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
|
|
83
|
+
-M, --model <code> \u9ED8\u8BA4\u6A21\u578B\uFF1B\u534F\u8BAE\u6309\u6A21\u578B\u8BE6\u60C5\u81EA\u52A8\u62E9\u4F18\uFF08\u975E completions \u4F18\u5148\uFF09
|
|
84
|
+
--base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
|
|
85
|
+
--skip-verify \u8DF3\u8FC7\u8FDE\u901A\u6027 / \u8BE6\u60C5\u62C9\u53D6
|
|
86
|
+
-v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA\uFF08\u4EC5 Claude\uFF09
|
|
87
|
+
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
88
|
+
-V, --version \u663E\u793A\u7248\u672C
|
|
50
89
|
|
|
51
90
|
\u793A\u4F8B:
|
|
52
|
-
npx -y trinity-config --type
|
|
53
|
-
npx -y trinity-config --type
|
|
54
|
-
npx -y trinity-config
|
|
91
|
+
npx -y trinity-config --type zcode -k xh-your-key --model gpt-5.4
|
|
92
|
+
npx -y trinity-config --type openclaw -k xh-your-key
|
|
55
93
|
`);
|
|
56
94
|
}
|
|
57
95
|
function parseArgs(argv) {
|
|
@@ -98,7 +136,7 @@ function parseArgs(argv) {
|
|
|
98
136
|
break;
|
|
99
137
|
case "--only": {
|
|
100
138
|
const legacy = args.shift();
|
|
101
|
-
console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type
|
|
139
|
+
console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type");
|
|
102
140
|
opts.type = parseToolType(String(legacy ?? ""));
|
|
103
141
|
break;
|
|
104
142
|
}
|
|
@@ -129,7 +167,7 @@ function normalizeApiKey(raw) {
|
|
|
129
167
|
function requireType(type, hasApiKeyFlag) {
|
|
130
168
|
if (type) return type;
|
|
131
169
|
if (!hasApiKeyFlag) return void 0;
|
|
132
|
-
throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude \u6216
|
|
170
|
+
throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
|
|
133
171
|
}
|
|
134
172
|
|
|
135
173
|
// src/merge-claude-env.ts
|
|
@@ -162,6 +200,167 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
|
162
200
|
`, "utf8");
|
|
163
201
|
}
|
|
164
202
|
|
|
203
|
+
// src/protocol.ts
|
|
204
|
+
import https from "https";
|
|
205
|
+
var PROTOCOL_PRIORITY = [
|
|
206
|
+
"anthropic_messages",
|
|
207
|
+
"responses",
|
|
208
|
+
"chat_completions"
|
|
209
|
+
];
|
|
210
|
+
var TOOL_SUPPORTED_PROTOCOLS = {
|
|
211
|
+
claude: ["anthropic_messages"],
|
|
212
|
+
codex: ["responses"],
|
|
213
|
+
opencode: ["anthropic_messages", "responses", "chat_completions"],
|
|
214
|
+
openclaw: ["anthropic_messages", "responses", "chat_completions"],
|
|
215
|
+
zcode: ["anthropic_messages", "responses", "chat_completions"]
|
|
216
|
+
};
|
|
217
|
+
var INTERFACE_ALIASES = {
|
|
218
|
+
"text.anthropic_messages": "anthropic_messages",
|
|
219
|
+
anthropic_messages: "anthropic_messages",
|
|
220
|
+
"text.responses": "responses",
|
|
221
|
+
responses: "responses",
|
|
222
|
+
"text.chat_completions": "chat_completions",
|
|
223
|
+
chat_completions: "chat_completions",
|
|
224
|
+
chat: "chat_completions"
|
|
225
|
+
};
|
|
226
|
+
function getJson(url, apiKey) {
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
const target = new URL(url);
|
|
229
|
+
const req = https.request(
|
|
230
|
+
{
|
|
231
|
+
protocol: target.protocol,
|
|
232
|
+
hostname: target.hostname,
|
|
233
|
+
port: target.port || 443,
|
|
234
|
+
path: `${target.pathname}${target.search}`,
|
|
235
|
+
method: "GET",
|
|
236
|
+
headers: {
|
|
237
|
+
Accept: "application/json",
|
|
238
|
+
Authorization: `Bearer ${apiKey}`
|
|
239
|
+
},
|
|
240
|
+
timeout: VERIFY_TIMEOUT_MS
|
|
241
|
+
},
|
|
242
|
+
(res) => {
|
|
243
|
+
const chunks = [];
|
|
244
|
+
res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
245
|
+
res.on("end", () => {
|
|
246
|
+
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
247
|
+
try {
|
|
248
|
+
resolve({
|
|
249
|
+
statusCode: res.statusCode ?? 0,
|
|
250
|
+
body: JSON.parse(text2)
|
|
251
|
+
});
|
|
252
|
+
} catch {
|
|
253
|
+
reject(new Error(`\u54CD\u5E94\u4E0D\u662F JSON\uFF08HTTP ${res.statusCode}\uFF09`));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
req.on("timeout", () => {
|
|
259
|
+
req.destroy();
|
|
260
|
+
reject(new Error(`\u8FDE\u63A5\u8D85\u65F6\uFF08>${VERIFY_TIMEOUT_MS}ms\uFF09`));
|
|
261
|
+
});
|
|
262
|
+
req.on("error", reject);
|
|
263
|
+
req.end();
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function normalizeInterface(raw) {
|
|
267
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
268
|
+
return INTERFACE_ALIASES[raw.trim().toLowerCase()] ?? null;
|
|
269
|
+
}
|
|
270
|
+
function parseInterfacesFromDetail(body) {
|
|
271
|
+
if (!body || typeof body !== "object") return [];
|
|
272
|
+
const root = body;
|
|
273
|
+
const caps = root.capabilities;
|
|
274
|
+
if (!caps || typeof caps !== "object") return [];
|
|
275
|
+
const text2 = caps.text;
|
|
276
|
+
if (!text2 || typeof text2 !== "object") return [];
|
|
277
|
+
const textObj = text2;
|
|
278
|
+
const rawList = textObj.interfaces ?? textObj.protocols;
|
|
279
|
+
if (!Array.isArray(rawList)) return [];
|
|
280
|
+
const out = [];
|
|
281
|
+
const seen = /* @__PURE__ */ new Set();
|
|
282
|
+
for (const item of rawList) {
|
|
283
|
+
const proto = normalizeInterface(item);
|
|
284
|
+
if (proto && !seen.has(proto)) {
|
|
285
|
+
seen.add(proto);
|
|
286
|
+
out.push(proto);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
function rootBaseUrl(baseUrl) {
|
|
292
|
+
const root = (baseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
|
|
293
|
+
return root.endsWith("/v1") ? root.slice(0, -3) : root;
|
|
294
|
+
}
|
|
295
|
+
function baseUrlForProtocol(protocol, root) {
|
|
296
|
+
const cleaned = rootBaseUrl(root);
|
|
297
|
+
return protocol === "anthropic_messages" ? cleaned : `${cleaned}/v1`;
|
|
298
|
+
}
|
|
299
|
+
function defaultProtocolForTool(tool) {
|
|
300
|
+
return TOOL_SUPPORTED_PROTOCOLS[tool][0];
|
|
301
|
+
}
|
|
302
|
+
function pickProtocol(tool, modelProtocols) {
|
|
303
|
+
const supported = TOOL_SUPPORTED_PROTOCOLS[tool];
|
|
304
|
+
const candidates = PROTOCOL_PRIORITY.filter(
|
|
305
|
+
(p2) => modelProtocols.includes(p2) && supported.includes(p2)
|
|
306
|
+
);
|
|
307
|
+
if (candidates.length > 0) {
|
|
308
|
+
return { protocol: candidates[0] };
|
|
309
|
+
}
|
|
310
|
+
if (modelProtocols.length === 0) {
|
|
311
|
+
return {
|
|
312
|
+
protocol: defaultProtocolForTool(tool),
|
|
313
|
+
note: "\u6A21\u578B\u8BE6\u60C5\u672A\u8FD4\u56DE interfaces\uFF0C\u5DF2\u6309\u5DE5\u5177\u9ED8\u8BA4\u534F\u8BAE\u964D\u7EA7"
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
throw new Error(
|
|
317
|
+
`\u6A21\u578B\u4E0D\u652F\u6301 ${tool} \u53EF\u7528\u534F\u8BAE\uFF08\u6A21\u578B: ${modelProtocols.join(", ") || "\u65E0"}\uFF1B\u5DE5\u5177: ${supported.join(", ")}\uFF09`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
async function fetchModelProtocols(apiKey, modelCode, root) {
|
|
321
|
+
const base = rootBaseUrl(root);
|
|
322
|
+
const url = `${base}/v1/models/${encodeURIComponent(modelCode)}`;
|
|
323
|
+
const { statusCode, body } = await getJson(url, apiKey);
|
|
324
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
return parseInterfacesFromDetail(body);
|
|
328
|
+
}
|
|
329
|
+
async function resolveProtocol(options) {
|
|
330
|
+
let modelProtocols = [];
|
|
331
|
+
let fromApi = false;
|
|
332
|
+
if (!options.skipFetch) {
|
|
333
|
+
try {
|
|
334
|
+
modelProtocols = await fetchModelProtocols(
|
|
335
|
+
options.apiKey,
|
|
336
|
+
options.model,
|
|
337
|
+
options.rootBaseUrl
|
|
338
|
+
);
|
|
339
|
+
fromApi = modelProtocols.length > 0;
|
|
340
|
+
} catch {
|
|
341
|
+
modelProtocols = [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const picked = pickProtocol(options.tool, modelProtocols);
|
|
345
|
+
return {
|
|
346
|
+
protocol: picked.protocol,
|
|
347
|
+
baseUrl: baseUrlForProtocol(picked.protocol, options.rootBaseUrl),
|
|
348
|
+
modelProtocols,
|
|
349
|
+
fromApi,
|
|
350
|
+
note: picked.note
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function protocolLabel(protocol) {
|
|
354
|
+
switch (protocol) {
|
|
355
|
+
case "anthropic_messages":
|
|
356
|
+
return "Anthropic Messages\uFF08/v1/messages\uFF09";
|
|
357
|
+
case "responses":
|
|
358
|
+
return "OpenAI Responses\uFF08/v1/responses\uFF09";
|
|
359
|
+
case "chat_completions":
|
|
360
|
+
return "Chat Completions\uFF08/v1/chat/completions\uFF09";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
165
364
|
// src/zcf.ts
|
|
166
365
|
import { execa } from "execa";
|
|
167
366
|
function sharedZcfArgs(verbose) {
|
|
@@ -222,23 +421,43 @@ async function configureCodex(apiKey, codexBaseUrl, codexModel, _verbose) {
|
|
|
222
421
|
const { configureCodexDirect } = await import("./merge-codex-config-GC7ZDP7R.js");
|
|
223
422
|
return configureCodexDirect(apiKey, codexBaseUrl, codexModel);
|
|
224
423
|
}
|
|
225
|
-
function
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
424
|
+
async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds, protocol) {
|
|
425
|
+
const { configureOpenCodeDirect } = await import("./merge-opencode-config-24P7MV7L.js");
|
|
426
|
+
return configureOpenCodeDirect({
|
|
427
|
+
apiKey,
|
|
428
|
+
baseUrl: openCodeBaseUrl,
|
|
429
|
+
model,
|
|
430
|
+
modelIds,
|
|
431
|
+
protocol
|
|
432
|
+
});
|
|
230
433
|
}
|
|
231
|
-
function
|
|
232
|
-
const {
|
|
233
|
-
return
|
|
434
|
+
async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds, protocol) {
|
|
435
|
+
const { configureOpenClawDirect } = await import("./merge-openclaw-config-BNKTK35F.js");
|
|
436
|
+
return configureOpenClawDirect({
|
|
437
|
+
apiKey,
|
|
438
|
+
baseUrl: openClawBaseUrl,
|
|
439
|
+
model,
|
|
440
|
+
modelIds,
|
|
441
|
+
protocol
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function configureZCode(apiKey, zcodeBaseUrl, model, modelIds, protocol) {
|
|
445
|
+
const { configureZCodeDirect } = await import("./merge-zcode-config-U4V7XCYT.js");
|
|
446
|
+
return configureZCodeDirect({
|
|
447
|
+
apiKey,
|
|
448
|
+
baseUrl: zcodeBaseUrl,
|
|
449
|
+
model,
|
|
450
|
+
modelIds,
|
|
451
|
+
protocol
|
|
452
|
+
});
|
|
234
453
|
}
|
|
235
454
|
|
|
236
455
|
// src/verify.ts
|
|
237
|
-
import
|
|
238
|
-
function
|
|
456
|
+
import https2 from "https";
|
|
457
|
+
function getJson2(url, apiKey) {
|
|
239
458
|
return new Promise((resolve, reject) => {
|
|
240
459
|
const target = new URL(url);
|
|
241
|
-
const req =
|
|
460
|
+
const req = https2.request(
|
|
242
461
|
{
|
|
243
462
|
protocol: target.protocol,
|
|
244
463
|
hostname: target.hostname,
|
|
@@ -279,7 +498,7 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
|
|
|
279
498
|
const root = baseUrl.replace(/\/+$/, "");
|
|
280
499
|
const modelsUrl = `${root}/v1/models`;
|
|
281
500
|
try {
|
|
282
|
-
const { statusCode, body } = await
|
|
501
|
+
const { statusCode, body } = await getJson2(modelsUrl, apiKey);
|
|
283
502
|
if (statusCode === 401 || statusCode === 403) {
|
|
284
503
|
return {
|
|
285
504
|
ok: false,
|
|
@@ -327,7 +546,10 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
|
|
|
327
546
|
function pickRecommendedModels(modelIds) {
|
|
328
547
|
const claude = modelIds.find((id) => id === DEFAULT_CLAUDE_MODEL) ?? modelIds.find((id) => id.includes("claude-sonnet")) ?? modelIds.find((id) => id.toLowerCase().includes("claude"));
|
|
329
548
|
const codex = modelIds.find((id) => id === DEFAULT_CODEX_MODEL) ?? modelIds.find((id) => id === "gpt-5.4") ?? modelIds.find((id) => id.startsWith("gpt-5")) ?? modelIds.find((id) => id.toLowerCase().includes("gpt"));
|
|
330
|
-
|
|
549
|
+
const opencode = modelIds.find((id) => id === DEFAULT_OPENCODE_MODEL) ?? codex ?? modelIds.find((id) => id.toLowerCase().includes("qwen"));
|
|
550
|
+
const openclaw = modelIds.find((id) => id === DEFAULT_OPENCLAW_MODEL) ?? opencode ?? codex;
|
|
551
|
+
const zcode = modelIds.find((id) => id === DEFAULT_ZCODE_MODEL) ?? opencode ?? codex;
|
|
552
|
+
return { claude, codex, opencode, openclaw, zcode };
|
|
331
553
|
}
|
|
332
554
|
|
|
333
555
|
// src/index.ts
|
|
@@ -355,8 +577,11 @@ async function promptToolType(initial) {
|
|
|
355
577
|
const value = await p.select({
|
|
356
578
|
message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
|
|
357
579
|
options: [
|
|
358
|
-
{ value: "claude", label: "Claude Code
|
|
359
|
-
{ value: "codex", label: "Codex CLI
|
|
580
|
+
{ value: "claude", label: "Claude Code" },
|
|
581
|
+
{ value: "codex", label: "Codex CLI" },
|
|
582
|
+
{ value: "opencode", label: "OpenCode" },
|
|
583
|
+
{ value: "openclaw", label: "OpenClaw" },
|
|
584
|
+
{ value: "zcode", label: "ZCode" }
|
|
360
585
|
]
|
|
361
586
|
});
|
|
362
587
|
if (p.isCancel(value)) {
|
|
@@ -384,6 +609,20 @@ async function promptModel(type, initial) {
|
|
|
384
609
|
}
|
|
385
610
|
return String(value).trim();
|
|
386
611
|
}
|
|
612
|
+
function recommendedHint(toolType, recommended) {
|
|
613
|
+
switch (toolType) {
|
|
614
|
+
case "claude":
|
|
615
|
+
return recommended.claude;
|
|
616
|
+
case "codex":
|
|
617
|
+
return recommended.codex;
|
|
618
|
+
case "opencode":
|
|
619
|
+
return recommended.opencode;
|
|
620
|
+
case "openclaw":
|
|
621
|
+
return recommended.openclaw;
|
|
622
|
+
case "zcode":
|
|
623
|
+
return recommended.zcode;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
387
626
|
async function run() {
|
|
388
627
|
let opts;
|
|
389
628
|
try {
|
|
@@ -404,67 +643,161 @@ async function run() {
|
|
|
404
643
|
const toolType = await promptToolType(opts.type);
|
|
405
644
|
const apiKey = await promptApiKey(opts.apiKey);
|
|
406
645
|
const model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType) : await promptModel(toolType);
|
|
407
|
-
const
|
|
408
|
-
const { claudeBaseUrl } = resolveBaseUrls(opts.baseUrl);
|
|
646
|
+
const apiRoot = rootBaseUrl(opts.baseUrl);
|
|
409
647
|
p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
|
|
410
648
|
const spinner2 = p.spinner();
|
|
649
|
+
let verifiedModelIds;
|
|
650
|
+
let verifyFailed = false;
|
|
651
|
+
if (!opts.skipVerify) {
|
|
652
|
+
spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
|
|
653
|
+
const result = await verifyTrinityApi(apiKey, apiRoot);
|
|
654
|
+
if (result.ok) {
|
|
655
|
+
spinner2.stop(pc.green(result.message));
|
|
656
|
+
verifiedModelIds = result.modelIds;
|
|
657
|
+
const recommended = pickRecommendedModels(result.modelIds ?? []);
|
|
658
|
+
const hint = recommendedHint(toolType, recommended);
|
|
659
|
+
if (hint && hint !== model) {
|
|
660
|
+
p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
|
|
661
|
+
}
|
|
662
|
+
} else {
|
|
663
|
+
verifyFailed = true;
|
|
664
|
+
spinner2.stop(pc.yellow(result.message));
|
|
665
|
+
p.note(
|
|
666
|
+
"\u8FDE\u901A\u6027\u68C0\u67E5\u672A\u901A\u8FC7\uFF1B\u4ECD\u5C06\u5C1D\u8BD5\u5199\u5165\u672C\u5730\u914D\u7F6E\u3002\u8BF7\u68C0\u67E5 API Key\u3001\u7F51\u7EDC\uFF0C\u6216\u5728\u63A7\u5236\u53F0\u786E\u8BA4 Key \u6709\u6548\u3002",
|
|
667
|
+
"\u63D0\u793A"
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
spinner2.start("\u89E3\u6790\u6A21\u578B\u534F\u8BAE\u2026");
|
|
672
|
+
let protocolRes;
|
|
673
|
+
try {
|
|
674
|
+
protocolRes = await resolveProtocol({
|
|
675
|
+
tool: toolType,
|
|
676
|
+
apiKey,
|
|
677
|
+
model,
|
|
678
|
+
rootBaseUrl: apiRoot,
|
|
679
|
+
skipFetch: opts.skipVerify
|
|
680
|
+
});
|
|
681
|
+
spinner2.stop(
|
|
682
|
+
`\u534F\u8BAE: ${protocolLabel(protocolRes.protocol)}` + (protocolRes.fromApi ? "" : "\uFF08\u964D\u7EA7\uFF09")
|
|
683
|
+
);
|
|
684
|
+
if (protocolRes.note) {
|
|
685
|
+
p.note(protocolRes.note, "\u534F\u8BAE");
|
|
686
|
+
}
|
|
687
|
+
} catch (error) {
|
|
688
|
+
spinner2.stop(pc.red("\u534F\u8BAE\u89E3\u6790\u5931\u8D25"));
|
|
689
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
690
|
+
return 1;
|
|
691
|
+
}
|
|
692
|
+
const writeBase = protocolRes.baseUrl;
|
|
693
|
+
const protocol = protocolRes.protocol;
|
|
411
694
|
if (toolType === "claude") {
|
|
412
695
|
spinner2.start("\u914D\u7F6E Claude Code\u2026");
|
|
413
696
|
try {
|
|
414
|
-
await configureClaude(apiKey,
|
|
415
|
-
mergeClaudeEnv(model, model,
|
|
697
|
+
await configureClaude(apiKey, writeBase, model, opts.verbose);
|
|
698
|
+
mergeClaudeEnv(model, model, writeBase);
|
|
416
699
|
spinner2.stop("Claude Code \u914D\u7F6E\u5DF2\u5199\u5165 ~/.claude/settings.json");
|
|
417
700
|
} catch (error) {
|
|
418
701
|
spinner2.stop(pc.red("Claude Code \u914D\u7F6E\u5931\u8D25"));
|
|
419
|
-
console.error(
|
|
702
|
+
console.error(formatConfigError(error));
|
|
420
703
|
return 1;
|
|
421
704
|
}
|
|
422
|
-
} else {
|
|
705
|
+
} else if (toolType === "codex") {
|
|
423
706
|
spinner2.start("\u914D\u7F6E Codex CLI\u2026");
|
|
424
707
|
try {
|
|
425
|
-
const backupDir = await configureCodex(apiKey,
|
|
708
|
+
const backupDir = await configureCodex(apiKey, writeBase, model, opts.verbose);
|
|
426
709
|
const backupHint = backupDir ? `
|
|
427
710
|
\u5907\u4EFD: ${backupDir}` : "";
|
|
428
711
|
spinner2.stop(`Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/config.toml \u4E0E auth.json${backupHint}`);
|
|
429
712
|
} catch (error) {
|
|
430
713
|
spinner2.stop(pc.red("Codex \u914D\u7F6E\u5931\u8D25"));
|
|
431
|
-
console.error(
|
|
714
|
+
console.error(formatConfigError(error));
|
|
432
715
|
return 1;
|
|
433
716
|
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
spinner2.stop(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
717
|
+
} else if (toolType === "opencode") {
|
|
718
|
+
spinner2.start("\u914D\u7F6E OpenCode\u2026");
|
|
719
|
+
try {
|
|
720
|
+
const backupDir = await configureOpenCode(
|
|
721
|
+
apiKey,
|
|
722
|
+
writeBase,
|
|
723
|
+
model,
|
|
724
|
+
verifiedModelIds,
|
|
725
|
+
protocol
|
|
726
|
+
);
|
|
727
|
+
const backupHint = backupDir ? `
|
|
728
|
+
\u5907\u4EFD: ${backupDir}` : "";
|
|
729
|
+
spinner2.stop(
|
|
730
|
+
`OpenCode \u914D\u7F6E\u5DF2\u5199\u5165 ~/.config/opencode/opencode.json${backupHint}`
|
|
731
|
+
);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
spinner2.stop(pc.red("OpenCode \u914D\u7F6E\u5931\u8D25"));
|
|
734
|
+
console.error(formatConfigError(error));
|
|
735
|
+
return 1;
|
|
736
|
+
}
|
|
737
|
+
} else if (toolType === "openclaw") {
|
|
738
|
+
spinner2.start("\u914D\u7F6E OpenClaw\u2026");
|
|
739
|
+
try {
|
|
740
|
+
const backupDir = await configureOpenClaw(
|
|
741
|
+
apiKey,
|
|
742
|
+
writeBase,
|
|
743
|
+
model,
|
|
744
|
+
verifiedModelIds,
|
|
745
|
+
protocol
|
|
746
|
+
);
|
|
747
|
+
const backupHint = backupDir ? `
|
|
748
|
+
\u5907\u4EFD: ${backupDir}` : "";
|
|
749
|
+
spinner2.stop(
|
|
750
|
+
`OpenClaw \u914D\u7F6E\u5DF2\u5199\u5165 ~/.openclaw/openclaw.json${backupHint}`
|
|
751
|
+
);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
spinner2.stop(pc.red("OpenClaw \u914D\u7F6E\u5931\u8D25"));
|
|
754
|
+
console.error(formatConfigError(error));
|
|
755
|
+
return 1;
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
spinner2.start("\u914D\u7F6E ZCode\u2026");
|
|
759
|
+
try {
|
|
760
|
+
const backupDir = await configureZCode(
|
|
761
|
+
apiKey,
|
|
762
|
+
writeBase,
|
|
763
|
+
model,
|
|
764
|
+
verifiedModelIds,
|
|
765
|
+
protocol
|
|
766
|
+
);
|
|
767
|
+
const backupHint = backupDir ? `
|
|
768
|
+
\u5907\u4EFD: ${backupDir}` : "";
|
|
769
|
+
spinner2.stop(
|
|
770
|
+
`ZCode \u914D\u7F6E\u5DF2\u5199\u5165 ~/.zcode/v2/config.json${backupHint}`
|
|
450
771
|
);
|
|
451
|
-
|
|
772
|
+
} catch (error) {
|
|
773
|
+
spinner2.stop(pc.red("ZCode \u914D\u7F6E\u5931\u8D25"));
|
|
774
|
+
console.error(formatConfigError(error));
|
|
452
775
|
return 1;
|
|
453
776
|
}
|
|
454
777
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
)
|
|
778
|
+
if (verifyFailed) {
|
|
779
|
+
p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
|
|
780
|
+
return 1;
|
|
781
|
+
}
|
|
782
|
+
const nextSteps = [
|
|
783
|
+
`\u8FD0\u884C ${launchCommand(toolType)} \u542F\u52A8 ${toolLabel(toolType)}`,
|
|
784
|
+
`\u5DF2\u6309\u534F\u8BAE\u63A5\u5165: ${protocolLabel(protocol)}`
|
|
785
|
+
];
|
|
786
|
+
if (toolType === "opencode") {
|
|
787
|
+
nextSteps.push("\u5728 OpenCode \u5185\u7528 /models \u9009\u62E9 trinity/<model_code>");
|
|
788
|
+
} else if (toolType === "openclaw") {
|
|
789
|
+
nextSteps.push("\u6539\u5B8C\u914D\u7F6E\u540E\u8BF7\u91CD\u542F Gateway");
|
|
790
|
+
} else if (toolType === "zcode") {
|
|
791
|
+
nextSteps.push("\u91CD\u542F ZCode \u540E\uFF0C\u5728\u6A21\u578B\u8BBE\u7F6E\u4E2D\u542F\u7528 Trinity \u4F9B\u5E94\u5546");
|
|
792
|
+
} else {
|
|
793
|
+
nextSteps.push("\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code");
|
|
794
|
+
}
|
|
795
|
+
nextSteps.push("\u914D\u7F6E\u540E\u8BF7\u6C42\u8D70 Trinity \u8BA1\u8D39\uFF0C\u4E0D\u518D\u4F7F\u7528\u5B98\u65B9\u989D\u5EA6");
|
|
796
|
+
p.note(nextSteps.join("\n"), "\u4E0B\u4E00\u6B65");
|
|
464
797
|
p.outro(pc.green("\u5B8C\u6210"));
|
|
465
798
|
return 0;
|
|
466
799
|
}
|
|
467
|
-
function
|
|
800
|
+
function formatConfigError(error) {
|
|
468
801
|
if (error instanceof Error && "timed out" in error) {
|
|
469
802
|
return "zcf \u6267\u884C\u8D85\u65F6\uFF08>3 \u5206\u949F\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
470
803
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// src/merge-openclaw-config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var TRINITY_OPENCLAW_PROVIDER = "trinity";
|
|
6
|
+
function openclawHome() {
|
|
7
|
+
if (process.env.OPENCLAW_HOME?.trim()) {
|
|
8
|
+
return process.env.OPENCLAW_HOME.trim();
|
|
9
|
+
}
|
|
10
|
+
return path.join(os.homedir(), ".openclaw");
|
|
11
|
+
}
|
|
12
|
+
function openclawConfigPath() {
|
|
13
|
+
if (process.env.OPENCLAW_CONFIG?.trim()) {
|
|
14
|
+
return process.env.OPENCLAW_CONFIG.trim();
|
|
15
|
+
}
|
|
16
|
+
return path.join(openclawHome(), "openclaw.json");
|
|
17
|
+
}
|
|
18
|
+
function timestampLabel() {
|
|
19
|
+
const now = /* @__PURE__ */ new Date();
|
|
20
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
21
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
|
|
22
|
+
}
|
|
23
|
+
function backupOpenClawConfig() {
|
|
24
|
+
const configPath = openclawConfigPath();
|
|
25
|
+
if (!fs.existsSync(configPath)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const backupDir = path.join(openclawHome(), "backup", `trinity-config-${timestampLabel()}`);
|
|
29
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
30
|
+
fs.copyFileSync(configPath, path.join(backupDir, path.basename(configPath)));
|
|
31
|
+
return backupDir;
|
|
32
|
+
}
|
|
33
|
+
function readOpenClawConfig() {
|
|
34
|
+
const configPath = openclawConfigPath();
|
|
35
|
+
if (!fs.existsSync(configPath)) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
39
|
+
if (!raw.trim()) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`\u65E0\u6CD5\u89E3\u6790 ${configPath}\u3002\u82E5\u6587\u4EF6\u4E3A JSON5\uFF0C\u8BF7\u5148\u6539\u4E3A\u6807\u51C6 JSON\uFF0C\u6216\u5907\u4EFD\u540E\u5220\u9664\u518D\u91CD\u8DD1\u3002`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isLikelyNonTextModel(id) {
|
|
51
|
+
const lower = id.toLowerCase();
|
|
52
|
+
return lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling");
|
|
53
|
+
}
|
|
54
|
+
function buildModelEntries(primaryModel, modelIds) {
|
|
55
|
+
const ids = [primaryModel];
|
|
56
|
+
const seen = new Set(ids);
|
|
57
|
+
for (const id of modelIds ?? []) {
|
|
58
|
+
const trimmed = id?.trim();
|
|
59
|
+
if (!trimmed || seen.has(trimmed) || isLikelyNonTextModel(trimmed)) continue;
|
|
60
|
+
seen.add(trimmed);
|
|
61
|
+
ids.push(trimmed);
|
|
62
|
+
if (ids.length >= 80) break;
|
|
63
|
+
}
|
|
64
|
+
return ids.map((id) => ({
|
|
65
|
+
id,
|
|
66
|
+
name: id,
|
|
67
|
+
reasoning: false,
|
|
68
|
+
input: ["text"],
|
|
69
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
70
|
+
contextWindow: 128e3,
|
|
71
|
+
maxTokens: 8192
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
function mergeOpenClawConfig(options) {
|
|
75
|
+
const configPath = openclawConfigPath();
|
|
76
|
+
const doc = readOpenClawConfig();
|
|
77
|
+
const protocol = options.protocol ?? "chat_completions";
|
|
78
|
+
const api = protocol === "anthropic_messages" ? "anthropic-messages" : protocol === "responses" ? "openai-responses" : "openai-completions";
|
|
79
|
+
const modelsBlock = doc.models && typeof doc.models === "object" ? { ...doc.models } : {};
|
|
80
|
+
modelsBlock.mode = modelsBlock.mode ?? "merge";
|
|
81
|
+
const providers = modelsBlock.providers && typeof modelsBlock.providers === "object" ? { ...modelsBlock.providers } : {};
|
|
82
|
+
const existing = providers[TRINITY_OPENCLAW_PROVIDER] ?? {};
|
|
83
|
+
const existingModels = Array.isArray(existing.models) ? existing.models : [];
|
|
84
|
+
const byId = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const m of existingModels) {
|
|
86
|
+
if (m && typeof m === "object" && typeof m.id === "string") {
|
|
87
|
+
byId.set(m.id, m);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const m of buildModelEntries(options.model, options.modelIds)) {
|
|
91
|
+
byId.set(m.id, { ...byId.get(m.id), ...m });
|
|
92
|
+
}
|
|
93
|
+
providers[TRINITY_OPENCLAW_PROVIDER] = {
|
|
94
|
+
...existing,
|
|
95
|
+
baseUrl: options.baseUrl.replace(/\/+$/, ""),
|
|
96
|
+
apiKey: options.apiKey,
|
|
97
|
+
api,
|
|
98
|
+
models: [...byId.values()]
|
|
99
|
+
};
|
|
100
|
+
modelsBlock.providers = providers;
|
|
101
|
+
doc.models = modelsBlock;
|
|
102
|
+
const agents = doc.agents && typeof doc.agents === "object" ? { ...doc.agents } : {};
|
|
103
|
+
const defaults = agents.defaults && typeof agents.defaults === "object" ? { ...agents.defaults } : {};
|
|
104
|
+
const modelCfg = defaults.model && typeof defaults.model === "object" ? { ...defaults.model } : {};
|
|
105
|
+
modelCfg.primary = `${TRINITY_OPENCLAW_PROVIDER}/${options.model}`;
|
|
106
|
+
defaults.model = modelCfg;
|
|
107
|
+
const allow = defaults.models && typeof defaults.models === "object" ? { ...defaults.models } : {};
|
|
108
|
+
allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] = allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] ?? {};
|
|
109
|
+
defaults.models = allow;
|
|
110
|
+
agents.defaults = defaults;
|
|
111
|
+
doc.agents = agents;
|
|
112
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
113
|
+
fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
|
|
114
|
+
`, "utf8");
|
|
115
|
+
}
|
|
116
|
+
function configureOpenClawDirect(options) {
|
|
117
|
+
const backupDir = backupOpenClawConfig();
|
|
118
|
+
mergeOpenClawConfig(options);
|
|
119
|
+
return backupDir;
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
TRINITY_OPENCLAW_PROVIDER,
|
|
123
|
+
backupOpenClawConfig,
|
|
124
|
+
configureOpenClawDirect,
|
|
125
|
+
mergeOpenClawConfig,
|
|
126
|
+
openclawConfigPath,
|
|
127
|
+
openclawHome
|
|
128
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/merge-opencode-config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var TRINITY_OPENCODE_PROVIDER = "trinity";
|
|
6
|
+
function opencodeConfigDir() {
|
|
7
|
+
if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
|
|
8
|
+
return process.env.OPENCODE_CONFIG_DIR.trim();
|
|
9
|
+
}
|
|
10
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
11
|
+
}
|
|
12
|
+
function opencodeConfigPath() {
|
|
13
|
+
if (process.env.OPENCODE_CONFIG?.trim()) {
|
|
14
|
+
return process.env.OPENCODE_CONFIG.trim();
|
|
15
|
+
}
|
|
16
|
+
return path.join(opencodeConfigDir(), "opencode.json");
|
|
17
|
+
}
|
|
18
|
+
function timestampLabel() {
|
|
19
|
+
const now = /* @__PURE__ */ new Date();
|
|
20
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
21
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
|
|
22
|
+
}
|
|
23
|
+
function backupOpenCodeConfig() {
|
|
24
|
+
const configPath = opencodeConfigPath();
|
|
25
|
+
if (!fs.existsSync(configPath)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const backupDir = path.join(opencodeConfigDir(), "backup", `trinity-config-${timestampLabel()}`);
|
|
29
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
30
|
+
fs.copyFileSync(configPath, path.join(backupDir, path.basename(configPath)));
|
|
31
|
+
return backupDir;
|
|
32
|
+
}
|
|
33
|
+
function readOpenCodeConfig() {
|
|
34
|
+
const configPath = opencodeConfigPath();
|
|
35
|
+
if (!fs.existsSync(configPath)) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
39
|
+
if (!raw.trim()) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(`\u65E0\u6CD5\u89E3\u6790 ${configPath}\uFF0C\u8BF7\u68C0\u67E5 JSON \u8BED\u6CD5\u540E\u91CD\u8BD5\u3002`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function buildModelMap(primaryModel, modelIds) {
|
|
49
|
+
const ids = [primaryModel];
|
|
50
|
+
const seen = new Set(ids);
|
|
51
|
+
for (const id of modelIds ?? []) {
|
|
52
|
+
const trimmed = id?.trim();
|
|
53
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
54
|
+
const lower = trimmed.toLowerCase();
|
|
55
|
+
if (lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling")) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
seen.add(trimmed);
|
|
59
|
+
ids.push(trimmed);
|
|
60
|
+
if (ids.length >= 80) break;
|
|
61
|
+
}
|
|
62
|
+
const models = {};
|
|
63
|
+
for (const id of ids) {
|
|
64
|
+
models[id] = { name: id };
|
|
65
|
+
}
|
|
66
|
+
return models;
|
|
67
|
+
}
|
|
68
|
+
function mergeOpenCodeConfig(options) {
|
|
69
|
+
const configPath = opencodeConfigPath();
|
|
70
|
+
const doc = readOpenCodeConfig();
|
|
71
|
+
const protocol = options.protocol ?? "chat_completions";
|
|
72
|
+
const npm = protocol === "anthropic_messages" ? "@ai-sdk/anthropic" : protocol === "responses" ? "@ai-sdk/openai" : "@ai-sdk/openai-compatible";
|
|
73
|
+
doc.$schema = doc.$schema ?? "https://opencode.ai/config.json";
|
|
74
|
+
doc.model = `${TRINITY_OPENCODE_PROVIDER}/${options.model}`;
|
|
75
|
+
const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
|
|
76
|
+
const existing = providers[TRINITY_OPENCODE_PROVIDER] ?? {};
|
|
77
|
+
const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
|
|
78
|
+
providers[TRINITY_OPENCODE_PROVIDER] = {
|
|
79
|
+
...existing,
|
|
80
|
+
npm,
|
|
81
|
+
name: "Trinity",
|
|
82
|
+
options: {
|
|
83
|
+
...existing.options ?? {},
|
|
84
|
+
baseURL: options.baseUrl.replace(/\/+$/, ""),
|
|
85
|
+
apiKey: options.apiKey
|
|
86
|
+
},
|
|
87
|
+
models: {
|
|
88
|
+
...existingModels,
|
|
89
|
+
...buildModelMap(options.model, options.modelIds)
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
doc.provider = providers;
|
|
93
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
94
|
+
fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
|
|
95
|
+
`, "utf8");
|
|
96
|
+
}
|
|
97
|
+
function configureOpenCodeDirect(options) {
|
|
98
|
+
const backupDir = backupOpenCodeConfig();
|
|
99
|
+
mergeOpenCodeConfig(options);
|
|
100
|
+
return backupDir;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
TRINITY_OPENCODE_PROVIDER,
|
|
104
|
+
backupOpenCodeConfig,
|
|
105
|
+
configureOpenCodeDirect,
|
|
106
|
+
mergeOpenCodeConfig,
|
|
107
|
+
opencodeConfigDir,
|
|
108
|
+
opencodeConfigPath
|
|
109
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/merge-zcode-config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var TRINITY_ZCODE_PROVIDER = "trinity";
|
|
6
|
+
function zcodeHome() {
|
|
7
|
+
if (process.env.ZCODE_HOME?.trim()) {
|
|
8
|
+
return process.env.ZCODE_HOME.trim();
|
|
9
|
+
}
|
|
10
|
+
return path.join(os.homedir(), ".zcode");
|
|
11
|
+
}
|
|
12
|
+
function zcodeConfigPath() {
|
|
13
|
+
if (process.env.ZCODE_CONFIG?.trim()) {
|
|
14
|
+
return process.env.ZCODE_CONFIG.trim();
|
|
15
|
+
}
|
|
16
|
+
return path.join(zcodeHome(), "v2", "config.json");
|
|
17
|
+
}
|
|
18
|
+
function timestampLabel() {
|
|
19
|
+
const now = /* @__PURE__ */ new Date();
|
|
20
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
21
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
|
|
22
|
+
}
|
|
23
|
+
function backupZCodeConfig() {
|
|
24
|
+
const configPath = zcodeConfigPath();
|
|
25
|
+
if (!fs.existsSync(configPath)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const backupDir = path.join(zcodeHome(), "backup", `trinity-config-${timestampLabel()}`);
|
|
29
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
30
|
+
fs.copyFileSync(configPath, path.join(backupDir, "config.json"));
|
|
31
|
+
return backupDir;
|
|
32
|
+
}
|
|
33
|
+
function readZCodeConfig() {
|
|
34
|
+
const configPath = zcodeConfigPath();
|
|
35
|
+
if (!fs.existsSync(configPath)) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
39
|
+
if (!raw.trim()) return {};
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error(`\u65E0\u6CD5\u89E3\u6790 ${configPath}\uFF0C\u8BF7\u68C0\u67E5 JSON \u8BED\u6CD5\u540E\u91CD\u8BD5\u3002`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function buildModels(primary, modelIds) {
|
|
47
|
+
const ids = /* @__PURE__ */ new Set([primary]);
|
|
48
|
+
for (const id of modelIds ?? []) {
|
|
49
|
+
const t = id?.trim();
|
|
50
|
+
if (!t) continue;
|
|
51
|
+
const lower = t.toLowerCase();
|
|
52
|
+
if (lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling")) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
ids.add(t);
|
|
56
|
+
if (ids.size >= 80) break;
|
|
57
|
+
}
|
|
58
|
+
const models = {};
|
|
59
|
+
for (const id of ids) {
|
|
60
|
+
models[id] = { name: id };
|
|
61
|
+
}
|
|
62
|
+
return models;
|
|
63
|
+
}
|
|
64
|
+
function mergeZCodeConfig(options) {
|
|
65
|
+
const configPath = zcodeConfigPath();
|
|
66
|
+
const doc = readZCodeConfig();
|
|
67
|
+
const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
|
|
68
|
+
for (const [id, provider] of Object.entries(providers)) {
|
|
69
|
+
if (id === TRINITY_ZCODE_PROVIDER) continue;
|
|
70
|
+
if (provider && typeof provider === "object") {
|
|
71
|
+
providers[id] = { ...provider, enabled: false };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const existing = providers[TRINITY_ZCODE_PROVIDER] ?? {};
|
|
75
|
+
const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
|
|
76
|
+
const root = options.baseUrl.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
|
|
77
|
+
const withV1 = `${root}/v1`;
|
|
78
|
+
const withoutV1 = root;
|
|
79
|
+
const primaryBase = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
80
|
+
providers[TRINITY_ZCODE_PROVIDER] = {
|
|
81
|
+
...existing,
|
|
82
|
+
enabled: true,
|
|
83
|
+
name: "Trinity",
|
|
84
|
+
options: {
|
|
85
|
+
...existing.options ?? {},
|
|
86
|
+
baseURL: primaryBase,
|
|
87
|
+
apiKey: options.apiKey,
|
|
88
|
+
openaiBaseURL: withV1,
|
|
89
|
+
anthropicBaseURL: withoutV1
|
|
90
|
+
},
|
|
91
|
+
models: {
|
|
92
|
+
...existingModels,
|
|
93
|
+
...buildModels(options.model, options.modelIds)
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
doc.provider = providers;
|
|
97
|
+
doc.model = `${TRINITY_ZCODE_PROVIDER}/${options.model}`;
|
|
98
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
99
|
+
fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
|
|
100
|
+
`, "utf8");
|
|
101
|
+
}
|
|
102
|
+
function configureZCodeDirect(options) {
|
|
103
|
+
const backupDir = backupZCodeConfig();
|
|
104
|
+
mergeZCodeConfig(options);
|
|
105
|
+
return backupDir;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
TRINITY_ZCODE_PROVIDER,
|
|
109
|
+
backupZCodeConfig,
|
|
110
|
+
configureZCodeDirect,
|
|
111
|
+
mergeZCodeConfig,
|
|
112
|
+
zcodeConfigPath,
|
|
113
|
+
zcodeHome
|
|
114
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trinity-config",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "一键把 Trinity API 接入 Claude Code
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "一键把 Trinity API 接入 Claude Code、Codex、OpenCode、OpenClaw",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"trinitydesk",
|
|
30
30
|
"claude-code",
|
|
31
31
|
"codex",
|
|
32
|
+
"opencode",
|
|
33
|
+
"openclaw",
|
|
32
34
|
"cli",
|
|
33
35
|
"ai-gateway",
|
|
34
36
|
"zcf"
|