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
+43 -4
View File
@@ -86,6 +86,26 @@ test("draft: pick model/approval -> reload restores them -> send creates the ses
await page.getByRole("button", { name: /放行只读/ }).click();
await expect(page.getByRole("button", { name: "审批模式" })).toContainText("放行只读");
// Conversation-time thinking level (backed by the Agent settings): the picker shows the
// seeded default (medium, short name 中); the menu carries a title bar and exactly the five
// short-name rows (no descriptions, no default row); picking 高 writes straight through to
// the Agent config, so the session created on send runs with it and it becomes the Agent's
// new default.
const thinkingBtn = page.getByRole("button", { name: "思考等级" });
await expect(thinkingBtn).toContainText("中");
await thinkingBtn.click();
await expect(page.getByText("思考等级", { exact: true })).toBeVisible(); // menu title bar
await page.getByRole("button", { name: "高", exact: true }).click();
await expect(thinkingBtn).toContainText("高");
await expect
.poll(async () => {
const cfg = await (
await page.request.get(`${BASE}/api/projects/${projectId}/agents/default_agent/config`)
).json();
return cfg.config.model?.thinkingLevel;
})
.toBe("high");
// Reload only after the body is persisted via debounce: both the body and the two selections should restore from the cache.
const draftKey = `penguin.chatDraft.${userId}.${projectId}`;
await expect
@@ -105,6 +125,8 @@ test("draft: pick model/approval -> reload restores them -> send creates the ses
.toBe(true);
await expect(page.getByRole("button", { name: "选择模型" })).toContainText("claude-4-8-mini");
await expect(page.getByRole("button", { name: "审批模式" })).toContainText("放行只读");
// The thinking level is NOT draft state: it restores from the Agent config (written through above), not the cache.
await expect(page.getByRole("button", { name: "思考等级" })).toContainText("高");
// The @ target restores along with the draft; removing it falls back to a normal send (no delegation triggered).
await expect(page.getByText("@agent_helper")).toBeVisible();
await page.getByRole("button", { name: "移除 @ 目标" }).click();
@@ -120,8 +142,24 @@ test("draft: pick model/approval -> reload restores them -> send creates the ses
expect(first.session.provider).toBe("custom");
expect(first.session.approvalMode).toBe("read-only");
// The cache clears as soon as sending succeeds.
await expect.poll(() => page.evaluate((k) => localStorage.getItem(k), draftKey)).toBeNull();
// The written-through thinking level reached the session: its trace's session_meta records
// the level llmConfig was assembled with (per-session fixed), and the input area shows the
// read-only tag next to the locked model.
const replay = await (
await page.request.get(`${BASE}/api/sessions/${firstSessionId}/messages`)
).json();
const meta = replay.messages.find((m) => m.type === "session_meta");
expect(meta?.payload?.thinking_level).toBe("high");
await expect(page.getByTitle("思考等级:高")).toBeVisible();
// On a successful send the cache clears — except the model selection, which carries over as
// the next conversation's default (switch-becomes-default, like the thinking level above).
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), draftKey))
.toContain("claude-4-8-mini");
expect(await page.evaluate((k) => localStorage.getItem(k), draftKey)).not.toContain(
"Draft body must not be lost",
);
// —— Default grouping: the sidebar groups Sessions by Workspace — the session just created
// used the auto temp directory, so it lands in the merged "临时工作区" group. ——
@@ -185,8 +223,9 @@ test("draft: pick model/approval -> reload restores them -> send creates the ses
expect(secondSessionId).not.toBe(firstSessionId);
const second = await (await page.request.get(`${BASE}/api/sessions/${secondSessionId}`)).json();
expect(second.session.agentId).toBe("agent_helper");
// No selection was changed: the model falls back to the project default, and approval mode falls back to allow-all (the previous draft was cleared, so read-only doesn't linger).
expect(second.session.modelId).toBe("claude-4-8");
// The previously picked model carries over as the new default (switch-becomes-default);
// approval mode falls back to allow-all (the rest of the draft was cleared, so read-only doesn't linger).
expect(second.session.modelId).toBe("claude-4-8-mini");
expect(second.session.provider).toBe("custom");
expect(second.session.approvalMode).toBe("allow-all");