@dsh-plus/llm-pi 0.1.13 → 0.1.15
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 +48 -50
- package/lib/index.d.ts +3 -2
- package/lib/index.js +633 -222
- package/package.json +28 -24
- package/src/auth-inline.ts +177 -0
- package/src/catalog/official.ts +0 -2
- package/src/client/api.ts +4 -3
- package/src/client/card.tsx +5 -13
- package/src/client/client.ts +16 -18
- package/src/client/constants.ts +11 -16
- package/src/compat.ts +136 -62
- package/src/config-api.ts +3 -3
- package/src/config.ts +25 -4
- package/src/deepseek-routes.ts +25 -0
- package/src/ns.ts +1 -1
- package/src/profiles-deepseek.ts +12 -6
- package/src/profiles.ts +439 -171
- package/src/resolve-dsh.ts +170 -13
- package/src/service.ts +26 -0
package/src/resolve-dsh.ts
CHANGED
|
@@ -9,15 +9,25 @@
|
|
|
9
9
|
* 的目录,按文件路径动态 import——与 dsh 官方插件共享同一模块实例;
|
|
10
10
|
* 2. vendored:回退到本插件 dependencies 里的固定版本副本(裸 import)。
|
|
11
11
|
*
|
|
12
|
-
* 两条路径产物都过形状自检(assertKitShape
|
|
12
|
+
* 两条路径产物都过形状自检(assertKitShape):上游重构导致形状漂移时
|
|
13
13
|
* 抛错,由调用方决定回退或放弃注册——绝不让坏套件进入消息链路。
|
|
14
|
+
*
|
|
15
|
+
* 0.1.2-alpha.1 起的新面(包根未导出、仅 src 子路径有):
|
|
16
|
+
* - resolveProfiles(config.ts):官方解析链,dev 布局树可经 src 子路径复用;
|
|
17
|
+
* - credentialStoreFrom/authContextFrom(auth.ts):PiAiAdapter 必需 auth 注入。
|
|
18
|
+
* npm 发布形态(lib/index.js 单 bundle + 无 src/)两者都拿不到:dsh 树优先
|
|
19
|
+
* 探测 src/*(含 lib/* 候选),未命中时 resolveProfiles 走插件等价实现
|
|
20
|
+
* (profiles.ts,门控表对齐官方 catalog.ts)、auth 走内联等价实现
|
|
21
|
+
* (auth-inline.ts,recordKeyFor 自包根导出同源注入)——形状自检兜底。
|
|
14
22
|
* @module llm-pi/resolve-dsh
|
|
15
23
|
*/
|
|
16
24
|
import { existsSync, realpathSync } from 'node:fs'
|
|
17
25
|
import { dirname, join } from 'node:path'
|
|
18
26
|
import { pathToFileURL } from 'node:url'
|
|
27
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
19
28
|
import type { getOrCreateAnonymousUserId as getAnonIdType } from '@deepseek-ai/dsh-anonymous-user-id'
|
|
20
29
|
import * as vendoredAnonId from '@deepseek-ai/dsh-anonymous-user-id'
|
|
30
|
+
import type { CredentialKey } from '@deepseek-ai/dsh-credentials'
|
|
21
31
|
import type * as DshLlm from '@deepseek-ai/dsh-llm'
|
|
22
32
|
import * as vendoredLlm from '@deepseek-ai/dsh-llm'
|
|
23
33
|
import type {
|
|
@@ -25,16 +35,26 @@ import type {
|
|
|
25
35
|
resolveAdapterOptions as resolveDeepSeekOptionsType,
|
|
26
36
|
} from '@deepseek-ai/dsh-llm-deepseek'
|
|
27
37
|
import * as vendoredDeepseek from '@deepseek-ai/dsh-llm-deepseek'
|
|
28
|
-
import type {
|
|
38
|
+
import type {
|
|
39
|
+
PiAiAdapter as PiAiAdapterType,
|
|
40
|
+
PiAiProviderProfile,
|
|
41
|
+
ResolvedPiAiProviderProfile,
|
|
42
|
+
} from '@deepseek-ai/dsh-llm-pi-ai'
|
|
29
43
|
import * as vendoredPiAiAdapter from '@deepseek-ai/dsh-llm-pi-ai'
|
|
30
44
|
import type * as PiAi from '@earendil-works/pi-ai'
|
|
45
|
+
import type { AuthContext, CredentialStore } from '@earendil-works/pi-ai'
|
|
31
46
|
import * as vendoredPiAi from '@earendil-works/pi-ai'
|
|
32
47
|
import { anthropicMessagesApi as vendoredAnthropic } from '@earendil-works/pi-ai/api/anthropic-messages.lazy'
|
|
33
48
|
import { openAICompletionsApi as vendoredCompletions } from '@earendil-works/pi-ai/api/openai-completions.lazy'
|
|
34
49
|
import { openAIResponsesApi as vendoredResponses } from '@earendil-works/pi-ai/api/openai-responses.lazy'
|
|
35
50
|
import * as vendoredCatalog from '@earendil-works/pi-ai/providers/all'
|
|
36
51
|
|
|
52
|
+
import {
|
|
53
|
+
authContextFrom as inlineAuthContextFrom,
|
|
54
|
+
credentialStoreFrom as inlineCredentialStoreFrom,
|
|
55
|
+
} from './auth-inline.ts'
|
|
37
56
|
import type { ProtocolId } from './config.ts'
|
|
57
|
+
import { type ResolverDeps, resolveProfilesFallback } from './profiles.ts'
|
|
38
58
|
|
|
39
59
|
/**
|
|
40
60
|
* deepseek 适配器模块表面(可选):dsh-llm-deepseek 的官方 DeepSeekAdapter
|
|
@@ -48,6 +68,17 @@ export interface DeepSeekKit {
|
|
|
48
68
|
getOrCreateAnonymousUserId: typeof getAnonIdType
|
|
49
69
|
}
|
|
50
70
|
|
|
71
|
+
/** profile 解析面(官方 config.ts resolveProfiles 的签名;包根未导出,见文件头)。 */
|
|
72
|
+
export type ResolveProfiles = (
|
|
73
|
+
providers: Readonly<Record<string, PiAiProviderProfile>> | undefined,
|
|
74
|
+
) => Map<string, ResolvedPiAiProviderProfile>
|
|
75
|
+
|
|
76
|
+
/** PiAiAdapter 必需 auth 注入的两个助手(官方 auth.ts 面;包根未导出)。 */
|
|
77
|
+
export interface AuthHelpers {
|
|
78
|
+
credentialStoreFrom(ctx: Context): CredentialStore
|
|
79
|
+
authContextFrom(ctx: Context): AuthContext
|
|
80
|
+
}
|
|
81
|
+
|
|
51
82
|
/** 插件运行期所需的全部上游模块表面(单一来源,内部一致)。 */
|
|
52
83
|
export interface DshKit {
|
|
53
84
|
/** 解析来源:dsh 安装树 / 插件 vendored 兜底副本。 */
|
|
@@ -65,6 +96,19 @@ export interface DshKit {
|
|
|
65
96
|
getBuiltinModels: typeof vendoredCatalog.getBuiltinModels
|
|
66
97
|
/** 三协议的 pi-ai api 实现工厂(与官方 PROTOCOLS 表同来源)。 */
|
|
67
98
|
protocolFactories: Record<ProtocolId, () => unknown>
|
|
99
|
+
/**
|
|
100
|
+
* profile 解析链:dsh 树 dev 布局(src/config.ts 存在)时为官方
|
|
101
|
+
* resolveProfiles;npm 形态(无 src)与 vendored 兜底时为插件等价实现。
|
|
102
|
+
*/
|
|
103
|
+
resolveProfiles: ResolveProfiles
|
|
104
|
+
/**
|
|
105
|
+
* PiAiAdapter 必需 auth 注入:dsh 树 dev 布局(src/auth.ts 存在)时为官方
|
|
106
|
+
* credentialStoreFrom/authContextFrom;其余形态为内联等价实现
|
|
107
|
+
* (auth-inline.ts,与官方同语义:记录作用域 llm-pi-ai + recordKeyFor)。
|
|
108
|
+
*/
|
|
109
|
+
auth: AuthHelpers
|
|
110
|
+
/** 附件引用 → 当前模型工具执行世界的只读路径桥(官方 dsh-llm 导出)。 */
|
|
111
|
+
resolveImageAttachmentAccess: typeof DshLlm.resolveImageAttachmentAccess
|
|
68
112
|
/** deepseek 文件通道路由所需模块;同源自检失败时为 undefined(见 diagnostics)。 */
|
|
69
113
|
deepseek?: DeepSeekKit
|
|
70
114
|
}
|
|
@@ -74,12 +118,18 @@ function assertKitShape(kit: DshKit, origin: string): void {
|
|
|
74
118
|
const problems: string[] = []
|
|
75
119
|
if (typeof kit.PiAiAdapter !== 'function') problems.push('PiAiAdapter 不是类')
|
|
76
120
|
else {
|
|
121
|
+
// 0.1.2-alpha.1 的 LlmAdapter 抽象面:prepareCall/providerRetryPolicy 为
|
|
122
|
+
// master 适配器必需 override;models.getModels/getModel 语义未变
|
|
123
|
+
// (pi-ai 0.84.2 Models.getModel(provider,id)/getModels(provider),
|
|
124
|
+
// 由同源 adapter 内部消费,本插件不直调)。
|
|
77
125
|
for (const method of [
|
|
78
126
|
'current',
|
|
79
127
|
'stream',
|
|
80
128
|
'listModels',
|
|
81
129
|
'resolveModel',
|
|
82
130
|
'providerInfo',
|
|
131
|
+
'prepareCall',
|
|
132
|
+
'providerRetryPolicy',
|
|
83
133
|
] as const) {
|
|
84
134
|
if (typeof (kit.PiAiAdapter.prototype as Record<string, unknown>)[method] !== 'function') {
|
|
85
135
|
problems.push(`PiAiAdapter.prototype.${method} 缺失`)
|
|
@@ -94,6 +144,16 @@ function assertKitShape(kit: DshKit, origin: string): void {
|
|
|
94
144
|
}
|
|
95
145
|
if (typeof kit.LlmError !== 'function') problems.push('dsh-llm LlmError 缺失')
|
|
96
146
|
if (typeof kit.resolveRetryPolicy !== 'function') problems.push('dsh-llm resolveRetryPolicy 缺失')
|
|
147
|
+
if (typeof kit.resolveProfiles !== 'function') problems.push('resolveProfiles 缺失')
|
|
148
|
+
if (typeof kit.resolveImageAttachmentAccess !== 'function') {
|
|
149
|
+
problems.push('resolveImageAttachmentAccess 缺失')
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
typeof kit.auth?.credentialStoreFrom !== 'function' ||
|
|
153
|
+
typeof kit.auth?.authContextFrom !== 'function'
|
|
154
|
+
) {
|
|
155
|
+
problems.push('auth 助手(credentialStoreFrom/authContextFrom)缺失')
|
|
156
|
+
}
|
|
97
157
|
if (problems.length > 0) {
|
|
98
158
|
throw new Error(`llm-pi: ${origin} 来源的运行时套件形状不兼容:${problems.join(';')}`)
|
|
99
159
|
}
|
|
@@ -160,6 +220,27 @@ async function importTreeModules(root: string): Promise<TreeModules> {
|
|
|
160
220
|
return { piAiAdapter, llm, piAi, catalog, completions, responses, anthropic }
|
|
161
221
|
}
|
|
162
222
|
|
|
223
|
+
/**
|
|
224
|
+
* 探测包目录下的子路径模块:src/<name>.ts(dsh 树 dev 布局 / 源码仓库)→
|
|
225
|
+
* src/<name>.js → lib/<name>.js(未来构建形态)。加载失败(依赖缺失等)
|
|
226
|
+
* 视为该候选不可用,继续下一候选;全部未命中返回 undefined(调用方兜底)。
|
|
227
|
+
*/
|
|
228
|
+
async function probeSubmodule(
|
|
229
|
+
pkgDir: string,
|
|
230
|
+
name: string,
|
|
231
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
232
|
+
for (const candidate of [`src/${name}.ts`, `src/${name}.js`, `lib/${name}.js`]) {
|
|
233
|
+
const absPath = join(pkgDir, candidate)
|
|
234
|
+
if (!existsSync(absPath)) continue
|
|
235
|
+
try {
|
|
236
|
+
return (await import(pathToFileURL(absPath).href)) as Record<string, unknown>
|
|
237
|
+
} catch {
|
|
238
|
+
// 候选存在但不可加载:尝试下一候选
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
163
244
|
/** 从同一 dsh 安装树加载 deepseek 适配器模块(失败返回 undefined,不拖垮核心套件)。 */
|
|
164
245
|
async function importTreeDeepseek(root: string): Promise<{ kit?: DeepSeekKit; problem?: string }> {
|
|
165
246
|
const nm = join(root, 'node_modules')
|
|
@@ -187,7 +268,37 @@ async function importTreeDeepseek(root: string): Promise<{ kit?: DeepSeekKit; pr
|
|
|
187
268
|
}
|
|
188
269
|
}
|
|
189
270
|
|
|
190
|
-
function
|
|
271
|
+
function protocolFactoriesOf(mods: TreeModules): DshKit['protocolFactories'] {
|
|
272
|
+
return {
|
|
273
|
+
'openai-completions': mods.completions['openAICompletionsApi'] as () => unknown,
|
|
274
|
+
'openai-responses': mods.responses['openAIResponsesApi'] as () => unknown,
|
|
275
|
+
'anthropic-messages': mods.anthropic['anthropicMessagesApi'] as () => unknown,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** 插件等价解析链的套件依赖(与 PiAiAdapter 同源,杜绝跨源混用)。 */
|
|
280
|
+
function treeResolverDeps(
|
|
281
|
+
mods: TreeModules,
|
|
282
|
+
protocolFactories: DshKit['protocolFactories'],
|
|
283
|
+
): ResolverDeps {
|
|
284
|
+
return {
|
|
285
|
+
createProvider: mods.piAi['createProvider'] as DshKit['createProvider'],
|
|
286
|
+
protocolFactories,
|
|
287
|
+
resolveRetryPolicy: mods.llm['resolveRetryPolicy'] as DshKit['resolveRetryPolicy'],
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function kitFromTree(
|
|
292
|
+
mods: TreeModules,
|
|
293
|
+
auth: Record<string, unknown> | undefined,
|
|
294
|
+
config: Record<string, unknown> | undefined,
|
|
295
|
+
): DshKit {
|
|
296
|
+
const protocolFactories = protocolFactoriesOf(mods)
|
|
297
|
+
const resolverDeps = treeResolverDeps(mods, protocolFactories)
|
|
298
|
+
const officialResolveProfiles = config?.['resolveProfiles']
|
|
299
|
+
const officialCredentialStoreFrom = auth?.['credentialStoreFrom']
|
|
300
|
+
const officialAuthContextFrom = auth?.['authContextFrom']
|
|
301
|
+
const recordKeyFor = mods.piAiAdapter['recordKeyFor'] as (providerId: string) => CredentialKey
|
|
191
302
|
return {
|
|
192
303
|
source: 'dsh-tree',
|
|
193
304
|
PiAiAdapter: mods.piAiAdapter['PiAiAdapter'] as DshKit['PiAiAdapter'],
|
|
@@ -201,11 +312,24 @@ function kitFromTree(mods: TreeModules): DshKit {
|
|
|
201
312
|
builtinProviders: mods.catalog['builtinProviders'] as DshKit['builtinProviders'],
|
|
202
313
|
getBuiltinProviders: mods.catalog['getBuiltinProviders'] as DshKit['getBuiltinProviders'],
|
|
203
314
|
getBuiltinModels: mods.catalog['getBuiltinModels'] as DshKit['getBuiltinModels'],
|
|
204
|
-
protocolFactories
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
315
|
+
protocolFactories,
|
|
316
|
+
resolveProfiles:
|
|
317
|
+
officialResolveProfiles !== undefined
|
|
318
|
+
? (officialResolveProfiles as ResolveProfiles)
|
|
319
|
+
: (providers) => resolveProfilesFallback(providers, resolverDeps),
|
|
320
|
+
auth: {
|
|
321
|
+
credentialStoreFrom:
|
|
322
|
+
officialCredentialStoreFrom !== undefined
|
|
323
|
+
? (officialCredentialStoreFrom as AuthHelpers['credentialStoreFrom'])
|
|
324
|
+
: (ctx) => inlineCredentialStoreFrom(ctx, recordKeyFor as never),
|
|
325
|
+
authContextFrom:
|
|
326
|
+
officialAuthContextFrom !== undefined
|
|
327
|
+
? (officialAuthContextFrom as AuthHelpers['authContextFrom'])
|
|
328
|
+
: inlineAuthContextFrom,
|
|
208
329
|
},
|
|
330
|
+
resolveImageAttachmentAccess: mods.llm[
|
|
331
|
+
'resolveImageAttachmentAccess'
|
|
332
|
+
] as DshKit['resolveImageAttachmentAccess'],
|
|
209
333
|
}
|
|
210
334
|
}
|
|
211
335
|
|
|
@@ -219,9 +343,18 @@ function loadVendoredDeepseek(): DeepSeekKit | undefined {
|
|
|
219
343
|
return checkDeepseekShape(kit).length === 0 ? kit : undefined
|
|
220
344
|
}
|
|
221
345
|
|
|
222
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* vendored 兜底副本套件(导出供单测直接使用,免走 dsh 树解析)。
|
|
348
|
+
* npm 发布形态不携带 src/、lib 仅 index.js/invariant.js——resolveProfiles 与
|
|
349
|
+
* auth 助手在此无条件走插件等价实现(内联/门控表对齐,见文件头说明)。
|
|
350
|
+
*/
|
|
223
351
|
export function loadVendoredKit(): DshKit {
|
|
224
352
|
const deepseek = loadVendoredDeepseek()
|
|
353
|
+
const protocolFactories = {
|
|
354
|
+
'openai-completions': vendoredCompletions,
|
|
355
|
+
'openai-responses': vendoredResponses,
|
|
356
|
+
'anthropic-messages': vendoredAnthropic,
|
|
357
|
+
}
|
|
225
358
|
const kit: DshKit = {
|
|
226
359
|
source: 'vendored',
|
|
227
360
|
PiAiAdapter: vendoredPiAiAdapter.PiAiAdapter,
|
|
@@ -235,11 +368,19 @@ export function loadVendoredKit(): DshKit {
|
|
|
235
368
|
builtinProviders: vendoredCatalog.builtinProviders,
|
|
236
369
|
getBuiltinProviders: vendoredCatalog.getBuiltinProviders,
|
|
237
370
|
getBuiltinModels: vendoredCatalog.getBuiltinModels,
|
|
238
|
-
protocolFactories
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
371
|
+
protocolFactories,
|
|
372
|
+
resolveProfiles: (providers) =>
|
|
373
|
+
resolveProfilesFallback(providers, {
|
|
374
|
+
createProvider: vendoredPiAi.createProvider,
|
|
375
|
+
protocolFactories,
|
|
376
|
+
resolveRetryPolicy: vendoredLlm.resolveRetryPolicy,
|
|
377
|
+
}),
|
|
378
|
+
auth: {
|
|
379
|
+
credentialStoreFrom: (ctx) =>
|
|
380
|
+
inlineCredentialStoreFrom(ctx, vendoredPiAiAdapter.recordKeyFor),
|
|
381
|
+
authContextFrom: inlineAuthContextFrom,
|
|
242
382
|
},
|
|
383
|
+
resolveImageAttachmentAccess: vendoredLlm.resolveImageAttachmentAccess,
|
|
243
384
|
...(deepseek === undefined ? {} : { deepseek }),
|
|
244
385
|
}
|
|
245
386
|
assertKitShape(kit, 'vendored')
|
|
@@ -269,9 +410,25 @@ export async function resolveDshKit(): Promise<{
|
|
|
269
410
|
const diagnostics: string[] = []
|
|
270
411
|
const anchor = dshTreeAnchor()
|
|
271
412
|
if (anchor !== undefined) {
|
|
413
|
+
const treePkgDir = join(anchor, 'node_modules', '@deepseek-ai', 'dsh-llm-pi-ai')
|
|
272
414
|
try {
|
|
273
|
-
const
|
|
415
|
+
const mods = await importTreeModules(anchor)
|
|
416
|
+
const [auth, config] = await Promise.all([
|
|
417
|
+
probeSubmodule(treePkgDir, 'auth'),
|
|
418
|
+
probeSubmodule(treePkgDir, 'config'),
|
|
419
|
+
])
|
|
420
|
+
const kit = kitFromTree(mods, auth, config)
|
|
274
421
|
assertKitShape(kit, 'dsh-tree')
|
|
422
|
+
if (auth === undefined) {
|
|
423
|
+
diagnostics.push(
|
|
424
|
+
'dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);认证助手使用插件内联等价实现',
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
if (config === undefined) {
|
|
428
|
+
diagnostics.push(
|
|
429
|
+
'dsh 树不含 dsh-llm-pi-ai/src(npm 发布形态);profile 解析使用插件等价实现(compat 门控对齐官方 catalog.ts)',
|
|
430
|
+
)
|
|
431
|
+
}
|
|
275
432
|
const tree = await importTreeDeepseek(anchor)
|
|
276
433
|
if (tree.kit !== undefined) {
|
|
277
434
|
return { kit: { ...kit, deepseek: tree.kit }, diagnostics }
|
package/src/service.ts
CHANGED
|
@@ -127,6 +127,32 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
|
|
|
127
127
|
profiles,
|
|
128
128
|
resolveApiKey: makeResolveApiKey(ctx, kit),
|
|
129
129
|
resolveAttachments: () => ctx.get('attachments'),
|
|
130
|
+
// 0.1.2-alpha.1 必需:登录/OAuth 类 provider 与 pi-ai 自有凭据写入的落点。
|
|
131
|
+
// dsh 树 dev 布局(src/auth.ts 存在)时为官方助手,其余形态为内联等价
|
|
132
|
+
// 实现(resolve-dsh.ts 探测,见 auth-inline.ts)。
|
|
133
|
+
auth: {
|
|
134
|
+
credentials: kit.auth.credentialStoreFrom(ctx),
|
|
135
|
+
authContext: kit.auth.authContextFrom(ctx),
|
|
136
|
+
},
|
|
137
|
+
// 官方接线范式(llm-deepseek/src/index.ts:447-452):附件宿主路径 →
|
|
138
|
+
// 当前模型工具执行世界的只读路径桥(经 ctx.fs 的 host→执行世界映射)。
|
|
139
|
+
resolveImageAccess: (attachments, ref) =>
|
|
140
|
+
kit.resolveImageAttachmentAccess(
|
|
141
|
+
attachments,
|
|
142
|
+
(hostPath) =>
|
|
143
|
+
(
|
|
144
|
+
ctx.get('fs') as
|
|
145
|
+
| { processPathFromHostPath(hostPath: string): string | undefined }
|
|
146
|
+
| undefined
|
|
147
|
+
)?.processPathFromHostPath(hostPath),
|
|
148
|
+
ref,
|
|
149
|
+
),
|
|
150
|
+
onReplayDegrade: ({ provider, model, reason }) => {
|
|
151
|
+
logger.warn(
|
|
152
|
+
`llm-pi: 助手历史中 "${provider}/${model}" 的回放状态不可用,` +
|
|
153
|
+
`该消息按 provider 中立内容发送(${reason})`,
|
|
154
|
+
)
|
|
155
|
+
},
|
|
130
156
|
})
|
|
131
157
|
|
|
132
158
|
const storedApiKey = async (provider: string | undefined): Promise<string | undefined> => {
|