mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 15:34:39 +00:00
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.
This commit is contained in:
@@ -20,7 +20,7 @@ import type {
|
||||
} from "./agent-sdk-types.js";
|
||||
import type { ProviderOptions } from "@getpaseo/protocol/agent-types";
|
||||
import type { ManagedAgent } from "./agent-manager.js";
|
||||
import { featuresFromControls } from "./option-controls.js";
|
||||
import { featuresFromControls } from "@getpaseo/protocol/option-controls";
|
||||
import type { JsonValue } from "../json-utils.js";
|
||||
import {
|
||||
isStoredAgentProviderAvailable,
|
||||
|
||||
@@ -9,7 +9,8 @@ import { toStoredAgentRecord } from "./agent-projections.js";
|
||||
import type { ManagedAgent } from "./agent-manager.js";
|
||||
import type { AgentSessionConfig } from "./agent-sdk-types.js";
|
||||
import { AgentOwnerSchema, daemonExecutionKey, type DaemonAgentOwner } from "./agent-owner.js";
|
||||
import { controlsFromFeatures, foldFeatureValues } from "./option-controls.js";
|
||||
import { controlsFromFeatures } from "@getpaseo/protocol/option-controls";
|
||||
import { foldFeatureValues } from "./option-controls.js";
|
||||
|
||||
const SERIALIZABLE_CONFIG_SCHEMA = z
|
||||
.object({
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import type {
|
||||
AgentFeature,
|
||||
AgentOptionControl,
|
||||
AgentOptionSelectControl,
|
||||
ProviderOptions,
|
||||
} from "@getpaseo/protocol/agent-types";
|
||||
import type { ProviderOptions } from "@getpaseo/protocol/agent-types";
|
||||
|
||||
/**
|
||||
* A patch over an agent's own options: top-level keys replace, `null` removes the key so the
|
||||
@@ -207,98 +202,3 @@ export function foldProfileFeatureValues<T extends ProfileOptionFields>(input: {
|
||||
export function withLegacyProfileFeatureValues<T extends ProfileOptionFields>(profile: T): T {
|
||||
return profile.options ? { ...profile, featureValues: { ...profile.options } } : profile;
|
||||
}
|
||||
|
||||
function selectedChoice(control: AgentOptionSelectControl, options: ProviderOptions) {
|
||||
const value = options[control.key];
|
||||
if (typeof value === "string") {
|
||||
const choice = control.choices.find((candidate) => candidate.value === value);
|
||||
if (choice) return choice.value;
|
||||
}
|
||||
return control.defaultValue ?? null;
|
||||
}
|
||||
|
||||
function describe(control: { description?: string; tooltip?: string; icon?: string }): {
|
||||
description?: string;
|
||||
tooltip?: string;
|
||||
icon?: string;
|
||||
} {
|
||||
return {
|
||||
...(control.description !== undefined ? { description: control.description } : {}),
|
||||
...(control.tooltip !== undefined ? { tooltip: control.tooltip } : {}),
|
||||
...(control.icon !== undefined ? { icon: control.icon } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function featureFromControl(control: AgentOptionControl, effective: ProviderOptions): AgentFeature {
|
||||
if (control.type === "toggle") {
|
||||
const value = effective[control.key];
|
||||
return {
|
||||
type: "toggle",
|
||||
id: control.key,
|
||||
label: control.label,
|
||||
...describe(control),
|
||||
value: typeof value === "boolean" ? value : (control.defaultValue ?? false),
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "select",
|
||||
id: control.key,
|
||||
label: control.label,
|
||||
...describe(control),
|
||||
...(control.desktopTrigger ? { desktopTrigger: control.desktopTrigger } : {}),
|
||||
value: selectedChoice(control, effective),
|
||||
options: control.choices.map((choice) => ({
|
||||
id: choice.value,
|
||||
label: choice.label,
|
||||
...(choice.description ? { description: choice.description } : {}),
|
||||
...(choice.value === control.defaultValue ? { isDefault: true } : {}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01.
|
||||
* Apps that predate controls render `features`; build them from controls + effective options.
|
||||
*/
|
||||
export function featuresFromControls(input: {
|
||||
controls: readonly AgentOptionControl[] | undefined;
|
||||
options: ProviderOptions | undefined;
|
||||
}): AgentFeature[] {
|
||||
const { controls, options } = input;
|
||||
const effective = options ?? {};
|
||||
return (controls ?? []).map((control) => featureFromControl(control, effective));
|
||||
}
|
||||
|
||||
/** A recorded feature's value is not carried over: controls hold no state, only `options` do. */
|
||||
function controlFromFeature(feature: AgentFeature): AgentOptionControl {
|
||||
const descriptor = {
|
||||
key: feature.id,
|
||||
label: feature.label,
|
||||
...describe(feature),
|
||||
};
|
||||
if (feature.type === "toggle") {
|
||||
return { type: "toggle", ...descriptor };
|
||||
}
|
||||
const defaultChoice = feature.options.find((option) => option.isDefault);
|
||||
return {
|
||||
type: "select",
|
||||
...descriptor,
|
||||
...(feature.desktopTrigger ? { desktopTrigger: feature.desktopTrigger } : {}),
|
||||
...(defaultChoice ? { defaultValue: defaultChoice.id } : {}),
|
||||
choices: feature.options.map((option) => ({
|
||||
value: option.id,
|
||||
label: option.label,
|
||||
...(option.description ? { description: option.description } : {}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01.
|
||||
* Agent records written before controls stored `features`; their descriptors become controls.
|
||||
*/
|
||||
export function controlsFromFeatures(
|
||||
features: readonly AgentFeature[] | undefined,
|
||||
): AgentOptionControl[] {
|
||||
return (features ?? []).map(controlFromFeature);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { describe, expect, test, vi } from "vitest";
|
||||
import { AgentManager } from "../agent-manager.js";
|
||||
import { toAgentPayload } from "../agent-projections.js";
|
||||
import { AgentStorage } from "../agent-storage.js";
|
||||
import { convertLegacyOptions, featuresFromControls } from "../option-controls.js";
|
||||
import { featuresFromControls } from "@getpaseo/protocol/option-controls";
|
||||
import { convertLegacyOptions } from "../option-controls.js";
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentSession,
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { AgentMode, AgentProvider, AgentSessionConfig } from "../agent-sdk-
|
||||
import type { AgentManager } from "../agent-manager.js";
|
||||
import { ActiveTurnBehaviorSchema, AgentProfileSchema } from "@getpaseo/protocol/messages";
|
||||
import type { DaemonConfigStore } from "../../daemon-config-store.js";
|
||||
import { featuresFromControls } from "../option-controls.js";
|
||||
import { featuresFromControls } from "@getpaseo/protocol/option-controls";
|
||||
import {
|
||||
AgentFeatureSchema,
|
||||
AgentPermissionRequestPayloadSchema,
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
type ProviderSnapshotEntry,
|
||||
} from "../../agent/agent-sdk-types.js";
|
||||
import type { ProviderAvailability } from "../../agent/agent-manager.js";
|
||||
import { featuresFromControls, foldFeatureValues } from "../../agent/option-controls.js";
|
||||
import { featuresFromControls } from "@getpaseo/protocol/option-controls";
|
||||
import { foldFeatureValues } from "../../agent/option-controls.js";
|
||||
import type { ProviderOptions } from "@getpaseo/protocol/agent-types";
|
||||
import { expandTilde } from "../../../utils/path.js";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user