@ljwei-stak/dsh-model-router 0.13.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.
Files changed (37) hide show
  1. package/.dsh-plugin/client.js +3092 -0
  2. package/.dsh-plugin/index.mjs +1651 -0
  3. package/.dsh-plugin/official-tools-remote-service.mjs +104 -0
  4. package/.dsh-plugin/shared/harness-plan.mjs +179 -0
  5. package/.dsh-plugin/shared/livebench.mjs +264 -0
  6. package/.dsh-plugin/shared/model-profiles.mjs +142 -0
  7. package/.dsh-plugin/shared/official-team-runtime.mjs +411 -0
  8. package/.dsh-plugin/shared/official-tool-executor.mjs +801 -0
  9. package/.dsh-plugin/shared/official-tool-registry.mjs +138 -0
  10. package/.dsh-plugin/shared/official-tools-remote.mjs +173 -0
  11. package/.dsh-plugin/shared/official-tools-runtime.mjs +642 -0
  12. package/.dsh-plugin/shared/router-state.mjs +207 -0
  13. package/.dsh-plugin/shared/router.mjs +1134 -0
  14. package/.dsh-plugin/shared/routing-presets.mjs +49 -0
  15. package/.dsh-plugin/shared/run-ledger.mjs +348 -0
  16. package/.dsh-plugin/shared/security-boundaries.mjs +54 -0
  17. package/.dsh-plugin/shared/subscription-billing.mjs +340 -0
  18. package/.dsh-plugin/shared/task-executors.mjs +1154 -0
  19. package/.dsh-plugin/shared/tool-health.mjs +311 -0
  20. package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +308 -0
  21. package/.dsh-plugin/shared/vendor-minimax-adapter.mjs +247 -0
  22. package/.dsh-plugin/shared/zcode-bundle.mjs +208 -0
  23. package/.dsh-plugin/shared/zcode-installer.mjs +247 -0
  24. package/CHANGELOG.md +36 -0
  25. package/INSTALLATION_GUIDE.zh.md +134 -0
  26. package/LICENSE +21 -0
  27. package/MIGRATION.md +53 -0
  28. package/README.i18n.yaml +3 -0
  29. package/README.md +424 -0
  30. package/README.zh.md +413 -0
  31. package/cordis.patch.yml +12 -0
  32. package/docs/assets/candidate-pruning.svg +80 -0
  33. package/docs/assets/desktop-official-tools-0.9.0.png +0 -0
  34. package/docs/assets/router-only-0.12.0.png +0 -0
  35. package/docs/assets/routing-workflow.svg +96 -0
  36. package/docs/assets/workbench-usage.svg +119 -0
  37. package/package.json +161 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Fixed registry of official model CLI tools, shared by the Host runtime
3
+ * (probe + install) and the Desktop panel (display). Pure data: no Node or
4
+ * browser APIs, so both sides and the test suite import it unchanged.
5
+ *
6
+ * The registry is fail-closed by design: versions and installer sources are
7
+ * pinned to the releases checked for this registry, and clients can only name a
8
+ * registry id — never an arbitrary package or command.
9
+ */
10
+
11
+ export const OFFICIAL_TOOLS = Object.freeze([
12
+ Object.freeze({
13
+ id: 'kimi-code',
14
+ label: 'Kimi Code · Node',
15
+ vendor: 'Moonshot AI',
16
+ purpose: 'Kimi 官方编程 CLI,提供 kimi 命令与 ACP 会话。',
17
+ package: '@moonshot-ai/kimi-code',
18
+ version: '2.1.1',
19
+ manager: 'npm',
20
+ installArgs: ['install', '-g', '@moonshot-ai/kimi-code@2.1.1', '--registry=https://registry.npmjs.org/'],
21
+ probeExecutables: ['kimi'],
22
+ probeNote: 'kimi 与旧 Python 版 kimi-cli 同名;请核对可执行文件来源和版本。',
23
+ providerHints: ['moonshot', 'kimi'],
24
+ }),
25
+ Object.freeze({
26
+ id: 'claude-code',
27
+ label: 'Claude Code',
28
+ vendor: 'Anthropic',
29
+ purpose: 'Anthropic 官方编程 CLI,提供 claude 命令。',
30
+ package: '@anthropic-ai/claude-code',
31
+ version: '2.1.283',
32
+ manager: 'npm',
33
+ installArgs: ['install', '-g', '@anthropic-ai/claude-code@2.1.283', '--registry=https://registry.npmjs.org/'],
34
+ probeExecutables: ['claude'],
35
+ providerHints: ['anthropic', 'claude'],
36
+ }),
37
+ Object.freeze({
38
+ id: 'codex',
39
+ label: 'Codex CLI',
40
+ vendor: 'OpenAI',
41
+ purpose: 'OpenAI 官方编程 CLI,提供 codex 命令。',
42
+ package: '@openai/codex',
43
+ version: '0.157.1',
44
+ installArgs: ['install', '-g', '@openai/codex@0.157.1', '--registry=https://registry.npmjs.org/'],
45
+ manager: 'npm',
46
+ probeExecutables: ['codex'],
47
+ probeNote: 'Codex 版本横幅由适配层宽匹配;安装时固定版本。',
48
+ providerHints: ['openai', 'gpt', 'codex'],
49
+ }),
50
+ Object.freeze({
51
+ id: 'minimax-code',
52
+ label: 'MiniMax Code',
53
+ vendor: 'MiniMax',
54
+ purpose: 'MiniMax 官方编程 CLI,提供 mcode 命令。',
55
+ package: '@minimax-ai/code',
56
+ version: '0.5.5',
57
+ manager: 'npm',
58
+ installArgs: ['install', '-g', '@minimax-ai/code@0.5.5', '--registry=https://registry.npmjs.org/', '--ignore-scripts=false', '--include=optional', '--allow-scripts=@minimax-ai/code,better-sqlite3'],
59
+ probeExecutables: ['mcode'],
60
+ providerHints: ['minimax'],
61
+ }),
62
+ Object.freeze({
63
+ id: 'mimo-code',
64
+ label: 'MiMo Code',
65
+ vendor: 'XiaoMi',
66
+ purpose: '小米 MiMo 官方编程 CLI,提供 mimo 命令。',
67
+ package: '@mimo-ai/cli',
68
+ version: '0.1.15',
69
+ manager: 'npm',
70
+ installArgs: ['install', '-g', '@mimo-ai/cli@0.1.15', '--registry=https://registry.npmjs.org/'],
71
+ probeExecutables: ['mimo'],
72
+ providerHints: ['mimo', 'xiaomi'],
73
+ }),
74
+ Object.freeze({
75
+ id: 'grok-build',
76
+ label: 'Grok Build',
77
+ vendor: 'xAI',
78
+ purpose: 'xAI 官方编程 CLI,提供 grok 命令与 ACP 会话。',
79
+ package: '@xai-official/grok',
80
+ version: '1.0.41',
81
+ manager: 'npm',
82
+ installArgs: ['install', '-g', '@xai-official/grok@1.0.41', '--registry=https://registry.npmjs.org/'],
83
+ probeExecutables: ['grok'],
84
+ providerHints: ['xai', 'grok'],
85
+ }),
86
+ Object.freeze({
87
+ id: 'gemini',
88
+ label: 'Gemini CLI',
89
+ vendor: 'Google',
90
+ purpose: 'Google 官方 Gemini CLI,无界面模式使用 gemini -p。',
91
+ package: '@google/gemini-cli',
92
+ version: '0.62.0',
93
+ manager: 'npm',
94
+ installArgs: ['install', '-g', '@google/gemini-cli@0.62.0', '--registry=https://registry.npmjs.org/'],
95
+ probeExecutables: ['gemini'],
96
+ providerHints: ['gemini', 'google'],
97
+ // Headless runs go through the task adapter. The signed sandbox runner does
98
+ // not launch this CLI; a missing or failed process falls back to the API.
99
+ headlessAdapter: true,
100
+ }),
101
+ Object.freeze({
102
+ id: 'zcode',
103
+ label: 'ZCode',
104
+ vendor: 'Z.ai',
105
+ purpose: '智谱官方 ZCode 桌面版,内含 GLM 编程代理。Windows 安装器可选择 D 盘目录。',
106
+ version: '3.14.3',
107
+ manager: 'signed-windows-installer',
108
+ installArgs: [],
109
+ probeExecutables: [],
110
+ probeNote: '检测经过有效签名的 ZCode.exe 和同目录 GLM 资源;桌面安装器需人工选择安装位置。',
111
+ providerHints: ['zai', 'z.ai', 'zcode', 'glm', 'zhipu', 'bigmodel'],
112
+ }),
113
+ ])
114
+
115
+ const TOOL_BY_ID = new Map(OFFICIAL_TOOLS.map(tool => [tool.id, tool]))
116
+
117
+ export function getOfficialTool(id) {
118
+ return TOOL_BY_ID.get(String(id ?? '').trim()) ?? null
119
+ }
120
+
121
+ /** Conservative keyword mapping from a catalog provider id/name to a tool. */
122
+ export function toolForProvider(provider) {
123
+ const value = String(provider ?? '').trim().toLowerCase()
124
+ if (!value) return null
125
+ for (const tool of OFFICIAL_TOOLS) {
126
+ if (tool.providerHints.some(hint => value.includes(hint))) return tool
127
+ }
128
+ return null
129
+ }
130
+
131
+ /** Human-readable install command line for one registry entry. */
132
+ export function installCommandLine(tool) {
133
+ if (!tool || tool.unsupported) return null
134
+ if (tool.manager === 'signed-windows-installer') return '打开官方签名安装器(选择安装目录)'
135
+ return tool.manager === 'npm'
136
+ ? `npm ${tool.installArgs.join(' ')}`
137
+ : `${tool.manager} ${tool.installArgs.join(' ')}`
138
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Narrow Typert Remote contract for the official desktop tool installer.
3
+ *
4
+ * The official Client Gateway mounts this contribution with remote.$mount;
5
+ * the Host Typert registry owns the matching strict descriptors. No endpoint
6
+ * accepts a command, package name, URL, argument vector, or executable path.
7
+ */
8
+ import { getOfficialTool } from './official-tool-registry.mjs'
9
+
10
+ export const OFFICIAL_TOOLS_REMOTE_PACKAGE = '@ljwei-stak/dsh-model-router'
11
+ export const OFFICIAL_TOOLS_REMOTE_NAMESPACE = 'modelRouterOfficialTools'
12
+
13
+ function strictCodec(typeSymbol, parse) {
14
+ return Object.freeze({ mode: 'strict', typeSymbol, create: () => ({ parse }) })
15
+ }
16
+
17
+ function plainObject(value, subject) {
18
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
19
+ throw new TypeError(`${subject} must be an object`)
20
+ }
21
+ return value
22
+ }
23
+
24
+ const toolIdCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolId`, value => {
25
+ if (typeof value !== 'string' || !getOfficialTool(value)) {
26
+ throw new TypeError('toolId must name a fixed official tool')
27
+ }
28
+ return value
29
+ })
30
+
31
+ const listResultCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolList`, value => {
32
+ const result = plainObject(value, 'tool list result')
33
+ if (!Array.isArray(result.tools)) throw new TypeError('tool list result needs a tools array')
34
+ return result
35
+ })
36
+
37
+ const installResultCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolInstall`, value => {
38
+ const result = plainObject(value, 'install result')
39
+ if (typeof result.accepted !== 'boolean') throw new TypeError('install result needs accepted')
40
+ if (result.accepted && !result.job) throw new TypeError('accepted install needs a job')
41
+ if (!result.accepted && typeof result.error !== 'string') throw new TypeError('refused install needs an error')
42
+ return result
43
+ })
44
+
45
+ const statusResultCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolStatus`, value => {
46
+ const result = plainObject(value, 'install status result')
47
+ if (result.job !== null && (typeof result.job !== 'object' || Array.isArray(result.job))) {
48
+ throw new TypeError('install status result needs a job or null')
49
+ }
50
+ return result
51
+ })
52
+
53
+ const anyObjectCodec = name => strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#${name}`, value => plainObject(value, name))
54
+
55
+ const freshCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#HealthFresh`, value => {
56
+ if (typeof value !== 'boolean') throw new TypeError('fresh must be a boolean')
57
+ return value
58
+ })
59
+
60
+ const idText = (value, subject) => {
61
+ if (typeof value !== 'string' || !/^[A-Za-z0-9._:-]{1,80}$/.test(value)) throw new TypeError(`${subject} must be a short id`)
62
+ return value
63
+ }
64
+
65
+ const optionalRouteText = (value, subject) => {
66
+ if (value === undefined || value === null || value === '') return undefined
67
+ if (typeof value !== 'string' || value.length > 240 || value.includes('\0')) throw new TypeError(`${subject} is invalid`)
68
+ return value
69
+ }
70
+
71
+ const rateRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RateRequest`, value => {
72
+ const request = plainObject(value, 'rate request')
73
+ if (!['up', 'down', 'clear'].includes(request.rating)) throw new TypeError('rating must be up, down or clear')
74
+ return { runId: idText(request.runId, 'runId'), packageId: idText(request.packageId, 'packageId'), rating: request.rating }
75
+ })
76
+
77
+ const rerunRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RerunRequest`, value => {
78
+ const request = plainObject(value, 'rerun request')
79
+ const provider = optionalRouteText(request.provider, 'provider')
80
+ const model = optionalRouteText(request.model, 'model')
81
+ if (Boolean(provider) !== Boolean(model)) throw new TypeError('provider and model must be supplied together')
82
+ return {
83
+ runId: idText(request.runId, 'runId'), packageId: idText(request.packageId, 'packageId'),
84
+ ...(provider ? { provider, model } : {}),
85
+ confirmOverBudget: request.confirmOverBudget === true,
86
+ confirmWrite: request.confirmWrite === true,
87
+ ...(['api', 'subscription', 'cancel'].includes(request.subscriptionChoice) ? { subscriptionChoice: request.subscriptionChoice } : {}),
88
+ }
89
+ })
90
+
91
+ export const RUN_CONFIRMATION_CODES = Object.freeze(['workspace-write', 'rerun-write', 'subscription-api', 'over-budget', 'unsandboxed'])
92
+ const MAX_RUN_TASK_CHARS = 200_000
93
+
94
+ /**
95
+ * Workbench "start a run" request. The workspace is a user-typed absolute
96
+ * directory (validated again on the Host); the run itself is read-only.
97
+ */
98
+ export function parseRunRequest(value) {
99
+ const request = plainObject(value, 'run request')
100
+ if (typeof request.task !== 'string' || !request.task.trim() || request.task.length > MAX_RUN_TASK_CHARS) {
101
+ throw new TypeError('task must be a non-empty string')
102
+ }
103
+ const provider = optionalRouteText(request.provider, 'provider')
104
+ const model = optionalRouteText(request.model, 'model')
105
+ if (Boolean(provider) !== Boolean(model)) throw new TypeError('provider and model must be supplied together')
106
+ const workspace = request.workspace === undefined || request.workspace === null || request.workspace === '' ? undefined : request.workspace
107
+ if (workspace !== undefined && (typeof workspace !== 'string' || workspace.length > 4_096 || workspace.includes('\0'))) throw new TypeError('workspace is invalid')
108
+ if (request.budgetUsd !== undefined && (typeof request.budgetUsd !== 'number' || !Number.isFinite(request.budgetUsd) || request.budgetUsd < 0)) {
109
+ throw new TypeError('budgetUsd must be a non-negative number')
110
+ }
111
+ const confirmed = Array.isArray(request.confirmedReasons) ? request.confirmedReasons : []
112
+ if (confirmed.length > RUN_CONFIRMATION_CODES.length || confirmed.some(code => !RUN_CONFIRMATION_CODES.includes(code))) {
113
+ throw new TypeError('confirmedReasons contains an unknown reason')
114
+ }
115
+ return {
116
+ task: request.task,
117
+ ...(provider ? { provider, model } : {}),
118
+ ...(['single', 'team'].includes(request.planMode) ? { planMode: request.planMode } : {}),
119
+ ...(['economy', 'balanced', 'quality'].includes(request.preset) ? { preset: request.preset } : {}),
120
+ ...(request.budgetUsd !== undefined ? { budgetUsd: request.budgetUsd } : {}),
121
+ ...(workspace ? { workspace } : {}),
122
+ confirmedReasons: [...new Set(confirmed)],
123
+ }
124
+ }
125
+
126
+ const runRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RunRequest`, parseRunRequest)
127
+
128
+ function descriptor(method, parameters, result) {
129
+ return Object.freeze({
130
+ id: `${OFFICIAL_TOOLS_REMOTE_PACKAGE}#${OFFICIAL_TOOLS_REMOTE_NAMESPACE}/${method}`,
131
+ service: OFFICIAL_TOOLS_REMOTE_NAMESPACE,
132
+ namespace: OFFICIAL_TOOLS_REMOTE_NAMESPACE,
133
+ method,
134
+ invocation: { kind: 'direct' },
135
+ parameters,
136
+ result,
137
+ })
138
+ }
139
+
140
+ const toolIdParameter = Object.freeze({
141
+ name: 'toolId', wire: 'toolId', source: 'json', codec: toolIdCodec,
142
+ })
143
+ const jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: 'json', codec })
144
+
145
+ export const OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
146
+ descriptor('list', [], listResultCodec),
147
+ descriptor('installTool', [toolIdParameter], installResultCodec),
148
+ descriptor('cancel', [toolIdParameter], installResultCodec),
149
+ descriptor('status', [toolIdParameter], statusResultCodec),
150
+ // Workbench: onboarding health check, run ledger, ratings, step retry, security boundaries.
151
+ descriptor('health', [jsonParameter('fresh', freshCodec)], anyObjectCodec('HealthReport')),
152
+ descriptor('completeOnboarding', [], anyObjectCodec('OnboardingState')),
153
+ descriptor('ledger', [], anyObjectCodec('RunLedger')),
154
+ descriptor('rateResult', [jsonParameter('request', rateRequestCodec)], anyObjectCodec('RateResult')),
155
+ descriptor('rerunStep', [jsonParameter('request', rerunRequestCodec)], anyObjectCodec('RerunResult')),
156
+ descriptor('boundaries', [], anyObjectCodec('SecurityBoundaries')),
157
+ // Workbench "start a run": plan preview with cost estimate and confirmation reasons, then execute.
158
+ descriptor('previewRun', [jsonParameter('request', runRequestCodec)], anyObjectCodec('RunPreview')),
159
+ descriptor('startRun', [jsonParameter('request', runRequestCodec)], anyObjectCodec('RunStarted')),
160
+ ])
161
+
162
+ export const OFFICIAL_TOOLS_CLIENT_REMOTE = Object.freeze({
163
+ package: OFFICIAL_TOOLS_REMOTE_PACKAGE,
164
+ descriptors: OFFICIAL_TOOLS_REMOTE_DESCRIPTORS,
165
+ })
166
+
167
+ export const OFFICIAL_TOOLS_HOST_TYPERT = Object.freeze({
168
+ package: OFFICIAL_TOOLS_REMOTE_PACKAGE,
169
+ face: 'host',
170
+ schemas: [],
171
+ invocations: OFFICIAL_TOOLS_REMOTE_DESCRIPTORS,
172
+ model: { services: [], events: [], objects: [] },
173
+ })