mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-13 18:52:18 +03:00
refactor(providers): split buildPrompt + extract applyCliDefaults to keep complexity under ratchet (#6220) (#5997)
This commit is contained in:
@@ -91,79 +91,74 @@ function extractTextContent(content: unknown): string {
|
||||
* re-emitting the same `<tool>` call forever (#6220). Complements the tool_call emission
|
||||
* added in #6051.
|
||||
*/
|
||||
export function buildPrompt(messages: OpenAIMessage[] | undefined): string {
|
||||
if (!Array.isArray(messages)) return "";
|
||||
|
||||
const hasToolExchange = messages.some((message) => {
|
||||
function hasToolExchange(messages: OpenAIMessage[]): boolean {
|
||||
return messages.some((message) => {
|
||||
const role = String(message?.role || "user").toLowerCase();
|
||||
if (role === "tool") return true;
|
||||
return (
|
||||
role === "assistant" &&
|
||||
Array.isArray(message?.tool_calls) &&
|
||||
message.tool_calls.length > 0
|
||||
role === "assistant" && Array.isArray(message?.tool_calls) && message.tool_calls.length > 0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/** Legacy flatten: system instructions + the latest user message. */
|
||||
function buildSimplePrompt(messages: OpenAIMessage[]): string {
|
||||
const systemParts: string[] = [];
|
||||
|
||||
if (!hasToolExchange) {
|
||||
// Legacy path — unchanged: system instructions + the latest user message.
|
||||
const userParts: string[] = [];
|
||||
for (const message of messages) {
|
||||
const role = String(message?.role || "user").toLowerCase();
|
||||
const text = extractTextContent(message?.content);
|
||||
if (!text) continue;
|
||||
if (role === "system" || role === "developer") {
|
||||
systemParts.push(text);
|
||||
continue;
|
||||
}
|
||||
if (role === "user") {
|
||||
userParts.push(text);
|
||||
}
|
||||
const userParts: string[] = [];
|
||||
for (const message of messages) {
|
||||
const role = String(message?.role || "user").toLowerCase();
|
||||
const text = extractTextContent(message?.content);
|
||||
if (!text) continue;
|
||||
if (role === "system" || role === "developer") {
|
||||
systemParts.push(text);
|
||||
} else if (role === "user") {
|
||||
userParts.push(text);
|
||||
}
|
||||
const latestUserPrompt = userParts.at(-1) || "";
|
||||
if (!systemParts.length) {
|
||||
return latestUserPrompt;
|
||||
}
|
||||
return `System instructions:\n${systemParts.join("\n\n")}\n\n${latestUserPrompt}`.trim();
|
||||
}
|
||||
const latestUserPrompt = userParts.at(-1) || "";
|
||||
if (!systemParts.length) return latestUserPrompt;
|
||||
return `System instructions:\n${systemParts.join("\n\n")}\n\n${latestUserPrompt}`.trim();
|
||||
}
|
||||
|
||||
// Tool-exchange path — serialize the full turn history so the model sees the tool
|
||||
// result and continues instead of repeating the tool call (#6220).
|
||||
/** Render an assistant turn (its text plus any tool calls) for the tool-exchange prompt. */
|
||||
function renderAssistantTurn(message: OpenAIMessage, text: string): string | null {
|
||||
const lines: string[] = [];
|
||||
if (text) lines.push(text);
|
||||
for (const tc of Array.isArray(message?.tool_calls) ? message.tool_calls : []) {
|
||||
const id = tc?.id ? ` [${tc.id}]` : "";
|
||||
lines.push(
|
||||
`Called tool ${tc?.function?.name || "tool"}${id} with arguments: ${tc?.function?.arguments ?? ""}`
|
||||
);
|
||||
}
|
||||
return lines.length ? `Assistant: ${lines.join("\n")}` : null;
|
||||
}
|
||||
|
||||
/** Render one message as a labeled conversation line for the tool-exchange prompt. */
|
||||
function renderConversationTurn(message: OpenAIMessage, role: string, text: string): string | null {
|
||||
if (role === "user") return text ? `User: ${text}` : null;
|
||||
if (role === "assistant") return renderAssistantTurn(message, text);
|
||||
if (role === "tool") {
|
||||
const id = message?.tool_call_id ? ` for ${message.tool_call_id}` : "";
|
||||
const name = message?.name ? ` (${message.name})` : "";
|
||||
return `Tool result${name}${id}: ${text}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Serialize the full turn history so the model sees the tool result (#6220). */
|
||||
function buildToolExchangePrompt(messages: OpenAIMessage[]): string {
|
||||
const systemParts: string[] = [];
|
||||
const convo: string[] = [];
|
||||
for (const message of messages) {
|
||||
const role = String(message?.role || "user").toLowerCase();
|
||||
const text = extractTextContent(message?.content);
|
||||
|
||||
if (role === "system" || role === "developer") {
|
||||
if (text) systemParts.push(text);
|
||||
continue;
|
||||
}
|
||||
if (role === "user") {
|
||||
if (text) convo.push(`User: ${text}`);
|
||||
continue;
|
||||
}
|
||||
if (role === "assistant") {
|
||||
const lines: string[] = [];
|
||||
if (text) lines.push(text);
|
||||
const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
|
||||
for (const tc of toolCalls) {
|
||||
const name = tc?.function?.name || "tool";
|
||||
const args = tc?.function?.arguments ?? "";
|
||||
const id = tc?.id ? ` [${tc.id}]` : "";
|
||||
lines.push(`Called tool ${name}${id} with arguments: ${args}`);
|
||||
}
|
||||
if (lines.length) convo.push(`Assistant: ${lines.join("\n")}`);
|
||||
continue;
|
||||
}
|
||||
if (role === "tool") {
|
||||
const id = message?.tool_call_id ? ` for ${message.tool_call_id}` : "";
|
||||
const name = message?.name ? ` (${message.name})` : "";
|
||||
convo.push(`Tool result${name}${id}: ${text}`);
|
||||
continue;
|
||||
}
|
||||
const line = renderConversationTurn(message, role, text);
|
||||
if (line) convo.push(line);
|
||||
}
|
||||
|
||||
const header = systemParts.length
|
||||
? `System instructions:\n${systemParts.join("\n\n")}\n\n`
|
||||
: "";
|
||||
@@ -172,6 +167,20 @@ export function buildPrompt(messages: OpenAIMessage[] | undefined): string {
|
||||
)}\n\nContinue the response using the tool result above; do not repeat the tool call.`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* GitLab code_suggestions is a single-prompt completion API (no chat roles), so the
|
||||
* OpenAI message array must be flattened to text. Simple conversations keep the legacy
|
||||
* shape; a tool exchange (assistant `tool_calls` or a `tool` result) serializes the full
|
||||
* conversation so the model continues instead of re-emitting the same `<tool>` call
|
||||
* forever (#6220). Complements the tool_call emission added in #6051.
|
||||
*/
|
||||
export function buildPrompt(messages: OpenAIMessage[] | undefined): string {
|
||||
if (!Array.isArray(messages)) return "";
|
||||
return hasToolExchange(messages)
|
||||
? buildToolExchangePrompt(messages)
|
||||
: buildSimplePrompt(messages);
|
||||
}
|
||||
|
||||
function toOpenAIError(status: number, message: string): Response {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -75,22 +75,24 @@ export function forwardOpencodeClientHeaders(
|
||||
|
||||
// 4. OpencodeExecutor-only: synthesize the OpenCode CLI identity Cloudflare expects
|
||||
// on VPS egress, for any key the client did not supply (#5997).
|
||||
const cliDefaults = options?.cliDefaults;
|
||||
if (cliDefaults) {
|
||||
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
||||
setUserAgentHeader(headers, cliDefaults.userAgent);
|
||||
}
|
||||
if (!headers["x-opencode-client"]) {
|
||||
headers["x-opencode-client"] = cliDefaults.client;
|
||||
}
|
||||
if (!headers["x-opencode-project"]) {
|
||||
headers["x-opencode-project"] = cliDefaults.project;
|
||||
}
|
||||
if (!headers["x-opencode-request"]) {
|
||||
headers["x-opencode-request"] = randomUUID();
|
||||
}
|
||||
if (!headers["x-opencode-session"]) {
|
||||
headers["x-opencode-session"] = randomUUID();
|
||||
}
|
||||
if (options?.cliDefaults) {
|
||||
applyCliDefaults(headers, options.cliDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the OpenCode CLI identity headers Cloudflare requires on VPS egress, but only for
|
||||
* keys the client did not already supply (client values always win). (#5997)
|
||||
*/
|
||||
function applyCliDefaults(
|
||||
headers: Record<string, string>,
|
||||
cliDefaults: { userAgent: string; client: string; project: string }
|
||||
): void {
|
||||
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
||||
setUserAgentHeader(headers, cliDefaults.userAgent);
|
||||
}
|
||||
headers["x-opencode-client"] ||= cliDefaults.client;
|
||||
headers["x-opencode-project"] ||= cliDefaults.project;
|
||||
headers["x-opencode-request"] ||= randomUUID();
|
||||
headers["x-opencode-session"] ||= randomUUID();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user