File size: 3,118 Bytes
6111b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | import { createHash } from "node:crypto";
import type { InterceptedRequest } from "./types";
/**
* Extract the system prompt string from an intercepted LLM request body.
* Supports OpenAI/Anthropic chat (messages[0] role=system),
* Anthropic messages API (top-level `system` field),
* and Gemini (systemInstruction.parts[].text).
*
* @returns Concatenated system prompt string, or null if not found.
*/
export function extractSystemPrompt(req: InterceptedRequest): string | null {
if (!req.requestBody) return null;
let parsed: unknown;
try {
parsed = JSON.parse(req.requestBody);
} catch {
return null;
}
if (!parsed || typeof parsed !== "object") return null;
const body = parsed as Record<string, unknown>;
// 1. Anthropic messages API — top-level `system` field (string or array)
if (typeof body.system === "string" && body.system.length > 0) {
return body.system;
}
if (Array.isArray(body.system)) {
const parts = body.system
.map((p: unknown) => {
if (typeof p === "object" && p !== null && "text" in p) {
return String((p as Record<string, unknown>).text);
}
return null;
})
.filter(Boolean);
if (parts.length > 0) return parts.join("\n");
}
// 2. OpenAI/Anthropic chat — messages[0] with role=system
if (Array.isArray(body.messages)) {
const first = body.messages[0];
if (
first &&
typeof first === "object" &&
"role" in first &&
(first as Record<string, unknown>).role === "system"
) {
const content = (first as Record<string, unknown>).content;
if (typeof content === "string") return content;
if (Array.isArray(content)) {
const texts = content
.map((c: unknown) => {
if (typeof c === "object" && c !== null && "text" in c) {
return String((c as Record<string, unknown>).text);
}
return null;
})
.filter(Boolean);
if (texts.length > 0) return texts.join("\n");
}
}
}
// 3. Gemini — systemInstruction.parts[].text
if (
body.systemInstruction &&
typeof body.systemInstruction === "object" &&
"parts" in body.systemInstruction
) {
const parts = (body.systemInstruction as Record<string, unknown>).parts;
if (Array.isArray(parts)) {
const texts = parts
.map((p: unknown) => {
if (typeof p === "object" && p !== null && "text" in p) {
return String((p as Record<string, unknown>).text);
}
return null;
})
.filter(Boolean);
if (texts.length > 0) return texts.join("\n");
}
}
return null;
}
/**
* Compute a 12-hex SHA-256 fingerprint of the system prompt.
* Returns null if no system prompt is found.
*/
export function computeContextKey(req: InterceptedRequest): string | null {
const sys = extractSystemPrompt(req);
if (!sys) return null;
return createHash("sha256").update(sys).digest("hex").slice(0, 12);
}
|