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 CHANGED
@@ -1,25 +1,21 @@
1
1
  # trinity-config
2
2
 
3
- > 一键把 [Trinity API](https://trinitydesk.ai) 接入 **Claude Code** / **Codex CLI** / **OpenCode** / **OpenClaw**
3
+ 一键接入 TrinityClaude 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 claude -k xh-你的key
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
- | `--type` | 默认模型 | 接口 | 写入 |
15
- | --- | --- | --- | --- |
16
- | `claude` | `claude-sonnet-4-6` | `/v1/messages` | `~/.claude/settings.json` |
17
- | `codex` | `gpt-5.4` | `/v1/responses` | `~/.codex/config.toml` + `auth.json` |
18
- | `opencode` | `gpt-5.4` | `/v1/chat/completions` | `~/.config/opencode/opencode.json` |
19
- | `openclaw` | `gpt-5.4` | `/v1/chat/completions` | `~/.openclaw/openclaw.json` |
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
- OpenClaw 改完后请重启 Gateway(或重新 `openclaw dashboard`)。
19
+ Anthropic Base **不带** `/v1`;Responses / Completions **带** `/v1`。
22
20
 
23
- ## License
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.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
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 openclaw");
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 / Agent \u7F51\u5173
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 <claude|codex|opencode|openclaw> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
75
- -k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
76
- -M, --model <model_code> \u9ED8\u8BA4\u6A21\u578B\uFF08\u7701\u7565\u5219\u6309 type \u63A8\u8350\uFF09
77
- --base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
78
- --skip-verify \u8DF3\u8FC7 GET /v1/models \u8FDE\u901A\u6027\u68C0\u67E5
79
- -v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA\uFF08\u4EC5 Claude\uFF09
80
- -h, --help \u663E\u793A\u5E2E\u52A9
81
- -V, --version \u663E\u793A\u7248\u672C
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 claude -k xh-your-key
85
- npx -y trinity-config --type codex -k xh-your-key --model gpt-5.4
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 openclaw");
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 resolveBaseUrls(baseUrl) {
260
- const root = (baseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
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-XV25MSSC.js");
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 https from "https";
290
- function getJson(url, apiKey) {
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 = https.request(
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 getJson(modelsUrl, apiKey);
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
- return { claude, codex, opencode, openclaw };
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\uFF08/v1/messages\uFF09" },
413
- { value: "codex", label: "Codex CLI\uFF08/v1/responses\uFF09" },
414
- { value: "opencode", label: "OpenCode\uFF08/v1/chat/completions\uFF09" },
415
- { value: "openclaw", label: "OpenClaw\uFF08/v1/chat/completions\uFF09" }
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 apiBaseUrl = baseUrlForType(toolType, opts.baseUrl);
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, claudeBaseUrl);
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, apiBaseUrl, model, opts.verbose);
503
- mergeClaudeEnv(model, model, apiBaseUrl);
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, apiBaseUrl, model, opts.verbose);
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
- apiBaseUrl,
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
- apiBaseUrl,
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 (!opts.skipVerify && verifiedModelIds === void 0) {
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("\u9ED8\u8BA4\u6A21\u578B\u4E3A trinity/<model_code>\uFF1B\u6539\u5B8C\u914D\u7F6E\u540E\u8BF7\u91CD\u542F Gateway");
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: "openai-completions",
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: "@ai-sdk/openai-compatible",
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trinity-config",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "一键把 Trinity API 接入 Claude Code、Codex、OpenCode、OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",