Handoff-style /model switch command and per-turn thinking level (#64)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
816a823b75
commit
1e23452f48
58 files changed
+1064
-336
No files matched your search
@@ -24,7 +24,7 @@ import {
|
||||
userText,
|
||||
} from "../src/omnimessage/index.js";
|
||||
import type { OmniMessage, TokenCounts } from "../src/omnimessage/index.js";
|
||||
import { GenerativeModel, groupHistoryToUniMessages, mapThinkingLevel } from "../src/llm/index.js";
|
||||
import { GenerativeModel, groupHistoryToUniMessages } from "../src/llm/index.js";
|
||||
import { readTrace } from "../src/trace/index.js";
|
||||
import { tracesDir } from "../src/state/paths.js";
|
||||
import { stubProviderKeys } from "./provider-keys.js";
|
||||
@@ -90,7 +90,6 @@ function metaFor(
|
||||
model_context_window: 1000000,
|
||||
system_prompt: "ORIGINAL SYSTEM PROMPT",
|
||||
tools: [],
|
||||
thinking_level: "default",
|
||||
agent_state: "/agent/state",
|
||||
workspace: workspaceDir,
|
||||
...(source !== undefined ? { source } : {}),
|
||||
@@ -176,35 +175,40 @@ describe("agent.resumeSession", () => {
|
||||
).toEqual(["text", "text", "abort"]);
|
||||
});
|
||||
|
||||
it("restores the recorded thinking level; the literal 'default' falls back to the Agent config", async () => {
|
||||
// A resumed subagent session must keep the level it inherited from its parent (recorded
|
||||
// in session_meta, like model/Workspace/system prompt) — never silently re-read this
|
||||
// Agent's own config. The seeded Agent config here pins "medium".
|
||||
it("honors a legacy trace's recorded thinking_level; new meta never re-records it", async () => {
|
||||
// session_meta no longer carries a thinking level (it became a per-turn run parameter),
|
||||
// but OLD traces still have it in their meta JSON: resume must keep honoring it as the
|
||||
// session's default level (a legacy subagent session keeps its inherited level instead of
|
||||
// re-reading this Agent's config). The seeded Agent config here pins "medium".
|
||||
const agent = await createAgent({});
|
||||
expect(agent.state.systemConfig.model?.thinking_level).toBe("medium");
|
||||
const levelOf = (session: unknown): unknown =>
|
||||
(
|
||||
(session as { engine: { deps: { llm: { uniConfig?: { thinking_level?: unknown } } } } })
|
||||
.engine.deps.llm.uniConfig ?? {}
|
||||
).thinking_level;
|
||||
(session as { engine: { deps: { llm: { defaultThinkingLevel?: unknown } } } }).engine.deps.llm
|
||||
.defaultThinkingLevel;
|
||||
|
||||
const recorded = metaFor(SID, workspace);
|
||||
(recorded.payload as { thinking_level: string }).thinking_level = "xhigh";
|
||||
// A legacy trace: inject the retired field loosely into the on-disk meta JSON.
|
||||
(recorded.payload as unknown as Record<string, unknown>).thinking_level = "xhigh";
|
||||
await writeTraceFile(tmpRoot, SID, [recorded, userText("hello")]);
|
||||
const inherited = await agent.resumeSession({ sessionId: SID });
|
||||
expect((inherited.metaMessage.payload as { thinking_level: string }).thinking_level).toBe(
|
||||
"xhigh",
|
||||
);
|
||||
expect(levelOf(inherited)).toBe(mapThinkingLevel("xhigh"));
|
||||
expect(levelOf(inherited)).toBe("xhigh");
|
||||
// The rebuilt meta holds invariants only: the legacy field is honored but never re-recorded.
|
||||
expect(
|
||||
"thinking_level" in (inherited.metaMessage.payload as unknown as Record<string, unknown>),
|
||||
).toBe(false);
|
||||
|
||||
// "default" records "no level": resume falls back to the Agent's current config, as before.
|
||||
// A current trace (no field) — and the legacy literal "default" — fall back to the Agent config.
|
||||
const SID2 = "session-2026-07-06-11-00-00-abcdef02";
|
||||
await writeTraceFile(tmpRoot, SID2, [metaFor(SID2, workspace), userText("hi")]);
|
||||
const fallback = await agent.resumeSession({ sessionId: SID2 });
|
||||
expect((fallback.metaMessage.payload as { thinking_level: string }).thinking_level).toBe(
|
||||
"medium",
|
||||
);
|
||||
expect(levelOf(fallback)).toBe(mapThinkingLevel("medium"));
|
||||
expect(levelOf(fallback)).toBe("medium");
|
||||
|
||||
const SID3 = "session-2026-07-06-12-00-00-abcdef03";
|
||||
const legacyDefault = metaFor(SID3, workspace);
|
||||
(legacyDefault.payload as unknown as Record<string, unknown>).thinking_level = "default";
|
||||
await writeTraceFile(tmpRoot, SID3, [legacyDefault, userText("hi")]);
|
||||
const viaDefault = await agent.resumeSession({ sessionId: SID3 });
|
||||
expect(levelOf(viaDefault)).toBe("medium");
|
||||
});
|
||||
|
||||
it("does not write pairing placeholders to the trace file (resume is side-effect free)", async () => {
|
||||
|
||||
Reference in new issue
Block a user