Files
openclaw/src/agents/openclaw-tools.subagents.sessions-spawn.model.test.ts
2026-04-07 07:01:34 +01:00

155 lines
4.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import {
resolveConfiguredSubagentRunTimeoutSeconds,
resolveSubagentModelAndThinkingPlan,
} from "./subagent-spawn-plan.js";
function createConfig(overrides?: Record<string, unknown>): OpenClawConfig {
return {
session: { mainKey: "main", scope: "per-sender" },
...overrides,
} as OpenClawConfig;
}
describe("subagent spawn model + thinking plan", () => {
it("includes explicit model overrides in the initial patch", () => {
const plan = resolveSubagentModelAndThinkingPlan({
cfg: createConfig(),
targetAgentId: "research",
modelOverride: "claude-haiku-4-5",
});
expect(plan).toMatchObject({
status: "ok",
resolvedModel: "claude-haiku-4-5",
modelApplied: true,
initialSessionPatch: {
model: "claude-haiku-4-5",
},
});
});
it("normalizes thinking overrides into the initial patch", () => {
const plan = resolveSubagentModelAndThinkingPlan({
cfg: createConfig(),
targetAgentId: "research",
thinkingOverrideRaw: "high",
});
expect(plan).toMatchObject({
status: "ok",
thinkingOverride: "high",
initialSessionPatch: {
thinkingLevel: "high",
},
});
});
it("rejects invalid thinking levels before any runtime work", () => {
const plan = resolveSubagentModelAndThinkingPlan({
cfg: createConfig(),
targetAgentId: "research",
thinkingOverrideRaw: "banana",
});
expect(plan).toMatchObject({
status: "error",
});
if (plan.status === "error") {
expect(plan.error).toMatch(/Invalid thinking level/i);
}
});
it("applies default subagent model from defaults config", () => {
const plan = resolveSubagentModelAndThinkingPlan({
cfg: createConfig({
agents: { defaults: { subagents: { model: "minimax/MiniMax-M2.7" } } },
}),
targetAgentId: "research",
});
expect(plan).toMatchObject({
status: "ok",
resolvedModel: "minimax/MiniMax-M2.7",
initialSessionPatch: { model: "minimax/MiniMax-M2.7" },
});
});
it("falls back to runtime default model when no model config is set", () => {
const plan = resolveSubagentModelAndThinkingPlan({
cfg: createConfig(),
targetAgentId: "research",
});
expect(plan).toMatchObject({
status: "ok",
resolvedModel: `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`,
initialSessionPatch: { model: `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}` },
});
});
it("prefers per-agent subagent model over defaults", () => {
const cfg = createConfig({
agents: {
defaults: { subagents: { model: "minimax/MiniMax-M2.7" } },
list: [{ id: "research", subagents: { model: "opencode/claude" } }],
},
});
const targetAgentConfig = {
id: "research",
subagents: { model: "opencode/claude" },
};
const plan = resolveSubagentModelAndThinkingPlan({
cfg,
targetAgentId: "research",
targetAgentConfig,
});
expect(plan).toMatchObject({
status: "ok",
resolvedModel: "opencode/claude",
initialSessionPatch: { model: "opencode/claude" },
});
});
it("prefers target agent primary model over global default", () => {
const cfg = createConfig({
agents: {
defaults: { model: { primary: "minimax/MiniMax-M2.7" } },
list: [{ id: "research", model: { primary: "opencode/claude" } }],
},
});
const targetAgentConfig = {
id: "research",
model: { primary: "opencode/claude" },
};
const plan = resolveSubagentModelAndThinkingPlan({
cfg,
targetAgentId: "research",
targetAgentConfig,
});
expect(plan).toMatchObject({
status: "ok",
resolvedModel: "opencode/claude",
initialSessionPatch: { model: "opencode/claude" },
});
});
it("uses config default timeout when agent omits runTimeoutSeconds", () => {
expect(
resolveConfiguredSubagentRunTimeoutSeconds({
cfg: createConfig({
agents: { defaults: { subagents: { runTimeoutSeconds: 120 } } },
}),
}),
).toBe(120);
});
it("explicit runTimeoutSeconds wins over config default", () => {
expect(
resolveConfiguredSubagentRunTimeoutSeconds({
cfg: createConfig({
agents: { defaults: { subagents: { runTimeoutSeconds: 120 } } },
}),
runTimeoutSeconds: 2,
}),
).toBe(2);
});
});