@ai-zen/agents-sdk 1.0.0-alpha.12 → 1.0.0-alpha.13
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 +42 -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 +6 -0
- package/dist/config/ConfigManager.js +17 -9
- package/dist/config/constants.js +30 -11
- package/dist/config/migrate.d.ts +47 -0
- package/dist/config/migrate.js +89 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -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 +6 -1
- package/dist/runtime/createModel.js +8 -2
- package/dist/shared/errors.d.ts +4 -8
- package/dist/shared/errors.js +8 -14
- package/dist/shared/reasoning.d.ts +64 -0
- package/dist/shared/reasoning.js +151 -0
- package/dist/types/index.d.ts +65 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-alpha.13] - 2026-10-22
|
|
4
|
+
|
|
5
|
+
### 💥 Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **`Model.maxContextTokens` / `maxContextChars` → `Model.tokenThresholds: { handoff, guard }`** —— 旧名字一直在误导(它既不是上下文窗口,也不是单一概念)。现在拆成两条**绝对值**:
|
|
8
|
+
- `handoff`:软线,响应返回后 `prompt_tokens` 超过它触发任务交接(`AutoMigratePlugin`);
|
|
9
|
+
- `guard`:硬线,请求前用量超过它抛 `ContextOverflowError` 中断本轮(`ContextGuardPlugin`)。
|
|
10
|
+
- `maxContextChars` 直接淘汰(字符口径早于 token 口径,两者并存只会让「按哪个判」长期含糊)。
|
|
11
|
+
- 迁移由 `config/migrate.ts` 完成,`ConfigManager.read()` 读到即迁、`ensureDefaultConfig()` 检测差异才写盘:`maxContextTokens` → `handoff`,`maxContextChars` → `floor(/4)`,缺 `guard` 时按 1.5 倍折算一次(保持迁移前行为),旧键随即删除。
|
|
12
|
+
- **`ContextGuardPlugin` 的 `ratio` / `suspendedRatio` / `isSuspended` 全部删除** —— 它们只是「相对阈值」的产物。现在 `new ContextGuardPlugin({ guardTokens })` 一个绝对数就够:想调松就把数字改大。代价是**交接回合不再有 2.0 倍的额外放宽**,若需要该缓冲请把 `guard` 配得更大(出厂取 `handoff × 1.5`,并写成整数)。
|
|
13
|
+
- **`ContextOverflowError(promptTokens, guardTokens)`** —— 去掉 `maxTokens` / `ratio` / `threshold` 三个派生字段,改为直接暴露被越过的 `guardTokens`。
|
|
14
|
+
- **`AutoMigratePlugin` 的 `maxTokens` → `handoffTokens`**,`AutoMigrateTriggerInfo.maxTokens` → `handoffTokens`,`HandoffService` 的 `HandoffParams.maxTokens` / `HandoffContext.maxTokens` → `handoffTokens`(仅字段改名,语义不变:交接阈值)。
|
|
15
|
+
|
|
16
|
+
### ✨ 新增
|
|
17
|
+
|
|
18
|
+
- **思考强度(reasoning effort)能力位** —— 档位此前完全没有建模,CLI 只能手改 `defaultParams`,而各家端点接受的值域并不相同(智谱 GLM-5.3 系对 `medium` 直接报错、对 `thinking.type = "disabled"` 也报错)。现在分三层:
|
|
19
|
+
- `Model.reasoning: { efforts: ReasoningEffort[], disable?: "thinking" | "effort-none" }` —— **物理能力上限**,按厂商官方文档声明(防 400);
|
|
20
|
+
- `AgentDefinition.reasoningEffort?: ReasoningEffort` —— 该 Agent 的实际档位,在 `createModel` 阶段覆盖模型 `defaultParams`。于是同一模型可以给不同 Agent 配不同强度(编程 Agent `max`、闲聊 Agent `low`、闲聊小号 `off`)。
|
|
21
|
+
- 刻意**没有**做 Agent 侧的档位白名单(`reasoningEfforts`):它解决的是伪需求(真正会 400 的是端点能力,那已由模型声明兜住),却引入「差一个 s」的命名陷阱与一层交集规则。
|
|
22
|
+
- **`shared/reasoning.ts`**(公共 API,均从包入口导出):`resolveReasoningEffort()`、`resolveModelParams()`、`applyReasoningEffort()`(按声明写入,非法档位抛错)、`modelEfforts()` / `modelAllowedEfforts()` / `readReasoningEffort()` / `isReasoningEffort()` / `capabilityOf()`,以及 `ReasoningEffort` / `ReasoningCapability` 类型。
|
|
23
|
+
- **写入形态由声明决定**:`disable: "thinking"` → 同时维护 `thinking.type` 与 `reasoning_effort`(DeepSeek);`disable: "effort-none"` → 只写 `reasoning_effort`(OpenAI 没有 `thinking` 参数,写它会被当未知参数);未声明 `disable` → 只写 `reasoning_effort`,`off` 直接抛错(智谱 5.3 系)。
|
|
24
|
+
- **出厂模型按官方文档声明能力**(依据已写入 `constants.ts` 注释):
|
|
25
|
+
- `deepseek-flash` → `low/high/max`,可关闭(`api-docs.deepseek.com/guides/thinking_mode`)
|
|
26
|
+
- `glm-5.3` / `glm-5.3-flash` / `glm-5.3-flashx` → `low/high/max`,**不可关闭**(`docs.bigmodel.cn/cn/guide/capabilities/thinking`)
|
|
27
|
+
- `glm-4.7-flash` → 不可调(强制思考,`reasoning_effort` 仅 5.2+ 支持)
|
|
28
|
+
- `gpt-6-astra` / `gpt-6.1-sol` / `gpt-6-luna` → `minimal/low/medium/high`,可用 `reasoning_effort: "none"` 关闭
|
|
29
|
+
- 未声明的模型一律视为不可调档,不推测
|
|
30
|
+
|
|
31
|
+
### 🔧 变更
|
|
32
|
+
|
|
33
|
+
- **`createModel(scope, modelId)` → `createModel(scope, modelId, overrides?: Pick<AgentDefinition, "reasoningEffort">)`** —— 新增的可选第三参由 `createAgent` 传入 `definition.reasoningEffort`。不传时行为与以前完全一致(原样返回模型 `defaultParams`)。
|
|
34
|
+
|
|
35
|
+
### ✅ 测试
|
|
36
|
+
|
|
37
|
+
- 新增 `shared/reasoning.test.ts` 22 例:能力声明与出厂清单一致性、`off` 只对有 `disable` 的模型开放、GLM-5.3 设 `medium` 与 `off` 均抛错、OpenAI 永不写 `thinking` 键、往返一致性、Agent 覆盖与回退。
|
|
38
|
+
- 新增 `config/migrate.test.ts` 13 例:旧字段迁移(tokens / chars / 两者 / 都无 / 脏值)、`guard` 折算、已是新形状时不迁移、不改入参、部分迁移时未变条目保持同一引用。
|
|
39
|
+
- 全量 **57 文件 / 599 用例** 通过(6 skipped),`tsc --noEmit` 与 `build` 零错误。
|
|
40
|
+
|
|
41
|
+
### 📄 Docs
|
|
42
|
+
|
|
43
|
+
- `docs/{zh,en}/sdk.md` 新增「思考强度(reasoning effort)」小节(三层模型、出厂声明表、写入形态、公共 API),核心实体表补充 `Model.reasoning` 与 Agent 的两个档位字段。
|
|
44
|
+
|
|
3
45
|
## [1.0.0-alpha.12] - 2026-10-22
|
|
4
46
|
|
|
5
47
|
### 💥 Breaking Changes
|
|
@@ -4,8 +4,8 @@ import type { HandoffMode, HandoffService } from "../runtime/HandoffService.js";
|
|
|
4
4
|
export interface AutoMigrateTriggerInfo {
|
|
5
5
|
/** 当前 prompt token 用量 */
|
|
6
6
|
promptTokens: number;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/** 交接阈值(当前模型的 tokenThresholds.handoff) */
|
|
8
|
+
handoffTokens: number;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* 迁移执行者解析器:只负责「用什么方式交接」,不负责「何时迁移」。
|
|
@@ -19,7 +19,7 @@ export interface AutoMigrateOptions {
|
|
|
19
19
|
/** 交接服务实例(持有交接前后钩子;编排 handoff / migrate 两条链路) */
|
|
20
20
|
service: HandoffService;
|
|
21
21
|
/** 触发迁移的上下文阈值 */
|
|
22
|
-
|
|
22
|
+
handoffTokens: number;
|
|
23
23
|
/**
|
|
24
24
|
* 执行者解析(可选)。上层可在此弹确认框、读用户偏好,
|
|
25
25
|
* 或直接返回 `null` 表达「本次不迁移」——**无需**再继承本插件。
|
|
@@ -29,14 +29,14 @@ export interface AutoMigrateOptions {
|
|
|
29
29
|
/**
|
|
30
30
|
* 自动迁移插件 —— 只负责「何时触发」,实际迁移统一委托给持有的 `HandoffService`。
|
|
31
31
|
*
|
|
32
|
-
* 当 `usage.prompt_tokens` 超过
|
|
32
|
+
* 当 `usage.prompt_tokens` 超过 handoffTokens 时触发迁移:
|
|
33
33
|
*
|
|
34
34
|
* ```ts
|
|
35
35
|
* agent.use(new AutoMigratePlugin({
|
|
36
36
|
* service: handoffService,
|
|
37
|
-
*
|
|
38
|
-
* resolveMode: async ({ promptTokens,
|
|
39
|
-
* (await askUser(promptTokens,
|
|
37
|
+
* handoffTokens: 250_000,
|
|
38
|
+
* resolveMode: async ({ promptTokens, handoffTokens }) =>
|
|
39
|
+
* (await askUser(promptTokens, handoffTokens)) ? "self" : null,
|
|
40
40
|
* }));
|
|
41
41
|
* ```
|
|
42
42
|
*
|
|
@@ -3,14 +3,14 @@ const log = getLogger();
|
|
|
3
3
|
/**
|
|
4
4
|
* 自动迁移插件 —— 只负责「何时触发」,实际迁移统一委托给持有的 `HandoffService`。
|
|
5
5
|
*
|
|
6
|
-
* 当 `usage.prompt_tokens` 超过
|
|
6
|
+
* 当 `usage.prompt_tokens` 超过 handoffTokens 时触发迁移:
|
|
7
7
|
*
|
|
8
8
|
* ```ts
|
|
9
9
|
* agent.use(new AutoMigratePlugin({
|
|
10
10
|
* service: handoffService,
|
|
11
|
-
*
|
|
12
|
-
* resolveMode: async ({ promptTokens,
|
|
13
|
-
* (await askUser(promptTokens,
|
|
11
|
+
* handoffTokens: 250_000,
|
|
12
|
+
* resolveMode: async ({ promptTokens, handoffTokens }) =>
|
|
13
|
+
* (await askUser(promptTokens, handoffTokens)) ? "self" : null,
|
|
14
14
|
* }));
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
@@ -26,22 +26,22 @@ export class AutoMigratePlugin {
|
|
|
26
26
|
}
|
|
27
27
|
async onAfterSend(ctx) {
|
|
28
28
|
const agent = ctx.agent;
|
|
29
|
-
const { service,
|
|
29
|
+
const { service, handoffTokens, resolveMode } = this.options;
|
|
30
30
|
// 交接/迁移进行中:让行(防递归)
|
|
31
31
|
if (service.busy)
|
|
32
32
|
return;
|
|
33
33
|
const promptTokens = agent.lastUsage?.prompt_tokens;
|
|
34
34
|
if (promptTokens == null)
|
|
35
35
|
return;
|
|
36
|
-
if (promptTokens <=
|
|
36
|
+
if (promptTokens <= handoffTokens)
|
|
37
37
|
return;
|
|
38
38
|
try {
|
|
39
39
|
const mode = resolveMode
|
|
40
|
-
? await resolveMode({ promptTokens,
|
|
40
|
+
? await resolveMode({ promptTokens, handoffTokens })
|
|
41
41
|
: undefined;
|
|
42
42
|
if (mode === null)
|
|
43
43
|
return; // 用户选择跳过本次迁移
|
|
44
|
-
await service.migrate({ agent, promptTokens,
|
|
44
|
+
await service.migrate({ agent, promptTokens, handoffTokens, mode: mode ?? undefined });
|
|
45
45
|
}
|
|
46
46
|
catch (err) {
|
|
47
47
|
log.error(`[autoMigrate] 迁移失败: ${err?.message ?? err}`);
|
|
@@ -1,44 +1,32 @@
|
|
|
1
1
|
import type { AgentPlugin, SendContext } from "@ai-zen/agents-core";
|
|
2
2
|
export interface ContextGuardOptions {
|
|
3
|
-
/** 上下文告警阈值(token)。用量超过 maxTokens × ratio 即中断。 */
|
|
4
|
-
maxTokens: number;
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
7
|
-
* 在请求前抛出 ContextOverflowError 中断对话。默认 1.5(即 +50%)。
|
|
8
|
-
*/
|
|
9
|
-
ratio?: number;
|
|
10
|
-
/**
|
|
11
|
-
* 是否处于「需要放宽」的阶段(例如交接回合)。返回 true 时改用 `suspendedRatio`。
|
|
4
|
+
* 强行阻断阈值(tokens,绝对值)。
|
|
12
5
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
6
|
+
* 语义只有一条:请求前上一轮用量超过它 → 中断本轮。没有倍数、没有放宽档——
|
|
7
|
+
* 想调松就把它调大,「安全边界写多少就是多少」比「隐式乘以某个系数」可预测得多。
|
|
15
8
|
*/
|
|
16
|
-
|
|
17
|
-
/** 放宽后的比例,默认 2.0(即 maxTokens × 2) */
|
|
18
|
-
suspendedRatio?: number;
|
|
9
|
+
guardTokens: number;
|
|
19
10
|
}
|
|
20
11
|
/**
|
|
21
12
|
* 上下文安全护栏插件。
|
|
22
13
|
*
|
|
23
|
-
* 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens
|
|
24
|
-
*
|
|
25
|
-
* 在请求前抛出 ContextOverflowError,**中断对话**,交由上层向用户报错。
|
|
14
|
+
* 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens):超过 `guardTokens`
|
|
15
|
+
* 即抛 `ContextOverflowError` 中断对话,交由上层向用户报错。
|
|
26
16
|
*
|
|
27
17
|
* 与迁移插件职责分离:
|
|
28
|
-
* - ContextGuardPlugin
|
|
29
|
-
* - AutoMigratePlugin
|
|
30
|
-
*
|
|
18
|
+
* - `ContextGuardPlugin`:硬线,只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
|
|
19
|
+
* - `AutoMigratePlugin`:软线(`tokenThresholds.handoff`),在正常范围内做交接迁移。
|
|
20
|
+
* 两条线都是绝对值,`guard` 应当明显大于 `handoff`,交接回合的额外请求才有余量。
|
|
31
21
|
*
|
|
32
22
|
* ```ts
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* }));
|
|
37
|
-
* agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens: 250_000 }));
|
|
23
|
+
* const { handoff, guard } = config.models[i].tokenThresholds;
|
|
24
|
+
* agent.use(new ContextGuardPlugin({ guardTokens: guard }));
|
|
25
|
+
* agent.use(new AutoMigratePlugin({ service: handoffService, handoffTokens: handoff }));
|
|
38
26
|
* ```
|
|
39
27
|
*/
|
|
40
28
|
export declare class ContextGuardPlugin implements AgentPlugin {
|
|
41
|
-
private
|
|
29
|
+
private readonly guardTokens;
|
|
42
30
|
constructor(options: ContextGuardOptions);
|
|
43
31
|
onInnerLoopStart(ctx: SendContext): Promise<void>;
|
|
44
32
|
}
|
|
@@ -2,39 +2,32 @@ import { ContextOverflowError } from "../shared/errors.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* 上下文安全护栏插件。
|
|
4
4
|
*
|
|
5
|
-
* 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens
|
|
6
|
-
*
|
|
7
|
-
* 在请求前抛出 ContextOverflowError,**中断对话**,交由上层向用户报错。
|
|
5
|
+
* 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens):超过 `guardTokens`
|
|
6
|
+
* 即抛 `ContextOverflowError` 中断对话,交由上层向用户报错。
|
|
8
7
|
*
|
|
9
8
|
* 与迁移插件职责分离:
|
|
10
|
-
* - ContextGuardPlugin
|
|
11
|
-
* - AutoMigratePlugin
|
|
12
|
-
*
|
|
9
|
+
* - `ContextGuardPlugin`:硬线,只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
|
|
10
|
+
* - `AutoMigratePlugin`:软线(`tokenThresholds.handoff`),在正常范围内做交接迁移。
|
|
11
|
+
* 两条线都是绝对值,`guard` 应当明显大于 `handoff`,交接回合的额外请求才有余量。
|
|
13
12
|
*
|
|
14
13
|
* ```ts
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* }));
|
|
19
|
-
* agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens: 250_000 }));
|
|
14
|
+
* const { handoff, guard } = config.models[i].tokenThresholds;
|
|
15
|
+
* agent.use(new ContextGuardPlugin({ guardTokens: guard }));
|
|
16
|
+
* agent.use(new AutoMigratePlugin({ service: handoffService, handoffTokens: handoff }));
|
|
20
17
|
* ```
|
|
21
18
|
*/
|
|
22
19
|
export class ContextGuardPlugin {
|
|
23
|
-
|
|
20
|
+
guardTokens;
|
|
24
21
|
constructor(options) {
|
|
25
|
-
this.
|
|
22
|
+
this.guardTokens = options.guardTokens;
|
|
26
23
|
}
|
|
27
24
|
async onInnerLoopStart(ctx) {
|
|
28
|
-
const
|
|
29
|
-
const { maxTokens, ratio = 1.5, isSuspended, suspendedRatio = 2 } = this.options;
|
|
25
|
+
const promptTokens = ctx.agent.lastUsage?.prompt_tokens;
|
|
30
26
|
// 首轮请求前尚无用量数据,跳过(只能在发出首轮请求、拿到 usage 后才有据可查)
|
|
31
|
-
const promptTokens = agent.lastUsage?.prompt_tokens;
|
|
32
27
|
if (promptTokens == null)
|
|
33
28
|
return;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (promptTokens > maxTokens * effectiveRatio) {
|
|
37
|
-
throw new ContextOverflowError(promptTokens, maxTokens, effectiveRatio);
|
|
29
|
+
if (promptTokens > this.guardTokens) {
|
|
30
|
+
throw new ContextOverflowError(promptTokens, this.guardTokens);
|
|
38
31
|
}
|
|
39
32
|
}
|
|
40
33
|
}
|
|
@@ -8,8 +8,14 @@ export declare class ConfigManager {
|
|
|
8
8
|
constructor(configPath: string);
|
|
9
9
|
/**
|
|
10
10
|
* 读取配置。文件不存在时返回出厂默认配置。
|
|
11
|
+
*
|
|
12
|
+
* 读到即**迁移**(`config/migrate.ts`):上层拿到的永远是当前字段形状,
|
|
13
|
+
* 旧字段(`maxContextTokens` / `maxContextChars`)不会漏到调用方。
|
|
14
|
+
* 迁移结果不在这里落盘——落盘只在 `ensureDefaultConfig()` 做一次。
|
|
11
15
|
*/
|
|
12
16
|
read(): Promise<AppConfig>;
|
|
17
|
+
/** 读磁盘原文(不做任何迁移/同步),供 `ensureDefaultConfig()` 对比写盘用 */
|
|
18
|
+
private readRaw;
|
|
13
19
|
write(config: AppConfig): Promise<void>;
|
|
14
20
|
/**
|
|
15
21
|
* 确保基础目录结构存在。
|
|
@@ -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 { migrateConfig } from "./migrate.js";
|
|
5
6
|
/**
|
|
6
7
|
* 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
|
|
7
8
|
*/
|
|
@@ -17,13 +18,22 @@ export class ConfigManager {
|
|
|
17
18
|
// -----------------------------------------------------------------------
|
|
18
19
|
/**
|
|
19
20
|
* 读取配置。文件不存在时返回出厂默认配置。
|
|
21
|
+
*
|
|
22
|
+
* 读到即**迁移**(`config/migrate.ts`):上层拿到的永远是当前字段形状,
|
|
23
|
+
* 旧字段(`maxContextTokens` / `maxContextChars`)不会漏到调用方。
|
|
24
|
+
* 迁移结果不在这里落盘——落盘只在 `ensureDefaultConfig()` 做一次。
|
|
20
25
|
*/
|
|
21
26
|
async read() {
|
|
27
|
+
const raw = await this.readRaw();
|
|
28
|
+
return raw ? migrateConfig(raw).config : { ...DEFAULT_APP_CONFIG };
|
|
29
|
+
}
|
|
30
|
+
/** 读磁盘原文(不做任何迁移/同步),供 `ensureDefaultConfig()` 对比写盘用 */
|
|
31
|
+
async readRaw() {
|
|
22
32
|
try {
|
|
23
33
|
await fs.access(this.configPath);
|
|
24
34
|
}
|
|
25
35
|
catch {
|
|
26
|
-
return
|
|
36
|
+
return null;
|
|
27
37
|
}
|
|
28
38
|
const raw = await fs.readFile(this.configPath, "utf-8");
|
|
29
39
|
return JSON.parse(raw);
|
|
@@ -62,18 +72,16 @@ export class ConfigManager {
|
|
|
62
72
|
* 条目视为用户自有,原样保留。同步结果与磁盘内容一致时不写盘。
|
|
63
73
|
*/
|
|
64
74
|
async ensureDefaultConfig() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await fs.access(this.configPath);
|
|
68
|
-
existing = await this.read();
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
75
|
+
const onDisk = await this.readRaw();
|
|
76
|
+
if (!onDisk) {
|
|
71
77
|
await this.ensureDirs();
|
|
72
78
|
await this.write(DEFAULT_APP_CONFIG);
|
|
73
79
|
return { ...DEFAULT_APP_CONFIG };
|
|
74
80
|
}
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
// 先迁移历史字段,再同步出厂模型清单;两者结果与磁盘原文不同才写盘
|
|
82
|
+
const migrated = migrateConfig(onDisk).config;
|
|
83
|
+
const synced = syncManagedModels(migrated);
|
|
84
|
+
if (JSON.stringify(synced) !== JSON.stringify(onDisk)) {
|
|
77
85
|
await this.write(synced);
|
|
78
86
|
}
|
|
79
87
|
return synced;
|
package/dist/config/constants.js
CHANGED
|
@@ -98,9 +98,20 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
98
98
|
// 标了 `custom: true` 的条目视为用户自有,原样保留。实现见
|
|
99
99
|
// ConfigManager.syncManagedModels。
|
|
100
100
|
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
101
|
+
// tokenThresholds 的两条线都与模型的真实上下文窗口无关:
|
|
102
|
+
// handoff(软线,交接触发)与 guard(硬线,请求前强行阻断)。
|
|
103
|
+
// 出厂取值:窗口 ≥1M 的模型统一 handoff=250K / guard=375K(约窗口的 25% / 37.5%),
|
|
104
|
+
// glm-4.7-flash 窗口仅 200K,取 handoff=100K / guard=150K。
|
|
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()` 返回空数组。
|
|
104
115
|
models: [
|
|
105
116
|
// ---------- OpenAI ----------
|
|
106
117
|
{
|
|
@@ -108,9 +119,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
108
119
|
name: "GPT-6 Astra",
|
|
109
120
|
endpointId: "openai",
|
|
110
121
|
modelName: "gpt-6-astra",
|
|
111
|
-
|
|
122
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
112
123
|
defaultParams: {},
|
|
113
124
|
vision: true,
|
|
125
|
+
reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
|
|
114
126
|
description: "OpenAI 最强旗舰(2026-09):高级分析、软件工程、深度研究与科学工作",
|
|
115
127
|
},
|
|
116
128
|
{
|
|
@@ -118,9 +130,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
118
130
|
name: "GPT-6.1 Sol",
|
|
119
131
|
endpointId: "openai",
|
|
120
132
|
modelName: "gpt-6.1-sol",
|
|
121
|
-
|
|
133
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
122
134
|
defaultParams: {},
|
|
123
135
|
vision: true,
|
|
136
|
+
reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
|
|
124
137
|
description: "OpenAI 最新旗舰(2026-09-29):Agentic Coding、计算机操作与文档密集型专业任务",
|
|
125
138
|
},
|
|
126
139
|
{
|
|
@@ -128,9 +141,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
128
141
|
name: "GPT-6 Luna",
|
|
129
142
|
endpointId: "openai",
|
|
130
143
|
modelName: "gpt-6-luna",
|
|
131
|
-
|
|
144
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
132
145
|
defaultParams: {},
|
|
133
146
|
vision: true,
|
|
147
|
+
reasoning: { efforts: ["minimal", "low", "medium", "high"], disable: "effort-none" },
|
|
134
148
|
description: "OpenAI GPT-6 系列快速低成本模型:适合高吞吐、低延迟场景",
|
|
135
149
|
},
|
|
136
150
|
// ---------- 智谱 BigModel ----------
|
|
@@ -139,8 +153,9 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
139
153
|
name: "GLM-5.3",
|
|
140
154
|
endpointId: "bigmodelcn",
|
|
141
155
|
modelName: "glm-5.3",
|
|
142
|
-
|
|
156
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
143
157
|
defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
|
|
158
|
+
reasoning: { efforts: ["low", "high", "max"] },
|
|
144
159
|
description: "智谱最新旗舰:复杂软件工程与长程 Agent 任务(强制开启思考,1M 上下文)",
|
|
145
160
|
},
|
|
146
161
|
{
|
|
@@ -148,9 +163,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
148
163
|
name: "GLM-5.3-Flash",
|
|
149
164
|
endpointId: "bigmodelcn",
|
|
150
165
|
modelName: "glm-5.3-flash",
|
|
151
|
-
|
|
166
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
152
167
|
defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
|
|
153
168
|
vision: true,
|
|
169
|
+
reasoning: { efforts: ["low", "high", "max"] },
|
|
154
170
|
description: "智谱原生多模态模型(图片/视频/文件):视觉 Coding,1M 上下文",
|
|
155
171
|
},
|
|
156
172
|
{
|
|
@@ -158,9 +174,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
158
174
|
name: "GLM-5.3-FlashX",
|
|
159
175
|
endpointId: "bigmodelcn",
|
|
160
176
|
modelName: "glm-5.3-flashx",
|
|
161
|
-
|
|
177
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
162
178
|
defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
|
|
163
179
|
vision: true,
|
|
180
|
+
reasoning: { efforts: ["low", "high", "max"] },
|
|
164
181
|
description: "GLM-5.3-Flash 高速版(200 tokens/s),原生多模态",
|
|
165
182
|
},
|
|
166
183
|
{
|
|
@@ -169,8 +186,9 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
169
186
|
endpointId: "bigmodelcn",
|
|
170
187
|
modelName: "glm-4.7-flash",
|
|
171
188
|
// 该模型真实窗口仅 200K,阈值下调至 100K(约窗口的 50%),留足 response 空间
|
|
172
|
-
|
|
189
|
+
tokenThresholds: { handoff: 100_000, guard: 150_000 },
|
|
173
190
|
defaultParams: {},
|
|
191
|
+
reasoning: { efforts: [] },
|
|
174
192
|
description: "智谱免费文本模型:200K 上下文,适合轻量任务",
|
|
175
193
|
},
|
|
176
194
|
// ---------- DeepSeek ----------
|
|
@@ -179,9 +197,10 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
179
197
|
name: "DeepSeek-V4.1-Flash",
|
|
180
198
|
endpointId: "deepseek",
|
|
181
199
|
modelName: "deepseek-flash",
|
|
182
|
-
|
|
200
|
+
tokenThresholds: { handoff: 250_000, guard: 375_000 },
|
|
183
201
|
defaultParams: { thinking: { type: "enabled" } },
|
|
184
202
|
vision: true,
|
|
203
|
+
reasoning: { efforts: ["low", "high", "max"], disable: "thinking" },
|
|
185
204
|
description: "DeepSeek 旗舰(2026-09-10):552B MoE 非对称架构,原生多模态,1M 上下文",
|
|
186
205
|
},
|
|
187
206
|
],
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
import type { AppConfig, Model } from "../types/index.js";
|
|
17
|
+
/** 当前字段名 */
|
|
18
|
+
export declare const TOKEN_THRESHOLDS_KEY = "tokenThresholds";
|
|
19
|
+
/** 已淘汰的旧字段名(仅迁移代码认识它们) */
|
|
20
|
+
export declare const LEGACY_TOKEN_FIELD = "maxContextTokens";
|
|
21
|
+
export declare const LEGACY_CHARS_FIELD = "maxContextChars";
|
|
22
|
+
/**
|
|
23
|
+
* 旧数据缺失阈值时的交接阈值兜底。
|
|
24
|
+
*
|
|
25
|
+
* 与 CLI 新建模型的默认值一致;宁可给一个保守值,也不要让模型缺阈值——
|
|
26
|
+
* 缺阈值意味着既没有交接触发、也没有阻断保护。
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_HANDOFF_THRESHOLD = 128000;
|
|
29
|
+
/**
|
|
30
|
+
* 迁移单个模型条目。
|
|
31
|
+
*
|
|
32
|
+
* 优先级:已有 `tokenThresholds` 的成员 > 旧 tokens 字段 > 旧 chars 字段折算 > 兜底默认。
|
|
33
|
+
* 旧字段一律删除(连同一份数据里残留的脏值),保证迁移后的形状唯一。
|
|
34
|
+
* 已有 `handoff` 但缺 `guard` 时按 1.5 倍补齐。
|
|
35
|
+
*/
|
|
36
|
+
export declare function migrateModelThresholds(model: Model): {
|
|
37
|
+
model: Model;
|
|
38
|
+
changed: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 迁移整个配置。只处理对话模型(`models`)——
|
|
42
|
+
* 图片模型(`imageModels`)从来没有上下文阈值概念。
|
|
43
|
+
*/
|
|
44
|
+
export declare function migrateConfig(config: AppConfig): {
|
|
45
|
+
config: AppConfig;
|
|
46
|
+
changed: boolean;
|
|
47
|
+
};
|
|
@@ -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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { EntityRepository } from "./shared/EntityRepository.js";
|
|
|
25
25
|
export { AgentRepository } from "./crud/AgentRepository.js";
|
|
26
26
|
export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
|
|
27
27
|
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";
|
|
28
29
|
export { createModel } from "./runtime/createModel.js";
|
|
29
30
|
export { SdkAgent } from "./runtime/SdkAgent.js";
|
|
30
31
|
export type { AgentPlugin, SendContext } from "./runtime/SdkAgent.js";
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,8 @@ export { EntityRepository } from "./shared/EntityRepository.js";
|
|
|
24
24
|
export { AgentRepository } from "./crud/AgentRepository.js";
|
|
25
25
|
// 文件 → 内容块(统一入口)— 上层应用构造媒体消息时请走这里,不要自己判断上传还是内联
|
|
26
26
|
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";
|
|
27
29
|
// 运行时
|
|
28
30
|
export { createModel } from "./runtime/createModel.js";
|
|
29
31
|
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,9 @@ 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
|
+
* 写入方式按模型的 `reasoning` 能力声明决定(见 `shared/reasoning.ts`)。
|
|
20
25
|
*/
|
|
21
|
-
export declare function createModel(scope: Scope, modelId: string): AgentModel;
|
|
26
|
+
export declare function createModel(scope: Scope, modelId: string, overrides?: Pick<AgentDefinition, "reasoningEffort">): AgentModel;
|
|
@@ -1,11 +1,16 @@
|
|
|
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
|
+
* 写入方式按模型的 `reasoning` 能力声明决定(见 `shared/reasoning.ts`)。
|
|
7
12
|
*/
|
|
8
|
-
export function createModel(scope, modelId) {
|
|
13
|
+
export function createModel(scope, modelId, overrides) {
|
|
9
14
|
const config = scope.config;
|
|
10
15
|
const modelConfig = config.models.find((m) => m.id === modelId);
|
|
11
16
|
if (!modelConfig)
|
|
@@ -25,6 +30,7 @@ export function createModel(scope, modelId) {
|
|
|
25
30
|
return {
|
|
26
31
|
client,
|
|
27
32
|
model: apiModelName,
|
|
28
|
-
|
|
33
|
+
// 档位覆盖在这里生效:未声明 reasoningEffort 时原样返回模型 defaultParams
|
|
34
|
+
modelConfig: resolveModelParams(modelConfig, overrides),
|
|
29
35
|
};
|
|
30
36
|
}
|
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,64 @@
|
|
|
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;
|
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,6 +25,35 @@ export interface EndpointCapabilities {
|
|
|
25
25
|
*/
|
|
26
26
|
filesApi?: boolean;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* 思考强度档位 —— 各端点官方枚举的**并集**,不含各家不认的值。
|
|
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`
|
|
35
|
+
*
|
|
36
|
+
* `off` 是本 SDK 的语义值(「关闭思考」),对接时按 `ReasoningCapability.disable` 选择表达方式。
|
|
37
|
+
* 它与 `none` 不重复:`none` 是端点自己认的档位值,`off` 是「没有档位、彻底不思考」。
|
|
38
|
+
*/
|
|
39
|
+
export type ReasoningEffort = "off" | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
40
|
+
/**
|
|
41
|
+
* 模型/端点的思考能力声明(**物理上限**,来自厂商官方文档)。
|
|
42
|
+
*
|
|
43
|
+
* 刻意不推断、不猜:未声明的模型视为「不可调档」,`/think` 不会列任何选项——
|
|
44
|
+
* 发一个端点不认的值会被直接报错(智谱 5.3 系原文「其余输入将报错」)。
|
|
45
|
+
*/
|
|
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";
|
|
56
|
+
}
|
|
28
57
|
/** API 端点 */
|
|
29
58
|
export interface Endpoint {
|
|
30
59
|
id: string;
|
|
@@ -34,6 +63,24 @@ export interface Endpoint {
|
|
|
34
63
|
description?: string;
|
|
35
64
|
capabilities?: EndpointCapabilities;
|
|
36
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 上下文用量阈值 —— 与模型的真实窗口无关,只描述「用量到了这条线该怎么办」。
|
|
68
|
+
*
|
|
69
|
+
* 两个阈值语义不同,消费方也不同:
|
|
70
|
+
* - `handoff`:软线。`AutoMigratePlugin` 在响应返回后拿 `usage.prompt_tokens` 比它,
|
|
71
|
+
* 超过即触发任务交接(交接本身还要再发 1~2 次请求,所以这条线必须留足余量)。
|
|
72
|
+
* - `guard`:硬线。`ContextGuardPlugin` 在**每次请求前**比它,超过即抛
|
|
73
|
+
* `ContextOverflowError` 中断本轮——这是最后一道保护,宁可报错也不要发出必然失败的请求。
|
|
74
|
+
*
|
|
75
|
+
* 建议 `guard ≈ handoff × 1.5`,给交接留出缓冲;两者都是绝对值,配置里写多少就是多少,
|
|
76
|
+
* 不做任何倍数换算(阈值就该是能直接读懂的数字)。
|
|
77
|
+
*/
|
|
78
|
+
export interface TokenThresholds {
|
|
79
|
+
/** 交接阈值(软线):prompt_tokens 超过它 → 触发任务交接 */
|
|
80
|
+
handoff: number;
|
|
81
|
+
/** 强行阻断阈值(硬线):请求前用量超过它 → 中断本轮并报错 */
|
|
82
|
+
guard: number;
|
|
83
|
+
}
|
|
37
84
|
/** 模型配置,绑定一个 Endpoint */
|
|
38
85
|
export interface Model {
|
|
39
86
|
id: string;
|
|
@@ -41,15 +88,20 @@ export interface Model {
|
|
|
41
88
|
endpointId: string;
|
|
42
89
|
modelName?: string;
|
|
43
90
|
/**
|
|
44
|
-
*
|
|
91
|
+
* 上下文用量阈值(交接软线 + 阻断硬线)。**不是**模型的真实上下文窗口。
|
|
45
92
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
93
|
+
* 历史字段 `maxContextTokens` / `maxContextChars` 已由 `config/migrate.ts`
|
|
94
|
+
* 迁移成这个结构体并删除。
|
|
48
95
|
*/
|
|
49
|
-
|
|
50
|
-
/** 旧版字符数阈值(兼容迁移) */
|
|
51
|
-
maxContextChars?: number;
|
|
96
|
+
tokenThresholds: TokenThresholds;
|
|
52
97
|
defaultParams?: Record<string, unknown>;
|
|
98
|
+
/**
|
|
99
|
+
* 思考强度能力(按厂商官方文档声明)。缺省视为不可调档。
|
|
100
|
+
*
|
|
101
|
+
* Agent 用 `AgentDefinition.reasoningEffort` 在这个范围内选一档;
|
|
102
|
+
* 超出范围的值不会发给服务端(`resolveModelParams()` 会退回模型默认)。
|
|
103
|
+
*/
|
|
104
|
+
reasoning?: ReasoningCapability;
|
|
53
105
|
/** 是否支持图片输入(视觉模型)。看图工具(view_image)仅对视觉模型启用;generate_image 是否返回图片内容块也据此判断 */
|
|
54
106
|
vision?: boolean;
|
|
55
107
|
description?: string;
|
|
@@ -69,6 +121,13 @@ export interface AgentDefinition {
|
|
|
69
121
|
description?: string;
|
|
70
122
|
messages: AgentNS.Message[];
|
|
71
123
|
modelId?: string;
|
|
124
|
+
/**
|
|
125
|
+
* 该 Agent 的思考档位(可选),覆盖模型 `defaultParams` 里的对应参数。
|
|
126
|
+
*
|
|
127
|
+
* 有了它,同一个模型可以给不同 Agent 配不同强度(编程 Agent `max`、闲聊 Agent `low`),
|
|
128
|
+
* 而模型本身的默认参数保持不变。未声明时沿用模型默认。
|
|
129
|
+
*/
|
|
130
|
+
reasoningEffort?: ReasoningEffort;
|
|
72
131
|
permissions?: AgentPermissions;
|
|
73
132
|
function?: AgentNS.FunctionDefine;
|
|
74
133
|
createdAt: string;
|