dsh-model-reasoning-defaults 0.0.4 → 0.0.6
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/README.md +8 -6
- package/lib/index.mjs +18 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
- **仓库**:<https://github.com/xht-code/dsh-model-reasoning-defaults>
|
|
11
11
|
|
|
12
12
|
> [!IMPORTANT]
|
|
13
|
-
> 本版本(0.0.
|
|
13
|
+
> 本版本(0.0.5+)面向 **DSH 0.1.7-alpha.2 及以上的设置体系**:设置项由 profile entry 的 `Config` schema 派生,可编辑字段必须声明为 `volatile`,写回落在 profile patch。旧版宿主(0.1.6 及以前)请使用 0.0.3。
|
|
14
|
+
>
|
|
15
|
+
> 插件把 `@deepseek-ai/schemastery` 声明为**运行时依赖**(`~3.18.4`,`.volatile()` 自该版本提供)而非交给 profile 解析的 peer:`dsh plugin` 生成的 profile 使用 `autoInstallPeers: false`,如果 profile 中已存在更低的 schemastery(例如与 `dsh-remote-terminal` 共用的 3.18.2),peer 会被解析成不满足的版本,插件将在加载期直接失败。
|
|
14
16
|
|
|
15
17
|
## 安装 / 更新
|
|
16
18
|
|
|
@@ -40,7 +42,7 @@ dsh plugin --profile web remove dsh-model-reasoning-defaults
|
|
|
40
42
|
- **设置页可视化编辑**:在 Web 设置页「推理等级默认」中直接增删路由并选择等级。路由表存放在插件自身的配置项里并声明为 `volatile`,宿主原地更新引用而不重载插件,保存后对新请求立即生效(带修订号并发保护)。
|
|
41
43
|
- **全入口统一注入**:无缝覆盖 Agent 主循环(`agent/request`)、直接调用(`llm.stream`)以及预准备入口(`prepareCall` / `resolveCallConfig`)。
|
|
42
44
|
- **对象安全与不可变性保证**:采用非破坏性浅复制机制,安全处理 `Object.freeze` 深冻结请求,原请求对象与未命中的配置始终保持只读与纯净。
|
|
43
|
-
-
|
|
45
|
+
- **跨副本的 Loop 边界识别**:不依赖 `@deepseek-ai/dsh-llm` 副本私有的 `WeakSet`,而按公开请求契约识别同时具备 `sessionId` 与顶层冻结状态的 Loop 请求,避免宿主与插件解析到不同模块副本时误复制请求。
|
|
44
46
|
- **无缝热重载(Hot-Reload Safe)**:基于 `RuntimePatchState` 管理多 Fiber 状态,配置变更即时生效,仅在最后一个插件 Fiber 卸载时还原原始方法,杜绝方法悬挂。
|
|
45
47
|
|
|
46
48
|
---
|
|
@@ -157,7 +159,7 @@ dsh plugin --profile web remove dsh-model-reasoning-defaults
|
|
|
157
159
|
【Agent Loop 请求】 【手写 / 辅助请求】
|
|
158
160
|
(agent/request Waterfall) (llm.stream / prepareCall)
|
|
159
161
|
│ │
|
|
160
|
-
|
|
162
|
+
注入默认 reasoningEffort sessionId + 冻结边界判定
|
|
161
163
|
到调用配置种子(loop 随后据此构建完整 │
|
|
162
164
|
请求并深冻结、打宿主进程标识) ┌─────────────┴─────────────┐
|
|
163
165
|
│ ▼ ▼
|
|
@@ -174,12 +176,12 @@ dsh plugin --profile web remove dsh-model-reasoning-defaults
|
|
|
174
176
|
|
|
175
177
|
### 1. Agent 主循环请求处理
|
|
176
178
|
- 注册全局 `agent/request` waterfall 监听器。此阶段操作的是调用配置种子,完整请求对象尚未构建;插件按路由计算默认等级并注入 `reasoningEffort`。
|
|
177
|
-
- loop
|
|
178
|
-
- 请求进入 `llm.stream`
|
|
179
|
+
- loop 随后将该配置连同派生消息组装为完整请求,写入 `sessionId`、深冻结并打上宿主 `markAgentLoopRequest` 进程标识。
|
|
180
|
+
- 请求进入 `llm.stream` 入口时,入口包装按公开的 `sessionId + Object.freeze` 契约识别 loop 请求,**直接按原引用透传**。不读取模块副本私有的 `isAgentLoopRequest` 标识,避免宿主与插件加载不同 `dsh-llm` 副本时误复制请求。
|
|
179
181
|
|
|
180
182
|
### 2. 手写及辅助调用处理
|
|
181
183
|
- 直接调用 `llm.stream(options)` 时,入口包装在 waterfall **开始前**执行拦截。
|
|
182
|
-
-
|
|
184
|
+
- 对不带该 Loop 边界契约的请求(包括带 `purpose: 'compaction'` 的辅助请求),通过浅复制补齐 `reasoningEffort` 后再进入下游链路。
|
|
183
185
|
|
|
184
186
|
### 3. 配置准备与解析入口
|
|
185
187
|
- 同步拦截 `llm.prepareCall` 与 `llm.resolveCallConfig`,在预计算阶段统一应用相同的路由查找规则,并完整透传 `AbortSignal`。
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReasoningEffortId
|
|
1
|
+
import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
/** 插件名(同时作为 Cordis 插件 id)。 */
|
|
@@ -343,6 +343,21 @@ function applyCurrentDefault(state, config) {
|
|
|
343
343
|
return Object.keys(routeTable).length === 0 ? config : applyDefaultReasoningEffort(config, routeTable);
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
346
|
+
* 判断请求是否已经进入 Agent Loop 的不可变边界。
|
|
347
|
+
*
|
|
348
|
+
* `isAgentLoopRequest()` 背后是 `@deepseek-ai/dsh-llm` 模块副本私有的
|
|
349
|
+
* `WeakSet`。宿主和插件若解析到不同副本,宿主写入的标识不会被插件读取,
|
|
350
|
+
* 入口包装就会错误地复制 Loop 请求。这里改用 DSH 公开的请求契约:Loop
|
|
351
|
+
* 在交给 LLM 前会把完整请求顶层冻结,并写入用于路由的 `sessionId`;冻结的
|
|
352
|
+
* 手写辅助请求(例如 compaction)没有 `sessionId`,仍应允许插件复制并补齐默认值。
|
|
353
|
+
*
|
|
354
|
+
* @param options - 进入 `llm.stream` 的完整请求。
|
|
355
|
+
* @returns 是否应按原引用透传该请求。
|
|
356
|
+
*/
|
|
357
|
+
function isAgentLoopRequestBoundary(options) {
|
|
358
|
+
return options.sessionId !== void 0 && Object.isFrozen(options);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
346
361
|
* 从当前 stream 包装上恢复跨热重载共享的状态。
|
|
347
362
|
*
|
|
348
363
|
* @param llm - LLM 服务实例。
|
|
@@ -356,7 +371,7 @@ function existingRuntimePatchState(llm) {
|
|
|
356
371
|
*
|
|
357
372
|
* 手写请求在 stream 入口浅复制补齐后再进 waterfall,保证每个监听器只处理
|
|
358
373
|
* 一次;loop 构建的请求由宿主打 markAgentLoopRequest 进程标识并深冻结,
|
|
359
|
-
*
|
|
374
|
+
* 经公开的冻结与 sessionId 契约判定后按原引用透传——浅复制会丢失请求身份。
|
|
360
375
|
*
|
|
361
376
|
* @param llm - LLM 服务实例。
|
|
362
377
|
* @returns 可复用的包装状态。
|
|
@@ -367,7 +382,7 @@ function createRuntimePatchState(llm) {
|
|
|
367
382
|
const originalResolveCallConfig = llm.resolveCallConfig;
|
|
368
383
|
let state;
|
|
369
384
|
const stream = (options) => {
|
|
370
|
-
const patched =
|
|
385
|
+
const patched = isAgentLoopRequestBoundary(options) ? options : applyCurrentDefault(state, options);
|
|
371
386
|
return originalStream.call(llm, patched);
|
|
372
387
|
};
|
|
373
388
|
const prepareCall = async (config, signal) => {
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * dsh-model-reasoning-defaults\n *\n * 为每个模型配置默认推理等级。请求已显式指定 reasoningEffort 时保持原值;\n * 其余按 provider/model 路由查找默认值并转换为 DSH 的品牌类型。\n *\n * 路由表存放在插件自身的配置项中(profile patch 里 id 为\n * model-reasoning-defaults 的 `defaults` 字段),DSH 设置页经 settings Remote\n * 写入。该字段声明为 volatile:宿主原地更新配置引用而不重载插件,因此每次\n * 请求都读取当下快照,设置页保存后立即对新请求生效。\n *\n * agent-loop 请求在 agent/request waterfall 中注入(此后到达 llm/stream 时\n * 已深冻结);手写调用在 llm.stream 入口复制后再进入 waterfall,调用配置\n * 方法走同一共享包装层。\n *\n * @module dsh-model-reasoning-defaults\n */\nimport type { Context, EventOptions } from '@deepseek-ai/cordis'\nimport type { Volatile } from '@deepseek-ai/cosmokit'\nimport { isAgentLoopRequest, ReasoningEffortId } from '@deepseek-ai/dsh-llm'\nimport type {\n LlmCallConfig,\n LlmRuntime,\n} from '@deepseek-ai/dsh-llm'\n// Type-only:拉入 ctx.settings 的声明增强。设置在运行时由宿主装载,本插件不做\n// 运行时导入(package.json 里的 peer 声明的是所需的最低宿主版本)。\nimport type {} from '@deepseek-ai/dsh-settings'\nimport z from '@deepseek-ai/schemastery'\n\n/** 插件名(同时作为 Cordis 插件 id)。 */\nexport const name = 'dsh-model-reasoning-defaults'\n\n/** 声明依赖的服务:LLM 服务就绪后装配;声明设置页策略需要设置服务。 */\nexport const inject = ['llm', 'settings'] as const\n\n/** 单个模型的推理等级配置。 */\nexport interface ModelConfig {\n /** 模型 ID(数组形式必填;字典形式以键名为准,可省略)。 */\n id?: string\n /** 默认推理等级(如 off | minimal | low | medium | high | xhigh | max)。 */\n reasoningEffort?: string\n}\n\n/** 单个模型配置输入:既可以是等级字符串简写,也可以是配置对象。 */\nexport type ModelConfigInput = string | ModelConfig\n\n/** 单个 Provider 的推理等级配置。 */\nexport interface ProviderConfig {\n /** 该 Provider 下所有模型的默认推理等级。 */\n reasoningEffort?: string\n /** 该 Provider 下各模型的推理等级配置(字典或对象列表)。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n}\n\n/** Provider 配置输入:既可以是等级字符串简写(作用于该 Provider 所有模型),也可以是配置对象。 */\nexport type ProviderConfigInput = string | ProviderConfig\n\n/**\n * 用户书写的插件配置:支持按 Provider 与模型嵌入式层级配置,也可使用扁平路由\n * 字典 defaults。两种形式是同等的一等配置入口,归一化到同一张路由表后统一参与\n * 六级匹配。\n *\n * 合并后的匹配优先级从高到低为:\n * - \"provider:model\":精确匹配(来自 providers[p].models[m] 或 defaults[\"provider:model\"])\n * - \"provider:*\":该 provider 下所有模型(来自 providers[p].reasoningEffort 或 defaults[\"provider:*\"])\n * - \"provider/*\":斜杠写法(来自 defaults[\"provider/*\"])\n * - \"provider\":裸 provider 写法(来自 defaults[\"provider\"])\n * - \"*:model\":跨 provider 的同名模型(来自 top-level models[m] 或 defaults[\"*:model\"])\n * - \"*\":全局匹配(来自 top-level reasoningEffort 或 defaults[\"*\"])\n */\nexport interface Config {\n /**\n * 扁平路由字典:键为上表六级路由键,值为推理等级。该字段是设置页的编辑对象,\n * 与结构化配置产生同键时覆盖结构化项。\n */\n defaults?: Record<string, string>\n /** 按 Provider 组织的嵌入式配置。 */\n providers?: Record<string, ProviderConfigInput>\n /** 跨 Provider 适用的模型默认配置(等价于 \"*:model\")。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n /** 全局默认推理等级(等价于 \"*\")。 */\n reasoningEffort?: string\n}\n\nconst nonBlankPattern = /^\\S+$/\n\n// 已知推理等级集合:llm-pi-ai 的 ModelThinkingLevel(off/minimal/low/medium/high/\n// xhigh/max)与 llm-deepseek(off/low/high/max,为其子集)的并集。仅用于告警提示\n// 而不阻断注入——最终合法性由适配器逐字匹配裁决,本地白名单收窄反而会在上游新增\n// 等级时挡住合法配置。\nconst KNOWN_REASONING_EFFORTS: ReadonlySet<string> = new Set([\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n 'max',\n])\n\nconst modelConfigSchema = z.object({\n id: z.string().pattern(nonBlankPattern),\n reasoningEffort: z.string().pattern(nonBlankPattern),\n})\n\nconst modelEntrySchema = z.union([\n z.string().pattern(nonBlankPattern),\n modelConfigSchema,\n])\n\nconst modelsFieldSchema = z.union([\n z.dict(modelEntrySchema, z.string().pattern(nonBlankPattern)),\n z.array(modelEntrySchema),\n])\n\nconst providerConfigSchema = z.object({\n reasoningEffort: z.string().pattern(nonBlankPattern),\n models: modelsFieldSchema,\n})\n\nconst providerEntrySchema = z.union([\n z.string().pattern(nonBlankPattern),\n providerConfigSchema,\n])\n\n/**\n * Cordis 解析后的配置形状:`defaults` 已由 schemastery 换成 volatile 引用,\n * 其余字段与书写形状一致。\n *\n * 该接口显式写出而非由 `Schemastery.TypeT` 推导,是为了让 `Config` 的注解保持\n * 可命名(推导会展开成内联结构类型,声明产物随之膨胀且绑死 schemastery 内部\n * 泛型);注解本身由编译器与真实 schema 交叉校验,两者不一致会直接报错。\n */\nexport interface ResolvedConfig {\n /** 设置页编辑的扁平路由表引用。 */\n defaults: Volatile<Record<string, string>>\n /** 按 Provider 组织的嵌入式配置。 */\n providers?: Record<string, ProviderConfigInput>\n /** 跨 Provider 适用的模型默认配置。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n /** 全局默认推理等级。 */\n reasoningEffort?: string\n}\n\n/**\n * Cordis 配置模式。\n *\n * `defaults` 声明为 volatile:设置页经宿主 configEditor 改写 profile patch 后,\n * 宿主原地更新该引用(见 cordis-plugin-loader 的 volatile 提交路径),插件无需\n * 重载即可读到新值。其余结构化字段保持普通字段——它们是 patch 层的书写形式,\n * 变更后正常走插件重载。\n */\nexport const Config: z<Config, ResolvedConfig> = z.object({\n // 路由键与等级值都要求非空白:空白键永远不会被 candidateKeys 命中,空白值\n // 会被适配器逐字匹配拒绝,都在配置阶段直接报错暴露最省排查成本。\n // .default({}) 与 schemastery 对 dict 的内建空对象默认重复,保留它是为了让\n // 编程直调路径(绕过 cordis resolveConfig)也能拿到确定的形状。\n defaults: z.dict(\n z.string().pattern(nonBlankPattern),\n z.string().pattern(nonBlankPattern),\n ).default({}).volatile(),\n providers: z.dict(\n providerEntrySchema,\n z.string().pattern(nonBlankPattern),\n ).default({}),\n models: modelsFieldSchema,\n reasoningEffort: z.string().pattern(nonBlankPattern),\n})\n\n/**\n * 从字符串简写或配置对象中提取非空推理等级。\n *\n * 只认单一规范字段 reasoningEffort,不做多字段兜底链:同义多源读取会在字段\n * 冲突时按书写顺序静默裁决(例如 reasoningEffort 与 effort 同时配置时无声地\n * 二选一),掩盖配置错误;确需别名应由用户层显式映射后再传入。\n *\n * @param val - 等级字符串,或 ModelConfig/ProviderConfig 形状的对象。\n * @returns trim 后非空的等级字符串;无法提取时返回 undefined。\n */\nfunction extractEffort(val: unknown): string | undefined {\n const candidate = typeof val === 'string'\n ? val\n : (typeof val === 'object' && val !== null)\n ? (val as { reasoningEffort?: unknown }).reasoningEffort\n : undefined\n if (typeof candidate !== 'string') return undefined\n const trimmed = candidate.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * 归一化扁平路由字典。\n *\n * 键值均 trim 后判断:空白键与空白值一样永不命中任何请求路由(cordis 路径由\n * schema 拒绝,此处覆盖绕开 schema 的直调路径)。\n *\n * @param defaults - 扁平路由字典;未配置时为 undefined。\n * @returns 归一化后的键值映射。\n */\nfunction normalizeFlatDefaults(defaults: Record<string, string> | undefined): Record<string, string> {\n // 空原型累加器:路由键是任意非空白字符串,普通对象上给 __proto__ 赋值不会生成\n // 自有属性。schema 解析本身就会丢弃 __proto__ 键,这里保证的是本函数对任意\n // 普通对象输入(含绕过 schema 的直调)都成立。\n const result: Record<string, string> = Object.create(null)\n if (!defaults || typeof defaults !== 'object') return result\n for (const [rawKey, value] of Object.entries(defaults)) {\n const key = rawKey.trim()\n const trimmedValue = typeof value === 'string' ? value.trim() : ''\n if (key.length === 0 || trimmedValue.length === 0) continue\n result[key] = trimmedValue\n }\n return result\n}\n\n/**\n * 将结构化 Config 归一化为统一的扁平路由字典。\n *\n * @param config - 插件配置对象(defaults 为普通字典形式)。\n * @returns 归一化后的键值映射;键与值均已去除首尾空白。\n */\nexport function normalizeConfig(config: Config): Record<string, string> {\n // 与 normalizeFlatDefaults 一致用空原型:合并 defaults 时若键为 __proto__,\n // 普通对象上的赋值不会生成自有属性,路由会被静默丢掉。\n const result: Record<string, string> = Object.create(null)\n\n // 1. 全局默认 (对应 \"*\")\n const globalEffort = extractEffort(config.reasoningEffort)\n if (globalEffort) {\n result['*'] = globalEffort\n }\n\n // 2. 顶层跨 Provider 模型默认 (对应 \"*:model\")\n if (config.models) {\n if (Array.isArray(config.models)) {\n for (const item of config.models) {\n // 数组形式必须携带非空白 id 才能生成路由键;缺失项由 apply() 的\n // 激活期诊断告警上报,这里只负责安静地跳过无效形状。\n const id = typeof item === 'object' && item !== null && typeof item.id === 'string' ? item.id.trim() : ''\n if (id.length === 0) continue\n const effort = extractEffort(item)\n if (effort) result['*:' + id] = effort\n }\n } else if (typeof config.models === 'object') {\n for (const [rawModelId, modelVal] of Object.entries(config.models)) {\n const modelId = rawModelId.trim()\n if (modelId.length === 0) continue\n const effort = extractEffort(modelVal)\n if (effort) result['*:' + modelId] = effort\n }\n }\n }\n\n // 3. Provider 层级配置\n if (config.providers && typeof config.providers === 'object') {\n for (const [rawProviderId, providerVal] of Object.entries(config.providers)) {\n const providerId = rawProviderId.trim()\n if (providerId.length === 0) continue\n\n // 3.1 Provider 级默认 (对应 \"provider:*\")\n const providerEffort = extractEffort(providerVal)\n if (providerEffort) {\n result[providerId + ':*'] = providerEffort\n }\n\n // 3.2 Provider 内的具体模型 (对应 \"provider:model\")\n if (typeof providerVal === 'object' && providerVal !== null && providerVal.models) {\n const models = providerVal.models\n if (Array.isArray(models)) {\n for (const item of models) {\n const id = typeof item === 'object' && item !== null && typeof item.id === 'string' ? item.id.trim() : ''\n if (id.length === 0) continue\n const effort = extractEffort(item)\n if (effort) result[providerId + ':' + id] = effort\n }\n } else if (typeof models === 'object') {\n for (const [rawModelId, modelVal] of Object.entries(models)) {\n const modelId = rawModelId.trim()\n if (modelId.length === 0) continue\n const effort = extractEffort(modelVal)\n if (effort) result[providerId + ':' + modelId] = effort\n }\n }\n }\n }\n }\n\n // 4. 扁平 defaults 字典:与结构化配置同等的一等入口,写在最后使其在与结构化\n // 配置同键冲突时胜出(显式路由键的表达意图更具体)。\n Object.assign(result, normalizeFlatDefaults(config.defaults))\n\n return result\n}\n\ntype ReasoningConfig = Pick<LlmCallConfig, 'provider' | 'model' | 'reasoningEffort'>\n\n/**\n * 未知推理等级的告警文案。\n *\n * 激活期体检与运行期读取共用同一文案:同一处配置在两条路径上都会被看到\n * (激活时既做体检、又会读一次路由表用于日志),文案一致才能靠 warnOnce\n * 收敛成一条日志,而不是打出两条内容几乎相同、仅差等级清单的告警。\n *\n * @param route - 命中的路由键。\n * @param level - 配置的推理等级。\n * @returns 供 logger 输出的告警文案。\n */\nfunction unknownLevelIssue(route: string, level: string): string {\n return '路由 ' + route + ' 的推理等级 \"' + level + '\" 不在已知集合 ('\n + [...KNOWN_REASONING_EFFORTS].join('/') + ') 内,请确认目标模型支持'\n}\n\n/**\n * 静态体检插件配置,返回人类可读的问题清单(纯函数,不修改任何数据)。\n *\n * schemastery 的对象属性全部可选且对未知键透传,\"拼错字段\"\"模型项缺 id\"\n * 这类错误能通过校验却在 normalizeConfig 中被静默忽略;此清单在插件激活时\n * 一次性告警,把这类静默降级变成可见信息。等级值不在已知集合也在此提示,\n * 但仍会注入——最终是否可用交给适配器逐字匹配裁决。\n *\n * @param config - 用户原始配置(未经归一化)。\n * @returns 问题描述列表;无问题时为空数组。\n */\nfunction collectConfigIssues(config: Config): string[] {\n const issues: string[] = []\n\n const checkLevel = (route: string, level: string | undefined): void => {\n if (level !== undefined && !KNOWN_REASONING_EFFORTS.has(level)) {\n issues.push(unknownLevelIssue(route, level))\n }\n }\n\n /**\n * 检查单个模型条目:字符串简写直接校验等级;\n * 对象形式要求携带有效等级,数组元素还要求有效 id。\n */\n const checkModelEntry = (route: string, entry: ModelConfigInput, requireId: boolean): void => {\n if (typeof entry === 'string') {\n // 数组位置没有键名可用作 id,字符串简写永远生成不了路由键;字典位置的\n // 字符串值以键名为 id(requireId=false),不受此限制。\n if (requireId) {\n issues.push('数组形式模型条目缺少有效 id(字符串简写无键名可用),已被忽略:' + JSON.stringify(entry))\n return\n }\n checkLevel(route, extractEffort(entry))\n return\n }\n const id = typeof entry.id === 'string' ? entry.id.trim() : ''\n if (requireId && id.length === 0) {\n issues.push('模型条目缺少有效 id,已被忽略:' + JSON.stringify(entry))\n return\n }\n const effort = extractEffort(entry)\n if (effort === undefined) {\n issues.push('模型条目未包含 reasoningEffort,已被忽略:' + JSON.stringify(entry))\n return\n }\n checkLevel(route, effort)\n }\n\n // 全局等级\n checkLevel('*', extractEffort(config.reasoningEffort))\n\n // 顶层跨 Provider 模型\n if (config.models) {\n if (Array.isArray(config.models)) {\n config.models.forEach((entry, index) => {\n checkModelEntry('*:<models[' + index + ']>', entry, true)\n })\n } else if (typeof config.models === 'object') {\n for (const [modelId, entry] of Object.entries(config.models)) {\n checkModelEntry('*:' + modelId, entry, false)\n }\n }\n }\n\n // Provider 层级\n if (config.providers && typeof config.providers === 'object') {\n for (const [providerId, providerVal] of Object.entries(config.providers)) {\n if (typeof providerVal === 'string') {\n checkLevel(providerId + ':*', extractEffort(providerVal))\n continue\n }\n const hasModels = providerVal.models !== undefined\n const providerEffort = extractEffort(providerVal)\n if (!hasModels && providerEffort === undefined) {\n issues.push('provider ' + providerId + ' 未包含 reasoningEffort 或 models,整条配置已被忽略')\n continue\n }\n checkLevel(providerId + ':*', providerEffort)\n if (hasModels) {\n const models = providerVal.models!\n if (Array.isArray(models)) {\n models.forEach((entry, index) => {\n checkModelEntry(providerId + ':<models[' + index + ']>', entry, true)\n })\n } else if (typeof models === 'object') {\n for (const [modelId, entry] of Object.entries(models)) {\n checkModelEntry(providerId + ':' + modelId, entry, false)\n }\n }\n }\n }\n }\n\n // 扁平 defaults 字典:schema 在解析阶段就拒绝空白键值,所以这里只对绕过 schema\n // 的直调形态生效(手工构造 ResolvedConfig 直接调用 apply,例如测试或其它插件)。\n // 空键检查与 normalizeFlatDefaults 的跳过逻辑一致,把静默降级变成可见告警。\n if (config.defaults && typeof config.defaults === 'object') {\n for (const [rawKey, value] of Object.entries(config.defaults)) {\n const key = rawKey.trim()\n if (key.length === 0) {\n issues.push('扁平路由键为空白,已被忽略:' + JSON.stringify(rawKey))\n continue\n }\n checkLevel(key, extractEffort(value))\n }\n }\n\n return issues\n}\n\n// agent/request 的 loop 侧 emit 实参是 { turn, step, signal };core/agent 的派发器\n// 会把路由主体 agent(Scoped<Agent>)融合进 payload 并同时作为监听器 this,因此\n// 实收 payload 含 agent 字段。本监听器不读取任何 payload 字段,此处仅按本插件\n// 用到的形状窄化 ctx.on 的签名。\ntype AgentRequestPayload = {\n turn: number\n step: number\n signal: AbortSignal\n}\n\ntype AgentRequestListener = (\n this: object,\n payload: AgentRequestPayload,\n next: () => Promise<LlmCallConfig>,\n) => Promise<LlmCallConfig>\n\ntype AgentRequestRegistrar = (\n name: 'agent/request',\n listener: AgentRequestListener,\n options?: boolean | EventOptions,\n) => () => boolean\n\n/**\n * 注册全局 agent/request waterfall。\n *\n * cordis 的 ctx.on 内部会把监听器注册为当前 fiber 上的 effect(见 events\n * 服务的 register()),无论 global 标志如何都会在 fiber 卸载时自动清理;\n * global 只影响事件派发时的上下文过滤。因此无需再包一层 ctx.effect。\n *\n * 该事件的运行时接收者是 Scoped<Agent>,Cordis 根 Context 的事件泛型表不会\n * 暴露它,因此在最小边界内窄化签名(仅约束本插件用到的事件名与参数)。\n *\n * @param ctx - 当前插件上下文。\n * @param listener - agent/request waterfall 监听器。\n */\nfunction registerAgentRequestListener(ctx: Context, listener: AgentRequestListener): void {\n // 窄化仅覆盖本插件实际使用的事件名与参数;内部转调仍通过 ctx.on 的真实实现。\n const register = ctx.on.bind(ctx) as AgentRequestRegistrar\n register('agent/request', listener, { global: true })\n}\n\n/**\n * 生成一个路由的候选配置键。\n *\n * 兼容键保留在明确的优先级表中,避免不同调用入口各自实现一套匹配规则。\n *\n * @param provider - 请求使用的 provider。\n * @param model - 请求使用的 model。\n * @returns 按匹配优先级排列的候选键。\n */\nfunction candidateKeys(provider: string, model: string): readonly string[] {\n return [\n provider + ':' + model,\n provider + ':*',\n provider + '/*',\n provider,\n '*:' + model,\n '*',\n ]\n}\n\n/**\n * 查找一个路由对应的默认推理等级。\n *\n * @param defaults - 归一化后的扁平路由映射。\n * @param provider - 请求使用的 provider。\n * @param model - 请求使用的 model。\n * @returns 找到的非空推理等级,未找到时返回 undefined。\n */\nfunction lookupEffort(\n defaults: Readonly<Record<string, string>>,\n provider: string,\n model: string,\n): string | undefined {\n for (const key of candidateKeys(provider, model)) {\n const effort = defaults[key]\n if (typeof effort === 'string' && effort.length > 0) return effort\n }\n return undefined\n}\n\n/**\n * 在不修改原对象的前提下应用默认推理等级。\n *\n * 该函数同时用于 GenerateOptions 和 LlmCallConfig,因此调用方可以保留\n * 原请求对象中的 messages、signal 等其它字段,并安全处理深冻结输入。\n *\n * 第二参数只接受归一化后的扁平路由表;结构化 Config 必须先经 normalizeConfig。\n * 曾尝试用对象形状嗅探让本函数同时接纳两种入参,但路由表允许任意非空白键,\n * 含 \"models\"/\"providers\" 等键的路由表会被误判成结构化配置(整表归一化为空、\n * 注入全部失效),反之仅含顶层 reasoningEffort 的最小结构化配置又会漏判——\n * 两类误判都是静默失效,因此彻底拆掉形状嗅探。\n *\n * @param config - 待补齐的 LLM 请求或调用配置。\n * @param defaults - normalizeConfig 产出的扁平路由表。\n * @returns 原配置或带有 reasoningEffort 的浅复制对象。\n */\nexport function applyDefaultReasoningEffort<T extends ReasoningConfig>(\n config: T,\n defaults: Readonly<Record<string, string>>,\n): T {\n if (config.reasoningEffort !== undefined || !config.provider || !config.model) return config\n const effort = lookupEffort(defaults, config.provider, config.model)\n if (effort === undefined) return config\n return { ...config, reasoningEffort: ReasoningEffortId(effort) }\n}\n\n/**\n * 一层插件 fiber 的路由表来源。\n *\n * 结构化字段在激活期归一化一次后不再变化(改动它们会触发插件重载);扁平\n * `defaults` 是设置页的编辑对象,其 volatile 引用被宿主原地更新,因此每次\n * 请求读取当下快照。\n */\ntype RouteTableSource = {\n /** 读取与告警所用的当前 fiber 上下文。 */\n ctx: Context\n /** providers/models/reasoningEffort 归一化结果(激活期一次)。 */\n structured: Record<string, string>\n /** 设置页可编辑的扁平路由表引用。 */\n defaults: Volatile<Record<string, string>>\n}\n\n// 告警去重:路由表读取位于每次请求的热路径上,同一问题只告警一次,避免日志\n// 刷屏;进程生命周期内出现一次提示即足以定位配置错误。激活期体检与运行期读取\n// 共用这里,同一处配置不会既被体检报一次、又被读取报一次。\nconst warnedMessages = new Set<string>()\n\n/** 发出一次性(进程内按文案去重)的插件告警,自动带上插件名前缀。 */\nfunction warnOnce(ctx: Context, message: string): void {\n if (warnedMessages.has(message)) return\n warnedMessages.add(message)\n ctx.logger?.warn?.('[' + name + '] ' + message)\n}\n\n/**\n * 读取一层 fiber 当前生效的路由表。\n *\n * 结构化配置为底、volatile defaults 快照覆盖。手工编辑 profile patch 时\n * defaults 走的也是 volatile 原地更新(不会重载插件、不再有激活期体检),\n * 因此未知等级在这里一次性告警,避免静默降级。\n *\n * @param source - 一层 fiber 的路由表来源。\n * @returns 合并后的扁平路由映射;两层均为空时为空对象。\n */\nfunction readRouteTable(source: RouteTableSource): Record<string, string> {\n const live = normalizeFlatDefaults(source.defaults.get())\n for (const [route, level] of Object.entries(live)) {\n if (!KNOWN_REASONING_EFFORTS.has(level)) {\n warnOnce(source.ctx, unknownLevelIssue(route, level))\n }\n }\n return { ...source.structured, ...live }\n}\n\ntype RuntimePatchState = {\n /** 各活跃 fiber 的路由表来源;热重载期间新 fiber 覆盖旧 fiber。 */\n sources: Map<symbol, RouteTableSource>\n originalStream: LlmRuntime['stream']\n originalPrepareCall: LlmRuntime['prepareCall']\n originalResolveCallConfig: LlmRuntime['resolveCallConfig']\n stream: LlmRuntime['stream']\n prepareCall: LlmRuntime['prepareCall']\n resolveCallConfig: LlmRuntime['resolveCallConfig']\n}\n\n// Symbol.for 让热重载后的模块副本复用同一层状态,而不是再包一层旧包装。\nconst runtimePatchStateKey = Symbol.for('dsh-model-reasoning-defaults.runtime-patch-state')\n\n/**\n * 取最后激活的路由表来源。热重载期间新 fiber 覆盖旧 fiber,旧 fiber 卸载后\n * 新配置仍保持生效;最后一个 fiber 卸载时才还原原始服务方法。\n *\n * @param state - 同一 LLM 服务上的包装状态。\n * @returns 当前生效的来源;没有活跃层时返回 undefined。\n */\nfunction currentSource(state: RuntimePatchState): RouteTableSource | undefined {\n let current: RouteTableSource | undefined\n for (const source of state.sources.values()) current = source\n return current\n}\n\n/**\n * 将当前活跃层的路由表应用到配置。保留原对象是卸载后仍被外部持有的旧包装的\n * 必要语义,也避免把一个空对象伪装成真实配置来源。\n *\n * @param state - 同一 LLM 服务上的包装状态。\n * @param config - 待处理的调用配置。\n * @returns 原配置或带默认推理等级的浅复制对象。\n */\nfunction applyCurrentDefault<T extends ReasoningConfig>(\n state: RuntimePatchState,\n config: T,\n): T {\n const source = currentSource(state)\n if (source === undefined) return config\n const routeTable = readRouteTable(source)\n return Object.keys(routeTable).length === 0 ? config : applyDefaultReasoningEffort(config, routeTable)\n}\n\n/**\n * 从当前 stream 包装上恢复跨热重载共享的状态。\n *\n * @param llm - LLM 服务实例。\n * @returns 现有包装状态;服务尚未被本插件包装时返回 undefined。\n */\nfunction existingRuntimePatchState(llm: LlmRuntime): RuntimePatchState | undefined {\n return (llm.stream as unknown as Record<PropertyKey, unknown>)[runtimePatchStateKey] as RuntimePatchState | undefined\n}\n\n/**\n * 创建覆盖三个公开入口的 LLM 服务包装。\n *\n * 手写请求在 stream 入口浅复制补齐后再进 waterfall,保证每个监听器只处理\n * 一次;loop 构建的请求由宿主打 markAgentLoopRequest 进程标识并深冻结,\n * 经 isAgentLoopRequest 判定后按原引用透传——浅复制会丢失该进程标识。\n *\n * @param llm - LLM 服务实例。\n * @returns 可复用的包装状态。\n */\nfunction createRuntimePatchState(llm: LlmRuntime): RuntimePatchState {\n const originalStream = llm.stream\n const originalPrepareCall = llm.prepareCall\n const originalResolveCallConfig = llm.resolveCallConfig\n let state!: RuntimePatchState\n\n const stream: LlmRuntime['stream'] = (options) => {\n const patched = isAgentLoopRequest(options) ? options : applyCurrentDefault(state, options)\n return originalStream.call(llm, patched)\n }\n const prepareCall: LlmRuntime['prepareCall'] = async (config, signal) => {\n return originalPrepareCall.call(llm, applyCurrentDefault(state, config), signal)\n }\n const resolveCallConfig: LlmRuntime['resolveCallConfig'] = async (config, signal) => {\n return originalResolveCallConfig.call(llm, applyCurrentDefault(state, config), signal)\n }\n\n state = {\n sources: new Map(),\n originalStream,\n originalPrepareCall,\n originalResolveCallConfig,\n stream,\n prepareCall,\n resolveCallConfig,\n }\n Object.defineProperty(stream, runtimePatchStateKey, { value: state })\n return state\n}\n\n/**\n * 安装一层可卸载的 LLM 服务包装。\n *\n * 每个插件 fiber 只持有自己的路由表来源。这样新 fiber 先装配、旧 fiber 后卸载\n * 时,不会把服务方法恢复为已经失效的旧包装。\n *\n * 路由表为空时同样安装:设置页可以在插件运行期间写入新路由,跳过安装会让这些\n * 改动直到宿主重启才生效。\n *\n * @param ctx - 当前插件上下文。\n * @param llm - LLM 服务实例。\n * @param source - 本 fiber 的路由表来源。\n * @returns 共享的包装状态。\n */\nfunction installRuntimePatches(\n ctx: Context,\n llm: LlmRuntime,\n source: RouteTableSource,\n): RuntimePatchState {\n const state = existingRuntimePatchState(llm) ?? createRuntimePatchState(llm)\n if (llm.stream !== state.stream) llm.stream = state.stream\n if (llm.prepareCall !== state.prepareCall) llm.prepareCall = state.prepareCall\n if (llm.resolveCallConfig !== state.resolveCallConfig) llm.resolveCallConfig = state.resolveCallConfig\n\n const token = Symbol(name)\n state.sources.set(token, source)\n ctx.effect(() => () => {\n state.sources.delete(token)\n if (state.sources.size !== 0) return\n if (llm.stream === state.stream) llm.stream = state.originalStream\n if (llm.prepareCall === state.prepareCall) llm.prepareCall = state.originalPrepareCall\n if (llm.resolveCallConfig === state.resolveCallConfig) llm.resolveCallConfig = state.originalResolveCallConfig\n }, name + ': restore LLM method wrappers')\n return state\n}\n\n/**\n * 安装默认推理等级注入逻辑。\n *\n * @param ctx - 当前插件上下文。\n * @param config - Cordis 解析后的插件配置。\n */\nexport function apply(ctx: Context, config: ResolvedConfig): void {\n // 结构化字段只在激活期归一化一次;defaults 是 volatile 引用,留给每次请求读取。\n // 两者必须分开:把激活期的 defaults 快照并进 structured,会让设置页删掉的路由\n // 继续从旧快照命中——运行期的删除只反映在 volatile 快照上。\n const structured = normalizeConfig({\n providers: config.providers,\n models: config.models,\n reasoningEffort: config.reasoningEffort,\n })\n\n // 结构化条目若因缺 id / 缺等级被归一化忽略,在此一次性上报,\n // 避免\"配了却不生效\"的静默降级无从排查。与运行期读取共用 warnOnce:\n // 激活期同时会读一次路由表(见下方日志),同一问题只应出现一条告警。\n for (const issue of collectConfigIssues({\n defaults: config.defaults.get(),\n providers: config.providers,\n models: config.models,\n reasoningEffort: config.reasoningEffort,\n })) {\n warnOnce(ctx, issue)\n }\n\n // 本插件自带设置页 section,声明宿主无需再为该配置项生成通用表单。\n ctx.effect(() => ctx.settings.configure({ auto: false }, ctx.fiber))\n\n const source: RouteTableSource = { ctx, structured, defaults: config.defaults }\n\n // 先建立共享配置层;agent/request 和三个公开 LLM 入口都从这层读取最新路由表。\n const state = installRuntimePatches(ctx, ctx.llm, source)\n\n // agent-loop 在这里仍允许替换配置;此阶段操作的是调用配置种子,loop 随后\n // 才据此构建并深冻结真正的请求对象,loop 识别由 stream 入口的\n // isAgentLoopRequest 完成(见 createRuntimePatchState)。\n registerAgentRequestListener(ctx, async function (_payload, next) {\n return applyCurrentDefault(state, await next())\n })\n\n // 激活日志保留生效路由清单,排障时可直接对照请求的 provider/model。\n ctx.logger?.info?.('[' + name + '] 已激活:routes=' + Object.keys(readRouteTable(source)).join(', '))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * dsh-model-reasoning-defaults\n *\n * 为每个模型配置默认推理等级。请求已显式指定 reasoningEffort 时保持原值;\n * 其余按 provider/model 路由查找默认值并转换为 DSH 的品牌类型。\n *\n * 路由表存放在插件自身的配置项中(profile patch 里 id 为\n * model-reasoning-defaults 的 `defaults` 字段),DSH 设置页经 settings Remote\n * 写入。该字段声明为 volatile:宿主原地更新配置引用而不重载插件,因此每次\n * 请求都读取当下快照,设置页保存后立即对新请求生效。\n *\n * agent-loop 请求在 agent/request waterfall 中注入(此后到达 llm/stream 时\n * 已深冻结);手写调用在 llm.stream 入口复制后再进入 waterfall,调用配置\n * 方法走同一共享包装层。\n *\n * @module dsh-model-reasoning-defaults\n */\nimport type { Context, EventOptions } from '@deepseek-ai/cordis'\nimport type { Volatile } from '@deepseek-ai/cosmokit'\nimport { ReasoningEffortId } from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmCallConfig,\n LlmRuntime,\n} from '@deepseek-ai/dsh-llm'\n// Type-only:拉入 ctx.settings 的声明增强。设置在运行时由宿主装载,本插件不做\n// 运行时导入(package.json 里的 peer 声明的是所需的最低宿主版本)。\nimport type {} from '@deepseek-ai/dsh-settings'\nimport z from '@deepseek-ai/schemastery'\n\n/** 插件名(同时作为 Cordis 插件 id)。 */\nexport const name = 'dsh-model-reasoning-defaults'\n\n/** 声明依赖的服务:LLM 服务就绪后装配;声明设置页策略需要设置服务。 */\nexport const inject = ['llm', 'settings'] as const\n\n/** 单个模型的推理等级配置。 */\nexport interface ModelConfig {\n /** 模型 ID(数组形式必填;字典形式以键名为准,可省略)。 */\n id?: string\n /** 默认推理等级(如 off | minimal | low | medium | high | xhigh | max)。 */\n reasoningEffort?: string\n}\n\n/** 单个模型配置输入:既可以是等级字符串简写,也可以是配置对象。 */\nexport type ModelConfigInput = string | ModelConfig\n\n/** 单个 Provider 的推理等级配置。 */\nexport interface ProviderConfig {\n /** 该 Provider 下所有模型的默认推理等级。 */\n reasoningEffort?: string\n /** 该 Provider 下各模型的推理等级配置(字典或对象列表)。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n}\n\n/** Provider 配置输入:既可以是等级字符串简写(作用于该 Provider 所有模型),也可以是配置对象。 */\nexport type ProviderConfigInput = string | ProviderConfig\n\n/**\n * 用户书写的插件配置:支持按 Provider 与模型嵌入式层级配置,也可使用扁平路由\n * 字典 defaults。两种形式是同等的一等配置入口,归一化到同一张路由表后统一参与\n * 六级匹配。\n *\n * 合并后的匹配优先级从高到低为:\n * - \"provider:model\":精确匹配(来自 providers[p].models[m] 或 defaults[\"provider:model\"])\n * - \"provider:*\":该 provider 下所有模型(来自 providers[p].reasoningEffort 或 defaults[\"provider:*\"])\n * - \"provider/*\":斜杠写法(来自 defaults[\"provider/*\"])\n * - \"provider\":裸 provider 写法(来自 defaults[\"provider\"])\n * - \"*:model\":跨 provider 的同名模型(来自 top-level models[m] 或 defaults[\"*:model\"])\n * - \"*\":全局匹配(来自 top-level reasoningEffort 或 defaults[\"*\"])\n */\nexport interface Config {\n /**\n * 扁平路由字典:键为上表六级路由键,值为推理等级。该字段是设置页的编辑对象,\n * 与结构化配置产生同键时覆盖结构化项。\n */\n defaults?: Record<string, string>\n /** 按 Provider 组织的嵌入式配置。 */\n providers?: Record<string, ProviderConfigInput>\n /** 跨 Provider 适用的模型默认配置(等价于 \"*:model\")。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n /** 全局默认推理等级(等价于 \"*\")。 */\n reasoningEffort?: string\n}\n\nconst nonBlankPattern = /^\\S+$/\n\n// 已知推理等级集合:llm-pi-ai 的 ModelThinkingLevel(off/minimal/low/medium/high/\n// xhigh/max)与 llm-deepseek(off/low/high/max,为其子集)的并集。仅用于告警提示\n// 而不阻断注入——最终合法性由适配器逐字匹配裁决,本地白名单收窄反而会在上游新增\n// 等级时挡住合法配置。\nconst KNOWN_REASONING_EFFORTS: ReadonlySet<string> = new Set([\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n 'max',\n])\n\nconst modelConfigSchema = z.object({\n id: z.string().pattern(nonBlankPattern),\n reasoningEffort: z.string().pattern(nonBlankPattern),\n})\n\nconst modelEntrySchema = z.union([\n z.string().pattern(nonBlankPattern),\n modelConfigSchema,\n])\n\nconst modelsFieldSchema = z.union([\n z.dict(modelEntrySchema, z.string().pattern(nonBlankPattern)),\n z.array(modelEntrySchema),\n])\n\nconst providerConfigSchema = z.object({\n reasoningEffort: z.string().pattern(nonBlankPattern),\n models: modelsFieldSchema,\n})\n\nconst providerEntrySchema = z.union([\n z.string().pattern(nonBlankPattern),\n providerConfigSchema,\n])\n\n/**\n * Cordis 解析后的配置形状:`defaults` 已由 schemastery 换成 volatile 引用,\n * 其余字段与书写形状一致。\n *\n * 该接口显式写出而非由 `Schemastery.TypeT` 推导,是为了让 `Config` 的注解保持\n * 可命名(推导会展开成内联结构类型,声明产物随之膨胀且绑死 schemastery 内部\n * 泛型);注解本身由编译器与真实 schema 交叉校验,两者不一致会直接报错。\n */\nexport interface ResolvedConfig {\n /** 设置页编辑的扁平路由表引用。 */\n defaults: Volatile<Record<string, string>>\n /** 按 Provider 组织的嵌入式配置。 */\n providers?: Record<string, ProviderConfigInput>\n /** 跨 Provider 适用的模型默认配置。 */\n models?: Record<string, ModelConfigInput> | ModelConfigInput[]\n /** 全局默认推理等级。 */\n reasoningEffort?: string\n}\n\n/**\n * Cordis 配置模式。\n *\n * `defaults` 声明为 volatile:设置页经宿主 configEditor 改写 profile patch 后,\n * 宿主原地更新该引用(见 cordis-plugin-loader 的 volatile 提交路径),插件无需\n * 重载即可读到新值。其余结构化字段保持普通字段——它们是 patch 层的书写形式,\n * 变更后正常走插件重载。\n */\nexport const Config: z<Config, ResolvedConfig> = z.object({\n // 路由键与等级值都要求非空白:空白键永远不会被 candidateKeys 命中,空白值\n // 会被适配器逐字匹配拒绝,都在配置阶段直接报错暴露最省排查成本。\n // .default({}) 与 schemastery 对 dict 的内建空对象默认重复,保留它是为了让\n // 编程直调路径(绕过 cordis resolveConfig)也能拿到确定的形状。\n defaults: z.dict(\n z.string().pattern(nonBlankPattern),\n z.string().pattern(nonBlankPattern),\n ).default({}).volatile(),\n providers: z.dict(\n providerEntrySchema,\n z.string().pattern(nonBlankPattern),\n ).default({}),\n models: modelsFieldSchema,\n reasoningEffort: z.string().pattern(nonBlankPattern),\n})\n\n/**\n * 从字符串简写或配置对象中提取非空推理等级。\n *\n * 只认单一规范字段 reasoningEffort,不做多字段兜底链:同义多源读取会在字段\n * 冲突时按书写顺序静默裁决(例如 reasoningEffort 与 effort 同时配置时无声地\n * 二选一),掩盖配置错误;确需别名应由用户层显式映射后再传入。\n *\n * @param val - 等级字符串,或 ModelConfig/ProviderConfig 形状的对象。\n * @returns trim 后非空的等级字符串;无法提取时返回 undefined。\n */\nfunction extractEffort(val: unknown): string | undefined {\n const candidate = typeof val === 'string'\n ? val\n : (typeof val === 'object' && val !== null)\n ? (val as { reasoningEffort?: unknown }).reasoningEffort\n : undefined\n if (typeof candidate !== 'string') return undefined\n const trimmed = candidate.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * 归一化扁平路由字典。\n *\n * 键值均 trim 后判断:空白键与空白值一样永不命中任何请求路由(cordis 路径由\n * schema 拒绝,此处覆盖绕开 schema 的直调路径)。\n *\n * @param defaults - 扁平路由字典;未配置时为 undefined。\n * @returns 归一化后的键值映射。\n */\nfunction normalizeFlatDefaults(defaults: Record<string, string> | undefined): Record<string, string> {\n // 空原型累加器:路由键是任意非空白字符串,普通对象上给 __proto__ 赋值不会生成\n // 自有属性。schema 解析本身就会丢弃 __proto__ 键,这里保证的是本函数对任意\n // 普通对象输入(含绕过 schema 的直调)都成立。\n const result: Record<string, string> = Object.create(null)\n if (!defaults || typeof defaults !== 'object') return result\n for (const [rawKey, value] of Object.entries(defaults)) {\n const key = rawKey.trim()\n const trimmedValue = typeof value === 'string' ? value.trim() : ''\n if (key.length === 0 || trimmedValue.length === 0) continue\n result[key] = trimmedValue\n }\n return result\n}\n\n/**\n * 将结构化 Config 归一化为统一的扁平路由字典。\n *\n * @param config - 插件配置对象(defaults 为普通字典形式)。\n * @returns 归一化后的键值映射;键与值均已去除首尾空白。\n */\nexport function normalizeConfig(config: Config): Record<string, string> {\n // 与 normalizeFlatDefaults 一致用空原型:合并 defaults 时若键为 __proto__,\n // 普通对象上的赋值不会生成自有属性,路由会被静默丢掉。\n const result: Record<string, string> = Object.create(null)\n\n // 1. 全局默认 (对应 \"*\")\n const globalEffort = extractEffort(config.reasoningEffort)\n if (globalEffort) {\n result['*'] = globalEffort\n }\n\n // 2. 顶层跨 Provider 模型默认 (对应 \"*:model\")\n if (config.models) {\n if (Array.isArray(config.models)) {\n for (const item of config.models) {\n // 数组形式必须携带非空白 id 才能生成路由键;缺失项由 apply() 的\n // 激活期诊断告警上报,这里只负责安静地跳过无效形状。\n const id = typeof item === 'object' && item !== null && typeof item.id === 'string' ? item.id.trim() : ''\n if (id.length === 0) continue\n const effort = extractEffort(item)\n if (effort) result['*:' + id] = effort\n }\n } else if (typeof config.models === 'object') {\n for (const [rawModelId, modelVal] of Object.entries(config.models)) {\n const modelId = rawModelId.trim()\n if (modelId.length === 0) continue\n const effort = extractEffort(modelVal)\n if (effort) result['*:' + modelId] = effort\n }\n }\n }\n\n // 3. Provider 层级配置\n if (config.providers && typeof config.providers === 'object') {\n for (const [rawProviderId, providerVal] of Object.entries(config.providers)) {\n const providerId = rawProviderId.trim()\n if (providerId.length === 0) continue\n\n // 3.1 Provider 级默认 (对应 \"provider:*\")\n const providerEffort = extractEffort(providerVal)\n if (providerEffort) {\n result[providerId + ':*'] = providerEffort\n }\n\n // 3.2 Provider 内的具体模型 (对应 \"provider:model\")\n if (typeof providerVal === 'object' && providerVal !== null && providerVal.models) {\n const models = providerVal.models\n if (Array.isArray(models)) {\n for (const item of models) {\n const id = typeof item === 'object' && item !== null && typeof item.id === 'string' ? item.id.trim() : ''\n if (id.length === 0) continue\n const effort = extractEffort(item)\n if (effort) result[providerId + ':' + id] = effort\n }\n } else if (typeof models === 'object') {\n for (const [rawModelId, modelVal] of Object.entries(models)) {\n const modelId = rawModelId.trim()\n if (modelId.length === 0) continue\n const effort = extractEffort(modelVal)\n if (effort) result[providerId + ':' + modelId] = effort\n }\n }\n }\n }\n }\n\n // 4. 扁平 defaults 字典:与结构化配置同等的一等入口,写在最后使其在与结构化\n // 配置同键冲突时胜出(显式路由键的表达意图更具体)。\n Object.assign(result, normalizeFlatDefaults(config.defaults))\n\n return result\n}\n\ntype ReasoningConfig = Pick<LlmCallConfig, 'provider' | 'model' | 'reasoningEffort'>\n\n/**\n * 未知推理等级的告警文案。\n *\n * 激活期体检与运行期读取共用同一文案:同一处配置在两条路径上都会被看到\n * (激活时既做体检、又会读一次路由表用于日志),文案一致才能靠 warnOnce\n * 收敛成一条日志,而不是打出两条内容几乎相同、仅差等级清单的告警。\n *\n * @param route - 命中的路由键。\n * @param level - 配置的推理等级。\n * @returns 供 logger 输出的告警文案。\n */\nfunction unknownLevelIssue(route: string, level: string): string {\n return '路由 ' + route + ' 的推理等级 \"' + level + '\" 不在已知集合 ('\n + [...KNOWN_REASONING_EFFORTS].join('/') + ') 内,请确认目标模型支持'\n}\n\n/**\n * 静态体检插件配置,返回人类可读的问题清单(纯函数,不修改任何数据)。\n *\n * schemastery 的对象属性全部可选且对未知键透传,\"拼错字段\"\"模型项缺 id\"\n * 这类错误能通过校验却在 normalizeConfig 中被静默忽略;此清单在插件激活时\n * 一次性告警,把这类静默降级变成可见信息。等级值不在已知集合也在此提示,\n * 但仍会注入——最终是否可用交给适配器逐字匹配裁决。\n *\n * @param config - 用户原始配置(未经归一化)。\n * @returns 问题描述列表;无问题时为空数组。\n */\nfunction collectConfigIssues(config: Config): string[] {\n const issues: string[] = []\n\n const checkLevel = (route: string, level: string | undefined): void => {\n if (level !== undefined && !KNOWN_REASONING_EFFORTS.has(level)) {\n issues.push(unknownLevelIssue(route, level))\n }\n }\n\n /**\n * 检查单个模型条目:字符串简写直接校验等级;\n * 对象形式要求携带有效等级,数组元素还要求有效 id。\n */\n const checkModelEntry = (route: string, entry: ModelConfigInput, requireId: boolean): void => {\n if (typeof entry === 'string') {\n // 数组位置没有键名可用作 id,字符串简写永远生成不了路由键;字典位置的\n // 字符串值以键名为 id(requireId=false),不受此限制。\n if (requireId) {\n issues.push('数组形式模型条目缺少有效 id(字符串简写无键名可用),已被忽略:' + JSON.stringify(entry))\n return\n }\n checkLevel(route, extractEffort(entry))\n return\n }\n const id = typeof entry.id === 'string' ? entry.id.trim() : ''\n if (requireId && id.length === 0) {\n issues.push('模型条目缺少有效 id,已被忽略:' + JSON.stringify(entry))\n return\n }\n const effort = extractEffort(entry)\n if (effort === undefined) {\n issues.push('模型条目未包含 reasoningEffort,已被忽略:' + JSON.stringify(entry))\n return\n }\n checkLevel(route, effort)\n }\n\n // 全局等级\n checkLevel('*', extractEffort(config.reasoningEffort))\n\n // 顶层跨 Provider 模型\n if (config.models) {\n if (Array.isArray(config.models)) {\n config.models.forEach((entry, index) => {\n checkModelEntry('*:<models[' + index + ']>', entry, true)\n })\n } else if (typeof config.models === 'object') {\n for (const [modelId, entry] of Object.entries(config.models)) {\n checkModelEntry('*:' + modelId, entry, false)\n }\n }\n }\n\n // Provider 层级\n if (config.providers && typeof config.providers === 'object') {\n for (const [providerId, providerVal] of Object.entries(config.providers)) {\n if (typeof providerVal === 'string') {\n checkLevel(providerId + ':*', extractEffort(providerVal))\n continue\n }\n const hasModels = providerVal.models !== undefined\n const providerEffort = extractEffort(providerVal)\n if (!hasModels && providerEffort === undefined) {\n issues.push('provider ' + providerId + ' 未包含 reasoningEffort 或 models,整条配置已被忽略')\n continue\n }\n checkLevel(providerId + ':*', providerEffort)\n if (hasModels) {\n const models = providerVal.models!\n if (Array.isArray(models)) {\n models.forEach((entry, index) => {\n checkModelEntry(providerId + ':<models[' + index + ']>', entry, true)\n })\n } else if (typeof models === 'object') {\n for (const [modelId, entry] of Object.entries(models)) {\n checkModelEntry(providerId + ':' + modelId, entry, false)\n }\n }\n }\n }\n }\n\n // 扁平 defaults 字典:schema 在解析阶段就拒绝空白键值,所以这里只对绕过 schema\n // 的直调形态生效(手工构造 ResolvedConfig 直接调用 apply,例如测试或其它插件)。\n // 空键检查与 normalizeFlatDefaults 的跳过逻辑一致,把静默降级变成可见告警。\n if (config.defaults && typeof config.defaults === 'object') {\n for (const [rawKey, value] of Object.entries(config.defaults)) {\n const key = rawKey.trim()\n if (key.length === 0) {\n issues.push('扁平路由键为空白,已被忽略:' + JSON.stringify(rawKey))\n continue\n }\n checkLevel(key, extractEffort(value))\n }\n }\n\n return issues\n}\n\n// agent/request 的 loop 侧 emit 实参是 { turn, step, signal };core/agent 的派发器\n// 会把路由主体 agent(Scoped<Agent>)融合进 payload 并同时作为监听器 this,因此\n// 实收 payload 含 agent 字段。本监听器不读取任何 payload 字段,此处仅按本插件\n// 用到的形状窄化 ctx.on 的签名。\ntype AgentRequestPayload = {\n turn: number\n step: number\n signal: AbortSignal\n}\n\ntype AgentRequestListener = (\n this: object,\n payload: AgentRequestPayload,\n next: () => Promise<LlmCallConfig>,\n) => Promise<LlmCallConfig>\n\ntype AgentRequestRegistrar = (\n name: 'agent/request',\n listener: AgentRequestListener,\n options?: boolean | EventOptions,\n) => () => boolean\n\n/**\n * 注册全局 agent/request waterfall。\n *\n * cordis 的 ctx.on 内部会把监听器注册为当前 fiber 上的 effect(见 events\n * 服务的 register()),无论 global 标志如何都会在 fiber 卸载时自动清理;\n * global 只影响事件派发时的上下文过滤。因此无需再包一层 ctx.effect。\n *\n * 该事件的运行时接收者是 Scoped<Agent>,Cordis 根 Context 的事件泛型表不会\n * 暴露它,因此在最小边界内窄化签名(仅约束本插件用到的事件名与参数)。\n *\n * @param ctx - 当前插件上下文。\n * @param listener - agent/request waterfall 监听器。\n */\nfunction registerAgentRequestListener(ctx: Context, listener: AgentRequestListener): void {\n // 窄化仅覆盖本插件实际使用的事件名与参数;内部转调仍通过 ctx.on 的真实实现。\n const register = ctx.on.bind(ctx) as AgentRequestRegistrar\n register('agent/request', listener, { global: true })\n}\n\n/**\n * 生成一个路由的候选配置键。\n *\n * 兼容键保留在明确的优先级表中,避免不同调用入口各自实现一套匹配规则。\n *\n * @param provider - 请求使用的 provider。\n * @param model - 请求使用的 model。\n * @returns 按匹配优先级排列的候选键。\n */\nfunction candidateKeys(provider: string, model: string): readonly string[] {\n return [\n provider + ':' + model,\n provider + ':*',\n provider + '/*',\n provider,\n '*:' + model,\n '*',\n ]\n}\n\n/**\n * 查找一个路由对应的默认推理等级。\n *\n * @param defaults - 归一化后的扁平路由映射。\n * @param provider - 请求使用的 provider。\n * @param model - 请求使用的 model。\n * @returns 找到的非空推理等级,未找到时返回 undefined。\n */\nfunction lookupEffort(\n defaults: Readonly<Record<string, string>>,\n provider: string,\n model: string,\n): string | undefined {\n for (const key of candidateKeys(provider, model)) {\n const effort = defaults[key]\n if (typeof effort === 'string' && effort.length > 0) return effort\n }\n return undefined\n}\n\n/**\n * 在不修改原对象的前提下应用默认推理等级。\n *\n * 该函数同时用于 GenerateOptions 和 LlmCallConfig,因此调用方可以保留\n * 原请求对象中的 messages、signal 等其它字段,并安全处理深冻结输入。\n *\n * 第二参数只接受归一化后的扁平路由表;结构化 Config 必须先经 normalizeConfig。\n * 曾尝试用对象形状嗅探让本函数同时接纳两种入参,但路由表允许任意非空白键,\n * 含 \"models\"/\"providers\" 等键的路由表会被误判成结构化配置(整表归一化为空、\n * 注入全部失效),反之仅含顶层 reasoningEffort 的最小结构化配置又会漏判——\n * 两类误判都是静默失效,因此彻底拆掉形状嗅探。\n *\n * @param config - 待补齐的 LLM 请求或调用配置。\n * @param defaults - normalizeConfig 产出的扁平路由表。\n * @returns 原配置或带有 reasoningEffort 的浅复制对象。\n */\nexport function applyDefaultReasoningEffort<T extends ReasoningConfig>(\n config: T,\n defaults: Readonly<Record<string, string>>,\n): T {\n if (config.reasoningEffort !== undefined || !config.provider || !config.model) return config\n const effort = lookupEffort(defaults, config.provider, config.model)\n if (effort === undefined) return config\n return { ...config, reasoningEffort: ReasoningEffortId(effort) }\n}\n\n/**\n * 一层插件 fiber 的路由表来源。\n *\n * 结构化字段在激活期归一化一次后不再变化(改动它们会触发插件重载);扁平\n * `defaults` 是设置页的编辑对象,其 volatile 引用被宿主原地更新,因此每次\n * 请求读取当下快照。\n */\ntype RouteTableSource = {\n /** 读取与告警所用的当前 fiber 上下文。 */\n ctx: Context\n /** providers/models/reasoningEffort 归一化结果(激活期一次)。 */\n structured: Record<string, string>\n /** 设置页可编辑的扁平路由表引用。 */\n defaults: Volatile<Record<string, string>>\n}\n\n// 告警去重:路由表读取位于每次请求的热路径上,同一问题只告警一次,避免日志\n// 刷屏;进程生命周期内出现一次提示即足以定位配置错误。激活期体检与运行期读取\n// 共用这里,同一处配置不会既被体检报一次、又被读取报一次。\nconst warnedMessages = new Set<string>()\n\n/** 发出一次性(进程内按文案去重)的插件告警,自动带上插件名前缀。 */\nfunction warnOnce(ctx: Context, message: string): void {\n if (warnedMessages.has(message)) return\n warnedMessages.add(message)\n ctx.logger?.warn?.('[' + name + '] ' + message)\n}\n\n/**\n * 读取一层 fiber 当前生效的路由表。\n *\n * 结构化配置为底、volatile defaults 快照覆盖。手工编辑 profile patch 时\n * defaults 走的也是 volatile 原地更新(不会重载插件、不再有激活期体检),\n * 因此未知等级在这里一次性告警,避免静默降级。\n *\n * @param source - 一层 fiber 的路由表来源。\n * @returns 合并后的扁平路由映射;两层均为空时为空对象。\n */\nfunction readRouteTable(source: RouteTableSource): Record<string, string> {\n const live = normalizeFlatDefaults(source.defaults.get())\n for (const [route, level] of Object.entries(live)) {\n if (!KNOWN_REASONING_EFFORTS.has(level)) {\n warnOnce(source.ctx, unknownLevelIssue(route, level))\n }\n }\n return { ...source.structured, ...live }\n}\n\ntype RuntimePatchState = {\n /** 各活跃 fiber 的路由表来源;热重载期间新 fiber 覆盖旧 fiber。 */\n sources: Map<symbol, RouteTableSource>\n originalStream: LlmRuntime['stream']\n originalPrepareCall: LlmRuntime['prepareCall']\n originalResolveCallConfig: LlmRuntime['resolveCallConfig']\n stream: LlmRuntime['stream']\n prepareCall: LlmRuntime['prepareCall']\n resolveCallConfig: LlmRuntime['resolveCallConfig']\n}\n\n// Symbol.for 让热重载后的模块副本复用同一层状态,而不是再包一层旧包装。\nconst runtimePatchStateKey = Symbol.for('dsh-model-reasoning-defaults.runtime-patch-state')\n\n/**\n * 取最后激活的路由表来源。热重载期间新 fiber 覆盖旧 fiber,旧 fiber 卸载后\n * 新配置仍保持生效;最后一个 fiber 卸载时才还原原始服务方法。\n *\n * @param state - 同一 LLM 服务上的包装状态。\n * @returns 当前生效的来源;没有活跃层时返回 undefined。\n */\nfunction currentSource(state: RuntimePatchState): RouteTableSource | undefined {\n let current: RouteTableSource | undefined\n for (const source of state.sources.values()) current = source\n return current\n}\n\n/**\n * 将当前活跃层的路由表应用到配置。保留原对象是卸载后仍被外部持有的旧包装的\n * 必要语义,也避免把一个空对象伪装成真实配置来源。\n *\n * @param state - 同一 LLM 服务上的包装状态。\n * @param config - 待处理的调用配置。\n * @returns 原配置或带默认推理等级的浅复制对象。\n */\nfunction applyCurrentDefault<T extends ReasoningConfig>(\n state: RuntimePatchState,\n config: T,\n): T {\n const source = currentSource(state)\n if (source === undefined) return config\n const routeTable = readRouteTable(source)\n return Object.keys(routeTable).length === 0 ? config : applyDefaultReasoningEffort(config, routeTable)\n}\n\n/**\n * 判断请求是否已经进入 Agent Loop 的不可变边界。\n *\n * `isAgentLoopRequest()` 背后是 `@deepseek-ai/dsh-llm` 模块副本私有的\n * `WeakSet`。宿主和插件若解析到不同副本,宿主写入的标识不会被插件读取,\n * 入口包装就会错误地复制 Loop 请求。这里改用 DSH 公开的请求契约:Loop\n * 在交给 LLM 前会把完整请求顶层冻结,并写入用于路由的 `sessionId`;冻结的\n * 手写辅助请求(例如 compaction)没有 `sessionId`,仍应允许插件复制并补齐默认值。\n *\n * @param options - 进入 `llm.stream` 的完整请求。\n * @returns 是否应按原引用透传该请求。\n */\nfunction isAgentLoopRequestBoundary(options: GenerateOptions): boolean {\n return options.sessionId !== undefined && Object.isFrozen(options)\n}\n\n/**\n * 从当前 stream 包装上恢复跨热重载共享的状态。\n *\n * @param llm - LLM 服务实例。\n * @returns 现有包装状态;服务尚未被本插件包装时返回 undefined。\n */\nfunction existingRuntimePatchState(llm: LlmRuntime): RuntimePatchState | undefined {\n return (llm.stream as unknown as Record<PropertyKey, unknown>)[runtimePatchStateKey] as RuntimePatchState | undefined\n}\n\n/**\n * 创建覆盖三个公开入口的 LLM 服务包装。\n *\n * 手写请求在 stream 入口浅复制补齐后再进 waterfall,保证每个监听器只处理\n * 一次;loop 构建的请求由宿主打 markAgentLoopRequest 进程标识并深冻结,\n * 经公开的冻结与 sessionId 契约判定后按原引用透传——浅复制会丢失请求身份。\n *\n * @param llm - LLM 服务实例。\n * @returns 可复用的包装状态。\n */\nfunction createRuntimePatchState(llm: LlmRuntime): RuntimePatchState {\n const originalStream = llm.stream\n const originalPrepareCall = llm.prepareCall\n const originalResolveCallConfig = llm.resolveCallConfig\n let state!: RuntimePatchState\n\n const stream: LlmRuntime['stream'] = (options) => {\n const patched = isAgentLoopRequestBoundary(options) ? options : applyCurrentDefault(state, options)\n return originalStream.call(llm, patched)\n }\n const prepareCall: LlmRuntime['prepareCall'] = async (config, signal) => {\n return originalPrepareCall.call(llm, applyCurrentDefault(state, config), signal)\n }\n const resolveCallConfig: LlmRuntime['resolveCallConfig'] = async (config, signal) => {\n return originalResolveCallConfig.call(llm, applyCurrentDefault(state, config), signal)\n }\n\n state = {\n sources: new Map(),\n originalStream,\n originalPrepareCall,\n originalResolveCallConfig,\n stream,\n prepareCall,\n resolveCallConfig,\n }\n Object.defineProperty(stream, runtimePatchStateKey, { value: state })\n return state\n}\n\n/**\n * 安装一层可卸载的 LLM 服务包装。\n *\n * 每个插件 fiber 只持有自己的路由表来源。这样新 fiber 先装配、旧 fiber 后卸载\n * 时,不会把服务方法恢复为已经失效的旧包装。\n *\n * 路由表为空时同样安装:设置页可以在插件运行期间写入新路由,跳过安装会让这些\n * 改动直到宿主重启才生效。\n *\n * @param ctx - 当前插件上下文。\n * @param llm - LLM 服务实例。\n * @param source - 本 fiber 的路由表来源。\n * @returns 共享的包装状态。\n */\nfunction installRuntimePatches(\n ctx: Context,\n llm: LlmRuntime,\n source: RouteTableSource,\n): RuntimePatchState {\n const state = existingRuntimePatchState(llm) ?? createRuntimePatchState(llm)\n if (llm.stream !== state.stream) llm.stream = state.stream\n if (llm.prepareCall !== state.prepareCall) llm.prepareCall = state.prepareCall\n if (llm.resolveCallConfig !== state.resolveCallConfig) llm.resolveCallConfig = state.resolveCallConfig\n\n const token = Symbol(name)\n state.sources.set(token, source)\n ctx.effect(() => () => {\n state.sources.delete(token)\n if (state.sources.size !== 0) return\n if (llm.stream === state.stream) llm.stream = state.originalStream\n if (llm.prepareCall === state.prepareCall) llm.prepareCall = state.originalPrepareCall\n if (llm.resolveCallConfig === state.resolveCallConfig) llm.resolveCallConfig = state.originalResolveCallConfig\n }, name + ': restore LLM method wrappers')\n return state\n}\n\n/**\n * 安装默认推理等级注入逻辑。\n *\n * @param ctx - 当前插件上下文。\n * @param config - Cordis 解析后的插件配置。\n */\nexport function apply(ctx: Context, config: ResolvedConfig): void {\n // 结构化字段只在激活期归一化一次;defaults 是 volatile 引用,留给每次请求读取。\n // 两者必须分开:把激活期的 defaults 快照并进 structured,会让设置页删掉的路由\n // 继续从旧快照命中——运行期的删除只反映在 volatile 快照上。\n const structured = normalizeConfig({\n providers: config.providers,\n models: config.models,\n reasoningEffort: config.reasoningEffort,\n })\n\n // 结构化条目若因缺 id / 缺等级被归一化忽略,在此一次性上报,\n // 避免\"配了却不生效\"的静默降级无从排查。与运行期读取共用 warnOnce:\n // 激活期同时会读一次路由表(见下方日志),同一问题只应出现一条告警。\n for (const issue of collectConfigIssues({\n defaults: config.defaults.get(),\n providers: config.providers,\n models: config.models,\n reasoningEffort: config.reasoningEffort,\n })) {\n warnOnce(ctx, issue)\n }\n\n // 本插件自带设置页 section,声明宿主无需再为该配置项生成通用表单。\n ctx.effect(() => ctx.settings.configure({ auto: false }, ctx.fiber))\n\n const source: RouteTableSource = { ctx, structured, defaults: config.defaults }\n\n // 先建立共享配置层;agent/request 和三个公开 LLM 入口都从这层读取最新路由表。\n const state = installRuntimePatches(ctx, ctx.llm, source)\n\n // agent-loop 在这里仍允许替换配置;此阶段操作的是调用配置种子,loop 随后\n // 才据此构建并深冻结真正的请求对象,stream 入口按公开冻结边界透传它。\n registerAgentRequestListener(ctx, async function (_payload, next) {\n return applyCurrentDefault(state, await next())\n })\n\n // 激活日志保留生效路由清单,排障时可直接对照请求的 provider/model。\n ctx.logger?.info?.('[' + name + '] 已激活:routes=' + Object.keys(readRouteTable(source)).join(', '))\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-reasoning-defaults",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "DSH (DeepSeek Harness) 插件:为每个 LLM 模型配置默认推理等级(reasoningEffort),并在请求进入底层调用链前按路由规则自动补齐",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/xht-code/dsh-model-reasoning-defaults/issues"
|
|
32
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@deepseek-ai/schemastery": "~3.18.4"
|
|
35
|
+
},
|
|
33
36
|
"peerDependencies": {
|
|
34
37
|
"@deepseek-ai/cordis": ">=4.0.0-rc <5",
|
|
35
38
|
"@deepseek-ai/cosmokit": ">=1.8.5 <2",
|
|
36
39
|
"@deepseek-ai/dsh-llm": ">=0.0.1-rc <2",
|
|
37
|
-
"@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2 <2"
|
|
38
|
-
"@deepseek-ai/schemastery": "^3.18.4"
|
|
40
|
+
"@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2 <2"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
@@ -44,7 +46,6 @@
|
|
|
44
46
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-alpha.2",
|
|
45
47
|
"@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
|
|
46
48
|
"@deepseek-ai/dsh-settings": "0.1.7-alpha.2",
|
|
47
|
-
"@deepseek-ai/schemastery": "^3.18.4",
|
|
48
49
|
"@types/node": "^24.13.3",
|
|
49
50
|
"@types/react": "~18.3.1",
|
|
50
51
|
"react": "^18.2.0",
|