@dsh-plus/llm-pi 0.1.7 → 0.1.8

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 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
- input: inputToWire(provider.input),
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,52 @@ 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) {
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
+ return [...piEntries, ...deepseekEntries];
527
+ }
528
+ /**
529
+ * 提交目录条目;整批被原子拒绝时逐个剔除冲突条目重试(每轮至多剔一个,
530
+ * 轮数不超过条目数,必然终止)。非冲突错误原样上抛。
531
+ */
532
+ function commitDirectory(register, entries, warn) {
533
+ let rest = entries;
534
+ for (let attempts = 0; attempts <= entries.length; attempts++) try {
535
+ register(rest);
536
+ return;
537
+ } catch (error) {
538
+ const match = /configurable provider "([^"]+)" is already declared/.exec(error instanceof Error ? error.message : String(error));
539
+ const next = match === null ? [] : rest.filter((entry) => entry.provider !== match[1]);
540
+ if (match === null || next.length === rest.length) throw error;
541
+ warn(`llm-pi: 目录条目 "${match[1]}" 与既有声明冲突(通常为官方内置 provider 目录),已跳过该条目`);
542
+ rest = next;
543
+ }
544
+ }
545
+ //#endregion
500
546
  //#region src/catalog/official.ts
501
547
  /** 按 kit 备忘(kit 实例在进程内唯一,目录随 dsh 树版本固定)。 */
502
548
  const cache = /* @__PURE__ */ new WeakMap();
@@ -1542,28 +1588,16 @@ async function startRuntime(ctx, rawConfig) {
1542
1588
  let directory;
1543
1589
  let directoryFacts;
1544
1590
  const ensureDirectory = () => {
1545
- const piEntries = [...profiles().entries()].map(([provider, profile]) => ({
1546
- provider,
1547
- displayName: profile.displayName,
1548
- settingsNs: SETTINGS_NS,
1549
- settingsPath: ["providers", provider],
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
1558
- }));
1559
- const entries = [...piEntries, ...deepseekEntries];
1591
+ const entries = buildDirectoryEntries(kit, SETTINGS_NS, profiles(), deepseekRoutes());
1560
1592
  if (deepEqualJson(entries, directoryFacts)) return;
1561
1593
  if (entries.length === 0) {
1562
1594
  directoryFacts = entries;
1563
1595
  return;
1564
1596
  }
1565
- if (directory === void 0) directory = ctx.llm.registerConfigurableProviders(entries);
1566
- else directory.replace(entries);
1597
+ commitDirectory((batch) => {
1598
+ if (directory === void 0) directory = ctx.llm.registerConfigurableProviders(batch);
1599
+ else directory.replace(batch);
1600
+ }, entries, (message) => logger.warn(message));
1567
1601
  directoryFacts = entries;
1568
1602
  };
1569
1603
  const deepseekRegistrar = new DeepseekRouteRegistrar({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/llm-pi",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、models.dev 目录兜底)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -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
- input: inputToWire(provider.input),
288
+ defaultInput: inputToWire(provider.input),
289
289
  reasoning: provider.reasoning,
290
290
  thinkingBudgets: budgetToWire(provider.thinkingBudgets),
291
291
  cacheRetention: provider.cacheRetention,
@@ -0,0 +1,73 @@
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
+ ): DirectoryEntry[] {
30
+ const piEntries = [...piProfiles.entries()].map(([provider, profile]) => ({
31
+ provider,
32
+ displayName: profile.displayName,
33
+ settingsNs,
34
+ settingsPath: ['providers', provider],
35
+ declared: !hasBuiltinProvider(kit, provider),
36
+ }))
37
+ const deepseekEntries = [...deepseekRoutes.values()].map((built) => ({
38
+ provider: built.route,
39
+ displayName: built.displayName,
40
+ settingsNs,
41
+ settingsPath: ['providers', built.route],
42
+ declared: true,
43
+ }))
44
+ return [...piEntries, ...deepseekEntries]
45
+ }
46
+
47
+ /**
48
+ * 提交目录条目;整批被原子拒绝时逐个剔除冲突条目重试(每轮至多剔一个,
49
+ * 轮数不超过条目数,必然终止)。非冲突错误原样上抛。
50
+ */
51
+ export function commitDirectory(
52
+ register: (entries: DirectoryEntry[]) => void,
53
+ entries: DirectoryEntry[],
54
+ warn: (message: string) => void,
55
+ ): void {
56
+ let rest = entries
57
+ for (let attempts = 0; attempts <= entries.length; attempts++) {
58
+ try {
59
+ register(rest)
60
+ return
61
+ } catch (error) {
62
+ const match = /configurable provider "([^"]+)" is already declared/.exec(
63
+ error instanceof Error ? error.message : String(error),
64
+ )
65
+ const next = match === null ? [] : rest.filter((entry) => entry.provider !== match[1])
66
+ if (match === null || next.length === rest.length) throw error
67
+ warn(
68
+ `llm-pi: 目录条目 "${match[1]}" 与既有声明冲突(通常为官方内置 provider 目录),已跳过该条目`,
69
+ )
70
+ rest = next
71
+ }
72
+ }
73
+ }
package/src/service.ts CHANGED
@@ -16,10 +16,10 @@ 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
24
  import { assertServiceable, buildProfiles } from './profiles.ts'
25
25
  import { buildDeepseekRoutes, type ResolvedDeepseekRoute } from './profiles-deepseek.ts'
@@ -213,29 +213,23 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
213
213
  let directory: { replace: (entries: unknown[]) => void } | undefined
214
214
  let directoryFacts: unknown
215
215
  const ensureDirectory = (): void => {
216
- const piEntries = [...profiles().entries()].map(([provider, profile]) => ({
217
- provider,
218
- displayName: profile.displayName,
219
- settingsNs: SETTINGS_NS,
220
- settingsPath: ['providers', provider],
221
- declared: !hasBuiltinProvider(kit, provider),
222
- }))
223
- const deepseekEntries = [...deepseekRoutes().values()].map((built) => ({
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]
216
+ const entries = buildDirectoryEntries(kit, SETTINGS_NS, profiles(), deepseekRoutes())
217
+ // 备忘键为目标全集(含被冲突跳过的条目):目标不变不重复尝试/告警
231
218
  if (deepEqualJson(entries, directoryFacts)) return
232
219
  if (entries.length === 0) {
233
220
  // 空目录不可注册(INVALID_DIRECTORY);等 settings 用户层供数后在 onChange 注册
234
221
  directoryFacts = entries
235
222
  return
236
223
  }
237
- if (directory === undefined) directory = ctx.llm.registerConfigurableProviders(entries as never)
238
- else directory.replace(entries)
224
+ commitDirectory(
225
+ (batch: DirectoryEntry[]) => {
226
+ if (directory === undefined)
227
+ directory = ctx.llm.registerConfigurableProviders(batch as never)
228
+ else directory.replace(batch)
229
+ },
230
+ entries,
231
+ (message) => logger.warn(message),
232
+ )
239
233
  directoryFacts = entries
240
234
  }
241
235