@dsh-plus/llm-pi 0.1.35 → 0.1.37

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/index.js CHANGED
@@ -815,7 +815,13 @@ var DeepseekRouteRegistrar = class {
815
815
  if (current === void 0) throw new kit.LlmError(`llm-pi: deepseek route "${route}" 已注销`, "INVALID_REQUEST");
816
816
  return current.connection;
817
817
  },
818
- resolveApiKey: this.resolveApiKey,
818
+ resolveAuth: (async (connection) => ({ headers: { "x-api-key": await this.resolveApiKey(connection) } })),
819
+ discoverModels: (provider) => {
820
+ const connection = routes().get(route)?.connection ?? built.connection;
821
+ const catalogModelInfo = kit.deepseek?.catalogModelInfo;
822
+ if (catalogModelInfo === void 0) return Promise.resolve([]);
823
+ return Promise.resolve(connection.models.map((model) => catalogModelInfo(provider, model)));
824
+ },
819
825
  resolveUserId: () => this.resolveUserId(),
820
826
  resolveAttachments: () => ctx.get("attachments"),
821
827
  resolveImageAccess: (attachments, ref) => kit.resolveImageAttachmentAccess(attachments, (hostPath) => ctx.get("fs")?.processPathFromHostPath(hostPath), ref),
@@ -1803,9 +1809,9 @@ function authContextFrom(ctx) {
1803
1809
  * dsh-llm-pi-ai / dsh-llm / pi-ai 之上,从而 dsh 升级即自动跟随上游。
1804
1810
  *
1805
1811
  * 解析策略(全部模块同源地整体成功或整体回退,杜绝跨源混用):
1806
- * 1. dsh-tree:从 process.argv[1](systemd/CLI 启动的 dsh 即 bin.js shim,
1807
- * realpath 后落在 dsh 安装树内)向上找到同时含有
1808
- * node_modules/@deepseek-ai/dsh-llm-pi-ai 与 node_modules/@earendil-works/pi-ai
1812
+ * 1. dsh-tree:锚点链 = profileContext.installAnchor(CLI/桌面端同一事实,
1813
+ * 桌面端 Electron 下 argv[1] 探测走不通)→ realpath(argv[1]) 向上找到同时
1814
+ * 含 node_modules/@deepseek-ai/dsh-llm-pi-ai 与 node_modules/@earendil-works/pi-ai
1809
1815
  * 的目录,按文件路径动态 import——与 dsh 官方插件共享同一模块实例;
1810
1816
  * 2. vendored:回退到本插件 dependencies 里的固定版本副本(裸 import)。
1811
1817
  *
@@ -1851,6 +1857,7 @@ function checkDeepseekShape(kit) {
1851
1857
  if (typeof kit.DeepSeekAdapter !== "function") problems.push("DeepSeekAdapter 不是类");
1852
1858
  else if (typeof kit.DeepSeekAdapter.prototype["stream"] !== "function") problems.push("DeepSeekAdapter.prototype.stream 缺失");
1853
1859
  if (typeof kit.resolveAdapterOptions !== "function") problems.push("resolveAdapterOptions 缺失");
1860
+ if (typeof kit.catalogModelInfo !== "function") problems.push("catalogModelInfo 缺失");
1854
1861
  if (typeof kit.getOrCreateAnonymousUserId !== "function") problems.push("getOrCreateAnonymousUserId 缺失");
1855
1862
  return problems;
1856
1863
  }
@@ -1915,7 +1922,8 @@ async function importTreeDeepseek(root) {
1915
1922
  const kit = {
1916
1923
  DeepSeekAdapter: deepseek["DeepSeekAdapter"],
1917
1924
  resolveAdapterOptions: deepseek["resolveAdapterOptions"],
1918
- getOrCreateAnonymousUserId: anonId["getOrCreateAnonymousUserId"]
1925
+ getOrCreateAnonymousUserId: anonId["getOrCreateAnonymousUserId"],
1926
+ catalogModelInfo: deepseek["catalogModelInfo"]
1919
1927
  };
1920
1928
  const problems = checkDeepseekShape(kit);
1921
1929
  if (problems.length > 0) return { problem: `形状不兼容:${problems.join(";")}` };
@@ -2001,7 +2009,8 @@ function loadVendoredDeepseek() {
2001
2009
  const kit = {
2002
2010
  DeepSeekAdapter: vendoredDeepseek.DeepSeekAdapter,
2003
2011
  resolveAdapterOptions: vendoredDeepseek.resolveAdapterOptions,
2004
- getOrCreateAnonymousUserId: vendoredAnonId.getOrCreateAnonymousUserId
2012
+ getOrCreateAnonymousUserId: vendoredAnonId.getOrCreateAnonymousUserId,
2013
+ catalogModelInfo: vendoredDeepseek.catalogModelInfo
2005
2014
  };
2006
2015
  return checkDeepseekShape(kit).length === 0 ? kit : void 0;
2007
2016
  }
@@ -2055,12 +2064,20 @@ function loadVendoredKit() {
2055
2064
  assertKitShape(kit, "vendored");
2056
2065
  return kit;
2057
2066
  }
2058
- /** 定位 dsh 安装树根:realpath(argv[1]) 向上查找;argv 异常时返回 undefined。 */
2059
- function dshTreeAnchor() {
2060
- const entry = process.argv[1];
2061
- if (entry === void 0) return void 0;
2067
+ /**
2068
+ * 定位 dsh 安装树根:优先 `profileContext.installAnchor`(CLI 与桌面端同一
2069
+ * 事实——桌面端 Electron 启动时 argv[1] 是 Electron 自身、走不通 argv 探测,
2070
+ * 锚点是唯一可靠来源),其次 realpath(argv[1]) 向上查找;均失败返回 undefined。
2071
+ * argvEntry 参数仅测试注入(缺省 process.argv[1])。
2072
+ */
2073
+ function dshTreeAnchor(installAnchor, argvEntry = process.argv[1]) {
2074
+ if (installAnchor !== void 0 && installAnchor.length > 0) {
2075
+ const anchored = findDshTreeRoot(dirname(installAnchor));
2076
+ if (anchored !== void 0) return anchored;
2077
+ }
2078
+ if (argvEntry === void 0) return void 0;
2062
2079
  try {
2063
- return findDshTreeRoot(dirname(realpathSync(entry)));
2080
+ return findDshTreeRoot(dirname(realpathSync(argvEntry)));
2064
2081
  } catch {
2065
2082
  return;
2066
2083
  }
@@ -2069,10 +2086,11 @@ function dshTreeAnchor() {
2069
2086
  * 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
2070
2087
  * 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
2071
2088
  * 返回的 diagnostics 记录回退原因,供配置卡片与日志展示。
2089
+ * @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
2072
2090
  */
2073
- async function resolveDshKit() {
2091
+ async function resolveDshKit(installAnchor) {
2074
2092
  const diagnostics = [];
2075
- const anchor = dshTreeAnchor();
2093
+ const anchor = dshTreeAnchor(installAnchor);
2076
2094
  if (anchor !== void 0) {
2077
2095
  const treePkgDir = join(anchor, "node_modules", "@deepseek-ai", "dsh-llm-pi-ai");
2078
2096
  try {
@@ -2099,7 +2117,7 @@ async function resolveDshKit() {
2099
2117
  } catch (error) {
2100
2118
  diagnostics.push(`dsh 安装树套件不可用(${anchor}):${error instanceof Error ? error.message : String(error)};回退 vendored 副本`);
2101
2119
  }
2102
- } else diagnostics.push("未能从 process.argv[1] 定位 dsh 安装树;回退 vendored 副本");
2120
+ } else diagnostics.push("未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本");
2103
2121
  const kit = loadVendoredKit();
2104
2122
  if (kit.deepseek === void 0) diagnostics.push("vendored 副本的 dsh-llm-deepseek 形状不兼容;adapter: deepseek 的 route 不可用");
2105
2123
  return {
@@ -2132,7 +2150,7 @@ function makeResolveApiKey(ctx, kit) {
2132
2150
  async function startRuntime(ctx, rawConfig) {
2133
2151
  const logger = ctx.logger("llm-pi");
2134
2152
  const fields = hasVolatileRefs(rawConfig) ? rawConfig : Config(rawConfig ?? {});
2135
- const { kit, diagnostics } = await resolveDshKit();
2153
+ const { kit, diagnostics } = await resolveDshKit(ctx.get("profileContext")?.installAnchor);
2136
2154
  for (const line of diagnostics) logger.warn(line);
2137
2155
  logger.info(`运行时套件来源:${kit.source}`);
2138
2156
  let snapshot = unwrapVolatile(fields);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/llm-pi",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、models.dev 目录兜底)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -32,40 +32,41 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "https-proxy-agent": "^9.1.0",
35
- "@dsh-plus/shared": "0.1.24"
35
+ "@dsh-plus/shared": "0.1.27"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.4",
39
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.7-rc.1",
40
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.1",
41
- "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.1",
42
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
43
- "@deepseek-ai/dsh-launch-environment": "^0.1.7-rc.1",
44
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
45
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.7-rc.1",
46
- "@deepseek-ai/dsh-llm-pi-ai": "^0.1.7-rc.1",
47
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
48
- "@deepseek-ai/dsh-util-values": "^0.1.7-rc.1",
39
+ "@deepseek-ai/dsh-anonymous-user-id": "^0.2.0-rc.1",
40
+ "@deepseek-ai/dsh-credentials": "^0.2.0-rc.1",
41
+ "@deepseek-ai/dsh-home-paths": "^0.2.0-rc.1",
42
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
43
+ "@deepseek-ai/dsh-launch-environment": "^0.2.0-rc.1",
44
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
45
+ "@deepseek-ai/dsh-llm-deepseek": "^0.2.0-rc.1",
46
+ "@deepseek-ai/dsh-llm-pi-ai": "^0.2.0-rc.1",
47
+ "@deepseek-ai/dsh-settings": "^0.2.0-rc.1",
48
+ "@deepseek-ai/dsh-util-values": "^0.2.0-rc.1",
49
49
  "@deepseek-ai/schemastery": "^3.18.4",
50
50
  "@earendil-works/pi-ai": "^0.85.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@deepseek-ai/cordis": "4.0.4",
54
- "@deepseek-ai/dsh-anonymous-user-id": "0.1.7-rc.1",
55
- "@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
56
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
57
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.1",
58
- "@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
59
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
60
- "@deepseek-ai/dsh-launch-environment": "0.1.7-rc.1",
61
- "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
62
- "@deepseek-ai/dsh-llm-deepseek": "0.1.7-rc.1",
63
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.1",
64
- "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
65
- "@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
66
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
67
- "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.1",
68
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.1",
54
+ "@deepseek-ai/dsh-anonymous-user-id": "0.2.0-rc.1",
55
+ "@deepseek-ai/dsh-app-boot": "0.2.0-rc.1",
56
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1",
57
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
58
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.1",
59
+ "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
60
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
61
+ "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.1",
62
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
63
+ "@deepseek-ai/dsh-llm-deepseek": "0.2.0-rc.1",
64
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.1",
65
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
66
+ "@deepseek-ai/dsh-timeout": "0.2.0-rc.1",
67
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1",
68
+ "@deepseek-ai/dsh-util-crypto": "0.2.0-rc.1",
69
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
69
70
  "@deepseek-ai/schemastery": "3.18.4",
70
71
  "@earendil-works/pi-ai": "0.85.1",
71
72
  "@types/react": "~18.3.31",
@@ -130,7 +130,19 @@ export class DeepseekRouteRegistrar {
130
130
  }
131
131
  return current.connection
132
132
  },
133
- resolveApiKey: this.resolveApiKey as never,
133
+ // 0.2.0 起认证由 resolveAuth 一次性给出请求头(resolveApiKey 已删),
134
+ // 头形与官方 llm-deepseek-api-key 接线一致(x-api-key)。
135
+ resolveAuth: (async (connection: { apiKeyEnv: unknown }) => ({
136
+ headers: { 'x-api-key': await this.resolveApiKey(connection) },
137
+ })) as never,
138
+ // 0.2.0 起 listModels 改读 discoverModels(缺省即空目录,模型选择器无项):
139
+ // 与官方 llm-deepseek-api-key 同款——现取当前物化 connection 的 models。
140
+ discoverModels: (provider: string) => {
141
+ const connection = routes().get(route)?.connection ?? built.connection
142
+ const catalogModelInfo = kit.deepseek?.catalogModelInfo
143
+ if (catalogModelInfo === undefined) return Promise.resolve([])
144
+ return Promise.resolve(connection.models.map((model) => catalogModelInfo(provider, model)))
145
+ },
134
146
  resolveUserId: () => this.resolveUserId() as never,
135
147
  resolveAttachments: () => ctx.get('attachments') as never,
136
148
  // 附件宿主路径 → 模型工具执行世界只读路径桥(官方接线同款,见
@@ -3,9 +3,9 @@
3
3
  * dsh-llm-pi-ai / dsh-llm / pi-ai 之上,从而 dsh 升级即自动跟随上游。
4
4
  *
5
5
  * 解析策略(全部模块同源地整体成功或整体回退,杜绝跨源混用):
6
- * 1. dsh-tree:从 process.argv[1](systemd/CLI 启动的 dsh 即 bin.js shim,
7
- * realpath 后落在 dsh 安装树内)向上找到同时含有
8
- * node_modules/@deepseek-ai/dsh-llm-pi-ai 与 node_modules/@earendil-works/pi-ai
6
+ * 1. dsh-tree:锚点链 = profileContext.installAnchor(CLI/桌面端同一事实,
7
+ * 桌面端 Electron 下 argv[1] 探测走不通)→ realpath(argv[1]) 向上找到同时
8
+ * 含 node_modules/@deepseek-ai/dsh-llm-pi-ai 与 node_modules/@earendil-works/pi-ai
9
9
  * 的目录,按文件路径动态 import——与 dsh 官方插件共享同一模块实例;
10
10
  * 2. vendored:回退到本插件 dependencies 里的固定版本副本(裸 import)。
11
11
  *
@@ -31,6 +31,7 @@ import type { CredentialKey } from '@deepseek-ai/dsh-credentials'
31
31
  import type * as DshLlm from '@deepseek-ai/dsh-llm'
32
32
  import * as vendoredLlm from '@deepseek-ai/dsh-llm'
33
33
  import type {
34
+ catalogModelInfo as catalogModelInfoType,
34
35
  DeepSeekAdapter as DeepSeekAdapterType,
35
36
  resolveAdapterOptions as resolveDeepSeekOptionsType,
36
37
  } from '@deepseek-ai/dsh-llm-deepseek'
@@ -68,6 +69,8 @@ export interface DeepSeekKit {
68
69
  DeepSeekAdapter: typeof DeepSeekAdapterType
69
70
  resolveAdapterOptions: typeof resolveDeepSeekOptionsType
70
71
  getOrCreateAnonymousUserId: typeof getAnonIdType
72
+ /** 目录项 → LlmModelInfo(0.2.0 起 listModels 经 discoverModels 消费)。 */
73
+ catalogModelInfo: typeof catalogModelInfoType
71
74
  }
72
75
 
73
76
  /** profile 解析面(官方 config.ts resolveProfiles 的签名;包根未导出,见文件头)。 */
@@ -178,6 +181,7 @@ function checkDeepseekShape(kit: DeepSeekKit): string[] {
178
181
  problems.push('DeepSeekAdapter.prototype.stream 缺失')
179
182
  }
180
183
  if (typeof kit.resolveAdapterOptions !== 'function') problems.push('resolveAdapterOptions 缺失')
184
+ if (typeof kit.catalogModelInfo !== 'function') problems.push('catalogModelInfo 缺失')
181
185
  if (typeof kit.getOrCreateAnonymousUserId !== 'function') {
182
186
  problems.push('getOrCreateAnonymousUserId 缺失')
183
187
  }
@@ -268,6 +272,7 @@ async function importTreeDeepseek(root: string): Promise<{ kit?: DeepSeekKit; pr
268
272
  getOrCreateAnonymousUserId: anonId[
269
273
  'getOrCreateAnonymousUserId'
270
274
  ] as DeepSeekKit['getOrCreateAnonymousUserId'],
275
+ catalogModelInfo: deepseek['catalogModelInfo'] as DeepSeekKit['catalogModelInfo'],
271
276
  }
272
277
  const problems = checkDeepseekShape(kit)
273
278
  if (problems.length > 0) return { problem: `形状不兼容:${problems.join(';')}` }
@@ -381,6 +386,7 @@ function loadVendoredDeepseek(): DeepSeekKit | undefined {
381
386
  DeepSeekAdapter: vendoredDeepseek.DeepSeekAdapter,
382
387
  resolveAdapterOptions: vendoredDeepseek.resolveAdapterOptions,
383
388
  getOrCreateAnonymousUserId: vendoredAnonId.getOrCreateAnonymousUserId,
389
+ catalogModelInfo: vendoredDeepseek.catalogModelInfo,
384
390
  }
385
391
  return checkDeepseekShape(kit).length === 0 ? kit : undefined
386
392
  }
@@ -444,12 +450,23 @@ export function loadVendoredKit(): DshKit {
444
450
  return kit
445
451
  }
446
452
 
447
- /** 定位 dsh 安装树根:realpath(argv[1]) 向上查找;argv 异常时返回 undefined。 */
448
- function dshTreeAnchor(): string | undefined {
449
- const entry = process.argv[1]
450
- if (entry === undefined) return undefined
453
+ /**
454
+ * 定位 dsh 安装树根:优先 `profileContext.installAnchor`(CLI 与桌面端同一
455
+ * 事实——桌面端 Electron 启动时 argv[1] 是 Electron 自身、走不通 argv 探测,
456
+ * 锚点是唯一可靠来源),其次 realpath(argv[1]) 向上查找;均失败返回 undefined。
457
+ * argvEntry 参数仅测试注入(缺省 process.argv[1])。
458
+ */
459
+ export function dshTreeAnchor(
460
+ installAnchor?: string | undefined,
461
+ argvEntry: string | undefined = process.argv[1],
462
+ ): string | undefined {
463
+ if (installAnchor !== undefined && installAnchor.length > 0) {
464
+ const anchored = findDshTreeRoot(dirname(installAnchor))
465
+ if (anchored !== undefined) return anchored
466
+ }
467
+ if (argvEntry === undefined) return undefined
451
468
  try {
452
- return findDshTreeRoot(dirname(realpathSync(entry)))
469
+ return findDshTreeRoot(dirname(realpathSync(argvEntry)))
453
470
  } catch {
454
471
  return undefined
455
472
  }
@@ -459,13 +476,14 @@ function dshTreeAnchor(): string | undefined {
459
476
  * 解析运行时套件:优先 dsh 安装树(自动跟随上游),失败回退 vendored 副本;
460
477
  * 两者都过不了形状自检时抛错(调用方应记日志并放弃注册 route)。
461
478
  * 返回的 diagnostics 记录回退原因,供配置卡片与日志展示。
479
+ * @param installAnchor profile 的 dsh 安装锚点(`ctx.profileContext.installAnchor`)。
462
480
  */
463
- export async function resolveDshKit(): Promise<{
481
+ export async function resolveDshKit(installAnchor?: string | undefined): Promise<{
464
482
  kit: DshKit
465
483
  diagnostics: string[]
466
484
  }> {
467
485
  const diagnostics: string[] = []
468
- const anchor = dshTreeAnchor()
486
+ const anchor = dshTreeAnchor(installAnchor)
469
487
  if (anchor !== undefined) {
470
488
  const treePkgDir = join(anchor, 'node_modules', '@deepseek-ai', 'dsh-llm-pi-ai')
471
489
  try {
@@ -502,7 +520,9 @@ export async function resolveDshKit(): Promise<{
502
520
  )
503
521
  }
504
522
  } else {
505
- diagnostics.push('未能从 process.argv[1] 定位 dsh 安装树;回退 vendored 副本')
523
+ diagnostics.push(
524
+ '未能从 profileContext.installAnchor / process.argv[1] 定位 dsh 安装树;回退 vendored 副本',
525
+ )
506
526
  }
507
527
  const kit = loadVendoredKit()
508
528
  if (kit.deepseek === undefined) {
package/src/service.ts CHANGED
@@ -11,6 +11,9 @@
11
11
  * @module llm-pi/service
12
12
  */
13
13
  import type { Context } from '@deepseek-ai/cordis'
14
+ // 平台类型面:profileContext.installAnchor(dsh 安装锚点,桌面端关键来源)。
15
+ // 纯类型导入,按开发规范仅需 devDeps。
16
+ import type {} from '@deepseek-ai/dsh-app-boot'
14
17
  import type { CredentialRef } from '@deepseek-ai/dsh-credentials'
15
18
  import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
16
19
  import type { DirectoryRegistrationHandle } from '@deepseek-ai/dsh-llm'
@@ -90,7 +93,7 @@ export async function startRuntime(
90
93
  const fields: LlmPiConfigFields = hasVolatileRefs(rawConfig)
91
94
  ? (rawConfig as LlmPiConfigFields)
92
95
  : Config((rawConfig ?? {}) as LlmPiConfigInput)
93
- const { kit, diagnostics } = await resolveDshKit()
96
+ const { kit, diagnostics } = await resolveDshKit(ctx.get('profileContext')?.installAnchor)
94
97
  for (const line of diagnostics) logger.warn(line)
95
98
  logger.info(`运行时套件来源:${kit.source}`)
96
99