mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 08:54:42 +00:00
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.
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
import type { 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,
|
|
SPEED_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 getFeatureHighlightColor(featureId: string): FeatureHighlightColor {
|
|
switch (featureId) {
|
|
case FAST_MODE_FEATURE_ID:
|
|
case SPEED_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 (!preferredModelId) {
|
|
return fallbackModel;
|
|
}
|
|
return findModelById(models, preferredModelId);
|
|
}
|
|
|
|
function resolveThinkingId(
|
|
runtimeThinkingOptionId: string | null | undefined,
|
|
explicitThinkingOptionId: string | null | undefined,
|
|
selectedModel: AgentModelDefinition | null,
|
|
): string | null {
|
|
const runtimeThinkingOption = selectedModel?.thinkingOptions?.find(
|
|
(option) => option.id === runtimeThinkingOptionId,
|
|
);
|
|
if (runtimeThinkingOption) {
|
|
return runtimeThinkingOption.id;
|
|
}
|
|
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;
|
|
runtimeThinkingOptionId: string | null | undefined;
|
|
explicitThinkingOptionId: string | null | undefined;
|
|
}) {
|
|
const {
|
|
models,
|
|
runtimeModelId,
|
|
configuredModelId,
|
|
runtimeThinkingOptionId,
|
|
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(
|
|
runtimeThinkingOptionId,
|
|
explicitThinkingOptionId,
|
|
selectedModel,
|
|
);
|
|
const effectiveThinking = resolveEffectiveThinking(thinkingOptions, resolvedThinkingId);
|
|
const selectedThinkingId = effectiveThinking?.id ?? resolvedThinkingId;
|
|
const displayThinking = resolveThinkingDisplay(
|
|
effectiveThinking,
|
|
selectedThinkingId,
|
|
i18n.t("agentControls.thinking.unknown"),
|
|
);
|
|
|
|
return {
|
|
selectedModel,
|
|
activeModelId,
|
|
displayModel,
|
|
thinkingOptions,
|
|
selectedThinkingId,
|
|
displayThinking,
|
|
};
|
|
}
|