Files
PaperClipAI/server/src/lib/agent-records.ts
T
Devin FoleyandPaperclip a09ef344b3 perf(agents): avoid duplicate configuration reads (#17612)
Reuse one complete company roster for list responses and narrow other-agent eligibility checks to their five consumed fields. Preserve full selected-agent configuration, terminated-manager evidence, current spend, authorization, and fresh reads.

Verified with 170 affected tests, four rejected regression mutations, build and typecheck, exact-head Greptile 5/5, and green required CI.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 07:38:18 -07:00

485 lines
19 KiB
TypeScript

import { createHash, randomBytes } from "node:crypto";
import { and, eq, gte, inArray, lt, sql } from "drizzle-orm";
import { agents, costEvents, type Db } from "@paperclipai/db";
import {
agentAppearanceSchema, resolveAgentAppearance, agentAvatarUrl,
AGENT_DEFAULT_MAX_CONCURRENT_RUNS, agentRuntimeConfigSchema, getAgentWorkEligibility,
normalizeAgentUrlKey, type AgentEligibilityAgent, type AgentRunnerChoice,
} from "@paperclipai/shared";
import { conflict, notFound, unprocessable } from "../errors.js";
import { REDACTED_EVENT_VALUE, sanitizeRecord } from "../redaction.js";
import { normalizeAgentPermissions } from "./agent-permissions.js";
import { builtInAgentMarkersEqual, readBuiltInAgentMarker } from "./built-in-agent-metadata.js";
export function hashToken(token: string) {
return createHash("sha256").update(token).digest("hex");
}
export function createToken() {
return `pcp_${randomBytes(24).toString("hex")}`;
}
const CONFIG_REVISION_FIELDS = [
"name",
"role",
"title",
"icon",
"appearance",
"reportsTo",
"capabilities",
"adapterType",
"adapterConfig",
"runtimeConfig",
"defaultEnvironmentId",
"budgetMonthlyCents",
"metadata",
] as const;
type ConfigRevisionField = (typeof CONFIG_REVISION_FIELDS)[number];
type AgentConfigSnapshot = Pick<typeof agents.$inferSelect, ConfigRevisionField>;
export interface RevisionMetadata {
createdByAgentId?: string | null;
createdByUserId?: string | null;
source?: string;
rolledBackFromRevisionId?: string | null;
}
/**
* The Claude login context for an agent write. The route derives the owner user
* from the authenticated actor, not from the request body, and forwards the
* non-secret `storedSessionId` claim from a completed Claude login session. A
* controlled internal override permits a migration or an administrator repair to
* bind or unbind the fixed OAuth token without a claim.
*
* The `applyExistingWithoutClaim` field is the user-actor apply-existing path.
* The route sets it only for an authenticated user actor and derives the owner
* from that actor. The path binds the fixed reference to the owner stored value
* with no login round trip. It is distinct from `allowInternalBindingOverride`,
* which does no ownership check.
*
* The `inheritedFromAgentId` field is the hire-inheritance path. The route
* sets it only for an authenticated agent actor whose hire request inherited
* the fixed reference from that named parent. The service re-reads the parent
* agent inside the write transaction and binds the fixed reference only when
* the parent exists, is in the same company, is a `claude_local` agent, and
* already holds the exact fixed binding.
*/
export interface ClaudeLoginContext {
storedSessionId?: string | null;
ownerUserId?: string | null;
allowInternalBindingOverride?: boolean;
applyExistingWithoutClaim?: boolean;
inheritedFromAgentId?: string | null;
}
export interface UpdateAgentOptions {
expectedConfiguration?: Pick<AgentConfigurationRecord, "adapterType" | "adapterConfig" | "runtimeConfig" | "defaultEnvironmentId">;
recordRevision?: RevisionMetadata;
allowBuiltInAgentMetadata?: boolean;
allowPendingApprovalConfigUpdate?: boolean;
claudeLogin?: ClaudeLoginContext;
}
export type CreateAgentData = Omit<typeof agents.$inferInsert, "companyId" | "lifecycleState" | "lifecycleRequiredPluginIds" | "lifecycleHolds" | "lifecycleVersion" | "lifecycleError" | "lifecycleOperation"> & { runner?: AgentRunnerChoice };
export type AgentConfigurationRecord = typeof agents.$inferSelect;
export const AGENT_CONFIGURATION_FIELDS = [...CONFIG_REVISION_FIELDS, "permissions"] as const;
export type AgentConfigurationPatch = Partial<Pick<typeof agents.$inferInsert, typeof AGENT_CONFIGURATION_FIELDS[number]>>;
export type AgentHireRecord = Pick<typeof agents.$inferSelect, "id" | "companyId" | "adapterType" | "adapterConfig" | "permissions" | "metadata" | "status">;
export interface CreateAgentOptions {
setupOnly?: boolean;
setupAgentId?: string;
runnerResolved?: boolean;
responsibleUserId?: string | null;
createdByUserId?: string | null;
aiConnectionInstall?: { connectionId: string; memberConnectionIds?: string[]; createdByUserId: string | null };
allowBuiltInAgentMetadata?: boolean;
claudeLogin?: ClaudeLoginContext;
}
type AgentEligibilityRow = Pick<typeof agents.$inferSelect, "id" | "companyId" | "name" | "status" | "reportsTo">;
interface AgentShortnameRow {
id: string;
name: string;
status: string;
}
interface AgentShortnameCollisionOptions {
excludeAgentId?: string | null;
}
export function isPlainRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function jsonEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
export function buildConfigSnapshot(
row: Pick<typeof agents.$inferSelect, ConfigRevisionField>,
): AgentConfigSnapshot {
const adapterConfig =
typeof row.adapterConfig === "object" && row.adapterConfig !== null && !Array.isArray(row.adapterConfig)
? sanitizeRecord(row.adapterConfig as Record<string, unknown>)
: {};
const runtimeConfig =
typeof row.runtimeConfig === "object" && row.runtimeConfig !== null && !Array.isArray(row.runtimeConfig)
? sanitizeRecord(row.runtimeConfig as Record<string, unknown>)
: {};
const metadata =
typeof row.metadata === "object" && row.metadata !== null && !Array.isArray(row.metadata)
? sanitizeRecord(row.metadata as Record<string, unknown>)
: row.metadata ?? null;
return {
name: row.name,
role: row.role,
title: row.title,
icon: row.icon,
appearance: row.appearance,
reportsTo: row.reportsTo,
capabilities: row.capabilities,
adapterType: row.adapterType,
adapterConfig,
runtimeConfig,
defaultEnvironmentId: row.defaultEnvironmentId,
budgetMonthlyCents: row.budgetMonthlyCents,
metadata,
};
}
export function containsRedactedMarker(value: unknown): boolean {
if (value === REDACTED_EVENT_VALUE) return true;
if (Array.isArray(value)) return value.some((item) => containsRedactedMarker(item));
if (typeof value !== "object" || value === null) return false;
return Object.values(value as Record<string, unknown>).some((entry) => containsRedactedMarker(entry));
}
export function hasConfigPatchFields(data: Partial<typeof agents.$inferInsert>) {
return CONFIG_REVISION_FIELDS.some((field) => Object.prototype.hasOwnProperty.call(data, field));
}
export function changedPendingApprovalConfigFields(
existing: Omit<typeof agents.$inferSelect, "lifecycleOperation" | "lifecycleRequiredPluginIds">,
data: Partial<typeof agents.$inferInsert>,
) {
return CONFIG_REVISION_FIELDS.filter((field) =>
Object.prototype.hasOwnProperty.call(data, field) && !jsonEqual(data[field], existing[field]),
);
}
export function configPatchFromApprovalPayload(payload: Record<string, unknown>) {
const patch: Partial<typeof agents.$inferInsert> = {};
if (typeof payload.name === "string") patch.name = payload.name;
if (typeof payload.role === "string") patch.role = payload.role;
if (payload.appearance != null) patch.appearance = agentAppearanceSchema.parse(payload.appearance);
if (Object.prototype.hasOwnProperty.call(payload, "title")) {
patch.title = typeof payload.title === "string" ? payload.title : null;
}
if (Object.prototype.hasOwnProperty.call(payload, "icon")) {
patch.icon = typeof payload.icon === "string" ? payload.icon : null;
}
if (Object.prototype.hasOwnProperty.call(payload, "reportsTo")) {
patch.reportsTo = typeof payload.reportsTo === "string" ? payload.reportsTo : null;
}
if (Object.prototype.hasOwnProperty.call(payload, "capabilities")) {
patch.capabilities = typeof payload.capabilities === "string" ? payload.capabilities : null;
}
if (typeof payload.adapterType === "string") patch.adapterType = payload.adapterType;
if (isPlainRecord(payload.adapterConfig)) patch.adapterConfig = payload.adapterConfig;
if (isPlainRecord(payload.runtimeConfig)) patch.runtimeConfig = payload.runtimeConfig;
if (Object.prototype.hasOwnProperty.call(payload, "defaultEnvironmentId")) {
patch.defaultEnvironmentId =
typeof payload.defaultEnvironmentId === "string" ? payload.defaultEnvironmentId : null;
}
if (typeof payload.budgetMonthlyCents === "number") {
patch.budgetMonthlyCents = payload.budgetMonthlyCents;
}
if (Object.prototype.hasOwnProperty.call(payload, "metadata")) {
patch.metadata = isPlainRecord(payload.metadata) ? payload.metadata : null;
}
if (isPlainRecord(payload.permissions)) {
patch.permissions = payload.permissions;
}
return patch;
}
function parseFiniteNumberLike(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value !== "string") return null;
const parsed = Number(value.trim());
return Number.isFinite(parsed) ? parsed : null;
}
export function normalizeRuntimeConfigForNewAgent(runtimeConfig: unknown): Record<string, unknown> {
const normalizedRuntimeConfig = isPlainRecord(runtimeConfig) ? { ...runtimeConfig } : {};
const heartbeat = isPlainRecord(normalizedRuntimeConfig.heartbeat)
? { ...normalizedRuntimeConfig.heartbeat }
: {};
if (parseFiniteNumberLike(heartbeat.maxConcurrentRuns) == null) {
heartbeat.maxConcurrentRuns = AGENT_DEFAULT_MAX_CONCURRENT_RUNS;
}
normalizedRuntimeConfig.heartbeat = heartbeat;
return normalizedRuntimeConfig;
}
export function diffConfigSnapshot(
before: AgentConfigSnapshot,
after: AgentConfigSnapshot,
): string[] {
return CONFIG_REVISION_FIELDS.filter((field) => !jsonEqual(before[field], after[field]));
}
export function configPatchFromSnapshot(snapshot: unknown): Partial<typeof agents.$inferInsert> {
if (!isPlainRecord(snapshot)) throw unprocessable("Invalid revision snapshot");
if (typeof snapshot.name !== "string" || snapshot.name.length === 0) {
throw unprocessable("Invalid revision snapshot: name");
}
if (typeof snapshot.role !== "string" || snapshot.role.length === 0) {
throw unprocessable("Invalid revision snapshot: role");
}
if (typeof snapshot.adapterType !== "string" || snapshot.adapterType.length === 0) {
throw unprocessable("Invalid revision snapshot: adapterType");
}
if (typeof snapshot.budgetMonthlyCents !== "number" || !Number.isFinite(snapshot.budgetMonthlyCents)) {
throw unprocessable("Invalid revision snapshot: budgetMonthlyCents");
}
const runtimeConfig = agentRuntimeConfigSchema.safeParse(
isPlainRecord(snapshot.runtimeConfig) ? snapshot.runtimeConfig : {},
);
if (!runtimeConfig.success) {
throw unprocessable("Invalid revision snapshot: runtimeConfig");
}
return {
name: snapshot.name,
role: snapshot.role,
title: typeof snapshot.title === "string" || snapshot.title === null ? snapshot.title : null,
reportsTo:
typeof snapshot.reportsTo === "string" || snapshot.reportsTo === null ? snapshot.reportsTo : null,
capabilities:
typeof snapshot.capabilities === "string" || snapshot.capabilities === null
? snapshot.capabilities
: null,
adapterType: snapshot.adapterType,
adapterConfig: isPlainRecord(snapshot.adapterConfig) ? snapshot.adapterConfig : {},
runtimeConfig: runtimeConfig.data,
defaultEnvironmentId:
typeof snapshot.defaultEnvironmentId === "string" || snapshot.defaultEnvironmentId === null
? snapshot.defaultEnvironmentId
: null,
budgetMonthlyCents: Math.max(0, Math.floor(snapshot.budgetMonthlyCents)),
metadata: isPlainRecord(snapshot.metadata) || snapshot.metadata === null ? snapshot.metadata : null,
};
}
export function hasAgentShortnameCollision(
candidateName: string,
existingAgents: AgentShortnameRow[],
options?: AgentShortnameCollisionOptions,
): boolean {
const candidateShortname = normalizeAgentUrlKey(candidateName);
if (!candidateShortname) return false;
return existingAgents.some((agent) => {
if (agent.status === "terminated") return false;
if (options?.excludeAgentId && agent.id === options.excludeAgentId) return false;
return normalizeAgentUrlKey(agent.name) === candidateShortname;
});
}
export function deduplicateAgentName(
candidateName: string,
existingAgents: AgentShortnameRow[],
): string {
if (!hasAgentShortnameCollision(candidateName, existingAgents)) {
return candidateName;
}
for (let i = 2; i <= 100; i++) {
const suffixed = `${candidateName} ${i}`;
if (!hasAgentShortnameCollision(suffixed, existingAgents)) {
return suffixed;
}
}
return `${candidateName} ${Date.now()}`;
}
export function agentRecordQueries(db: Db) {
function currentUtcMonthWindow(now = new Date()) {
const year = now.getUTCFullYear();
const month = now.getUTCMonth();
return {
start: new Date(Date.UTC(year, month, 1, 0, 0, 0, 0)),
end: new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0)),
};
}
function withUrlKey<T extends { id: string; name: string }>(row: T) {
return {
...row,
urlKey: normalizeAgentUrlKey(row.name) ?? row.id,
};
}
function normalizeAgentBaseRow(row: typeof agents.$inferSelect) {
const { lifecycleOperation, lifecycleRequiredPluginIds, ...publicRow } = row;
return withUrlKey({
...publicRow,
permissions: normalizeAgentPermissions(row.permissions),
});
}
function toEligibilityAgent(row: Pick<typeof agents.$inferSelect, "id" | "companyId" | "name" | "status" | "reportsTo">): AgentEligibilityAgent {
return {
id: row.id,
companyId: row.companyId,
name: row.name,
status: row.status,
reportsTo: row.reportsTo,
};
}
function normalizeAgentRows(rows: (typeof agents.$inferSelect)[], allCompanyRows: AgentEligibilityRow[] = rows) {
const eligibilityAgents = allCompanyRows.map(toEligibilityAgent);
return rows.map((row) => {
const base = normalizeAgentBaseRow(row);
const appearance = resolveAgentAppearance(row.appearance, row.id);
return {
...base,
appearance,
avatarUrl: agentAvatarUrl(appearance),
orgChainHealth: getAgentWorkEligibility({
agent: toEligibilityAgent(row),
agents: eligibilityAgents,
}).orgChainHealth,
};
});
}
function normalizeAgentRow(row: typeof agents.$inferSelect, allCompanyRows?: AgentEligibilityRow[]) {
return normalizeAgentRows([row], allCompanyRows)[0]!;
}
async function listCompanyAgentRows(companyId: string) {
return db.select().from(agents).where(eq(agents.companyId, companyId));
}
async function listCompanyAgentEligibilityRows(companyId: string): Promise<AgentEligibilityRow[]> {
return db.select({ id: agents.id, companyId: agents.companyId, name: agents.name,
status: agents.status, reportsTo: agents.reportsTo }).from(agents).where(eq(agents.companyId, companyId));
}
async function getMonthlySpendByAgentIds(companyId: string, agentIds: string[]) {
if (agentIds.length === 0) return new Map<string, number>();
const { start, end } = currentUtcMonthWindow();
const rows = await db
.select({
agentId: costEvents.agentId,
spentMonthlyCents: sql<number>`coalesce(sum(${costEvents.costCents}), 0)::double precision`,
})
.from(costEvents)
.where(
and(
eq(costEvents.companyId, companyId),
inArray(costEvents.agentId, agentIds),
gte(costEvents.occurredAt, start),
lt(costEvents.occurredAt, end),
),
)
.groupBy(costEvents.agentId);
return new Map(rows.map((row) => [row.agentId, Number(row.spentMonthlyCents ?? 0)]));
}
async function hydrateAgentSpend<T extends { id: string; companyId: string; spentMonthlyCents: number }>(rows: T[]) {
const agentIds = rows.map((row) => row.id);
const companyId = rows[0]?.companyId;
if (!companyId || agentIds.length === 0) return rows;
const spendByAgentId = await getMonthlySpendByAgentIds(companyId, agentIds);
return rows.map((row) => ({
...row,
spentMonthlyCents: spendByAgentId.get(row.id) ?? 0,
}));
}
async function getById(id: string) {
const row = await db
.select()
.from(agents)
.where(eq(agents.id, id))
.then((rows) => rows[0] ?? null);
if (!row) return null;
const [companyRows, hydrated] = await Promise.all([
listCompanyAgentEligibilityRows(row.companyId),
hydrateAgentSpend([row]).then((rows) => rows[0]!),
]);
return normalizeAgentRow(hydrated, companyRows);
}
async function ensureManager(companyId: string, managerId: string) {
const manager = await getById(managerId);
if (!manager) throw notFound("Manager not found");
if (manager.companyId !== companyId) {
throw unprocessable("Manager must belong to same company");
}
return manager;
}
async function assertNoCycle(agentId: string, reportsTo: string | null | undefined) {
if (!reportsTo) return;
if (reportsTo === agentId) throw unprocessable("Agent cannot report to itself");
let cursor: string | null = reportsTo;
while (cursor) {
if (cursor === agentId) throw unprocessable("Reporting relationship would create cycle");
const next = await getById(cursor);
cursor = next?.reportsTo ?? null;
}
}
async function assertCompanyShortnameAvailable(
companyId: string,
candidateName: string,
options?: AgentShortnameCollisionOptions,
) {
const candidateShortname = normalizeAgentUrlKey(candidateName);
if (!candidateShortname) return;
const existingAgents = await db
.select({
id: agents.id,
name: agents.name,
status: agents.status,
})
.from(agents)
.where(eq(agents.companyId, companyId));
const hasCollision = hasAgentShortnameCollision(candidateName, existingAgents, options);
if (hasCollision) {
throw conflict(
`Agent shortname '${candidateShortname}' is already in use in this company`,
);
}
}
function assertBuiltInAgentMetadataMutationAllowed(
beforeMetadata: unknown,
afterMetadata: unknown,
options?: { allowBuiltInAgentMetadata?: boolean },
) {
if (options?.allowBuiltInAgentMetadata) return;
const beforeMarker = readBuiltInAgentMarker(beforeMetadata);
const afterMarker = readBuiltInAgentMarker(afterMetadata);
if (builtInAgentMarkersEqual(beforeMarker, afterMarker)) return;
throw conflict("Built-in agent marker is managed by Paperclip and cannot be edited directly", {
code: "built_in_agent_marker_readonly",
key: beforeMarker?.key ?? afterMarker?.key ?? null,
});
}
return { normalizeAgentRows, normalizeAgentRow, listCompanyAgentRows, listCompanyAgentEligibilityRows, hydrateAgentSpend,
getById, ensureManager, assertNoCycle, assertCompanyShortnameAvailable, assertBuiltInAgentMetadataMutationAllowed };
}