@dsh-plus/llm-pi 0.1.8 → 0.1.10
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 +47 -10
- package/package.json +1 -1
- package/src/deepseek-routes.ts +23 -7
- package/src/directory.ts +10 -1
- package/src/profiles.ts +15 -0
- package/src/service.ts +15 -2
package/lib/index.js
CHANGED
|
@@ -449,20 +449,23 @@ var DeepseekRouteRegistrar = class {
|
|
|
449
449
|
for (const [route, built] of target) {
|
|
450
450
|
const existing = this.registrations.get(route);
|
|
451
451
|
if (existing !== void 0) {
|
|
452
|
-
this.
|
|
452
|
+
this.refreshRegistration(route, built, existing);
|
|
453
453
|
continue;
|
|
454
454
|
}
|
|
455
455
|
this.register(route, built);
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
458
|
+
/** retryPolicy 或 displayName 变化时原地 replace(两者均为注册期捕获事实)。 */
|
|
459
|
+
refreshRegistration(route, built, registration) {
|
|
460
|
+
const policyUnchanged = deepEqualJson(built.connection.retryPolicy, registration.retryPolicy);
|
|
461
|
+
const nameUnchanged = built.displayName === registration.displayName;
|
|
462
|
+
if (policyUnchanged && nameUnchanged) return;
|
|
461
463
|
try {
|
|
462
464
|
registration.handle.replace([route]);
|
|
463
|
-
registration.retryPolicy =
|
|
465
|
+
registration.retryPolicy = built.connection.retryPolicy;
|
|
466
|
+
registration.displayName = built.displayName;
|
|
464
467
|
} catch (error) {
|
|
465
|
-
this.deps.logger.error(`llm-pi: deepseek route "${route}"
|
|
468
|
+
this.deps.logger.error(`llm-pi: deepseek route "${route}" 注册事实更新被拒,保留此前注册`);
|
|
466
469
|
this.deps.logger.error(error);
|
|
467
470
|
}
|
|
468
471
|
}
|
|
@@ -484,11 +487,16 @@ var DeepseekRouteRegistrar = class {
|
|
|
484
487
|
resolveUserId: () => this.resolveUserId(),
|
|
485
488
|
resolveAttachments: () => ctx.get("attachments")
|
|
486
489
|
});
|
|
490
|
+
adapter.providerInfo = (provider) => ({
|
|
491
|
+
id: provider,
|
|
492
|
+
name: routes().get(route)?.displayName ?? built.displayName
|
|
493
|
+
});
|
|
487
494
|
try {
|
|
488
495
|
const handle = ctx.llm.registerAdapter([route], adapter);
|
|
489
496
|
this.registrations.set(route, {
|
|
490
497
|
handle,
|
|
491
|
-
retryPolicy: built.connection.retryPolicy
|
|
498
|
+
retryPolicy: built.connection.retryPolicy,
|
|
499
|
+
displayName: built.displayName
|
|
492
500
|
});
|
|
493
501
|
} catch (error) {
|
|
494
502
|
logger.error(`llm-pi: deepseek route "${route}" 注册失败(可能与其他 adapter 重名),该 route 不可用`);
|
|
@@ -508,7 +516,7 @@ var DeepseekRouteRegistrar = class {
|
|
|
508
516
|
* @module llm-pi/directory
|
|
509
517
|
*/
|
|
510
518
|
/** 组装当前全部 route 的目录条目(pi + deepseek 两族)。 */
|
|
511
|
-
function buildDirectoryEntries(kit, settingsNs, piProfiles, deepseekRoutes) {
|
|
519
|
+
function buildDirectoryEntries(kit, settingsNs, piProfiles, deepseekRoutes, drafts) {
|
|
512
520
|
const piEntries = [...piProfiles.entries()].map(([provider, profile]) => ({
|
|
513
521
|
provider,
|
|
514
522
|
displayName: profile.displayName,
|
|
@@ -523,7 +531,18 @@ function buildDirectoryEntries(kit, settingsNs, piProfiles, deepseekRoutes) {
|
|
|
523
531
|
settingsPath: ["providers", built.route],
|
|
524
532
|
declared: true
|
|
525
533
|
}));
|
|
526
|
-
|
|
534
|
+
const draftEntries = drafts.map(({ route, displayName }) => ({
|
|
535
|
+
provider: route,
|
|
536
|
+
displayName,
|
|
537
|
+
settingsNs,
|
|
538
|
+
settingsPath: ["providers", route],
|
|
539
|
+
declared: true
|
|
540
|
+
}));
|
|
541
|
+
return [
|
|
542
|
+
...piEntries,
|
|
543
|
+
...deepseekEntries,
|
|
544
|
+
...draftEntries
|
|
545
|
+
];
|
|
527
546
|
}
|
|
528
547
|
/**
|
|
529
548
|
* 提交目录条目;整批被原子拒绝时逐个剔除冲突条目重试(每轮至多剔一个,
|
|
@@ -1200,14 +1219,24 @@ function materializeRouteModels(route, profile, deps, defaultInput) {
|
|
|
1200
1219
|
};
|
|
1201
1220
|
}
|
|
1202
1221
|
/**
|
|
1222
|
+
* 草稿路由:adapter pi、无 models 且无 provider 级 extends——占位待补
|
|
1223
|
+
* (用户先建路由、后填模型的工作流)。草稿不注册进 adapter(无模型可服务),
|
|
1224
|
+
* 但仍出现在可配置 provider 目录里(Models 页/配置卡片可见可编辑)。
|
|
1225
|
+
*/
|
|
1226
|
+
function isDraftRoute(profile) {
|
|
1227
|
+
return (profile.adapter ?? "pi") === "pi" && (profile.models === void 0 || profile.models.length === 0) && profile.extends === void 0;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1203
1230
|
* 校验并物化全部 route。任一 route 不可服务即整体抛错——
|
|
1204
1231
|
* settings 写入校验与运行期 profiles 回调共用本函数,
|
|
1205
1232
|
* 保证"写入时被拒"与"运行期不可能拿到坏配置"互为表里。
|
|
1233
|
+
* 草稿路由(isDraftRoute)跳过物化,不参与 adapter 注册。
|
|
1206
1234
|
*/
|
|
1207
1235
|
function buildProfiles(providers, deps) {
|
|
1208
1236
|
const resolved = /* @__PURE__ */ new Map();
|
|
1209
1237
|
for (const [route, profile] of Object.entries(providers ?? {})) {
|
|
1210
1238
|
if ((profile.adapter ?? "pi") === "deepseek") continue;
|
|
1239
|
+
if (isDraftRoute(profile)) continue;
|
|
1211
1240
|
if (route.length === 0) throw new Error("llm-pi: provider 名不能为空");
|
|
1212
1241
|
if (profile.baseURL !== void 0 && profile.baseURL.length === 0) invalid(route, "baseURL 为空");
|
|
1213
1242
|
if (profile.displayName !== void 0 && profile.displayName.length === 0) invalid(route, "displayName 为空");
|
|
@@ -1587,8 +1616,16 @@ async function startRuntime(ctx, rawConfig) {
|
|
|
1587
1616
|
};
|
|
1588
1617
|
let directory;
|
|
1589
1618
|
let directoryFacts;
|
|
1619
|
+
/** 草稿路由(无模型占位)从原始配置直读——它们不进 adapter profiles。 */
|
|
1620
|
+
const draftRoutes = () => {
|
|
1621
|
+
const providers = current().providers ?? {};
|
|
1622
|
+
return Object.entries(providers).filter(([, profile]) => isDraftRoute(profile)).map(([route, profile]) => ({
|
|
1623
|
+
route,
|
|
1624
|
+
displayName: profile.displayName ?? route
|
|
1625
|
+
}));
|
|
1626
|
+
};
|
|
1590
1627
|
const ensureDirectory = () => {
|
|
1591
|
-
const entries = buildDirectoryEntries(kit, SETTINGS_NS, profiles(), deepseekRoutes());
|
|
1628
|
+
const entries = buildDirectoryEntries(kit, SETTINGS_NS, profiles(), deepseekRoutes(), draftRoutes());
|
|
1592
1629
|
if (deepEqualJson(entries, directoryFacts)) return;
|
|
1593
1630
|
if (entries.length === 0) {
|
|
1594
1631
|
directoryFacts = entries;
|
package/package.json
CHANGED
package/src/deepseek-routes.ts
CHANGED
|
@@ -25,6 +25,8 @@ interface RouteRegistration {
|
|
|
25
25
|
handle: AdapterRegistrationHandle
|
|
26
26
|
/** 注册期捕获的重试策略(变化才 replace)。 */
|
|
27
27
|
retryPolicy: unknown
|
|
28
|
+
/** 注册期捕获的展示名(providerInfo 是注册期捕获事实,变化才 replace)。 */
|
|
29
|
+
displayName: string
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface DeepseekRegistrarDeps {
|
|
@@ -80,25 +82,28 @@ export class DeepseekRouteRegistrar {
|
|
|
80
82
|
for (const [route, built] of target) {
|
|
81
83
|
const existing = this.registrations.get(route)
|
|
82
84
|
if (existing !== undefined) {
|
|
83
|
-
this.
|
|
85
|
+
this.refreshRegistration(route, built, existing)
|
|
84
86
|
continue
|
|
85
87
|
}
|
|
86
88
|
this.register(route, built)
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
/** retryPolicy 或 displayName 变化时原地 replace(两者均为注册期捕获事实)。 */
|
|
93
|
+
private refreshRegistration(
|
|
91
94
|
route: string,
|
|
92
95
|
built: ResolvedDeepseekRoute,
|
|
93
96
|
registration: RouteRegistration,
|
|
94
97
|
): void {
|
|
95
|
-
const
|
|
96
|
-
|
|
98
|
+
const policyUnchanged = deepEqualJson(built.connection.retryPolicy, registration.retryPolicy)
|
|
99
|
+
const nameUnchanged = built.displayName === registration.displayName
|
|
100
|
+
if (policyUnchanged && nameUnchanged) return
|
|
97
101
|
try {
|
|
98
102
|
registration.handle.replace([route])
|
|
99
|
-
registration.retryPolicy =
|
|
103
|
+
registration.retryPolicy = built.connection.retryPolicy
|
|
104
|
+
registration.displayName = built.displayName
|
|
100
105
|
} catch (error) {
|
|
101
|
-
this.deps.logger.error(`llm-pi: deepseek route "${route}"
|
|
106
|
+
this.deps.logger.error(`llm-pi: deepseek route "${route}" 注册事实更新被拒,保留此前注册`)
|
|
102
107
|
this.deps.logger.error(error)
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -126,9 +131,20 @@ export class DeepseekRouteRegistrar {
|
|
|
126
131
|
resolveUserId: () => this.resolveUserId() as never,
|
|
127
132
|
resolveAttachments: () => ctx.get('attachments') as never,
|
|
128
133
|
})
|
|
134
|
+
// 官方 providerInfo 硬编码 name: "DeepSeek"——覆盖为按路由动态读取的
|
|
135
|
+
// displayName,否则自定义 deepseek 路由在模型选择器里与 deepseek-official
|
|
136
|
+
// 撞名(两个 "DeepSeek" 分组,无法区分)。
|
|
137
|
+
adapter.providerInfo = (provider: string) => ({
|
|
138
|
+
id: provider,
|
|
139
|
+
name: routes().get(route)?.displayName ?? built.displayName,
|
|
140
|
+
})
|
|
129
141
|
try {
|
|
130
142
|
const handle = ctx.llm.registerAdapter([route], adapter as never)
|
|
131
|
-
this.registrations.set(route, {
|
|
143
|
+
this.registrations.set(route, {
|
|
144
|
+
handle,
|
|
145
|
+
retryPolicy: built.connection.retryPolicy,
|
|
146
|
+
displayName: built.displayName,
|
|
147
|
+
})
|
|
132
148
|
} catch (error) {
|
|
133
149
|
logger.error(
|
|
134
150
|
`llm-pi: deepseek route "${route}" 注册失败(可能与其他 adapter 重名),该 route 不可用`,
|
package/src/directory.ts
CHANGED
|
@@ -26,6 +26,7 @@ export function buildDirectoryEntries(
|
|
|
26
26
|
settingsNs: string,
|
|
27
27
|
piProfiles: Map<string, { displayName: string }>,
|
|
28
28
|
deepseekRoutes: Map<string, ResolvedDeepseekRoute>,
|
|
29
|
+
drafts: ReadonlyArray<{ route: string; displayName: string }>,
|
|
29
30
|
): DirectoryEntry[] {
|
|
30
31
|
const piEntries = [...piProfiles.entries()].map(([provider, profile]) => ({
|
|
31
32
|
provider,
|
|
@@ -41,7 +42,15 @@ export function buildDirectoryEntries(
|
|
|
41
42
|
settingsPath: ['providers', built.route],
|
|
42
43
|
declared: true,
|
|
43
44
|
}))
|
|
44
|
-
|
|
45
|
+
// 草稿路由不进 adapter,但要在目录里可见(Models 页/配置卡片入口)
|
|
46
|
+
const draftEntries = drafts.map(({ route, displayName }) => ({
|
|
47
|
+
provider: route,
|
|
48
|
+
displayName,
|
|
49
|
+
settingsNs,
|
|
50
|
+
settingsPath: ['providers', route],
|
|
51
|
+
declared: true,
|
|
52
|
+
}))
|
|
53
|
+
return [...piEntries, ...deepseekEntries, ...draftEntries]
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/**
|
package/src/profiles.ts
CHANGED
|
@@ -295,10 +295,24 @@ function materializeRouteModels(
|
|
|
295
295
|
return { models, configuredMaxTokens }
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/**
|
|
299
|
+
* 草稿路由:adapter pi、无 models 且无 provider 级 extends——占位待补
|
|
300
|
+
* (用户先建路由、后填模型的工作流)。草稿不注册进 adapter(无模型可服务),
|
|
301
|
+
* 但仍出现在可配置 provider 目录里(Models 页/配置卡片可见可编辑)。
|
|
302
|
+
*/
|
|
303
|
+
export function isDraftRoute(profile: ProviderProfileConfig): boolean {
|
|
304
|
+
return (
|
|
305
|
+
(profile.adapter ?? 'pi') === 'pi' &&
|
|
306
|
+
(profile.models === undefined || profile.models.length === 0) &&
|
|
307
|
+
profile.extends === undefined
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
298
311
|
/**
|
|
299
312
|
* 校验并物化全部 route。任一 route 不可服务即整体抛错——
|
|
300
313
|
* settings 写入校验与运行期 profiles 回调共用本函数,
|
|
301
314
|
* 保证"写入时被拒"与"运行期不可能拿到坏配置"互为表里。
|
|
315
|
+
* 草稿路由(isDraftRoute)跳过物化,不参与 adapter 注册。
|
|
302
316
|
*/
|
|
303
317
|
export function buildProfiles(
|
|
304
318
|
providers: Record<string, ProviderProfileConfig> | undefined,
|
|
@@ -308,6 +322,7 @@ export function buildProfiles(
|
|
|
308
322
|
for (const [route, profile] of Object.entries(providers ?? {})) {
|
|
309
323
|
// adapter: deepseek 的 route 由 profiles-deepseek.ts 物化(官方 DeepSeekAdapter 链路)
|
|
310
324
|
if ((profile.adapter ?? 'pi') === 'deepseek') continue
|
|
325
|
+
if (isDraftRoute(profile)) continue // 草稿路由:占位待补,不进 adapter
|
|
311
326
|
if (route.length === 0) throw new Error('llm-pi: provider 名不能为空')
|
|
312
327
|
// 注意:不做"route 名与内置 provider 名重名"的静态校验——内置名 ≠ 已注册
|
|
313
328
|
// route(如官方 llm-pi-ai 配置清空后 anthropic 名可用)。真实冲突只在注册期
|
package/src/service.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { Config, type LlmPiConfig, SETTINGS_NS } from './config.ts'
|
|
|
21
21
|
import { DeepseekRouteRegistrar } from './deepseek-routes.ts'
|
|
22
22
|
import { buildDirectoryEntries, commitDirectory, type DirectoryEntry } from './directory.ts'
|
|
23
23
|
import { discoverModels } from './discovery.ts'
|
|
24
|
-
import { assertServiceable, buildProfiles } from './profiles.ts'
|
|
24
|
+
import { assertServiceable, buildProfiles, isDraftRoute } from './profiles.ts'
|
|
25
25
|
import { buildDeepseekRoutes, type ResolvedDeepseekRoute } from './profiles-deepseek.ts'
|
|
26
26
|
import { type DshKit, resolveDshKit } from './resolve-dsh.ts'
|
|
27
27
|
|
|
@@ -212,8 +212,21 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
|
|
|
212
212
|
|
|
213
213
|
let directory: { replace: (entries: unknown[]) => void } | undefined
|
|
214
214
|
let directoryFacts: unknown
|
|
215
|
+
/** 草稿路由(无模型占位)从原始配置直读——它们不进 adapter profiles。 */
|
|
216
|
+
const draftRoutes = (): { route: string; displayName: string }[] => {
|
|
217
|
+
const providers = current().providers ?? {}
|
|
218
|
+
return Object.entries(providers)
|
|
219
|
+
.filter(([, profile]) => isDraftRoute(profile))
|
|
220
|
+
.map(([route, profile]) => ({ route, displayName: profile.displayName ?? route }))
|
|
221
|
+
}
|
|
215
222
|
const ensureDirectory = (): void => {
|
|
216
|
-
const entries = buildDirectoryEntries(
|
|
223
|
+
const entries = buildDirectoryEntries(
|
|
224
|
+
kit,
|
|
225
|
+
SETTINGS_NS,
|
|
226
|
+
profiles(),
|
|
227
|
+
deepseekRoutes(),
|
|
228
|
+
draftRoutes(),
|
|
229
|
+
)
|
|
217
230
|
// 备忘键为目标全集(含被冲突跳过的条目):目标不变不重复尝试/告警
|
|
218
231
|
if (deepEqualJson(entries, directoryFacts)) return
|
|
219
232
|
if (entries.length === 0) {
|