trinity-config 1.2.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 +12 -16
- package/dist/index.js +278 -58
- package/dist/{merge-openclaw-config-XV25MSSC.js → merge-openclaw-config-BNKTK35F.js} +3 -1
- package/dist/{merge-opencode-config-P4QJ63DF.js → merge-opencode-config-24P7MV7L.js} +3 -1
- package/dist/merge-zcode-config-U4V7XCYT.js +114 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
# trinity-config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一键接入 Trinity:Claude Code / Codex / OpenCode / OpenClaw / ZCode。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
协议按 `GET /v1/models/{code}` 的 `capabilities.text.interfaces` **自动择优**(Anthropic → Responses → Completions),再与工具能力求交。
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx -y trinity-config --type
|
|
9
|
-
npx -y trinity-config --type codex -k xh-你的key --model gpt-5.4
|
|
10
|
-
npx -y trinity-config --type opencode -k xh-你的key --model gpt-5.4
|
|
11
|
-
npx -y trinity-config --type openclaw -k xh-你的key --model gpt-5.4
|
|
8
|
+
npx -y trinity-config --type zcode -k xh-你的key --model gpt-5.4
|
|
12
9
|
```
|
|
13
10
|
|
|
14
|
-
|
|
|
15
|
-
| --- | --- |
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
11
|
+
| type | 配置文件 |
|
|
12
|
+
| --- | --- |
|
|
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` |
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
Anthropic Base **不带** `/v1`;Responses / Completions **带** `/v1`。
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
MIT
|
|
21
|
+
License: MIT
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,14 @@ import pc from "picocolors";
|
|
|
5
5
|
|
|
6
6
|
// src/constants.ts
|
|
7
7
|
var PACKAGE_NAME = "trinity-config";
|
|
8
|
-
var PACKAGE_VERSION = "1.2.
|
|
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
13
|
var DEFAULT_OPENCODE_MODEL = "gpt-5.4";
|
|
14
14
|
var DEFAULT_OPENCLAW_MODEL = "gpt-5.4";
|
|
15
|
+
var DEFAULT_ZCODE_MODEL = "gpt-5.4";
|
|
15
16
|
var VERIFY_TIMEOUT_MS = 15e3;
|
|
16
17
|
|
|
17
18
|
// src/model.ts
|
|
@@ -25,6 +26,8 @@ function defaultModelForType(type) {
|
|
|
25
26
|
return DEFAULT_OPENCODE_MODEL;
|
|
26
27
|
case "openclaw":
|
|
27
28
|
return DEFAULT_OPENCLAW_MODEL;
|
|
29
|
+
case "zcode":
|
|
30
|
+
return DEFAULT_ZCODE_MODEL;
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
function resolveModel(type, model) {
|
|
@@ -32,10 +35,10 @@ function resolveModel(type, model) {
|
|
|
32
35
|
}
|
|
33
36
|
function parseToolType(raw) {
|
|
34
37
|
const value = raw.trim().toLowerCase();
|
|
35
|
-
if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw") {
|
|
38
|
+
if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw" || value === "zcode") {
|
|
36
39
|
return value;
|
|
37
40
|
}
|
|
38
|
-
throw new Error("--type \u987B\u4E3A claude\u3001codex\u3001opencode \u6216
|
|
41
|
+
throw new Error("--type \u987B\u4E3A claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
|
|
39
42
|
}
|
|
40
43
|
function toolLabel(type) {
|
|
41
44
|
switch (type) {
|
|
@@ -47,6 +50,8 @@ function toolLabel(type) {
|
|
|
47
50
|
return "OpenCode";
|
|
48
51
|
case "openclaw":
|
|
49
52
|
return "OpenClaw";
|
|
53
|
+
case "zcode":
|
|
54
|
+
return "ZCode";
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
function launchCommand(type) {
|
|
@@ -59,33 +64,32 @@ function launchCommand(type) {
|
|
|
59
64
|
return "opencode";
|
|
60
65
|
case "openclaw":
|
|
61
66
|
return "openclaw dashboard";
|
|
67
|
+
case "zcode":
|
|
68
|
+
return "ZCode \u684C\u9762\u5E94\u7528";
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
// src/args.ts
|
|
66
73
|
function printHelp() {
|
|
67
74
|
console.log(`
|
|
68
|
-
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
|
|
69
76
|
|
|
70
77
|
\u7528\u6CD5:
|
|
71
|
-
npx -y trinity-config --type <claude|codex|opencode|openclaw> [\u9009\u9879]
|
|
78
|
+
npx -y trinity-config --type <claude|codex|opencode|openclaw|zcode> [\u9009\u9879]
|
|
72
79
|
|
|
73
80
|
\u9009\u9879:
|
|
74
|
-
-t, --type
|
|
75
|
-
-k, --api-key <key>
|
|
76
|
-
-M, --model <
|
|
77
|
-
--base-url <url>
|
|
78
|
-
--skip-verify
|
|
79
|
-
-v, --verbose
|
|
80
|
-
-h, --help
|
|
81
|
-
-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
|
|
82
89
|
|
|
83
90
|
\u793A\u4F8B:
|
|
84
|
-
npx -y trinity-config --type
|
|
85
|
-
npx -y trinity-config --type
|
|
86
|
-
npx -y trinity-config --type opencode -k xh-your-key --model gpt-5.4
|
|
87
|
-
npx -y trinity-config --type openclaw -k xh-your-key --model gpt-5.4
|
|
88
|
-
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
|
|
89
93
|
`);
|
|
90
94
|
}
|
|
91
95
|
function parseArgs(argv) {
|
|
@@ -163,7 +167,7 @@ function normalizeApiKey(raw) {
|
|
|
163
167
|
function requireType(type, hasApiKeyFlag) {
|
|
164
168
|
if (type) return type;
|
|
165
169
|
if (!hasApiKeyFlag) return void 0;
|
|
166
|
-
throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode \u6216
|
|
170
|
+
throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
// src/merge-claude-env.ts
|
|
@@ -196,6 +200,167 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
|
196
200
|
`, "utf8");
|
|
197
201
|
}
|
|
198
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
|
+
|
|
199
364
|
// src/zcf.ts
|
|
200
365
|
import { execa } from "execa";
|
|
201
366
|
function sharedZcfArgs(verbose) {
|
|
@@ -256,41 +421,43 @@ async function configureCodex(apiKey, codexBaseUrl, codexModel, _verbose) {
|
|
|
256
421
|
const { configureCodexDirect } = await import("./merge-codex-config-GC7ZDP7R.js");
|
|
257
422
|
return configureCodexDirect(apiKey, codexBaseUrl, codexModel);
|
|
258
423
|
}
|
|
259
|
-
function
|
|
260
|
-
const
|
|
261
|
-
const codexBaseUrl = root.endsWith("/v1") ? root : `${root}/v1`;
|
|
262
|
-
const claudeBaseUrl = root.endsWith("/v1") ? root.slice(0, -3) : root;
|
|
263
|
-
return { claudeBaseUrl, codexBaseUrl };
|
|
264
|
-
}
|
|
265
|
-
function baseUrlForType(type, baseUrl) {
|
|
266
|
-
const { claudeBaseUrl, codexBaseUrl } = resolveBaseUrls(baseUrl);
|
|
267
|
-
return type === "claude" ? claudeBaseUrl : codexBaseUrl;
|
|
268
|
-
}
|
|
269
|
-
async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds) {
|
|
270
|
-
const { configureOpenCodeDirect } = await import("./merge-opencode-config-P4QJ63DF.js");
|
|
424
|
+
async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds, protocol) {
|
|
425
|
+
const { configureOpenCodeDirect } = await import("./merge-opencode-config-24P7MV7L.js");
|
|
271
426
|
return configureOpenCodeDirect({
|
|
272
427
|
apiKey,
|
|
273
428
|
baseUrl: openCodeBaseUrl,
|
|
274
429
|
model,
|
|
275
|
-
modelIds
|
|
430
|
+
modelIds,
|
|
431
|
+
protocol
|
|
276
432
|
});
|
|
277
433
|
}
|
|
278
|
-
async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds) {
|
|
279
|
-
const { configureOpenClawDirect } = await import("./merge-openclaw-config-
|
|
434
|
+
async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds, protocol) {
|
|
435
|
+
const { configureOpenClawDirect } = await import("./merge-openclaw-config-BNKTK35F.js");
|
|
280
436
|
return configureOpenClawDirect({
|
|
281
437
|
apiKey,
|
|
282
438
|
baseUrl: openClawBaseUrl,
|
|
283
439
|
model,
|
|
284
|
-
modelIds
|
|
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
|
|
285
452
|
});
|
|
286
453
|
}
|
|
287
454
|
|
|
288
455
|
// src/verify.ts
|
|
289
|
-
import
|
|
290
|
-
function
|
|
456
|
+
import https2 from "https";
|
|
457
|
+
function getJson2(url, apiKey) {
|
|
291
458
|
return new Promise((resolve, reject) => {
|
|
292
459
|
const target = new URL(url);
|
|
293
|
-
const req =
|
|
460
|
+
const req = https2.request(
|
|
294
461
|
{
|
|
295
462
|
protocol: target.protocol,
|
|
296
463
|
hostname: target.hostname,
|
|
@@ -331,7 +498,7 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
|
|
|
331
498
|
const root = baseUrl.replace(/\/+$/, "");
|
|
332
499
|
const modelsUrl = `${root}/v1/models`;
|
|
333
500
|
try {
|
|
334
|
-
const { statusCode, body } = await
|
|
501
|
+
const { statusCode, body } = await getJson2(modelsUrl, apiKey);
|
|
335
502
|
if (statusCode === 401 || statusCode === 403) {
|
|
336
503
|
return {
|
|
337
504
|
ok: false,
|
|
@@ -381,7 +548,8 @@ function pickRecommendedModels(modelIds) {
|
|
|
381
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"));
|
|
382
549
|
const opencode = modelIds.find((id) => id === DEFAULT_OPENCODE_MODEL) ?? codex ?? modelIds.find((id) => id.toLowerCase().includes("qwen"));
|
|
383
550
|
const openclaw = modelIds.find((id) => id === DEFAULT_OPENCLAW_MODEL) ?? opencode ?? codex;
|
|
384
|
-
|
|
551
|
+
const zcode = modelIds.find((id) => id === DEFAULT_ZCODE_MODEL) ?? opencode ?? codex;
|
|
552
|
+
return { claude, codex, opencode, openclaw, zcode };
|
|
385
553
|
}
|
|
386
554
|
|
|
387
555
|
// src/index.ts
|
|
@@ -409,10 +577,11 @@ async function promptToolType(initial) {
|
|
|
409
577
|
const value = await p.select({
|
|
410
578
|
message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
|
|
411
579
|
options: [
|
|
412
|
-
{ value: "claude", label: "Claude Code
|
|
413
|
-
{ value: "codex", label: "Codex CLI
|
|
414
|
-
{ value: "opencode", label: "OpenCode
|
|
415
|
-
{ value: "openclaw", label: "OpenClaw
|
|
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" }
|
|
416
585
|
]
|
|
417
586
|
});
|
|
418
587
|
if (p.isCancel(value)) {
|
|
@@ -450,6 +619,8 @@ function recommendedHint(toolType, recommended) {
|
|
|
450
619
|
return recommended.opencode;
|
|
451
620
|
case "openclaw":
|
|
452
621
|
return recommended.openclaw;
|
|
622
|
+
case "zcode":
|
|
623
|
+
return recommended.zcode;
|
|
453
624
|
}
|
|
454
625
|
}
|
|
455
626
|
async function run() {
|
|
@@ -472,14 +643,14 @@ async function run() {
|
|
|
472
643
|
const toolType = await promptToolType(opts.type);
|
|
473
644
|
const apiKey = await promptApiKey(opts.apiKey);
|
|
474
645
|
const model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType) : await promptModel(toolType);
|
|
475
|
-
const
|
|
476
|
-
const { claudeBaseUrl } = resolveBaseUrls(opts.baseUrl);
|
|
646
|
+
const apiRoot = rootBaseUrl(opts.baseUrl);
|
|
477
647
|
p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
|
|
478
648
|
const spinner2 = p.spinner();
|
|
479
649
|
let verifiedModelIds;
|
|
650
|
+
let verifyFailed = false;
|
|
480
651
|
if (!opts.skipVerify) {
|
|
481
652
|
spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
|
|
482
|
-
const result = await verifyTrinityApi(apiKey,
|
|
653
|
+
const result = await verifyTrinityApi(apiKey, apiRoot);
|
|
483
654
|
if (result.ok) {
|
|
484
655
|
spinner2.stop(pc.green(result.message));
|
|
485
656
|
verifiedModelIds = result.modelIds;
|
|
@@ -489,6 +660,7 @@ async function run() {
|
|
|
489
660
|
p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
|
|
490
661
|
}
|
|
491
662
|
} else {
|
|
663
|
+
verifyFailed = true;
|
|
492
664
|
spinner2.stop(pc.yellow(result.message));
|
|
493
665
|
p.note(
|
|
494
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",
|
|
@@ -496,11 +668,34 @@ async function run() {
|
|
|
496
668
|
);
|
|
497
669
|
}
|
|
498
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;
|
|
499
694
|
if (toolType === "claude") {
|
|
500
695
|
spinner2.start("\u914D\u7F6E Claude Code\u2026");
|
|
501
696
|
try {
|
|
502
|
-
await configureClaude(apiKey,
|
|
503
|
-
mergeClaudeEnv(model, model,
|
|
697
|
+
await configureClaude(apiKey, writeBase, model, opts.verbose);
|
|
698
|
+
mergeClaudeEnv(model, model, writeBase);
|
|
504
699
|
spinner2.stop("Claude Code \u914D\u7F6E\u5DF2\u5199\u5165 ~/.claude/settings.json");
|
|
505
700
|
} catch (error) {
|
|
506
701
|
spinner2.stop(pc.red("Claude Code \u914D\u7F6E\u5931\u8D25"));
|
|
@@ -510,7 +705,7 @@ async function run() {
|
|
|
510
705
|
} else if (toolType === "codex") {
|
|
511
706
|
spinner2.start("\u914D\u7F6E Codex CLI\u2026");
|
|
512
707
|
try {
|
|
513
|
-
const backupDir = await configureCodex(apiKey,
|
|
708
|
+
const backupDir = await configureCodex(apiKey, writeBase, model, opts.verbose);
|
|
514
709
|
const backupHint = backupDir ? `
|
|
515
710
|
\u5907\u4EFD: ${backupDir}` : "";
|
|
516
711
|
spinner2.stop(`Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/config.toml \u4E0E auth.json${backupHint}`);
|
|
@@ -524,9 +719,10 @@ async function run() {
|
|
|
524
719
|
try {
|
|
525
720
|
const backupDir = await configureOpenCode(
|
|
526
721
|
apiKey,
|
|
527
|
-
|
|
722
|
+
writeBase,
|
|
528
723
|
model,
|
|
529
|
-
verifiedModelIds
|
|
724
|
+
verifiedModelIds,
|
|
725
|
+
protocol
|
|
530
726
|
);
|
|
531
727
|
const backupHint = backupDir ? `
|
|
532
728
|
\u5907\u4EFD: ${backupDir}` : "";
|
|
@@ -538,14 +734,15 @@ async function run() {
|
|
|
538
734
|
console.error(formatConfigError(error));
|
|
539
735
|
return 1;
|
|
540
736
|
}
|
|
541
|
-
} else {
|
|
737
|
+
} else if (toolType === "openclaw") {
|
|
542
738
|
spinner2.start("\u914D\u7F6E OpenClaw\u2026");
|
|
543
739
|
try {
|
|
544
740
|
const backupDir = await configureOpenClaw(
|
|
545
741
|
apiKey,
|
|
546
|
-
|
|
742
|
+
writeBase,
|
|
547
743
|
model,
|
|
548
|
-
verifiedModelIds
|
|
744
|
+
verifiedModelIds,
|
|
745
|
+
protocol
|
|
549
746
|
);
|
|
550
747
|
const backupHint = backupDir ? `
|
|
551
748
|
\u5907\u4EFD: ${backupDir}` : "";
|
|
@@ -557,18 +754,41 @@ async function run() {
|
|
|
557
754
|
console.error(formatConfigError(error));
|
|
558
755
|
return 1;
|
|
559
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}`
|
|
771
|
+
);
|
|
772
|
+
} catch (error) {
|
|
773
|
+
spinner2.stop(pc.red("ZCode \u914D\u7F6E\u5931\u8D25"));
|
|
774
|
+
console.error(formatConfigError(error));
|
|
775
|
+
return 1;
|
|
776
|
+
}
|
|
560
777
|
}
|
|
561
|
-
if (
|
|
778
|
+
if (verifyFailed) {
|
|
562
779
|
p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
|
|
563
780
|
return 1;
|
|
564
781
|
}
|
|
565
782
|
const nextSteps = [
|
|
566
|
-
`\u8FD0\u884C ${launchCommand(toolType)} \u542F\u52A8 ${toolLabel(toolType)}
|
|
783
|
+
`\u8FD0\u884C ${launchCommand(toolType)} \u542F\u52A8 ${toolLabel(toolType)}`,
|
|
784
|
+
`\u5DF2\u6309\u534F\u8BAE\u63A5\u5165: ${protocolLabel(protocol)}`
|
|
567
785
|
];
|
|
568
786
|
if (toolType === "opencode") {
|
|
569
787
|
nextSteps.push("\u5728 OpenCode \u5185\u7528 /models \u9009\u62E9 trinity/<model_code>");
|
|
570
788
|
} else if (toolType === "openclaw") {
|
|
571
|
-
nextSteps.push("\
|
|
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");
|
|
572
792
|
} else {
|
|
573
793
|
nextSteps.push("\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code");
|
|
574
794
|
}
|
|
@@ -74,6 +74,8 @@ function buildModelEntries(primaryModel, modelIds) {
|
|
|
74
74
|
function mergeOpenClawConfig(options) {
|
|
75
75
|
const configPath = openclawConfigPath();
|
|
76
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";
|
|
77
79
|
const modelsBlock = doc.models && typeof doc.models === "object" ? { ...doc.models } : {};
|
|
78
80
|
modelsBlock.mode = modelsBlock.mode ?? "merge";
|
|
79
81
|
const providers = modelsBlock.providers && typeof modelsBlock.providers === "object" ? { ...modelsBlock.providers } : {};
|
|
@@ -92,7 +94,7 @@ function mergeOpenClawConfig(options) {
|
|
|
92
94
|
...existing,
|
|
93
95
|
baseUrl: options.baseUrl.replace(/\/+$/, ""),
|
|
94
96
|
apiKey: options.apiKey,
|
|
95
|
-
api
|
|
97
|
+
api,
|
|
96
98
|
models: [...byId.values()]
|
|
97
99
|
};
|
|
98
100
|
modelsBlock.providers = providers;
|
|
@@ -68,6 +68,8 @@ function buildModelMap(primaryModel, modelIds) {
|
|
|
68
68
|
function mergeOpenCodeConfig(options) {
|
|
69
69
|
const configPath = opencodeConfigPath();
|
|
70
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";
|
|
71
73
|
doc.$schema = doc.$schema ?? "https://opencode.ai/config.json";
|
|
72
74
|
doc.model = `${TRINITY_OPENCODE_PROVIDER}/${options.model}`;
|
|
73
75
|
const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
|
|
@@ -75,7 +77,7 @@ function mergeOpenCodeConfig(options) {
|
|
|
75
77
|
const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
|
|
76
78
|
providers[TRINITY_OPENCODE_PROVIDER] = {
|
|
77
79
|
...existing,
|
|
78
|
-
npm
|
|
80
|
+
npm,
|
|
79
81
|
name: "Trinity",
|
|
80
82
|
options: {
|
|
81
83
|
...existing.options ?? {},
|
|
@@ -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
|
+
};
|