@dsh-plus/llm-pi 0.1.42 → 0.1.43

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.
@@ -7,10 +7,17 @@
7
7
  * 桌面端 Electron 下 argv[1] 探测走不通)→ realpath(argv[1]) 向上找到同时
8
8
  * 含 node_modules/@deepseek-ai/dsh-llm-pi-ai 与 node_modules/@earendil-works/pi-ai
9
9
  * 的目录,按文件路径动态 import——与 dsh 官方插件共享同一模块实例;
10
- * 2. vendored:回退到本插件 dependencies 里的固定版本副本(裸 import)。
10
+ * 2. vendored:回退到本插件 devDependencies 里的副本(裸 import)。
11
11
  *
12
- * 两条路径产物都过形状自检(assertKitShape):上游重构导致形状漂移时
13
- * 抛错,由调用方决定回退或放弃注册——绝不让坏套件进入消息链路。
12
+ * 两条路径产物都过形状自检(assertKitShape):上游重构导致形状漂移时抛错,
13
+ * 由调用方决定回退或放弃注册——绝不让坏套件进入消息链路。
14
+ *
15
+ * **自动跟随(版本不参与分支)**:插件不认识"某个 pi-ai 版本",只认识形状与
16
+ * 现场推导出的适配面。协议集合取自已装官方包的 `supportedProtocols()`,每个协议
17
+ * 的惰性工厂按 `pi-ai/dist/api/<api>.lazy.js`(api id 即模块名,pi-ai 包导出约定)
18
+ * 动态加载;缺失的协议逐项跳过并记诊断,其余协议照常。compat 门控/取值约束与
19
+ * 模型条目字段集同样现场推导(official-surface.ts)。生效版本只用于状态行显示,
20
+ * 超出已验证区间也只是提示(kit-meta.ts),永不阻断。
14
21
  *
15
22
  * 0.1.2-alpha 线的新面(0.1.2-alpha.2 复核包根仍未导出、仅 src 子路径有):
16
23
  * - resolveProfiles(config.ts):官方解析链,dev 布局树可经 src 子路径复用;
@@ -55,8 +62,16 @@ import {
55
62
  credentialStoreFrom as inlineCredentialStoreFrom,
56
63
  } from './auth-inline.ts'
57
64
  import { installCompatTable } from './compat.ts'
58
- import { deriveGates, deriveSpecs, FALLBACK_TABLE } from './compat-gates.ts'
59
- import type { ProtocolId } from './config.ts'
65
+ import { collectTreeVersions, collectVendoredVersions, type KitVersions } from './kit-meta.ts'
66
+ import {
67
+ deriveGates,
68
+ deriveModelEntryFields,
69
+ deriveProtocols,
70
+ deriveSpecs,
71
+ FALLBACK_MODEL_ENTRY_FIELDS,
72
+ FALLBACK_TABLE,
73
+ fallbackProtocols,
74
+ } from './official-surface.ts'
60
75
  import { type ResolverDeps, resolveProfilesFallback } from './profiles.ts'
61
76
 
62
77
  /**
@@ -88,6 +103,19 @@ export interface AuthHelpers {
88
103
  export interface DshKit {
89
104
  /** 解析来源:dsh 安装树 / 插件 vendored 兜底副本。 */
90
105
  source: 'dsh-tree' | 'vendored'
106
+ /** 套件所在根(dsh 安装树根 / vendored 包所在 node_modules 的父目录),诊断用。 */
107
+ root?: string
108
+ /** 实际生效版本(状态行显示;不参与任何分支判断)。 */
109
+ versions: KitVersions
110
+ /** 当前可服务的线协议(顺序即官方表顺序;无工厂的协议已被剔除)。 */
111
+ protocols: string[]
112
+ /** 协议集合来源:official = 官方 supportedProtocols();fallback = 内置三元组。 */
113
+ protocolSource: 'official' | 'fallback'
114
+ protocolFactories: Record<string, () => unknown>
115
+ /** 官方 Config schema 接受的模型条目键集(继承透传白名单,现场推导)。 */
116
+ officialModelFields: string[]
117
+ /** 内置目录数据生成时间(毫秒);官方未导出该信息时省略。 */
118
+ catalogGeneratedAt?: number
91
119
  PiAiAdapter: typeof PiAiAdapterType
92
120
  LlmError: typeof DshLlm.LlmError
93
121
  resolveRetryPolicy: typeof DshLlm.resolveRetryPolicy
@@ -99,8 +127,6 @@ export interface DshKit {
99
127
  builtinProviders: typeof vendoredCatalog.builtinProviders
100
128
  getBuiltinProviders: typeof vendoredCatalog.getBuiltinProviders
101
129
  getBuiltinModels: typeof vendoredCatalog.getBuiltinModels
102
- /** 三协议的 pi-ai api 实现工厂(与官方 PROTOCOLS 表同来源)。 */
103
- protocolFactories: Record<ProtocolId, () => unknown>
104
130
  /**
105
131
  * profile 解析链:dsh 树 dev 布局(src/config.ts 存在)时为官方
106
132
  * resolveProfiles;npm 形态(无 src)与 vendored 兜底时为插件等价实现。
@@ -149,9 +175,13 @@ function assertKitShape(kit: DshKit, origin: string): void {
149
175
  if (typeof kit.createProvider !== 'function') problems.push('pi-ai createProvider 缺失')
150
176
  if (typeof kit.getBuiltinModels !== 'function') problems.push('pi-ai getBuiltinModels 缺失')
151
177
  if (typeof kit.builtinProviders !== 'function') problems.push('pi-ai builtinProviders 缺失')
152
- for (const [api, factory] of Object.entries(kit.protocolFactories)) {
153
- if (typeof factory !== 'function') problems.push(`协议工厂 ${api} 缺失`)
178
+ if (kit.protocols.length === 0) problems.push('没有可服务的线协议(协议工厂全部加载失败)')
179
+ for (const api of kit.protocols) {
180
+ if (typeof kit.protocolFactories[api] !== 'function') {
181
+ problems.push(`协议 ${api} 的工厂缺失`)
182
+ }
154
183
  }
184
+ if (kit.officialModelFields.length === 0) problems.push('官方模型条目字段集为空')
155
185
  if (typeof kit.LlmError !== 'function') problems.push('dsh-llm LlmError 缺失')
156
186
  if (typeof kit.resolveRetryPolicy !== 'function') problems.push('dsh-llm resolveRetryPolicy 缺失')
157
187
  if (typeof kit.resolveProfiles !== 'function') problems.push('resolveProfiles 缺失')
@@ -206,31 +236,26 @@ function findDshTreeRoot(startDir: string): string | undefined {
206
236
  return undefined
207
237
  }
208
238
 
239
+ /** dsh 树内与官方插件同源加载的核心模块(协议工厂按推导结果另加载)。 */
209
240
  interface TreeModules {
210
241
  piAiAdapter: Record<string, unknown>
211
242
  llm: Record<string, unknown>
212
243
  piAi: Record<string, unknown>
213
244
  catalog: Record<string, unknown>
214
- completions: Record<string, unknown>
215
- responses: Record<string, unknown>
216
- anthropic: Record<string, unknown>
217
245
  }
218
246
 
219
- /** 从 dsh 安装树按文件路径动态 import 全部套件模块(与官方插件同实例)。 */
247
+ /** 从 dsh 安装树按文件路径动态 import 核心套件模块(与官方插件同实例)。 */
220
248
  async function importTreeModules(root: string): Promise<TreeModules> {
221
249
  const nm = join(root, 'node_modules')
222
250
  const load = (absPath: string): Promise<Record<string, unknown>> =>
223
251
  import(pathToFileURL(absPath).href) as Promise<Record<string, unknown>>
224
- const [piAiAdapter, llm, piAi, catalog, completions, responses, anthropic] = await Promise.all([
252
+ const [piAiAdapter, llm, piAi, catalog] = await Promise.all([
225
253
  load(join(nm, '@deepseek-ai', 'dsh-llm-pi-ai', 'lib', 'index.js')),
226
254
  load(join(nm, '@deepseek-ai', 'dsh-llm', 'lib', 'index.js')),
227
255
  load(join(nm, '@earendil-works', 'pi-ai', 'dist', 'index.js')),
228
256
  load(join(nm, '@earendil-works', 'pi-ai', 'dist', 'providers', 'all.js')),
229
- load(join(nm, '@earendil-works', 'pi-ai', 'dist', 'api', 'openai-completions.lazy.js')),
230
- load(join(nm, '@earendil-works', 'pi-ai', 'dist', 'api', 'openai-responses.lazy.js')),
231
- load(join(nm, '@earendil-works', 'pi-ai', 'dist', 'api', 'anthropic-messages.lazy.js')),
232
257
  ])
233
- return { piAiAdapter, llm, piAi, catalog, completions, responses, anthropic }
258
+ return { piAiAdapter, llm, piAi, catalog }
234
259
  }
235
260
 
236
261
  /**
@@ -282,72 +307,227 @@ async function importTreeDeepseek(root: string): Promise<{ kit?: DeepSeekKit; pr
282
307
  }
283
308
  }
284
309
 
285
- function protocolFactoriesOf(mods: TreeModules): DshKit['protocolFactories'] {
310
+ /**
311
+ * 取惰性协议模块里的工厂:约定每个 `api/<api>.lazy.js` 只导出一个 `*Api`
312
+ * 工厂函数。名字不符时退化为"唯一函数导出",多函数且无 `*Api` 命名则判失败
313
+ * (宁可跳过该协议,也不猜错工厂)。
314
+ */
315
+ function protocolFactoryOf(module: Record<string, unknown>): (() => unknown) | undefined {
316
+ const functions = Object.entries(module).filter(
317
+ (entry): entry is [string, () => unknown] => typeof entry[1] === 'function',
318
+ )
319
+ const named = functions.filter(([name]) => /Api$/.test(name))
320
+ if (named.length === 1) return named[0]?.[1]
321
+ if (functions.length === 1) return functions[0]?.[1]
322
+ return undefined
323
+ }
324
+
325
+ /** pi-ai 的惰性协议实现目录(dsh 树布局)。 */
326
+ function piAiApiDir(root: string): string {
327
+ return join(root, 'node_modules', '@earendil-works', 'pi-ai', 'dist', 'api')
328
+ }
329
+
330
+ /** 内置三元组的静态加载(协议推导失败或全部工厂加载失败时的兜底)。 */
331
+ async function loadFallbackTreeFactories(
332
+ root: string,
333
+ diagnostics: string[],
334
+ ): Promise<Record<string, () => unknown>> {
335
+ const dir = piAiApiDir(root)
336
+ const out: Record<string, () => unknown> = {}
337
+ for (const api of fallbackProtocols()) {
338
+ const file = join(dir, `${api}.lazy.js`)
339
+ if (!existsSync(file)) continue
340
+ try {
341
+ const factory = protocolFactoryOf((await import(pathToFileURL(file).href)) as never)
342
+ if (factory !== undefined) out[api] = factory
343
+ } catch {
344
+ // 单个兜底协议加载失败:静默留给 assertKitShape 汇总(下方诊断已说明回退)
345
+ }
346
+ }
347
+ if (Object.keys(out).length < fallbackProtocols().length) {
348
+ diagnostics.push(
349
+ `pi-ai 协议兜底三元组未能全部加载(${Object.keys(out).join('/') || '空'});` +
350
+ '请检查 dsh 安装树里 @earendil-works/pi-ai/dist/api 的形态',
351
+ )
352
+ }
353
+ return out
354
+ }
355
+
356
+ /**
357
+ * 按官方声明的协议集合加载 pi-ai 惰性工厂(api id 即模块名)。
358
+ * 逐项失败只跳过该协议并记诊断;一个都加载不到时回退内置三元组。
359
+ */
360
+ async function loadTreeProtocols(
361
+ root: string,
362
+ derived: string[],
363
+ diagnostics: string[],
364
+ ): Promise<{ protocols: string[]; protocolFactories: Record<string, () => unknown> }> {
365
+ const dir = piAiApiDir(root)
366
+ const out: Record<string, () => unknown> = {}
367
+ for (const api of derived) {
368
+ const file = join(dir, `${api}.lazy.js`)
369
+ if (!existsSync(file)) {
370
+ diagnostics.push(
371
+ `pi-ai 未提供协议 "${api}" 的惰性实现(缺 ${file});该协议不可用,其余协议不受影响`,
372
+ )
373
+ continue
374
+ }
375
+ try {
376
+ const factory = protocolFactoryOf((await import(pathToFileURL(file).href)) as never)
377
+ if (factory === undefined) {
378
+ diagnostics.push(`协议 "${api}" 的实现模块未导出唯一 *Api 工厂;该协议不可用`)
379
+ continue
380
+ }
381
+ out[api] = factory
382
+ } catch (error) {
383
+ diagnostics.push(
384
+ `协议 "${api}" 的实现加载失败(${error instanceof Error ? error.message : String(error)});该协议不可用`,
385
+ )
386
+ }
387
+ }
388
+ if (Object.keys(out).length > 0) {
389
+ return { protocols: derived.filter((api) => out[api] !== undefined), protocolFactories: out }
390
+ }
391
+ diagnostics.push('官方声明的协议一个都加载不到;回退内置三元组')
392
+ const fallback = await loadFallbackTreeFactories(root, diagnostics)
286
393
  return {
287
- 'openai-completions': mods.completions['openAICompletionsApi'] as () => unknown,
288
- 'openai-responses': mods.responses['openAIResponsesApi'] as () => unknown,
289
- 'anthropic-messages': mods.anthropic['anthropicMessagesApi'] as () => unknown,
394
+ protocols: fallbackProtocols().filter((api) => fallback[api] !== undefined),
395
+ protocolFactories: fallback,
396
+ }
397
+ }
398
+
399
+ function protocolFactoriesOf(
400
+ protocols: string[],
401
+ factories: Record<string, () => unknown>,
402
+ ): DshKit['protocolFactories'] {
403
+ const out: DshKit['protocolFactories'] = {}
404
+ for (const api of protocols) {
405
+ const factory = factories[api]
406
+ if (factory !== undefined) out[api] = factory
290
407
  }
408
+ return out
291
409
  }
292
410
 
293
411
  /** 插件等价解析链的套件依赖(与 PiAiAdapter 同源,杜绝跨源混用)。 */
294
412
  function treeResolverDeps(
295
413
  mods: TreeModules,
296
414
  protocolFactories: DshKit['protocolFactories'],
415
+ protocols: string[],
297
416
  ): ResolverDeps {
298
417
  return {
299
418
  createProvider: mods.piAi['createProvider'] as DshKit['createProvider'],
300
419
  protocolFactories,
420
+ protocols,
301
421
  resolveRetryPolicy: mods.llm['resolveRetryPolicy'] as DshKit['resolveRetryPolicy'],
302
422
  }
303
423
  }
304
424
 
425
+ /** 官方适配面推导结果(协议/模型字段/compat 表/compat 来源)。 */
426
+ interface OfficialSurface {
427
+ protocols: string[]
428
+ protocolSource: 'official' | 'fallback'
429
+ officialModelFields: string[]
430
+ catalogGeneratedAt?: number
431
+ }
432
+
433
+ /** 读内置目录数据生成时间(官方未导出/形状变化时省略)。 */
434
+ function catalogGeneratedAtOf(catalog: Record<string, unknown>): number | undefined {
435
+ const reader = catalog['getBuiltinModelDataGeneratedAt']
436
+ if (typeof reader !== 'function') return undefined
437
+ try {
438
+ const value = (reader as () => unknown)()
439
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined
440
+ } catch {
441
+ return undefined
442
+ }
443
+ }
444
+
305
445
  /**
306
- * 从官方安装副本推导 compat 门控表(bundle 文本给分型、Config schema 给取值约束),
307
- * 并安装为插件生效表。任一环节失败回退 FALLBACK_TABLE 并给诊断——绝不因推导失败
308
- * 弄挂插件启动(宁可放宽校验,也不误拒官方可配字段)。
446
+ * 从官方安装副本推导适配面并安装 compat 生效表(bundle 文本给分型、Config schema
447
+ * 给取值约束与模型条目字段集、supportedProtocols() 给协议集合)。
448
+ * 任一环节失败都逐项回退并记诊断——绝不因推导失败弄挂插件启动。
309
449
  */
310
- function installOfficialCompatTable(
311
- bundlePath: string,
312
- module: Record<string, unknown>,
313
- diagnostics: string[],
314
- ): void {
315
- const label = 'compat 门控表'
450
+ function installOfficialSurface(options: {
451
+ bundlePath: string
452
+ adapterModule: Record<string, unknown>
453
+ catalogModule: Record<string, unknown>
454
+ origin: string
455
+ diagnostics: string[]
456
+ }): OfficialSurface {
457
+ const { bundlePath, adapterModule, origin, diagnostics } = options
458
+ const compatLabel = 'compat 门控表'
316
459
  try {
317
460
  const bundle = readFileSync(bundlePath, 'utf8')
318
461
  const gates = deriveGates(bundle)
319
462
  if (gates === undefined) {
320
463
  // 官方改打包形态导致 COMPAT_GATES 不可解析:用冻结快照并告警。
464
+ // 快照模式下未知 compat 键**放行**(交给官方自身校验),只拦快照里明确的
465
+ // withhold——表落后时"误拒官方新字段"比"多放一个键"严重得多。
321
466
  diagnostics.push(
322
- `${label}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});` +
323
- `使用内置快照(官方新增字段可能被误拒,请检查 dsh-llm-pi-ai 打包形态)`,
467
+ `${compatLabel}未能从官方 bundle 解析 COMPAT_GATES(${bundlePath});` +
468
+ `使用内置快照(未知字段放行、withhold 仍拒绝;请检查 dsh-llm-pi-ai 打包形态)`,
324
469
  )
325
470
  installCompatTable(FALLBACK_TABLE)
326
- return
471
+ } else {
472
+ // 取值约束:推导值优先,内置快照补缺(官方 schema 未覆盖的字段保持可写)。
473
+ const specs = { ...FALLBACK_TABLE.specs, ...deriveSpecs(adapterModule) }
474
+ installCompatTable({ gates, specs, source: 'official' })
327
475
  }
328
- // 取值约束:推导值优先,内置快照补缺(官方 schema 未覆盖的字段保持可写)。
329
- const specs = { ...FALLBACK_TABLE.specs, ...deriveSpecs(module) }
330
- installCompatTable({ gates, specs, source: 'official' })
331
476
  } catch (error) {
332
477
  const reason = error instanceof Error ? error.message : String(error)
333
- diagnostics.push(`${label}推导失败(${reason});使用内置快照`)
478
+ diagnostics.push(`${compatLabel}推导失败(${reason});使用内置快照(未知字段放行)`)
334
479
  installCompatTable(FALLBACK_TABLE)
335
480
  }
481
+
482
+ const derivedProtocols = deriveProtocols(adapterModule)
483
+ if (derivedProtocols === undefined) {
484
+ diagnostics.push(
485
+ `${origin}:官方 supportedProtocols() 不可用;协议集合回退内置三元组 ${fallbackProtocols().join('/')}`,
486
+ )
487
+ }
488
+ const fields = deriveModelEntryFields(adapterModule)
489
+ if (fields === undefined) {
490
+ diagnostics.push(
491
+ `${origin}:官方 Config schema 的模型条目字段集不可推导;` +
492
+ `继承字段回退已知键 ${FALLBACK_MODEL_ENTRY_FIELDS.join('/')}(官方新增字段可能不被继承)`,
493
+ )
494
+ }
495
+ const generatedAt = catalogGeneratedAtOf(options.catalogModule)
496
+ return {
497
+ protocols: derivedProtocols ?? fallbackProtocols(),
498
+ protocolSource: derivedProtocols === undefined ? 'fallback' : 'official',
499
+ officialModelFields: fields ?? [...FALLBACK_MODEL_ENTRY_FIELDS],
500
+ ...(generatedAt === undefined ? {} : { catalogGeneratedAt: generatedAt }),
501
+ }
336
502
  }
337
503
 
338
- function kitFromTree(
339
- mods: TreeModules,
340
- auth: Record<string, unknown> | undefined,
341
- config: Record<string, unknown> | undefined,
342
- ): DshKit {
343
- const protocolFactories = protocolFactoriesOf(mods)
344
- const resolverDeps = treeResolverDeps(mods, protocolFactories)
345
- const officialResolveProfiles = config?.['resolveProfiles']
346
- const officialCredentialStoreFrom = auth?.['credentialStoreFrom']
347
- const officialAuthContextFrom = auth?.['authContextFrom']
504
+ function kitFromTree(options: {
505
+ mods: TreeModules
506
+ auth: Record<string, unknown> | undefined
507
+ config: Record<string, unknown> | undefined
508
+ surface: OfficialSurface
509
+ protocols: string[]
510
+ protocolFactories: DshKit['protocolFactories']
511
+ root: string
512
+ versions: KitVersions
513
+ }): DshKit {
514
+ const { mods, surface, protocols, protocolFactories, root, versions } = options
515
+ const resolverDeps = treeResolverDeps(mods, protocolFactories, protocols)
516
+ const officialResolveProfiles = options.config?.['resolveProfiles']
517
+ const officialCredentialStoreFrom = options.auth?.['credentialStoreFrom']
518
+ const officialAuthContextFrom = options.auth?.['authContextFrom']
348
519
  const recordKeyFor = mods.piAiAdapter['recordKeyFor'] as (providerId: string) => CredentialKey
349
520
  return {
350
521
  source: 'dsh-tree',
522
+ root,
523
+ versions,
524
+ protocols,
525
+ protocolSource: surface.protocolSource,
526
+ protocolFactories,
527
+ officialModelFields: surface.officialModelFields,
528
+ ...(surface.catalogGeneratedAt === undefined
529
+ ? {}
530
+ : { catalogGeneratedAt: surface.catalogGeneratedAt }),
351
531
  PiAiAdapter: mods.piAiAdapter['PiAiAdapter'] as DshKit['PiAiAdapter'],
352
532
  LlmError: mods.llm['LlmError'] as DshKit['LlmError'],
353
533
  resolveRetryPolicy: mods.llm['resolveRetryPolicy'] as DshKit['resolveRetryPolicy'],
@@ -359,7 +539,6 @@ function kitFromTree(
359
539
  builtinProviders: mods.catalog['builtinProviders'] as DshKit['builtinProviders'],
360
540
  getBuiltinProviders: mods.catalog['getBuiltinProviders'] as DshKit['getBuiltinProviders'],
361
541
  getBuiltinModels: mods.catalog['getBuiltinModels'] as DshKit['getBuiltinModels'],
362
- protocolFactories,
363
542
  resolveProfiles:
364
543
  officialResolveProfiles !== undefined
365
544
  ? (officialResolveProfiles as ResolveProfiles)
@@ -399,27 +578,53 @@ function vendoredAdapterBundlePath(): string {
399
578
  return fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-llm-pi-ai'))
400
579
  }
401
580
 
581
+ /** vendored 副本的静态协议工厂表(devDependencies 钉版,故无需动态加载)。 */
582
+ function vendoredProtocolFactories(): Record<string, () => unknown> {
583
+ return {
584
+ 'openai-completions': vendoredCompletions,
585
+ 'openai-responses': vendoredResponses,
586
+ 'anthropic-messages': vendoredAnthropic,
587
+ }
588
+ }
589
+
402
590
  /**
403
591
  * vendored 兜底副本套件(导出供单测直接使用,免走 dsh 树解析)。
592
+ * 副本是**钉版**的,故协议工厂走静态导入,协议集合取"官方声明 ∩ 静态工厂";
404
593
  * npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
405
594
  * auth 助手在此无条件走插件等价实现(内联,见文件头说明);compat 门控表则
406
595
  * 仍从 vendored 副本的 bundle + Config schema 现场推导(与 dsh 树同一推导链)。
596
+ * @param diagnostics 可选诊断收集器(回退原因与逐项降级说明)。
407
597
  */
408
- export function loadVendoredKit(): DshKit {
409
- const deepseek = loadVendoredDeepseek()
410
- const protocolFactories = {
411
- 'openai-completions': vendoredCompletions,
412
- 'openai-responses': vendoredResponses,
413
- 'anthropic-messages': vendoredAnthropic,
598
+ export function loadVendoredKit(diagnostics: string[] = []): DshKit {
599
+ const staticFactories = vendoredProtocolFactories()
600
+ const surface = installOfficialSurface({
601
+ bundlePath: vendoredAdapterBundlePath(),
602
+ adapterModule: vendoredPiAiAdapter as unknown as Record<string, unknown>,
603
+ catalogModule: vendoredCatalog as unknown as Record<string, unknown>,
604
+ origin: 'vendored 副本',
605
+ diagnostics,
606
+ })
607
+ const protocols = surface.protocols.filter((api) => staticFactories[api] !== undefined)
608
+ const protocolFactories = protocolFactoriesOf(protocols, staticFactories)
609
+ const resolverDeps: ResolverDeps = {
610
+ createProvider: vendoredPiAi.createProvider,
611
+ protocolFactories,
612
+ protocols,
613
+ resolveRetryPolicy: vendoredLlm.resolveRetryPolicy,
414
614
  }
415
- // 与 dsh 树路径同源推导:vendored 副本同样是官方发布包(lib/index.js + Config)
416
- installOfficialCompatTable(
417
- vendoredAdapterBundlePath(),
418
- vendoredPiAiAdapter as unknown as Record<string, unknown>,
419
- [],
420
- )
615
+ const deepseek = loadVendoredDeepseek()
421
616
  const kit: DshKit = {
422
617
  source: 'vendored',
618
+ versions: collectVendoredVersions(),
619
+ protocols,
620
+ // 官方声明里若有本副本未携带工厂的协议(副本钉版),集合仍是"官方声明"的
621
+ // 子集,故来源标注跟随推导结果;一个都不剩时按回退处理(assertKitShape 兜底)。
622
+ protocolSource: protocols.length > 0 ? surface.protocolSource : 'fallback',
623
+ protocolFactories,
624
+ officialModelFields: surface.officialModelFields,
625
+ ...(surface.catalogGeneratedAt === undefined
626
+ ? {}
627
+ : { catalogGeneratedAt: surface.catalogGeneratedAt }),
423
628
  PiAiAdapter: vendoredPiAiAdapter.PiAiAdapter,
424
629
  LlmError: vendoredLlm.LlmError,
425
630
  resolveRetryPolicy: vendoredLlm.resolveRetryPolicy,
@@ -431,13 +636,7 @@ export function loadVendoredKit(): DshKit {
431
636
  builtinProviders: vendoredCatalog.builtinProviders,
432
637
  getBuiltinProviders: vendoredCatalog.getBuiltinProviders,
433
638
  getBuiltinModels: vendoredCatalog.getBuiltinModels,
434
- protocolFactories,
435
- resolveProfiles: (providers) =>
436
- resolveProfilesFallback(providers, {
437
- createProvider: vendoredPiAi.createProvider,
438
- protocolFactories,
439
- resolveRetryPolicy: vendoredLlm.resolveRetryPolicy,
440
- }),
639
+ resolveProfiles: (providers) => resolveProfilesFallback(providers, resolverDeps),
441
640
  auth: {
442
641
  credentialStoreFrom: (ctx) =>
443
642
  inlineCredentialStoreFrom(ctx, vendoredPiAiAdapter.recordKeyFor),
@@ -475,7 +674,7 @@ export function dshTreeAnchor(
475
674
  /**
476
675
  * 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
477
676
  * 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
478
- * 返回的 diagnostics 记录回退原因,供配置卡片与日志展示。
677
+ * 返回的 diagnostics 记录回退原因与逐项降级说明,供配置卡片与日志展示。
479
678
  * @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
480
679
  */
481
680
  export async function resolveDshKit(installAnchor?: string | undefined): Promise<{
@@ -488,13 +687,33 @@ export async function resolveDshKit(installAnchor?: string | undefined): Promise
488
687
  const treePkgDir = join(anchor, 'node_modules', '@deepseek-ai', 'dsh-llm-pi-ai')
489
688
  try {
490
689
  const mods = await importTreeModules(anchor)
690
+ const surface = installOfficialSurface({
691
+ bundlePath: join(treePkgDir, 'lib', 'index.js'),
692
+ adapterModule: mods.piAiAdapter,
693
+ catalogModule: mods.catalog,
694
+ origin: 'dsh 安装树',
695
+ diagnostics,
696
+ })
697
+ const { protocols, protocolFactories } = await loadTreeProtocols(
698
+ anchor,
699
+ surface.protocols,
700
+ diagnostics,
701
+ )
491
702
  const [auth, config] = await Promise.all([
492
703
  probeSubmodule(treePkgDir, 'auth'),
493
704
  probeSubmodule(treePkgDir, 'config'),
494
705
  ])
495
- const kit = kitFromTree(mods, auth, config)
706
+ const kit = kitFromTree({
707
+ mods,
708
+ auth,
709
+ config,
710
+ surface,
711
+ protocols,
712
+ protocolFactories,
713
+ root: anchor,
714
+ versions: collectTreeVersions(anchor),
715
+ })
496
716
  assertKitShape(kit, 'dsh-tree')
497
- installOfficialCompatTable(join(treePkgDir, 'lib', 'index.js'), mods.piAiAdapter, diagnostics)
498
717
  if (auth === undefined) {
499
718
  diagnostics.push(
500
719
  'dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);认证助手使用插件内联等价实现',
@@ -524,7 +743,7 @@ export async function resolveDshKit(installAnchor?: string | undefined): Promise
524
743
  '未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本',
525
744
  )
526
745
  }
527
- const kit = loadVendoredKit()
746
+ const kit = loadVendoredKit(diagnostics)
528
747
  if (kit.deepseek === undefined) {
529
748
  diagnostics.push(
530
749
  'vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用',