@ai-zen/agents-sdk 1.0.0-alpha.14 → 1.0.0-alpha.16

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,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-alpha.16] - 2026-10-22
4
+
5
+ ### 🔧 行为变更(不兼容,向上修复)
6
+
7
+ - **升级 `@ai-zen/agents-core` 至 `4.5.2`:请求体里的固定字段不再被 `modelParams` 覆盖** —— core 之前把模型参数 spread 在请求体**最后**(`{ model, messages, tools, stream, stream_options, ...modelConfig }`),而 `modelConfig` 来自用户可编辑的 `Model.defaultParams` / `AgentDefinition.modelParams`。一旦其中出现同名键就会静默生效:最惨的是 `stream: false`,它会把流式关掉而调用方仍在按异步流解析,整轮直接失败。现在顺序反过来,固定字段永远胜出,非固定键(`temperature` / `reasoning_effort` / 厂商特有 `thinking` 等)照常透传。
8
+
9
+ 对本 SDK 的实际影响:`AgentDefinition.modelParams` 里写 `model` / `messages` / `tools` / `stream` / `stream_options` 将不再有任何效果。正常配置(温度、思考参数)不受影响。
10
+
11
+ ### ✅ 测试
12
+
13
+ - 依赖的 core 侧新增 3 例(非固定键透传、固定键被忽略、无 `modelConfig` 时请求体只含框架字段)。SDK 自身 **57 文件 / 600 例通过**(6 skipped)。
14
+
15
+ ## [1.0.0-alpha.15] - 2026-10-22
16
+
17
+ ### 🔧 行为变更(不兼容)
18
+
19
+ - **SDK 不再「消费」思考档位,只保留表本身** —— alpha.14 把「档位 → 请求参数」的整套解析塞进了 SDK 运行时:`applyReasoningEffort` / `modelLevels` / `resolveReasoningEffort` / `resolveModelParams`,还要 `Scope.reasoningPresets` 注入、Agent 上存档位名、运行时查表拼参数。但 `low` / `high` / `max` / `off` 是**产品语义,不是协议事实**,SDK 唯一客观知道的是「请求参数长什么样」——那本来就由 `defaultParams` 承载。现在:
20
+ - **删除 `shared/reasoning.ts` 的全部 API**(`applyReasoningEffort` / `modelLevels` / `presetOf` / `readReasoningEffort` / `resolveReasoningEffort` / `resolveModelParams`)以及 `Scope.reasoningPresets`;
21
+ - **保留**思考预设的**定义与出厂内容**(`ReasoningPresets` / `ReasoningPreset` / `ReasoningPatch` 类型、`config/reasoning-presets.ts`、`ConfigManager.readReasoningPresets()` / `ensureReasoningPresets()`)—— 它是 CLI / 桌面端等客户端**共享的资产**(「智谱 5.3 只认 low/high/max」这种知识不该被每个客户端各写一遍),但那只是**数据**,SDK 不拿它生成请求参数。
22
+ - **`AgentDefinition.reasoningEffort` → `AgentDefinition.modelParams?: ModelParams`** —— Agent 级覆盖从「档位名」变成「具体参数」。客户端读到表、拿档位补丁,在**写入配置时**就把它展开存进 `modelParams` 里。于是**配置即事实**:运行期不查表、不推导。
23
+ - **`createModel(scope, modelId, overrides?: ModelParams)`** —— 第三参从 `Pick<AgentDefinition, "reasoningEffort">` 变成裸的参数对象,内部只有一行:
24
+ ```ts
25
+ modelConfig = { ...model.defaultParams, ...overrides };
26
+ ```
27
+ 浅合并(嵌套对象整体替换),没有覆盖时原样返回 `defaultParams`。SDK 从此不解释任何键名,也不知道 `thinking` / `reasoning_effort` 是什么。SubAgent 指定独立 `modelId` 时同样应用自己的 `modelParams`。
28
+ - **`Model.reasoningPreset` 保留**,语义改为「引用策略表的键」,供客户端判断该模型能选哪几档;SDK 只保证它能随模型配置一起读写与出厂同步,不消费。
29
+
30
+ ### 🐛 修复
31
+
32
+ - **迁移 `Model.reasoning` 旧字段(alpha.13 遗留)** —— 未标 `custom` 的模型会被出厂定义整块覆盖,但**标了 `custom: true` 的会原地保留**,于是留下一个无人认识的 `reasoning`,同时因为缺 `reasoningPreset` 而被客户端判定为「不可调档」。`ConfigManager.read()` 现在会删掉该旧字段,并按同 id 的出厂模型补回 `reasoningPreset`;id 不在出厂清单的(真·自建模型)只删不补。只处理「旧字段存在」这一种情况,用户后续主动删掉 `reasoningPreset` 不会被反复补回来。
33
+
34
+ ### ✅ 测试
35
+
36
+ - `src/runtime/createModel.test.ts` 新增 6 例锁住合并语义:无覆盖原样返回、空覆盖等同无覆盖、同名键覆盖而其它键保留、模型无 `defaultParams` 时覆盖即全部、**浅合并不做深合并**(换档位不残留旧子键)、两个 Agent 各配一套互不影响。
37
+ - `src/config/migrate.test.ts` 新增 6 例(19 例合计)。
38
+ - 删除 `src/shared/reasoning.test.ts`(26 例,运行时解析已移除);`src/config/reasoning-presets.test.ts`(11 例)保留 —— 表还在 SDK。
39
+ - 全量 **57 文件 / 600 例通过**(6 skipped)。
40
+
3
41
  ## [1.0.0-alpha.14] - 2026-10-22
4
42
 
5
43
  ### 🔧 行为变更(不兼容)
@@ -64,7 +64,7 @@ export declare class ConfigManager {
64
64
  * 读取思考策略表。
65
65
  *
66
66
  * 文件缺失或无法解析时返回出厂种子(内存值,不写盘)——保证调用方永远拿到可用的表:
67
- * 用户误删文件后仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
67
+ * 用户误删文件后客户端仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
68
68
  */
69
69
  readReasoningPresets(): Promise<ReasoningPresets>;
70
70
  /**
@@ -2,7 +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
+ import { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE } from "./reasoning-presets.js";
6
6
  import { migrateConfig } from "./migrate.js";
7
7
  /**
8
8
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
@@ -203,7 +203,7 @@ export class ConfigManager {
203
203
  * 读取思考策略表。
204
204
  *
205
205
  * 文件缺失或无法解析时返回出厂种子(内存值,不写盘)——保证调用方永远拿到可用的表:
206
- * 用户误删文件后仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
206
+ * 用户误删文件后客户端仍能正常选档位,下次 `ensureReasoningPresets()` 会重新释放。
207
207
  */
208
208
  async readReasoningPresets() {
209
209
  try {
@@ -103,10 +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
- // reasoningPreset:思考策略名,引用 `<basePath>/reasoning-presets.json` 里的键
107
- // (出厂内容见 `config/reasoning-presets.ts`,档位的协议差异全部写在表里)。
108
- // 不写这个字段 = 不可调档(如 GLM-4.7 强制思考且无 reasoning_effort 参数)。
109
- // 新接入端点时先在策略表里加一张表,再在这里引用它的名字。
106
+ // reasoningPreset:应用层元数据,**SDK 不解释**(既不参与请求参数生成,也不影响运行行为)。
107
+ // 它由客户端(如 CLI)自己定义含义:指向哪张思考档位表、`/think` 能选哪几档。
108
+ // 之所以留在出厂定义里:字段声明与行为消费是两件事,出厂模型需要把它带上,
109
+ // 否则 `syncManagedModels` 会用出厂定义整块覆盖并抹掉应用声明的值。
110
+ // 不写这个字段 = 应用层视作「不可调档」(如 GLM-4.7 强制思考且无 reasoning_effort 参数)。
110
111
  models: [
111
112
  // ---------- OpenAI ----------
112
113
  {
@@ -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 };
@@ -1,12 +1,20 @@
1
1
  /**
2
- * 思考预设 —— 出厂种子 + 策略表文件名
2
+ * 思考预设 —— 出厂内容 + 策略表文件名
3
3
  *
4
4
  * 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
5
5
  * 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
6
6
  * 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
7
7
  *
8
- * 为什么档位细节不留在代码里:档位与厂商协议绑定,属于「数据」而非「逻辑」。
9
- * 放进用户目录后,厂商新增档位或接入新端点时用户能自己加,不必等 SDK 发版。
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
+ * 于是配置本身就是事实,运行期不再查表。
10
18
  *
11
19
  * ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
12
20
  *
@@ -1,12 +1,20 @@
1
1
  /**
2
- * 思考预设 —— 出厂种子 + 策略表文件名
2
+ * 思考预设 —— 出厂内容 + 策略表文件名
3
3
  *
4
4
  * 这里的内容只是**初始值**:`ConfigManager.ensureReasoningPresets()` 在首次运行时把它
5
5
  * 写到 `<basePath>/reasoning-presets.json`,之后以磁盘文件为准(用户可改、可加)。
6
6
  * 与 `DEFAULT_APP_CONFIG` 写进 `config.json` 完全同一个套路。
7
7
  *
8
- * 为什么档位细节不留在代码里:档位与厂商协议绑定,属于「数据」而非「逻辑」。
9
- * 放进用户目录后,厂商新增档位或接入新端点时用户能自己加,不必等 SDK 发版。
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
+ * 于是配置本身就是事实,运行期不再查表。
10
18
  *
11
19
  * ## 为什么每张表长这样(**全部来自官方文档,不要凭印象改**)
12
20
  *
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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";
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";
@@ -26,7 +26,6 @@ export { EntityRepository } from "./shared/EntityRepository.js";
26
26
  export { AgentRepository } from "./crud/AgentRepository.js";
27
27
  export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
28
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";
30
29
  export { createModel } from "./runtime/createModel.js";
31
30
  export { SdkAgent } from "./runtime/SdkAgent.js";
32
31
  export type { AgentPlugin, SendContext } from "./runtime/SdkAgent.js";
package/dist/index.js CHANGED
@@ -19,15 +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)
22
+ // 出厂资产 — 思考策略表(随版本释放到 <basePath>/reasoning-presets.json)
23
+ // 表是跨客户端共享的「定义 + 出厂默认」,SDK 不拿它生成请求参数
23
24
  export { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE, } from "./config/reasoning-presets.js";
24
25
  // CRUD — 实体仓储(仅 Agent 定义等能力实体)
25
26
  export { EntityRepository } from "./shared/EntityRepository.js";
26
27
  export { AgentRepository } from "./crud/AgentRepository.js";
27
28
  // 文件 → 内容块(统一入口)— 上层应用构造媒体消息时请走这里,不要自己判断上传还是内联
28
29
  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";
31
30
  // 运行时
32
31
  export { createModel } from "./runtime/createModel.js";
33
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,9 +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
- * 档位如何翻译成请求参数,由 `scope.reasoningPresets` 里该模型引用的策略表决定
25
- * (见 `shared/reasoning.ts`)。
22
+ * `overrides` 是 **Agent 级参数覆盖**(`AgentDefinition.modelParams`):
23
+ * 同一个模型可以给不同 Agent 配不同参数,而模型自身的 `defaultParams` 保持不变。
26
24
  */
27
- 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,15 +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
- * 档位如何翻译成请求参数,由 `scope.reasoningPresets` 里该模型引用的策略表决定
12
- * (见 `shared/reasoning.ts`)。
22
+ * `overrides` 是 **Agent 级参数覆盖**(`AgentDefinition.modelParams`):
23
+ * 同一个模型可以给不同 Agent 配不同参数,而模型自身的 `defaultParams` 保持不变。
13
24
  */
14
25
  export function createModel(scope, modelId, overrides) {
15
26
  const config = scope.config;
@@ -31,7 +42,6 @@ export function createModel(scope, modelId, overrides) {
31
42
  return {
32
43
  client,
33
44
  model: apiModelName,
34
- // 档位覆盖在这里生效:未声明 reasoningEffort 时原样返回模型 defaultParams
35
- modelConfig: resolveModelParams(modelConfig, scope.reasoningPresets, overrides),
45
+ modelConfig: mergeParams(modelConfig.defaultParams, overrides),
36
46
  };
37
47
  }
@@ -1,5 +1,5 @@
1
1
  import type { Tool } from "@ai-zen/agents-core";
2
- import type { AppConfig, AgentDefinition, ReasoningPresets, ToolEnv } from "../types/index.js";
2
+ import type { AppConfig, AgentDefinition, ToolEnv } from "../types/index.js";
3
3
  import type { ScopePlugin } from "./ScopePlugin.js";
4
4
  /**
5
5
  * 能力过滤系统的公共类型。
@@ -56,13 +56,6 @@ 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;
66
59
  /** 已注册的插件 */
67
60
  private _plugins;
68
61
  /** 是否已 init(init 之后禁止再 use) */
@@ -71,7 +64,6 @@ export declare class Scope {
71
64
  config: AppConfig;
72
65
  agentsDir: string;
73
66
  cwd?: string;
74
- reasoningPresets?: ReasoningPresets;
75
67
  });
76
68
  /** 已注册插件(只读视图) */
77
69
  get plugins(): readonly ScopePlugin[];
@@ -29,20 +29,12 @@ 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;
39
32
  /** 已注册的插件 */
40
33
  _plugins = [];
41
34
  /** 是否已 init(init 之后禁止再 use) */
42
35
  _initialized = false;
43
36
  constructor(options) {
44
37
  this.config = options.config;
45
- this.reasoningPresets = options.reasoningPresets ?? {};
46
38
  this.cwd = options.cwd ?? process.cwd();
47
39
  this.env = { cwd: this.cwd, config: this.config };
48
40
  this.agentsDir = options.agentsDir;
@@ -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,14 +26,20 @@ export interface EndpointCapabilities {
26
26
  filesApi?: boolean;
27
27
  }
28
28
  /**
29
- * 单个档位的请求参数补丁 —— 原样合并进模型请求参数。
29
+ * 模型请求参数(`defaultParams` / `modelParams` 的形状)。
30
30
  *
31
- * 各家协议差异全部关在补丁里,SDK 不解释、不硬编码任何键名:
31
+ * 含厂商特有字段(`thinking` / `reasoning_effort` 等),SDK **原样透传**、不解释。
32
+ */
33
+ export type ModelParams = Record<string, unknown>;
34
+ /**
35
+ * 单个档位的请求参数补丁。
36
+ *
37
+ * 各家协议差异全部关在补丁里,不硬编码任何键名:
32
38
  * OpenAI → `{ reasoning_effort: "high" }`(没有 `thinking` 参数)
33
39
  * DeepSeek → `{ thinking: { type: "enabled" }, reasoning_effort: "high" }`
34
40
  * 关闭 → `{ reasoning_effort: "none" }` 或 `{ thinking: { type: "disabled" } }`
35
41
  */
36
- export type ReasoningPatch = Record<string, unknown>;
42
+ export type ReasoningPatch = ModelParams;
37
43
  /**
38
44
  * 一个具名思考策略:档位名 → 参数补丁。
39
45
  *
@@ -50,8 +56,12 @@ export interface ReasoningPreset {
50
56
  * 思考策略表 —— `<basePath>/reasoning-presets.json` 的结构,出厂内容见
51
57
  * `config/reasoning-presets.ts`。
52
58
  *
53
- * 键是策略名,由 `Model.reasoningPreset` 引用。**策略名一旦发布即视为稳定标识**
54
- * (用户配置文件里写着它),改名等同破坏性变更。
59
+ * 这份表是 **CLI / 桌面端等客户端共享的资产**:它定义「有哪些档位、每档写什么参数」,
60
+ * 由 `Model.reasoningPreset` 引用。客户端据此限制可选强度、把选中的档位展开成参数
61
+ * 写进 `AgentDefinition.modelParams`。
62
+ *
63
+ * **SDK 自身不消费它生成请求参数**:参数合并只是 `{...defaultParams, ...modelParams}`
64
+ * 一次浅合并(见 `createModel`)。表在这里只负责「定义 + 出厂默认 + 读写」。
55
65
  */
56
66
  export type ReasoningPresets = Record<string, ReasoningPreset>;
57
67
  /** API 端点 */
@@ -96,11 +106,11 @@ export interface Model {
96
106
  tokenThresholds: TokenThresholds;
97
107
  defaultParams?: Record<string, unknown>;
98
108
  /**
99
- * 思考策略名,引用 `reasoning-presets.json` 里的键。缺省视为不可调档。
109
+ * 思考策略名,引用 `reasoning-presets.json` 里的键。
100
110
  *
101
- * 为什么只存名字不存整张表:档位细节属于**厂商协议知识**,跟 SDK 版本一起演进;
102
- * 配置只需要记录「这个型号套用哪套协议」。名字指向同目录下的策略表文件,
103
- * 用户看得见、改得动,不必等 SDK 发版。
111
+ * 它是**跨客户端共享的元数据**:CLI / 桌面端据此判断这个模型能选哪几档。
112
+ * SDK 不拿它去生成请求参数(那条路只有 `defaultParams` + `modelParams` 浅合并),
113
+ * 只保证该字段能随模型配置一起读写与出厂同步。
104
114
  */
105
115
  reasoningPreset?: string;
106
116
  /** 是否支持图片输入(视觉模型)。看图工具(view_image)仅对视觉模型启用;generate_image 是否返回图片内容块也据此判断 */
@@ -123,15 +133,16 @@ export interface AgentDefinition {
123
133
  messages: AgentNS.Message[];
124
134
  modelId?: string;
125
135
  /**
126
- * 该 Agent 的思考档位(可选),必须是该模型策略表 `levels` 里的键。
136
+ * Agent 级模型参数覆盖,浅合并进模型的 `defaultParams`。
127
137
  *
128
- * 有了它,同一个模型可以给不同 Agent 配不同强度(编程 Agent `max`、闲聊 Agent `low`),
129
- * 而模型本身的默认参数保持不变。未声明时沿用模型默认。
138
+ * SDK 只做 `{ ...model.defaultParams, ...modelParams }`,不解释任何键名、不做
139
+ * 档位推导、不知道厂商差异。於是「配置即事实」:应用(如 CLI)在**写入配置时**就把
140
+ * 档位展开成具体参数存在这里,运行时不再查表。
130
141
  *
131
- * 类型是 `string` 而非联合类型:档位名由策略表定义,各家本就不同名,
132
- * 用一个全局枚举假装统一只会把厂商差异藏进字段里。合法性在运行时按表校验。
142
+ * 于是同一个模型可以给不同 Agent 配不同强度(编程 Agent `reasoning_effort: "max"`、
143
+ * 闲聊 Agent `{ thinking: { type: "disabled" } }`),而模型自身的 `defaultParams` 不动。
133
144
  */
134
- reasoningEffort?: string;
145
+ modelParams?: ModelParams;
135
146
  permissions?: AgentPermissions;
136
147
  function?: AgentNS.FunctionDefine;
137
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.14",
3
+ "version": "1.0.0-alpha.16",
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",
@@ -16,7 +16,7 @@
16
16
  "author": "LZQCN",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "@ai-zen/agents-core": "^4.5.1",
19
+ "@ai-zen/agents-core": "^4.5.2",
20
20
  "@modelcontextprotocol/sdk": "^1.29.0",
21
21
  "dayjs": "^1.11.21",
22
22
  "openai": "^7.5.0"
@@ -1,62 +0,0 @@
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;
@@ -1,159 +0,0 @@
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
- }