mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
feat(headroom): add proxy lifecycle management + dashboard UI
Build on the optional Headroom Token Saver from Carmelo Campos
(PR: feat: add optional Headroom token saver). Add managed start/stop
of the local headroom proxy from the dashboard, install detection,
status probing, and a simplified Token Saver UI.
- detect headroom CLI + python>=3.10, probe proxy /health
- spawn/stop proxy as a detached, pid-tracked process
- /api/headroom/{status,start,stop} routes, gated local-only in dashboardGuard
- one-click Start/Stop Headroom modal, no manual config needed
- claude<->openai shape conversion for /v1/compress via 9router translators
Thanks to Carmelo Campos (@carmelogunsroses) for the original Headroom integration.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Shared system-prompt injector: appends an instruction into the system message of
|
||||
// the final request body, dispatching by format so it works for translated and
|
||||
// native-passthrough flows. Used by caveman.js and ponytail.js.
|
||||
|
||||
import { FORMATS } from "../translator/formats.js";
|
||||
|
||||
const SEP = "\n\n";
|
||||
|
||||
export function injectSystemPrompt(body, format, prompt) {
|
||||
if (!body || !prompt) return;
|
||||
|
||||
switch (format) {
|
||||
case FORMATS.CLAUDE:
|
||||
injectClaudeSystem(body, prompt);
|
||||
return;
|
||||
case FORMATS.GEMINI:
|
||||
case FORMATS.GEMINI_CLI:
|
||||
case FORMATS.VERTEX:
|
||||
case FORMATS.ANTIGRAVITY:
|
||||
// Antigravity wraps Gemini shape in body.request → injectGeminiSystem handles it
|
||||
injectGeminiSystem(body, prompt);
|
||||
return;
|
||||
default:
|
||||
// OpenAI and OpenAI-shaped formats (responses/codex/cursor/kiro/ollama)
|
||||
injectMessagesSystem(body, prompt);
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAI-shaped: messages[] (chat) or input[] (responses) or instructions (responses string)
|
||||
function injectMessagesSystem(body, prompt) {
|
||||
// OpenAI Responses API: top-level string field
|
||||
if (typeof body.instructions === "string") {
|
||||
body.instructions = body.instructions
|
||||
? `${body.instructions}${SEP}${prompt}`
|
||||
: prompt;
|
||||
return;
|
||||
}
|
||||
|
||||
const arr = Array.isArray(body.messages) ? body.messages
|
||||
: Array.isArray(body.input) ? body.input
|
||||
: null;
|
||||
if (!arr) return;
|
||||
|
||||
const idx = arr.findIndex(m => m && (m.role === "system" || m.role === "developer"));
|
||||
if (idx >= 0) {
|
||||
appendToOpenAIMessage(arr[idx], prompt);
|
||||
} else {
|
||||
arr.unshift({ role: "system", content: prompt });
|
||||
}
|
||||
}
|
||||
|
||||
function appendToOpenAIMessage(msg, prompt) {
|
||||
if (typeof msg.content === "string") {
|
||||
msg.content = `${msg.content}${SEP}${prompt}`;
|
||||
} else if (Array.isArray(msg.content)) {
|
||||
// Responses-style array of parts {type:"input_text"|"text", text}
|
||||
msg.content.push({ type: "input_text", text: prompt });
|
||||
} else {
|
||||
msg.content = prompt;
|
||||
}
|
||||
}
|
||||
|
||||
// Claude shape: body.system as string | array of {type:"text", text}
|
||||
// Insert before the last cache_control block to keep injection inside the cached prefix.
|
||||
function injectClaudeSystem(body, prompt) {
|
||||
if (typeof body.system === "string" && body.system.length > 0) {
|
||||
body.system = `${body.system}${SEP}${prompt}`;
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(body.system)) {
|
||||
const block = { type: "text", text: prompt };
|
||||
let lastCacheIdx = -1;
|
||||
for (let i = body.system.length - 1; i >= 0; i--) {
|
||||
if (body.system[i]?.cache_control) { lastCacheIdx = i; break; }
|
||||
}
|
||||
if (lastCacheIdx >= 0) {
|
||||
body.system.splice(lastCacheIdx, 0, block);
|
||||
} else {
|
||||
body.system.push(block);
|
||||
}
|
||||
return;
|
||||
}
|
||||
body.system = prompt;
|
||||
}
|
||||
|
||||
// Gemini shape: body.system_instruction | body.systemInstruction | body.request.systemInstruction
|
||||
// Each shape: { parts: [{ text }] }
|
||||
function injectGeminiSystem(body, prompt) {
|
||||
const target = body.request && typeof body.request === "object" ? body.request : body;
|
||||
const useSnake = Object.prototype.hasOwnProperty.call(target, "system_instruction");
|
||||
const key = useSnake ? "system_instruction" : "systemInstruction";
|
||||
const sys = target[key];
|
||||
if (sys && Array.isArray(sys.parts)) {
|
||||
sys.parts.push({ text: prompt });
|
||||
return;
|
||||
}
|
||||
target[key] = { parts: [{ text: prompt }] };
|
||||
}
|
||||
Reference in New Issue
Block a user