adaptive / main.ts
hongshi-files's picture
Update main.ts
2ff0f55 verified
import { serve } from "https://deno.land/[email protected]/http/server.ts";
// --- 配置常量 ---
const AUTH_KEY = Deno.env.get("AUTH_KEY") ?? "default_api_key_value"; //API密钥在Environment Variables中添加,否则默认是default_api_key_value
const TARGET_URL = 'https://assistant.on.adaptive.ai/api/sendMessage';
const PROXY_MODEL_NAME = "gpt-4o"; // 代理服务返回的模型名称
const TARGET_HEADERS = {
'content-type': 'application/json',
'x-channel-id': "0"
};
// --- 辅助函数:创建 SSE 数据块 ---
function createSSEChunk(id: string, model: string, content: string | null, role: string | null, finish_reason: string | null): string {
const now = Math.floor(Date.now() / 1000);
const chunk: any = {
id: id,
object: "chat.completion.chunk",
created: now,
model: model,
choices: [
{
index: 0,
delta: {},
finish_reason: finish_reason,
logprobs: null,
}
],
// system_fingerprint: null, // 可选
};
if (role) {
chunk.choices[0].delta.role = role;
}
if (content) {
chunk.choices[0].delta.content = content;
}
// 如果 delta 为空且有 finish_reason,确保 delta 是空对象
if (!role && !content && finish_reason) {
chunk.choices[0].delta = {};
}
return `data: ${JSON.stringify(chunk)}\n\n`;
}
/**
* 向 Adaptive AI 发送创建新聊天的请求。
* 成功时返回新聊天的 ID (字符串)。
* 如果在过程中发生任何错误,则捕获错误并返500。
*
* @returns {Promise<string | 500>} 返回成功创建的聊天的 ID (string),或在失败时返回 500。
*/
async function createChatAndGetId(): Promise<string> {
const url = 'https://assistant.on.adaptive.ai/api/createChat';
// 定义请求头 (省略了 cookie 和 priority)
const headers = {
'content-type': 'application/json',
};
// 构建请求体,使用当前时间戳作为请求 ID
const payload = {
json: {
jsonrpc: "2.0",
id: Date.now(), // 使用动态 ID
method: "createChat",
params: []
}
};
console.log("正在发送创建聊天请求...");
try {
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload) // 将 payload 对象转换为 JSON 字符串
});
// 检查 HTTP 响应状态码是否表示成功
if (!response.ok) {
let errorBody = "无法读取响应体";
try {
errorBody = await response.text(); // 尝试读取错误响应体
} catch (readError) {
console.warn("读取错误响应体失败:", readError);
}
throw new Error(`创建聊天请求失败,HTTP 状态码: ${response.status}. 响应: ${errorBody}`);
}
// 解析 JSON 响应体
let responseData: any;
try {
responseData = await response.json();
} catch (parseError) {
// 如果响应不是有效的 JSON,则抛出错误
console.error("解析创建聊天响应 JSON 时出错:", parseError);
throw new Error(`无法将响应解析为 JSON: ${parseError.message}`);
}
// 提取并验证 ID
// ID 预期在 responseData.json.result.id
// 使用可选链操作符 (?.) 来安全地访问嵌套属性,防止因中间属性不存在而报错
const chatId = responseData?.json?.result?.id;
// 检查提取到的 ID 是否是一个有效的、非空的字符串
if (typeof chatId === 'string' && chatId.length > 0) {
console.log(`成功创建聊天,获取到 ID: ${chatId}`);
return chatId; // 返回提取到的 ID
} else {
// 如果 ID 不存在或格式不正确,则抛出错误
console.error("从响应中未能提取有效的聊天 ID。响应数据:", JSON.stringify(responseData));
throw new Error("创建聊天的响应格式无效或缺少 'json.result.id' 字段。");
}
} catch (error) {
// 捕获 try 块中抛出的任何错误,或 fetch 本身的网络错误
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("执行 createChatAndGetId 时发生错误:", errorMessage); // 记录详细错误信息
// 创建并返回一个 Response 对象
// 状态码为 500 (Internal Server Error)
// 响应体为 JSON 格式,包含错误信息
return new Response(
JSON.stringify({ error: `创建聊天会话失败: ${errorMessage}` }), // 将错误信息包装在 JSON 对象中
{
status: 500, // 设置 HTTP 状态码为 500
headers: {
"Content-Type": "application/json", // 设置响应内容类型为 JSON
"Access-Control-Allow-Origin": "*" // 如果需要跨域,添加此头
}
}
);
}
}
// --- 主处理函数 ---
async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
// --- CORS 预检请求处理 ---
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
},
});
}
// 模型列表接口
if (url.pathname === "/v1/models" && req.method === "GET") {
return new Response(
JSON.stringify({
object: "list",
data: [
{
id: "gpt-4o",
object: "model",
created: 0,
owned_by: "unlimitedai",
permission: [{
id: "modelperm-gpt-4o",
object: "model_permission",
created: 0,
allow_create_engine: false,
allow_sampling: true,
allow_logprobs: false,
allow_search_indices: false,
allow_view: true,
allow_fine_tuning: false,
organization: "*",
group: null,
is_blocking: false,
}],
root: "gpt-4o",
parent: null,
},
],
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
// --- 路径和方法检查 ---
if (url.pathname !== "/v1/chat/completions" || req.method !== "POST") {
return new Response(JSON.stringify({ error: "Not Found or Method Not Allowed" }), {
status: 404,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
// --- 添加认证检查 ---
const authHeader = req.headers.get("Authorization");
let providedKey = "";
// 检查 Authorization header 是否存在且格式正确 (Bearer <key>)
if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) {
console.warn(`认证失败: 缺少或格式错误的 Authorization header`);
return new Response(JSON.stringify({
error: {
message: "Unauthorized: Missing or invalid Authorization header. Use 'Bearer <YOUR_API_KEY>' format.",
type: "invalid_request_error",
param: null,
code: "missing_or_invalid_header"
}
}), {
status: 401, // Unauthorized
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"WWW-Authenticate": 'Bearer realm="API Access"'
}
});
}
// 提取 key 部分
providedKey = authHeader.substring(7); // "Bearer ".length is 7
console.log("providedKey:" + providedKey);
// 直接比较提供的 key 和硬编码的 key
if (providedKey !== AUTH_KEY) {
console.warn(`认证失败: 无效的 API Key 提供`);
return new Response(JSON.stringify({
error: {
message: "Unauthorized: Invalid API Key provided.",
type: "invalid_request_error",
param: null,
code: "inAUTH_KEY"
}
}), {
status: 401, // Unauthorized
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"WWW-Authenticate": 'Bearer realm="API Access"'
}
});
}
// --- 处理 POST 请求 ---
try {
// 1. 解析入站请求体
let requestBody: any;
try {
requestBody = await req.json();
console.log(requestBody)
} catch (e) {
console.error("Failed to parse request JSON:", e);
return new Response(JSON.stringify({ error: "Invalid JSON in request body" }), {
status: 400, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
// 2. 检查是否请求流式响应
const isStream = requestBody.stream === true;
// 3. 提取用户输入的内容 - 将 messages 数组转换为字符串
let userContent: string | undefined;
if (Array.isArray(requestBody.messages) && requestBody.messages.length > 0) {
try {
// 直接将整个 messages 数组转换为 JSON 字符串
userContent = JSON.stringify(requestBody.messages);
} catch (e) {
console.error("Failed to stringify 'messages' array:", e);
// 如果 JSON.stringify 失败 (虽然对数组不太可能,但以防万一)
return new Response(JSON.stringify({ error: "Failed to process 'messages' array." }), {
status: 400, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
}
// 检查 userContent 是否成功生成
// 如果 requestBody.messages 不存在、不是数组、为空,或者转换出错,userContent 会是 undefined
if (!userContent) {
console.error("Request body must contain a non-empty 'messages' array.");
return new Response(JSON.stringify({ error: "Request body must contain a non-empty 'messages' array." }), {
status: 400, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
// 现在 userContent 包含了整个对话历史的字符串表示
console.log("Formatted user content:", userContent); // 可以取消注释来调试输出
const CHAT_ID = await createChatAndGetId();//获取新的聊天ID
// 4. 构建目标 API Payload
const payload = {
json: {
jsonrpc: "2.0", id: Date.now(), method: "sendMessage",
params: [{ chatId: CHAT_ID, content: userContent, fileId: null, fileIds: [] }]
},
meta: { values: { "params.0.fileId": ["undefined"] } }
};
// 5. 发送请求到目标 API (无论是否流式,都需要先获取完整响应)
console.log("Forwarding request to:", TARGET_URL);
const targetResponse = await fetch(TARGET_URL, {
method: 'POST', headers: TARGET_HEADERS, body: JSON.stringify(payload),
});
// 6. 处理目标 API 的响应
if (!targetResponse.ok) {
const errorBody = await targetResponse.text();
console.error(`Target API Error (${targetResponse.status}):`, errorBody);
// 即使是流式请求失败,也返回 JSON 错误
return new Response(JSON.stringify({ error: `Upstream API request failed with status ${targetResponse.status}`, details: errorBody }), {
status: 502, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
// 7. 解析目标 API 的 JSON 响应
let targetData: any;
try {
targetData = await targetResponse.json();
} catch (e) {
console.error("Failed to parse target API response JSON:", e);
// 即使是流式请求失败,也返回 JSON 错误
return new Response(JSON.stringify({ error: "Failed to parse upstream API response" }), {
status: 500, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
// 8. 从目标响应中提取内容
const assistantContent = targetData?.json?.result?.content;
if (typeof assistantContent !== 'string') {
console.error("Could not extract 'content' from target API response:", JSON.stringify(targetData));
// 即使是流式请求失败,也返回 JSON 错误
return new Response(JSON.stringify({ error: "Invalid response format from upstream API" }), {
status: 500, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
// 9. 根据 isStream 决定返回格式
const chatCompletionId = `chatcmpl-${crypto.randomUUID()}`; // 为本次交互生成唯一 ID
const modelName = requestBody.model || PROXY_MODEL_NAME; // 确定模型名称
if (isStream) {
// --- 返回模拟的流式响应 ---
console.log("Simulating stream response...");
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
// 模拟发送块
// 块 1: 发送角色信息
controller.enqueue(encoder.encode(
createSSEChunk(chatCompletionId, modelName, null, "assistant", null)
));
await new Promise(resolve => setTimeout(resolve, 10)); // 短暂延迟,模拟处理
// 块 2: 发送完整内容
controller.enqueue(encoder.encode(
createSSEChunk(chatCompletionId, modelName, assistantContent, null, null)
));
await new Promise(resolve => setTimeout(resolve, 10)); // 短暂延迟
// 块 3: 发送结束信号
controller.enqueue(encoder.encode(
createSSEChunk(chatCompletionId, modelName, null, null, "stop")
));
// 发送 [DONE] 标记
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
// 关闭流
controller.close();
} catch (error) {
console.error("Error during stream simulation:", error);
controller.error(error); // 通知流出错了
}
}
});
return new Response(stream, {
status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive', // 建议 SSE 使用
'Access-Control-Allow-Origin': '*'
},
});
} else {
// --- 返回完整的 JSON 响应 ---
console.log("Returning non-stream response.");
const finalResponse = {
id: chatCompletionId,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: modelName,
choices: [
{
index: 0,
message: {
role: "assistant",
content: assistantContent,
},
finish_reason: "stop",
logprobs: null,
}
],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
};
return new Response(JSON.stringify(finalResponse), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
});
}
} catch (error) {
// --- 全局错误处理 ---
console.error("Unhandled error in handler:", error);
// 即使请求流式,也返回 JSON 错误
return new Response(JSON.stringify({ error: "Internal Server Error" }), {
status: 500,
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
});
}
}
serve(handler, { port: 7860 });