188 lines
6.2 KiB
TypeScript
188 lines
6.2 KiB
TypeScript
/**
|
|
* System Prompt Injection — Phase 10.1
|
|
*
|
|
* Injects TWO global system prompts into all requests at proxy level:
|
|
* - prefixPrompt: prepended BEFORE existing system/agent content
|
|
* - suffixPrompt: appended AFTER existing system/agent content
|
|
*
|
|
* This gives the user full control over instruction priority (#2468):
|
|
* prefix → agent/provider instructions → suffix (highest recency priority)
|
|
*
|
|
* Uses globalThis to share config across Turbopack module instances (#2470).
|
|
*/
|
|
|
|
const GLOBAL_KEY = "__omniroute_systemPrompt_config__";
|
|
|
|
interface SystemPromptConfig {
|
|
enabled: boolean;
|
|
prefixPrompt: string;
|
|
suffixPrompt: string;
|
|
prompt: string;
|
|
}
|
|
|
|
// Typed accessor for globalThis storage — avoids `as any` casts (#2470)
|
|
const _store = globalThis as unknown as Record<string, SystemPromptConfig | undefined>;
|
|
|
|
function getConfig(): SystemPromptConfig {
|
|
if (!_store[GLOBAL_KEY]) {
|
|
_store[GLOBAL_KEY] = {
|
|
enabled: false,
|
|
prefixPrompt: "",
|
|
suffixPrompt: "",
|
|
prompt: "",
|
|
};
|
|
}
|
|
return _store[GLOBAL_KEY]!;
|
|
}
|
|
|
|
function setConfig(cfg: SystemPromptConfig): void {
|
|
_store[GLOBAL_KEY] = cfg;
|
|
}
|
|
|
|
/**
|
|
* Set system prompt config (supports legacy `prompt` field for migration)
|
|
*/
|
|
export function setSystemPromptConfig(config: Partial<SystemPromptConfig>) {
|
|
const current = getConfig();
|
|
const base = { ...current };
|
|
if ("prefixPrompt" in config || "suffixPrompt" in config) {
|
|
base.prompt = "";
|
|
}
|
|
const merged = { ...base, ...config };
|
|
if (merged.prompt && !merged.suffixPrompt && !("suffixPrompt" in config)) {
|
|
merged.suffixPrompt = merged.prompt;
|
|
}
|
|
setConfig(merged);
|
|
}
|
|
|
|
/**
|
|
* Get system prompt config
|
|
*/
|
|
export function getSystemPromptConfig() {
|
|
const cfg = getConfig();
|
|
return {
|
|
enabled: cfg.enabled,
|
|
prefixPrompt: cfg.prefixPrompt,
|
|
suffixPrompt: cfg.suffixPrompt,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Inject system prompts into request body.
|
|
*
|
|
* prefixPrompt is prepended before existing system content.
|
|
* suffixPrompt is appended after existing system content.
|
|
* This ensures: prefix → agent instructions → suffix (#2468).
|
|
*
|
|
* @param {object} body - Request body
|
|
* @returns {object} Modified body
|
|
*/
|
|
export function injectSystemPrompt(body) {
|
|
const cfg = getConfig();
|
|
if (!cfg.enabled) return body;
|
|
const prefix = cfg.prefixPrompt || "";
|
|
const suffix = cfg.suffixPrompt || "";
|
|
if (!prefix && !suffix) return body;
|
|
if (!body || typeof body !== "object") return body;
|
|
if (body._skipSystemPrompt) return body;
|
|
|
|
const result = { ...body };
|
|
|
|
// OpenAI/Claude format (messages[])
|
|
if (result.messages && Array.isArray(result.messages)) {
|
|
const sysIdx = result.messages.findIndex((m) => m.role === "system" || m.role === "developer");
|
|
result.messages = [...result.messages];
|
|
if (sysIdx >= 0) {
|
|
const msg = { ...result.messages[sysIdx] };
|
|
if (Array.isArray(msg.content)) {
|
|
const content = [...msg.content];
|
|
if (prefix) content.unshift({ type: "text", text: prefix });
|
|
if (suffix) content.push({ type: "text", text: suffix });
|
|
msg.content = content;
|
|
} else {
|
|
let content = msg.content || "";
|
|
if (prefix) content = prefix + "\n\n" + content;
|
|
if (suffix) content = content + "\n\n" + suffix;
|
|
msg.content = content;
|
|
}
|
|
result.messages[sysIdx] = msg;
|
|
} else {
|
|
// No existing system message — combine both into one
|
|
const combined = [prefix, suffix].filter(Boolean).join("\n\n");
|
|
if (combined) {
|
|
result.messages = [{ role: "system", content: combined }, ...result.messages];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Claude format (system field)
|
|
if (result.system !== undefined) {
|
|
if (typeof result.system === "string") {
|
|
let sys = result.system;
|
|
if (prefix) sys = prefix + "\n\n" + sys;
|
|
if (suffix) sys = sys + "\n\n" + suffix;
|
|
result.system = sys;
|
|
} else if (Array.isArray(result.system)) {
|
|
let arr = [...result.system];
|
|
if (prefix) arr = [{ type: "text", text: prefix }, ...arr];
|
|
if (suffix) arr = [...arr, { type: "text", text: suffix }];
|
|
result.system = arr;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Inject a per-request custom system prompt into the request body.
|
|
*
|
|
* Unlike injectSystemPrompt (which reads from globalThis config), this
|
|
* function takes an explicit prompt string and appends it as a suffix
|
|
* after any existing system content — mirroring the caveman/ponytail
|
|
* injection pattern but driven by per-endpoint settings.
|
|
*
|
|
* @param body - Translated request body (OpenAI/Claude/Gemini format)
|
|
* @param prompt - The custom system prompt text to inject
|
|
* @returns Modified body with prompt appended to the system message
|
|
*/
|
|
export function injectCustomSystemPrompt(body: Record<string, unknown>, prompt: string) {
|
|
if (!prompt || typeof prompt !== "string") return body;
|
|
if (!body || typeof body !== "object") return body;
|
|
if (body._skipSystemPrompt) return body;
|
|
|
|
const result = { ...body };
|
|
|
|
// OpenAI/Claude messages[] format
|
|
if (result.messages && Array.isArray(result.messages)) {
|
|
const sysIdx = (result.messages as Array<{ role: string; content: unknown }>).findIndex(
|
|
(m) => m.role === "system" || m.role === "developer"
|
|
);
|
|
result.messages = [...(result.messages as Array<{ role: string; content: unknown }>)];
|
|
if (sysIdx >= 0) {
|
|
const msg = { ...(result.messages as Array<{ role: string; content: unknown }>)[sysIdx] };
|
|
if (Array.isArray(msg.content)) {
|
|
msg.content = [...(msg.content as unknown[]), { type: "text", text: prompt }];
|
|
} else {
|
|
msg.content = (msg.content ? msg.content + "\n\n" : "") + prompt;
|
|
}
|
|
(result.messages as Array<{ role: string; content: unknown }>)[sysIdx] = msg;
|
|
} else {
|
|
result.messages = [
|
|
{ role: "system", content: prompt },
|
|
...(result.messages as Array<{ role: string; content: unknown }>),
|
|
];
|
|
}
|
|
}
|
|
|
|
// Claude direct system field
|
|
if (result.system !== undefined) {
|
|
if (typeof result.system === "string") {
|
|
result.system = result.system ? result.system + "\n\n" + prompt : prompt;
|
|
} else if (Array.isArray(result.system)) {
|
|
result.system = [...(result.system as unknown[]), { type: "text", text: prompt }];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|