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

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-alpha.18] - 2026-10-22
4
+
5
+ ### 🔧 行为变更(不兼容)
6
+
7
+ - **模型的唯一真相源改为 `AgentDefinition.modelId`,`AppConfig.defaultModel` 删除** —— 此前模型有两处来源:Agent 定义里的 `modelId` 优先,缺失时回退 `config.defaultModel`。而 CLI 等客户端还会在**会话存档**里另记一份模型名用于展示。三份值一旦分叉,就会出现「界面写着 DeepSeek、请求却发给 GLM」的静默错位:用户看到 401 却怎么切都切不回来(切换命令按显示层判断「已经是当前模型」直接返回,实际模型从未改变)。现在:
8
+ - `AppConfig` 删除 `defaultModel` 字段,出厂配置同步删除;`migrateConfig` 会把磁盘上的旧字段**清掉**(一次性,写入后不再出现);
9
+ - `createAgent` 只读 `definition.modelId`,**缺失直接报错**,不再有任何隐式回退;
10
+ - `ViewImageTool.isAvailable` / `resolveModelAndEndpoint` 同步去掉 `defaultModel` 兜底,反查不到即视为「无端点」,让调用方走降级路径而不是猜一个模型。
11
+ - **`DEFAULT_AGENT_DEFINITION.modelId = "deepseek-flash"`(新增导出 `DEFAULT_MODEL_ID`)** —— 出厂默认 Agent 自带模型 ID,装完即用,不再依赖配置层的默认值。
12
+ - **升级兼容:`ensureDefaultAgent()` 会为缺 `modelId` 的历史 Agent 补上** —— 优先沿用老的 `config.defaultModel`(若该模型仍在清单里),否则回落到出厂 `deepseek-flash`,因此升级不会改变用户正在用的模型。
13
+
14
+ ### ✅ 测试
15
+
16
+ - 全量 **57 文件 / 602 例通过**(6 skipped)。
17
+ - 新增/改写重点:`createAgent` 缺 `modelId` 必须抛错、`ensureDefaultAgent` 补齐并落盘、`migrateConfig` 清除废弃字段且不就地改原对象、`resolveModelAndEndpoint` 未命中返回 `null` 端点、MCP / 图片工具按 `ctx.agent.model` 反查端点(不再靠默认模型兜底)。
18
+
3
19
  ## [1.0.0-alpha.16] - 2026-10-22
4
20
 
5
21
  ### 🔧 行为变更(不兼容,向上修复)
@@ -5,6 +5,8 @@ import type { AppConfig, AgentDefinition, ReasoningPresets } from "../types/inde
5
5
  export declare class ConfigManager {
6
6
  readonly configPath: string;
7
7
  readonly basePath: string;
8
+ /** 当前配置里的模型 id 集合(bootstrap 时填充,供默认 Agent 校验 modelId) */
9
+ private knownModelIds;
8
10
  constructor(configPath: string);
9
11
  /**
10
12
  * 读取配置。文件不存在时返回出厂默认配置。
@@ -34,13 +36,20 @@ export declare class ConfigManager {
34
36
  /**
35
37
  * 确保 basePath/agents/default.json 存在。
36
38
  *
37
- * - default.json 已存在且 custom === true → 用户声明为自定义,原样返回,不做任何改动
38
- * - default.json 已存在且 custom !== true → 仅同步出厂提示词(messages),
39
- * 其余字段(name/permissions/modelId 等)保留;提示词一致时不写盘
40
- * - agents/ 为空 → 写入默认 Agent
39
+ * - default.json 已存在且 custom === true → 用户声明为自定义,不替换提示词(缺 modelId 仍补)
40
+ * - default.json 已存在且 custom !== true → 同步出厂提示词(messages),
41
+ * 其余字段(name/permissions 等)保留;提示词与 modelId 都一致时不写盘
42
+ * - agents/ 为空 → 写入默认 Agent(含 modelId)
41
43
  * - 已有其他 Agent → 返回 null
44
+ *
45
+ * 模型真相源只有 `AgentDefinition.modelId`:这里保证默认 Agent **一定有** modelId,
46
+ * 运行期不再有任何 `config.defaultModel` 兜底。
47
+ *
48
+ * @param legacyDefaultModel 老配置里的 `config.defaultModel`。仅在 Agent **既无 modelId**
49
+ * 且该模型仍在模型清单里时用它补齐,让升级不改变用户实际在用的模型;之后
50
+ * 这个字段就彻底不存在了。
42
51
  */
43
- ensureDefaultAgent(): Promise<AgentDefinition | null>;
52
+ ensureDefaultAgent(legacyDefaultModel?: string): Promise<AgentDefinition | null>;
44
53
  /**
45
54
  * 确保 basePath/sub-agents/{DEFAULT_SUBAGENT_ID}.json 存在。
46
55
  *
@@ -119,10 +128,12 @@ export declare class ConfigManager {
119
128
  * 规则:
120
129
  * - 未标 `custom: true` 的条目 → 由 SDK 托管,从结果中移除,改用 defaults 中的最新定义;
121
130
  * - 标了 `custom: true` 的条目 → 用户自有,原样保留;与出厂定义同 id 时用户版本优先;
122
- * - 默认模型 ID(defaultModel / defaultImageModel / defaultMigrationModel)
131
+ * - 默认模型 ID(defaultImageModel / defaultMigrationModel)
123
132
  * 在合并后若已不存在 → 回退到出厂默认,避免悬空引用。
124
133
  *
134
+ * 注意 `defaultModel` 已不再存在:模型真相源是 `AgentDefinition.modelId`。
135
+ *
125
136
  * 端点(endpoints)不参与托管,始终保持用户配置。
126
137
  */
127
138
  export declare function syncManagedModels(config: AppConfig): AppConfig;
128
- export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
139
+ export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_MODEL_ID, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
@@ -10,6 +10,8 @@ import { migrateConfig } from "./migrate.js";
10
10
  export class ConfigManager {
11
11
  configPath;
12
12
  basePath;
13
+ /** 当前配置里的模型 id 集合(bootstrap 时填充,供默认 Agent 校验 modelId) */
14
+ knownModelIds = new Set();
13
15
  constructor(configPath) {
14
16
  this.configPath = configPath;
15
17
  this.basePath = dirname(configPath);
@@ -90,28 +92,48 @@ export class ConfigManager {
90
92
  /**
91
93
  * 确保 basePath/agents/default.json 存在。
92
94
  *
93
- * - default.json 已存在且 custom === true → 用户声明为自定义,原样返回,不做任何改动
94
- * - default.json 已存在且 custom !== true → 仅同步出厂提示词(messages),
95
- * 其余字段(name/permissions/modelId 等)保留;提示词一致时不写盘
96
- * - agents/ 为空 → 写入默认 Agent
95
+ * - default.json 已存在且 custom === true → 用户声明为自定义,不替换提示词(缺 modelId 仍补)
96
+ * - default.json 已存在且 custom !== true → 同步出厂提示词(messages),
97
+ * 其余字段(name/permissions 等)保留;提示词与 modelId 都一致时不写盘
98
+ * - agents/ 为空 → 写入默认 Agent(含 modelId)
97
99
  * - 已有其他 Agent → 返回 null
100
+ *
101
+ * 模型真相源只有 `AgentDefinition.modelId`:这里保证默认 Agent **一定有** modelId,
102
+ * 运行期不再有任何 `config.defaultModel` 兜底。
103
+ *
104
+ * @param legacyDefaultModel 老配置里的 `config.defaultModel`。仅在 Agent **既无 modelId**
105
+ * 且该模型仍在模型清单里时用它补齐,让升级不改变用户实际在用的模型;之后
106
+ * 这个字段就彻底不存在了。
98
107
  */
99
- async ensureDefaultAgent() {
108
+ async ensureDefaultAgent(legacyDefaultModel) {
100
109
  const agentsDir = join(this.basePath, "agents");
101
110
  const defaultPath = join(agentsDir, `${DEFAULT_AGENT_ID}.json`);
111
+ const fallbackModelId = legacyDefaultModel && this.knownModelIds.has(legacyDefaultModel)
112
+ ? legacyDefaultModel
113
+ : DEFAULT_AGENT_DEFINITION.modelId;
102
114
  try {
103
115
  const existing = JSON.parse(await fs.readFile(defaultPath, "utf-8"));
104
- // 用户声明为自定义 → 完全跳过替换
105
- if (existing.custom === true)
106
- return existing;
116
+ // 缺 modelId → 补上(历史配置升级路径)
117
+ const withModel = existing.modelId
118
+ ? existing
119
+ : { ...existing, modelId: fallbackModelId };
120
+ const modelPatched = !existing.modelId;
121
+ // 用户声明为自定义 → 不替换提示词,但补上的 modelId 要落盘
122
+ if (withModel.custom === true) {
123
+ if (modelPatched) {
124
+ await fs.writeFile(defaultPath, JSON.stringify(withModel, null, 2), "utf-8");
125
+ }
126
+ return withModel;
127
+ }
107
128
  // 仅同步提示词;内容一致则不写盘、不动时间戳
108
129
  const messages = DEFAULT_AGENT_DEFINITION.messages;
109
- if (sameMessages(existing.messages ?? [], messages))
110
- return existing;
130
+ const sameMsgs = sameMessages(withModel.messages ?? [], messages);
131
+ if (sameMsgs && !modelPatched)
132
+ return withModel;
111
133
  const updated = {
112
- ...existing,
134
+ ...withModel,
113
135
  messages,
114
- updatedAt: new Date().toISOString(),
136
+ updatedAt: sameMsgs ? withModel.updatedAt : new Date().toISOString(),
115
137
  };
116
138
  await fs.writeFile(defaultPath, JSON.stringify(updated, null, 2), "utf-8");
117
139
  return updated;
@@ -184,8 +206,12 @@ export class ConfigManager {
184
206
  */
185
207
  async bootstrap() {
186
208
  await this.ensureDirs();
209
+ // 迁移前读一次磁盘原文:只为了拿旧的 `defaultModel`(字段已从类型里移除),
210
+ // 让升级不改变用户原本在用的模型
211
+ const raw = (await this.readRaw());
187
212
  const config = await this.ensureDefaultConfig();
188
- const agent = await this.ensureDefaultAgent();
213
+ this.knownModelIds = new Set((config.models ?? []).map((m) => m.id));
214
+ const agent = await this.ensureDefaultAgent(raw?.defaultModel);
189
215
  const subAgent = await this.ensureDefaultSubAgent();
190
216
  await this.ensureDefaultMcpConfig();
191
217
  await this.ensureFactorySkills();
@@ -365,9 +391,11 @@ export class ConfigManager {
365
391
  * 规则:
366
392
  * - 未标 `custom: true` 的条目 → 由 SDK 托管,从结果中移除,改用 defaults 中的最新定义;
367
393
  * - 标了 `custom: true` 的条目 → 用户自有,原样保留;与出厂定义同 id 时用户版本优先;
368
- * - 默认模型 ID(defaultModel / defaultImageModel / defaultMigrationModel)
394
+ * - 默认模型 ID(defaultImageModel / defaultMigrationModel)
369
395
  * 在合并后若已不存在 → 回退到出厂默认,避免悬空引用。
370
396
  *
397
+ * 注意 `defaultModel` 已不再存在:模型真相源是 `AgentDefinition.modelId`。
398
+ *
371
399
  * 端点(endpoints)不参与托管,始终保持用户配置。
372
400
  */
373
401
  export function syncManagedModels(config) {
@@ -386,7 +414,6 @@ export function syncManagedModels(config) {
386
414
  ...config,
387
415
  models,
388
416
  imageModels,
389
- defaultModel: resolve(config.defaultModel, modelIds, DEFAULT_APP_CONFIG.defaultModel),
390
417
  defaultImageModel: resolve(config.defaultImageModel, imageIds, DEFAULT_APP_CONFIG.defaultImageModel),
391
418
  defaultMigrationModel: resolve(config.defaultMigrationModel, modelIds, DEFAULT_APP_CONFIG.defaultMigrationModel),
392
419
  };
@@ -403,4 +430,4 @@ function sameMessages(a, b) {
403
430
  return a.every((message, index) => message.role === b[index].role &&
404
431
  JSON.stringify(message.content) === JSON.stringify(b[index].content));
405
432
  }
406
- export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
433
+ export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_MODEL_ID, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
@@ -1,6 +1,13 @@
1
1
  import type { AgentDefinition, AppConfig } from "../types/index.js";
2
2
  /** 默认 Agent ID */
3
3
  export declare const DEFAULT_AGENT_ID = "default";
4
+ /**
5
+ * 出厂默认模型 id。
6
+ *
7
+ * 模型的**唯一真相源是 `AgentDefinition.modelId`**:配置里不再有 `defaultModel`,
8
+ * 会话存档里也不记模型。这个常量只用于给出厂默认 Agent 填一个初始值。
9
+ */
10
+ export declare const DEFAULT_MODEL_ID = "deepseek-flash";
4
11
  /** 默认 Agent 定义(不含时间戳) */
5
12
  export declare const DEFAULT_AGENT_DEFINITION: Omit<AgentDefinition, "createdAt" | "updatedAt">;
6
13
  /** 默认 SubAgent ID */
@@ -4,11 +4,20 @@ import { Message } from "@ai-zen/agents-core";
4
4
  // ---------------------------------------------------------------------------
5
5
  /** 默认 Agent ID */
6
6
  export const DEFAULT_AGENT_ID = "default";
7
+ /**
8
+ * 出厂默认模型 id。
9
+ *
10
+ * 模型的**唯一真相源是 `AgentDefinition.modelId`**:配置里不再有 `defaultModel`,
11
+ * 会话存档里也不记模型。这个常量只用于给出厂默认 Agent 填一个初始值。
12
+ */
13
+ export const DEFAULT_MODEL_ID = "deepseek-flash";
7
14
  /** 默认 Agent 定义(不含时间戳) */
8
15
  export const DEFAULT_AGENT_DEFINITION = {
9
16
  id: DEFAULT_AGENT_ID,
10
17
  name: "默认助手",
11
18
  description: "默认的 AI 助手,适用于日常问答和任务执行。",
19
+ // 模型写在 Agent 定义上,是运行期唯一真相源(无 config.defaultModel 兜底)
20
+ modelId: DEFAULT_MODEL_ID,
12
21
  messages: [
13
22
  Message.System(`你是一个幽默风趣、严谨可靠的智能助手,请用中文回复。当前操作系统:${process.platform}。
14
23
 
@@ -223,7 +232,6 @@ export const DEFAULT_APP_CONFIG = {
223
232
  defaultSize: "1024x1024",
224
233
  },
225
234
  ],
226
- defaultModel: "deepseek-flash",
227
235
  defaultImageModel: "cogview-4",
228
236
  defaultAgent: "default",
229
237
  defaultMigrationModel: "deepseek-flash",
@@ -57,6 +57,9 @@ export declare function migrateModelReasoning(model: Model): {
57
57
  /**
58
58
  * 迁移整个配置。只处理对话模型(`models`)——
59
59
  * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
60
+ *
61
+ * 同时清除已废弃的 `defaultModel`:模型的真相源已改为 `AgentDefinition.modelId`,
62
+ * 配置级默认值连同字段一起消失(旧值先由 `ConfigManager.bootstrap()` 搬到默认 Agent 上)。
60
63
  */
61
64
  export declare function migrateConfig(config: AppConfig): {
62
65
  config: AppConfig;
@@ -102,6 +102,9 @@ export function migrateModelReasoning(model) {
102
102
  /**
103
103
  * 迁移整个配置。只处理对话模型(`models`)——
104
104
  * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
105
+ *
106
+ * 同时清除已废弃的 `defaultModel`:模型的真相源已改为 `AgentDefinition.modelId`,
107
+ * 配置级默认值连同字段一起消失(旧值先由 `ConfigManager.bootstrap()` 搬到默认 Agent 上)。
105
108
  */
106
109
  export function migrateConfig(config) {
107
110
  let changed = false;
@@ -112,7 +115,13 @@ export function migrateConfig(config) {
112
115
  changed = true;
113
116
  return reasoning.model;
114
117
  });
118
+ const next = { ...config, models };
119
+ // 已废弃字段:不参与运行,也不允许再写回磁盘
120
+ if ("defaultModel" in next) {
121
+ delete next.defaultModel;
122
+ changed = true;
123
+ }
115
124
  if (!changed)
116
125
  return { config, changed };
117
- return { config: { ...config, models }, changed };
126
+ return { config: next, changed };
118
127
  }
@@ -15,9 +15,10 @@ export async function createAgent(scope, agentId) {
15
15
  const definition = await new AgentRepository(scope.agentsDir).read(agentId);
16
16
  if (!definition)
17
17
  throw new Error(`Agent "${agentId}" 不存在`);
18
- const modelId = definition.modelId ?? scope.config.defaultModel;
19
- if (!modelId)
20
- throw new Error("未指定模型且无默认模型");
18
+ const modelId = definition.modelId;
19
+ if (!modelId) {
20
+ throw new Error(`Agent "${agentId}" 未绑定模型(缺少 modelId)。请在配置里给它指定一个模型。`);
21
+ }
21
22
  const { client, model, modelConfig } = createModel(scope, modelId, definition.modelParams);
22
23
  const tools = scope.buildTools(definition, {
23
24
  exclude: {
@@ -29,7 +29,7 @@ function isLocalUrl(value) {
29
29
  export class ViewImageTool extends SdkCallbackTool {
30
30
  /** 仅视觉模型可用:从 definition.modelId 解析当前模型,非视觉模型由 buildTools 过滤 */
31
31
  isAvailable(config, definition) {
32
- const modelId = definition.modelId ?? config.defaultModel;
32
+ const modelId = definition.modelId;
33
33
  const model = (config.models ?? []).find((m) => m.id === modelId);
34
34
  return model?.vision === true;
35
35
  }
@@ -74,9 +74,6 @@ export function resolveModelAndEndpoint(config, apiModelName) {
74
74
  : undefined;
75
75
  if (byApiName)
76
76
  return { model: byApiName, endpoint: findEndpoint(byApiName.endpointId) };
77
- const fallback = models.find((m) => m.id === config.defaultModel);
78
- if (fallback)
79
- return { model: fallback, endpoint: findEndpoint(fallback.endpointId) };
80
77
  return { endpoint: null };
81
78
  }
82
79
  /** 端点是否声明了 Files API 能力 */
@@ -131,6 +131,12 @@ export interface AgentDefinition {
131
131
  name: string;
132
132
  description?: string;
133
133
  messages: AgentNS.Message[];
134
+ /**
135
+ * 绑定模型 id —— **运行期唯一真相源**。
136
+ *
137
+ * 缺省即「无模型可用」,`createAgent` 会直接报错;不存在配置层的 `defaultModel`
138
+ * 兜底,也不存在会话级的模型记录。要换模型就改这里(`/model` 写的就是它)。
139
+ */
134
140
  modelId?: string;
135
141
  /**
136
142
  * Agent 级模型参数覆盖,浅合并进模型的 `defaultParams`。
@@ -176,7 +182,6 @@ export interface ImageModel {
176
182
  }
177
183
  /** config.json 结构 */
178
184
  export interface AppConfig {
179
- defaultModel?: string;
180
185
  endpoints: Endpoint[];
181
186
  models: Model[];
182
187
  /** 图片生成模型列表 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.18",
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",