Files
PaperClipAI/server/src/services/agent-runtime-settings.ts
T
DottaandPaperclip 9bf7dc0c8f feat(agents): bulk edit connections and default models (#17215)
Add harness-scoped bulk connection and default-model edits to the agent roster, with compatible choices, change previews, per-agent save results, and retries.

Use an icon-based harness filter, keep agent rows readable, remove the status selector and selection hint, and redirect legacy status URLs to the full roster.

Preserve authorization, audit history, unrelated settings, and optimistic concurrency checks through the existing agent update route.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-22 18:47:06 -05:00

30 lines
2.0 KiB
TypeScript

import { isDeepStrictEqual } from "node:util";
import { agentRuntimeSettingsHarness, type UpdateAgent } from "@paperclipai/shared";
import type { AgentConfigurationRecord } from "../lib/agent-records.js";
import { conflict, unprocessable } from "../errors.js";
/** Translate a scoped edit into the existing validated configuration write. */
export function prepareRuntimeSettingsPatch(existing: Pick<AgentConfigurationRecord, "adapterType" | "adapterConfig" | "runtimeConfig" | "status">, input: UpdateAgent): Record<string, unknown> {
const settings = input.runtimeSettings!;
if (Object.keys(input).some(key => key !== "runtimeSettings")) {
throw unprocessable("Runtime settings cannot be combined with other agent changes");
}
const harness = agentRuntimeSettingsHarness(existing.adapterType, existing.adapterConfig);
if (harness !== settings.harness) throw conflict("This agent's harness changed. Refresh the roster and select it again.");
if (["process", "http"].includes(harness) || ["terminated", "pending_approval"].includes(existing.status)) {
throw unprocessable("This agent cannot be updated from the roster");
}
const model = typeof existing.adapterConfig.model === "string" ? existing.adapterConfig.model.trim() || null : null;
const binding = existing.runtimeConfig.aiConnection ?? null;
if (model !== settings.expected.model || !isDeepStrictEqual(binding, settings.expected.aiConnection)) {
throw conflict("This agent's connection or model changed. Refresh the roster and review the changes again.");
}
if (settings.aiConnection?.mode === "router" || (binding && typeof binding === "object" && "mode" in binding && binding.mode === "router")) {
throw unprocessable("Edit connection pool members on the agent's configuration page");
}
return {
...(settings.model !== undefined ? { adapterConfig: { model: settings.model } } : {}),
...(settings.aiConnection !== undefined ? { runtimeConfig: { ...existing.runtimeConfig, aiConnection: settings.aiConnection } } : {}),
};
}