Files
paseo/packages/app/src/composer/agent-controls/utils.ts
T
Mohamed BoudraandGitHub f2054cc701 Preserve Claude model and thinking preferences (#2912)
* fix(agents): preserve Claude thinking selections

Model switches now resolve thinking against the target model's saved preference instead of resetting to the catalog default. Claude owns its configured-model defaults and legacy Fable alias handling, while persisted permission modes remain unchanged.

* chore(claude): tag Fable compatibility shim

* fix(app): resolve persisted model aliases

* fix(models): preserve aliases for older clients

* fix(models): hide compatibility aliases from listings

* fix(settings): hide compatibility model aliases

* test(claude): cover legacy catalog entry

* test(app): expect mock thinking controls in schedules

* fix(app): preserve queued thinking preferences

* fix(app): reconcile schedule provider snapshots

* fix(app): preserve schedule model selections

* fix(claude): preserve exact configured model ids

* fix(app): preserve committed thinking preferences

* fix(app): preserve aliased schedule thinking
2026-08-06 11:33:18 +02:00

183 lines
5.7 KiB
TypeScript

import type { AgentFeature, AgentModelDefinition } from "@getpaseo/protocol/agent-types";
import { i18n } from "@/i18n/i18next";
import { formatThinkingOptionLabel } from "@/agent-controls/labels";
import { FAST_MODE_FEATURE_ID, PLAN_MODE_FEATURE_ID } from "@/agent-controls/policy";
export type ExplainedAgentControl = "mode" | "model" | "thinking";
export type FeatureHighlightColor = "blue" | "default" | "green" | "yellow";
export type AgentControlHintKey =
| "agentControls.hints.thinking"
| "agentControls.hints.model"
| "agentControls.hints.mode";
export function getAgentControlHintKey(selector: ExplainedAgentControl): AgentControlHintKey {
switch (selector) {
case "thinking":
return "agentControls.hints.thinking";
case "model":
return "agentControls.hints.model";
case "mode":
return "agentControls.hints.mode";
default:
throw new Error("unreachable");
}
}
export function normalizeModelId(modelId: string | null | undefined): string | null {
const normalized = typeof modelId === "string" ? modelId.trim() : "";
if (!normalized) {
return null;
}
return normalized;
}
export function getFeatureTooltip(feature: Pick<AgentFeature, "label" | "tooltip">): string {
return feature.tooltip ?? feature.label;
}
export function getFeatureHighlightColor(featureId: string): FeatureHighlightColor {
switch (featureId) {
case FAST_MODE_FEATURE_ID:
return "yellow";
case "auto_accept":
return "green";
case PLAN_MODE_FEATURE_ID:
return "blue";
default:
return "default";
}
}
function findModelById(
models: AgentModelDefinition[] | null,
modelId: string | null,
): AgentModelDefinition | null {
if (!models || !modelId) {
return null;
}
return (
models.find((model) => model.id === modelId) ??
models.find((model) => model.aliases?.includes(modelId)) ??
null
);
}
function getFallbackModel(models: AgentModelDefinition[] | null): AgentModelDefinition | null {
return models?.find((model) => model.isDefault) ?? models?.[0] ?? null;
}
function resolvePreferredModelId(
runtimeSelectedModel: AgentModelDefinition | null,
normalizedConfiguredModelId: string | null,
normalizedRuntimeModelId: string | null,
): string | null {
return runtimeSelectedModel?.id ?? normalizedConfiguredModelId ?? normalizedRuntimeModelId;
}
function pickSelectedModel(
models: AgentModelDefinition[] | null,
preferredModelId: string | null,
fallbackModel: AgentModelDefinition | null,
): AgentModelDefinition | null {
if (!models || !preferredModelId) {
return fallbackModel;
}
return findModelById(models, preferredModelId) ?? fallbackModel;
}
function resolveThinkingId(
explicitThinkingOptionId: string | null | undefined,
selectedModel: AgentModelDefinition | null,
): string | null {
if (explicitThinkingOptionId && explicitThinkingOptionId !== "default") {
return explicitThinkingOptionId;
}
return selectedModel?.defaultThinkingOptionId ?? null;
}
type ThinkingOption = NonNullable<AgentModelDefinition["thinkingOptions"]>[number];
function resolveEffectiveThinking(
thinkingOptions: ThinkingOption[] | null,
resolvedThinkingId: string | null,
): ThinkingOption | null {
const selectedThinking =
thinkingOptions?.find((option) => option.id === resolvedThinkingId) ?? null;
return selectedThinking ?? thinkingOptions?.[0] ?? null;
}
function resolveModelDisplay(
selectedModel: AgentModelDefinition | null,
preferredModelId: string | null,
fallbackModel: AgentModelDefinition | null,
unknownModelLabel: string,
): { activeModelId: string | null; displayModel: string } {
return {
activeModelId: selectedModel?.id ?? preferredModelId ?? null,
displayModel:
selectedModel?.label ?? preferredModelId ?? fallbackModel?.label ?? unknownModelLabel,
};
}
function resolveThinkingDisplay(
effectiveThinking: ThinkingOption | null,
selectedThinkingId: string | null,
unknownThinkingLabel: string,
): string {
if (effectiveThinking) {
return formatThinkingOptionLabel(effectiveThinking);
}
if (selectedThinkingId) {
return formatThinkingOptionLabel({ id: selectedThinkingId });
}
return unknownThinkingLabel;
}
export function resolveAgentModelSelection(input: {
models: AgentModelDefinition[] | null;
runtimeModelId: string | null | undefined;
configuredModelId: string | null | undefined;
explicitThinkingOptionId: string | null | undefined;
}) {
const { models, runtimeModelId, configuredModelId, explicitThinkingOptionId } = input;
const normalizedRuntimeModelId = normalizeModelId(runtimeModelId);
const normalizedConfiguredModelId = normalizeModelId(configuredModelId);
const runtimeSelectedModel = findModelById(models, normalizedRuntimeModelId);
const preferredModelId = resolvePreferredModelId(
runtimeSelectedModel,
normalizedConfiguredModelId,
normalizedRuntimeModelId,
);
const fallbackModel = getFallbackModel(models);
const selectedModel = pickSelectedModel(models, preferredModelId, fallbackModel);
const { activeModelId, displayModel } = resolveModelDisplay(
selectedModel,
preferredModelId,
fallbackModel,
i18n.t("agentControls.model.unknown"),
);
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
const resolvedThinkingId = resolveThinkingId(explicitThinkingOptionId, selectedModel);
const effectiveThinking = resolveEffectiveThinking(thinkingOptions, resolvedThinkingId);
const selectedThinkingId = effectiveThinking?.id ?? null;
const displayThinking = resolveThinkingDisplay(
effectiveThinking,
selectedThinkingId,
i18n.t("agentControls.thinking.unknown"),
);
return {
selectedModel,
activeModelId,
displayModel,
thinkingOptions,
selectedThinkingId,
displayThinking,
};
}