dsh-model-reasoning-defaults 0.0.3 → 0.0.4

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.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
1
+ import { ReasoningEffortId, isAgentLoopRequest } from "@deepseek-ai/dsh-llm";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  //#region src/index.ts
4
4
  /** 插件名(同时作为 Cordis 插件 id)。 */
5
5
  const name = "dsh-model-reasoning-defaults";
6
- /** 声明依赖的服务:LLM 服务就绪后装配;注册/读取设置命名空间需要设置服务。 */
6
+ /** 声明依赖的服务:LLM 服务就绪后装配;声明设置页策略需要设置服务。 */
7
7
  const inject = ["llm", "settings"];
8
8
  const nonBlankPattern = /^\S+$/;
9
9
  const KNOWN_REASONING_EFFORTS = /* @__PURE__ */ new Set([
@@ -15,7 +15,6 @@ const KNOWN_REASONING_EFFORTS = /* @__PURE__ */ new Set([
15
15
  "xhigh",
16
16
  "max"
17
17
  ]);
18
- const DEEPSEEK_OFFICIAL_PROVIDER_ID = "deepseek-official";
19
18
  const modelConfigSchema = z.object({
20
19
  id: z.string().pattern(nonBlankPattern),
21
20
  reasoningEffort: z.string().pattern(nonBlankPattern)
@@ -27,9 +26,16 @@ const providerConfigSchema = z.object({
27
26
  models: modelsFieldSchema
28
27
  });
29
28
  const providerEntrySchema = z.union([z.string().pattern(nonBlankPattern), providerConfigSchema]);
30
- /** Cordis 配置模式(显式注解接口类型,满足声明产物的可命名要求)。 */
29
+ /**
30
+ * Cordis 配置模式。
31
+ *
32
+ * `defaults` 声明为 volatile:设置页经宿主 configEditor 改写 profile patch 后,
33
+ * 宿主原地更新该引用(见 cordis-plugin-loader 的 volatile 提交路径),插件无需
34
+ * 重载即可读到新值。其余结构化字段保持普通字段——它们是 patch 层的书写形式,
35
+ * 变更后正常走插件重载。
36
+ */
31
37
  const Config = z.object({
32
- defaults: z.dict(z.string().pattern(nonBlankPattern), z.string().pattern(nonBlankPattern)).default({}),
38
+ defaults: z.dict(z.string().pattern(nonBlankPattern), z.string().pattern(nonBlankPattern)).default({}).volatile(),
33
39
  providers: z.dict(providerEntrySchema, z.string().pattern(nonBlankPattern)).default({}),
34
40
  models: modelsFieldSchema,
35
41
  reasoningEffort: z.string().pattern(nonBlankPattern)
@@ -51,13 +57,33 @@ function extractEffort(val) {
51
57
  return trimmed.length > 0 ? trimmed : void 0;
52
58
  }
53
59
  /**
60
+ * 归一化扁平路由字典。
61
+ *
62
+ * 键值均 trim 后判断:空白键与空白值一样永不命中任何请求路由(cordis 路径由
63
+ * schema 拒绝,此处覆盖绕开 schema 的直调路径)。
64
+ *
65
+ * @param defaults - 扁平路由字典;未配置时为 undefined。
66
+ * @returns 归一化后的键值映射。
67
+ */
68
+ function normalizeFlatDefaults(defaults) {
69
+ const result = Object.create(null);
70
+ if (!defaults || typeof defaults !== "object") return result;
71
+ for (const [rawKey, value] of Object.entries(defaults)) {
72
+ const key = rawKey.trim();
73
+ const trimmedValue = typeof value === "string" ? value.trim() : "";
74
+ if (key.length === 0 || trimmedValue.length === 0) continue;
75
+ result[key] = trimmedValue;
76
+ }
77
+ return result;
78
+ }
79
+ /**
54
80
  * 将结构化 Config 归一化为统一的扁平路由字典。
55
81
  *
56
- * @param config - 插件配置对象。
82
+ * @param config - 插件配置对象(defaults 为普通字典形式)。
57
83
  * @returns 归一化后的键值映射;键与值均已去除首尾空白。
58
84
  */
59
85
  function normalizeConfig(config) {
60
- const result = {};
86
+ const result = Object.create(null);
61
87
  const globalEffort = extractEffort(config.reasoningEffort);
62
88
  if (globalEffort) result["*"] = globalEffort;
63
89
  if (config.models) {
@@ -95,15 +121,24 @@ function normalizeConfig(config) {
95
121
  }
96
122
  }
97
123
  }
98
- if (config.defaults && typeof config.defaults === "object") for (const [rawKey, value] of Object.entries(config.defaults)) {
99
- const key = rawKey.trim();
100
- const trimmedValue = value.trim();
101
- if (key.length === 0 || trimmedValue.length === 0) continue;
102
- result[key] = trimmedValue;
103
- }
124
+ Object.assign(result, normalizeFlatDefaults(config.defaults));
104
125
  return result;
105
126
  }
106
127
  /**
128
+ * 未知推理等级的告警文案。
129
+ *
130
+ * 激活期体检与运行期读取共用同一文案:同一处配置在两条路径上都会被看到
131
+ * (激活时既做体检、又会读一次路由表用于日志),文案一致才能靠 warnOnce
132
+ * 收敛成一条日志,而不是打出两条内容几乎相同、仅差等级清单的告警。
133
+ *
134
+ * @param route - 命中的路由键。
135
+ * @param level - 配置的推理等级。
136
+ * @returns 供 logger 输出的告警文案。
137
+ */
138
+ function unknownLevelIssue(route, level) {
139
+ return "路由 " + route + " 的推理等级 \"" + level + "\" 不在已知集合 (" + [...KNOWN_REASONING_EFFORTS].join("/") + ") 内,请确认目标模型支持";
140
+ }
141
+ /**
107
142
  * 静态体检插件配置,返回人类可读的问题清单(纯函数,不修改任何数据)。
108
143
  *
109
144
  * schemastery 的对象属性全部可选且对未知键透传,"拼错字段""模型项缺 id"
@@ -116,9 +151,8 @@ function normalizeConfig(config) {
116
151
  */
117
152
  function collectConfigIssues(config) {
118
153
  const issues = [];
119
- const knownLevels = [...KNOWN_REASONING_EFFORTS].join("/");
120
154
  const checkLevel = (route, level) => {
121
- if (level !== void 0 && !KNOWN_REASONING_EFFORTS.has(level)) issues.push("路由 " + route + " 的推理等级 \"" + level + "\" 不在已知集合 (" + knownLevels + ") 内,请确认目标模型支持");
155
+ if (level !== void 0 && !KNOWN_REASONING_EFFORTS.has(level)) issues.push(unknownLevelIssue(route, level));
122
156
  };
123
157
  /**
124
158
  * 检查单个模型条目:字符串简写直接校验等级;
@@ -256,227 +290,57 @@ function applyDefaultReasoningEffort(config, defaults) {
256
290
  reasoningEffort: ReasoningEffortId(effort)
257
291
  };
258
292
  }
259
- /**
260
- * 设置页 UI 专用命名空间。该命名空间由本插件注册为可写设置项,浏览器设置
261
- * 面板通过宿主统一的 settings API 编辑它;生效优先级位于自动提取之上、
262
- * cordis 插件配置之下(见 resolveEffectiveDefaults)。
263
- */
264
- const UI_DEFAULTS_NAMESPACE = "model-reasoning-defaults";
265
- const uiDefaultsSectionSchema = z.object({ defaults: z.dict(z.string().pattern(nonBlankPattern), z.string().pattern(nonBlankPattern)).default({}) });
266
- /**
267
- * 取当前上下文上的设置服务;不存在或 get 不可调用时返回 undefined。
268
- *
269
- * apply() 的注入门槛判定与本提取路径共用这一个函数,避免两处标准不一致
270
- * 造成"装了包装层却永远提取不到数据"的错位。
271
- */
272
- function settingsService(ctx) {
273
- const settings = ctx.settings;
274
- if (!settings || typeof settings.get !== "function") return void 0;
275
- return settings;
276
- }
277
293
  const warnedMessages = /* @__PURE__ */ new Set();
278
- /** 经当前上下文的 logger 发出一次性告警。 */
294
+ /** 发出一次性(进程内按文案去重)的插件告警,自动带上插件名前缀。 */
279
295
  function warnOnce(ctx, message) {
280
296
  if (warnedMessages.has(message)) return;
281
297
  warnedMessages.add(message);
282
298
  ctx.logger?.warn?.("[" + name + "] " + message);
283
299
  }
284
300
  /**
285
- * 创建一条提取结果的记录器:等级 trim 后写入映射;不在已知集合时一次性告警
286
- * 但仍注入,让适配器的逐字匹配给出最终裁决(避免本地白名单过期后挡住上游
287
- * 新增的等级)。设置提取位于每次请求的热路径上,告警按消息去重防止刷屏。
288
- *
289
- * @param ctx - Cordis 上下文。
290
- * @param extracted - 提取结果的目标映射。
291
- * @returns 记录函数。
292
- */
293
- function createEffortRecorder(ctx, extracted) {
294
- return (routeKey, level) => {
295
- if (typeof level !== "string") return;
296
- const trimmedLevel = level.trim();
297
- if (trimmedLevel.length === 0) return;
298
- if (!KNOWN_REASONING_EFFORTS.has(trimmedLevel)) warnOnce(ctx, "设置中路由 " + routeKey + " 的推理等级 \"" + trimmedLevel + "\" 不在已知集合内,请确认目标模型支持");
299
- extracted[routeKey] = trimmedLevel;
300
- };
301
- }
302
- /**
303
- * 从当前 Cordis 上下文的设置服务中读取各 Provider 与模型的默认推理等级。
304
- *
305
- * 字段约定(每个来源只读单一规范字段,不做别名兜底):
306
- * - llm-pi-ai:provider 级 `reasoning` 是宿主原生声明字段;模型级
307
- * `models[].reasoning` 与 `modelOverrides.<model>.reasoning` 是本插件约定
308
- * 读取的扩展字段,依赖其设置解析对未声明键的透传行为。宿主在模型层另有
309
- * 原生字段 `reasoningEfforts`,但那是"可选等级集合 + 线格式映射"的能力
310
- * 声明而非默认值选择,语义不同,本插件不读取;若上游收紧对未声明键的
311
- * 解析策略,`reasoning` 这部分提取会静默失效。
312
- * - llm-deepseek:顶层 `reasoningEffort` 为宿主原生字段,映射到其注册的
313
- * provider 路由 deepseek-official(见 DEEPSEEK_OFFICIAL_PROVIDER_ID)。
301
+ * 读取一层 fiber 当前生效的路由表。
314
302
  *
315
- * 整个函数不包 try/catch:宿主的 settings.get 对未注册命名空间返回 undefined
316
- * 且不会抛错,函数体已对全部外部数据做了形状防御,剩余可能抛错的只有代码
317
- * 缺陷,应当让它暴露,而不是吞掉后以空映射继续主流程。
303
+ * 结构化配置为底、volatile defaults 快照覆盖。手工编辑 profile patch 时
304
+ * defaults 走的也是 volatile 原地更新(不会重载插件、不再有激活期体检),
305
+ * 因此未知等级在这里一次性告警,避免静默降级。
318
306
  *
319
- * @param ctx - Cordis 上下文。
320
- * @returns 提取出的路由默认映射字典。
307
+ * @param source - 一层 fiber 的路由表来源。
308
+ * @returns 合并后的扁平路由映射;两层均为空时为空对象。
321
309
  */
322
- function extractSettingsDefaults(ctx) {
323
- const extracted = {};
324
- const settings = settingsService(ctx);
325
- if (!settings) return extracted;
326
- const record = createEffortRecorder(ctx, extracted);
327
- const piAi = settings.get("llm-pi-ai");
328
- if (piAi?.providers && typeof piAi.providers === "object") for (const [providerId, providerProfile] of Object.entries(piAi.providers)) {
329
- if (!providerId || !providerProfile || typeof providerProfile !== "object") continue;
330
- if (providerId === DEEPSEEK_OFFICIAL_PROVIDER_ID) {
331
- warnOnce(ctx, "llm-pi-ai 中名为 deepseek-official 的 provider 无法注册该路由,已跳过其推理等级提取");
332
- continue;
333
- }
334
- record(providerId + ":*", providerProfile.reasoning);
335
- if (Array.isArray(providerProfile.models)) for (const m of providerProfile.models) {
336
- const id = m && typeof m === "object" && typeof m.id === "string" ? m.id.trim() : "";
337
- if (id.length === 0) continue;
338
- record(providerId + ":" + id, m.reasoning);
339
- }
340
- if (providerProfile.modelOverrides && typeof providerProfile.modelOverrides === "object") for (const [modelId, override] of Object.entries(providerProfile.modelOverrides)) {
341
- const id = modelId.trim();
342
- if (id.length === 0 || !override || typeof override !== "object") continue;
343
- record(providerId + ":" + id, override.reasoning);
344
- }
345
- }
346
- const deepseek = settings.get("llm-deepseek");
347
- if (deepseek && typeof deepseek === "object") record("deepseek-official:*", deepseek.reasoningEffort);
348
- return extracted;
349
- }
350
- /**
351
- * 从本插件专用的设置命名空间读取设置页 UI 写入的路由表。
352
- *
353
- * 该命名空间由 apply() 注册、浏览器设置面板写入,是"设置里改默认推理强度"
354
- * 的存储层;每次请求实时读取,改动即时生效。形状非法(非对象/缺字段)时
355
- * 返回空映射——schema 校验已保证写入合法,这里的防御只覆盖外部手工编辑
356
- * settings 文档的场景。
357
- *
358
- * @param ctx - Cordis 上下文。
359
- * @returns 路由映射字典。
360
- */
361
- function extractUiNamespaceDefaults(ctx) {
362
- const extracted = {};
363
- const settings = settingsService(ctx);
364
- if (!settings) return extracted;
365
- const section = settings.get(UI_DEFAULTS_NAMESPACE);
366
- if (!section || typeof section !== "object") return extracted;
367
- const record = createEffortRecorder(ctx, extracted);
368
- const defaults = section.defaults;
369
- if (defaults && typeof defaults === "object") for (const [rawKey, level] of Object.entries(defaults)) {
370
- const key = rawKey.trim();
371
- if (key.length === 0) continue;
372
- record(key, level);
373
- }
374
- return extracted;
375
- }
376
- const UI_NS_RETRY_INTERVAL_MS = 250;
377
- const UI_NS_RETRY_MAX_ATTEMPTS = 240;
378
- /**
379
- * 在设置服务上注册本插件的可编辑命名空间(跨热重载安全)。
380
- *
381
- * 每次激活都先直接尝试注册——不缓存"已注册"标记,因为旧代的卸载会把命名空间
382
- * 一并注销,缓存的真值会在"停用后再启用"的场景下变成谎言。撞上
383
- * "already registered" 说明旧代尚未释放,进入短周期轮询直到键位空出;
384
- * 其它错误告警后放弃,不阻断主流程(patch 层配置仍然生效)。
385
- *
386
- * @param ctx - 当前插件上下文。
387
- * @param settings - 当前上下文的设置服务。
388
- */
389
- function registerUiDefaultsNamespace(ctx, settings) {
390
- if (settings.register === void 0) {
391
- warnOnce(ctx, "当前设置服务不支持命名空间注册,设置页将无法编辑推理等级默认值");
392
- return;
393
- }
394
- const attempt = () => {
395
- try {
396
- settings.register(UI_DEFAULTS_NAMESPACE, uiDefaultsSectionSchema, { applies: "live" });
397
- return "ok";
398
- } catch (error) {
399
- if (!String(error).includes("already registered")) {
400
- ctx.logger?.warn?.("[" + name + "] 注册设置命名空间失败,设置页将无法编辑推理等级默认值:" + String(error));
401
- return "ok";
402
- }
403
- return "busy";
404
- }
310
+ function readRouteTable(source) {
311
+ const live = normalizeFlatDefaults(source.defaults.get());
312
+ for (const [route, level] of Object.entries(live)) if (!KNOWN_REASONING_EFFORTS.has(level)) warnOnce(source.ctx, unknownLevelIssue(route, level));
313
+ return {
314
+ ...source.structured,
315
+ ...live
405
316
  };
406
- if (attempt() !== "busy") return;
407
- let attempts = 0;
408
- const timer = setInterval(() => {
409
- attempts += 1;
410
- if (attempts > UI_NS_RETRY_MAX_ATTEMPTS) {
411
- clearInterval(timer);
412
- warnOnce(ctx, "等待旧代释放设置命名空间超时,本次运行设置页不可用;请重启宿主或再次重载本插件");
413
- return;
414
- }
415
- if (attempt() === "ok") clearInterval(timer);
416
- }, UI_NS_RETRY_INTERVAL_MS);
417
- ctx.effect(() => () => {
418
- clearInterval(timer);
419
- }, name + ": stop UI namespace registration retry");
420
317
  }
421
318
  const runtimePatchStateKey = Symbol.for("dsh-model-reasoning-defaults.runtime-patch-state");
422
319
  /**
423
- * 取最后激活的配置层。热重载期间新 fiber 覆盖旧 fiber,旧 fiber 卸载后
320
+ * 取最后激活的路由表来源。热重载期间新 fiber 覆盖旧 fiber,旧 fiber 卸载后
424
321
  * 新配置仍保持生效;最后一个 fiber 卸载时才还原原始服务方法。
425
322
  *
426
323
  * @param state - 同一 LLM 服务上的包装状态。
427
- * @returns 当前生效的 defaults;没有活跃层时返回 undefined。
324
+ * @returns 当前生效的来源;没有活跃层时返回 undefined。
428
325
  */
429
- function currentDefaults(state) {
326
+ function currentSource(state) {
430
327
  let current;
431
- for (const defaults of state.defaults.values()) current = defaults;
328
+ for (const source of state.sources.values()) current = source;
432
329
  return current;
433
330
  }
434
331
  /**
435
- * 解析当前生效的完整 defaults 映射。三个配置层按优先级从低到高合并:
436
- * 设置服务自动提取项(llm-pi-ai / llm-deepseek 原生字段)→ 本插件设置
437
- * 命名空间(设置页 UI 写入)→ 插件自身 cordis 配置(patch 层)。
438
- * 同一路由键上高优先级层覆盖低优先级层。
332
+ * 将当前活跃层的路由表应用到配置。保留原对象是卸载后仍被外部持有的旧包装的
333
+ * 必要语义,也避免把一个空对象伪装成真实配置来源。
439
334
  *
440
- * @param ctx - 当前共享包装层绑定的上下文(热重载后为新 fiber 的 ctx)。
441
335
  * @param state - 同一 LLM 服务上的包装状态。
442
- * @returns 合并后的路由映射;三层均为空时为空对象。
443
- */
444
- function resolveEffectiveDefaults(ctx, state) {
445
- const pluginDefaults = currentDefaults(state) ?? {};
446
- const uiStoreDefaults = extractUiNamespaceDefaults(ctx);
447
- return {
448
- ...extractSettingsDefaults(ctx),
449
- ...uiStoreDefaults,
450
- ...pluginDefaults
451
- };
452
- }
453
- /**
454
- * 将当前活跃层应用到配置。保留原对象是卸载后仍被外部持有的旧包装的必要语义,
455
- * 也避免把一个空对象伪装成真实配置来源。
456
- *
457
- * @param state - 同一 LLM 服务上的包装状态(ctx 从 state 读取,保证热重载后
458
- * 提取路径跟随最新 fiber)。
459
336
  * @param config - 待处理的调用配置。
460
337
  * @returns 原配置或带默认推理等级的浅复制对象。
461
338
  */
462
339
  function applyCurrentDefault(state, config) {
463
- const defaults = resolveEffectiveDefaults(state.ctx, state);
464
- return Object.keys(defaults).length === 0 ? config : applyDefaultReasoningEffort(config, defaults);
465
- }
466
- /**
467
- * 判断请求是否已经进入 Agent Loop 的不可变边界。
468
- *
469
- * `isAgentLoopRequest()` 背后是 `@deepseek-ai/dsh-llm` 模块副本私有的
470
- * `WeakSet`。宿主和插件若解析到不同副本,宿主写入的标识不会被插件读取,
471
- * 入口包装就会错误地复制 Loop 请求。这里改用 DSH 公开的请求契约:Loop
472
- * 在交给 LLM 前会把完整请求顶层冻结,并写入用于路由的 `sessionId`;冻结的
473
- * 手写辅助请求(例如 compaction)没有 `sessionId`,仍应允许插件复制并补齐默认值。
474
- *
475
- * @param options - 进入 `llm.stream` 的完整请求。
476
- * @returns 是否应按原引用透传该请求。
477
- */
478
- function isAgentLoopRequestBoundary(options) {
479
- return options.sessionId !== void 0 && Object.isFrozen(options);
340
+ const source = currentSource(state);
341
+ if (source === void 0) return config;
342
+ const routeTable = readRouteTable(source);
343
+ return Object.keys(routeTable).length === 0 ? config : applyDefaultReasoningEffort(config, routeTable);
480
344
  }
481
345
  /**
482
346
  * 从当前 stream 包装上恢复跨热重载共享的状态。
@@ -492,19 +356,18 @@ function existingRuntimePatchState(llm) {
492
356
  *
493
357
  * 手写请求在 stream 入口浅复制补齐后再进 waterfall,保证每个监听器只处理
494
358
  * 一次;loop 构建的请求由宿主打 markAgentLoopRequest 进程标识并深冻结,
495
- * 经公开的冻结与 sessionId 契约判定后按原引用透传——浅复制会丢失请求身份。
359
+ * 经 isAgentLoopRequest 判定后按原引用透传——浅复制会丢失该进程标识。
496
360
  *
497
- * @param ctx - 当前插件上下文。
498
361
  * @param llm - LLM 服务实例。
499
362
  * @returns 可复用的包装状态。
500
363
  */
501
- function createRuntimePatchState(ctx, llm) {
364
+ function createRuntimePatchState(llm) {
502
365
  const originalStream = llm.stream;
503
366
  const originalPrepareCall = llm.prepareCall;
504
367
  const originalResolveCallConfig = llm.resolveCallConfig;
505
368
  let state;
506
369
  const stream = (options) => {
507
- const patched = isAgentLoopRequestBoundary(options) ? options : applyCurrentDefault(state, options);
370
+ const patched = isAgentLoopRequest(options) ? options : applyCurrentDefault(state, options);
508
371
  return originalStream.call(llm, patched);
509
372
  };
510
373
  const prepareCall = async (config, signal) => {
@@ -514,8 +377,7 @@ function createRuntimePatchState(ctx, llm) {
514
377
  return originalResolveCallConfig.call(llm, applyCurrentDefault(state, config), signal);
515
378
  };
516
379
  state = {
517
- ctx,
518
- defaults: /* @__PURE__ */ new Map(),
380
+ sources: /* @__PURE__ */ new Map(),
519
381
  originalStream,
520
382
  originalPrepareCall,
521
383
  originalResolveCallConfig,
@@ -529,24 +391,27 @@ function createRuntimePatchState(ctx, llm) {
529
391
  /**
530
392
  * 安装一层可卸载的 LLM 服务包装。
531
393
  *
532
- * 每个插件 fiber 只持有自己的配置层。这样新 fiber 先装配、旧 fiber 后卸载时,
533
- * 不会把服务方法恢复为已经失效的旧包装。
394
+ * 每个插件 fiber 只持有自己的路由表来源。这样新 fiber 先装配、旧 fiber 后卸载
395
+ * 时,不会把服务方法恢复为已经失效的旧包装。
396
+ *
397
+ * 路由表为空时同样安装:设置页可以在插件运行期间写入新路由,跳过安装会让这些
398
+ * 改动直到宿主重启才生效。
534
399
  *
535
400
  * @param ctx - 当前插件上下文。
536
401
  * @param llm - LLM 服务实例。
537
- * @param defaults - 默认推理等级映射。
402
+ * @param source - 本 fiber 的路由表来源。
403
+ * @returns 共享的包装状态。
538
404
  */
539
- function installRuntimePatches(ctx, llm, defaults) {
540
- const state = existingRuntimePatchState(llm) ?? createRuntimePatchState(ctx, llm);
541
- state.ctx = ctx;
405
+ function installRuntimePatches(ctx, llm, source) {
406
+ const state = existingRuntimePatchState(llm) ?? createRuntimePatchState(llm);
542
407
  if (llm.stream !== state.stream) llm.stream = state.stream;
543
408
  if (llm.prepareCall !== state.prepareCall) llm.prepareCall = state.prepareCall;
544
409
  if (llm.resolveCallConfig !== state.resolveCallConfig) llm.resolveCallConfig = state.resolveCallConfig;
545
410
  const token = Symbol(name);
546
- state.defaults.set(token, defaults);
411
+ state.sources.set(token, source);
547
412
  ctx.effect(() => () => {
548
- state.defaults.delete(token);
549
- if (state.defaults.size !== 0) return;
413
+ state.sources.delete(token);
414
+ if (state.sources.size !== 0) return;
550
415
  if (llm.stream === state.stream) llm.stream = state.originalStream;
551
416
  if (llm.prepareCall === state.prepareCall) llm.prepareCall = state.originalPrepareCall;
552
417
  if (llm.resolveCallConfig === state.resolveCallConfig) llm.resolveCallConfig = state.originalResolveCallConfig;
@@ -557,25 +422,33 @@ function installRuntimePatches(ctx, llm, defaults) {
557
422
  * 安装默认推理等级注入逻辑。
558
423
  *
559
424
  * @param ctx - 当前插件上下文。
560
- * @param config - 插件配置。
425
+ * @param config - Cordis 解析后的插件配置。
561
426
  */
562
- function apply(ctx, config = {}) {
563
- const defaults = normalizeConfig(config);
564
- for (const issue of collectConfigIssues(config)) ctx.logger?.warn?.("[" + name + "] " + issue);
565
- const settings = settingsService(ctx);
566
- if (settings) registerUiDefaultsNamespace(ctx, settings);
567
- if (Object.keys(defaults).length === 0 && !settings) {
568
- ctx.logger?.info?.("[" + name + "] 未配置推理等级默认值且无设置服务,跳过注入");
569
- return;
570
- }
571
- const llm = ctx.llm;
572
- const state = installRuntimePatches(ctx, llm, defaults);
427
+ function apply(ctx, config) {
428
+ const structured = normalizeConfig({
429
+ providers: config.providers,
430
+ models: config.models,
431
+ reasoningEffort: config.reasoningEffort
432
+ });
433
+ for (const issue of collectConfigIssues({
434
+ defaults: config.defaults.get(),
435
+ providers: config.providers,
436
+ models: config.models,
437
+ reasoningEffort: config.reasoningEffort
438
+ })) warnOnce(ctx, issue);
439
+ ctx.effect(() => ctx.settings.configure({ auto: false }, ctx.fiber));
440
+ const source = {
441
+ ctx,
442
+ structured,
443
+ defaults: config.defaults
444
+ };
445
+ const state = installRuntimePatches(ctx, ctx.llm, source);
573
446
  registerAgentRequestListener(ctx, async function(_payload, next) {
574
447
  return applyCurrentDefault(state, await next());
575
448
  });
576
- ctx.logger?.info?.("[" + name + "] 已激活:routes=" + Object.keys(defaults).join(", "));
449
+ ctx.logger?.info?.("[" + name + "] 已激活:routes=" + Object.keys(readRouteTable(source)).join(", "));
577
450
  }
578
451
  //#endregion
579
- export { Config, UI_DEFAULTS_NAMESPACE, apply, applyDefaultReasoningEffort, extractSettingsDefaults, extractUiNamespaceDefaults, inject, name, normalizeConfig };
452
+ export { Config, apply, applyDefaultReasoningEffort, inject, name, normalizeConfig };
580
453
 
581
454
  //# sourceMappingURL=index.mjs.map