Files
2026-07-13 13:39:12 +08:00

192 lines
4.5 KiB
TypeScript

// OpenAI-shaped response builders for the GitLab Duo executor. Extracted from
// gitlab.ts (leaf module — must not import from gitlab.ts) so the executor stays
// under the file-size cap. Covers plain text (streaming + JSON) and the
// tool_calls emulation variants added for #6051.
function buildSseChunk(data: unknown): string {
return `data: ${JSON.stringify(data)}\n\n`;
}
export function buildStreamingResponse(
content: string,
model: string,
id: string,
created: number
): Response {
const encoder = new TextEncoder();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
if (content) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { content }, finish_reason: null }],
})
)
);
}
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
export function buildJsonCompletion(
content: string,
model: string,
id: string,
created: number
): Response {
const estimated = Math.max(1, Math.ceil(content.length / 4));
return new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model,
choices: [
{
index: 0,
message: { role: "assistant", content },
finish_reason: "stop",
},
],
usage: {
prompt_tokens: estimated,
completion_tokens: estimated,
total_tokens: estimated * 2,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
export function buildToolJsonCompletion(
message: Record<string, unknown>,
finishReason: string,
model: string,
id: string,
created: number
): Response {
const contentForEstimate = typeof message.content === "string" ? message.content : "";
const estimated = Math.max(1, Math.ceil(contentForEstimate.length / 4));
return new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model,
choices: [
{
index: 0,
message,
finish_reason: finishReason,
},
],
usage: {
prompt_tokens: estimated,
completion_tokens: estimated,
total_tokens: estimated * 2,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
export function buildToolStreamingResponse(
message: Record<string, unknown>,
finishReason: string,
model: string,
id: string,
created: number
): Response {
const encoder = new TextEncoder();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: message, finish_reason: null }],
})
)
);
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}