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/README.md +40 -61
- package/cordis.patch.yml +6 -3
- package/lib/client.js +150 -58
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +40 -55
- package/lib/index.mjs +110 -237
- package/lib/index.mjs.map +1 -1
- package/lib/types/client/index.d.ts +2 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/route-table.d.ts +60 -0
- package/lib/types/client/route-table.d.ts.map +1 -0
- package/package.json +11 -8
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
|
-
/**
|
|
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
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
303
|
+
* 结构化配置为底、volatile defaults 快照覆盖。手工编辑 profile patch 时
|
|
304
|
+
* defaults 走的也是 volatile 原地更新(不会重载插件、不再有激活期体检),
|
|
305
|
+
* 因此未知等级在这里一次性告警,避免静默降级。
|
|
318
306
|
*
|
|
319
|
-
* @param
|
|
320
|
-
* @returns
|
|
307
|
+
* @param source - 一层 fiber 的路由表来源。
|
|
308
|
+
* @returns 合并后的扁平路由映射;两层均为空时为空对象。
|
|
321
309
|
*/
|
|
322
|
-
function
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
*
|
|
320
|
+
* 取最后激活的路由表来源。热重载期间新 fiber 覆盖旧 fiber,旧 fiber 卸载后
|
|
424
321
|
* 新配置仍保持生效;最后一个 fiber 卸载时才还原原始服务方法。
|
|
425
322
|
*
|
|
426
323
|
* @param state - 同一 LLM 服务上的包装状态。
|
|
427
|
-
* @returns
|
|
324
|
+
* @returns 当前生效的来源;没有活跃层时返回 undefined。
|
|
428
325
|
*/
|
|
429
|
-
function
|
|
326
|
+
function currentSource(state) {
|
|
430
327
|
let current;
|
|
431
|
-
for (const
|
|
328
|
+
for (const source of state.sources.values()) current = source;
|
|
432
329
|
return current;
|
|
433
330
|
}
|
|
434
331
|
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
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
|
|
464
|
-
|
|
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
|
-
*
|
|
359
|
+
* 经 isAgentLoopRequest 判定后按原引用透传——浅复制会丢失该进程标识。
|
|
496
360
|
*
|
|
497
|
-
* @param ctx - 当前插件上下文。
|
|
498
361
|
* @param llm - LLM 服务实例。
|
|
499
362
|
* @returns 可复用的包装状态。
|
|
500
363
|
*/
|
|
501
|
-
function createRuntimePatchState(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
533
|
-
*
|
|
394
|
+
* 每个插件 fiber 只持有自己的路由表来源。这样新 fiber 先装配、旧 fiber 后卸载
|
|
395
|
+
* 时,不会把服务方法恢复为已经失效的旧包装。
|
|
396
|
+
*
|
|
397
|
+
* 路由表为空时同样安装:设置页可以在插件运行期间写入新路由,跳过安装会让这些
|
|
398
|
+
* 改动直到宿主重启才生效。
|
|
534
399
|
*
|
|
535
400
|
* @param ctx - 当前插件上下文。
|
|
536
401
|
* @param llm - LLM 服务实例。
|
|
537
|
-
* @param
|
|
402
|
+
* @param source - 本 fiber 的路由表来源。
|
|
403
|
+
* @returns 共享的包装状态。
|
|
538
404
|
*/
|
|
539
|
-
function installRuntimePatches(ctx, llm,
|
|
540
|
-
const state = existingRuntimePatchState(llm) ?? createRuntimePatchState(
|
|
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.
|
|
411
|
+
state.sources.set(token, source);
|
|
547
412
|
ctx.effect(() => () => {
|
|
548
|
-
state.
|
|
549
|
-
if (state.
|
|
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
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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(
|
|
449
|
+
ctx.logger?.info?.("[" + name + "] 已激活:routes=" + Object.keys(readRouteTable(source)).join(", "));
|
|
577
450
|
}
|
|
578
451
|
//#endregion
|
|
579
|
-
export { Config,
|
|
452
|
+
export { Config, apply, applyDefaultReasoningEffort, inject, name, normalizeConfig };
|
|
580
453
|
|
|
581
454
|
//# sourceMappingURL=index.mjs.map
|