@dsh-plus/llm-pi 0.1.27 → 0.1.29
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/lib/client.js +126 -108
- package/lib/index.js +352 -147
- package/package.json +27 -27
- package/src/client/api.ts +8 -0
- package/src/client/card.tsx +3 -0
- package/src/client/constants.ts +12 -52
- package/src/client/fields.tsx +58 -0
- package/src/client/i18n.ts +2 -0
- package/src/client/views/compat.tsx +16 -1
- package/src/compat-gates.ts +265 -0
- package/src/compat.ts +70 -141
- package/src/config-api.ts +29 -0
- package/src/resolve-dsh.ts +54 -3
package/src/compat.ts
CHANGED
|
@@ -1,161 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 逐协议 compat
|
|
2
|
+
* 逐协议 compat 校验:门控表与取值约束**从官方 dsh-llm-pi-ai 安装副本自动继承**
|
|
3
|
+
* (见 compat-gates.ts),不再手抄镜像。
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 形态不携带 src/、包根也不导出该符号),插件在 dsh 树(npm 布局)与 vendored
|
|
10
|
-
* 兜底两条路径都无法静态引用,故本文件按官方 catalog.ts 逐字段镜像门控表
|
|
11
|
-
* (字段全集与分型随 pi-ai 升级同步维护,官方以 Record<keyof Compat> 编译期
|
|
12
|
-
* 约束漂移,插件以本表 + 注释人工同步)。
|
|
5
|
+
* 语义:官方门控表是 compat 可配性的唯一事实源——按协议分型(offer/withhold),
|
|
6
|
+
* withhold 字段(官方内置目录已为对应厂商设置,如 openRouterRouting/zaiToolStream/
|
|
7
|
+
* sendSessionAffinityHeaders/supportsToolSearch/supportsMidConvoEffort 等)写时
|
|
8
|
+
* 拒绝并提示以目录 provider 名为 route;未知键拒绝;无值键(null/undefined)拒绝
|
|
9
|
+
* (对齐官方 assertOfferedCompatFields:"写了但没生效"的表面状态不允许)。
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - 原表内 openRouterRouting/vercelGatewayRouting/zaiToolStream/
|
|
18
|
-
* sendSessionAffinityHeaders/deferredToolsMode/sessionAffinityFormat/
|
|
19
|
-
* supportsOpenAIGrammarTools(completions/responses)与 supportsToolSearch/
|
|
20
|
-
* supportsExplicitPromptCacheMode(responses)、supportsToolReferences
|
|
21
|
-
* (anthropic)改为 withhold → 写时拒绝;
|
|
22
|
-
* - responses 不再 offer sessionAffinityFormat(withhold)。
|
|
11
|
+
* 历史:本表曾是手抄镜像,0.1.5-rc.1 官方扩容 offer 字段而旧表漏收,导致官方可配
|
|
12
|
+
* 字段被本插件**误拒**(静默功能缺失、无任何报错)。现改为现场推导:官方新增字段
|
|
13
|
+
* 即自动可用,`tests/compat-gates.test.ts` 直读官方 bundle 守门推导正确性。
|
|
23
14
|
*
|
|
24
|
-
* pi-ai 侧消费语义:getCompat 逐字段 `??` 覆盖 detectCompat 的
|
|
25
|
-
*
|
|
15
|
+
* pi-ai 侧消费语义:getCompat 逐字段 `??` 覆盖 detectCompat 的 baseURL/名称猜测;
|
|
16
|
+
* undefined 视为未设置(无法显式清空检测值)。
|
|
26
17
|
* @module llm-pi/compat
|
|
27
18
|
*/
|
|
19
|
+
import type { CompatDisposition, CompatTable, CompatValue } from './compat-gates.ts'
|
|
20
|
+
import { FALLBACK_TABLE } from './compat-gates.ts'
|
|
28
21
|
import type { ProtocolId } from './config.ts'
|
|
29
22
|
|
|
30
|
-
type CompatValue
|
|
31
|
-
type CompatDisposition = 'offer' | 'withhold'
|
|
23
|
+
export type { CompatDisposition, CompatValue } from './compat-gates.ts'
|
|
32
24
|
|
|
33
25
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
26
|
+
* 生效门控表:由 resolve-dsh 的套件加载流程经 {@link installCompatTable} 注入
|
|
27
|
+
* (与 PiAiAdapter 同源——即 dsh 树或 vendored 副本里**正在运行**的那份官方代码)。
|
|
28
|
+
* 未注入时用 FALLBACK_TABLE(纯函数测试路径/极端启动顺序下的保守兜底)。
|
|
36
29
|
*/
|
|
37
|
-
|
|
38
|
-
supportsStore: 'offer',
|
|
39
|
-
supportsDeveloperRole: 'offer',
|
|
40
|
-
supportsReasoningEffort: 'offer',
|
|
41
|
-
supportsUsageInStreaming: 'offer',
|
|
42
|
-
supportsFinishReason: 'offer',
|
|
43
|
-
maxTokensField: 'offer',
|
|
44
|
-
requiresToolResultName: 'offer',
|
|
45
|
-
requiresAssistantAfterToolResult: 'offer',
|
|
46
|
-
requiresThinkingAsText: 'offer',
|
|
47
|
-
requiresReasoningContentOnAssistantMessages: 'offer',
|
|
48
|
-
thinkingFormat: 'offer',
|
|
49
|
-
chatTemplateKwargs: 'offer',
|
|
50
|
-
chatTemplateArgs: 'offer',
|
|
51
|
-
supportsThinkingTokenBudget: 'offer',
|
|
52
|
-
supportsStrictMode: 'offer',
|
|
53
|
-
cacheControlFormat: 'offer',
|
|
54
|
-
supportsLongCacheRetention: 'offer',
|
|
55
|
-
openRouterRouting: 'withhold',
|
|
56
|
-
vercelGatewayRouting: 'withhold',
|
|
57
|
-
zaiToolStream: 'withhold',
|
|
58
|
-
supportsOpenAIGrammarTools: 'withhold',
|
|
59
|
-
sendSessionAffinityHeaders: 'withhold',
|
|
60
|
-
deferredToolsMode: 'withhold',
|
|
61
|
-
sessionAffinityFormat: 'withhold',
|
|
62
|
-
}
|
|
30
|
+
let active: CompatTable = FALLBACK_TABLE
|
|
63
31
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*/
|
|
68
|
-
const RESPONSES_COMPAT_GATE: Readonly<Record<string, CompatDisposition>> = {
|
|
69
|
-
supportsDeveloperRole: 'offer',
|
|
70
|
-
supportsStrictMode: 'offer',
|
|
71
|
-
supportsLongCacheRetention: 'offer',
|
|
72
|
-
sessionAffinityFormat: 'withhold',
|
|
73
|
-
supportsOpenAIGrammarTools: 'withhold',
|
|
74
|
-
supportsAdditionalTools: 'withhold',
|
|
75
|
-
supportsToolSearch: 'withhold',
|
|
76
|
-
supportsExplicitPromptCacheMode: 'withhold',
|
|
32
|
+
/** 注入推导结果(幂等;resolve-dsh 在套件加载后调用一次)。 */
|
|
33
|
+
export function installCompatTable(table: CompatTable): void {
|
|
34
|
+
active = table
|
|
77
35
|
}
|
|
78
36
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
supportsLongCacheRetention: 'offer',
|
|
85
|
-
supportsCacheControlOnTools: 'offer',
|
|
86
|
-
supportsTemperature: 'offer',
|
|
87
|
-
forceAdaptiveThinking: 'offer',
|
|
88
|
-
allowEmptySignature: 'offer',
|
|
89
|
-
supportsStrictTools: 'offer',
|
|
90
|
-
sendSessionAffinityHeaders: 'withhold',
|
|
91
|
-
supportsToolReferences: 'withhold',
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const GATES_BY_PROTOCOL: Record<ProtocolId, Readonly<Record<string, CompatDisposition>>> = {
|
|
95
|
-
'openai-completions': COMPLETIONS_COMPAT_GATE,
|
|
96
|
-
'openai-responses': RESPONSES_COMPAT_GATE,
|
|
97
|
-
'anthropic-messages': ANTHROPIC_COMPAT_GATE,
|
|
37
|
+
/** 当前生效表的来源诊断(状态行/日志)。 */
|
|
38
|
+
export function compatTableInfo(): { source: CompatTable['source']; problem?: string } {
|
|
39
|
+
return active.problem === undefined
|
|
40
|
+
? { source: active.source }
|
|
41
|
+
: { source: active.source, problem: active.problem }
|
|
98
42
|
}
|
|
99
43
|
|
|
100
44
|
/** 某协议某字段的可配性('offer'/'withhold';未列出 = 无此字段)。 */
|
|
101
|
-
export function compatDispositionOf(api:
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 字段取值约束(对齐官方 config.ts compatProfile schema):
|
|
107
|
-
* boolean 字段 → 'boolean';maxTokensField/thinkingFormat/cacheControlFormat
|
|
108
|
-
* → 枚举;chatTemplateKwargs/chatTemplateArgs → 对象。
|
|
109
|
-
*/
|
|
110
|
-
const VALUE_SPECS: Readonly<Record<string, CompatValue>> = {
|
|
111
|
-
supportsStore: 'boolean',
|
|
112
|
-
supportsDeveloperRole: 'boolean',
|
|
113
|
-
supportsReasoningEffort: 'boolean',
|
|
114
|
-
supportsUsageInStreaming: 'boolean',
|
|
115
|
-
supportsFinishReason: 'boolean',
|
|
116
|
-
maxTokensField: ['max_completion_tokens', 'max_tokens'],
|
|
117
|
-
requiresToolResultName: 'boolean',
|
|
118
|
-
requiresAssistantAfterToolResult: 'boolean',
|
|
119
|
-
requiresThinkingAsText: 'boolean',
|
|
120
|
-
requiresReasoningContentOnAssistantMessages: 'boolean',
|
|
121
|
-
thinkingFormat: [
|
|
122
|
-
// 官方 SUPPORTED_THINKING_FORMATS(含 baseten,随 chatTemplateArgs 使用)
|
|
123
|
-
'openai',
|
|
124
|
-
'deepseek',
|
|
125
|
-
'openrouter',
|
|
126
|
-
'together',
|
|
127
|
-
'baseten',
|
|
128
|
-
'zai',
|
|
129
|
-
'qwen',
|
|
130
|
-
'chat-template',
|
|
131
|
-
'qwen-chat-template',
|
|
132
|
-
'string-thinking',
|
|
133
|
-
'ant-ling',
|
|
134
|
-
],
|
|
135
|
-
chatTemplateKwargs: 'object',
|
|
136
|
-
chatTemplateArgs: 'object',
|
|
137
|
-
supportsThinkingTokenBudget: 'boolean',
|
|
138
|
-
supportsStrictMode: 'boolean',
|
|
139
|
-
cacheControlFormat: ['anthropic'],
|
|
140
|
-
supportsLongCacheRetention: 'boolean',
|
|
141
|
-
supportsEagerToolInputStreaming: 'boolean',
|
|
142
|
-
supportsCacheControlOnTools: 'boolean',
|
|
143
|
-
supportsTemperature: 'boolean',
|
|
144
|
-
forceAdaptiveThinking: 'boolean',
|
|
145
|
-
allowEmptySignature: 'boolean',
|
|
146
|
-
supportsStrictTools: 'boolean',
|
|
45
|
+
export function compatDispositionOf(api: string, field: string): CompatDisposition | undefined {
|
|
46
|
+
return active.gates[api]?.[field]
|
|
147
47
|
}
|
|
148
48
|
|
|
149
49
|
/** 某协议全部可配置(offer)的 compat 键(UI 渲染字段组与校验共用)。 */
|
|
150
|
-
export function compatFieldsOf(api: ProtocolId): readonly string[] {
|
|
151
|
-
|
|
50
|
+
export function compatFieldsOf(api: ProtocolId | string): readonly string[] {
|
|
51
|
+
const gate = active.gates[api]
|
|
52
|
+
if (gate === undefined) return []
|
|
53
|
+
return Object.entries(gate).flatMap(([field, disposition]) =>
|
|
152
54
|
disposition === 'offer' ? [field] : [],
|
|
153
55
|
)
|
|
154
56
|
}
|
|
155
57
|
|
|
156
58
|
/** 某协议某 offer 字段的取值约束(UI 渲染开关/下拉用)。 */
|
|
157
|
-
export function compatFieldSpec(api:
|
|
158
|
-
return
|
|
59
|
+
export function compatFieldSpec(api: string, field: string): CompatValue | undefined {
|
|
60
|
+
return active.gates[api]?.[field] === 'offer' ? active.specs[field] : undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 官方声明的全部可配置字段(未知键报错时列出,对齐官方 allOfferedCompatFields)。 */
|
|
64
|
+
function allOfferedFields(): string[] {
|
|
65
|
+
const out = new Set<string>()
|
|
66
|
+
for (const gate of Object.values(active.gates)) {
|
|
67
|
+
for (const [field, disposition] of Object.entries(gate)) {
|
|
68
|
+
if (disposition === 'offer') out.add(field)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [...out]
|
|
159
72
|
}
|
|
160
73
|
|
|
161
74
|
function checkValue(field: string, spec: CompatValue, value: unknown, where: string): void {
|
|
@@ -163,6 +76,19 @@ function checkValue(field: string, spec: CompatValue, value: unknown, where: str
|
|
|
163
76
|
if (typeof value !== 'boolean') throw new Error(`${where}: compat.${field} 必须是布尔值`)
|
|
164
77
|
return
|
|
165
78
|
}
|
|
79
|
+
if (spec === 'integer') {
|
|
80
|
+
// 官方 z.number().step(1):整数(小数/NaN/Infinity 均不合格)
|
|
81
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
82
|
+
throw new Error(`${where}: compat.${field} 必须是整数`)
|
|
83
|
+
}
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (spec === 'number') {
|
|
87
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
88
|
+
throw new Error(`${where}: compat.${field} 必须是数字`)
|
|
89
|
+
}
|
|
90
|
+
return
|
|
91
|
+
}
|
|
166
92
|
if (spec === 'object') {
|
|
167
93
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
168
94
|
throw new Error(`${where}: compat.${field} 必须是对象`)
|
|
@@ -178,10 +104,9 @@ function checkValue(field: string, spec: CompatValue, value: unknown, where: str
|
|
|
178
104
|
|
|
179
105
|
/**
|
|
180
106
|
* 校验一份 compat 字典对指定协议合法(对齐官方门控语义 + schema 值约束):
|
|
181
|
-
* - 未知键/withhold
|
|
107
|
+
* - 未知键/withhold 字段拒绝(官方写时拒绝,替代旧版静默丢弃);
|
|
182
108
|
* - 值类型/枚举按官方 schema 校验;
|
|
183
|
-
* - 无值键(null/undefined)拒绝(官方 assertOfferedCompatFields
|
|
184
|
-
* "写了但没生效" 的表面状态不允许)。
|
|
109
|
+
* - 无值键(null/undefined)拒绝(官方 assertOfferedCompatFields 同款)。
|
|
185
110
|
*/
|
|
186
111
|
export function validateCompat(
|
|
187
112
|
api: string,
|
|
@@ -189,13 +114,13 @@ export function validateCompat(
|
|
|
189
114
|
where: string,
|
|
190
115
|
): void {
|
|
191
116
|
if (compat === undefined) return
|
|
192
|
-
const gate =
|
|
117
|
+
const gate = active.gates[api]
|
|
193
118
|
if (gate === undefined) {
|
|
194
119
|
throw new Error(
|
|
195
|
-
`${where}: 协议 ${JSON.stringify(api)} 无 compat 字段表(支持:${Object.keys(
|
|
120
|
+
`${where}: 协议 ${JSON.stringify(api)} 无 compat 字段表(支持:${Object.keys(active.gates).join(', ')})`,
|
|
196
121
|
)
|
|
197
122
|
}
|
|
198
|
-
const offered = compatFieldsOf(api
|
|
123
|
+
const offered = compatFieldsOf(api)
|
|
199
124
|
for (const [key, value] of Object.entries(compat)) {
|
|
200
125
|
const disposition = gate[key]
|
|
201
126
|
if (disposition !== 'offer') {
|
|
@@ -206,13 +131,17 @@ export function validateCompat(
|
|
|
206
131
|
)
|
|
207
132
|
}
|
|
208
133
|
throw new Error(
|
|
209
|
-
`${where}: compat.${key} 不是 ${api} 协议的合法字段(可配置字段:${offered.join(', ')}
|
|
134
|
+
`${where}: compat.${key} 不是 ${api} 协议的合法字段(可配置字段:${offered.join(', ')};` +
|
|
135
|
+
`官方全部可配字段:${allOfferedFields().join(', ')})`,
|
|
210
136
|
)
|
|
211
137
|
}
|
|
212
138
|
if (value === undefined || value === null) {
|
|
213
139
|
throw new Error(`${where}: compat.${key} 未设置值;给出值或移除该键(留空不会生效)`)
|
|
214
140
|
}
|
|
215
|
-
|
|
141
|
+
// 官方 schema 未给出取值约束的字段(未来新增而推导未覆盖)跳过值校验,
|
|
142
|
+
// 由官方调用链自行裁决——宁可放行也不误拒。
|
|
143
|
+
const spec = active.specs[key]
|
|
144
|
+
if (spec !== undefined) checkValue(key, spec, value, where)
|
|
216
145
|
}
|
|
217
146
|
}
|
|
218
147
|
|
package/src/config-api.ts
CHANGED
|
@@ -11,10 +11,36 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
11
11
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
12
12
|
|
|
13
13
|
import { builtinModelIds } from './catalog/builtin.ts'
|
|
14
|
+
import { compatFieldSpec, compatFieldsOf, compatTableInfo } from './compat.ts'
|
|
15
|
+
import { PROTOCOL_IDS } from './config.ts'
|
|
14
16
|
import type { LlmPiRuntime } from './service.ts'
|
|
15
17
|
|
|
16
18
|
const ROUTE_CATALOG = '/dsh-plus/llm-pi/catalog'
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 服务端推导的 compat 字段表(协议 → 字段 → 取值约束)。浏览器半读不到官方包,
|
|
22
|
+
* 由本端点下发,UI 渲染与服务端校验同源(不再各存一份手抄镜像表)。
|
|
23
|
+
*/
|
|
24
|
+
function compatTablePayload(): {
|
|
25
|
+
fields: Record<string, Record<string, unknown>>
|
|
26
|
+
source: string
|
|
27
|
+
problem?: string
|
|
28
|
+
} {
|
|
29
|
+
const fields: Record<string, Record<string, unknown>> = {}
|
|
30
|
+
for (const api of PROTOCOL_IDS) {
|
|
31
|
+
const perField: Record<string, unknown> = {}
|
|
32
|
+
for (const field of compatFieldsOf(api)) {
|
|
33
|
+
const spec = compatFieldSpec(api, field)
|
|
34
|
+
if (spec !== undefined) perField[field] = spec
|
|
35
|
+
}
|
|
36
|
+
fields[api] = perField
|
|
37
|
+
}
|
|
38
|
+
const info = compatTableInfo()
|
|
39
|
+
return info.problem === undefined
|
|
40
|
+
? { fields, source: info.source }
|
|
41
|
+
: { fields, source: info.source, problem: info.problem }
|
|
42
|
+
}
|
|
43
|
+
|
|
18
44
|
function sendJson(res: ServerResponse, status: number, body: unknown): void {
|
|
19
45
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
20
46
|
res.end(JSON.stringify(body))
|
|
@@ -32,12 +58,14 @@ function handleCatalog(runtime: LlmPiRuntime, req: IncomingMessage, res: ServerR
|
|
|
32
58
|
const url = new URL(req.url ?? '', 'http://localhost')
|
|
33
59
|
const provider = url.searchParams.get('provider') ?? ''
|
|
34
60
|
const source = url.searchParams.get('source') ?? 'builtin'
|
|
61
|
+
const compat = compatTablePayload()
|
|
35
62
|
if (source === 'models-dev') {
|
|
36
63
|
sendJson(res, 200, {
|
|
37
64
|
providers: runtime.modelsDev.providerIds(),
|
|
38
65
|
models: provider.length > 0 ? runtime.modelsDev.modelIds(provider) : [],
|
|
39
66
|
status: runtime.modelsDev.status(),
|
|
40
67
|
kitSource,
|
|
68
|
+
compat,
|
|
41
69
|
})
|
|
42
70
|
return
|
|
43
71
|
}
|
|
@@ -45,6 +73,7 @@ function handleCatalog(runtime: LlmPiRuntime, req: IncomingMessage, res: ServerR
|
|
|
45
73
|
providers: runtime.kit.getBuiltinProviders(),
|
|
46
74
|
models: provider.length > 0 ? builtinModelIds(runtime.kit, provider) : [],
|
|
47
75
|
kitSource,
|
|
76
|
+
compat,
|
|
48
77
|
})
|
|
49
78
|
}
|
|
50
79
|
|
package/src/resolve-dsh.ts
CHANGED
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
* (auth-inline.ts,recordKeyFor 自包根导出同源注入)——形状自检兜底。
|
|
22
22
|
* @module llm-pi/resolve-dsh
|
|
23
23
|
*/
|
|
24
|
-
import { existsSync, realpathSync } from 'node:fs'
|
|
24
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
25
25
|
import { dirname, join } from 'node:path'
|
|
26
|
-
import { pathToFileURL } from 'node:url'
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
27
27
|
import type { Context } from '@deepseek-ai/cordis'
|
|
28
28
|
import type { getOrCreateAnonymousUserId as getAnonIdType } from '@deepseek-ai/dsh-anonymous-user-id'
|
|
29
29
|
import * as vendoredAnonId from '@deepseek-ai/dsh-anonymous-user-id'
|
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
authContextFrom as inlineAuthContextFrom,
|
|
54
54
|
credentialStoreFrom as inlineCredentialStoreFrom,
|
|
55
55
|
} from './auth-inline.ts'
|
|
56
|
+
import { installCompatTable } from './compat.ts'
|
|
57
|
+
import { deriveGates, deriveSpecs, FALLBACK_TABLE } from './compat-gates.ts'
|
|
56
58
|
import type { ProtocolId } from './config.ts'
|
|
57
59
|
import { type ResolverDeps, resolveProfilesFallback } from './profiles.ts'
|
|
58
60
|
|
|
@@ -295,6 +297,39 @@ function treeResolverDeps(
|
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
/**
|
|
301
|
+
* 从官方安装副本推导 compat 门控表(bundle 文本给分型、Config schema 给取值约束),
|
|
302
|
+
* 并安装为插件生效表。任一环节失败回退 FALLBACK_TABLE 并给诊断——绝不因推导失败
|
|
303
|
+
* 弄挂插件启动(宁可放宽校验,也不误拒官方可配字段)。
|
|
304
|
+
*/
|
|
305
|
+
function installOfficialCompatTable(
|
|
306
|
+
bundlePath: string,
|
|
307
|
+
module: Record<string, unknown>,
|
|
308
|
+
diagnostics: string[],
|
|
309
|
+
): void {
|
|
310
|
+
const label = 'compat 门控表'
|
|
311
|
+
try {
|
|
312
|
+
const bundle = readFileSync(bundlePath, 'utf8')
|
|
313
|
+
const gates = deriveGates(bundle)
|
|
314
|
+
if (gates === undefined) {
|
|
315
|
+
// 官方改打包形态导致 COMPAT_GATES 不可解析:用冻结快照并告警。
|
|
316
|
+
diagnostics.push(
|
|
317
|
+
`${label}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});` +
|
|
318
|
+
`使用内置快照(官方新增字段可能被误拒,请检查 dsh-llm-pi-ai 打包形态)`,
|
|
319
|
+
)
|
|
320
|
+
installCompatTable(FALLBACK_TABLE)
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
// 取值约束:推导值优先,内置快照补缺(官方 schema 未覆盖的字段保持可写)。
|
|
324
|
+
const specs = { ...FALLBACK_TABLE.specs, ...deriveSpecs(module) }
|
|
325
|
+
installCompatTable({ gates, specs, source: 'official' })
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
328
|
+
diagnostics.push(`${label}推导失败(${reason});使用内置快照`)
|
|
329
|
+
installCompatTable(FALLBACK_TABLE)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
298
333
|
function kitFromTree(
|
|
299
334
|
mods: TreeModules,
|
|
300
335
|
auth: Record<string, unknown> | undefined,
|
|
@@ -350,10 +385,19 @@ function loadVendoredDeepseek(): DeepSeekKit | undefined {
|
|
|
350
385
|
return checkDeepseekShape(kit).length === 0 ? kit : undefined
|
|
351
386
|
}
|
|
352
387
|
|
|
388
|
+
/**
|
|
389
|
+
* vendored `dsh-llm-pi-ai` 的 bundle 绝对路径(compat 门控推导用)。
|
|
390
|
+
* 经包名解析拿到入口文件——npm/pnpm 各布局都适用,不硬编码 node_modules 结构。
|
|
391
|
+
*/
|
|
392
|
+
function vendoredAdapterBundlePath(): string {
|
|
393
|
+
return fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-llm-pi-ai'))
|
|
394
|
+
}
|
|
395
|
+
|
|
353
396
|
/**
|
|
354
397
|
* vendored 兜底副本套件(导出供单测直接使用,免走 dsh 树解析)。
|
|
355
398
|
* npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
|
|
356
|
-
* auth
|
|
399
|
+
* auth 助手在此无条件走插件等价实现(内联,见文件头说明);compat 门控表则
|
|
400
|
+
* 仍从 vendored 副本的 bundle + Config schema 现场推导(与 dsh 树同一推导链)。
|
|
357
401
|
*/
|
|
358
402
|
export function loadVendoredKit(): DshKit {
|
|
359
403
|
const deepseek = loadVendoredDeepseek()
|
|
@@ -362,6 +406,12 @@ export function loadVendoredKit(): DshKit {
|
|
|
362
406
|
'openai-responses': vendoredResponses,
|
|
363
407
|
'anthropic-messages': vendoredAnthropic,
|
|
364
408
|
}
|
|
409
|
+
// 与 dsh 树路径同源推导:vendored 副本同样是官方发布包(lib/index.js + Config)
|
|
410
|
+
installOfficialCompatTable(
|
|
411
|
+
vendoredAdapterBundlePath(),
|
|
412
|
+
vendoredPiAiAdapter as unknown as Record<string, unknown>,
|
|
413
|
+
[],
|
|
414
|
+
)
|
|
365
415
|
const kit: DshKit = {
|
|
366
416
|
source: 'vendored',
|
|
367
417
|
PiAiAdapter: vendoredPiAiAdapter.PiAiAdapter,
|
|
@@ -426,6 +476,7 @@ export async function resolveDshKit(): Promise<{
|
|
|
426
476
|
])
|
|
427
477
|
const kit = kitFromTree(mods, auth, config)
|
|
428
478
|
assertKitShape(kit, 'dsh-tree')
|
|
479
|
+
installOfficialCompatTable(join(treePkgDir, 'lib', 'index.js'), mods.piAiAdapter, diagnostics)
|
|
429
480
|
if (auth === undefined) {
|
|
430
481
|
diagnostics.push(
|
|
431
482
|
'dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);认证助手使用插件内联等价实现',
|