Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

938 lines
27 KiB
TypeScript

export const desktopDockPlacements = ["bottom", "left"] as const;
export type DesktopDockPlacement = (typeof desktopDockPlacements)[number];
export const defaultDesktopDockPlacement: DesktopDockPlacement = "bottom";
export const desktopDockIconStyles = ["default", "flat"] as const;
export type DesktopDockIconStyle = (typeof desktopDockIconStyles)[number];
export const defaultDesktopDockIconStyle: DesktopDockIconStyle = "default";
export const desktopMinimizeAnimations = ["scale", "genie", "off"] as const;
export type DesktopMinimizeAnimation =
(typeof desktopMinimizeAnimations)[number];
export const defaultDesktopMinimizeAnimation: DesktopMinimizeAnimation =
"genie";
export const desktopWorkbenchWindowSnappingShortcutPresets = [
"commandArrows",
"commandShiftArrows"
] as const;
export type DesktopWorkbenchWindowSnappingShortcutPreset =
(typeof desktopWorkbenchWindowSnappingShortcutPresets)[number];
export interface DesktopWorkbenchWindowSnapping {
enabled: boolean;
shortcutPreset: DesktopWorkbenchWindowSnappingShortcutPreset;
}
export const defaultDesktopWorkbenchWindowSnapping: DesktopWorkbenchWindowSnapping =
{
enabled: false,
shortcutPreset: "commandArrows"
};
export const desktopBrowserUseConnectionModes = [
"isolated",
"autoConnect"
] as const;
export type DesktopBrowserUseConnectionMode =
(typeof desktopBrowserUseConnectionModes)[number];
export const defaultDesktopBrowserUseConnectionMode: DesktopBrowserUseConnectionMode =
"isolated";
export const desktopAppCatalogChannels = ["production", "staging"] as const;
export type DesktopAppCatalogChannel =
(typeof desktopAppCatalogChannels)[number];
export const defaultDesktopAppCatalogChannel: DesktopAppCatalogChannel =
"production";
export const defaultDesktopShowAppDeveloperSources = false;
export const defaultDesktopAgentCliUpdateCheckEnabled = true;
export function normalizeDesktopAgentCliUpdateCheckEnabled(
value: unknown
): boolean {
return typeof value === "boolean"
? value
: defaultDesktopAgentCliUpdateCheckEnabled;
}
export type DesktopFeatureFlags = Record<string, boolean>;
export const defaultDesktopFeatureFlags: DesktopFeatureFlags = {};
export const desktopWorkspaceUiModes = ["os", "agent"] as const;
export type DesktopWorkspaceUiMode = (typeof desktopWorkspaceUiModes)[number];
export const defaultDesktopWorkspaceUiMode: DesktopWorkspaceUiMode = "os";
export interface DesktopWorkbenchShortcuts {
newAgentConversation: string | null;
newSameTypeWindow: string | null;
/**
* Global screenshot capture binding. Unlike the other bindings, null means
* the built-in default accelerator applies, not "unbound".
*/
captureScreenshot: string | null;
}
/**
* Wire-compatible shortcuts shape: generated client payloads may omit newer
* bindings, so boundaries that normalize accept this partial form.
*/
export type DesktopWorkbenchShortcutsInput = {
[Key in keyof DesktopWorkbenchShortcuts]?: string | null;
};
export const defaultDesktopWorkbenchShortcuts: DesktopWorkbenchShortcuts = {
newAgentConversation: null,
newSameTypeWindow: null,
captureScreenshot: null
};
export const desktopAgentConversationDetailModes = [
"coding",
"general"
] as const;
export type DesktopAgentConversationDetailMode =
(typeof desktopAgentConversationDetailModes)[number];
export const defaultDesktopAgentConversationDetailMode: DesktopAgentConversationDetailMode =
"coding";
export const deletedAgentConversationRetentionDaysOptions = [15, 30] as const;
export type DeletedAgentConversationRetentionDays =
(typeof deletedAgentConversationRetentionDaysOptions)[number];
export const defaultDeletedAgentConversationRetentionDays: DeletedAgentConversationRetentionDays = 30;
export function normalizeDeletedAgentConversationRetentionDays(
value: unknown
): DeletedAgentConversationRetentionDays {
return value === 15 || value === 30
? value
: defaultDeletedAgentConversationRetentionDays;
}
export function readInitialDockPlacementFromLocation(
locationSearch?: string
): DesktopDockPlacement {
if (typeof window === "undefined" && locationSearch === undefined) {
return defaultDesktopDockPlacement;
}
const value = new URLSearchParams(
locationSearch ?? window.location.search
).get("dockPlacement");
return isDesktopDockPlacement(value) ? value : defaultDesktopDockPlacement;
}
export function isDesktopDockPlacement(
value: unknown
): value is DesktopDockPlacement {
return (
typeof value === "string" &&
desktopDockPlacements.includes(value as DesktopDockPlacement)
);
}
export function isDesktopDockIconStyle(
value: unknown
): value is DesktopDockIconStyle {
return (
typeof value === "string" &&
desktopDockIconStyles.includes(value as DesktopDockIconStyle)
);
}
export function isDesktopMinimizeAnimation(
value: unknown
): value is DesktopMinimizeAnimation {
return (
typeof value === "string" &&
desktopMinimizeAnimations.includes(value as DesktopMinimizeAnimation)
);
}
export function isDesktopWorkbenchWindowSnappingShortcutPreset(
value: unknown
): value is DesktopWorkbenchWindowSnappingShortcutPreset {
return (
typeof value === "string" &&
desktopWorkbenchWindowSnappingShortcutPresets.includes(
value as DesktopWorkbenchWindowSnappingShortcutPreset
)
);
}
export function isDesktopBrowserUseConnectionMode(
value: unknown
): value is DesktopBrowserUseConnectionMode {
return (
typeof value === "string" &&
desktopBrowserUseConnectionModes.includes(
value as DesktopBrowserUseConnectionMode
)
);
}
export function isDesktopAppCatalogChannel(
value: unknown
): value is DesktopAppCatalogChannel {
return (
typeof value === "string" &&
desktopAppCatalogChannels.includes(value as DesktopAppCatalogChannel)
);
}
export function isDesktopAgentConversationDetailMode(
value: unknown
): value is DesktopAgentConversationDetailMode {
return (
typeof value === "string" &&
desktopAgentConversationDetailModes.includes(
value as DesktopAgentConversationDetailMode
)
);
}
export function normalizeDesktopAgentConversationDetailMode(
value: unknown
): DesktopAgentConversationDetailMode {
return isDesktopAgentConversationDetailMode(value)
? value
: defaultDesktopAgentConversationDetailMode;
}
export const desktopAgentProviders = [
"claude-code",
"codex",
"tutti-agent",
"cursor",
"nexight",
"openclaw",
"opencode"
] as const;
export type DesktopAgentProvider = (typeof desktopAgentProviders)[number];
export const desktopDefaultAgentProviders = [
"tutti-agent",
"codex",
"claude-code",
"cursor",
"opencode"
] as const;
export type DesktopDefaultAgentProvider =
(typeof desktopDefaultAgentProviders)[number];
export const defaultDesktopAgentProvider: DesktopDefaultAgentProvider =
"tutti-agent";
export interface DesktopAgentComposerDefaults {
codexSaverMode?: boolean;
rtkSaverMode?: boolean;
model?: string;
permissionModeId?: string;
reasoningEffort?: string;
speed?: string;
}
export type DesktopAgentComposerDefaultsByProvider = Partial<
Record<DesktopAgentProvider, DesktopAgentComposerDefaults>
>;
export type DesktopAgentComposerDefaultsByAgentTarget = Record<
string,
DesktopAgentComposerDefaults
>;
// Patch for one agent target's remembered defaults: undefined leaves a field
// untouched; text fields use null (or empty) to clear, while the saver-mode
// boolean is stored only when true.
export interface DesktopAgentComposerDefaultsPatch {
codexSaverMode?: boolean;
rtkSaverMode?: boolean;
model?: string | null;
permissionModeId?: string | null;
reasoningEffort?: string | null;
speed?: string | null;
}
export const desktopAgentComposerDefaultsFields = [
"codexSaverMode",
"rtkSaverMode",
"model",
"permissionModeId",
"reasoningEffort",
"speed"
] as const;
export type DesktopAgentGuiConversationRailCollapsedByProvider = Partial<
Record<DesktopAgentProvider, boolean>
>;
export const desktopAgentSessionLaunchModes = ["local", "worktree"] as const;
export type DesktopAgentSessionLaunchMode =
(typeof desktopAgentSessionLaunchModes)[number];
export type DesktopAgentSessionLaunchModesByProjectSectionKey = Record<
string,
DesktopAgentSessionLaunchMode
>;
export type DesktopAgentSessionLaunchModesByWorkspace = Record<
string,
DesktopAgentSessionLaunchModesByProjectSectionKey
>;
export const defaultDesktopAgentSessionLaunchModesByWorkspace: DesktopAgentSessionLaunchModesByWorkspace =
{};
export const desktopFileDefaultOpeners = [
"appBrowser",
"defaultBrowser",
"fileViewer",
"system"
] as const;
export type DesktopFileDefaultOpener =
(typeof desktopFileDefaultOpeners)[number];
export type DesktopFileDefaultOpenersByExtension = Record<
string,
DesktopFileDefaultOpener
>;
export const defaultDesktopFileDefaultOpenersByExtension: DesktopFileDefaultOpenersByExtension =
{
htm: "appBrowser",
html: "appBrowser",
shtml: "appBrowser",
xhtml: "appBrowser"
};
export const desktopSleepPreventionModes = [
"never",
"whileAgentRunning",
"always"
] as const;
export type DesktopSleepPreventionMode =
(typeof desktopSleepPreventionModes)[number];
export const defaultDesktopSleepPreventionMode: DesktopSleepPreventionMode =
"never";
export const desktopUpdatePolicies = ["off", "prompt", "auto"] as const;
export type DesktopUpdatePolicy = (typeof desktopUpdatePolicies)[number];
export const defaultDesktopUpdatePolicy: DesktopUpdatePolicy = "prompt";
export const desktopUpdateChannels = ["stable", "rc"] as const;
export type DesktopUpdateChannel = (typeof desktopUpdateChannels)[number];
export const defaultDesktopUpdateChannel: DesktopUpdateChannel = "stable";
export function isDesktopSleepPreventionMode(
value: unknown
): value is DesktopSleepPreventionMode {
return (
typeof value === "string" &&
desktopSleepPreventionModes.includes(value as DesktopSleepPreventionMode)
);
}
export function isDesktopUpdatePolicy(
value: unknown
): value is DesktopUpdatePolicy {
return (
typeof value === "string" &&
desktopUpdatePolicies.includes(value as DesktopUpdatePolicy)
);
}
export function isDesktopUpdateChannel(
value: unknown
): value is DesktopUpdateChannel {
return (
typeof value === "string" &&
desktopUpdateChannels.includes(value as DesktopUpdateChannel)
);
}
export function isDesktopAgentProvider(
value: unknown
): value is DesktopAgentProvider {
return (
typeof value === "string" &&
desktopAgentProviders.includes(value as DesktopAgentProvider)
);
}
export function isDesktopDefaultAgentProvider(
value: unknown
): value is DesktopDefaultAgentProvider {
return (
typeof value === "string" &&
desktopDefaultAgentProviders.includes(value as DesktopDefaultAgentProvider)
);
}
export function isDesktopFileDefaultOpener(
value: unknown
): value is DesktopFileDefaultOpener {
return (
typeof value === "string" &&
desktopFileDefaultOpeners.includes(value as DesktopFileDefaultOpener)
);
}
export function normalizeDesktopWorkbenchWindowSnapping(
value: unknown
): DesktopWorkbenchWindowSnapping {
if (!isRecord(value)) {
return { ...defaultDesktopWorkbenchWindowSnapping };
}
return {
enabled: value.enabled === true,
shortcutPreset: isDesktopWorkbenchWindowSnappingShortcutPreset(
value.shortcutPreset
)
? value.shortcutPreset
: defaultDesktopWorkbenchWindowSnapping.shortcutPreset
};
}
export function desktopWorkbenchWindowSnappingEqual(
left: DesktopWorkbenchWindowSnapping | null | undefined,
right: DesktopWorkbenchWindowSnapping | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopWorkbenchWindowSnapping(left);
const normalizedRight = normalizeDesktopWorkbenchWindowSnapping(right);
return (
normalizedLeft.enabled === normalizedRight.enabled &&
normalizedLeft.shortcutPreset === normalizedRight.shortcutPreset
);
}
export function normalizeDesktopShortcutBinding(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim();
return normalized && normalized.length <= 80 ? normalized : null;
}
export function normalizeDesktopFeatureFlags(
value: unknown
): DesktopFeatureFlags {
if (!isRecord(value)) {
return { ...defaultDesktopFeatureFlags };
}
const result: DesktopFeatureFlags = {};
for (const [key, flag] of Object.entries(value)) {
const normalizedKey = key.trim();
if (!normalizedKey || normalizedKey.length > 128) {
continue;
}
result[normalizedKey] = Boolean(flag);
}
return result;
}
export function desktopFeatureFlagsEqual(
left: DesktopFeatureFlags | null | undefined,
right: DesktopFeatureFlags | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopFeatureFlags(left);
const normalizedRight = normalizeDesktopFeatureFlags(right);
const leftKeys = Object.keys(normalizedLeft);
const rightKeys = Object.keys(normalizedRight);
if (leftKeys.length !== rightKeys.length) {
return false;
}
return leftKeys.every((key) => normalizedLeft[key] === normalizedRight[key]);
}
export function normalizeDesktopWorkbenchShortcuts(
value: unknown
): DesktopWorkbenchShortcuts {
if (!isRecord(value)) {
return { ...defaultDesktopWorkbenchShortcuts };
}
return {
newAgentConversation: normalizeDesktopShortcutBinding(
value.newAgentConversation
),
newSameTypeWindow: normalizeDesktopShortcutBinding(value.newSameTypeWindow),
captureScreenshot: normalizeDesktopShortcutBinding(value.captureScreenshot)
};
}
export function desktopWorkbenchShortcutsEqual(
left: DesktopWorkbenchShortcutsInput | null | undefined,
right: DesktopWorkbenchShortcutsInput | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopWorkbenchShortcuts(left);
const normalizedRight = normalizeDesktopWorkbenchShortcuts(right);
return (
normalizedLeft.newAgentConversation ===
normalizedRight.newAgentConversation &&
normalizedLeft.newSameTypeWindow === normalizedRight.newSameTypeWindow &&
normalizedLeft.captureScreenshot === normalizedRight.captureScreenshot
);
}
export function normalizeDesktopShortcutKey(key: string): string | null {
if (
!key ||
key === "Meta" ||
key === "Control" ||
key === "Alt" ||
key === "Shift"
) {
return null;
}
if (key === " ") {
return "Space";
}
if (key.length === 1) {
return key.toUpperCase();
}
return key;
}
export function formatDesktopShortcutBinding(input: {
key: string;
metaKey: boolean;
ctrlKey: boolean;
altKey: boolean;
shiftKey: boolean;
}): string | null {
const key = normalizeDesktopShortcutKey(input.key);
if (!key) {
return null;
}
const modifiers: string[] = [];
if (input.metaKey) {
modifiers.push("Meta");
}
if (input.ctrlKey) {
modifiers.push("Ctrl");
}
if (input.altKey) {
modifiers.push("Alt");
}
if (input.shiftKey) {
modifiers.push("Shift");
}
if (modifiers.length === 0 && !/^F\d{1,2}$/u.test(key)) {
return null;
}
const binding = [...modifiers, key].join("+");
return normalizeDesktopShortcutBinding(binding);
}
export function normalizeDesktopFileExtension(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase().replace(/^\.+/u, "");
return /^[a-z0-9][a-z0-9_-]{0,31}$/u.test(normalized) ? normalized : null;
}
export function normalizeDesktopFileDefaultOpenersByExtension(
value: unknown
): DesktopFileDefaultOpenersByExtension {
if (!isRecord(value)) {
return { ...defaultDesktopFileDefaultOpenersByExtension };
}
const result: DesktopFileDefaultOpenersByExtension = {};
for (const [extension, opener] of Object.entries(value)) {
const normalizedExtension = normalizeDesktopFileExtension(extension);
if (!normalizedExtension || !isDesktopFileDefaultOpener(opener)) {
continue;
}
result[normalizedExtension] = opener;
}
return result;
}
export function desktopFileDefaultOpenersByExtensionEqual(
left: DesktopFileDefaultOpenersByExtension | null | undefined,
right: DesktopFileDefaultOpenersByExtension | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopFileDefaultOpenersByExtension(left);
const normalizedRight = normalizeDesktopFileDefaultOpenersByExtension(right);
const keys = new Set([
...Object.keys(normalizedLeft),
...Object.keys(normalizedRight)
]);
for (const key of keys) {
if (normalizedLeft[key] !== normalizedRight[key]) {
return false;
}
}
return true;
}
export function normalizeDesktopAgentComposerDefaults(
value: unknown
): DesktopAgentComposerDefaults | null {
if (!isRecord(value)) {
return null;
}
const defaults: DesktopAgentComposerDefaults = {};
if (value.codexSaverMode === true) {
defaults.codexSaverMode = true;
}
if (value.rtkSaverMode === true) {
defaults.rtkSaverMode = true;
}
const model = normalizeOptionalText(value.model);
const permissionModeId = normalizeOptionalText(value.permissionModeId);
const reasoningEffort = normalizeOptionalText(value.reasoningEffort);
const speed = normalizeOptionalText(value.speed);
if (model) {
defaults.model = model;
}
if (permissionModeId) {
defaults.permissionModeId = permissionModeId;
}
if (reasoningEffort) {
defaults.reasoningEffort = reasoningEffort;
}
if (speed) {
defaults.speed = speed;
}
return Object.keys(defaults).length > 0 ? defaults : null;
}
export function normalizeDesktopAgentComposerDefaultsByAgentTarget(
value: unknown
): DesktopAgentComposerDefaultsByAgentTarget {
if (!isRecord(value)) {
return {};
}
const defaultsByAgentTarget: DesktopAgentComposerDefaultsByAgentTarget = {};
for (const [agentTargetId, entry] of Object.entries(value)) {
const normalizedAgentTargetId = normalizeOptionalText(agentTargetId);
if (!normalizedAgentTargetId) {
continue;
}
const defaults = normalizeDesktopAgentComposerDefaults(entry);
if (defaults) {
defaultsByAgentTarget[normalizedAgentTargetId] = defaults;
}
}
return defaultsByAgentTarget;
}
export function normalizeDesktopAgentComposerDefaultsByProvider(
value: unknown
): DesktopAgentComposerDefaultsByProvider {
if (!isRecord(value)) {
return {};
}
const defaultsByProvider: DesktopAgentComposerDefaultsByProvider = {};
for (const provider of desktopAgentProviders) {
const defaults = normalizeDesktopAgentComposerDefaults(value[provider]);
if (defaults) {
defaultsByProvider[provider] = defaults;
}
}
return defaultsByProvider;
}
export function normalizeDesktopAgentGuiConversationRailCollapsedByProvider(
value: unknown
): DesktopAgentGuiConversationRailCollapsedByProvider {
if (!isRecord(value)) {
return {};
}
const collapsedByProvider: DesktopAgentGuiConversationRailCollapsedByProvider =
{};
for (const provider of desktopAgentProviders) {
if (typeof value[provider] === "boolean") {
collapsedByProvider[provider] = value[provider];
}
}
return collapsedByProvider;
}
export function isDesktopAgentSessionLaunchMode(
value: unknown
): value is DesktopAgentSessionLaunchMode {
return (
typeof value === "string" &&
desktopAgentSessionLaunchModes.includes(
value as DesktopAgentSessionLaunchMode
)
);
}
export function normalizeDesktopAgentSessionLaunchModesByWorkspace(
value: unknown
): DesktopAgentSessionLaunchModesByWorkspace {
if (!isRecord(value)) {
return {};
}
const result: DesktopAgentSessionLaunchModesByWorkspace = {};
for (const [workspaceId, projectModesValue] of Object.entries(value)) {
const normalizedWorkspaceId = workspaceId.trim();
if (!normalizedWorkspaceId || !isRecord(projectModesValue)) {
continue;
}
const projectModes: DesktopAgentSessionLaunchModesByProjectSectionKey = {};
for (const [projectSectionKey, mode] of Object.entries(projectModesValue)) {
const normalizedProjectSectionKey = projectSectionKey.trim();
if (
normalizedProjectSectionKey &&
isDesktopAgentSessionLaunchMode(mode)
) {
projectModes[normalizedProjectSectionKey] = mode;
}
}
if (Object.keys(projectModes).length > 0) {
result[normalizedWorkspaceId] = projectModes;
}
}
return result;
}
export function mergeDesktopAgentSessionLaunchMode(
current: DesktopAgentSessionLaunchModesByWorkspace | null | undefined,
workspaceId: string,
projectSectionKey: string,
mode: DesktopAgentSessionLaunchMode
): DesktopAgentSessionLaunchModesByWorkspace {
const normalizedCurrent =
normalizeDesktopAgentSessionLaunchModesByWorkspace(current);
const normalizedWorkspaceId = workspaceId.trim();
const normalizedProjectSectionKey = projectSectionKey.trim();
if (!normalizedWorkspaceId || !normalizedProjectSectionKey) {
return normalizedCurrent;
}
return {
...normalizedCurrent,
[normalizedWorkspaceId]: {
...normalizedCurrent[normalizedWorkspaceId],
[normalizedProjectSectionKey]: mode
}
};
}
export function desktopAgentSessionLaunchModesByWorkspaceEqual(
left: DesktopAgentSessionLaunchModesByWorkspace | null | undefined,
right: DesktopAgentSessionLaunchModesByWorkspace | null | undefined
): boolean {
const normalizedLeft =
normalizeDesktopAgentSessionLaunchModesByWorkspace(left);
const normalizedRight =
normalizeDesktopAgentSessionLaunchModesByWorkspace(right);
const workspaceIds = new Set([
...Object.keys(normalizedLeft),
...Object.keys(normalizedRight)
]);
for (const workspaceId of workspaceIds) {
const leftModes = normalizedLeft[workspaceId] ?? {};
const rightModes = normalizedRight[workspaceId] ?? {};
const projectKeys = new Set([
...Object.keys(leftModes),
...Object.keys(rightModes)
]);
for (const projectKey of projectKeys) {
if (leftModes[projectKey] !== rightModes[projectKey]) {
return false;
}
}
}
return true;
}
export function mergeDesktopAgentGuiConversationRailCollapsedByProvider(
current:
| DesktopAgentGuiConversationRailCollapsedByProvider
| null
| undefined,
provider: DesktopAgentProvider,
collapsed: boolean
): DesktopAgentGuiConversationRailCollapsedByProvider {
return {
...normalizeDesktopAgentGuiConversationRailCollapsedByProvider(current),
[provider]: collapsed
};
}
export function desktopAgentGuiConversationRailCollapsedByProviderEqual(
left: DesktopAgentGuiConversationRailCollapsedByProvider | null | undefined,
right: DesktopAgentGuiConversationRailCollapsedByProvider | null | undefined
): boolean {
const normalizedLeft =
normalizeDesktopAgentGuiConversationRailCollapsedByProvider(left);
const normalizedRight =
normalizeDesktopAgentGuiConversationRailCollapsedByProvider(right);
return desktopAgentProviders.every(
(provider) =>
(normalizedLeft[provider] ?? false) ===
(normalizedRight[provider] ?? false)
);
}
export function mergeDesktopAgentComposerDefaultsByProvider(
current: DesktopAgentComposerDefaultsByProvider | null | undefined,
provider: DesktopAgentProvider,
defaults: DesktopAgentComposerDefaults | null | undefined
): DesktopAgentComposerDefaultsByProvider {
const normalizedCurrent =
normalizeDesktopAgentComposerDefaultsByProvider(current);
const normalizedDefaults = normalizeDesktopAgentComposerDefaults(defaults);
if (!normalizedDefaults) {
const { [provider]: _removed, ...remaining } = normalizedCurrent;
return remaining;
}
return {
...normalizedCurrent,
[provider]: normalizedDefaults
};
}
// Merges a patch into the existing entry: only the fields present in the
// patch change, so a partial user switch never clobbers other remembered
// fields. An explicit null (or empty) field clears the remembered value; a
// null patch removes the whole entry.
export function mergeDesktopAgentComposerDefaultsByAgentTarget(
current: DesktopAgentComposerDefaultsByAgentTarget | null | undefined,
agentTargetId: string,
patch: DesktopAgentComposerDefaultsPatch | null | undefined
): DesktopAgentComposerDefaultsByAgentTarget {
const normalizedCurrent =
normalizeDesktopAgentComposerDefaultsByAgentTarget(current);
const normalizedAgentTargetId = agentTargetId.trim();
if (!normalizedAgentTargetId) {
return normalizedCurrent;
}
const { [normalizedAgentTargetId]: existing, ...remaining } =
normalizedCurrent;
if (patch === null || patch === undefined) {
return remaining;
}
const merged: DesktopAgentComposerDefaults = { ...existing };
for (const field of desktopAgentComposerDefaultsFields) {
const value = patch[field];
if (value === undefined) {
continue;
}
if (field === "codexSaverMode" || field === "rtkSaverMode") {
if (value === true) {
merged[field] = true;
} else {
delete merged[field];
}
continue;
}
const normalizedValue = normalizeOptionalText(value);
if (normalizedValue) {
merged[field] = normalizedValue;
} else {
delete merged[field];
}
}
if (Object.keys(merged).length === 0) {
return remaining;
}
return {
...remaining,
[normalizedAgentTargetId]: merged
};
}
export function desktopAgentComposerDefaultsByAgentTargetEqual(
left: DesktopAgentComposerDefaultsByAgentTarget | null | undefined,
right: DesktopAgentComposerDefaultsByAgentTarget | null | undefined
): boolean {
const normalizedLeft =
normalizeDesktopAgentComposerDefaultsByAgentTarget(left);
const normalizedRight =
normalizeDesktopAgentComposerDefaultsByAgentTarget(right);
const agentTargetIds = new Set([
...Object.keys(normalizedLeft),
...Object.keys(normalizedRight)
]);
for (const agentTargetId of agentTargetIds) {
if (
!desktopAgentComposerDefaultsEqual(
normalizedLeft[agentTargetId],
normalizedRight[agentTargetId]
)
) {
return false;
}
}
return true;
}
export function desktopAgentComposerDefaultsByProviderEqual(
left: DesktopAgentComposerDefaultsByProvider | null | undefined,
right: DesktopAgentComposerDefaultsByProvider | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopAgentComposerDefaultsByProvider(left);
const normalizedRight =
normalizeDesktopAgentComposerDefaultsByProvider(right);
return desktopAgentProviders.every((provider) =>
desktopAgentComposerDefaultsEqual(
normalizedLeft[provider],
normalizedRight[provider]
)
);
}
export function desktopAgentComposerDefaultsEqual(
left: DesktopAgentComposerDefaults | null | undefined,
right: DesktopAgentComposerDefaults | null | undefined
): boolean {
const normalizedLeft = normalizeDesktopAgentComposerDefaults(left);
const normalizedRight = normalizeDesktopAgentComposerDefaults(right);
return (
(normalizedLeft?.model ?? null) === (normalizedRight?.model ?? null) &&
(normalizedLeft?.permissionModeId ?? null) ===
(normalizedRight?.permissionModeId ?? null) &&
(normalizedLeft?.reasoningEffort ?? null) ===
(normalizedRight?.reasoningEffort ?? null) &&
(normalizedLeft?.speed ?? null) === (normalizedRight?.speed ?? null)
);
}
function normalizeOptionalText(value: unknown): string | null {
return typeof value === "string" ? value.trim() || null : null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}