192 lines
4.5 KiB
TypeScript
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" },
|
|
});
|
|
}
|