@dsh-plus/llm-pi 0.1.5 → 0.1.7

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/src/service.ts CHANGED
@@ -18,10 +18,12 @@ import { deepEqualJson, installSettingsSection } from '@deepseek-ai/dsh-settings
18
18
 
19
19
  import { hasBuiltinProvider } from './catalog/builtin.ts'
20
20
  import { ModelsDevSource } from './catalog/models-dev.ts'
21
- import { Config, SETTINGS_NS, type LlmPiConfig } from './config.ts'
21
+ import { Config, type LlmPiConfig, SETTINGS_NS } from './config.ts'
22
+ import { DeepseekRouteRegistrar } from './deepseek-routes.ts'
22
23
  import { discoverModels } from './discovery.ts'
23
24
  import { assertServiceable, buildProfiles } from './profiles.ts'
24
- import { resolveDshKit, type DshKit } from './resolve-dsh.ts'
25
+ import { buildDeepseekRoutes, type ResolvedDeepseekRoute } from './profiles-deepseek.ts'
26
+ import { type DshKit, resolveDshKit } from './resolve-dsh.ts'
25
27
 
26
28
  export interface LlmPiRuntime {
27
29
  /** 当前生效配置(settings 用户层解析结果或 cordis 行级 config)。 */
@@ -46,7 +48,10 @@ function registrationFacts(profiles: ReadonlyMap<string, ResolvedPiAiProviderPro
46
48
 
47
49
  /** 凭据解析(逐行对齐官方 resolveApiKey):凭据服务优先,缺失时启动环境兜底。 */
48
50
  function makeResolveApiKey(ctx: Context, kit: DshKit) {
49
- return async (provider: string, profile: ResolvedPiAiProviderProfile): Promise<string | undefined> => {
51
+ return async (
52
+ provider: string,
53
+ profile: ResolvedPiAiProviderProfile,
54
+ ): Promise<string | undefined> => {
50
55
  const ref = profile.apiKeyEnv
51
56
  if (ref === undefined) return undefined
52
57
  const credentials = ctx.get('credentials')
@@ -54,7 +59,8 @@ function makeResolveApiKey(ctx: Context, kit: DshKit) {
54
59
  credentials !== undefined
55
60
  ? (await credentials.resolve(ref as CredentialRef))?.value
56
61
  : launchEnvironmentOf(ctx).get(ref as unknown as string)?.value
57
- if (hit !== undefined && hit.length > 0) return kit.assertUsableApiKey(hit, 'llm-pi', ref as unknown as string)
62
+ if (hit !== undefined && hit.length > 0)
63
+ return kit.assertUsableApiKey(hit, 'llm-pi', ref as unknown as string)
58
64
  throw new kit.LlmError(
59
65
  `llm-pi: provider route "${provider}" 的凭据引用 ${String(ref)} 未解析到值——` +
60
66
  '请经凭据服务(web Models 页)存放或导出环境变量;仅当该 provider 应使用 pi-ai 自有环境发现时才移除 apiKeyEnv',
@@ -85,6 +91,7 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
85
91
  let current: () => LlmPiConfig = () => config
86
92
  let lastRaw: LlmPiConfig | undefined
87
93
  let memoized: Map<string, ResolvedPiAiProviderProfile> | undefined
94
+ let memoizedDeepseek: Map<string, ResolvedDeepseekRoute> | undefined
88
95
  const deps = { kit, modelsDev }
89
96
  /** 当前已解析 profiles,按原始 config identity 备忘(官方同款模式)。
90
97
  * 运行期走 lenient:数据源漂移时降级/跳过并告警,而非抛错弄挂整个 route。 */
@@ -92,12 +99,28 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
92
99
  const raw = current()
93
100
  if (raw === lastRaw && memoized !== undefined) return memoized
94
101
  const next = raw.enabled
95
- ? buildProfiles(raw.providers, { ...deps, lenient: true, warn: (message) => logger.warn(message) })
102
+ ? buildProfiles(raw.providers, {
103
+ ...deps,
104
+ lenient: true,
105
+ warn: (message) => logger.warn(message),
106
+ })
107
+ : new Map()
108
+ memoizedDeepseek = raw.enabled
109
+ ? buildDeepseekRoutes(raw.providers, {
110
+ kit,
111
+ lenient: true,
112
+ warn: (message) => logger.warn(message),
113
+ })
96
114
  : new Map()
97
115
  lastRaw = raw
98
116
  memoized = next
99
117
  return next
100
118
  }
119
+ /** deepseek 路由物化表(与 profiles 同一次备忘窗口)。 */
120
+ const deepseekRoutes = (): Map<string, ResolvedDeepseekRoute> => {
121
+ profiles()
122
+ return memoizedDeepseek ?? new Map()
123
+ }
101
124
  profiles() // 行级 config 不可服务则启动即失败(官方同款 fail-fast)
102
125
 
103
126
  const adapter = new kit.PiAiAdapter({
@@ -132,7 +155,10 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
132
155
  let registrations: RegistrationGroup[] | undefined
133
156
  let registeredFacts: unknown
134
157
 
135
- const registerGroup = (routes: string[], fallback: (error: unknown) => void): RegistrationGroup[] => {
158
+ const registerGroup = (
159
+ routes: string[],
160
+ fallback: (error: unknown) => void,
161
+ ): RegistrationGroup[] => {
136
162
  try {
137
163
  const handle = ctx.llm.registerAdapter(routes, adapter as never)
138
164
  return [{ routes, handle }]
@@ -144,7 +170,9 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
144
170
  const handle = ctx.llm.registerAdapter([route], adapter as never)
145
171
  groups.push({ routes: [route], handle })
146
172
  } catch (routeError) {
147
- logger.error(`llm-pi: route "${route}" 注册失败(可能与其他 adapter 重名),该 route 不可用`)
173
+ logger.error(
174
+ `llm-pi: route "${route}" 注册失败(可能与其他 adapter 重名),该 route 不可用`,
175
+ )
148
176
  logger.error(routeError)
149
177
  }
150
178
  }
@@ -168,9 +196,11 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
168
196
  })
169
197
  } else {
170
198
  try {
171
- registrations[0]!.handle.replace(routes)
172
- for (let i = 1; i < registrations.length; i += 1) registrations[i]!.handle.replace([])
173
- registrations = [{ routes, handle: registrations[0]!.handle }]
199
+ const [first, ...rest] = registrations
200
+ if (first === undefined) throw new Error('registrations 为空(此前整批注册全部失败)')
201
+ first.handle.replace(routes)
202
+ for (const group of rest) group.handle.replace([])
203
+ registrations = [{ routes, handle: first.handle }]
174
204
  } catch (error) {
175
205
  // 原子 replace 被拒(含冲突):保留此前注册(官方同款护栏)
176
206
  logger.error('llm-pi: 更新被拒,保留此前注册的 route')
@@ -183,13 +213,21 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
183
213
  let directory: { replace: (entries: unknown[]) => void } | undefined
184
214
  let directoryFacts: unknown
185
215
  const ensureDirectory = (): void => {
186
- const entries = [...profiles().entries()].map(([provider, profile]) => ({
216
+ const piEntries = [...profiles().entries()].map(([provider, profile]) => ({
187
217
  provider,
188
218
  displayName: profile.displayName,
189
219
  settingsNs: SETTINGS_NS,
190
220
  settingsPath: ['providers', provider],
191
221
  declared: !hasBuiltinProvider(kit, provider),
192
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]
193
231
  if (deepEqualJson(entries, directoryFacts)) return
194
232
  if (entries.length === 0) {
195
233
  // 空目录不可注册(INVALID_DIRECTORY);等 settings 用户层供数后在 onChange 注册
@@ -201,14 +239,21 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
201
239
  directoryFacts = entries
202
240
  }
203
241
 
242
+ const deepseekRegistrar = new DeepseekRouteRegistrar({
243
+ ctx,
244
+ kit,
245
+ logger: { warn: (m) => logger.warn(m), error: (m) => logger.error(m) },
246
+ routes: deepseekRoutes,
247
+ })
248
+ const ensureDeepseek = (): void => deepseekRegistrar.sync(deepseekRoutes())
249
+
204
250
  ensureRegistration()
251
+ ensureDeepseek()
205
252
  ensureDirectory()
206
253
 
207
- let settingsBound = false
208
254
  installSettingsSection(ctx, SETTINGS_NS, Config, config, {
209
255
  validate: (cfg: LlmPiConfig) => assertServiceable(cfg, deps),
210
256
  setSource: (source: () => LlmPiConfig) => {
211
- settingsBound = true
212
257
  current = source
213
258
  },
214
259
  onChange: () => {
@@ -218,6 +263,12 @@ export async function startRuntime(ctx: Context, rawConfig: LlmPiConfig): Promis
218
263
  logger.error('llm-pi: 更新被拒,保留此前注册的 route')
219
264
  logger.error(error)
220
265
  }
266
+ try {
267
+ ensureDeepseek()
268
+ } catch (error) {
269
+ logger.error('llm-pi: deepseek route 更新失败,保留此前注册')
270
+ logger.error(error)
271
+ }
221
272
  try {
222
273
  ensureDirectory()
223
274
  } catch (error) {