mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 03:04:29 -04:00
refactor(subagents): share effective model selection resolver
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { resolveAgentModelPrimary } from "./agent-scope.js";
|
import { resolveAgentConfig, resolveAgentModelPrimary } from "./agent-scope.js";
|
||||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
||||||
import type { ModelCatalogEntry } from "./model-catalog.js";
|
import type { ModelCatalogEntry } from "./model-catalog.js";
|
||||||
import { normalizeGoogleModelId } from "./models-config.providers.js";
|
import { normalizeGoogleModelId } from "./models-config.providers.js";
|
||||||
@@ -316,6 +316,38 @@ export function resolveDefaultModelForAgent(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveSubagentConfiguredModelSelection(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
agentId: string;
|
||||||
|
}): string | undefined {
|
||||||
|
const agentConfig = resolveAgentConfig(params.cfg, params.agentId);
|
||||||
|
return (
|
||||||
|
normalizeModelSelection(agentConfig?.subagents?.model) ??
|
||||||
|
normalizeModelSelection(params.cfg.agents?.defaults?.subagents?.model) ??
|
||||||
|
normalizeModelSelection(agentConfig?.model)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSubagentSpawnModelSelection(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
agentId: string;
|
||||||
|
modelOverride?: unknown;
|
||||||
|
}): string {
|
||||||
|
const runtimeDefault = resolveDefaultModelForAgent({
|
||||||
|
cfg: params.cfg,
|
||||||
|
agentId: params.agentId,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
normalizeModelSelection(params.modelOverride) ??
|
||||||
|
resolveSubagentConfiguredModelSelection({
|
||||||
|
cfg: params.cfg,
|
||||||
|
agentId: params.agentId,
|
||||||
|
}) ??
|
||||||
|
normalizeModelSelection(params.cfg.agents?.defaults?.model?.primary) ??
|
||||||
|
`${runtimeDefault.provider}/${runtimeDefault.model}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildAllowedModelSet(params: {
|
export function buildAllowedModelSet(params: {
|
||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
catalog: ModelCatalogEntry[];
|
catalog: ModelCatalogEntry[];
|
||||||
|
|||||||
@@ -255,6 +255,40 @@ describe("openclaw-tools: subagents (sessions_spawn model + thinking)", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sessions_spawn prefers target agent primary model over global default", async () => {
|
||||||
|
resetSubagentRegistryForTests();
|
||||||
|
callGatewayMock.mockReset();
|
||||||
|
setSessionsSpawnConfigOverride({
|
||||||
|
session: { mainKey: "main", scope: "per-sender" },
|
||||||
|
agents: {
|
||||||
|
defaults: { model: { primary: "minimax/MiniMax-M2.1" } },
|
||||||
|
list: [{ id: "research", model: { primary: "opencode/claude" } }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const calls: GatewayCall[] = [];
|
||||||
|
mockPatchAndSingleAgentRun({ calls, runId: "run-agent-primary-model" });
|
||||||
|
|
||||||
|
const tool = await getSessionsSpawnTool({
|
||||||
|
agentSessionKey: "agent:research:main",
|
||||||
|
agentChannel: "discord",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await tool.execute("call-agent-primary-model", {
|
||||||
|
task: "do thing",
|
||||||
|
});
|
||||||
|
expect(result.details).toMatchObject({
|
||||||
|
status: "accepted",
|
||||||
|
modelApplied: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchCall = calls.find(
|
||||||
|
(call) => call.method === "sessions.patch" && (call.params as { model?: string })?.model,
|
||||||
|
);
|
||||||
|
expect(patchCall?.params).toMatchObject({
|
||||||
|
model: "opencode/claude",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("sessions_spawn fails when model patch is rejected", async () => {
|
it("sessions_spawn fails when model patch is rejected", async () => {
|
||||||
resetSubagentRegistryForTests();
|
resetSubagentRegistryForTests();
|
||||||
callGatewayMock.mockReset();
|
callGatewayMock.mockReset();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.j
|
|||||||
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
|
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||||
import { resolveAgentConfig } from "./agent-scope.js";
|
import { resolveAgentConfig } from "./agent-scope.js";
|
||||||
import { AGENT_LANE_SUBAGENT } from "./lanes.js";
|
import { AGENT_LANE_SUBAGENT } from "./lanes.js";
|
||||||
import { normalizeModelSelection, resolveDefaultModelForAgent } from "./model-selection.js";
|
import { resolveSubagentSpawnModelSelection } from "./model-selection.js";
|
||||||
import { buildSubagentSystemPrompt } from "./subagent-announce.js";
|
import { buildSubagentSystemPrompt } from "./subagent-announce.js";
|
||||||
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
||||||
import { countActiveRunsForSession, registerSubagentRun } from "./subagent-registry.js";
|
import { countActiveRunsForSession, registerSubagentRun } from "./subagent-registry.js";
|
||||||
@@ -149,16 +149,11 @@ export async function spawnSubagentDirect(
|
|||||||
const childDepth = callerDepth + 1;
|
const childDepth = callerDepth + 1;
|
||||||
const spawnedByKey = requesterInternalKey;
|
const spawnedByKey = requesterInternalKey;
|
||||||
const targetAgentConfig = resolveAgentConfig(cfg, targetAgentId);
|
const targetAgentConfig = resolveAgentConfig(cfg, targetAgentId);
|
||||||
const runtimeDefaultModel = resolveDefaultModelForAgent({
|
const resolvedModel = resolveSubagentSpawnModelSelection({
|
||||||
cfg,
|
cfg,
|
||||||
agentId: targetAgentId,
|
agentId: targetAgentId,
|
||||||
|
modelOverride,
|
||||||
});
|
});
|
||||||
const resolvedModel =
|
|
||||||
normalizeModelSelection(modelOverride) ??
|
|
||||||
normalizeModelSelection(targetAgentConfig?.subagents?.model) ??
|
|
||||||
normalizeModelSelection(cfg.agents?.defaults?.subagents?.model) ??
|
|
||||||
normalizeModelSelection(cfg.agents?.defaults?.model?.primary) ??
|
|
||||||
normalizeModelSelection(`${runtimeDefaultModel.provider}/${runtimeDefaultModel.model}`);
|
|
||||||
|
|
||||||
const resolvedThinkingDefaultRaw =
|
const resolvedThinkingDefaultRaw =
|
||||||
readStringParam(targetAgentConfig?.subagents ?? {}, "thinking") ??
|
readStringParam(targetAgentConfig?.subagents ?? {}, "thinking") ??
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { resolveAgentConfig, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||||
import type { ModelCatalogEntry } from "../agents/model-catalog.js";
|
import type { ModelCatalogEntry } from "../agents/model-catalog.js";
|
||||||
import {
|
import {
|
||||||
normalizeModelSelection,
|
|
||||||
resolveAllowedModelRef,
|
resolveAllowedModelRef,
|
||||||
resolveDefaultModelForAgent,
|
resolveDefaultModelForAgent,
|
||||||
|
resolveSubagentConfiguredModelSelection,
|
||||||
} from "../agents/model-selection.js";
|
} from "../agents/model-selection.js";
|
||||||
import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
|
import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
|
||||||
import {
|
import {
|
||||||
@@ -62,15 +62,6 @@ function normalizeExecAsk(raw: string): "off" | "on-miss" | "always" | undefined
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSubagentModelHint(cfg: OpenClawConfig, agentId: string): string | undefined {
|
|
||||||
const agentConfig = resolveAgentConfig(cfg, agentId);
|
|
||||||
return (
|
|
||||||
normalizeModelSelection(agentConfig?.subagents?.model) ??
|
|
||||||
normalizeModelSelection(cfg.agents?.defaults?.subagents?.model) ??
|
|
||||||
normalizeModelSelection(agentConfig?.model)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function applySessionsPatchToStore(params: {
|
export async function applySessionsPatchToStore(params: {
|
||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
store: Record<string, SessionEntry>;
|
store: Record<string, SessionEntry>;
|
||||||
@@ -84,7 +75,7 @@ export async function applySessionsPatchToStore(params: {
|
|||||||
const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
|
const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
|
||||||
const resolvedDefault = resolveDefaultModelForAgent({ cfg, agentId: sessionAgentId });
|
const resolvedDefault = resolveDefaultModelForAgent({ cfg, agentId: sessionAgentId });
|
||||||
const subagentModelHint = isSubagentSessionKey(storeKey)
|
const subagentModelHint = isSubagentSessionKey(storeKey)
|
||||||
? resolveSubagentModelHint(cfg, sessionAgentId)
|
? resolveSubagentConfiguredModelSelection({ cfg, agentId: sessionAgentId })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const existing = store[storeKey];
|
const existing = store[storeKey];
|
||||||
|
|||||||
Reference in New Issue
Block a user