Spaces:
Running
Running
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 }); |