Explorar o código

feat!(core): v2.0.0 drop LLMConfig.maxTokens / maxTokensCap fields (C1+C4)

BREAKING CHANGE: LLMConfig.maxTokens 和 LLMConfig.maxTokensCap 字段已删除。
- 每个模型的真实 maxOutput 来自 providers bank 的 InkosModel.maxOutput。
- 老配置文件里的 maxTokens/maxTokensCap 会被 zod strip 静默丢弃(不报错)。
- 删除 createLLMClient 里的 deprecation warning(字段已物理删除)。
- 删除 LLMClient.defaults.maxTokensCap 字段 + per-call cap 逻辑。
- per-call maxTokens(chatCompletion/chatWithTools 的 options.maxTokens)保留。
- Studio /api/v1/project 响应不再带 maxTokens。
fanghanjun hai 6 meses
pai
achega
07fde1a32c

+ 11 - 11
packages/core/src/__tests__/architect.test.ts

@@ -23,7 +23,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -89,7 +89,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -156,7 +156,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -221,7 +221,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -287,7 +287,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -352,7 +352,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -412,7 +412,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -476,7 +476,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -527,7 +527,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -590,7 +590,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -653,7 +653,7 @@ describe("ArchitectAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 5 - 5
packages/core/src/__tests__/chapter-analyzer.test.ts

@@ -28,7 +28,7 @@ describe("ChapterAnalyzerAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -113,7 +113,7 @@ describe("ChapterAnalyzerAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -236,7 +236,7 @@ describe("ChapterAnalyzerAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -325,7 +325,7 @@ describe("ChapterAnalyzerAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -454,7 +454,7 @@ describe("ChapterAnalyzerAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 0 - 1
packages/core/src/__tests__/config-loader.test.ts

@@ -112,7 +112,6 @@ describe("loadProjectConfig local provider auth", () => {
     expect(config.llm.model).toBe("kimi-k2.5");
     expect(config.llm.model).toBe("kimi-k2.5");
     expect(config.llm.apiKey).toBe("sk-moon");
     expect(config.llm.apiKey).toBe("sk-moon");
     expect(config.llm.temperature).toBe(1);
     expect(config.llm.temperature).toBe(1);
-    expect(config.llm.maxTokens).toBe(4096);
   });
   });
 
 
   it("derives provider/baseUrl from the MiniMax preset single source of truth", async () => {
   it("derives provider/baseUrl from the MiniMax preset single source of truth", async () => {

+ 3 - 3
packages/core/src/__tests__/continuity.test.ts

@@ -56,7 +56,7 @@ describe("ContinuityAuditor", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -128,7 +128,7 @@ describe("ContinuityAuditor", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -223,7 +223,7 @@ describe("ContinuityAuditor", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 1 - 1
packages/core/src/__tests__/length-normalizer.test.ts

@@ -18,7 +18,7 @@ const AGENT_CONTEXT = {
     defaults: {
     defaults: {
       temperature: 0.7,
       temperature: 0.7,
       maxTokens: 4096,
       maxTokens: 4096,
-      thinkingBudget: 0, maxTokensCap: null,
+      thinkingBudget: 0,
       extra: {},
       extra: {},
     },
     },
   } as const,
   } as const,

+ 1 - 1
packages/core/src/__tests__/pipeline-agent.test.ts

@@ -25,7 +25,7 @@ describe("agent pipeline tools", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 1 - 1
packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts

@@ -187,7 +187,7 @@ describe("PipelineRunner structured-state memory sync", () => {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
           thinkingBudget: 0,
           thinkingBudget: 0,
-          maxTokensCap: null,
+          
         },
         },
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       model: "test-model",
       model: "test-model",

+ 6 - 11
packages/core/src/__tests__/pipeline-runner.test.ts

@@ -187,7 +187,7 @@ async function createRunnerFixture(
       defaults: {
       defaults: {
         temperature: 0.7,
         temperature: 0.7,
         maxTokens: 4096,
         maxTokens: 4096,
-        thinkingBudget: 0, maxTokensCap: null,
+        thinkingBudget: 0,
       },
       },
     } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
     } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
     model: "test-model",
     model: "test-model",
@@ -240,7 +240,7 @@ describe("PipelineRunner", () => {
           defaults: {
           defaults: {
             temperature: 0.7,
             temperature: 0.7,
             maxTokens: 4096,
             maxTokens: 4096,
-            thinkingBudget: 0, maxTokensCap: null,
+            thinkingBudget: 0,
           },
           },
         } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
         } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
         model: "base-model",
         model: "base-model",
@@ -253,7 +253,6 @@ describe("PipelineRunner", () => {
           apiKey: "base-key",
           apiKey: "base-key",
           model: "base-model",
           model: "base-model",
           temperature: 0.7,
           temperature: 0.7,
-          maxTokens: 4096,
           thinkingBudget: 0,
           thinkingBudget: 0,
           apiFormat: "chat",
           apiFormat: "chat",
           stream: false,
           stream: false,
@@ -317,8 +316,7 @@ describe("PipelineRunner", () => {
         stream: false,
         stream: false,
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
-          maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
         },
         },
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       model: "test-model",
       model: "test-model",
@@ -372,8 +370,7 @@ describe("PipelineRunner", () => {
         stream: false,
         stream: false,
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
-          maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
         },
         },
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       model: "test-model",
       model: "test-model",
@@ -420,8 +417,7 @@ describe("PipelineRunner", () => {
         stream: false,
         stream: false,
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
-          maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
         },
         },
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       model: "test-model",
       model: "test-model",
@@ -531,8 +527,7 @@ describe("PipelineRunner", () => {
         stream: false,
         stream: false,
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
-          maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
         },
         },
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       } as ConstructorParameters<typeof PipelineRunner>[0]["client"],
       model: "test-model",
       model: "test-model",

+ 6 - 86
packages/core/src/__tests__/provider.test.ts

@@ -120,7 +120,7 @@ function makeClient(temperature = 0.7, extra: Partial<LLMClient> = {}): LLMClien
       temperature,
       temperature,
       maxTokens: 512,
       maxTokens: 512,
       thinkingBudget: 0,
       thinkingBudget: 0,
-      maxTokensCap: null,
+      
       extra: {},
       extra: {},
     },
     },
     ...extra,
     ...extra,
@@ -485,22 +485,11 @@ describe("chatCompletion fixed-temperature clamp (thinking models)", () => {
   });
   });
 });
 });
 
 
-// ── 回归测试:per-call maxTokens 不能被 config.maxTokens 误封顶 ─────────────
-//
-// 背景 / bug 成因:LLMConfigSchema.maxTokens 有 zod default 8192,曾经
-// createLLMClient 里 `maxTokensCap: config.maxTokens ?? null` 的实现会让 cap
-// 永远等于 config.maxTokens。architect 的 per-call 16384 会被 Math.min(16384,
-// 8192) 裁到 8192,基础设定输出被截断——这是 CLAUDE.md 禁止的 maxTokens 回归。
-//
-// 修复后 maxTokens 和 maxTokensCap 是两个独立字段:
-//   - maxTokens: agent 没传 per-call 时的 fallback
-//   - maxTokensCap: per-call 的硬上限,默认 null(不封顶)
-//
-// 如果后续有人把两个字段的语义合回去、或者把 cap 默认改成非 null,这组回归
-// 测试会立刻挂掉。
-
-describe("createLLMClient maxTokensCap regression", () => {
-  it("setting config.maxTokens alone leaves defaults.maxTokensCap null (no cap)", async () => {
+// ── 回归测试:per-call maxTokens 不被裁剪(v2.0.0 精简版)─────────────
+// 背景:v2.0.0 删除了 config.maxTokens / maxTokensCap 字段,provider 层不再做 cap,
+//      agent per-call 传的 maxTokens 原样透传到下游。
+describe("createLLMClient per-call maxTokens not capped (v2.0.0)", () => {
+  it("per-call maxTokens 16384 reaches the API as-is", async () => {
     const { createLLMClient } = await import("../llm/provider.js");
     const { createLLMClient } = await import("../llm/provider.js");
     const { LLMConfigSchema } = await import("../models/project.js");
     const { LLMConfigSchema } = await import("../models/project.js");
 
 
@@ -508,51 +497,6 @@ describe("createLLMClient maxTokensCap regression", () => {
       provider: "openai",
       provider: "openai",
       baseUrl: "http://localhost:0",
       baseUrl: "http://localhost:0",
       model: "test-model",
       model: "test-model",
-      maxTokens: 8192, // 用户配了 fallback,但没有显式要求封顶
-    }));
-
-    expect(client.defaults.maxTokens).toBe(8192);
-    expect(client.defaults.maxTokensCap).toBeNull();
-  });
-
-  it("setting config.maxTokensCap flips cap on", async () => {
-    const { createLLMClient } = await import("../llm/provider.js");
-    const { LLMConfigSchema } = await import("../models/project.js");
-
-    const client = createLLMClient(LLMConfigSchema.parse({
-      provider: "openai",
-      baseUrl: "http://localhost:0",
-      model: "test-model",
-      maxTokens: 8192,
-      maxTokensCap: 4096, // 显式要求封顶
-    }));
-
-    expect(client.defaults.maxTokens).toBe(8192);
-    expect(client.defaults.maxTokensCap).toBe(4096);
-  });
-
-  it("defaults (no config keys) leave cap null", async () => {
-    const { createLLMClient } = await import("../llm/provider.js");
-    const { LLMConfigSchema } = await import("../models/project.js");
-
-    const client = createLLMClient(LLMConfigSchema.parse({
-      provider: "openai",
-      baseUrl: "http://localhost:0",
-      model: "test-model",
-    }));
-
-    expect(client.defaults.maxTokensCap).toBeNull();
-  });
-
-  it("per-call maxTokens 16384 reaches the API when config.maxTokens is 8192", async () => {
-    const { createLLMClient } = await import("../llm/provider.js");
-    const { LLMConfigSchema } = await import("../models/project.js");
-
-    const client = createLLMClient(LLMConfigSchema.parse({
-      provider: "openai",
-      baseUrl: "http://localhost:0",
-      model: "test-model",
-      maxTokens: 8192,
     }));
     }));
 
 
     mockStreamSimple.mockReset();
     mockStreamSimple.mockReset();
@@ -563,32 +507,8 @@ describe("createLLMClient maxTokensCap regression", () => {
     ], { maxTokens: 16384 });
     ], { maxTokens: 16384 });
 
 
     const opts = mockStreamSimple.mock.calls[0]?.[2] as Record<string, unknown>;
     const opts = mockStreamSimple.mock.calls[0]?.[2] as Record<string, unknown>;
-    // 16384 必须原样传到下游,不能被 config.maxTokens=8192 裁成 8192
     expect(opts.maxTokens).toBe(16384);
     expect(opts.maxTokens).toBe(16384);
   });
   });
-
-  it("per-call maxTokens is capped when config.maxTokensCap is set explicitly", async () => {
-    const { createLLMClient } = await import("../llm/provider.js");
-    const { LLMConfigSchema } = await import("../models/project.js");
-
-    const client = createLLMClient(LLMConfigSchema.parse({
-      provider: "openai",
-      baseUrl: "http://localhost:0",
-      model: "test-model",
-      maxTokens: 8192,
-      maxTokensCap: 4096, // 用户确实要硬上限
-    }));
-
-    mockStreamSimple.mockReset();
-    mockStreamSimple.mockReturnValue(makeTextStream("ok"));
-
-    await chatCompletion(client, "test-model", [
-      { role: "user", content: "test" },
-    ], { maxTokens: 16384 });
-
-    const opts = mockStreamSimple.mock.calls[0]?.[2] as Record<string, unknown>;
-    expect(opts.maxTokens).toBe(4096);
-  });
 });
 });
 
 
 describe("createLLMClient with providers lookup", () => {
 describe("createLLMClient with providers lookup", () => {

+ 5 - 5
packages/core/src/__tests__/reviser.test.ts

@@ -54,7 +54,7 @@ describe("ReviserAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -107,7 +107,7 @@ describe("ReviserAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -165,7 +165,7 @@ describe("ReviserAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -237,7 +237,7 @@ describe("ReviserAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -369,7 +369,7 @@ describe("ReviserAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 1 - 1
packages/core/src/__tests__/scheduler.test.ts

@@ -11,7 +11,7 @@ function createConfig(): SchedulerConfig {
       defaults: {
       defaults: {
         temperature: 0.7,
         temperature: 0.7,
         maxTokens: 1024,
         maxTokens: 1024,
-        thinkingBudget: 0, maxTokensCap: null,
+        thinkingBudget: 0,
       },
       },
     } as SchedulerConfig["client"],
     } as SchedulerConfig["client"],
     model: "test-model",
     model: "test-model",

+ 0 - 3
packages/core/src/__tests__/state-validator-agent.test.ts

@@ -22,7 +22,6 @@ describe("StateValidatorAgent", () => {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
           thinkingBudget: 0,
           thinkingBudget: 0,
-          maxTokensCap: null,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -65,7 +64,6 @@ describe("StateValidatorAgent", () => {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 8192,
           maxTokens: 8192,
           thinkingBudget: 0,
           thinkingBudget: 0,
-          maxTokensCap: null,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -95,7 +93,6 @@ describe("StateValidatorAgent", () => {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
           thinkingBudget: 0,
           thinkingBudget: 0,
-          maxTokensCap: null,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 8 - 8
packages/core/src/__tests__/writer.test.ts

@@ -101,7 +101,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -289,7 +289,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -438,7 +438,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -587,7 +587,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -712,7 +712,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -833,7 +833,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -957,7 +957,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },
@@ -1100,7 +1100,7 @@ describe("WriterAgent", () => {
         defaults: {
         defaults: {
           temperature: 0.7,
           temperature: 0.7,
           maxTokens: 4096,
           maxTokens: 4096,
-          thinkingBudget: 0, maxTokensCap: null,
+          thinkingBudget: 0,
           extra: {},
           extra: {},
         },
         },
       },
       },

+ 6 - 37
packages/core/src/llm/provider.ts

@@ -18,16 +18,6 @@ import { resolveServicePreset } from "./service-presets.js";
 import { getProvider } from "./providers/index.js";
 import { getProvider } from "./providers/index.js";
 import { lookupModel } from "./providers/lookup.js";
 import { lookupModel } from "./providers/lookup.js";
 
 
-let providersDeprecationWarnedOnce = false;
-function warnOnceAboutMaxTokensDeprecation(): void {
-  if (providersDeprecationWarnedOnce) return;
-  providersDeprecationWarnedOnce = true;
-  console.warn(
-    "[inkos] config.maxTokens / maxTokensCap 已被 providers 接管:" +
-      "各模型的真实 maxOutput / contextWindow 来自 providers/<name>.ts。" +
-      "2.0.0 将彻底删除 LLMConfig.maxTokens / maxTokensCap 字段。",
-  );
-}
 
 
 // === Streaming Monitor Types ===
 // === Streaming Monitor Types ===
 
 
@@ -108,19 +98,9 @@ export interface LLMClient {
     readonly temperature: number;
     readonly temperature: number;
     /**
     /**
      * Per-call fallback: 当 agent 调 chat() 不传 options.maxTokens 时用这个值。
      * Per-call fallback: 当 agent 调 chat() 不传 options.maxTokens 时用这个值。
-     * 不是硬上限——per-call 显式传的值会覆盖它,不会被它限制。
+     * 来自 providers bank 的 modelCard.maxOutput(v2.0.0 起)。
      */
      */
     readonly maxTokens: number;
     readonly maxTokens: number;
-    /**
-     * Per-call 硬上限。null 表示不封顶;非 null 表示给 chat() 的 per-call
-     * maxTokens 加一个 Math.min(perCall, cap) 约束。
-     *
-     * 语义必须跟 defaults.maxTokens 严格分开:maxTokens 是 fallback,
-     * maxTokensCap 是 cap。旧实现把两者用同一个数推导,导致 agent per-call 16384
-     * 被 config.maxTokens=8192 误裁(见 tests/__tests__/provider.test.ts 的
-     * "per-call maxTokens is not capped by config.maxTokens" 回归测试)。
-     */
-    readonly maxTokensCap: number | null;
     readonly thinkingBudget: number;
     readonly thinkingBudget: number;
     readonly extra: Record<string, unknown>;
     readonly extra: Record<string, unknown>;
   };
   };
@@ -154,16 +134,11 @@ export interface ChatWithToolsResult {
 // === Factory ===
 // === Factory ===
 
 
 export function createLLMClient(config: LLMConfig): LLMClient {
 export function createLLMClient(config: LLMConfig): LLMClient {
-  // 提前查一次 modelCard,用于 defaults.maxTokens 推导(下面 piModel 构造再查一次是幂等的)
+  // C1 (v2.0.0):config.maxTokens / maxTokensCap 已删除;defaults.maxTokens 完全从 modelCard 推导。
   const _earlyCard = lookupModel(config.service ?? "custom", config.model);
   const _earlyCard = lookupModel(config.service ?? "custom", config.model);
   const defaults = {
   const defaults = {
     temperature: config.temperature ?? 0.7,
     temperature: config.temperature ?? 0.7,
-    // A 组起:providers 接管 — 命中 modelCard 用其 maxOutput,否则 fallback 到 config.maxTokens / 8192
-    maxTokens: _earlyCard?.maxOutput ?? config.maxTokens ?? 8192,
-    // cap: 只在用户显式配 maxTokensCap 时生效;默认 null = 不封顶 per-call。
-    // **禁止**改成 `config.maxTokens ?? null` —— 那样会让 architect 的 per-call
-    // 16384 被用户 config.maxTokens=8192 自动裁剪,基础设定输出被截断。
-    maxTokensCap: config.maxTokensCap ?? null,
+    maxTokens: _earlyCard?.maxOutput ?? 8192,
     thinkingBudget: config.thinkingBudget ?? 0,
     thinkingBudget: config.thinkingBudget ?? 0,
     extra: config.extra ?? {},
     extra: config.extra ?? {},
   };
   };
@@ -177,11 +152,6 @@ export function createLLMClient(config: LLMConfig): LLMClient {
   const inkosProvider = getProvider(serviceName);
   const inkosProvider = getProvider(serviceName);
   const modelCard = lookupModel(serviceName, config.model);
   const modelCard = lookupModel(serviceName, config.model);
 
 
-  // A 组过渡期:命中 modelCard 时打一次性 warning(表示用户的 maxTokens 配置已被 providers 接管)
-  if (modelCard) {
-    warnOnceAboutMaxTokensDeprecation();
-  }
-
   const piApi = resolvePiApi(serviceName, config.apiFormat, (inkosProvider?.api ?? preset?.api) as PiApi) as PiApi;
   const piApi = resolvePiApi(serviceName, config.apiFormat, (inkosProvider?.api ?? preset?.api) as PiApi) as PiApi;
   const baseUrl = config.baseUrl || inkosProvider?.baseUrl || preset?.baseUrl || "";
   const baseUrl = config.baseUrl || inkosProvider?.baseUrl || preset?.baseUrl || "";
   const extraHeaders = config.headers ?? parseEnvHeaders();
   const extraHeaders = config.headers ?? parseEnvHeaders();
@@ -198,7 +168,7 @@ export function createLLMClient(config: LLMConfig): LLMClient {
     input: modelCard?.abilities?.vision ? ["text", "image"] : ["text"],
     input: modelCard?.abilities?.vision ? ["text", "image"] : ["text"],
     cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
     cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
     contextWindow: modelCard?.contextWindowTokens ?? 128_000,
     contextWindow: modelCard?.contextWindowTokens ?? 128_000,
-    maxTokens: modelCard?.maxOutput ?? config.maxTokens ?? 8192,
+    maxTokens: modelCard?.maxOutput ?? 8192,
     ...(extraHeaders ? { headers: extraHeaders } : {}),
     ...(extraHeaders ? { headers: extraHeaders } : {}),
   };
   };
 
 
@@ -773,14 +743,13 @@ export async function chatCompletion(
     readonly onTextDelta?: (text: string) => void;
     readonly onTextDelta?: (text: string) => void;
   },
   },
 ): Promise<LLMResponse> {
 ): Promise<LLMResponse> {
-  const perCallMax = options?.maxTokens ?? client.defaults.maxTokens;
-  const cap = client.defaults.maxTokensCap;
+  // C1 (v2.0.0):删除 maxTokensCap 机制。per-call 显式传的 maxTokens 永远不被裁剪。
   const resolved = {
   const resolved = {
     temperature: clampTemperatureForModel(
     temperature: clampTemperatureForModel(
       model,
       model,
       options?.temperature ?? client.defaults.temperature,
       options?.temperature ?? client.defaults.temperature,
     ),
     ),
-    maxTokens: cap !== null ? Math.min(perCallMax, cap) : perCallMax,
+    maxTokens: options?.maxTokens ?? client.defaults.maxTokens,
     extra: client.defaults.extra,
     extra: client.defaults.extra,
   };
   };
   const onStreamProgress = options?.onStreamProgress;
   const onStreamProgress = options?.onStreamProgress;

+ 4 - 9
packages/core/src/models/project.ts

@@ -1,15 +1,18 @@
 import { z } from "zod";
 import { z } from "zod";
 
 
+// C1 (v2.0.0 breaking): `maxTokens` 字段已被 providers bank 接管;zod 用 strip mode 静默丢弃老配置里的 `maxTokens`。
 const LLMServiceEntrySchema = z.object({
 const LLMServiceEntrySchema = z.object({
   service: z.string().min(1),
   service: z.string().min(1),
   name: z.string().min(1).optional(),
   name: z.string().min(1).optional(),
   baseUrl: z.string().url().optional(),
   baseUrl: z.string().url().optional(),
   temperature: z.number().min(0).max(2).optional(),
   temperature: z.number().min(0).max(2).optional(),
-  maxTokens: z.number().int().min(1).optional(),
   apiFormat: z.enum(["chat", "responses"]).optional(),
   apiFormat: z.enum(["chat", "responses"]).optional(),
   stream: z.boolean().optional(),
   stream: z.boolean().optional(),
 });
 });
 
 
+// C1 (v2.0.0 breaking): 删除 maxTokens / maxTokensCap 字段。
+// 每个模型的真实 maxOutput 来自 providers/<name>.ts 的 InkosModel.maxOutput;
+// 老配置里写的 maxTokens / maxTokensCap 会被 zod strip 静默丢弃(不报错)。
 export const LLMConfigSchema = z.object({
 export const LLMConfigSchema = z.object({
   provider: z.enum(["anthropic", "openai", "custom"]),
   provider: z.enum(["anthropic", "openai", "custom"]),
   service: z.string().default("custom"),
   service: z.string().default("custom"),
@@ -18,14 +21,6 @@ export const LLMConfigSchema = z.object({
   apiKey: z.string().default(""),
   apiKey: z.string().default(""),
   model: z.string().min(1),
   model: z.string().min(1),
   temperature: z.number().min(0).max(2).default(0.7),
   temperature: z.number().min(0).max(2).default(0.7),
-  maxTokens: z.number().int().min(1).default(8192),
-  // per-call 硬上限。默认 undefined → createLLMClient 里变 null → 不封顶 per-call。
-  // 只有用户确实要限制单次调用最大输出时才显式设。注意:这个字段跟 maxTokens
-  // 语义不同:maxTokens 是"agent 没传 per-call 时的 fallback",maxTokensCap
-  // 是"给 agent per-call 加硬上限"。分开成两个字段是为了避免旧实现下
-  // config.maxTokens 同时被当 fallback 和 cap 的歧义(旧行为会把 architect
-  // per-call 16384 裁到 config.maxTokens=8192,导致基础设定输出被截断)。
-  maxTokensCap: z.number().int().min(1).optional(),
   thinkingBudget: z.number().int().min(0).default(0),
   thinkingBudget: z.number().int().min(0).default(0),
   extra: z.record(z.unknown()).optional(),
   extra: z.record(z.unknown()).optional(),
   headers: z.record(z.string()).optional(),
   headers: z.record(z.string()).optional(),

+ 0 - 1
packages/core/src/pipeline/runner.ts

@@ -400,7 +400,6 @@ export class PipelineRunner {
         apiKey,
         apiKey,
         model: override.model,
         model: override.model,
         temperature: base?.temperature ?? 0.7,
         temperature: base?.temperature ?? 0.7,
-        maxTokens: base?.maxTokens ?? 8192,
         thinkingBudget: base?.thinkingBudget ?? 0,
         thinkingBudget: base?.thinkingBudget ?? 0,
         apiFormat,
         apiFormat,
         stream,
         stream,

+ 0 - 1
packages/studio/src/api/server.ts

@@ -1124,7 +1124,6 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
       baseUrl: currentConfig.llm.baseUrl,
       baseUrl: currentConfig.llm.baseUrl,
       stream: currentConfig.llm.stream,
       stream: currentConfig.llm.stream,
       temperature: currentConfig.llm.temperature,
       temperature: currentConfig.llm.temperature,
-      maxTokens: currentConfig.llm.maxTokens,
     });
     });
   });
   });