@ljwei-stak/dsh-model-router 0.14.0-beta.3 → 0.14.0
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/.dsh-plugin/client.js +37 -34
- package/.dsh-plugin/index.mjs +14 -8
- package/.dsh-plugin/shared/harness-plan.mjs +1 -1
- package/.dsh-plugin/shared/latest-versions.mjs +200 -0
- package/.dsh-plugin/shared/npm-attestation.mjs +315 -0
- package/.dsh-plugin/shared/official-tool-executor.mjs +37 -20
- package/.dsh-plugin/shared/official-tool-registry.mjs +14 -19
- package/.dsh-plugin/shared/official-tools-runtime.mjs +53 -54
- package/.dsh-plugin/shared/tool-health.mjs +25 -8
- package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +55 -37
- package/.dsh-plugin/shared/zcode-bundle.mjs +95 -32
- package/.dsh-plugin/shared/zcode-installer.mjs +33 -32
- package/CHANGELOG.md +20 -29
- package/INSTALLATION_GUIDE.zh.md +10 -10
- package/MIGRATION.md +3 -3
- package/README.md +26 -16
- package/README.zh.md +42 -30
- package/package.json +4 -2
package/.dsh-plugin/client.js
CHANGED
|
@@ -53,9 +53,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
53
53
|
vendor: "Moonshot AI",
|
|
54
54
|
purpose: "Kimi \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B kimi \u547D\u4EE4\u4E0E ACP \u4F1A\u8BDD\u3002",
|
|
55
55
|
package: "@moonshot-ai/kimi-code",
|
|
56
|
-
version: "2.1.1",
|
|
57
56
|
manager: "npm",
|
|
58
|
-
installArgs: ["install", "-g", "@moonshot-ai/kimi-code@
|
|
57
|
+
installArgs: ["install", "-g", "@moonshot-ai/kimi-code@latest", "--registry=https://registry.npmjs.org/"],
|
|
59
58
|
probeExecutables: ["kimi"],
|
|
60
59
|
probeNote: "kimi \u4E0E\u65E7 Python \u7248 kimi-cli \u540C\u540D\uFF1B\u8BF7\u6838\u5BF9\u53EF\u6267\u884C\u6587\u4EF6\u6765\u6E90\u548C\u7248\u672C\u3002",
|
|
61
60
|
providerHints: ["moonshot", "kimi"]
|
|
@@ -66,9 +65,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
66
65
|
vendor: "Anthropic",
|
|
67
66
|
purpose: "Anthropic \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B claude \u547D\u4EE4\u3002",
|
|
68
67
|
package: "@anthropic-ai/claude-code",
|
|
69
|
-
version: "2.1.283",
|
|
70
68
|
manager: "npm",
|
|
71
|
-
installArgs: ["install", "-g", "@anthropic-ai/claude-code@
|
|
69
|
+
installArgs: ["install", "-g", "@anthropic-ai/claude-code@latest", "--registry=https://registry.npmjs.org/"],
|
|
72
70
|
probeExecutables: ["claude"],
|
|
73
71
|
providerHints: ["anthropic", "claude"]
|
|
74
72
|
}),
|
|
@@ -78,11 +76,10 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
78
76
|
vendor: "OpenAI",
|
|
79
77
|
purpose: "OpenAI \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B codex \u547D\u4EE4\u3002",
|
|
80
78
|
package: "@openai/codex",
|
|
81
|
-
|
|
82
|
-
installArgs: ["install", "-g", "@openai/codex@0.157.1", "--registry=https://registry.npmjs.org/"],
|
|
79
|
+
installArgs: ["install", "-g", "@openai/codex@latest", "--registry=https://registry.npmjs.org/"],
|
|
83
80
|
manager: "npm",
|
|
84
81
|
probeExecutables: ["codex"],
|
|
85
|
-
probeNote: "Codex \u7248\u672C\u6A2A\u5E45\u7531\u9002\u914D\u5C42\u5BBD\u5339\u914D\uFF1B\u5B89\u88C5\u65F6\
|
|
82
|
+
probeNote: "Codex \u7248\u672C\u6A2A\u5E45\u7531\u9002\u914D\u5C42\u5BBD\u5339\u914D\uFF1B\u5B89\u88C5\u65F6\u53D6 npm \u6700\u65B0\u7248\u3002",
|
|
86
83
|
providerHints: ["openai", "gpt", "codex"]
|
|
87
84
|
}),
|
|
88
85
|
Object.freeze({
|
|
@@ -91,9 +88,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
91
88
|
vendor: "MiniMax",
|
|
92
89
|
purpose: "MiniMax \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B mcode \u547D\u4EE4\u3002",
|
|
93
90
|
package: "@minimax-ai/code",
|
|
94
|
-
version: "0.5.5",
|
|
95
91
|
manager: "npm",
|
|
96
|
-
installArgs: ["install", "-g", "@minimax-ai/code@
|
|
92
|
+
installArgs: ["install", "-g", "@minimax-ai/code@latest", "--registry=https://registry.npmjs.org/", "--ignore-scripts=false", "--include=optional", "--allow-scripts=@minimax-ai/code,better-sqlite3"],
|
|
97
93
|
probeExecutables: ["mcode"],
|
|
98
94
|
providerHints: ["minimax"]
|
|
99
95
|
}),
|
|
@@ -103,9 +99,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
103
99
|
vendor: "XiaoMi",
|
|
104
100
|
purpose: "\u5C0F\u7C73 MiMo \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B mimo \u547D\u4EE4\u3002",
|
|
105
101
|
package: "@mimo-ai/cli",
|
|
106
|
-
version: "0.1.15",
|
|
107
102
|
manager: "npm",
|
|
108
|
-
installArgs: ["install", "-g", "@mimo-ai/cli@
|
|
103
|
+
installArgs: ["install", "-g", "@mimo-ai/cli@latest", "--registry=https://registry.npmjs.org/"],
|
|
109
104
|
probeExecutables: ["mimo"],
|
|
110
105
|
providerHints: ["mimo", "xiaomi"]
|
|
111
106
|
}),
|
|
@@ -115,9 +110,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
115
110
|
vendor: "xAI",
|
|
116
111
|
purpose: "xAI \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B grok \u547D\u4EE4\u4E0E ACP \u4F1A\u8BDD\u3002",
|
|
117
112
|
package: "@xai-official/grok",
|
|
118
|
-
version: "1.0.41",
|
|
119
113
|
manager: "npm",
|
|
120
|
-
installArgs: ["install", "-g", "@xai-official/grok@
|
|
114
|
+
installArgs: ["install", "-g", "@xai-official/grok@latest", "--registry=https://registry.npmjs.org/"],
|
|
121
115
|
probeExecutables: ["grok"],
|
|
122
116
|
providerHints: ["xai", "grok"]
|
|
123
117
|
}),
|
|
@@ -127,9 +121,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
127
121
|
vendor: "Google",
|
|
128
122
|
purpose: "Google \u5B98\u65B9 Gemini CLI\uFF0C\u65E0\u754C\u9762\u6A21\u5F0F\u4F7F\u7528 gemini -p\u3002",
|
|
129
123
|
package: "@google/gemini-cli",
|
|
130
|
-
version: "0.62.0",
|
|
131
124
|
manager: "npm",
|
|
132
|
-
installArgs: ["install", "-g", "@google/gemini-cli@
|
|
125
|
+
installArgs: ["install", "-g", "@google/gemini-cli@latest", "--registry=https://registry.npmjs.org/"],
|
|
133
126
|
probeExecutables: ["gemini"],
|
|
134
127
|
providerHints: ["gemini", "google"],
|
|
135
128
|
// Headless runs go through the task adapter. The signed sandbox runner does
|
|
@@ -141,7 +134,6 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
141
134
|
label: "ZCode",
|
|
142
135
|
vendor: "Z.ai",
|
|
143
136
|
purpose: "\u667A\u8C31\u5B98\u65B9 ZCode \u684C\u9762\u7248\uFF0C\u5185\u542B GLM \u7F16\u7A0B\u4EE3\u7406\u3002Windows \u5B89\u88C5\u5668\u53EF\u9009\u62E9 D \u76D8\u76EE\u5F55\u3002",
|
|
144
|
-
version: "3.14.3",
|
|
145
137
|
manager: "signed-windows-installer",
|
|
146
138
|
installArgs: [],
|
|
147
139
|
probeExecutables: [],
|
|
@@ -1359,7 +1351,7 @@ function channelForProvider(provider, installedToolIds = [], runnableToolIds = [
|
|
|
1359
1351
|
preference,
|
|
1360
1352
|
tool: tool.id,
|
|
1361
1353
|
label: tool.label,
|
|
1362
|
-
detail: tool.id === "zcode" ? "ZCode \u5DF2\u5B89\u88C5\uFF0C\u63D2\u4EF6\u53EF\u8C03\u7528\u5176\u5B98\u65B9\u7F16\u7A0B\u4EE3\u7406\
|
|
1354
|
+
detail: tool.id === "zcode" ? "ZCode \u5DF2\u5B89\u88C5\uFF0C\u63D2\u4EF6\u53EF\u8C03\u7528\u5176\u5B98\u65B9\u7F16\u7A0B\u4EE3\u7406\uFF1B\u5176 CLI \u4F7F\u7528\u81EA\u8EAB\u914D\u7F6E\u7684\u9ED8\u8BA4\u6A21\u578B\uFF0C\u4E0D\u80FD\u4FDD\u8BC1\u4E0E Harness \u5EFA\u8BAE\u6A21\u578B\u4E00\u81F4\u3002" : headless && !runnable ? `${tool.label} \u5DF2\u5B89\u88C5\u3002\u5206\u914D\u5230\u8BE5\u6A21\u578B\u7684\u4EFB\u52A1\u4F1A\u5148\u8D70\u5B98\u65B9\u65E0\u754C\u9762\u547D\u4EE4\uFF1B\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002` : `${tool.label} \u5DF2\u5B89\u88C5\uFF0C\u63D2\u4EF6\u53EF\u6258\u7BA1\u8C03\u7528\u5176\u5B98\u65B9 CLI\uFF1BHarness \u6A21\u578B\u76EE\u5F55\u4E0E\u5382\u5546 CLI \u540D\u79F0\u53EF\u80FD\u4E0D\u540C\uFF0C\u56E2\u961F\u65E0\u6CD5\u786E\u8BA4\u6620\u5C04\u65F6\u4F7F\u7528 CLI \u9ED8\u8BA4\u6A21\u578B\uFF0C\u5B9E\u9645\u6A21\u578B\u4ECD\u987B\u6838\u5BF9\u8FD0\u884C\u8BB0\u5F55\u3002`
|
|
1363
1355
|
};
|
|
1364
1356
|
}
|
|
1365
1357
|
return {
|
|
@@ -1729,16 +1721,19 @@ function stableVersionOrder(left, right) {
|
|
|
1729
1721
|
}
|
|
1730
1722
|
return 0;
|
|
1731
1723
|
}
|
|
1732
|
-
function toolInstallAction({ tool, probe, readiness, job, probeStatus }) {
|
|
1724
|
+
function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVersion = null }) {
|
|
1733
1725
|
const running = job?.status === "running";
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
const
|
|
1737
|
-
const
|
|
1738
|
-
const
|
|
1726
|
+
const installed = probe?.installed === true;
|
|
1727
|
+
const order = installed && latestVersion ? stableVersionOrder(probe.version, latestVersion) : null;
|
|
1728
|
+
const ready = readiness?.ready === true;
|
|
1729
|
+
const desktop = tool.manager === "signed-windows-installer";
|
|
1730
|
+
const noRunner = desktop || tool.headlessAdapter === true;
|
|
1731
|
+
const repair = installed && !noRunner && readiness?.ready === false;
|
|
1732
|
+
const label = running ? "\u5B89\u88C5\u4E2D\u2026" : !installed ? job?.status === "failed" ? "\u91CD\u8BD5\u5B89\u88C5" : desktop ? "\u4E0B\u8F7D\u6700\u65B0\u5B89\u88C5\u5668" : "\u4E00\u952E\u5B89\u88C5\u6700\u65B0\u7248" : order === -1 ? `\u66F4\u65B0\u5230\u6700\u65B0\u7248 ${latestVersion}` : order === 1 ? "\u5DF2\u9AD8\u4E8E\u6700\u65B0\u6B63\u5F0F\u7248" : repair ? "\u4FEE\u590D\u5B98\u65B9\u6267\u884C\u5165\u53E3" : order === 0 ? "\u5DF2\u662F\u6700\u65B0\u7248\u672C" : "\u5B89\u88C5\u6700\u65B0\u7248";
|
|
1733
|
+
const current = order === 0 && (ready || noRunner);
|
|
1739
1734
|
return {
|
|
1740
1735
|
label,
|
|
1741
|
-
disabled: probeStatus !== "ready" || running ||
|
|
1736
|
+
disabled: probeStatus !== "ready" || running || installed && (order === 1 || current)
|
|
1742
1737
|
};
|
|
1743
1738
|
}
|
|
1744
1739
|
|
|
@@ -1782,7 +1777,14 @@ function dagLayers(packages) {
|
|
|
1782
1777
|
return layers.filter(Boolean);
|
|
1783
1778
|
}
|
|
1784
1779
|
var LOGIN_LABEL = Object.freeze({ "logged-in": "\u5DF2\u767B\u5F55", "logged-out": "\u672A\u767B\u5F55", unknown: "\u767B\u5F55\u72B6\u6001\u672A\u77E5" });
|
|
1785
|
-
var VERSION_LABEL = Object.freeze({
|
|
1780
|
+
var VERSION_LABEL = Object.freeze({ latest: "\u5DF2\u662F\u6700\u65B0\u7248", "update-available": "\u6709\u65B0\u7248\u672C", unknown: "\u6700\u65B0\u7248\u672C\u672A\u77E5" });
|
|
1781
|
+
var UNTESTED_VERSION_NOTE = "\u63D2\u4EF6\u4E0D\u518D\u56FA\u5B9A\u5B98\u65B9\u5DE5\u5177\u7248\u672C\uFF0C\u5B89\u88C5\u548C\u66F4\u65B0\u90FD\u53D6\u5404\u5382\u5546\u7684\u6700\u65B0\u7248\uFF1B\u65B0\u7248\u672C\u672A\u7ECF\u63D2\u4EF6\u6D4B\u8BD5\uFF0C\u8F93\u51FA\u683C\u5F0F\u53D8\u5316\u65F6\u63D2\u4EF6\u4F1A\u5C3D\u91CF\u517C\u5BB9\uFF0C\u9047\u5230\u95EE\u9898\u8BF7\u53CD\u9988\u3002";
|
|
1782
|
+
function versionLine(entry) {
|
|
1783
|
+
if (!entry?.installed) return "";
|
|
1784
|
+
const installed = entry.version ? `\u5DF2\u5B89\u88C5 ${entry.version}` : "\u5DF2\u5B89\u88C5\uFF08\u7248\u672C\u672A\u77E5\uFF09";
|
|
1785
|
+
if (!entry.latestVersion) return `${installed} \xB7 ${VERSION_LABEL.unknown}`;
|
|
1786
|
+
return entry.versionStatus === "update-available" ? `${installed} \xB7 \u6700\u65B0 ${entry.latestVersion}\uFF08\u6709\u65B0\u7248\u672C\uFF0C\u53EF\u5728\u4E0B\u65B9\u66F4\u65B0\uFF09` : `${installed} \xB7 ${VERSION_LABEL.latest}`;
|
|
1787
|
+
}
|
|
1786
1788
|
function healthSummary(tools) {
|
|
1787
1789
|
const list = Array.isArray(tools) ? tools : [];
|
|
1788
1790
|
const installed = list.filter((item) => item.installed);
|
|
@@ -1792,7 +1794,7 @@ function healthSummary(tools) {
|
|
|
1792
1794
|
ready: installed.filter((item) => item.login?.state === "logged-in").length,
|
|
1793
1795
|
loggedOut: installed.filter((item) => item.login?.state === "logged-out").length,
|
|
1794
1796
|
unknown: installed.filter((item) => item.login?.state === "unknown").length,
|
|
1795
|
-
|
|
1797
|
+
updates: installed.filter((item) => item.versionStatus === "update-available").length
|
|
1796
1798
|
};
|
|
1797
1799
|
}
|
|
1798
1800
|
var money = formatUsd;
|
|
@@ -1953,8 +1955,9 @@ var SUBSCRIPTION_FAILURE_OPTIONS = [["ask", "\u6682\u505C\u5E76\u8BE2\u95EE"], [
|
|
|
1953
1955
|
var SUBSCRIPTION_CHOICE_BUTTONS = [["api", "\u6539\u7528 API \u91CD\u8BD5", false], ["subscription", "\u91CD\u8BD5\u8BA2\u9605", true], ["cancel", "\u53D6\u6D88", true]];
|
|
1954
1956
|
function OnboardingBanner({ health, onDone, onRefresh, refreshing, error }) {
|
|
1955
1957
|
const summary = healthSummary(health?.tools);
|
|
1956
|
-
const problems = (health?.tools ?? []).filter((item) => item.installed &&
|
|
1957
|
-
|
|
1958
|
+
const problems = (health?.tools ?? []).filter((item) => item.installed && item.login?.state === "logged-out");
|
|
1959
|
+
const updates = (health?.tools ?? []).filter((item) => item.installed && item.versionStatus === "update-available");
|
|
1960
|
+
return /* @__PURE__ */ import_react2.default.createElement("section", { className: "mr-card mr-onboarding", "aria-label": "\u5F00\u7BB1\u4F53\u68C0" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react2.default.createElement("div", null, /* @__PURE__ */ import_react2.default.createElement("h2", { className: "mr-card-title" }, "\u5F00\u7BB1\u4F53\u68C0"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-card-copy" }, "\u9996\u6B21\u6253\u5F00\u65F6\u68C0\u6D4B\u6BCF\u4E2A\u5B98\u65B9\u5DE5\u5177\u662F\u5426\u5DF2\u5B89\u88C5\u3001\u5F53\u524D\u7248\u672C\uFF08\u4EE5\u53CA\u80FD\u67E5\u5230\u65F6\u7684\u6700\u65B0\u7248\u672C\uFF09\u3001\u662F\u5426\u5DF2\u767B\u5F55\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u4F1A\u88AB\u8DEF\u7531\u76F4\u63A5\u8DF3\u8FC7\uFF0C\u6539\u8D70\u6A21\u578B\u76EE\u5F55 API\uFF0C\u4E0D\u5FC5\u7B49\u5F85 CLI \u5931\u8D25\u3002")), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: refreshing, onClick: onRefresh }, refreshing ? "\u68C0\u6D4B\u4E2D\u2026" : "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-card-body" }, error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-error", role: "alert" }, error), !health && !error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u4F53\u68C0\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), health && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-result-grid" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-label" }, "\u5DF2\u5B89\u88C5"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-value" }, summary.installed, " / ", summary.total)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-label" }, "\u5DF2\u767B\u5F55\u53EF\u7528"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-value" }, summary.ready)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-label" }, "\u672A\u767B\u5F55"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-value" }, summary.loggedOut)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-label" }, "\u767B\u5F55\u72B6\u6001\u672A\u77E5"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-metric-value" }, summary.unknown))), problems.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("ul", { className: "mr-checklist" }, problems.map((item) => /* @__PURE__ */ import_react2.default.createElement("li", { key: item.id }, /* @__PURE__ */ import_react2.default.createElement("strong", null, item.label), "\uFF1A\u672A\u767B\u5F55\uFF0C\u53EF\u5728\u4E0B\u65B9\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u65B9\u6CD5\uFF08", item.login.command ?? "\u89C1\u8BF4\u660E", "\uFF09\u3002"))) : /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u5DF2\u5B89\u88C5\u7684\u5DE5\u5177\u6CA1\u6709\u53D1\u73B0\u672A\u767B\u5F55\u7684\u95EE\u9898\u3002\u672A\u5B89\u88C5\u7684\u5DE5\u5177\u53EF\u5728\u4E0B\u65B9\u4E00\u952E\u5B89\u88C5\u3002"), updates.length > 0 && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u6709\u65B0\u7248\u672C\uFF1A", updates.map((item) => `${item.label} ${item.version ?? "?"} \u2192 ${item.latestVersion}`).join("\uFF1B"), "\u3002\u53EF\u5728\u4E0B\u65B9\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u4E2D\u66F4\u65B0\uFF08\u53EF\u9009\uFF09\u3002"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, UNTESTED_VERSION_NOTE), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button", type: "button", onClick: onDone }, "\u5B8C\u6210\u4F53\u68C0"), /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u4E4B\u540E\u53EF\u968F\u65F6\u5728\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u5361\u7247\u4E2D\u91CD\u65B0\u4F53\u68C0\u3002")))));
|
|
1958
1961
|
}
|
|
1959
1962
|
function ToolLoginLine({ entry }) {
|
|
1960
1963
|
const [open, setOpen] = import_react2.default.useState(false);
|
|
@@ -1969,7 +1972,7 @@ function ToolLoginLine({ entry }) {
|
|
|
1969
1972
|
setCopied(false);
|
|
1970
1973
|
}
|
|
1971
1974
|
};
|
|
1972
|
-
return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-login" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-tool-status" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: `mr-tool-dot ${state === "logged-in" ? "installed" : state === "logged-out" ? "missing-strong" : ""}` }), LOGIN_LABEL[state], " \xB7 ",
|
|
1975
|
+
return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-login" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-tool-status" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: `mr-tool-dot ${state === "logged-in" ? "installed" : state === "logged-out" ? "missing-strong" : ""}` }), LOGIN_LABEL[state], " \xB7 ", versionLine(entry)), entry.login?.detail && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption mr-tool-detail" }, entry.login.detail), state === "logged-in" && entry.login?.billing === "subscription" && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "\u8BA2\u9605\u8D26\u53F7\u767B\u5F55\uFF1A\u8D39\u7528\u53EA\u4F5C\u53C2\u8003\u663E\u793A\uFF0C\u4E0D\u8BA1\u5165\u9884\u7B97\u3002"), state === "logged-in" && entry.login?.billing === "api-key" && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "API Key \u8BA1\u8D39\uFF1A\u8D39\u7528\u8BA1\u5165\u6BCF\u65E5/\u6BCF\u6708\u9884\u7B97\u3002"), state !== "logged-in" && /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button mr-login-button", type: "button", "aria-expanded": open, onClick: () => setOpen((value) => !value) }, "\u53BB\u767B\u5F55"), open && /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-login-guide" }, /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, entry.login.steps), entry.login.command && /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-login-command" }, /* @__PURE__ */ import_react2.default.createElement("code", { className: "mr-tool-command" }, entry.login.command), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
|
|
1973
1976
|
void copy();
|
|
1974
1977
|
} }, copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u547D\u4EE4")), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u63D2\u4EF6\u4E0D\u4F1A\u66FF\u4F60\u542F\u52A8\u4EA4\u4E92\u5F0F\u767B\u5F55\u3002\u767B\u5F55\u5B8C\u6210\u540E\u70B9\u201C\u91CD\u65B0\u4F53\u68C0\u201D\u3002")));
|
|
1975
1978
|
}
|
|
@@ -11601,7 +11604,7 @@ var xterm_default = `/**
|
|
|
11601
11604
|
`;
|
|
11602
11605
|
|
|
11603
11606
|
// .dsh-plugin/client/host-version.mjs
|
|
11604
|
-
var ROUTER_CLIENT_VERSION = true ? "0.14.0
|
|
11607
|
+
var ROUTER_CLIENT_VERSION = true ? "0.14.0" : "";
|
|
11605
11608
|
var STALE_HOST_MESSAGE = "\u63D2\u4EF6\u540E\u53F0\u7248\u672C\u8F83\u65E7\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F Harness\uFF08\u5305\u62EC\u6258\u76D8\u56FE\u6807\uFF09\u540E\u518D\u4F7F\u7528\u3002";
|
|
11606
11609
|
function isMissingRemoteMethod(message) {
|
|
11607
11610
|
const value = String(message ?? "");
|
|
@@ -12443,7 +12446,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
|
|
|
12443
12446
|
const byId = Object.fromEntries(probeState.probes.map((probe) => [probe.id, probe]));
|
|
12444
12447
|
const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
|
|
12445
12448
|
const readinessById = Object.fromEntries(probeState.readiness.map((item) => [item.id, item]));
|
|
12446
|
-
return /* @__PURE__ */ import_react5.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-card-copy" }, "\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u3001\u7248\u672C\u548C\u767B\u5F55\u72B6\u6001\uFF0C\u5E76\u4ECE\u56FA\u5B9A\u6CE8\u518C\u8868\u4E00\u952E\u5B89\u88C5\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u547D\u4EE4\u3002ZCode \u4F1A\u6253\u5F00\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u4F9B\u4F60\u9009\u62E9\u76EE\u5F55\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u4F53\u68C0\u3002")), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: probeState.status === "loading", onClick: () => {
|
|
12449
|
+
return /* @__PURE__ */ import_react5.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-card-copy" }, "\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u3001\u7248\u672C\u548C\u767B\u5F55\u72B6\u6001\uFF0C\u5E76\u4ECE\u56FA\u5B9A\u6CE8\u518C\u8868\u4E00\u952E\u5B89\u88C5\u6216\u66F4\u65B0\u5230\u5404\u5382\u5546\u6700\u65B0\u7248\uFF08\u65B0\u7248\u672C\u672A\u7ECF\u63D2\u4EF6\u6D4B\u8BD5\uFF09\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u547D\u4EE4\u3002ZCode \u4F1A\u6253\u5F00\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u4F9B\u4F60\u9009\u62E9\u76EE\u5F55\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u4F53\u68C0\u3002")), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: probeState.status === "loading", onClick: () => {
|
|
12447
12450
|
void refresh();
|
|
12448
12451
|
} }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, probeState.status === "loading" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), probeState.status === "error" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, probeState.error), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tools", role: "list", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u6CE8\u518C\u8868" }, OFFICIAL_TOOLS.map((tool) => {
|
|
12449
12452
|
const command = installCommandLine(tool);
|
|
@@ -12452,10 +12455,10 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
|
|
|
12452
12455
|
const readiness = readinessById[tool.id];
|
|
12453
12456
|
const job = jobs[tool.id];
|
|
12454
12457
|
const running = job?.status === "running";
|
|
12455
|
-
const action = toolInstallAction({ tool, probe, readiness, job, probeStatus: probeState.status });
|
|
12458
|
+
const action = toolInstallAction({ tool, probe, readiness, job, probeStatus: probeState.status, latestVersion: healthById[tool.id]?.latestVersion ?? null });
|
|
12456
12459
|
const verified = job?.status === "succeeded" && job.postInstallProbe?.installed === true;
|
|
12457
12460
|
const status = running ? job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u5B89\u88C5\u2026" : "\u5B89\u88C5\u4E2D\u2026" : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? "\u5B89\u88C5\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B" : verified ? "\u5B89\u88C5\u6210\u529F\u5E76\u9A8C\u8BC1" : probeLabel(probe);
|
|
12458
|
-
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status,
|
|
12461
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status, probe?.installed && probe.version ? ` \xB7 ${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), probe?.installed && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, tool.headlessAdapter ? "\u5DF2\u53EF\u7531 model_router_execute \u4EE5\u65E0\u754C\u9762\u65B9\u5F0F\u8C03\u7528\u3002\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002\u7B7E\u540D\u6C99\u7BB1\u5165\u53E3\u4E0D\u542F\u52A8\u6B64 CLI\u3002" : readiness?.ready ? `\u5B98\u65B9\u6267\u884C\u5165\u53E3\u5DF2\u6838\u9A8C\uFF0C\u53EF\u5728\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_tool_run\uFF1B${capability?.modes?.includes("read-only") ? "\u652F\u6301\u53EA\u8BFB\u548C\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u4EFB\u52A1" : "\u4EC5\u652F\u6301\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u9694\u79BB\u5DE5\u4F5C\u533A\u4EFB\u52A1"}\uFF0C\u8D26\u53F7\u53CA\u6A21\u578B\u4ECD\u9700\u5B9E\u6D4B\u3002` : `\u5DF2\u5B89\u88C5\uFF0C\u4F46\u5F53\u524D\u4E0D\u53EF\u6258\u7BA1\u6267\u884C\uFF1A${readiness?.reason || capability?.reason || "\u6267\u884C\u5165\u53E3\u5C1A\u672A\u6838\u9A8C\u3002"}`), command ? /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command" }, command) : /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { margin: "6px 0 0" } }, tool.unsupportedReason), probe?.detail && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, probe.detail), (rowErrors[tool.id] || job?.error) && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error mr-tool-error", role: "alert" }, rowErrors[tool.id] || job.error), Array.isArray(job?.outputTail) && job.outputTail.length > 0 && /* @__PURE__ */ import_react5.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, "\u5B89\u88C5\u65E5\u5FD7"), /* @__PURE__ */ import_react5.default.createElement("pre", null, job.outputTail.slice(-6).join("\n")))), command && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled, onClick: () => {
|
|
12459
12462
|
void install(tool.id);
|
|
12460
12463
|
} }, action.label), running && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: job.cancelRequested, onClick: () => {
|
|
12461
12464
|
void cancel(tool.id);
|
|
@@ -12754,7 +12757,7 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
|
|
|
12754
12757
|
}
|
|
12755
12758
|
), /* @__PURE__ */ import_react5.default.createElement(SecurityCard, { data: workbench.boundaries.value, error: workbench.boundaries.error, onRefresh: () => {
|
|
12756
12759
|
void workbench.refreshBoundaries();
|
|
12757
|
-
} }), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-notice" }, "\u53EA\u8BFB\u4EFB\u52A1\u53EF\u5728\u4E0A\u65B9\u201C\u5728\u5DE5\u4F5C\u53F0\u6267\u884C\u201D\u4E2D\u76F4\u63A5\u9884\u89C8\u5E76\u6267\u884C\uFF1B\u5728\u5B98\u65B9\u4F1A\u8BDD\u4E2D\u53EF\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_execute"), "\uFF08\u6309\u8DEF\u7531\u6216\u6307\u5B9A\u6A21\u578B\u6267\u884C\uFF0C\u5B98\u65B9 CLI \u5931\u8D25\u5219\u56DE\u9000 API\uFF09\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_consult"), "\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_run"), " \u6216 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_team_execute"), "\u3002\u6307\u5B9A\u6A21\u578B\u4F1A\u8DF3\u8FC7\u8DEF\u7EBF\u6BD4\u8F83\u3002\u6258\u7BA1\u6267\u884C\u80FD\u529B\u548C\u5C31\u7EEA\u72B6\u6001\u89C1\u4E0A\u65B9\u5404\u5DE5\u5177\u5361\u7247\uFF1B\u5B9E\u9645\u4F7F\u7528\u7684\u6A21\u578B\u4EE5\u5382\u5546\u8BB0\u5F55\u4E3A\u51C6\u3002ZCode
|
|
12760
|
+
} }), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-notice" }, "\u53EA\u8BFB\u4EFB\u52A1\u53EF\u5728\u4E0A\u65B9\u201C\u5728\u5DE5\u4F5C\u53F0\u6267\u884C\u201D\u4E2D\u76F4\u63A5\u9884\u89C8\u5E76\u6267\u884C\uFF1B\u5728\u5B98\u65B9\u4F1A\u8BDD\u4E2D\u53EF\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_execute"), "\uFF08\u6309\u8DEF\u7531\u6216\u6307\u5B9A\u6A21\u578B\u6267\u884C\uFF0C\u5B98\u65B9 CLI \u5931\u8D25\u5219\u56DE\u9000 API\uFF09\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_consult"), "\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_run"), " \u6216 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_team_execute"), "\u3002\u6307\u5B9A\u6A21\u578B\u4F1A\u8DF3\u8FC7\u8DEF\u7EBF\u6BD4\u8F83\u3002\u6258\u7BA1\u6267\u884C\u80FD\u529B\u548C\u5C31\u7EEA\u72B6\u6001\u89C1\u4E0A\u65B9\u5404\u5DE5\u5177\u5361\u7247\uFF1B\u5B9E\u9645\u4F7F\u7528\u7684\u6A21\u578B\u4EE5\u5382\u5546\u8BB0\u5F55\u4E3A\u51C6\u3002ZCode \u4F7F\u7528\u5176\u81EA\u8EAB\u914D\u7F6E\u7684\u9ED8\u8BA4\u6A21\u578B\u3002\u53EF\u7F16\u8F91\u56E2\u961F\u4EFB\u52A1\u8981\u6C42\u5E72\u51C0\u7684 Git \u4ED3\u5E93\uFF0C\u5E76\u7ECF\u5B98\u65B9\u5DE5\u5177\u5BA1\u6279\u3002\u8BBE\u7F6E\u4F4D\u4E8E\u201C\u63D2\u4EF6 \u2192 \u5DF2\u5B89\u88C5 \u2192 @ljwei-stak/model-router-galgame\u201D\u3002")));
|
|
12758
12761
|
}
|
|
12759
12762
|
|
|
12760
12763
|
// .dsh-plugin/shared/official-tools-remote.mjs
|
package/.dsh-plugin/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
7
7
|
import { DEFAULT_ROUTER_SETTINGS, modelMetadata } from './shared/router.mjs'
|
|
8
8
|
import { DEFAULT_ROUTING_PRESET, normalizeRoutingPreset, routingPreset } from './shared/routing-presets.mjs'
|
|
9
9
|
import { HEALTH_CACHE_MS, apiKeyEnvPresent, healthCache, runHealthCheck, subscriptionLoginOf } from './shared/tool-health.mjs'
|
|
10
|
+
import { latestVersions } from './shared/latest-versions.mjs'
|
|
10
11
|
import {
|
|
11
12
|
DEFAULT_COOLDOWN_MINUTES, billingOverview, createQuotaTracker, detectQuotaExhaustion, parseQuotaPatterns, vendorKey,
|
|
12
13
|
} from './shared/subscription-billing.mjs'
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
parseTerminalWrite,
|
|
29
30
|
} from './shared/cli-terminal-protocol.mjs'
|
|
30
31
|
import {
|
|
32
|
+
OFFICIAL_TOOLS,
|
|
31
33
|
getOfficialTool,
|
|
32
34
|
installCommandLine,
|
|
33
35
|
toolForProvider,
|
|
@@ -293,9 +295,13 @@ const readAuthState = async path => {
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
/** 开箱体检: install, version and login state for every registry tool. */
|
|
296
|
-
export async function toolHealthReport({ fresh = false, runner = defaultRunner } = {}) {
|
|
297
|
-
|
|
298
|
-
const
|
|
298
|
+
export async function toolHealthReport({ fresh = false, runner = defaultRunner, latest = latestVersions } = {}) {
|
|
299
|
+
// Latest-version lookups run alongside the probes; failures are non-fatal (offline-safe).
|
|
300
|
+
const [probes, latestById] = await Promise.all([
|
|
301
|
+
probeAllTools({ fresh }),
|
|
302
|
+
latest.lookupAll(OFFICIAL_TOOLS).catch(() => ({})),
|
|
303
|
+
])
|
|
304
|
+
const report = await runHealthCheck(probes, { runner, home: homedir(), exists: fileExists, readAuthState, latest: latestById })
|
|
299
305
|
healthCache.remember(report)
|
|
300
306
|
try { await routerStateStore().saveHealth(report) } catch { /* the cache still serves this process */ }
|
|
301
307
|
return report
|
|
@@ -957,7 +963,7 @@ export async function planToolRun(ctx, config, args = {}, { signal } = {}) {
|
|
|
957
963
|
}
|
|
958
964
|
if (text(args.cliModel)) {
|
|
959
965
|
if (!route) throw new Error('cliModel 需要同时提供已配置的 provider 和 model 路线')
|
|
960
|
-
if (toolId === 'zcode') throw new Error('ZCode
|
|
966
|
+
if (toolId === 'zcode') throw new Error('ZCode CLI 不支持在单次调用中指定模型')
|
|
961
967
|
modelId = text(args.cliModel)
|
|
962
968
|
}
|
|
963
969
|
const estimate = route ? createPlanFromRoutes(taskText, routes, { mode: 'direct', directProvider: route.provider, directModel: route.model }).estimatedCost : null
|
|
@@ -1503,7 +1509,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1503
1509
|
}))
|
|
1504
1510
|
ctx.tools.register(defineTool({
|
|
1505
1511
|
name: 'model_router_tool_install',
|
|
1506
|
-
description: 'Install one official model tool by registry id
|
|
1512
|
+
description: 'Install one official model tool by registry id from its fixed official source at the vendor latest release. ZCode opens the latest publisher-signed interactive desktop installer with a directory picker. Only registry ids are accepted; arbitrary packages or executables are refused.',
|
|
1507
1513
|
parameters: {
|
|
1508
1514
|
tool: { type: 'string', required: true, description: 'Registry tool id, e.g. kimi-code.' },
|
|
1509
1515
|
},
|
|
@@ -1532,7 +1538,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1532
1538
|
task: { type: 'string', required: true, description: 'Concrete task for the official CLI model.' },
|
|
1533
1539
|
provider: { type: 'string', description: 'Optional configured provider, paired with model.' },
|
|
1534
1540
|
model: { type: 'string', description: 'Optional model ID from the Harness directory, paired with provider. This ID is advisory for CLIs except Claude/Codex.' },
|
|
1535
|
-
cliModel: { type: 'string', description: 'Optional model name already configured in this vendor CLI; requires provider and model. MiniMax/MiMo require provider/model format. ZCode
|
|
1541
|
+
cliModel: { type: 'string', description: 'Optional model name already configured in this vendor CLI; requires provider and model. MiniMax/MiMo require provider/model format. ZCode cannot switch models per call.' },
|
|
1536
1542
|
mode: { type: 'string', enum: ['read-only', 'workspace-write'], description: 'Default is read-only. Kimi, MiniMax and ZCode require workspace-write. Write mode requires official approval and a clean Git repository.' },
|
|
1537
1543
|
confirmOverBudget: { type: 'boolean', description: 'Set only after the user agreed to exceed the daily/monthly budget. Triggers an approval prompt.' },
|
|
1538
1544
|
},
|
|
@@ -1597,7 +1603,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1597
1603
|
}))
|
|
1598
1604
|
ctx.tools.register(defineTool({
|
|
1599
1605
|
name: 'model_router_health',
|
|
1600
|
-
description: 'Onboarding health check: for each official CLI in the fixed registry, report installed, version
|
|
1606
|
+
description: 'Onboarding health check: for each official CLI in the fixed registry, report installed version, the latest published version when it can be looked up cheaply (update hint only), and login state (cheap status commands only; never starts a login), plus a per-provider billing table: billing mode (subscription-first by default), subscription state (logged in / coding-plan key route / exhausted until a time) and whether an API-key route is available for fallback. Logged-out tools are skipped by routing until re-checked.',
|
|
1601
1607
|
parameters: {
|
|
1602
1608
|
fresh: { type: 'boolean', description: 'Re-probe instead of using the 60s probe cache.' },
|
|
1603
1609
|
},
|
|
@@ -1658,7 +1664,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1658
1664
|
task: { type: 'string', required: true, description: 'Full task to plan, distribute and execute.' },
|
|
1659
1665
|
mode: { type: 'string', enum: ['read-only', 'workspace-write'], description: 'Default read-only; workspace-write needs a clean Git repository and approval.' },
|
|
1660
1666
|
budgetUsd: { type: 'number', description: 'Estimated planning ceiling only, not a vendor billing limit.' },
|
|
1661
|
-
cliModelsJson: { type: 'string', description: 'Optional JSON object mapping official tool IDs or work package IDs to exact model names configured in those CLIs. MiniMax/MiMo require provider/model; ZCode
|
|
1667
|
+
cliModelsJson: { type: 'string', description: 'Optional JSON object mapping official tool IDs or work package IDs to exact model names configured in those CLIs. MiniMax/MiMo require provider/model; ZCode cannot switch per call.' },
|
|
1662
1668
|
confirmOverBudget: { type: 'boolean', description: 'Set only after the user agreed to exceed the daily/monthly budget. Triggers an approval prompt.' },
|
|
1663
1669
|
},
|
|
1664
1670
|
output: JSON_OUTPUT,
|
|
@@ -38,7 +38,7 @@ export function channelForProvider(provider, installedToolIds = [], runnableTool
|
|
|
38
38
|
return {
|
|
39
39
|
kind: 'official-cli', preference, tool: tool.id, label: tool.label,
|
|
40
40
|
detail: tool.id === 'zcode'
|
|
41
|
-
? 'ZCode
|
|
41
|
+
? 'ZCode 已安装,插件可调用其官方编程代理;其 CLI 使用自身配置的默认模型,不能保证与 Harness 建议模型一致。'
|
|
42
42
|
: headless && !runnable
|
|
43
43
|
? `${tool.label} 已安装。分配到该模型的任务会先走官方无界面命令;命令缺失或失败时回退模型目录 API。`
|
|
44
44
|
: `${tool.label} 已安装,插件可托管调用其官方 CLI;Harness 模型目录与厂商 CLI 名称可能不同,团队无法确认映射时使用 CLI 默认模型,实际模型仍须核对运行记录。`,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Latest version" lookup for the official tools (health card hint only).
|
|
3
|
+
*
|
|
4
|
+
* The plugin no longer pins tool versions: installs take each vendor's latest
|
|
5
|
+
* release, and the health check shows the installed version next to the latest
|
|
6
|
+
* one with an "update available" hint. Lookups are cheap metadata reads (the
|
|
7
|
+
* npm registry's `<package>/latest` document, ZCode's official download page),
|
|
8
|
+
* cached for about 12 hours in memory and in `model-router/latest-versions.json`.
|
|
9
|
+
* Every failure is non-fatal: offline, the last known value (or nothing) is used.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
12
|
+
import { dirname, join } from 'node:path'
|
|
13
|
+
import { resolveStateHome } from './router-state.mjs'
|
|
14
|
+
import { OFFICIAL_TOOLS } from './official-tool-registry.mjs'
|
|
15
|
+
|
|
16
|
+
export const LATEST_CACHE_MS = 12 * 60 * 60_000
|
|
17
|
+
export const LATEST_FAILURE_RETRY_MS = 30 * 60_000
|
|
18
|
+
export const LATEST_TIMEOUT_MS = 5_000
|
|
19
|
+
export const NPM_REGISTRY = 'https://registry.npmjs.org'
|
|
20
|
+
export const ZCODE_DOWNLOAD_PAGE = 'https://zcode.z.ai/en/docs/install'
|
|
21
|
+
const MAX_DOCUMENT_BYTES = 2_000_000
|
|
22
|
+
const VERSION = /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})(?:-[0-9A-Za-z.-]{1,64})?$/
|
|
23
|
+
|
|
24
|
+
export function isReleaseVersion(value) {
|
|
25
|
+
return typeof value === 'string' && VERSION.test(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Numeric comparison of x.y.z[-pre]; a prerelease sorts below its release. */
|
|
29
|
+
export function compareReleaseVersions(left, right) {
|
|
30
|
+
const parse = value => {
|
|
31
|
+
const match = VERSION.exec(String(value ?? ''))
|
|
32
|
+
return match ? { parts: match.slice(1, 4).map(Number), pre: String(value).includes('-') } : null
|
|
33
|
+
}
|
|
34
|
+
const a = parse(left)
|
|
35
|
+
const b = parse(right)
|
|
36
|
+
if (!a || !b) return null
|
|
37
|
+
for (let index = 0; index < 3; index += 1) {
|
|
38
|
+
if (a.parts[index] !== b.parts[index]) return a.parts[index] > b.parts[index] ? 1 : -1
|
|
39
|
+
}
|
|
40
|
+
if (a.pre !== b.pre) return a.pre ? -1 : 1
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** True only when both versions parse and the latest is strictly newer. */
|
|
45
|
+
export function updateAvailable(installed, latest) {
|
|
46
|
+
return compareReleaseVersions(latest, installed) === 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function latestCachePath(env = process.env) {
|
|
50
|
+
return join(resolveStateHome(env), 'model-router', 'latest-versions.json')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The newest Windows x64 installer linked from ZCode's official install page. */
|
|
54
|
+
export function parseZCodeDownloadPage(html, { platform = 'windows-x64' } = {}) {
|
|
55
|
+
const pattern = /https:\/\/cdn-zcode\.z\.ai\/zcode\/electron\/releases\/(\d+\.\d+\.\d+)\/([a-z0-9-]+)\/(ZCode-\1-[a-z0-9-]+\.(?:exe|dmg|AppImage))/g
|
|
56
|
+
let best = null
|
|
57
|
+
for (const match of String(html ?? '').matchAll(pattern)) {
|
|
58
|
+
if (match[2] !== platform) continue
|
|
59
|
+
if (!best || compareReleaseVersions(match[1], best.version) === 1) {
|
|
60
|
+
best = { version: match[1], url: `https://cdn-zcode.z.ai/zcode/electron/releases/${match[1]}/${match[2]}/${match[3]}` }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return best
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function boundedText(fetchImpl, url, timeoutMs, accept) {
|
|
67
|
+
const response = await fetchImpl(url, {
|
|
68
|
+
headers: { accept },
|
|
69
|
+
redirect: 'follow',
|
|
70
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
71
|
+
})
|
|
72
|
+
if (!response?.ok) throw new Error(`HTTP ${response?.status ?? '失败'}`)
|
|
73
|
+
const declared = Number(response.headers?.get?.('content-length'))
|
|
74
|
+
if (Number.isFinite(declared) && declared > MAX_DOCUMENT_BYTES) throw new Error('响应过大')
|
|
75
|
+
const body = await response.text()
|
|
76
|
+
if (body.length > MAX_DOCUMENT_BYTES) throw new Error('响应过大')
|
|
77
|
+
return body
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Where to look up a tool's latest release; null when there is no cheap source. */
|
|
81
|
+
export function latestSourceFor(tool) {
|
|
82
|
+
if (!tool || tool.unsupported) return null
|
|
83
|
+
if (tool.manager === 'npm' && typeof tool.package === 'string') return { kind: 'npm', package: tool.package }
|
|
84
|
+
if (tool.id === 'zcode') return { kind: 'zcode-download-page' }
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function lookupSource(source, { fetchImpl, timeoutMs }) {
|
|
89
|
+
if (source.kind === 'npm') {
|
|
90
|
+
const body = JSON.parse(await boundedText(fetchImpl, `${NPM_REGISTRY}/${source.package}/latest`, timeoutMs, 'application/json'))
|
|
91
|
+
if (body?.name !== source.package || !isReleaseVersion(body?.version)) throw new Error('npm 元数据格式无效')
|
|
92
|
+
return { version: body.version, source: 'npm' }
|
|
93
|
+
}
|
|
94
|
+
if (source.kind === 'zcode-download-page') {
|
|
95
|
+
const found = parseZCodeDownloadPage(await boundedText(fetchImpl, ZCODE_DOWNLOAD_PAGE, timeoutMs, 'text/html'))
|
|
96
|
+
if (!found) throw new Error('官方下载页没有 Windows x64 安装包链接')
|
|
97
|
+
return { version: found.version, url: found.url, source: 'zcode-download-page' }
|
|
98
|
+
}
|
|
99
|
+
throw new Error('不支持的版本来源')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Latest-version lookups with a shared cache. Every dependency is injectable so
|
|
104
|
+
* tests can run offline with a mocked fetch and clock.
|
|
105
|
+
*/
|
|
106
|
+
export function createLatestVersions({
|
|
107
|
+
fetchImpl = (...args) => globalThis.fetch(...args),
|
|
108
|
+
now = Date.now,
|
|
109
|
+
cachePath = () => latestCachePath(),
|
|
110
|
+
ttlMs = LATEST_CACHE_MS,
|
|
111
|
+
failureRetryMs = LATEST_FAILURE_RETRY_MS,
|
|
112
|
+
timeoutMs = LATEST_TIMEOUT_MS,
|
|
113
|
+
persist = true,
|
|
114
|
+
} = {}) {
|
|
115
|
+
let entries = null
|
|
116
|
+
const inflight = new Map()
|
|
117
|
+
|
|
118
|
+
async function load() {
|
|
119
|
+
if (entries) return entries
|
|
120
|
+
entries = {}
|
|
121
|
+
if (!persist) return entries
|
|
122
|
+
try {
|
|
123
|
+
const saved = JSON.parse(await readFile(cachePath(), 'utf8'))
|
|
124
|
+
if (saved && typeof saved.entries === 'object' && !Array.isArray(saved.entries)) {
|
|
125
|
+
for (const [id, entry] of Object.entries(saved.entries)) {
|
|
126
|
+
if (entry && typeof entry === 'object' && (entry.version === null || isReleaseVersion(entry.version))) entries[id] = entry
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch { /* first run or unreadable cache: start empty */ }
|
|
130
|
+
return entries
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function save() {
|
|
134
|
+
if (!persist) return
|
|
135
|
+
try {
|
|
136
|
+
const path = cachePath()
|
|
137
|
+
await mkdir(dirname(path), { recursive: true })
|
|
138
|
+
const temporary = `${path}.${process.pid}.tmp`
|
|
139
|
+
await writeFile(temporary, `${JSON.stringify({ version: 1, entries }, null, 2)}\n`, 'utf8')
|
|
140
|
+
await rename(temporary, path)
|
|
141
|
+
} catch { /* the in-memory cache still serves this process */ }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const view = (entry, stale = false) => entry ? {
|
|
145
|
+
version: entry.version ?? null,
|
|
146
|
+
checkedAt: entry.checkedAt ?? null,
|
|
147
|
+
source: entry.source ?? null,
|
|
148
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
149
|
+
...(entry.error ? { error: entry.error } : {}),
|
|
150
|
+
...(stale ? { stale: true } : {}),
|
|
151
|
+
} : null
|
|
152
|
+
|
|
153
|
+
async function lookup(tool, { fresh = false } = {}) {
|
|
154
|
+
const source = latestSourceFor(tool)
|
|
155
|
+
if (!source) return null
|
|
156
|
+
const cache = await load()
|
|
157
|
+
const entry = cache[tool.id]
|
|
158
|
+
const at = now()
|
|
159
|
+
if (!fresh && entry) {
|
|
160
|
+
if (!entry.error && at - (entry.checkedAt ?? 0) < ttlMs) return view(entry)
|
|
161
|
+
if (entry.error && at - (entry.failedAt ?? 0) < failureRetryMs) return view(entry, true)
|
|
162
|
+
}
|
|
163
|
+
if (inflight.has(tool.id)) return inflight.get(tool.id)
|
|
164
|
+
const pending = (async () => {
|
|
165
|
+
try {
|
|
166
|
+
const found = await lookupSource(source, { fetchImpl, timeoutMs })
|
|
167
|
+
cache[tool.id] = { version: found.version, checkedAt: now(), source: found.source, ...(found.url ? { url: found.url } : {}) }
|
|
168
|
+
await save()
|
|
169
|
+
return view(cache[tool.id])
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const message = String(error?.name === 'TimeoutError' ? '查询超时' : error?.message ?? error).slice(0, 160)
|
|
172
|
+
// Keep the last good version; remember the failure to avoid retrying every call offline.
|
|
173
|
+
cache[tool.id] = { version: entry?.version ?? null, checkedAt: entry?.checkedAt ?? null, source: entry?.source ?? null,
|
|
174
|
+
...(entry?.url ? { url: entry.url } : {}), error: message, failedAt: now() }
|
|
175
|
+
await save()
|
|
176
|
+
return view(cache[tool.id], true)
|
|
177
|
+
} finally {
|
|
178
|
+
inflight.delete(tool.id)
|
|
179
|
+
}
|
|
180
|
+
})()
|
|
181
|
+
inflight.set(tool.id, pending)
|
|
182
|
+
return pending
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
lookup,
|
|
187
|
+
/** Look up every registry tool in parallel; never throws. */
|
|
188
|
+
async lookupAll(tools = OFFICIAL_TOOLS, options = {}) {
|
|
189
|
+
const results = await Promise.all(tools.map(async tool => {
|
|
190
|
+
try { return [tool.id, await lookup(tool, options)] } catch { return [tool.id, null] }
|
|
191
|
+
}))
|
|
192
|
+
return Object.fromEntries(results)
|
|
193
|
+
},
|
|
194
|
+
/** Test helper and cache inspection. */
|
|
195
|
+
async snapshot() { return { ...(await load()) } },
|
|
196
|
+
reset() { entries = null; inflight.clear() },
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const latestVersions = createLatestVersions()
|