/** * ARIA-patched openai-to-cli adapter. * * Erweitert die npm-Version von claude-max-api-proxy: * - Multimodal-Content (Array von text-Parts) wird zu String reduziert. * - Wenn die Anfrage ein `tools`-Feld enthaelt: die Tool-Definitionen * werden in den Prompt als -Block injiziert, mit klarer * Anweisung das {...} Format * zu verwenden statt freiem Text. * - Wenn Messages role=tool enthalten: deren Inhalt wird als * ins Prompt-Fragment * eingewoben damit Claude den Loop-Step bekommt. * * Wird zur Container-Startzeit ueber die npm-Version geschrieben * (siehe docker-compose.yml proxy-Block). */ const MODEL_MAP = { "claude-opus-4": "opus", "claude-sonnet-4": "sonnet", "claude-haiku-4": "haiku", "claude-code-cli/claude-opus-4": "opus", "claude-code-cli/claude-sonnet-4": "sonnet", "claude-code-cli/claude-haiku-4": "haiku", "opus": "opus", "sonnet": "sonnet", "haiku": "haiku", }; export function extractModel(model) { if (MODEL_MAP[model]) return MODEL_MAP[model]; const stripped = (model || "").replace(/^claude-code-cli\//, ""); if (MODEL_MAP[stripped]) return MODEL_MAP[stripped]; return "opus"; } /** Multimodal: content kann String oder Array von Parts sein. */ function _text(c) { if (typeof c === "string") return c; if (Array.isArray(c)) { return c .filter((b) => b && b.type === "text") .map((b) => b.text || "") .join(""); } return String(c == null ? "" : c); } /** * Baut den Tool-Use-Block fuer den System-Prompt. * Anweisung: Claude soll {json args} * ausgeben statt das Tool intern via Bash zu simulieren. */ function _toolsBlock(tools) { if (!Array.isArray(tools) || tools.length === 0) return ""; const lines = []; lines.push("# Verfuegbare Tools"); lines.push(""); lines.push( "Du hast neben deinen eigenen internen Tools (Bash, Read, etc.) auch " + "diese externen Tools, die im Backend-System angesiedelt sind. " + "Sie sind die EINZIGE Moeglichkeit Aktionen auszuloesen wie Trigger anlegen, " + "Skills aufrufen, oder Konfiguration aendern. Simuliere sie NICHT mit Bash/sleep — " + "rufe sie sauber auf:" ); lines.push(""); for (const t of tools) { if (!t || t.type !== "function" || !t.function) continue; const fn = t.function; const name = fn.name || ""; const desc = fn.description || ""; const params = fn.parameters || {}; lines.push(`## ${name}`); if (desc) lines.push(desc); try { lines.push("Schema: " + JSON.stringify(params)); } catch (_) { lines.push("Schema: (nicht serialisierbar)"); } lines.push(""); } lines.push("# Tool-Call-Format"); lines.push(""); lines.push( "Wenn du eines der OBIGEN externen Tools aufrufen willst, antworte " + "**ausschliesslich** mit einem oder mehreren Bloecken in genau dieser Form, " + "JEDER fuer sich auf einer eigenen Zeile:" ); lines.push(""); lines.push('{"arg1":"value","arg2":123}'); lines.push(""); lines.push( "Regeln: (1) Innerhalb des Blocks steht NUR gueltiges JSON mit den Argumenten. " + "(2) Kein Text drumherum. (3) Keine Code-Fences, kein Markdown. " + "(4) Mehrere Tool-Calls = mehrere Bloecke untereinander. " + "(5) Nach den Bloecken aufhoeren — der Server fuehrt die Tools aus und " + "schickt dir die Ergebnisse fuer den naechsten Turn. " + "(6) Wenn KEIN externes Tool noetig ist, antworte normal als Text fuer den User. " + "(7) Nutze Bash/sleep NICHT als Ersatz fuer trigger_timer — das ist genau " + "der Bug den wir damit fixen." ); return lines.join("\n"); } /** * Wandelt OpenAI-messages in einen Single-String-Prompt um. * - system/user/assistant wie bisher * - tool-role: als eingewoben */ export function messagesToPrompt(messages, tools) { const parts = []; const toolsBlock = _toolsBlock(tools); if (toolsBlock) { parts.push(`\n${toolsBlock}\n\n`); } for (const msg of messages) { if (!msg) continue; switch (msg.role) { case "system": parts.push(`\n${_text(msg.content)}\n\n`); break; case "user": parts.push(_text(msg.content)); break; case "assistant": { const txt = _text(msg.content); const tcs = Array.isArray(msg.tool_calls) ? msg.tool_calls : []; const tcParts = tcs.map((tc) => { const name = tc?.function?.name || tc?.name || ""; let args = tc?.function?.arguments ?? tc?.arguments ?? "{}"; if (typeof args !== "string") { try { args = JSON.stringify(args); } catch (_) { args = "{}"; } } return `${args}`; }).join("\n"); const combined = [txt, tcParts].filter(Boolean).join("\n").trim(); if (combined) parts.push(`\n${combined}\n\n`); break; } case "tool": { const name = msg.name || ""; const id = msg.tool_call_id || ""; parts.push( `\n${_text(msg.content)}\n\n` ); break; } } } return parts.join("\n").trim(); } export function openaiToCli(request) { return { prompt: messagesToPrompt(request.messages, request.tools), model: extractModel(request.model), sessionId: request.user, }; }