mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-26 06:01:13 +00:00
* feat(agents): derive a provider-declared default utility model when unset When agents.defaults.utilityModel is not set, utility tasks (titles, progress narration) now use the primary provider's declared small model (modelCatalog.providers.<id>.defaultUtilityModel: OpenAI -> gpt-5.6-luna, Anthropic -> claude-haiku-4-5). Auth is inherent because derivation follows the agent's primary provider. Setting utilityModel to an empty string disables utility routing entirely; narration turns on automatically when a default resolves and stays off otherwise. Formatting/lint/tests verified on Testbox (oxfmt, oxlint, plugins:inventory, docs:map, import-cycles, check:test-types, 4 test files). * fix(ai): drop the temperature parameter for models that reject it The GPT-5.6 family 400s on temperature via the Responses API (live-verified: gpt-5.6-luna/-terra reject it; gpt-5.5 and gpt-5.4-mini/nano accept it). supportsOpenAITemperature gates all three OpenAI payload builders, with a catalog compat override (compat.supportsTemperature) declared for the 5.6 family in the openai manifest. Without this, utility tasks (titles, narration) would fail once gpt-5.6-luna becomes the derived default. Gates on Testbox: oxfmt, oxlint, plugins:inventory, import-cycles, check:test-types, 6 test files. Live proof on Testbox: gpt-5.6-luna and claude-haiku-4-5 one-shot completions succeed with temperature requested. * fix(agents): carry the primary model's auth profile onto the derived utility default A primary like openai/gpt-5.5@work previously reached utility tasks via the profiled fallback; the derived default shares the provider, so its trailing auth profile carries over instead of silently switching to default credentials (Codex review). Gates on Testbox: oxfmt, check:test-types, tests. * docs: realign the manifest provider-fields table after adding defaultUtilityModel
152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
// Utility-model resolution tests cover explicit/disabled/auto settings and
|
|
// provider-declared default derivation.
|
|
import { describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
|
import {
|
|
readUtilityModelSetting,
|
|
resolveProviderDefaultUtilityModelRef,
|
|
resolveUtilityModelRefForAgent,
|
|
} from "./utility-model.js";
|
|
|
|
function snapshotWithDefaults(defaults: Record<string, string>): PluginMetadataSnapshot {
|
|
const plugins = Object.entries(defaults).map(([provider, defaultUtilityModel], index) => ({
|
|
id: `plugin-${index}`,
|
|
modelCatalog: {
|
|
providers: {
|
|
[provider]: { defaultUtilityModel, models: [{ id: defaultUtilityModel }] },
|
|
},
|
|
},
|
|
}));
|
|
return { plugins } as unknown as PluginMetadataSnapshot;
|
|
}
|
|
|
|
describe("readUtilityModelSetting", () => {
|
|
it("distinguishes unset, explicit, and empty-string disable", () => {
|
|
expect(readUtilityModelSetting({} as OpenClawConfig, "main")).toEqual({ kind: "auto" });
|
|
expect(
|
|
readUtilityModelSetting(
|
|
{ agents: { defaults: { utilityModel: " openai/gpt-5.4-mini " } } } as OpenClawConfig,
|
|
"main",
|
|
),
|
|
).toEqual({ kind: "explicit", modelRef: "openai/gpt-5.4-mini" });
|
|
expect(
|
|
readUtilityModelSetting(
|
|
{ agents: { defaults: { utilityModel: "" } } } as OpenClawConfig,
|
|
"main",
|
|
),
|
|
).toEqual({ kind: "disabled" });
|
|
expect(
|
|
readUtilityModelSetting(
|
|
{ agents: { defaults: { utilityModel: " " } } } as OpenClawConfig,
|
|
"main",
|
|
),
|
|
).toEqual({ kind: "disabled" });
|
|
});
|
|
|
|
it("lets an agent-level empty string disable a defaults-level model", () => {
|
|
const cfg = {
|
|
agents: {
|
|
defaults: { utilityModel: "openai/gpt-5.4-mini" },
|
|
list: [{ id: "ops", utilityModel: "" }],
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
expect(readUtilityModelSetting(cfg, "ops")).toEqual({ kind: "disabled" });
|
|
expect(readUtilityModelSetting(cfg, "main")).toEqual({
|
|
kind: "explicit",
|
|
modelRef: "openai/gpt-5.4-mini",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("resolveProviderDefaultUtilityModelRef", () => {
|
|
it("reads the provider-declared default from the metadata snapshot", () => {
|
|
const metadataSnapshot = snapshotWithDefaults({
|
|
openai: "gpt-5.6-luna",
|
|
anthropic: "claude-haiku-4-5",
|
|
});
|
|
|
|
expect(
|
|
resolveProviderDefaultUtilityModelRef({
|
|
cfg: {} as OpenClawConfig,
|
|
provider: "Anthropic",
|
|
metadataSnapshot,
|
|
}),
|
|
).toBe("anthropic/claude-haiku-4-5");
|
|
expect(
|
|
resolveProviderDefaultUtilityModelRef({
|
|
cfg: {} as OpenClawConfig,
|
|
provider: "ollama",
|
|
metadataSnapshot,
|
|
}),
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("resolveUtilityModelRefForAgent", () => {
|
|
const metadataSnapshot = snapshotWithDefaults({
|
|
openai: "gpt-5.6-luna",
|
|
anthropic: "claude-haiku-4-5",
|
|
});
|
|
|
|
it("passes explicit config through untouched", () => {
|
|
const cfg = {
|
|
agents: { defaults: { utilityModel: "openrouter/mistralai/mistral-small" } },
|
|
} as OpenClawConfig;
|
|
|
|
expect(resolveUtilityModelRefForAgent({ cfg, agentId: "main", metadataSnapshot })).toBe(
|
|
"openrouter/mistralai/mistral-small",
|
|
);
|
|
});
|
|
|
|
it("returns undefined when utility routing is disabled", () => {
|
|
const cfg = { agents: { defaults: { utilityModel: "" } } } as OpenClawConfig;
|
|
|
|
expect(
|
|
resolveUtilityModelRefForAgent({ cfg, agentId: "main", metadataSnapshot }),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("derives the provider default from the agent's primary model", () => {
|
|
const cfg = {
|
|
agents: { defaults: { model: "anthropic/claude-fable-5" } },
|
|
} as OpenClawConfig;
|
|
|
|
expect(resolveUtilityModelRefForAgent({ cfg, agentId: "main", metadataSnapshot })).toBe(
|
|
"anthropic/claude-haiku-4-5",
|
|
);
|
|
});
|
|
|
|
it("carries the primary model's auth profile onto the derived default", () => {
|
|
const cfg = {
|
|
agents: { defaults: { model: "openai/gpt-5.5@work" } },
|
|
} as OpenClawConfig;
|
|
|
|
expect(resolveUtilityModelRefForAgent({ cfg, agentId: "main", metadataSnapshot })).toBe(
|
|
"openai/gpt-5.6-luna@work",
|
|
);
|
|
});
|
|
|
|
it("prefers a caller-resolved primary provider over re-derivation", () => {
|
|
expect(
|
|
resolveUtilityModelRefForAgent({
|
|
cfg: {} as OpenClawConfig,
|
|
agentId: "main",
|
|
primaryProvider: "openai",
|
|
metadataSnapshot,
|
|
}),
|
|
).toBe("openai/gpt-5.6-luna");
|
|
});
|
|
|
|
it("returns undefined for providers without a declared default", () => {
|
|
const cfg = {
|
|
agents: { defaults: { model: "ollama/llama-4-70b" } },
|
|
} as OpenClawConfig;
|
|
|
|
expect(
|
|
resolveUtilityModelRefForAgent({ cfg, agentId: "main", metadataSnapshot }),
|
|
).toBeUndefined();
|
|
});
|
|
});
|