@ai-zen/agents-sdk 1.0.0-alpha.12 → 1.0.0-alpha.14
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/CHANGELOG.md +74 -0
- package/dist/agent-plugins/AutoMigratePlugin.d.ts +7 -7
- package/dist/agent-plugins/AutoMigratePlugin.js +8 -8
- package/dist/agent-plugins/ContextGuardPlugin.d.ts +13 -25
- package/dist/agent-plugins/ContextGuardPlugin.js +13 -20
- package/dist/config/ConfigManager.d.ts +28 -1
- package/dist/config/ConfigManager.js +84 -9
- package/dist/config/constants.js +24 -11
- package/dist/config/migrate.d.ts +47 -0
- package/dist/config/migrate.js +89 -0
- package/dist/config/reasoning-presets.d.ts +29 -0
- package/dist/config/reasoning-presets.js +56 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -0
- package/dist/runtime/HandoffService.d.ts +2 -2
- package/dist/runtime/HandoffService.js +3 -3
- package/dist/runtime/createAgent.js +4 -1
- package/dist/runtime/createModel.d.ts +7 -1
- package/dist/runtime/createModel.js +9 -2
- package/dist/scope/Scope.d.ts +9 -1
- package/dist/scope/Scope.js +8 -0
- package/dist/shared/errors.d.ts +4 -8
- package/dist/shared/errors.js +8 -14
- package/dist/shared/reasoning.d.ts +62 -0
- package/dist/shared/reasoning.js +159 -0
- package/dist/types/index.d.ts +69 -6
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置字段迁移 —— 历史形状升级到当前形状
|
|
3
|
+
*
|
|
4
|
+
* 三条遗留字段被淘汰:
|
|
5
|
+
*
|
|
6
|
+
* 1. `Model.maxContextTokens` / `Model.maxContextChars` → **`Model.tokenThresholds`**
|
|
7
|
+
* 旧名字一直在误导:它既不是「模型的真实上下文窗口」,也不是单一概念。现在拆成两条线:
|
|
8
|
+
* `handoff`(交接阈值,软线)与 `guard`(强行阻断阈值,硬线)。
|
|
9
|
+
* `maxContextChars` 是 token 口径之前的历史遗留,按 `floor(chars / 4)` 折成 tokens
|
|
10
|
+
* (与旧代码的回退算法一致)后并入 `handoff`。
|
|
11
|
+
*
|
|
12
|
+
* 迁移时机:`ConfigManager.read()` 读出即迁(内存里永远是当前形状);
|
|
13
|
+
* 落盘由 `ensureDefaultConfig()` 负责 —— 它比较迁移结果与磁盘原文,不同才写回,
|
|
14
|
+
* 因此迁移只发生一次,且不会在每次启动时无谓地改写文件。
|
|
15
|
+
*/
|
|
16
|
+
/** 当前字段名 */
|
|
17
|
+
export const TOKEN_THRESHOLDS_KEY = "tokenThresholds";
|
|
18
|
+
/** 已淘汰的旧字段名(仅迁移代码认识它们) */
|
|
19
|
+
export const LEGACY_TOKEN_FIELD = "maxContextTokens";
|
|
20
|
+
export const LEGACY_CHARS_FIELD = "maxContextChars";
|
|
21
|
+
/**
|
|
22
|
+
* 旧数据缺失阈值时的交接阈值兜底。
|
|
23
|
+
*
|
|
24
|
+
* 与 CLI 新建模型的默认值一致;宁可给一个保守值,也不要让模型缺阈值——
|
|
25
|
+
* 缺阈值意味着既没有交接触发、也没有阻断保护。
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_HANDOFF_THRESHOLD = 128_000;
|
|
28
|
+
function toPositiveInt(value) {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
return Math.floor(value);
|
|
32
|
+
}
|
|
33
|
+
function readThresholds(value) {
|
|
34
|
+
if (typeof value !== "object" || value === null)
|
|
35
|
+
return {};
|
|
36
|
+
const raw = value;
|
|
37
|
+
return {
|
|
38
|
+
handoff: toPositiveInt(raw.handoff),
|
|
39
|
+
guard: toPositiveInt(raw.guard),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 迁移单个模型条目。
|
|
44
|
+
*
|
|
45
|
+
* 优先级:已有 `tokenThresholds` 的成员 > 旧 tokens 字段 > 旧 chars 字段折算 > 兜底默认。
|
|
46
|
+
* 旧字段一律删除(连同一份数据里残留的脏值),保证迁移后的形状唯一。
|
|
47
|
+
* 已有 `handoff` 但缺 `guard` 时按 1.5 倍补齐。
|
|
48
|
+
*/
|
|
49
|
+
export function migrateModelThresholds(model) {
|
|
50
|
+
const source = model;
|
|
51
|
+
const current = readThresholds(source[TOKEN_THRESHOLDS_KEY]);
|
|
52
|
+
const legacyTokens = toPositiveInt(source[LEGACY_TOKEN_FIELD]);
|
|
53
|
+
const legacyChars = toPositiveInt(source[LEGACY_CHARS_FIELD]);
|
|
54
|
+
const legacySingle = toPositiveInt(source.tokenThreshold);
|
|
55
|
+
const hasLegacy = LEGACY_TOKEN_FIELD in source ||
|
|
56
|
+
LEGACY_CHARS_FIELD in source ||
|
|
57
|
+
"tokenThreshold" in source;
|
|
58
|
+
const complete = current.handoff !== undefined && current.guard !== undefined;
|
|
59
|
+
if (!hasLegacy && complete)
|
|
60
|
+
return { model, changed: false };
|
|
61
|
+
const fromChars = legacyChars !== undefined ? Math.max(1, Math.floor(legacyChars / 4)) : undefined;
|
|
62
|
+
const handoff = current.handoff ?? legacyTokens ?? fromChars ?? legacySingle ?? DEFAULT_HANDOFF_THRESHOLD;
|
|
63
|
+
// 旧数据里没有 guard 这一项:按重构前 ContextGuardPlugin 的默认 ratio(1.5)折算一次,
|
|
64
|
+
// 让迁移后的实际行为与迁移前一致。这是**一次性**换算,落盘后两个值都是整数,
|
|
65
|
+
// 运行时不再有任何倍数关系(想调硬线就直接改 guard 的数字)。
|
|
66
|
+
const guard = current.guard ?? Math.round(handoff * 1.5);
|
|
67
|
+
const next = { ...source };
|
|
68
|
+
delete next[LEGACY_TOKEN_FIELD];
|
|
69
|
+
delete next[LEGACY_CHARS_FIELD];
|
|
70
|
+
delete next.tokenThreshold;
|
|
71
|
+
next[TOKEN_THRESHOLDS_KEY] = { handoff, guard };
|
|
72
|
+
return { model: next, changed: true };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 迁移整个配置。只处理对话模型(`models`)——
|
|
76
|
+
* 图片模型(`imageModels`)从来没有上下文阈值概念。
|
|
77
|
+
*/
|
|
78
|
+
export function migrateConfig(config) {
|
|
79
|
+
let changed = false;
|
|
80
|
+
const models = (config.models ?? []).map((model) => {
|
|
81
|
+
const result = migrateModelThresholds(model);
|
|
82
|
+
if (result.changed)
|
|
83
|
+
changed = true;
|
|
84
|
+
return result.model;
|
|
85
|
+
});
|
|
86
|
+
if (!changed)
|
|
87
|
+
return { config, changed };
|
|
88
|
+
return { config: { ...config, models }, changed };
|
|
89
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 思考预设 —— 出厂种子 + 策略表文件名
|
|
3
|
+
*
|
|
4
|
+
* 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
|
|
5
|
+
* 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
|
|
6
|
+
* 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
|
|
7
|
+
*
|
|
8
|
+
* 为什么档位细节不留在代码里:档位与厂商协议绑定,属于「数据」而非「逻辑」。
|
|
9
|
+
* 放进用户目录后,厂商新增档位或接入新端点时用户能自己加,不必等 SDK 发版。
|
|
10
|
+
*
|
|
11
|
+
* ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
|
|
12
|
+
*
|
|
13
|
+
* | 端点 | 官方依据 | 若写错会怎样 |
|
|
14
|
+
* |------|---------|-------------|
|
|
15
|
+
* | OpenAI | `openai-node` 的 `Shared.ReasoningEffort`(`none/minimal/low/medium/high/xhigh/max`) | 无 `thinking` 参数,写了会被当未知参数拒绝 → 表里干脆不出现该键 |
|
|
16
|
+
* | DeepSeek | `api-docs.deepseek.com/guides/thinking_mode` | 可调 `low/high/max`;关闭用 `thinking.type = "disabled"` |
|
|
17
|
+
* | 智谱 GLM-5.3 系 | `docs.bigmodel.cn/cn/guide/capabilities/thinking` | **只认** `low/high/max`,其余输入报错;**不再支持关闭**(传 disabled 也报错)→ 表里没有 `off` |
|
|
18
|
+
* | 智谱 GLM-4.7 | 同上 | 强制思考且无 `reasoning_effort` 参数 → 不给它挂策略 |
|
|
19
|
+
*
|
|
20
|
+
* 三个结构性保证,都是「表的形状」带来的,不需要任何代码分支:
|
|
21
|
+
* 1. 可选档位 = `Object.keys(levels)`,不会有「菜单里有、发出去报错」的档位;
|
|
22
|
+
* 2. 能否关闭思考 = 表里有没有 `off` 键;
|
|
23
|
+
* 3. 某个键属于思考参数 = 它在表里出现过 —— 写入时按此剔除旧值,不需要硬编码键名。
|
|
24
|
+
*/
|
|
25
|
+
import type { ReasoningPresets } from "../types/index.js";
|
|
26
|
+
/** 策略表文件名(位于 `<basePath>/` 下,与 `config.json` 同级) */
|
|
27
|
+
export declare const REASONING_PRESETS_FILE = "reasoning-presets.json";
|
|
28
|
+
/** 出厂策略表 */
|
|
29
|
+
export declare const FACTORY_REASONING_PRESETS: ReasoningPresets;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 思考预设 —— 出厂种子 + 策略表文件名
|
|
3
|
+
*
|
|
4
|
+
* 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
|
|
5
|
+
* 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
|
|
6
|
+
* 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
|
|
7
|
+
*
|
|
8
|
+
* 为什么档位细节不留在代码里:档位与厂商协议绑定,属于「数据」而非「逻辑」。
|
|
9
|
+
* 放进用户目录后,厂商新增档位或接入新端点时用户能自己加,不必等 SDK 发版。
|
|
10
|
+
*
|
|
11
|
+
* ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
|
|
12
|
+
*
|
|
13
|
+
* | 端点 | 官方依据 | 若写错会怎样 |
|
|
14
|
+
* |------|---------|-------------|
|
|
15
|
+
* | OpenAI | `openai-node` 的 `Shared.ReasoningEffort`(`none/minimal/low/medium/high/xhigh/max`) | 无 `thinking` 参数,写了会被当未知参数拒绝 → 表里干脆不出现该键 |
|
|
16
|
+
* | DeepSeek | `api-docs.deepseek.com/guides/thinking_mode` | 可调 `low/high/max`;关闭用 `thinking.type = "disabled"` |
|
|
17
|
+
* | 智谱 GLM-5.3 系 | `docs.bigmodel.cn/cn/guide/capabilities/thinking` | **只认** `low/high/max`,其余输入报错;**不再支持关闭**(传 disabled 也报错)→ 表里没有 `off` |
|
|
18
|
+
* | 智谱 GLM-4.7 | 同上 | 强制思考且无 `reasoning_effort` 参数 → 不给它挂策略 |
|
|
19
|
+
*
|
|
20
|
+
* 三个结构性保证,都是「表的形状」带来的,不需要任何代码分支:
|
|
21
|
+
* 1. 可选档位 = `Object.keys(levels)`,不会有「菜单里有、发出去报错」的档位;
|
|
22
|
+
* 2. 能否关闭思考 = 表里有没有 `off` 键;
|
|
23
|
+
* 3. 某个键属于思考参数 = 它在表里出现过 —— 写入时按此剔除旧值,不需要硬编码键名。
|
|
24
|
+
*/
|
|
25
|
+
/** 策略表文件名(位于 `<basePath>/` 下,与 `config.json` 同级) */
|
|
26
|
+
export const REASONING_PRESETS_FILE = "reasoning-presets.json";
|
|
27
|
+
/** 出厂策略表 */
|
|
28
|
+
export const FACTORY_REASONING_PRESETS = {
|
|
29
|
+
/** OpenAI 系:只认 `reasoning_effort`;关闭思考 = `"none"` */
|
|
30
|
+
openai: {
|
|
31
|
+
levels: {
|
|
32
|
+
minimal: { reasoning_effort: "minimal" },
|
|
33
|
+
low: { reasoning_effort: "low" },
|
|
34
|
+
medium: { reasoning_effort: "medium" },
|
|
35
|
+
high: { reasoning_effort: "high" },
|
|
36
|
+
off: { reasoning_effort: "none" },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
/** 智谱 GLM-5.3 / 5.3-Flash / 5.3-FlashX:只认三档,且**不能关闭思考** */
|
|
40
|
+
"zhipu-5.3": {
|
|
41
|
+
levels: {
|
|
42
|
+
low: { thinking: { type: "enabled" }, reasoning_effort: "low" },
|
|
43
|
+
high: { thinking: { type: "enabled" }, reasoning_effort: "high" },
|
|
44
|
+
max: { thinking: { type: "enabled" }, reasoning_effort: "max" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
/** DeepSeek:`thinking` 开关 + `reasoning_effort`,可关闭 */
|
|
48
|
+
deepseek: {
|
|
49
|
+
levels: {
|
|
50
|
+
low: { thinking: { type: "enabled" }, reasoning_effort: "low" },
|
|
51
|
+
high: { thinking: { type: "enabled" }, reasoning_effort: "high" },
|
|
52
|
+
max: { thinking: { type: "enabled" }, reasoning_effort: "max" },
|
|
53
|
+
off: { thinking: { type: "disabled" } },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { PermissionPolicy, AgentPermissions, Endpoint, EndpointCapabilities, Model, ImageModel, AgentDefinition, AppConfig, ToolEnv, McpIcon, McpAnnotations, McpToolDef, McpResourceDef, McpPromptDef, McpServerManifest, McpServerConfig, McpConnectionState, } from "./types/index.js";
|
|
1
|
+
export type { PermissionPolicy, AgentPermissions, Endpoint, EndpointCapabilities, Model, TokenThresholds, ReasoningPatch, ReasoningPreset, ReasoningPresets, ImageModel, AgentDefinition, AppConfig, ToolEnv, McpIcon, McpAnnotations, McpToolDef, McpResourceDef, McpPromptDef, McpServerManifest, McpServerConfig, McpConnectionState, } from "./types/index.js";
|
|
2
2
|
export { PermissionEvaluator } from "./scope/PermissionEvaluator.js";
|
|
3
3
|
export type { CandidateSets } from "./scope/PermissionEvaluator.js";
|
|
4
4
|
export { createDisclosureParam } from "./scope/disclosure.js";
|
|
@@ -21,10 +21,12 @@ export { allInOne } from "./scope/plugins/allInOne.js";
|
|
|
21
21
|
export type { AllInOneOptions } from "./scope/plugins/allInOne.js";
|
|
22
22
|
export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
|
|
23
23
|
export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
|
|
24
|
+
export { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE, } from "./config/reasoning-presets.js";
|
|
24
25
|
export { EntityRepository } from "./shared/EntityRepository.js";
|
|
25
26
|
export { AgentRepository } from "./crud/AgentRepository.js";
|
|
26
27
|
export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
|
|
27
28
|
export type { FileSource, FileToSectionInput, FileToSectionResult, ContentTransport, UploadFn, } from "./shared/content-blocks.js";
|
|
29
|
+
export { applyReasoningEffort, modelLevels, presetOf, readReasoningEffort, resolveModelParams, resolveReasoningEffort, } from "./shared/reasoning.js";
|
|
28
30
|
export { createModel } from "./runtime/createModel.js";
|
|
29
31
|
export { SdkAgent } from "./runtime/SdkAgent.js";
|
|
30
32
|
export type { AgentPlugin, SendContext } from "./runtime/SdkAgent.js";
|
package/dist/index.js
CHANGED
|
@@ -19,11 +19,15 @@ export { allInOne } from "./scope/plugins/allInOne.js";
|
|
|
19
19
|
export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
|
|
20
20
|
// 出厂资产 — 出厂 Skill(随版本释放到 <basePath>/skills/)
|
|
21
21
|
export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
|
|
22
|
+
// 出厂资产 — 出厂思考策略(随版本释放到 <basePath>/reasoning-presets.json)
|
|
23
|
+
export { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE, } from "./config/reasoning-presets.js";
|
|
22
24
|
// CRUD — 实体仓储(仅 Agent 定义等能力实体)
|
|
23
25
|
export { EntityRepository } from "./shared/EntityRepository.js";
|
|
24
26
|
export { AgentRepository } from "./crud/AgentRepository.js";
|
|
25
27
|
// 文件 → 内容块(统一入口)— 上层应用构造媒体消息时请走这里,不要自己判断上传还是内联
|
|
26
28
|
export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
|
|
29
|
+
// 思考预设(reasoning presets)— 档位读取、校验与参数写入
|
|
30
|
+
export { applyReasoningEffort, modelLevels, presetOf, readReasoningEffort, resolveModelParams, resolveReasoningEffort, } from "./shared/reasoning.js";
|
|
27
31
|
// 运行时
|
|
28
32
|
export { createModel } from "./runtime/createModel.js";
|
|
29
33
|
export { SdkAgent } from "./runtime/SdkAgent.js";
|
|
@@ -37,7 +37,7 @@ export interface HandoffContext {
|
|
|
37
37
|
/** 当前 prompt token 用量(自动迁移时由插件提供;手动触发可省略) */
|
|
38
38
|
promptTokens?: number;
|
|
39
39
|
/** 上下文阈值(自动迁移时由插件提供;手动触发可省略) */
|
|
40
|
-
|
|
40
|
+
handoffTokens?: number;
|
|
41
41
|
/** 本次是否会在交接后开新会话(`migrate()` 为 true,`handoff()` 为 false)。钩子据此区分两种语义 */
|
|
42
42
|
migrating: boolean;
|
|
43
43
|
/** 交接前消息总数 */
|
|
@@ -83,7 +83,7 @@ export interface HandoffParams {
|
|
|
83
83
|
/** 当前 prompt token 用量(可选,仅透传给钩子) */
|
|
84
84
|
promptTokens?: number;
|
|
85
85
|
/** 上下文阈值(可选,仅透传给钩子) */
|
|
86
|
-
|
|
86
|
+
handoffTokens?: number;
|
|
87
87
|
/** 会话 id(可选,用于交接文件名,便于回溯) */
|
|
88
88
|
conversationId?: string;
|
|
89
89
|
}
|
|
@@ -194,7 +194,7 @@ export class HandoffService {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
async runHandoff(params, migrating) {
|
|
197
|
-
const { agent, promptTokens,
|
|
197
|
+
const { agent, promptTokens, handoffTokens, conversationId } = params;
|
|
198
198
|
const requestedMode = params.mode ?? this.defaultMode;
|
|
199
199
|
const handoffPath = join(this.handoffDirPath, HandoffService.createHandoffFileName(new Date(), conversationId));
|
|
200
200
|
const model = this.model ?? agent.model;
|
|
@@ -207,7 +207,7 @@ export class HandoffService {
|
|
|
207
207
|
handoffPath,
|
|
208
208
|
model,
|
|
209
209
|
promptTokens,
|
|
210
|
-
|
|
210
|
+
handoffTokens,
|
|
211
211
|
migrating,
|
|
212
212
|
messageCountBefore,
|
|
213
213
|
historyText: "",
|
|
@@ -241,7 +241,7 @@ export class HandoffService {
|
|
|
241
241
|
handoffDoc,
|
|
242
242
|
model,
|
|
243
243
|
promptTokens,
|
|
244
|
-
|
|
244
|
+
handoffTokens,
|
|
245
245
|
migrating,
|
|
246
246
|
messageCountBefore,
|
|
247
247
|
historyText,
|
|
@@ -18,7 +18,10 @@ export async function createAgent(scope, agentId) {
|
|
|
18
18
|
const modelId = definition.modelId ?? scope.config.defaultModel;
|
|
19
19
|
if (!modelId)
|
|
20
20
|
throw new Error("未指定模型且无默认模型");
|
|
21
|
-
const { client, model, modelConfig } = createModel(scope, modelId
|
|
21
|
+
const { client, model, modelConfig } = createModel(scope, modelId, {
|
|
22
|
+
// Agent 级思考档位覆盖(未声明时为 undefined,行为与以前一致)
|
|
23
|
+
reasoningEffort: definition.reasoningEffort,
|
|
24
|
+
});
|
|
22
25
|
const tools = scope.buildTools(definition, {
|
|
23
26
|
exclude: {
|
|
24
27
|
subagents: definition.function?.name
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import type { Scope } from "../scope/Scope.js";
|
|
3
|
+
import type { AgentDefinition } from "../types/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Agent 模型装配结果:openai SDK client + 模型名 + 模型参数。
|
|
5
6
|
* Agent 构造时直接消费此结构。
|
|
@@ -17,5 +18,10 @@ export interface AgentModel {
|
|
|
17
18
|
*
|
|
18
19
|
* 端点(baseUrl / apiKey)来自 config.endpoints,发送给 API 的模型名 = modelName || id。
|
|
19
20
|
* 任何 OpenAI 兼容端点(OpenAI / 智谱 BigModel / DeepSeek 等)均可通过 baseURL 接入。
|
|
21
|
+
*
|
|
22
|
+
* `overrides.reasoningEffort` 是 **Agent 级覆盖**(`AgentDefinition.reasoningEffort`):
|
|
23
|
+
* 同一个模型可以给不同 Agent 配不同思考强度,而模型自身的 `defaultParams` 不变。
|
|
24
|
+
* 档位如何翻译成请求参数,由 `scope.reasoningPresets` 里该模型引用的策略表决定
|
|
25
|
+
* (见 `shared/reasoning.ts`)。
|
|
20
26
|
*/
|
|
21
|
-
export declare function createModel(scope: Scope, modelId: string): AgentModel;
|
|
27
|
+
export declare function createModel(scope: Scope, modelId: string, overrides?: Pick<AgentDefinition, "reasoningEffort">): AgentModel;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
+
import { resolveModelParams } from "../shared/reasoning.js";
|
|
2
3
|
/**
|
|
3
4
|
* 模型工厂 — 根据 modelId 和 Scope 装配 openai SDK client。
|
|
4
5
|
*
|
|
5
6
|
* 端点(baseUrl / apiKey)来自 config.endpoints,发送给 API 的模型名 = modelName || id。
|
|
6
7
|
* 任何 OpenAI 兼容端点(OpenAI / 智谱 BigModel / DeepSeek 等)均可通过 baseURL 接入。
|
|
8
|
+
*
|
|
9
|
+
* `overrides.reasoningEffort` 是 **Agent 级覆盖**(`AgentDefinition.reasoningEffort`):
|
|
10
|
+
* 同一个模型可以给不同 Agent 配不同思考强度,而模型自身的 `defaultParams` 不变。
|
|
11
|
+
* 档位如何翻译成请求参数,由 `scope.reasoningPresets` 里该模型引用的策略表决定
|
|
12
|
+
* (见 `shared/reasoning.ts`)。
|
|
7
13
|
*/
|
|
8
|
-
export function createModel(scope, modelId) {
|
|
14
|
+
export function createModel(scope, modelId, overrides) {
|
|
9
15
|
const config = scope.config;
|
|
10
16
|
const modelConfig = config.models.find((m) => m.id === modelId);
|
|
11
17
|
if (!modelConfig)
|
|
@@ -25,6 +31,7 @@ export function createModel(scope, modelId) {
|
|
|
25
31
|
return {
|
|
26
32
|
client,
|
|
27
33
|
model: apiModelName,
|
|
28
|
-
|
|
34
|
+
// 档位覆盖在这里生效:未声明 reasoningEffort 时原样返回模型 defaultParams
|
|
35
|
+
modelConfig: resolveModelParams(modelConfig, scope.reasoningPresets, overrides),
|
|
29
36
|
};
|
|
30
37
|
}
|
package/dist/scope/Scope.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Tool } from "@ai-zen/agents-core";
|
|
2
|
-
import type { AppConfig, AgentDefinition, ToolEnv } from "../types/index.js";
|
|
2
|
+
import type { AppConfig, AgentDefinition, ReasoningPresets, ToolEnv } from "../types/index.js";
|
|
3
3
|
import type { ScopePlugin } from "./ScopePlugin.js";
|
|
4
4
|
/**
|
|
5
5
|
* 能力过滤系统的公共类型。
|
|
@@ -56,6 +56,13 @@ export declare class Scope {
|
|
|
56
56
|
readonly env: ToolEnv;
|
|
57
57
|
/** Agent 定义目录(createAgent 用) */
|
|
58
58
|
readonly agentsDir: string;
|
|
59
|
+
/**
|
|
60
|
+
* 思考策略表(`<basePath>/reasoning-presets.json`)。
|
|
61
|
+
*
|
|
62
|
+
* 与 `config` 平级、同样由调用方加载后注入:`createModel` 靠它把 Agent 的
|
|
63
|
+
* `reasoningEffort` 翻译成具体请求参数。不传则视为「无任何可选档位」。
|
|
64
|
+
*/
|
|
65
|
+
readonly reasoningPresets: ReasoningPresets;
|
|
59
66
|
/** 已注册的插件 */
|
|
60
67
|
private _plugins;
|
|
61
68
|
/** 是否已 init(init 之后禁止再 use) */
|
|
@@ -64,6 +71,7 @@ export declare class Scope {
|
|
|
64
71
|
config: AppConfig;
|
|
65
72
|
agentsDir: string;
|
|
66
73
|
cwd?: string;
|
|
74
|
+
reasoningPresets?: ReasoningPresets;
|
|
67
75
|
});
|
|
68
76
|
/** 已注册插件(只读视图) */
|
|
69
77
|
get plugins(): readonly ScopePlugin[];
|
package/dist/scope/Scope.js
CHANGED
|
@@ -29,12 +29,20 @@ export class Scope {
|
|
|
29
29
|
env;
|
|
30
30
|
/** Agent 定义目录(createAgent 用) */
|
|
31
31
|
agentsDir;
|
|
32
|
+
/**
|
|
33
|
+
* 思考策略表(`<basePath>/reasoning-presets.json`)。
|
|
34
|
+
*
|
|
35
|
+
* 与 `config` 平级、同样由调用方加载后注入:`createModel` 靠它把 Agent 的
|
|
36
|
+
* `reasoningEffort` 翻译成具体请求参数。不传则视为「无任何可选档位」。
|
|
37
|
+
*/
|
|
38
|
+
reasoningPresets;
|
|
32
39
|
/** 已注册的插件 */
|
|
33
40
|
_plugins = [];
|
|
34
41
|
/** 是否已 init(init 之后禁止再 use) */
|
|
35
42
|
_initialized = false;
|
|
36
43
|
constructor(options) {
|
|
37
44
|
this.config = options.config;
|
|
45
|
+
this.reasoningPresets = options.reasoningPresets ?? {};
|
|
38
46
|
this.cwd = options.cwd ?? process.cwd();
|
|
39
47
|
this.env = { cwd: this.cwd, config: this.config };
|
|
40
48
|
this.agentsDir = options.agentsDir;
|
package/dist/shared/errors.d.ts
CHANGED
|
@@ -8,17 +8,13 @@ export declare class SdkError extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* 上下文超限错误。由 ContextGuardPlugin 在请求前抛出,用于中断对话。
|
|
11
|
-
* 触发条件通常是上下文 token
|
|
11
|
+
* 触发条件通常是上下文 token 用量超过硬线(如读入超大文件),
|
|
12
12
|
* 继续对话已无意义且会持续放大费用/延迟,故由上层捕获后向用户显式报错。
|
|
13
13
|
*/
|
|
14
14
|
export declare class ContextOverflowError extends Error {
|
|
15
15
|
/** 触发中断时的刚用量(usage.prompt_tokens) */
|
|
16
16
|
readonly promptTokens: number;
|
|
17
|
-
/**
|
|
18
|
-
readonly
|
|
19
|
-
|
|
20
|
-
readonly threshold: number;
|
|
21
|
-
/** 超出比例(如 1.2 表示 20%) */
|
|
22
|
-
readonly ratio: number;
|
|
23
|
-
constructor(promptTokens: number, maxTokens: number, ratio: number);
|
|
17
|
+
/** 被越过的强行阻断阈值(`tokenThresholds.guard`,绝对值) */
|
|
18
|
+
readonly guardTokens: number;
|
|
19
|
+
constructor(promptTokens: number, guardTokens: number);
|
|
24
20
|
}
|
package/dist/shared/errors.js
CHANGED
|
@@ -15,26 +15,20 @@ export class SdkError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* 上下文超限错误。由 ContextGuardPlugin 在请求前抛出,用于中断对话。
|
|
18
|
-
* 触发条件通常是上下文 token
|
|
18
|
+
* 触发条件通常是上下文 token 用量超过硬线(如读入超大文件),
|
|
19
19
|
* 继续对话已无意义且会持续放大费用/延迟,故由上层捕获后向用户显式报错。
|
|
20
20
|
*/
|
|
21
21
|
export class ContextOverflowError extends Error {
|
|
22
22
|
/** 触发中断时的刚用量(usage.prompt_tokens) */
|
|
23
23
|
promptTokens;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
constructor(promptTokens, maxTokens, ratio) {
|
|
31
|
-
const threshold = maxTokens * ratio;
|
|
32
|
-
super(`🛑 上下文严重超限:已用 ${promptTokens} token,超过硬上限 ${threshold}(${Math.round((ratio - 1) * 100)}%,配置上限 ${maxTokens})。` +
|
|
33
|
-
`当前对话可能因读入超大文件而失控,已中断,请检查并重新开始。`);
|
|
24
|
+
/** 被越过的强行阻断阈值(`tokenThresholds.guard`,绝对值) */
|
|
25
|
+
guardTokens;
|
|
26
|
+
constructor(promptTokens, guardTokens) {
|
|
27
|
+
super(`🛑 上下文严重超限:已用 ${promptTokens} token,超过阻断阈值 ${guardTokens}。` +
|
|
28
|
+
`当前对话可能因读入超大文件而失控,已中断,请检查并重新开始。` +
|
|
29
|
+
`(如确需更长上下文,请调高 config.json 中该模型的 tokenThresholds.guard。)`);
|
|
34
30
|
this.name = "ContextOverflowError";
|
|
35
31
|
this.promptTokens = promptTokens;
|
|
36
|
-
this.
|
|
37
|
-
this.threshold = threshold;
|
|
38
|
-
this.ratio = ratio;
|
|
32
|
+
this.guardTokens = guardTokens;
|
|
39
33
|
}
|
|
40
34
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 思考预设(reasoning presets)—— 档位读取、校验与参数写入的唯一实现
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么是「表」而不是「字段」
|
|
5
|
+
*
|
|
6
|
+
* 档位不是全局统一枚举:各端点接受的值域互不相同,同一家不同型号也不同
|
|
7
|
+
* (见 `config/reasoning-presets.ts` 的文档表)。所以本模块不判断「厂商是谁」,
|
|
8
|
+
* 只做一件事:**在模型关联的那张策略表里查档位、写补丁**。
|
|
9
|
+
*
|
|
10
|
+
* 于是三个问题都变成查表,不需要任何 if/else 分支:
|
|
11
|
+
* 1. 可选档位有哪些? → `Object.keys(preset.levels)`
|
|
12
|
+
* 2. 能不能关闭思考? → 表里有没有 `off` 键
|
|
13
|
+
* 3. 哪些参数键属于思考? → 所有补丁里出现过的键的并集(写入前先剔除)
|
|
14
|
+
*
|
|
15
|
+
* 第 3 条是关键:有了它,从 `max` 切到 `off` 不会残留上一档的参数,而代码里
|
|
16
|
+
* 一个键名都不必硬编码(`thinking` / `reasoning_effort` 只出现在策略表里)。
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentDefinition, Model, ReasoningPreset, ReasoningPresets } from "../types/index.js";
|
|
19
|
+
/**
|
|
20
|
+
* 模型关联的策略(未声明 `reasoningPreset`、或名字在表里不存在 → `undefined`)。
|
|
21
|
+
*
|
|
22
|
+
* 名字不存在时**不猜、不兜底**:宁可视作不可调档,也不把来路不明的参数发给服务端。
|
|
23
|
+
*/
|
|
24
|
+
export declare function presetOf(model?: Model | null, presets?: ReasoningPresets | null): ReasoningPreset | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* 模型可选的全部档位(顺序 = 策略表声明顺序)。
|
|
27
|
+
*
|
|
28
|
+
* 不再区分「档位」与「关闭」:表里的键就是菜单里的项。未关联策略 → 空数组。
|
|
29
|
+
*/
|
|
30
|
+
export declare function modelLevels(model?: Model | null, presets?: ReasoningPresets | null): string[];
|
|
31
|
+
/**
|
|
32
|
+
* 反查当前档位:参数里命中哪个补丁的键值,就是哪一档。
|
|
33
|
+
*
|
|
34
|
+
* 与 `applyReasoningEffort()` 互为逆运算,两边共用同一张表。
|
|
35
|
+
* 多张补丁同时命中时取**最具体**的那个(键最多的),键数相同取先声明的。
|
|
36
|
+
*
|
|
37
|
+
* 返回 `undefined` = 参数不完整匹配任何档位(例如 DeepSeek 出厂只写了
|
|
38
|
+
* `thinking: { type: "enabled" }` 而没写 effort),展示层应视为「跟随模型默认」。
|
|
39
|
+
*/
|
|
40
|
+
export declare function readReasoningEffort(model?: Model | null, presets?: ReasoningPresets | null, params?: Record<string, unknown> | null): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* 把档位写成请求参数(不改入参)。
|
|
43
|
+
*
|
|
44
|
+
* 写入方式 = **先剔除思考参数键的旧值,再合并新补丁**。这样 `max` → `off` 不会残留
|
|
45
|
+
* 上一档的 `reasoning_effort`,模型换策略也不会反刍旧策略的参数,而「哪些键属于思考」
|
|
46
|
+
* 由策略表自己声明,代码零硬编码。
|
|
47
|
+
*
|
|
48
|
+
* @throws 模型未关联策略、或策略里没有该档位(绝不静默降级:档位写错必须当场发现)
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyReasoningEffort(model: Model | null | undefined, presets: ReasoningPresets | null | undefined, level: string, params?: Record<string, unknown> | null): Record<string, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* 解析某 Agent 的实际档位:Agent 声明优先,否则反查模型默认参数。
|
|
53
|
+
*
|
|
54
|
+
* 声明了但策略里没有这一档 → 退化为模型默认(宁可忽略设置,也不发非法参数)。
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveReasoningEffort(model?: Model | null, presets?: ReasoningPresets | null, definition?: Pick<AgentDefinition, "reasoningEffort"> | null): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* 把 Agent 的档位覆盖应用到模型参数上(`createModel` 用)。
|
|
59
|
+
*
|
|
60
|
+
* 未声明 `reasoningEffort` 时原样返回模型参数,保证既有行为不变。
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveModelParams(model?: Model | null, presets?: ReasoningPresets | null, definition?: Pick<AgentDefinition, "reasoningEffort"> | null): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 思考预设(reasoning presets)—— 档位读取、校验与参数写入的唯一实现
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么是「表」而不是「字段」
|
|
5
|
+
*
|
|
6
|
+
* 档位不是全局统一枚举:各端点接受的值域互不相同,同一家不同型号也不同
|
|
7
|
+
* (见 `config/reasoning-presets.ts` 的文档表)。所以本模块不判断「厂商是谁」,
|
|
8
|
+
* 只做一件事:**在模型关联的那张策略表里查档位、写补丁**。
|
|
9
|
+
*
|
|
10
|
+
* 于是三个问题都变成查表,不需要任何 if/else 分支:
|
|
11
|
+
* 1. 可选档位有哪些? → `Object.keys(preset.levels)`
|
|
12
|
+
* 2. 能不能关闭思考? → 表里有没有 `off` 键
|
|
13
|
+
* 3. 哪些参数键属于思考? → 所有补丁里出现过的键的并集(写入前先剔除)
|
|
14
|
+
*
|
|
15
|
+
* 第 3 条是关键:有了它,从 `max` 切到 `off` 不会残留上一档的参数,而代码里
|
|
16
|
+
* 一个键名都不必硬编码(`thinking` / `reasoning_effort` 只出现在策略表里)。
|
|
17
|
+
*/
|
|
18
|
+
/** 安全取自有属性(策略名源自用户配置,可能是 `constructor` / `__proto__` 等) */
|
|
19
|
+
function own(bag, key) {
|
|
20
|
+
if (!bag || !Object.prototype.hasOwnProperty.call(bag, key))
|
|
21
|
+
return undefined;
|
|
22
|
+
return bag[key];
|
|
23
|
+
}
|
|
24
|
+
/** 结构化相等 —— 补丁是纯 JSON,逐层比对即可 */
|
|
25
|
+
function sameValue(a, b) {
|
|
26
|
+
if (a === b)
|
|
27
|
+
return true;
|
|
28
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
29
|
+
return false;
|
|
30
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
31
|
+
return false;
|
|
32
|
+
const left = a;
|
|
33
|
+
const right = b;
|
|
34
|
+
const leftKeys = Object.keys(left);
|
|
35
|
+
if (leftKeys.length !== Object.keys(right).length)
|
|
36
|
+
return false;
|
|
37
|
+
return leftKeys.every((k) => Object.prototype.hasOwnProperty.call(right, k) && sameValue(left[k], right[k]));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 模型关联的策略(未声明 `reasoningPreset`、或名字在表里不存在 → `undefined`)。
|
|
41
|
+
*
|
|
42
|
+
* 名字不存在时**不猜、不兜底**:宁可视作不可调档,也不把来路不明的参数发给服务端。
|
|
43
|
+
*/
|
|
44
|
+
export function presetOf(model, presets) {
|
|
45
|
+
const name = model?.reasoningPreset;
|
|
46
|
+
if (!name)
|
|
47
|
+
return undefined;
|
|
48
|
+
return own(presets ?? undefined, name);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 模型可选的全部档位(顺序 = 策略表声明顺序)。
|
|
52
|
+
*
|
|
53
|
+
* 不再区分「档位」与「关闭」:表里的键就是菜单里的项。未关联策略 → 空数组。
|
|
54
|
+
*/
|
|
55
|
+
export function modelLevels(model, presets) {
|
|
56
|
+
const preset = presetOf(model, presets);
|
|
57
|
+
if (!preset)
|
|
58
|
+
return [];
|
|
59
|
+
return Object.keys(preset.levels ?? {});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 「思考参数键」的全集 —— **整张策略表**里任何补丁出现过的键。
|
|
63
|
+
*
|
|
64
|
+
* 用整张表而非单个策略,是为了让换策略时不留残渣:模型从 `deepseek` 改挂 `openai`
|
|
65
|
+
* 时,旧策略写进去的 `thinking` 也必须被清掉(OpenAI 不认这个参数,带着就 400)。
|
|
66
|
+
* 好处依旧:键名一个都不用在代码里硬编码,全部由表声明。
|
|
67
|
+
*/
|
|
68
|
+
function ownedKeys(presets) {
|
|
69
|
+
const keys = new Set();
|
|
70
|
+
for (const preset of Object.values(presets ?? {})) {
|
|
71
|
+
for (const patch of Object.values(preset?.levels ?? {})) {
|
|
72
|
+
for (const key of Object.keys(patch ?? {}))
|
|
73
|
+
keys.add(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return keys;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 反查当前档位:参数里命中哪个补丁的键值,就是哪一档。
|
|
80
|
+
*
|
|
81
|
+
* 与 `applyReasoningEffort()` 互为逆运算,两边共用同一张表。
|
|
82
|
+
* 多张补丁同时命中时取**最具体**的那个(键最多的),键数相同取先声明的。
|
|
83
|
+
*
|
|
84
|
+
* 返回 `undefined` = 参数不完整匹配任何档位(例如 DeepSeek 出厂只写了
|
|
85
|
+
* `thinking: { type: "enabled" }` 而没写 effort),展示层应视为「跟随模型默认」。
|
|
86
|
+
*/
|
|
87
|
+
export function readReasoningEffort(model, presets, params) {
|
|
88
|
+
const preset = presetOf(model, presets);
|
|
89
|
+
if (!preset)
|
|
90
|
+
return undefined;
|
|
91
|
+
// 显式传 null = 「没有参数」,不回落模型默认;不传才读模型默认
|
|
92
|
+
const actual = params === undefined ? model?.defaultParams : params;
|
|
93
|
+
if (!actual)
|
|
94
|
+
return undefined;
|
|
95
|
+
let best;
|
|
96
|
+
let bestSize = 0;
|
|
97
|
+
for (const [level, patch] of Object.entries(preset.levels ?? {})) {
|
|
98
|
+
const keys = Object.keys(patch ?? {});
|
|
99
|
+
if (keys.length === 0 || keys.length <= bestSize)
|
|
100
|
+
continue;
|
|
101
|
+
if (!keys.every((k) => sameValue(actual[k], patch[k])))
|
|
102
|
+
continue;
|
|
103
|
+
best = level;
|
|
104
|
+
bestSize = keys.length;
|
|
105
|
+
}
|
|
106
|
+
return best;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 把档位写成请求参数(不改入参)。
|
|
110
|
+
*
|
|
111
|
+
* 写入方式 = **先剔除思考参数键的旧值,再合并新补丁**。这样 `max` → `off` 不会残留
|
|
112
|
+
* 上一档的 `reasoning_effort`,模型换策略也不会反刍旧策略的参数,而「哪些键属于思考」
|
|
113
|
+
* 由策略表自己声明,代码零硬编码。
|
|
114
|
+
*
|
|
115
|
+
* @throws 模型未关联策略、或策略里没有该档位(绝不静默降级:档位写错必须当场发现)
|
|
116
|
+
*/
|
|
117
|
+
export function applyReasoningEffort(model, presets, level, params) {
|
|
118
|
+
const id = model?.id ?? "(未知)";
|
|
119
|
+
const name = model?.reasoningPreset;
|
|
120
|
+
const preset = presetOf(model, presets);
|
|
121
|
+
if (!preset) {
|
|
122
|
+
throw new Error(name
|
|
123
|
+
? `模型 "${id}" 关联的思考策略 "${name}" 不存在(检查 reasoning-presets.json)`
|
|
124
|
+
: `模型 "${id}" 未关联思考策略(reasoningPreset),无法设置档位`);
|
|
125
|
+
}
|
|
126
|
+
const patch = own(preset.levels, level);
|
|
127
|
+
if (!patch) {
|
|
128
|
+
throw new Error(`策略 "${name}" 没有档位 "${level}"(可选:${Object.keys(preset.levels).join(" / ")})`);
|
|
129
|
+
}
|
|
130
|
+
const owned = ownedKeys(presets);
|
|
131
|
+
const base = params === undefined ? model?.defaultParams : params;
|
|
132
|
+
const kept = Object.entries(base ?? {}).filter(([key]) => !owned.has(key));
|
|
133
|
+
return { ...Object.fromEntries(kept), ...patch };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 解析某 Agent 的实际档位:Agent 声明优先,否则反查模型默认参数。
|
|
137
|
+
*
|
|
138
|
+
* 声明了但策略里没有这一档 → 退化为模型默认(宁可忽略设置,也不发非法参数)。
|
|
139
|
+
*/
|
|
140
|
+
export function resolveReasoningEffort(model, presets, definition) {
|
|
141
|
+
const declared = definition?.reasoningEffort;
|
|
142
|
+
if (declared && modelLevels(model, presets).includes(declared))
|
|
143
|
+
return declared;
|
|
144
|
+
return readReasoningEffort(model, presets);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 把 Agent 的档位覆盖应用到模型参数上(`createModel` 用)。
|
|
148
|
+
*
|
|
149
|
+
* 未声明 `reasoningEffort` 时原样返回模型参数,保证既有行为不变。
|
|
150
|
+
*/
|
|
151
|
+
export function resolveModelParams(model, presets, definition) {
|
|
152
|
+
const declared = definition?.reasoningEffort;
|
|
153
|
+
if (!declared)
|
|
154
|
+
return model?.defaultParams;
|
|
155
|
+
// 模型没关联策略、或策略里没这一档:保持模型默认,绝不发非法参数
|
|
156
|
+
if (!modelLevels(model, presets).includes(declared))
|
|
157
|
+
return model?.defaultParams;
|
|
158
|
+
return applyReasoningEffort(model, presets, declared);
|
|
159
|
+
}
|