feat: per-model max output tokens and a conversation-time thinking level backed by agent settings (#28)

Co-authored-by: Alice <alice@prismshadow.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-22 22:22:41 +08:00
committed by GitHub
parent 99f391f379
commit cabbab1c16
36 changed files with 797 additions and 42 deletions
+70
View File
@@ -229,6 +229,76 @@ describe("penguin config model add/list (--root plus provider / model_id stored
expect(by("mylab", "special-1").client_type).toBe("verbatim-type");
});
it("--max-tokens round-trips to the entry's max_tokens; 0/negative/non-number are rejected before anything is written", async () => {
const add = await runModel([
"add",
"--model-id",
"local-32k",
"--provider",
"custom",
"--base-url",
"http://127.0.0.1:8000/v1",
"--max-tokens",
"8000",
"--root",
tmpRoot,
]);
expect(add.code).toBe(0);
const entryOf = async () => {
const parsed = parseToml(
await fs.readFile(projectConfigPath(tmpRoot, DEFAULT_PROJECT_ID), "utf8"),
) as { models: Array<Record<string, unknown>> };
return parsed.models.find((m) => m.provider === "custom" && m.model_id === "local-32k");
};
expect((await entryOf())?.max_tokens).toBe(8000);
// Upsert without --max-tokens keeps the existing annotation (same merge policy as context_window).
const update = await runModel([
"add",
"--model-id",
"local-32k",
"--provider",
"custom",
"--context-window",
"32768",
"--root",
tmpRoot,
]);
expect(update.code).toBe(0);
expect((await entryOf())?.max_tokens).toBe(8000);
// 0 / negative: rejected with a clear error, and the config is untouched.
for (const bad of ["0", "-5"]) {
const res = await runModel([
"add",
"--model-id",
"local-32k",
"--provider",
"custom",
"--max-tokens",
bad,
"--root",
tmpRoot,
]);
expect(res.code).toBe(1);
expect(res.err).toContain("--max-tokens must be a positive integer");
}
// Non-number: parseIntArg throws a commander usage error (nonzero exit).
const nan = await runModel([
"add",
"--model-id",
"local-32k",
"--provider",
"custom",
"--max-tokens",
"many",
"--root",
tmpRoot,
]);
expect(nan.code).not.toBe(0);
expect((await entryOf())?.max_tokens).toBe(8000);
});
it("model default sets the default model under the --root data root (--model-id upstream id + --provider as a pair)", async () => {
const set = await runModel([
"default",