@ai-zen/agents-sdk 1.0.0-alpha.13 → 1.0.0-alpha.15

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 CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-alpha.15] - 2026-10-22
4
+
5
+ ### 🔧 行为变更(不兼容)
6
+
7
+ - **SDK 不再「消费」思考档位,只保留表本身** —— alpha.14 把「档位 → 请求参数」的整套解析塞进了 SDK 运行时:`applyReasoningEffort` / `modelLevels` / `resolveReasoningEffort` / `resolveModelParams`,还要 `Scope.reasoningPresets` 注入、Agent 上存档位名、运行时查表拼参数。但 `low` / `high` / `max` / `off` 是**产品语义,不是协议事实**,SDK 唯一客观知道的是「请求参数长什么样」——那本来就由 `defaultParams` 承载。现在:
8
+ - **删除 `shared/reasoning.ts` 的全部 API**(`applyReasoningEffort` / `modelLevels` / `presetOf` / `readReasoningEffort` / `resolveReasoningEffort` / `resolveModelParams`)以及 `Scope.reasoningPresets`;
9
+ - **保留**思考预设的**定义与出厂内容**(`ReasoningPresets` / `ReasoningPreset` / `ReasoningPatch` 类型、`config/reasoning-presets.ts`、`ConfigManager.readReasoningPresets()` / `ensureReasoningPresets()`)—— 它是 CLI / 桌面端等客户端**共享的资产**(「智谱 5.3 只认 low/high/max」这种知识不该被每个客户端各写一遍),但那只是**数据**,SDK 不拿它生成请求参数。
10
+ - **`AgentDefinition.reasoningEffort` → `AgentDefinition.modelParams?: ModelParams`** —— Agent 级覆盖从「档位名」变成「具体参数」。客户端读到表、拿档位补丁,在**写入配置时**就把它展开存进 `modelParams` 里。于是**配置即事实**:运行期不查表、不推导。
11
+ - **`createModel(scope, modelId, overrides?: ModelParams)`** —— 第三参从 `Pick<AgentDefinition, "reasoningEffort">` 变成裸的参数对象,内部只有一行:
12
+ ```ts
13
+ modelConfig = { ...model.defaultParams, ...overrides };
14
+ ```
15
+ 浅合并(嵌套对象整体替换),没有覆盖时原样返回 `defaultParams`。SDK 从此不解释任何键名,也不知道 `thinking` / `reasoning_effort` 是什么。SubAgent 指定独立 `modelId` 时同样应用自己的 `modelParams`。
16
+ - **`Model.reasoningPreset` 保留**,语义改为「引用策略表的键」,供客户端判断该模型能选哪几档;SDK 只保证它能随模型配置一起读写与出厂同步,不消费。
17
+
18
+ ### 🐛 修复
19
+
20
+ - **迁移 `Model.reasoning` 旧字段(alpha.13 遗留)** —— 未标 `custom` 的模型会被出厂定义整块覆盖,但**标了 `custom: true` 的会原地保留**,于是留下一个无人认识的 `reasoning`,同时因为缺 `reasoningPreset` 而被客户端判定为「不可调档」。`ConfigManager.read()` 现在会删掉该旧字段,并按同 id 的出厂模型补回 `reasoningPreset`;id 不在出厂清单的(真·自建模型)只删不补。只处理「旧字段存在」这一种情况,用户后续主动删掉 `reasoningPreset` 不会被反复补回来。
21
+
22
+ ### ✅ 测试
23
+
24
+ - `src/runtime/createModel.test.ts` 新增 6 例锁住合并语义:无覆盖原样返回、空覆盖等同无覆盖、同名键覆盖而其它键保留、模型无 `defaultParams` 时覆盖即全部、**浅合并不做深合并**(换档位不残留旧子键)、两个 Agent 各配一套互不影响。
25
+ - `src/config/migrate.test.ts` 新增 6 例(19 例合计)。
26
+ - 删除 `src/shared/reasoning.test.ts`(26 例,运行时解析已移除);`src/config/reasoning-presets.test.ts`(11 例)保留 —— 表还在 SDK。
27
+ - 全量 **57 文件 / 600 例通过**(6 skipped)。
28
+
29
+ ## [1.0.0-alpha.14] - 2026-10-22
30
+
31
+ ### 🔧 行为变更(不兼容)
32
+
33
+ - **思考能力声明从「模型字段」改为「策略表」** —— alpha.13 那版用 `Model.reasoning: { efforts, disable? }` 表达厂商差异,结果三处结构性缺陷:`disable` 一词双义(既指关闭方式又指能力)、`off` 靠拼接而非声明(`disable ? ["off", ...efforts] : efforts`)、用全局 `ReasoningEffort` 枚举假装各家档位等价。现在改为**档位 → 请求参数补丁**的声明式表:
34
+ - 新增 `<basePath>/reasoning-presets.json`(与 `config.json` 同级),出厂内容见 `config/reasoning-presets.ts`;
35
+ - `Model.reasoning?` → **`Model.reasoningPreset?: string`**(引用策略名;缺省 = 不可调档);
36
+ - `AgentDefinition.reasoningEffort` 类型 `ReasoningEffort` → **`string`**(档位名由策略表定义,不再用假枚举假装统一);
37
+ - 删除 `ReasoningEffort` / `ReasoningCapability` 类型与 `capabilityOf` / `modelEfforts` / `modelAllowedEfforts` / `isReasoningEffort` 函数;
38
+ - 函数签名统一以 `(model, presets, …)` 开头:`readReasoningEffort(model, presets, params?)`、`applyReasoningEffort(model, presets, level, params?)`、`resolveReasoningEffort(model, presets, definition?)`、`resolveModelParams(model, presets, definition?)`;
39
+ - `Scope` 新增 `reasoningPresets`(与 `config` 平级注入,不做全局单例)。
40
+
41
+ 换来三个结构性保证,全部来自表的形状、代码零分支:① 可选档位 = `Object.keys(levels)`;② 能否关闭思考 = 表里有没有 `off` 键;③ 「哪些参数键属于思考」= 整张表出现过的键的并集(写入前按此剔除旧值,`max` → `off` 不再残留 `reasoning_effort`,模型换策略也不会反刍上一套参数)。
42
+
43
+ - **`applyReasoningEffort` 的档位校验更严** —— 策略里没有该档位直接抛错(旧版对未声明能力的模型给的是「不支持档位」文案),错误信息带上策略名与可选档位列表。
44
+
45
+ ### 🚀 新功能
46
+
47
+ - **`ConfigManager.readReasoningPresets()` / `ensureReasoningPresets()`** —— 策略表的读写与出厂同步。同步粒度**到单个策略**,规则与出厂模型同步同构:文件缺失即写种子;`custom: true` 的策略冻结;未标的用出厂版覆盖;用户自建的非出厂策略永远保留。`bootstrap()` 会自动调用。文件缺失或损坏时 `readReasoningPresets()` 回落到出厂种子,不让上层拿到空表。
48
+ - **`presetOf(model, presets)` / `modelLevels(model, presets)`** —— 策略查找与可选档位读取(`modelLevels` 取代旧的 `modelEfforts` + `modelAllowedEfforts` 两个函数:`off` 现在只是表里的一行,没有「档位」与「关闭」的二分)。
49
+ - **包入口补导出 `TokenThresholds` / `ReasoningPatch` / `ReasoningPreset` / `ReasoningPresets`** —— alpha.13 漏了这几个类型的导出(原 `7b10196` 补丁并入本版本)。
50
+
51
+ ### ✅ 测试
52
+
53
+ - `src/shared/reasoning.test.ts` 重写(26 例):出厂表协议事实(OpenAI 表无 `thinking` 键、`zhipu-5.3` 表无 `off`、DeepSeek 用 `thinking.type` 关闭)、可调档位 = 表的键、反查取最具体匹配、写入剔除旧键、往返一致、Agent 声明非法回退模型默认、原型链污染名字(`constructor`)不生效。
54
+ - `src/config/reasoning-presets.test.ts` 新增(11 例):落盘位置、首次释放、幂等不写盘、未标 `custom` 被覆盖、标了 `custom` 冻结、用户自建策略保留、损坏/数组内容回落种子、`bootstrap()` 顺带释放。
55
+
56
+ ### 📄 文档
57
+
58
+ - `docs/{zh,en}/sdk.md`:「思考强度」章节重写为「思考预设(reasoning presets)」,含策略表结构、出厂策略对照表、三个结构性保证、新 API 表与 `Scope` 注入示例。
59
+ - 顺手修掉 alpha.13 之后就过期的示例:`ContextGuardPlugin({ maxTokens, isSuspended })` → `{ guardTokens }`、`AutoMigratePlugin({ maxTokens })` → `{ handoffTokens }`、「交接回合护栏放宽 × 2」的旧描述。
60
+
3
61
  ## [1.0.0-alpha.13] - 2026-10-22
4
62
 
5
63
  ### 💥 Breaking Changes
@@ -1,4 +1,4 @@
1
- import type { AppConfig, AgentDefinition } from "../types/index.js";
1
+ import type { AppConfig, AgentDefinition, ReasoningPresets } from "../types/index.js";
2
2
  /**
3
3
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
4
4
  */
@@ -58,6 +58,27 @@ export declare class ConfigManager {
58
58
  agent: AgentDefinition | null;
59
59
  subAgent: AgentDefinition | null;
60
60
  }>;
61
+ /** 策略表文件路径(与 config.json 同级) */
62
+ get reasoningPresetsPath(): string;
63
+ /**
64
+ * 读取思考策略表。
65
+ *
66
+ * 文件缺失或无法解析时返回出厂种子(内存值,不写盘)——保证调用方永远拿到可用的表:
67
+ * 用户误删文件后客户端仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
68
+ */
69
+ readReasoningPresets(): Promise<ReasoningPresets>;
70
+ /**
71
+ * 释放 / 同步出厂思考策略。
72
+ *
73
+ * 覆盖策略与 `syncManagedModels` 同构,**按策略逐个判断**(粒度到单个策略):
74
+ * - 文件不存在 → 写入出厂种子;
75
+ * - 某策略标了 `custom: true` → 用户自有,原样保留;
76
+ * - 某策略未标 → 用出厂版覆盖该策略(内容相同则不写盘);
77
+ * - 用户自建的非出厂策略 → 始终保留。
78
+ *
79
+ * @returns 实际写入的策略名列表(未变化 / 用户自有 均不计入)
80
+ */
81
+ ensureReasoningPresets(): Promise<string[]>;
61
82
  /**
62
83
  * 释放 / 同步出厂 Skill。
63
84
  *
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
4
4
  import { FACTORY_SKILLS, isCustomSkill } from "./factory-skills.js";
5
+ import { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE } from "./reasoning-presets.js";
5
6
  import { migrateConfig } from "./migrate.js";
6
7
  /**
7
8
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
@@ -188,9 +189,75 @@ export class ConfigManager {
188
189
  const subAgent = await this.ensureDefaultSubAgent();
189
190
  await this.ensureDefaultMcpConfig();
190
191
  await this.ensureFactorySkills();
192
+ await this.ensureReasoningPresets();
191
193
  return { config, agent, subAgent };
192
194
  }
193
195
  // -----------------------------------------------------------------------
196
+ // 思考策略表({basePath}/reasoning-presets.json)
197
+ // -----------------------------------------------------------------------
198
+ /** 策略表文件路径(与 config.json 同级) */
199
+ get reasoningPresetsPath() {
200
+ return join(this.basePath, REASONING_PRESETS_FILE);
201
+ }
202
+ /**
203
+ * 读取思考策略表。
204
+ *
205
+ * 文件缺失或无法解析时返回出厂种子(内存值,不写盘)——保证调用方永远拿到可用的表:
206
+ * 用户误删文件后客户端仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
207
+ */
208
+ async readReasoningPresets() {
209
+ try {
210
+ const raw = await fs.readFile(this.reasoningPresetsPath, "utf-8");
211
+ const parsed = JSON.parse(raw);
212
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
213
+ return parsed;
214
+ }
215
+ catch {
216
+ // 缺失或损坏 → 回落出厂种子
217
+ }
218
+ return { ...FACTORY_REASONING_PRESETS };
219
+ }
220
+ /**
221
+ * 释放 / 同步出厂思考策略。
222
+ *
223
+ * 覆盖策略与 `syncManagedModels` 同构,**按策略逐个判断**(粒度到单个策略):
224
+ * - 文件不存在 → 写入出厂种子;
225
+ * - 某策略标了 `custom: true` → 用户自有,原样保留;
226
+ * - 某策略未标 → 用出厂版覆盖该策略(内容相同则不写盘);
227
+ * - 用户自建的非出厂策略 → 始终保留。
228
+ *
229
+ * @returns 实际写入的策略名列表(未变化 / 用户自有 均不计入)
230
+ */
231
+ async ensureReasoningPresets() {
232
+ let onDisk = null;
233
+ try {
234
+ onDisk = JSON.parse(await fs.readFile(this.reasoningPresetsPath, "utf-8"));
235
+ }
236
+ catch {
237
+ onDisk = null;
238
+ }
239
+ if (!onDisk || typeof onDisk !== "object" || Array.isArray(onDisk)) {
240
+ await fs.mkdir(this.basePath, { recursive: true });
241
+ await fs.writeFile(this.reasoningPresetsPath, JSON.stringify(FACTORY_REASONING_PRESETS, null, 2), "utf-8");
242
+ return Object.keys(FACTORY_REASONING_PRESETS);
243
+ }
244
+ const next = { ...onDisk };
245
+ const written = [];
246
+ for (const [name, preset] of Object.entries(FACTORY_REASONING_PRESETS)) {
247
+ const existing = onDisk[name];
248
+ if (existing?.custom === true)
249
+ continue; // 用户改过 → 冻结
250
+ if (JSON.stringify(existing) === JSON.stringify(preset))
251
+ continue; // 已是最新
252
+ next[name] = preset;
253
+ written.push(name);
254
+ }
255
+ if (written.length > 0) {
256
+ await fs.writeFile(this.reasoningPresetsPath, JSON.stringify(next, null, 2), "utf-8");
257
+ }
258
+ return written;
259
+ }
260
+ // -----------------------------------------------------------------------
194
261
  // 出厂 Skill({basePath}/skills/<id>/SKILL.md)
195
262
  // -----------------------------------------------------------------------
196
263
  /**
@@ -103,15 +103,11 @@ export const DEFAULT_APP_CONFIG = {
103
103
  // 出厂取值:窗口 ≥1M 的模型统一 handoff=250K / guard=375K(约窗口的 25% / 37.5%),
104
104
  // glm-4.7-flash 窗口仅 200K,取 handoff=100K / guard=150K。
105
105
  //
106
- // reasoning(思考强度能力)按厂商官方文档声明,**不要推测**:
107
- // OpenAI → openai-node 的 Shared.ReasoningEffort(none/minimal/low/medium/high/xhigh/max);
108
- // 关闭用 reasoning_effort:"none"(OpenAI 没有 thinking 参数,写它会被当未知参数)
109
- // DeepSeek → api-docs.deepseek.com/guides/thinking_mode:可调 low/high/max;
110
- // 关闭用 thinking.type:"disabled";默认开思考且默认 effort=high
111
- // 智谱 → docs.bigmodel.cn/cn/guide/capabilities/thinking:GLM-5.3 系**只认**
112
- // low/high/max(其余输入报错),且不再支持关闭思考(传 disabled 会报错);
113
- // GLM-4.7 强制思考且无 reasoning_effort 参数(仅 5.2+ 支持)
114
- // 未声明 reasoning 的模型 = 不可调档,`modelAllowedEfforts()` 返回空数组。
106
+ // reasoningPreset:应用层元数据,**SDK 不解释**(既不参与请求参数生成,也不影响运行行为)。
107
+ // 它由客户端(如 CLI)自己定义含义:指向哪张思考档位表、`/think` 能选哪几档。
108
+ // 之所以留在出厂定义里:字段声明与行为消费是两件事,出厂模型需要把它带上,
109
+ // 否则 `syncManagedModels` 会用出厂定义整块覆盖并抹掉应用声明的值。
110
+ // 不写这个字段 = 应用层视作「不可调档」(如 GLM-4.7 强制思考且无 reasoning_effort 参数)。
115
111
  models: [
116
112
  // ---------- OpenAI ----------
117
113
  {
@@ -122,7 +118,7 @@ export const DEFAULT_APP_CONFIG = {
122
118
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
123
119
  defaultParams: {},
124
120
  vision: true,
125
- reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
121
+ reasoningPreset: "openai",
126
122
  description: "OpenAI 最强旗舰(2026-09):高级分析、软件工程、深度研究与科学工作",
127
123
  },
128
124
  {
@@ -133,7 +129,7 @@ export const DEFAULT_APP_CONFIG = {
133
129
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
134
130
  defaultParams: {},
135
131
  vision: true,
136
- reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
132
+ reasoningPreset: "openai",
137
133
  description: "OpenAI 最新旗舰(2026-09-29):Agentic Coding、计算机操作与文档密集型专业任务",
138
134
  },
139
135
  {
@@ -144,7 +140,7 @@ export const DEFAULT_APP_CONFIG = {
144
140
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
145
141
  defaultParams: {},
146
142
  vision: true,
147
- reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
143
+ reasoningPreset: "openai",
148
144
  description: "OpenAI GPT-6 系列快速低成本模型:适合高吞吐、低延迟场景",
149
145
  },
150
146
  // ---------- 智谱 BigModel ----------
@@ -155,7 +151,7 @@ export const DEFAULT_APP_CONFIG = {
155
151
  modelName: "glm-5.3",
156
152
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
157
153
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
158
- reasoning: { efforts: ["low", "high", "max"] },
154
+ reasoningPreset: "zhipu-5.3",
159
155
  description: "智谱最新旗舰:复杂软件工程与长程 Agent 任务(强制开启思考,1M 上下文)",
160
156
  },
161
157
  {
@@ -166,7 +162,7 @@ export const DEFAULT_APP_CONFIG = {
166
162
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
167
163
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
168
164
  vision: true,
169
- reasoning: { efforts: ["low", "high", "max"] },
165
+ reasoningPreset: "zhipu-5.3",
170
166
  description: "智谱原生多模态模型(图片/视频/文件):视觉 Coding,1M 上下文",
171
167
  },
172
168
  {
@@ -177,7 +173,7 @@ export const DEFAULT_APP_CONFIG = {
177
173
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
178
174
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
179
175
  vision: true,
180
- reasoning: { efforts: ["low", "high", "max"] },
176
+ reasoningPreset: "zhipu-5.3",
181
177
  description: "GLM-5.3-Flash 高速版(200 tokens/s),原生多模态",
182
178
  },
183
179
  {
@@ -188,7 +184,6 @@ export const DEFAULT_APP_CONFIG = {
188
184
  // 该模型真实窗口仅 200K,阈值下调至 100K(约窗口的 50%),留足 response 空间
189
185
  tokenThresholds: { handoff: 100_000, guard: 150_000 },
190
186
  defaultParams: {},
191
- reasoning: { efforts: [] },
192
187
  description: "智谱免费文本模型:200K 上下文,适合轻量任务",
193
188
  },
194
189
  // ---------- DeepSeek ----------
@@ -200,7 +195,7 @@ export const DEFAULT_APP_CONFIG = {
200
195
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
201
196
  defaultParams: { thinking: { type: "enabled" } },
202
197
  vision: true,
203
- reasoning: { efforts: ["low", "high", "max"], disable: "thinking" },
198
+ reasoningPreset: "deepseek",
204
199
  description: "DeepSeek 旗舰(2026-09-10):552B MoE 非对称架构,原生多模态,1M 上下文",
205
200
  },
206
201
  ],
@@ -8,6 +8,12 @@
8
8
  * `handoff`(交接阈值,软线)与 `guard`(强行阻断阈值,硬线)。
9
9
  * `maxContextChars` 是 token 口径之前的历史遗留,按 `floor(chars / 4)` 折成 tokens
10
10
  * (与旧代码的回退算法一致)后并入 `handoff`。
11
+ * 2. **`Model.reasoning` → `Model.reasoningPreset`**
12
+ * alpha.13 曾用 `reasoning: { efforts, disable? }` 在模型上声明思考能力,后来改成
13
+ * 「引用策略表里的名字」。未标 `custom` 的模型会被出厂定义整块覆盖,但**标了 `custom`
14
+ * 的模型会原地保留**,于是留下一个无人认识的 `reasoning` 死字段,同时因为缺
15
+ * `reasoningPreset` 而默默失去调档能力。这里删掉旧字段,并按**同 id 的出厂模型**
16
+ * 补上正确的策略名(同一 id 即同一厂商型号,协议能力不会因用户改过参数而变)。
11
17
  *
12
18
  * 迁移时机:`ConfigManager.read()` 读出即迁(内存里永远是当前形状);
13
19
  * 落盘由 `ensureDefaultConfig()` 负责 —— 它比较迁移结果与磁盘原文,不同才写回,
@@ -19,6 +25,7 @@ export declare const TOKEN_THRESHOLDS_KEY = "tokenThresholds";
19
25
  /** 已淘汰的旧字段名(仅迁移代码认识它们) */
20
26
  export declare const LEGACY_TOKEN_FIELD = "maxContextTokens";
21
27
  export declare const LEGACY_CHARS_FIELD = "maxContextChars";
28
+ export declare const LEGACY_REASONING_FIELD = "reasoning";
22
29
  /**
23
30
  * 旧数据缺失阈值时的交接阈值兜底。
24
31
  *
@@ -37,9 +44,19 @@ export declare function migrateModelThresholds(model: Model): {
37
44
  model: Model;
38
45
  changed: boolean;
39
46
  };
47
+ /**
48
+ * 迁移单个模型的思考能力声明:`reasoning` → `reasoningPreset`。
49
+ *
50
+ * 只处理「旧字段存在」这一种情况:字段一旦落盘消失,用户后续主动删掉
51
+ * `reasoningPreset`(表示「这个模型我不想调档」)就不会被反复补回来。
52
+ */
53
+ export declare function migrateModelReasoning(model: Model): {
54
+ model: Model;
55
+ changed: boolean;
56
+ };
40
57
  /**
41
58
  * 迁移整个配置。只处理对话模型(`models`)——
42
- * 图片模型(`imageModels`)从来没有上下文阈值概念。
59
+ * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
43
60
  */
44
61
  export declare function migrateConfig(config: AppConfig): {
45
62
  config: AppConfig;
@@ -8,16 +8,24 @@
8
8
  * `handoff`(交接阈值,软线)与 `guard`(强行阻断阈值,硬线)。
9
9
  * `maxContextChars` 是 token 口径之前的历史遗留,按 `floor(chars / 4)` 折成 tokens
10
10
  * (与旧代码的回退算法一致)后并入 `handoff`。
11
+ * 2. **`Model.reasoning` → `Model.reasoningPreset`**
12
+ * alpha.13 曾用 `reasoning: { efforts, disable? }` 在模型上声明思考能力,后来改成
13
+ * 「引用策略表里的名字」。未标 `custom` 的模型会被出厂定义整块覆盖,但**标了 `custom`
14
+ * 的模型会原地保留**,于是留下一个无人认识的 `reasoning` 死字段,同时因为缺
15
+ * `reasoningPreset` 而默默失去调档能力。这里删掉旧字段,并按**同 id 的出厂模型**
16
+ * 补上正确的策略名(同一 id 即同一厂商型号,协议能力不会因用户改过参数而变)。
11
17
  *
12
18
  * 迁移时机:`ConfigManager.read()` 读出即迁(内存里永远是当前形状);
13
19
  * 落盘由 `ensureDefaultConfig()` 负责 —— 它比较迁移结果与磁盘原文,不同才写回,
14
20
  * 因此迁移只发生一次,且不会在每次启动时无谓地改写文件。
15
21
  */
22
+ import { DEFAULT_APP_CONFIG } from "./constants.js";
16
23
  /** 当前字段名 */
17
24
  export const TOKEN_THRESHOLDS_KEY = "tokenThresholds";
18
25
  /** 已淘汰的旧字段名(仅迁移代码认识它们) */
19
26
  export const LEGACY_TOKEN_FIELD = "maxContextTokens";
20
27
  export const LEGACY_CHARS_FIELD = "maxContextChars";
28
+ export const LEGACY_REASONING_FIELD = "reasoning";
21
29
  /**
22
30
  * 旧数据缺失阈值时的交接阈值兜底。
23
31
  *
@@ -71,17 +79,38 @@ export function migrateModelThresholds(model) {
71
79
  next[TOKEN_THRESHOLDS_KEY] = { handoff, guard };
72
80
  return { model: next, changed: true };
73
81
  }
82
+ /**
83
+ * 迁移单个模型的思考能力声明:`reasoning` → `reasoningPreset`。
84
+ *
85
+ * 只处理「旧字段存在」这一种情况:字段一旦落盘消失,用户后续主动删掉
86
+ * `reasoningPreset`(表示「这个模型我不想调档」)就不会被反复补回来。
87
+ */
88
+ export function migrateModelReasoning(model) {
89
+ const source = model;
90
+ if (!(LEGACY_REASONING_FIELD in source))
91
+ return { model, changed: false };
92
+ const next = { ...source };
93
+ delete next[LEGACY_REASONING_FIELD];
94
+ // 同 id 的出厂模型能告诉我们「这个型号属于哪套协议」
95
+ if (next.reasoningPreset === undefined) {
96
+ const factory = (DEFAULT_APP_CONFIG.models ?? []).find((item) => item.id === model.id);
97
+ if (factory?.reasoningPreset)
98
+ next.reasoningPreset = factory.reasoningPreset;
99
+ }
100
+ return { model: next, changed: true };
101
+ }
74
102
  /**
75
103
  * 迁移整个配置。只处理对话模型(`models`)——
76
- * 图片模型(`imageModels`)从来没有上下文阈值概念。
104
+ * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
77
105
  */
78
106
  export function migrateConfig(config) {
79
107
  let changed = false;
80
108
  const models = (config.models ?? []).map((model) => {
81
- const result = migrateModelThresholds(model);
82
- if (result.changed)
109
+ const thresholds = migrateModelThresholds(model);
110
+ const reasoning = migrateModelReasoning(thresholds.model);
111
+ if (thresholds.changed || reasoning.changed)
83
112
  changed = true;
84
- return result.model;
113
+ return reasoning.model;
85
114
  });
86
115
  if (!changed)
87
116
  return { config, changed };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 思考预设 —— 出厂内容 + 策略表文件名
3
+ *
4
+ * 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
5
+ * 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
6
+ * 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
7
+ *
8
+ * ## 为什么这份数据在 SDK
9
+ *
10
+ * 它是**跨客户端共享的资产**:CLI 用它决定 `/think` 能选哪几档,桌面端同理;两端不该
11
+ * 各维护一份「智谱 5.3 只认 low/high/max」的知识。所以**定义、出厂内容、读写**都在 SDK。
12
+ *
13
+ * ## 但 SDK 不拿它生成请求参数
14
+ *
15
+ * 参数合并只有一条路:`{ ...model.defaultParams, ...agent.modelParams }`(见 `createModel`)。
16
+ * 客户端读表 → 限制可选强度 → 把选中档位的补丁**写进** `AgentDefinition.modelParams`,
17
+ * 于是配置本身就是事实,运行期不再查表。
18
+ *
19
+ * ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
20
+ *
21
+ * | 端点 | 官方依据 | 若写错会怎样 |
22
+ * |------|---------|-------------|
23
+ * | OpenAI | `openai-node` 的 `Shared.ReasoningEffort`(`none/minimal/low/medium/high/xhigh/max`) | 无 `thinking` 参数,写了会被当未知参数拒绝 → 表里干脆不出现该键 |
24
+ * | DeepSeek | `api-docs.deepseek.com/guides/thinking_mode` | 可调 `low/high/max`;关闭用 `thinking.type = "disabled"` |
25
+ * | 智谱 GLM-5.3 系 | `docs.bigmodel.cn/cn/guide/capabilities/thinking` | **只认** `low/high/max`,其余输入报错;**不再支持关闭**(传 disabled 也报错)→ 表里没有 `off` |
26
+ * | 智谱 GLM-4.7 | 同上 | 强制思考且无 `reasoning_effort` 参数 → 不给它挂策略 |
27
+ *
28
+ * 三个结构性保证,都是「表的形状」带来的,不需要任何代码分支:
29
+ * 1. 可选档位 = `Object.keys(levels)`,不会有「菜单里有、发出去报错」的档位;
30
+ * 2. 能否关闭思考 = 表里有没有 `off` 键;
31
+ * 3. 某个键属于思考参数 = 它在表里出现过 —— 写入时按此剔除旧值,不需要硬编码键名。
32
+ */
33
+ import type { ReasoningPresets } from "../types/index.js";
34
+ /** 策略表文件名(位于 `<basePath>/` 下,与 `config.json` 同级) */
35
+ export declare const REASONING_PRESETS_FILE = "reasoning-presets.json";
36
+ /** 出厂策略表 */
37
+ export declare const FACTORY_REASONING_PRESETS: ReasoningPresets;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 思考预设 —— 出厂内容 + 策略表文件名
3
+ *
4
+ * 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
5
+ * 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
6
+ * 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
7
+ *
8
+ * ## 为什么这份数据在 SDK
9
+ *
10
+ * 它是**跨客户端共享的资产**:CLI 用它决定 `/think` 能选哪几档,桌面端同理;两端不该
11
+ * 各维护一份「智谱 5.3 只认 low/high/max」的知识。所以**定义、出厂内容、读写**都在 SDK。
12
+ *
13
+ * ## 但 SDK 不拿它生成请求参数
14
+ *
15
+ * 参数合并只有一条路:`{ ...model.defaultParams, ...agent.modelParams }`(见 `createModel`)。
16
+ * 客户端读表 → 限制可选强度 → 把选中档位的补丁**写进** `AgentDefinition.modelParams`,
17
+ * 于是配置本身就是事实,运行期不再查表。
18
+ *
19
+ * ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
20
+ *
21
+ * | 端点 | 官方依据 | 若写错会怎样 |
22
+ * |------|---------|-------------|
23
+ * | OpenAI | `openai-node` 的 `Shared.ReasoningEffort`(`none/minimal/low/medium/high/xhigh/max`) | 无 `thinking` 参数,写了会被当未知参数拒绝 → 表里干脆不出现该键 |
24
+ * | DeepSeek | `api-docs.deepseek.com/guides/thinking_mode` | 可调 `low/high/max`;关闭用 `thinking.type = "disabled"` |
25
+ * | 智谱 GLM-5.3 系 | `docs.bigmodel.cn/cn/guide/capabilities/thinking` | **只认** `low/high/max`,其余输入报错;**不再支持关闭**(传 disabled 也报错)→ 表里没有 `off` |
26
+ * | 智谱 GLM-4.7 | 同上 | 强制思考且无 `reasoning_effort` 参数 → 不给它挂策略 |
27
+ *
28
+ * 三个结构性保证,都是「表的形状」带来的,不需要任何代码分支:
29
+ * 1. 可选档位 = `Object.keys(levels)`,不会有「菜单里有、发出去报错」的档位;
30
+ * 2. 能否关闭思考 = 表里有没有 `off` 键;
31
+ * 3. 某个键属于思考参数 = 它在表里出现过 —— 写入时按此剔除旧值,不需要硬编码键名。
32
+ */
33
+ /** 策略表文件名(位于 `<basePath>/` 下,与 `config.json` 同级) */
34
+ export const REASONING_PRESETS_FILE = "reasoning-presets.json";
35
+ /** 出厂策略表 */
36
+ export const FACTORY_REASONING_PRESETS = {
37
+ /** OpenAI 系:只认 `reasoning_effort`;关闭思考 = `"none"` */
38
+ openai: {
39
+ levels: {
40
+ minimal: { reasoning_effort: "minimal" },
41
+ low: { reasoning_effort: "low" },
42
+ medium: { reasoning_effort: "medium" },
43
+ high: { reasoning_effort: "high" },
44
+ off: { reasoning_effort: "none" },
45
+ },
46
+ },
47
+ /** 智谱 GLM-5.3 / 5.3-Flash / 5.3-FlashX:只认三档,且**不能关闭思考** */
48
+ "zhipu-5.3": {
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
+ },
54
+ },
55
+ /** DeepSeek:`thinking` 开关 + `reasoning_effort`,可关闭 */
56
+ deepseek: {
57
+ levels: {
58
+ low: { thinking: { type: "enabled" }, reasoning_effort: "low" },
59
+ high: { thinking: { type: "enabled" }, reasoning_effort: "high" },
60
+ max: { thinking: { type: "enabled" }, reasoning_effort: "max" },
61
+ off: { thinking: { type: "disabled" } },
62
+ },
63
+ },
64
+ };
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, ModelParams, 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,11 +21,11 @@ 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";
28
- export { applyReasoningEffort, capabilityOf, isReasoningEffort, modelAllowedEfforts, modelEfforts, readReasoningEffort, resolveModelParams, resolveReasoningEffort, } from "./shared/reasoning.js";
29
29
  export { createModel } from "./runtime/createModel.js";
30
30
  export { SdkAgent } from "./runtime/SdkAgent.js";
31
31
  export type { AgentPlugin, SendContext } from "./runtime/SdkAgent.js";
package/dist/index.js CHANGED
@@ -19,13 +19,14 @@ 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
+ // 表是跨客户端共享的「定义 + 出厂默认」,SDK 不拿它生成请求参数
24
+ export { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE, } from "./config/reasoning-presets.js";
22
25
  // CRUD — 实体仓储(仅 Agent 定义等能力实体)
23
26
  export { EntityRepository } from "./shared/EntityRepository.js";
24
27
  export { AgentRepository } from "./crud/AgentRepository.js";
25
28
  // 文件 → 内容块(统一入口)— 上层应用构造媒体消息时请走这里,不要自己判断上传还是内联
26
29
  export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
27
- // 思考强度(reasoning effort)— 能力声明、可用档位与参数写入
28
- export { applyReasoningEffort, capabilityOf, isReasoningEffort, modelAllowedEfforts, modelEfforts, readReasoningEffort, resolveModelParams, resolveReasoningEffort, } from "./shared/reasoning.js";
29
30
  // 运行时
30
31
  export { createModel } from "./runtime/createModel.js";
31
32
  export { SdkAgent } from "./runtime/SdkAgent.js";
@@ -18,10 +18,7 @@ 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, {
22
- // Agent 级思考档位覆盖(未声明时为 undefined,行为与以前一致)
23
- reasoningEffort: definition.reasoningEffort,
24
- });
21
+ const { client, model, modelConfig } = createModel(scope, modelId, definition.modelParams);
25
22
  const tools = scope.buildTools(definition, {
26
23
  exclude: {
27
24
  subagents: definition.function?.name
@@ -1,17 +1,17 @@
1
1
  import OpenAI from "openai";
2
2
  import type { Scope } from "../scope/Scope.js";
3
- import type { AgentDefinition } from "../types/index.js";
3
+ import type { ModelParams } from "../types/index.js";
4
4
  /**
5
5
  * Agent 模型装配结果:openai SDK client + 模型名 + 模型参数。
6
6
  * Agent 构造时直接消费此结构。
7
7
  */
8
8
  export interface AgentModel {
9
- /** openai 官方 SDK 客户端 */
9
+ /** openai SDK 客户端 */
10
10
  client: OpenAI;
11
11
  /** 发送给 API 的模型名 */
12
12
  model: string;
13
- /** 模型参数(defaultParams),透传给 API(可含厂商特有字段,如 DeepSeek thinking) */
14
- modelConfig?: Record<string, unknown>;
13
+ /** 模型参数,透传给 API(可含厂商特有字段,如 DeepSeek thinking) */
14
+ modelConfig?: ModelParams;
15
15
  }
16
16
  /**
17
17
  * 模型工厂 — 根据 modelId 和 Scope 装配 openai SDK client。
@@ -19,8 +19,7 @@ export interface AgentModel {
19
19
  * 端点(baseUrl / apiKey)来自 config.endpoints,发送给 API 的模型名 = modelName || id。
20
20
  * 任何 OpenAI 兼容端点(OpenAI / 智谱 BigModel / DeepSeek 等)均可通过 baseURL 接入。
21
21
  *
22
- * `overrides.reasoningEffort` 是 **Agent 级覆盖**(`AgentDefinition.reasoningEffort`):
23
- * 同一个模型可以给不同 Agent 配不同思考强度,而模型自身的 `defaultParams` 不变。
24
- * 写入方式按模型的 `reasoning` 能力声明决定(见 `shared/reasoning.ts`)。
22
+ * `overrides` 是 **Agent 级参数覆盖**(`AgentDefinition.modelParams`):
23
+ * 同一个模型可以给不同 Agent 配不同参数,而模型自身的 `defaultParams` 保持不变。
25
24
  */
26
- export declare function createModel(scope: Scope, modelId: string, overrides?: Pick<AgentDefinition, "reasoningEffort">): AgentModel;
25
+ export declare function createModel(scope: Scope, modelId: string, overrides?: ModelParams): AgentModel;
@@ -1,14 +1,26 @@
1
1
  import OpenAI from "openai";
2
- import { resolveModelParams } from "../shared/reasoning.js";
2
+ /**
3
+ * 参数覆盖 —— 浅合并,一行。
4
+ *
5
+ * 这是 SDK 对「模型参数」的唯一处理逻辑:不解释键名、不做档位推导、不知道厂商差异。
6
+ * 需要多层次语义(如「思考档位」)的应用,应该在**写配置时**就把档位展开成具体参数,
7
+ * 存进 `Model.defaultParams` / `AgentDefinition.modelParams`,运行期不再查表。
8
+ *
9
+ * 没有覆盖时返回原对象(保持 `undefined` 语义,避免凭空塞一个空对象)。
10
+ */
11
+ function mergeParams(base, overrides) {
12
+ if (!overrides || Object.keys(overrides).length === 0)
13
+ return base;
14
+ return { ...base, ...overrides };
15
+ }
3
16
  /**
4
17
  * 模型工厂 — 根据 modelId 和 Scope 装配 openai SDK client。
5
18
  *
6
19
  * 端点(baseUrl / apiKey)来自 config.endpoints,发送给 API 的模型名 = modelName || id。
7
20
  * 任何 OpenAI 兼容端点(OpenAI / 智谱 BigModel / DeepSeek 等)均可通过 baseURL 接入。
8
21
  *
9
- * `overrides.reasoningEffort` 是 **Agent 级覆盖**(`AgentDefinition.reasoningEffort`):
10
- * 同一个模型可以给不同 Agent 配不同思考强度,而模型自身的 `defaultParams` 不变。
11
- * 写入方式按模型的 `reasoning` 能力声明决定(见 `shared/reasoning.ts`)。
22
+ * `overrides` 是 **Agent 级参数覆盖**(`AgentDefinition.modelParams`):
23
+ * 同一个模型可以给不同 Agent 配不同参数,而模型自身的 `defaultParams` 保持不变。
12
24
  */
13
25
  export function createModel(scope, modelId, overrides) {
14
26
  const config = scope.config;
@@ -30,7 +42,6 @@ export function createModel(scope, modelId, overrides) {
30
42
  return {
31
43
  client,
32
44
  model: apiModelName,
33
- // 档位覆盖在这里生效:未声明 reasoningEffort 时原样返回模型 defaultParams
34
- modelConfig: resolveModelParams(modelConfig, overrides),
45
+ modelConfig: mergeParams(modelConfig.defaultParams, overrides),
35
46
  };
36
47
  }
@@ -24,7 +24,8 @@ export function createSubAgentTool(def, scope) {
24
24
  let model;
25
25
  let modelConfig;
26
26
  if (def.modelId) {
27
- const m = createModel(scope, def.modelId);
27
+ // SubAgent 可声明自己的模型与参数覆盖(与主 Agent 同一套规则:直接合并)
28
+ const m = createModel(scope, def.modelId, def.modelParams);
28
29
  client = m.client;
29
30
  model = m.model;
30
31
  modelConfig = m.modelConfig;
@@ -26,34 +26,44 @@ export interface EndpointCapabilities {
26
26
  filesApi?: boolean;
27
27
  }
28
28
  /**
29
- * 思考强度档位 —— 各端点官方枚举的**并集**,不含各家不认的值。
29
+ * 模型请求参数(`defaultParams` / `modelParams` 的形状)。
30
30
  *
31
- * OpenAI(`openai-node` 的 `Shared.ReasoningEffort`):`none | minimal | low | medium | high | xhigh | max`
32
- * DeepSeek(`/guides/thinking_mode`):`low | high | max`(另接受 minimal/medium/xhigh/ultra 并做映射)
33
- * 智谱 GLM-5.3 系(`/cn/guide/capabilities/thinking`):`low | high | max`,**其余值直接报错**
34
- * 智谱 GLM-5.2:`none | minimal | low | medium | high | xhigh | max`
31
+ * 含厂商特有字段(`thinking` / `reasoning_effort` 等),SDK **原样透传**、不解释。
32
+ */
33
+ export type ModelParams = Record<string, unknown>;
34
+ /**
35
+ * 单个档位的请求参数补丁。
35
36
  *
36
- * `off` 是本 SDK 的语义值(「关闭思考」),对接时按 `ReasoningCapability.disable` 选择表达方式。
37
- * 它与 `none` 不重复:`none` 是端点自己认的档位值,`off` 是「没有档位、彻底不思考」。
37
+ * 各家协议差异全部关在补丁里,不硬编码任何键名:
38
+ * OpenAI → `{ reasoning_effort: "high" }`(没有 `thinking` 参数)
39
+ * DeepSeek → `{ thinking: { type: "enabled" }, reasoning_effort: "high" }`
40
+ * 关闭 → `{ reasoning_effort: "none" }` 或 `{ thinking: { type: "disabled" } }`
38
41
  */
39
- export type ReasoningEffort = "off" | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
42
+ export type ReasoningPatch = ModelParams;
40
43
  /**
41
- * 模型/端点的思考能力声明(**物理上限**,来自厂商官方文档)。
44
+ * 一个具名思考策略:档位名 → 参数补丁。
42
45
  *
43
- * 刻意不推断、不猜:未声明的模型视为「不可调档」,`/think` 不会列任何选项——
44
- * 发一个端点不认的值会被直接报错(智谱 5.3 系原文「其余输入将报错」)。
46
+ * `levels` 的键集合**就是**该策略的全部可选档位 —— 没有单独的「能否关闭思考」字段:
47
+ * 表里有 `off` 键就能关,没有就不能(如智谱 GLM-5.3 系,官方原文「其余输入将报错」)。
45
48
  */
46
- export interface ReasoningCapability {
47
- /** 型号真实接受的档位(不含 `off`);空数组 = 支持思考但不可调档 */
48
- efforts: ReasoningEffort[];
49
- /**
50
- * 关闭思考的表达方式:
51
- * - `thinking` → `thinking: { type: "disabled" }`(DeepSeek / GLM-5.2)
52
- * - `effort-none` → `reasoning_effort: "none"`(OpenAI / GLM-5.2)
53
- * - 缺省 → 不支持关闭(如 GLM-5.3 系,传 disabled 会报错)
54
- */
55
- disable?: "thinking" | "effort-none";
49
+ export interface ReasoningPreset {
50
+ /** 用户改过 → SDK 初始化不再覆盖该策略(与 `Model.custom` 同构) */
51
+ custom?: boolean;
52
+ /** 档位名 → 参数补丁 */
53
+ levels: Record<string, ReasoningPatch>;
56
54
  }
55
+ /**
56
+ * 思考策略表 —— `<basePath>/reasoning-presets.json` 的结构,出厂内容见
57
+ * `config/reasoning-presets.ts`。
58
+ *
59
+ * 这份表是 **CLI / 桌面端等客户端共享的资产**:它定义「有哪些档位、每档写什么参数」,
60
+ * 由 `Model.reasoningPreset` 引用。客户端据此限制可选强度、把选中的档位展开成参数
61
+ * 写进 `AgentDefinition.modelParams`。
62
+ *
63
+ * **SDK 自身不消费它生成请求参数**:参数合并只是 `{...defaultParams, ...modelParams}`
64
+ * 一次浅合并(见 `createModel`)。表在这里只负责「定义 + 出厂默认 + 读写」。
65
+ */
66
+ export type ReasoningPresets = Record<string, ReasoningPreset>;
57
67
  /** API 端点 */
58
68
  export interface Endpoint {
59
69
  id: string;
@@ -96,12 +106,13 @@ export interface Model {
96
106
  tokenThresholds: TokenThresholds;
97
107
  defaultParams?: Record<string, unknown>;
98
108
  /**
99
- * 思考强度能力(按厂商官方文档声明)。缺省视为不可调档。
109
+ * 思考策略名,引用 `reasoning-presets.json` 里的键。
100
110
  *
101
- * Agent 用 `AgentDefinition.reasoningEffort` 在这个范围内选一档;
102
- * 超出范围的值不会发给服务端(`resolveModelParams()` 会退回模型默认)。
111
+ * 它是**跨客户端共享的元数据**:CLI / 桌面端据此判断这个模型能选哪几档。
112
+ * SDK 不拿它去生成请求参数(那条路只有 `defaultParams` + `modelParams` 浅合并),
113
+ * 只保证该字段能随模型配置一起读写与出厂同步。
103
114
  */
104
- reasoning?: ReasoningCapability;
115
+ reasoningPreset?: string;
105
116
  /** 是否支持图片输入(视觉模型)。看图工具(view_image)仅对视觉模型启用;generate_image 是否返回图片内容块也据此判断 */
106
117
  vision?: boolean;
107
118
  description?: string;
@@ -122,12 +133,16 @@ export interface AgentDefinition {
122
133
  messages: AgentNS.Message[];
123
134
  modelId?: string;
124
135
  /**
125
- * 该 Agent 的思考档位(可选),覆盖模型 `defaultParams` 里的对应参数。
136
+ * Agent 级模型参数覆盖,浅合并进模型的 `defaultParams`。
137
+ *
138
+ * SDK 只做 `{ ...model.defaultParams, ...modelParams }`,不解释任何键名、不做
139
+ * 档位推导、不知道厂商差异。於是「配置即事实」:应用(如 CLI)在**写入配置时**就把
140
+ * 档位展开成具体参数存在这里,运行时不再查表。
126
141
  *
127
- * 有了它,同一个模型可以给不同 Agent 配不同强度(编程 Agent `max`、闲聊 Agent `low`),
128
- * 而模型本身的默认参数保持不变。未声明时沿用模型默认。
142
+ * 于是同一个模型可以给不同 Agent 配不同强度(编程 Agent `reasoning_effort: "max"`、
143
+ * 闲聊 Agent `{ thinking: { type: "disabled" } }`),而模型自身的 `defaultParams` 不动。
129
144
  */
130
- reasoningEffort?: ReasoningEffort;
145
+ modelParams?: ModelParams;
131
146
  permissions?: AgentPermissions;
132
147
  function?: AgentNS.FunctionDefine;
133
148
  createdAt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.15",
4
4
  "description": "AI-Zen Agents SDK — shared business logic for CLI and Desktop",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,64 +0,0 @@
1
- /**
2
- * 思考强度(reasoning effort)—— 能力声明、可用档位与参数写入的唯一实现
3
- *
4
- * 为什么要有这一层:档位不是全局统一枚举,各端点接受的值域**互不相同**,而且
5
- * 同一家不同型号也不同(智谱文档原文):
6
- *
7
- * | 端点 | 官方依据 | 可调档位 | 能否关闭 |
8
- * |------|---------|---------|---------|
9
- * | DeepSeek | `/guides/thinking_mode` | `low / high / max` | 能,`thinking.type = "disabled"` |
10
- * | 智谱 GLM-5.3 系 | `/cn/guide/capabilities/thinking` | `low / high / max`(**其余报错**) | **不能**,传 disabled 直接报错 |
11
- * | 智谱 GLM-5.2 | 同上 | `none / minimal / low / medium / high / xhigh / max` | 能(`none` 或 `disabled`) |
12
- * | OpenAI | `openai-node` 的 `Shared.ReasoningEffort` | `minimal / low / medium / high`(枚举含 none/xhigh/max) | 能,`reasoning_effort = "none"` |
13
- *
14
- * 所以「能力声明在模型上,Agent 只能收窄」,写入时按声明选择表达方式,
15
- * 绝不把端点不认的值发出去。
16
- */
17
- import type { AgentDefinition, Model, ReasoningCapability, ReasoningEffort } from "../types/index.js";
18
- /** 是否为合法档位值 */
19
- export declare function isReasoningEffort(value: unknown): value is ReasoningEffort;
20
- /** 模型的能力声明(缺省 = 不可调档) */
21
- export declare function capabilityOf(model?: Model | null): ReasoningCapability | undefined;
22
- /**
23
- * 模型可调的档位(不含 `off`)。
24
- *
25
- * 未声明能力的模型返回空数组 —— 不猜、不给默认值,避免把非法参数发给服务端。
26
- */
27
- export declare function modelEfforts(model?: Model | null): ReasoningEffort[];
28
- /**
29
- * 模型允许的全部取值(含 `off`)。
30
- *
31
- * `off` 只在声明了 `disable` 方式时出现:没有合法表达式时,「关闭思考」这个选项
32
- * 本身就不该出现在菜单里。
33
- */
34
- export declare function modelAllowedEfforts(model?: Model | null): ReasoningEffort[];
35
- /**
36
- * 从 `defaultParams` 读当前档位。
37
- *
38
- * 返回 `undefined` 表示「没有显式档位」:可能是模型默认开思考但没写 effort
39
- * (DeepSeek 出厂配置就是 `{thinking:{type:"enabled"}}`),也可能压根没有思考参数。
40
- * 展示层要区分这两种情况,请配合 `capabilityOf()` 判断。
41
- */
42
- export declare function readReasoningEffort(params?: Record<string, unknown> | null): ReasoningEffort | undefined;
43
- /**
44
- * 按档位生成新的 `defaultParams`(不改入参)。
45
- *
46
- * 三种写入形态,取决于模型声明:
47
- * - `disable: "thinking"` → 同时维护 `thinking.type` 与 `reasoning_effort`
48
- * - `disable: "effort-none"` → 只写 `reasoning_effort`(OpenAI 不认 `thinking` 键,
49
- * 对它写 `thinking` 会被当作未知参数拒绝)
50
- * - 未声明 `disable`(如 GLM-5.3 系)→ 只写 `reasoning_effort`,传 `off` 直接抛错
51
- *
52
- * @throws 当档位为 `off` 而模型不支持关闭思考,或档位不在模型能力范围内
53
- */
54
- export declare function applyReasoningEffort(params: Record<string, unknown> | undefined | null, model: Model | null | undefined, effort: ReasoningEffort): Record<string, unknown>;
55
- /**
56
- * 解析某 Agent 的实际档位:Agent 声明优先,否则看模型默认参数。
57
- */
58
- export declare function resolveReasoningEffort(model?: Model | null, definition?: Pick<AgentDefinition, "reasoningEffort"> | null): ReasoningEffort | undefined;
59
- /**
60
- * 把 Agent 的档位覆盖应用到模型参数上(`createModel` 用)。
61
- *
62
- * 未声明 `reasoningEffort` 时原样返回模型参数,保证既有行为不变。
63
- */
64
- export declare function resolveModelParams(model?: Model | null, definition?: Pick<AgentDefinition, "reasoningEffort"> | null): Record<string, unknown> | undefined;
@@ -1,151 +0,0 @@
1
- /**
2
- * 思考强度(reasoning effort)—— 能力声明、可用档位与参数写入的唯一实现
3
- *
4
- * 为什么要有这一层:档位不是全局统一枚举,各端点接受的值域**互不相同**,而且
5
- * 同一家不同型号也不同(智谱文档原文):
6
- *
7
- * | 端点 | 官方依据 | 可调档位 | 能否关闭 |
8
- * |------|---------|---------|---------|
9
- * | DeepSeek | `/guides/thinking_mode` | `low / high / max` | 能,`thinking.type = "disabled"` |
10
- * | 智谱 GLM-5.3 系 | `/cn/guide/capabilities/thinking` | `low / high / max`(**其余报错**) | **不能**,传 disabled 直接报错 |
11
- * | 智谱 GLM-5.2 | 同上 | `none / minimal / low / medium / high / xhigh / max` | 能(`none` 或 `disabled`) |
12
- * | OpenAI | `openai-node` 的 `Shared.ReasoningEffort` | `minimal / low / medium / high`(枚举含 none/xhigh/max) | 能,`reasoning_effort = "none"` |
13
- *
14
- * 所以「能力声明在模型上,Agent 只能收窄」,写入时按声明选择表达方式,
15
- * 绝不把端点不认的值发出去。
16
- */
17
- /** `defaultParams` 里承载开关的键 */
18
- const THINKING_KEY = "thinking";
19
- /** `defaultParams` 里承载档位的键 */
20
- const EFFORT_KEY = "reasoning_effort";
21
- const KNOWN_EFFORTS = [
22
- "off",
23
- "none",
24
- "minimal",
25
- "low",
26
- "medium",
27
- "high",
28
- "xhigh",
29
- "max",
30
- ];
31
- /** 是否为合法档位值 */
32
- export function isReasoningEffort(value) {
33
- return typeof value === "string" && KNOWN_EFFORTS.includes(value);
34
- }
35
- /** 模型的能力声明(缺省 = 不可调档) */
36
- export function capabilityOf(model) {
37
- return model?.reasoning;
38
- }
39
- /**
40
- * 模型可调的档位(不含 `off`)。
41
- *
42
- * 未声明能力的模型返回空数组 —— 不猜、不给默认值,避免把非法参数发给服务端。
43
- */
44
- export function modelEfforts(model) {
45
- return [...(capabilityOf(model)?.efforts ?? [])];
46
- }
47
- /**
48
- * 模型允许的全部取值(含 `off`)。
49
- *
50
- * `off` 只在声明了 `disable` 方式时出现:没有合法表达式时,「关闭思考」这个选项
51
- * 本身就不该出现在菜单里。
52
- */
53
- export function modelAllowedEfforts(model) {
54
- const capability = capabilityOf(model);
55
- if (!capability)
56
- return [];
57
- return capability.disable ? ["off", ...capability.efforts] : [...capability.efforts];
58
- }
59
- function thinkingType(params) {
60
- const thinking = params?.[THINKING_KEY];
61
- if (typeof thinking !== "object" || thinking === null)
62
- return undefined;
63
- const type = thinking.type;
64
- return typeof type === "string" ? type : undefined;
65
- }
66
- /**
67
- * 从 `defaultParams` 读当前档位。
68
- *
69
- * 返回 `undefined` 表示「没有显式档位」:可能是模型默认开思考但没写 effort
70
- * (DeepSeek 出厂配置就是 `{thinking:{type:"enabled"}}`),也可能压根没有思考参数。
71
- * 展示层要区分这两种情况,请配合 `capabilityOf()` 判断。
72
- */
73
- export function readReasoningEffort(params) {
74
- if (thinkingType(params) === "disabled")
75
- return "off";
76
- const raw = params?.[EFFORT_KEY];
77
- if (typeof raw !== "string")
78
- return undefined;
79
- // `none` 与 `off` 语义一致(都表示不思考),统一归一为 off,避免菜单里出现两个「关」
80
- if (raw === "none")
81
- return "off";
82
- return isReasoningEffort(raw) ? raw : undefined;
83
- }
84
- /**
85
- * 按档位生成新的 `defaultParams`(不改入参)。
86
- *
87
- * 三种写入形态,取决于模型声明:
88
- * - `disable: "thinking"` → 同时维护 `thinking.type` 与 `reasoning_effort`
89
- * - `disable: "effort-none"` → 只写 `reasoning_effort`(OpenAI 不认 `thinking` 键,
90
- * 对它写 `thinking` 会被当作未知参数拒绝)
91
- * - 未声明 `disable`(如 GLM-5.3 系)→ 只写 `reasoning_effort`,传 `off` 直接抛错
92
- *
93
- * @throws 当档位为 `off` 而模型不支持关闭思考,或档位不在模型能力范围内
94
- */
95
- export function applyReasoningEffort(params, model, effort) {
96
- const capability = capabilityOf(model);
97
- const next = { ...(params ?? {}) };
98
- if (effort === "off") {
99
- if (capability?.disable === "thinking") {
100
- next[THINKING_KEY] = { type: "disabled" };
101
- delete next[EFFORT_KEY];
102
- return next;
103
- }
104
- if (capability?.disable === "effort-none") {
105
- next[EFFORT_KEY] = "none";
106
- // 不在 OpenAI 的请求里塞 thinking:那是未知参数
107
- delete next[THINKING_KEY];
108
- return next;
109
- }
110
- throw new Error(`模型 "${model?.id ?? "(未知)"}" 不支持关闭思考`);
111
- }
112
- if (!capability) {
113
- throw new Error(`模型 "${model?.id ?? "(未知)"}" 未声明思考能力,无法设置档位`);
114
- }
115
- if (!capability.efforts.includes(effort)) {
116
- throw new Error(`模型 "${model?.id ?? "(未知)"}" 不支持档位 "${effort}"(可选:${capability.efforts.join(" / ")})`);
117
- }
118
- if (capability.disable === "thinking") {
119
- next[THINKING_KEY] = { type: "enabled" };
120
- }
121
- next[EFFORT_KEY] = effort;
122
- return next;
123
- }
124
- /**
125
- * 解析某 Agent 的实际档位:Agent 声明优先,否则看模型默认参数。
126
- */
127
- export function resolveReasoningEffort(model, definition) {
128
- const declared = definition?.reasoningEffort;
129
- if (declared) {
130
- // 声明了但模型不支持 → 退化为模型默认,避免把非法值发给服务端
131
- const allowed = modelAllowedEfforts(model);
132
- if (allowed.length === 0 || allowed.includes(declared))
133
- return declared;
134
- }
135
- return readReasoningEffort(model?.defaultParams);
136
- }
137
- /**
138
- * 把 Agent 的档位覆盖应用到模型参数上(`createModel` 用)。
139
- *
140
- * 未声明 `reasoningEffort` 时原样返回模型参数,保证既有行为不变。
141
- */
142
- export function resolveModelParams(model, definition) {
143
- const declared = definition?.reasoningEffort;
144
- if (!declared)
145
- return model?.defaultParams;
146
- const allowed = modelAllowedEfforts(model);
147
- // 模型不支持声明值:宁可保持模型默认,也不发非法参数
148
- if (allowed.length === 0 || !allowed.includes(declared))
149
- return model?.defaultParams;
150
- return applyReasoningEffort(model?.defaultParams, model, declared);
151
- }