@dsh-plus/llm-pi 0.1.7 → 0.1.9
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 +1 -1
- package/lib/index.js +80 -17
- package/package.json +1 -1
- package/src/client/draft.ts +1 -1
- package/src/directory.ts +82 -0
- package/src/profiles.ts +15 -0
- package/src/service.ts +26 -19
package/lib/client.js
CHANGED
|
@@ -230,7 +230,7 @@ window.__ModuleLoader__.load({
|
|
|
230
230
|
compat: provider.compat,
|
|
231
231
|
defaultContextWindow: toNum(provider.defaultContextWindow),
|
|
232
232
|
defaultMaxTokens: toNum(provider.defaultMaxTokens),
|
|
233
|
-
|
|
233
|
+
defaultInput: inputToWire(provider.input),
|
|
234
234
|
reasoning: provider.reasoning,
|
|
235
235
|
thinkingBudgets: budgetToWire(provider.thinkingBudgets),
|
|
236
236
|
cacheRetention: provider.cacheRetention,
|
package/lib/index.js
CHANGED
|
@@ -497,6 +497,63 @@ var DeepseekRouteRegistrar = class {
|
|
|
497
497
|
}
|
|
498
498
|
};
|
|
499
499
|
//#endregion
|
|
500
|
+
//#region src/directory.ts
|
|
501
|
+
/**
|
|
502
|
+
* 可配置 provider 目录注册:条目组装与冲突降级。
|
|
503
|
+
*
|
|
504
|
+
* 官方 `registerConfigurableProviders`/`replace` 的拒绝语义是原子的——任一条目
|
|
505
|
+
* 与既有声明冲突(典型:route 名撞官方内置 provider 目录条目,如 anthropic)则
|
|
506
|
+
* 整批不落盘。此处按 registerAdapter 同款降级模式:逐个剔除冲突条目重试,
|
|
507
|
+
* 让其余 route 的目录条目照常生效。
|
|
508
|
+
* @module llm-pi/directory
|
|
509
|
+
*/
|
|
510
|
+
/** 组装当前全部 route 的目录条目(pi + deepseek 两族)。 */
|
|
511
|
+
function buildDirectoryEntries(kit, settingsNs, piProfiles, deepseekRoutes, drafts) {
|
|
512
|
+
const piEntries = [...piProfiles.entries()].map(([provider, profile]) => ({
|
|
513
|
+
provider,
|
|
514
|
+
displayName: profile.displayName,
|
|
515
|
+
settingsNs,
|
|
516
|
+
settingsPath: ["providers", provider],
|
|
517
|
+
declared: !hasBuiltinProvider(kit, provider)
|
|
518
|
+
}));
|
|
519
|
+
const deepseekEntries = [...deepseekRoutes.values()].map((built) => ({
|
|
520
|
+
provider: built.route,
|
|
521
|
+
displayName: built.displayName,
|
|
522
|
+
settingsNs,
|
|
523
|
+
settingsPath: ["providers", built.route],
|
|
524
|
+
declared: true
|
|
525
|
+
}));
|
|
526
|
+
const draftEntries = drafts.map(({ route, displayName }) => ({
|
|
527
|
+
provider: route,
|
|
528
|
+
displayName,
|
|
529
|
+
settingsNs,
|
|
530
|
+
settingsPath: ["providers", route],
|
|
531
|
+
declared: true
|
|
532
|
+
}));
|
|
533
|
+
return [
|
|
534
|
+
...piEntries,
|
|
535
|
+
...deepseekEntries,
|
|
536
|
+
...draftEntries
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 提交目录条目;整批被原子拒绝时逐个剔除冲突条目重试(每轮至多剔一个,
|
|
541
|
+
* 轮数不超过条目数,必然终止)。非冲突错误原样上抛。
|
|
542
|
+
*/
|
|
543
|
+
function commitDirectory(register, entries, warn) {
|
|
544
|
+
let rest = entries;
|
|
545
|
+
for (let attempts = 0; attempts <= entries.length; attempts++) try {
|
|
546
|
+
register(rest);
|
|
547
|
+
return;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
const match = /configurable provider "([^"]+)" is already declared/.exec(error instanceof Error ? error.message : String(error));
|
|
550
|
+
const next = match === null ? [] : rest.filter((entry) => entry.provider !== match[1]);
|
|
551
|
+
if (match === null || next.length === rest.length) throw error;
|
|
552
|
+
warn(`llm-pi: 目录条目 "${match[1]}" 与既有声明冲突(通常为官方内置 provider 目录),已跳过该条目`);
|
|
553
|
+
rest = next;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
//#endregion
|
|
500
557
|
//#region src/catalog/official.ts
|
|
501
558
|
/** 按 kit 备忘(kit 实例在进程内唯一,目录随 dsh 树版本固定)。 */
|
|
502
559
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
@@ -1154,14 +1211,24 @@ function materializeRouteModels(route, profile, deps, defaultInput) {
|
|
|
1154
1211
|
};
|
|
1155
1212
|
}
|
|
1156
1213
|
/**
|
|
1214
|
+
* 草稿路由:adapter pi、无 models 且无 provider 级 extends——占位待补
|
|
1215
|
+
* (用户先建路由、后填模型的工作流)。草稿不注册进 adapter(无模型可服务),
|
|
1216
|
+
* 但仍出现在可配置 provider 目录里(Models 页/配置卡片可见可编辑)。
|
|
1217
|
+
*/
|
|
1218
|
+
function isDraftRoute(profile) {
|
|
1219
|
+
return (profile.adapter ?? "pi") === "pi" && (profile.models === void 0 || profile.models.length === 0) && profile.extends === void 0;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1157
1222
|
* 校验并物化全部 route。任一 route 不可服务即整体抛错——
|
|
1158
1223
|
* settings 写入校验与运行期 profiles 回调共用本函数,
|
|
1159
1224
|
* 保证"写入时被拒"与"运行期不可能拿到坏配置"互为表里。
|
|
1225
|
+
* 草稿路由(isDraftRoute)跳过物化,不参与 adapter 注册。
|
|
1160
1226
|
*/
|
|
1161
1227
|
function buildProfiles(providers, deps) {
|
|
1162
1228
|
const resolved = /* @__PURE__ */ new Map();
|
|
1163
1229
|
for (const [route, profile] of Object.entries(providers ?? {})) {
|
|
1164
1230
|
if ((profile.adapter ?? "pi") === "deepseek") continue;
|
|
1231
|
+
if (isDraftRoute(profile)) continue;
|
|
1165
1232
|
if (route.length === 0) throw new Error("llm-pi: provider 名不能为空");
|
|
1166
1233
|
if (profile.baseURL !== void 0 && profile.baseURL.length === 0) invalid(route, "baseURL 为空");
|
|
1167
1234
|
if (profile.displayName !== void 0 && profile.displayName.length === 0) invalid(route, "displayName 为空");
|
|
@@ -1541,29 +1608,25 @@ async function startRuntime(ctx, rawConfig) {
|
|
|
1541
1608
|
};
|
|
1542
1609
|
let directory;
|
|
1543
1610
|
let directoryFacts;
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
declared: !hasBuiltinProvider(kit, provider)
|
|
1551
|
-
}));
|
|
1552
|
-
const deepseekEntries = [...deepseekRoutes().values()].map((built) => ({
|
|
1553
|
-
provider: built.route,
|
|
1554
|
-
displayName: built.displayName,
|
|
1555
|
-
settingsNs: SETTINGS_NS,
|
|
1556
|
-
settingsPath: ["providers", built.route],
|
|
1557
|
-
declared: true
|
|
1611
|
+
/** 草稿路由(无模型占位)从原始配置直读——它们不进 adapter profiles。 */
|
|
1612
|
+
const draftRoutes = () => {
|
|
1613
|
+
const providers = current().providers ?? {};
|
|
1614
|
+
return Object.entries(providers).filter(([, profile]) => isDraftRoute(profile)).map(([route, profile]) => ({
|
|
1615
|
+
route,
|
|
1616
|
+
displayName: profile.displayName ?? route
|
|
1558
1617
|
}));
|
|
1559
|
-
|
|
1618
|
+
};
|
|
1619
|
+
const ensureDirectory = () => {
|
|
1620
|
+
const entries = buildDirectoryEntries(kit, SETTINGS_NS, profiles(), deepseekRoutes(), draftRoutes());
|
|
1560
1621
|
if (deepEqualJson(entries, directoryFacts)) return;
|
|
1561
1622
|
if (entries.length === 0) {
|
|
1562
1623
|
directoryFacts = entries;
|
|
1563
1624
|
return;
|
|
1564
1625
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1626
|
+
commitDirectory((batch) => {
|
|
1627
|
+
if (directory === void 0) directory = ctx.llm.registerConfigurableProviders(batch);
|
|
1628
|
+
else directory.replace(batch);
|
|
1629
|
+
}, entries, (message) => logger.warn(message));
|
|
1567
1630
|
directoryFacts = entries;
|
|
1568
1631
|
};
|
|
1569
1632
|
const deepseekRegistrar = new DeepseekRouteRegistrar({
|
package/package.json
CHANGED
package/src/client/draft.ts
CHANGED
|
@@ -285,7 +285,7 @@ function providerToWire(provider: ProviderDraft): WireProvider {
|
|
|
285
285
|
compat: provider.compat,
|
|
286
286
|
defaultContextWindow: toNum(provider.defaultContextWindow),
|
|
287
287
|
defaultMaxTokens: toNum(provider.defaultMaxTokens),
|
|
288
|
-
|
|
288
|
+
defaultInput: inputToWire(provider.input),
|
|
289
289
|
reasoning: provider.reasoning,
|
|
290
290
|
thinkingBudgets: budgetToWire(provider.thinkingBudgets),
|
|
291
291
|
cacheRetention: provider.cacheRetention,
|
package/src/directory.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可配置 provider 目录注册:条目组装与冲突降级。
|
|
3
|
+
*
|
|
4
|
+
* 官方 `registerConfigurableProviders`/`replace` 的拒绝语义是原子的——任一条目
|
|
5
|
+
* 与既有声明冲突(典型:route 名撞官方内置 provider 目录条目,如 anthropic)则
|
|
6
|
+
* 整批不落盘。此处按 registerAdapter 同款降级模式:逐个剔除冲突条目重试,
|
|
7
|
+
* 让其余 route 的目录条目照常生效。
|
|
8
|
+
* @module llm-pi/directory
|
|
9
|
+
*/
|
|
10
|
+
import { hasBuiltinProvider } from './catalog/builtin.ts'
|
|
11
|
+
import type { ResolvedDeepseekRoute } from './profiles-deepseek.ts'
|
|
12
|
+
import type { DshKit } from './resolve-dsh.ts'
|
|
13
|
+
|
|
14
|
+
/** 一条可配置 provider 目录条目(registerConfigurableProviders 的入参形状)。 */
|
|
15
|
+
export interface DirectoryEntry {
|
|
16
|
+
provider: string
|
|
17
|
+
displayName: string
|
|
18
|
+
settingsNs: string
|
|
19
|
+
settingsPath: string[]
|
|
20
|
+
declared: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 组装当前全部 route 的目录条目(pi + deepseek 两族)。 */
|
|
24
|
+
export function buildDirectoryEntries(
|
|
25
|
+
kit: DshKit,
|
|
26
|
+
settingsNs: string,
|
|
27
|
+
piProfiles: Map<string, { displayName: string }>,
|
|
28
|
+
deepseekRoutes: Map<string, ResolvedDeepseekRoute>,
|
|
29
|
+
drafts: ReadonlyArray<{ route: string; displayName: string }>,
|
|
30
|
+
): DirectoryEntry[] {
|
|
31
|
+
const piEntries = [...piProfiles.entries()].map(([provider, profile]) => ({
|
|
32
|
+
provider,
|
|
33
|
+
displayName: profile.displayName,
|
|
34
|
+
settingsNs,
|
|
35
|
+
settingsPath: ['providers', provider],
|
|
36
|
+
declared: !hasBuiltinProvider(kit, provider),
|
|
37
|
+
}))
|
|
38
|
+
const deepseekEntries = [...deepseekRoutes.values()].map((built) => ({
|
|
39
|
+
provider: built.route,
|
|
40
|
+
displayName: built.displayName,
|
|
41
|
+
settingsNs,
|
|
42
|
+
settingsPath: ['providers', built.route],
|
|
43
|
+
declared: true,
|
|
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]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 提交目录条目;整批被原子拒绝时逐个剔除冲突条目重试(每轮至多剔一个,
|
|
58
|
+
* 轮数不超过条目数,必然终止)。非冲突错误原样上抛。
|
|
59
|
+
*/
|
|
60
|
+
export function commitDirectory(
|
|
61
|
+
register: (entries: DirectoryEntry[]) => void,
|
|
62
|
+
entries: DirectoryEntry[],
|
|
63
|
+
warn: (message: string) => void,
|
|
64
|
+
): void {
|
|
65
|
+
let rest = entries
|
|
66
|
+
for (let attempts = 0; attempts <= entries.length; attempts++) {
|
|
67
|
+
try {
|
|
68
|
+
register(rest)
|
|
69
|
+
return
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const match = /configurable provider "([^"]+)" is already declared/.exec(
|
|
72
|
+
error instanceof Error ? error.message : String(error),
|
|
73
|
+
)
|
|
74
|
+
const next = match === null ? [] : rest.filter((entry) => entry.provider !== match[1])
|
|
75
|
+
if (match === null || next.length === rest.length) throw error
|
|
76
|
+
warn(
|
|
77
|
+
`llm-pi: 目录条目 "${match[1]}" 与既有声明冲突(通常为官方内置 provider 目录),已跳过该条目`,
|
|
78
|
+
)
|
|
79
|
+
rest = next
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
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
|
@@ -16,12 +16,12 @@ import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
|
|
|
16
16
|
import type { ResolvedPiAiProviderProfile } from '@deepseek-ai/dsh-llm-pi-ai'
|
|
17
17
|
import { deepEqualJson, installSettingsSection } from '@deepseek-ai/dsh-settings'
|
|
18
18
|
|
|
19
|
-
import { hasBuiltinProvider } from './catalog/builtin.ts'
|
|
20
19
|
import { ModelsDevSource } from './catalog/models-dev.ts'
|
|
21
20
|
import { Config, type LlmPiConfig, SETTINGS_NS } from './config.ts'
|
|
22
21
|
import { DeepseekRouteRegistrar } from './deepseek-routes.ts'
|
|
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,30 +212,37 @@ 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
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
provider: built.route,
|
|
225
|
-
displayName: built.displayName,
|
|
226
|
-
settingsNs: SETTINGS_NS,
|
|
227
|
-
settingsPath: ['providers', built.route],
|
|
228
|
-
declared: true,
|
|
229
|
-
}))
|
|
230
|
-
const entries = [...piEntries, ...deepseekEntries]
|
|
223
|
+
const entries = buildDirectoryEntries(
|
|
224
|
+
kit,
|
|
225
|
+
SETTINGS_NS,
|
|
226
|
+
profiles(),
|
|
227
|
+
deepseekRoutes(),
|
|
228
|
+
draftRoutes(),
|
|
229
|
+
)
|
|
230
|
+
// 备忘键为目标全集(含被冲突跳过的条目):目标不变不重复尝试/告警
|
|
231
231
|
if (deepEqualJson(entries, directoryFacts)) return
|
|
232
232
|
if (entries.length === 0) {
|
|
233
233
|
// 空目录不可注册(INVALID_DIRECTORY);等 settings 用户层供数后在 onChange 注册
|
|
234
234
|
directoryFacts = entries
|
|
235
235
|
return
|
|
236
236
|
}
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
commitDirectory(
|
|
238
|
+
(batch: DirectoryEntry[]) => {
|
|
239
|
+
if (directory === undefined)
|
|
240
|
+
directory = ctx.llm.registerConfigurableProviders(batch as never)
|
|
241
|
+
else directory.replace(batch)
|
|
242
|
+
},
|
|
243
|
+
entries,
|
|
244
|
+
(message) => logger.warn(message),
|
|
245
|
+
)
|
|
239
246
|
directoryFacts = entries
|
|
240
247
|
}
|
|
241
248
|
|