/** * LLM Redaction Extension * * WHAT: Before each provider request, redacts secrets from the outbound * payload so tokens/keys never leave the machine in a prompt. Uses the * `before_provider_request` hook and returns a modified payload. * * CONFIGURE: secret patterns live in extensions/lib/secrets.ts. * * DISABLE: remove/rename this file from the package `extensions/` folder. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { redact } from "./lib/secrets.ts"; import { audit } from "./lib/audit.ts"; /** Recursively redact secrets in every string within a JSON-like value. */ function redactDeep(value: unknown, counter: { n: number }): unknown { if (typeof value === "string") { const cleaned = redact(value); if (cleaned !== value) counter.n++; return cleaned; } if (Array.isArray(value)) return value.map((v) => redactDeep(v, counter)); if (value && typeof value === "object") { const out: Record = {}; for (const [k, v] of Object.entries(value as Record)) { out[k] = redactDeep(v, counter); } return out; } return value; } export default function llmRedactionExtension(pi: ExtensionAPI) { pi.on("before_provider_request", async (event) => { const payload = event.payload; if (!payload) return undefined; const counter = { n: 0 }; // Only rewrite the message content to avoid disturbing payload metadata. const messages = (payload as { messages?: unknown }).messages; if (messages === undefined) return undefined; const redactedMessages = redactDeep(messages, counter); if (counter.n === 0) return undefined; audit({ kind: "redaction", occurrences: counter.n }); return { ...payload, messages: redactedMessages }; }); }