Files
paseo/packages/app/src/composer/agent-controls/utils.test.ts
T
Mohamed Boudra 0d82eed6b5 feat(app): the client and app work on options and controls
The agent snapshot's `options` are the state and `controls` only draw them.
Running and closed agents draw from the snapshot; drafts and profile forms ask
`provider.controls.list.request` with their `providerOptions`, so aliases such
as a legacy Fast profile's `service_tier: "fast"` are resolved once, by the
daemon, and the draft keeps Fast highlighted. A draft adds only the user's
edits and each provider's last-used control values; applying a profile seeds
its non-control options (e.g. `sandbox_mode`) into the draft, and a profile
form saves them back instead of pruning to the controls it can draw.

A daemon without `server_info.features.providerOptionControls` still sends
`features` and `featureValues`. `LegacyProviderOptions` in the client package
is the one place that translates snapshots, draft listings, create and apply
requests, and daemon-config profiles, so the app never branches on it.
Persisted preferences and tab layouts rename `featureValues` through tagged
COMPAT preprocessors.

The features/controls bridge moves from the server into the protocol package
so the client can reuse it.
2026-11-05 15:30:11 +01:00

254 lines
7.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
getFeatureHighlightColor,
getAgentControlHintKey,
normalizeModelId,
resolveAgentModelSelection,
} from "./utils";
describe("getAgentControlHintKey", () => {
it("returns translation keys for each editable agent control hint", () => {
expect(getAgentControlHintKey("thinking")).toBe("agentControls.hints.thinking");
expect(getAgentControlHintKey("model")).toBe("agentControls.hints.model");
expect(getAgentControlHintKey("mode")).toBe("agentControls.hints.mode");
});
});
describe("feature metadata helpers", () => {
it("maps feature highlight colors by feature id", () => {
expect(getFeatureHighlightColor("fast_mode")).toBe("yellow");
expect(getFeatureHighlightColor("service_tier")).toBe("yellow");
expect(getFeatureHighlightColor("plan_mode")).toBe("blue");
expect(getFeatureHighlightColor("other")).toBe("default");
});
});
describe("normalizeModelId", () => {
it("treats empty values as unset", () => {
expect(normalizeModelId("")).toBeNull();
expect(normalizeModelId(undefined)).toBeNull();
});
it("returns trimmed model ids", () => {
expect(normalizeModelId(" gpt-5.1-codex ")).toBe("gpt-5.1-codex");
expect(normalizeModelId(" default ")).toBe("default");
});
});
describe("resolveAgentModelSelection", () => {
it("resolves a configured model alias to its canonical catalog model", () => {
const selection = resolveAgentModelSelection({
models: [
{
provider: "claude",
id: "claude-fable-5",
aliases: ["claude-fable-5[1m]"],
label: "Fable 5",
thinkingOptions: [{ id: "high", label: "High" }],
defaultThinkingOptionId: "high",
},
],
runtimeModelId: null,
configuredModelId: "claude-fable-5[1m]",
runtimeThinkingOptionId: null,
explicitThinkingOptionId: null,
});
expect(selection.activeModelId).toBe("claude-fable-5");
expect(selection.displayModel).toBe("Fable 5");
expect(selection.selectedThinkingId).toBe("high");
});
it("prefers runtime model over configured model", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
thinkingOptions: [{ id: "low", label: "Low" }],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: "a",
configuredModelId: "b",
runtimeThinkingOptionId: null,
explicitThinkingOptionId: null,
});
expect(selection.activeModelId).toBe("a");
expect(selection.displayModel).toBe("Model A");
expect(selection.selectedThinkingId).toBe("low");
});
it("uses explicit thinking option when provided", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "high", label: "High" },
],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: "a",
configuredModelId: null,
runtimeThinkingOptionId: null,
explicitThinkingOptionId: "high",
});
expect(selection.selectedThinkingId).toBe("high");
expect(selection.displayThinking).toBe("High");
});
it("shows the thinking level the agent runs on when none was chosen", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "openrouter/google/gemini-3.6-flash",
provider: "pi",
label: "Gemini 3.6 Flash",
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium", isDefault: true },
{ id: "high", label: "High" },
],
defaultThinkingOptionId: "medium",
},
],
runtimeModelId: "openrouter/google/gemini-3.6-flash",
configuredModelId: null,
runtimeThinkingOptionId: "high",
explicitThinkingOptionId: null,
});
expect(selection.selectedThinkingId).toBe("high");
expect(selection.displayThinking).toBe("High");
});
it("falls back to the chosen thinking level when the runtime level is not a model option", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "high", label: "High" },
],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: "a",
configuredModelId: null,
runtimeThinkingOptionId: "xhigh",
explicitThinkingOptionId: "high",
});
expect(selection.selectedThinkingId).toBe("high");
});
it("formats raw thinking labels in the selected model display", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "claude",
label: "Model A",
thinkingOptions: [
{ id: "none", label: "none" },
{ id: "xhigh", label: "xhigh" },
],
},
],
runtimeModelId: "a",
configuredModelId: null,
runtimeThinkingOptionId: null,
explicitThinkingOptionId: "xhigh",
});
expect(selection.selectedThinkingId).toBe("xhigh");
expect(selection.displayThinking).toBe("Extra high");
});
it("falls back to the provider default model label instead of Auto", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
isDefault: true,
thinkingOptions: [{ id: "low", label: "Low" }],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: null,
configuredModelId: null,
runtimeThinkingOptionId: null,
explicitThinkingOptionId: null,
});
expect(selection.displayModel).toBe("Model A");
expect(selection.displayThinking).toBe("Low");
});
it("prefers the configured model when runtime model is not in the model list", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "default",
provider: "claude",
label: "Default (Sonnet 4.6)",
isDefault: true,
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
],
},
],
runtimeModelId: "claude-sonnet-4-6-20260101",
configuredModelId: "default",
runtimeThinkingOptionId: null,
explicitThinkingOptionId: null,
});
expect(selection.activeModelId).toBe("default");
expect(selection.displayModel).toBe("Default (Sonnet 4.6)");
expect(selection.selectedThinkingId).toBe("low");
expect(selection.displayThinking).toBe("Low");
});
it("shows the agent's own model when the catalog does not list it", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "gpt-6-sol",
provider: "codex",
label: "GPT-6-Sol",
isDefault: true,
thinkingOptions: [
{ id: "medium", label: "medium" },
{ id: "max", label: "max" },
],
defaultThinkingOptionId: "medium",
},
],
runtimeModelId: "gpt-6.1-sol",
configuredModelId: "gpt-6.1-sol",
runtimeThinkingOptionId: null,
explicitThinkingOptionId: "max",
});
expect(selection.selectedModel).toBeNull();
expect(selection.activeModelId).toBe("gpt-6.1-sol");
expect(selection.displayModel).toBe("gpt-6.1-sol");
expect(selection.displayThinking).toBe("Max");
});
});