File size: 5,353 Bytes
4c76b0d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /**
* Subagent spawn planning helpers.
*
* Resolves model, thinking, and timeout choices before the sessions_spawn executor launches work.
*/
import { formatThinkingLevels } from "../../../auto-reply/thinking.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import type { FastMode } from "../../../shared/fast-mode.js";
import { splitTrailingAuthProfile } from "../../model-ref-profile.js";
import {
type ModelRef,
resolveDefaultModelForAgent,
resolveSubagentConfiguredModelSelection,
resolveSubagentSpawnModelSelection,
} from "../../model-selection.js";
import { resolveSubagentThinkingOverride } from "./subagent-spawn-thinking.js";
/** Splits a provider/model ref while preserving model-only refs. */
export function splitModelRef(ref?: string) {
if (!ref) {
return { provider: undefined, model: undefined };
}
const trimmed = ref.trim();
if (!trimmed) {
return { provider: undefined, model: undefined };
}
const slash = trimmed.indexOf("/");
if (slash > 0 && slash < trimmed.length - 1) {
const provider = trimmed.slice(0, slash);
const model = trimmed.slice(slash + 1);
return { provider, model };
}
const provider = undefined;
const model = trimmed;
if (model) {
return { provider, model };
}
return { provider: undefined, model: trimmed };
}
/** Resolves the effective subagent run timeout from per-call override or config default. */
export function resolveConfiguredSubagentRunTimeoutSeconds(params: {
cfg: OpenClawConfig;
runTimeoutSeconds?: number;
}) {
const cfgSubagentTimeout =
typeof params.cfg?.agents?.defaults?.subagents?.runTimeoutSeconds === "number" &&
Number.isFinite(params.cfg.agents.defaults.subagents.runTimeoutSeconds)
? Math.max(0, Math.floor(params.cfg.agents.defaults.subagents.runTimeoutSeconds))
: 0;
return typeof params.runTimeoutSeconds === "number" && Number.isFinite(params.runTimeoutSeconds)
? Math.max(0, Math.floor(params.runTimeoutSeconds))
: cfgSubagentTimeout;
}
/** Resolves the subagent model plus thinking patch to apply to the spawned session. */
export function resolveSubagentModelAndThinkingPlan(params: {
cfg: OpenClawConfig;
targetAgentId: string;
requesterAgentConfig?: unknown;
targetAgentConfig?: unknown;
modelOverride?: string;
thinkingOverrideRaw?: string;
callerThinkingRaw?: string;
inheritedModel?: ModelRef;
fastMode?: FastMode;
}) {
const { model: rawResolvedModel, resolvedModel: inheritedModel } =
resolveSubagentSpawnModelSelection({
cfg: params.cfg,
agentId: params.targetAgentId,
modelOverride: params.modelOverride,
inheritedModel: params.inheritedModel,
});
const { model: resolvedModel, profile: authProfileId } =
splitTrailingAuthProfile(rawResolvedModel);
const thinkingPlan = resolveSubagentThinkingOverride({
cfg: params.cfg,
requesterAgentConfig: params.requesterAgentConfig,
targetAgentConfig: params.targetAgentConfig,
thinkingOverrideRaw: params.thinkingOverrideRaw,
callerThinkingRaw: params.callerThinkingRaw,
});
if (thinkingPlan.status === "error") {
const { provider, model } = splitModelRef(resolvedModel);
// The hint is provider/model-specific because valid thinking levels vary by backend.
const hint = formatThinkingLevels(provider, model);
return {
status: "error" as const,
resolvedModel,
error: `Invalid thinking level "${thinkingPlan.thinkingCandidateRaw}". Use one of: ${hint}.`,
};
}
const modelOverrideSource = params.modelOverride?.trim() ? "user" : "auto";
const hasSelectedAutoModel =
modelOverrideSource === "auto" &&
Boolean(
params.inheritedModel ??
resolveSubagentConfiguredModelSelection({
cfg: params.cfg,
agentId: params.targetAgentId,
}),
);
const configuredModelRef = hasSelectedAutoModel ? splitModelRef(resolvedModel) : undefined;
const modelOrigin = configuredModelRef?.model
? {
provider:
configuredModelRef.provider ??
resolveDefaultModelForAgent({
cfg: params.cfg,
agentId: params.targetAgentId,
}).provider,
model: configuredModelRef.model,
}
: undefined;
return {
status: "ok" as const,
resolvedModel,
...(inheritedModel ? { inheritedModel } : {}),
modelApplied: Boolean(resolvedModel),
thinkingOverride: thinkingPlan.thinkingOverride,
initialSessionPatch: {
...(resolvedModel
? {
model: resolvedModel,
modelOverrideSource,
...(modelOrigin
? {
// Selected child models are session overrides, not legacy fallback residue.
// Self-origin metadata keeps cleanup from discarding them before first use.
modelOverrideFallbackOriginProvider: modelOrigin.provider,
modelOverrideFallbackOriginModel: modelOrigin.model,
}
: {}),
}
: {}),
...(authProfileId
? {
authProfileOverride: authProfileId,
authProfileOverrideSource: "user" as const,
}
: {}),
...thinkingPlan.initialSessionPatch,
...(params.fastMode !== undefined ? { fastMode: params.fastMode } : {}),
},
};
}
|