Files
WebAI2API/src/server/http/respond.js
T

148 lines
4.4 KiB
JavaScript

/**
* @fileoverview 统一响应写出模块
* @description 封装 JSON、SSE 响应和错误响应的统一处理函数
*/
import { getErrorDetails } from '../errors.js';
/**
* 发送 JSON 响应
* @param {import('http').ServerResponse} res - HTTP 响应对象
* @param {number} status - HTTP 状态码
* @param {object} payload - 响应数据
*/
export function sendJson(res, status, payload) {
if (res.writableEnded) return;
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(payload));
}
/**
* 发送 SSE 事件
* @param {import('http').ServerResponse} res - HTTP 响应对象
* @param {object} payload - 事件数据
*/
export function sendSse(res, payload) {
if (res.writableEnded) return;
res.write(`data: ${JSON.stringify(payload)}\n\n`);
}
/**
* 发送 SSE 结束标记
* @param {import('http').ServerResponse} res - HTTP 响应对象
*/
export function sendSseDone(res) {
if (res.writableEnded) return;
res.write(`data: [DONE]\n\n`);
res.end();
}
/**
* 发送 SSE 心跳包
* @param {import('http').ServerResponse} res - HTTP 响应对象
* @param {string} mode - 心跳模式 ('comment' | 'content')
* @param {string} [modelName] - 模型名称(content 模式需要)
*/
export function sendHeartbeat(res, mode, modelName) {
if (res.writableEnded) return;
if (mode === 'comment') {
res.write(`:keepalive\n\n`);
} else {
// content 模式:发送空 delta
const chunk = {
id: 'chatcmpl-' + Date.now(),
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: modelName || 'default-model',
choices: [{
index: 0,
delta: { content: '' },
finish_reason: null
}]
};
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
}
/**
* 发送统一 API 错误响应
* @param {import('http').ServerResponse} res - HTTP 响应对象
* @param {object} options - 错误选项
* @param {string} [options.code] - 错误码(使用 ERROR_CODES 枚举)
* @param {string} [options.error] - 自定义错误消息(如提供则覆盖 code 对应的消息)
* @param {number} [options.status] - 自定义 HTTP 状态码
* @param {boolean} [options.isStreaming=false] - 是否为流式响应
* @param {string} [options.raw] - 原始错误信息(用于调试)
*/
export function sendApiError(res, options) {
const { code, error, status, isStreaming = false, raw } = options;
// 获取错误详情
const details = code ? getErrorDetails(code) : null;
const errorMessage = error || (details ? details.message : '未知错误');
const httpStatus = status || (details ? details.status : 500);
// 构造错误响应体
const payload = {
error: errorMessage,
code: code || 'INTERNAL_ERROR',
};
if (raw) {
payload.raw = raw;
}
if (isStreaming) {
// 流式响应
sendSse(res, payload);
sendSseDone(res);
} else {
// 非流式响应
sendJson(res, httpStatus, payload);
}
}
/**
* 构造 OpenAI 格式的聊天完成响应(非流式)
* @param {string} content - 响应内容
* @param {string} [modelName] - 模型名称
* @returns {object} OpenAI 格式的响应对象
*/
export function buildChatCompletion(content, modelName) {
return {
id: 'chatcmpl-' + Date.now(),
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: modelName || 'default-model',
choices: [{
index: 0,
message: {
role: 'assistant',
content: content
},
finish_reason: 'stop'
}]
};
}
/**
* 构造 OpenAI 格式的流式聊天完成响应块
* @param {string} content - 响应内容
* @param {string} [modelName] - 模型名称
* @param {string|null} [finishReason='stop'] - 完成原因
* @returns {object} OpenAI 格式的流式响应块
*/
export function buildChatCompletionChunk(content, modelName, finishReason = 'stop') {
return {
id: 'chatcmpl-' + Date.now(),
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: modelName || 'default-model',
choices: [{
index: 0,
delta: { content },
finish_reason: finishReason
}]
};
}