@gehennawu/dsh-service 2.0.7 → 2.0.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/index.js CHANGED
@@ -20,6 +20,46 @@ import { createSessionsRoutes } from './session-routes.js'
20
20
  import { createQuotaRoutes } from './quota-routes.js'
21
21
  import { createSubagentRoutes } from './subagent-routes.js'
22
22
  import { createBackupRoutes } from './backup-routes.js'
23
+ import { createModelConfigRoutes } from './model-config-routes.js'
24
+ import { createProviderAvailability, installProviderAvailability } from './provider-availability.js'
25
+ import { createWorkbenchStore, loadPiAiCatalog } from './model-workbench.js'
26
+ import {
27
+ WORKBENCH_SIDECAR_VERSION,
28
+ applySuggestions,
29
+ applyUndo,
30
+ arbitrateModelSuggestions,
31
+ buildPiAiIndex,
32
+ abilityPatch,
33
+ consensusFields,
34
+ createFreshnessManager,
35
+ deriveKeyRefFor,
36
+ diffModelLists,
37
+ emptyWorkbenchSidecar,
38
+ fetchModelsProbe,
39
+ hostnameOf,
40
+ indexModelParamsDev,
41
+ isPrivateIpAddress,
42
+ loadModelParamsSnapshot,
43
+ loadWorkbenchSidecar,
44
+ modelParamsCandidates,
45
+ modelParamsDevEfforts,
46
+ modelPatchFromPiAi,
47
+ openRouterModelFacts,
48
+ openRouterVerifySource,
49
+ pickOpenRouterMatch,
50
+ modelsProbeLookup,
51
+ normalizeBaseURLForMatch,
52
+ parseModelListing,
53
+ probeBaseURLCheck,
54
+ profileFingerprint,
55
+ readLlmProfiles,
56
+ saveModelParamsSnapshot,
57
+ saveWorkbenchSidecar,
58
+ selfCheckModelsPatch,
59
+ suggestRouteId,
60
+ suggestionId,
61
+ undoPreviewModels,
62
+ } from './model-workbench.js'
23
63
  import { collectPluginHealth, pluginCheckItem, restartPluginEntry } from './plugin-health.js'
24
64
  import {
25
65
  buildCliproxyAccountPlan as buildCliproxyAccountPlanAdapter,
@@ -88,6 +128,8 @@ const DEFAULT_FEATURE_SETTINGS = Object.freeze({
88
128
  // v1.8 新增:composer 模型按钮显示厂家/渠道图标(宽态加在模型名前,
89
129
  // 窄态替换官方默认图标;未适配渠道保留官方默认图标)。
90
130
  modelProviderIcons: true,
131
+ // v1.10 新增:模型配置工作台(供应商与模型 / 子代理路由),写路径经两段式确认。
132
+ modelCatalog: true,
91
133
  })
92
134
  /** 标记为「可热更新」的 Config 字段(0.1.7-alpha.1 的表单与原地热更新以此为准)。 */
93
135
  const volatileField = (schema) => (typeof schema?.extra === 'function' ? schema.extra('volatile', true) : schema)
@@ -4236,7 +4278,7 @@ function subagentRouteCandidates(request, config, options = {}) {
4236
4278
 
4237
4279
  function resolveSubagentInjection(request, config, options = {}) {
4238
4280
  for (const candidate of subagentRouteCandidates(request, config, options)) {
4239
- if (options.isRoutable !== undefined && !options.isRoutable(candidate.provider)) {
4281
+ if (options.isRoutable !== undefined && !options.isRoutable(candidate.provider, candidate.model)) {
4240
4282
  options.note?.(`${candidate.provider}/${candidate.model} skipped (not routable)`)
4241
4283
  continue
4242
4284
  }
@@ -5348,6 +5390,18 @@ function apply(ctx, featureConfig) {
5348
5390
  // 版本一变即自然失效,不必手工清理。
5349
5391
  const releaseNotesCache = new Map()
5350
5392
  const quotaThrottle = createQuotaThrottle()
5393
+ // 模型配置工作台(v1.10):侧车仓库持有写前快照/绑定/验证摘要/人工恢复态。
5394
+ // 写串行化在仓库内部;目录随 DSH 升级才变,进程内缓存一次。
5395
+ const modelProfileDir = ctx.get('profileContext')?.dir ?? process.env.DSH_PROFILE_DIR
5396
+ const workbenchStore = createWorkbenchStore(typeof modelProfileDir === 'string' && modelProfileDir !== '' ? modelProfileDir : join(dshHome, 'dsh-service-model-config', 'legacy'))
5397
+ const providerAvailability = createProviderAvailability(workbenchStore)
5398
+ const notifyProviderAvailability = () => ctx.emit('llm/adapters-updated')
5399
+ ctx.inject(['llm'], (scope) => {
5400
+ const llm = scope.get('llm') ?? ctx.get('llm')
5401
+ if (!llm) return
5402
+ const dispose = installProviderAvailability(llm, providerAvailability, notifyProviderAvailability)
5403
+ scope.effect(() => dispose)
5404
+ })
5351
5405
  // 技能管理(v0.22):侧车索引缓存 + 批量补全状态。批量随 Fiber 销毁中止。
5352
5406
  // 可重赋值的 let 一律以 { current } 箱体跨模块传递:端点工厂解构是值快照,
5353
5407
  // let 重赋值不会穿过解构边界;箱体靠对象身份保持活绑定。
@@ -5432,7 +5486,8 @@ function apply(ctx, featureConfig) {
5432
5486
  const hasActivation = typeof subagents?.startActivation === 'function'
5433
5487
  const hasLegacyStarts = typeof subagents?.start === 'function' && typeof subagents?.startContinuable === 'function'
5434
5488
  if (!hasActivation && !hasLegacyStarts) return
5435
- const isRoutable = (provider) => {
5489
+ const isRoutable = (provider, model) => {
5490
+ if (model !== undefined && !providerAvailability.modelEnabled(provider, model)) return false
5436
5491
  const llm = ctx.get('llm')
5437
5492
  if (llm === undefined || typeof llm.listProviders !== 'function') return false
5438
5493
  try {
@@ -5483,6 +5538,8 @@ function apply(ctx, featureConfig) {
5483
5538
  return quotaProviderUnusable(view) === false
5484
5539
  }
5485
5540
  const applyInjection = (request) => {
5541
+ const explicit = request?.agentOptions?.provider
5542
+ if (typeof explicit === 'string' && explicit) providerAvailability.assertEnabled(explicit, request?.agentOptions?.model)
5486
5543
  // 功能关闭:零记录(完全静默,与原生行为一致)——记录是 subagentRoute 功能的一部分。
5487
5544
  if (!featureEnabled('subagentRoute')) return { request, dispatch: undefined }
5488
5545
  const routeOptions = {
@@ -5493,7 +5550,7 @@ function apply(ctx, featureConfig) {
5493
5550
  }
5494
5551
  const injected = resolveSubagentInjection(request, subagentRouteRef.current, routeOptions)
5495
5552
  const routeCandidates = injected === undefined ? [] : subagentRouteCandidates(request, subagentRouteRef.current, routeOptions)
5496
- .filter((candidate) => isRoutable(candidate.provider) && isQuotaHealthy(candidate.provider) !== false)
5553
+ .filter((candidate) => isRoutable(candidate.provider, candidate.model) && isQuotaHealthy(candidate.provider) !== false)
5497
5554
  .filter((candidate, index, candidates) => candidates.findIndex((item) => item.provider === candidate.provider && item.model === candidate.model) === index)
5498
5555
  const injectedIndex = routeCandidates.findIndex((candidate) => candidate.provider === injected?.provider && candidate.model === injected?.model)
5499
5556
  const eligibleRoutes = injectedIndex < 0 ? [] : routeCandidates.slice(injectedIndex)
@@ -5525,17 +5582,20 @@ function apply(ctx, featureConfig) {
5525
5582
  const originalStart = subagents.start
5526
5583
  const originalStartContinuable = subagents.startContinuable
5527
5584
  if (hasActivation) {
5528
- subagents.startActivation = (spec) => {
5585
+ subagents.startActivation = async (spec) => {
5586
+ await providerAvailability.loading
5529
5587
  const { request: decorated, dispatch } = applyInjection(spec.request)
5530
5588
  if (dispatch === undefined) return originalStartActivation.call(subagents, spec)
5531
5589
  return runWithDispatch(dispatch, () => originalStartActivation.call(subagents, { ...spec, request: decorated }))
5532
5590
  }
5533
5591
  } else {
5534
- subagents.start = (name, request) => {
5592
+ subagents.start = async (name, request) => {
5593
+ await providerAvailability.loading
5535
5594
  const { request: decorated, dispatch } = applyInjection(request)
5536
5595
  return runWithDispatch(dispatch, () => originalStart.call(subagents, name, decorated))
5537
5596
  }
5538
- subagents.startContinuable = (spec) => {
5597
+ subagents.startContinuable = async (spec) => {
5598
+ await providerAvailability.loading
5539
5599
  const { request: decorated, dispatch } = applyInjection(spec.request)
5540
5600
  return runWithDispatch(dispatch, () => originalStartContinuable.call(subagents, { ...spec, request: decorated }))
5541
5601
  }
@@ -6201,8 +6261,29 @@ function apply(ctx, featureConfig) {
6201
6261
  createSkillsRoutes({ ctx, describeJobs, dshHome, makeDescribeJobLogger, registerSkillCall, serializeSkillsIndexWrite, skillFileCache, skillsActiveControllers, skillsBatchRef, skillsIndexRef, SKILL_DESCRIPTION_MAX_CHARS, SKILL_USAGE_MAX_CHARS, bodyHashOf, describeSkillDraft, evaluateSkillFile, fixLegacySkillInvocationKeys, listSkillModels, locateSkillFrontmatter, mutateSkillEntryById, name, normalizeSkillDescribeLang, publicSkillEntry, rpcTechnicalFailure, sanitizeSkillDraftText, scanSkillEntries, selectSkillBatchCandidates, setSkillInvocationKey }),
6202
6262
  createSessionsRoutes({ ctx, dshHome, sessionBytesCache, sessionDeletePlans, sessionTitleCache, sessionTitlesReady, sessionViewCache, SESSIONS_BYTES_MAX_IDS, SESSIONS_DELETE_PLAN_TTL_MS, SESSIONS_VIEW_PAGE_SIZE, listSessionsForManage, loadDeletedSessions, name, resolveSessionBytesForIds, resolveSessionForDelete, rpcFailure, rpcTechnicalFailure, saveDeletedSessions, searchSessionsContent, sessionExists, sessionIsLive, viewSessionPage }),
6203
6263
  createQuotaRoutes({ ctx, kickQuotaRefresh, quotaThrottle, refreshQuotaConfigCache, serializeQuotaConfigWrite, MAX_QUOTA_PROVIDER_NAME, MAX_QUOTA_RESET_CARDS, MAX_QUOTA_RESET_CARDS_PER_PROVIDER, MAX_QUOTA_RESET_CARD_ACCOUNT, canonicalResetCardExpiresAt, QUOTA_ADAPTER_BY_KIND, name, quotaCredentialConfigured, quotaCredentialEndpoint, quotaCredentialHintNames, readQuotaProfiles, resolveQuotaKind, rpcTechnicalFailure }),
6204
- createSubagentRoutes({ ctx, dispatchRing, serializeSubagentRouteWrite, subagentRouteRef, subagentRouteLoadPromise, subagentSeamRef, MAX_SUBAGENT_ROUTE_FIELD, SUBAGENT_ROUTE_FALLBACK_MAX, SUBAGENT_ROUTE_MODES, listSubagentDispatches, listSubagentModels, rpcTechnicalFailure }),
6264
+ createSubagentRoutes({ ctx, providerAvailability, dispatchRing, serializeSubagentRouteWrite, subagentRouteRef, subagentRouteLoadPromise, subagentSeamRef, MAX_SUBAGENT_ROUTE_FIELD, SUBAGENT_ROUTE_FALLBACK_MAX, SUBAGENT_ROUTE_MODES, listSubagentDispatches, listSubagentModels, rpcTechnicalFailure }),
6205
6265
  createBackupRoutes({ ctx, backupIntegrity, backupProgress, clearBackupProgress, downloadTokens, dshHome, dshVersion, setBackupProgress, withBackupLock, backupNameVersionSuffix, createBackup, deleteBackup, exportBackup, formatBackupTimestamp, importBackup, listBackups, listLocalProfiles, name, rpcFailure }),
6266
+ createModelConfigRoutes({
6267
+ ctx,
6268
+ loadPiCatalog: loadPiAiCatalog,
6269
+ workbenchStore,
6270
+ providerAvailability,
6271
+ notifyProviderAvailability,
6272
+ // 移除守卫的只读引用面:子代理路由配置 + 官方默认模型选择(都是宿主侧事实,浏览器不参与)。
6273
+ loadSubagentRoute: async () => {
6274
+ await subagentRouteLoadPromise
6275
+ return subagentRouteRef.current
6276
+ },
6277
+ loadAgentDefaultModelSelection: () => {
6278
+ const service = ctx.get('agentDefaultModel')
6279
+ if (service === undefined || typeof service.currentSelection !== 'function') return undefined
6280
+ return service.currentSelection()
6281
+ },
6282
+ writeSubagentRoute: serializeSubagentRouteWrite,
6283
+ listSubagentModels,
6284
+ dshHome,
6285
+ pluginVersion,
6286
+ }),
6206
6287
  ])
6207
6288
 
6208
6289
  const dispatchEndpoint = createRpcDispatcher({ endpoints: rpcEndpoints, featureEnabled, logger: ctx.logger })
@@ -6312,6 +6393,45 @@ export {
6312
6393
  saveUnifiedConfig,
6313
6394
  updateUnifiedConfigSection,
6314
6395
  sanitizeSettingsNavConfig,
6396
+ // 模型配置工作台(v1.10):领域纯函数经 index.js 再导出,测试从 '../index.js' 导入的
6397
+ // 既有约定保持不变(旧分支 test/workbench.test.js 即此形状)。
6398
+ WORKBENCH_SIDECAR_VERSION,
6399
+ applySuggestions,
6400
+ applyUndo,
6401
+ arbitrateModelSuggestions,
6402
+ buildPiAiIndex,
6403
+ createWorkbenchStore,
6404
+ deriveKeyRefFor,
6405
+ diffModelLists,
6406
+ emptyWorkbenchSidecar,
6407
+ fetchModelsProbe,
6408
+ hostnameOf,
6409
+ isPrivateIpAddress,
6410
+ loadWorkbenchSidecar,
6411
+ modelPatchFromPiAi,
6412
+ modelsProbeLookup,
6413
+ normalizeBaseURLForMatch,
6414
+ parseModelListing,
6415
+ probeBaseURLCheck,
6416
+ profileFingerprint,
6417
+ readLlmProfiles,
6418
+ saveWorkbenchSidecar,
6419
+ selfCheckModelsPatch,
6420
+ suggestRouteId,
6421
+ suggestionId,
6422
+ undoPreviewModels,
6423
+ // P3.5 补充源(modelparams.dev)与多源共识:同样经 index.js 再导出供测试直达。
6424
+ abilityPatch,
6425
+ consensusFields,
6426
+ createFreshnessManager,
6427
+ indexModelParamsDev,
6428
+ loadModelParamsSnapshot,
6429
+ modelParamsCandidates,
6430
+ modelParamsDevEfforts,
6431
+ openRouterModelFacts,
6432
+ openRouterVerifySource,
6433
+ pickOpenRouterMatch,
6434
+ saveModelParamsSnapshot,
6315
6435
  }
6316
6436
  // 插件 Config:0.1.7-alpha.1 起宿主配置面由 Profile 的插件 Config 托管;声明它即可让
6317
6437
  // 功能开关出现在插件管理页,并让宿主经 `loader/volatile-update` 热更新(无需重启)。