scria / main.ts
ERROR418's picture
Update main.ts
67cf308 verified
import { serve } from "https://deno.land/[email protected]/http/server.ts";
// 定义常量
const API_URL = "https://mcp.scira.ai/api/chat";
const FIXED_USER_ID = "2jFMDM1A1R_XxOTxPjhwe";
const FIXED_CHAT_ID = "ZIWa36kd6MSqzw-ifXGzE";
const DEFAULT_MODEL = "qwen-qwq";
const PORT = 7860;
// 定义接口
interface Message {
role: string;
content: string;
parts?: Array<{
type: string;
text: string;
}>;
}
interface SciraPayload {
id: string;
messages: Message[];
selectedModel: string;
mcpServers: any[];
chatId: string;
userId: string;
}
interface OpenAIModel {
id: string;
created: number;
object: string;
}
// 可用模型列表
const AVAILABLE_MODELS: OpenAIModel[] = [
{
id: "qwen-qwq",
created: Date.now(),
object: "model",
},
{
id: "gemini-2.5-flash",
created: Date.now(),
object: "model",
},
{
id: "gpt-4.1-mini",
created: Date.now(),
object: "model",
},
{
id: "claude-3-7-sonnet",
created: Date.now(),
object: "model",
},
];
// 格式化消息为Scira格式
function formatMessagesForScira(messages: Message[]): Message[] {
return messages.map(msg => ({
role: msg.role,
content: msg.content,
parts: [{
type: "text",
text: msg.content
}]
}));
}
// 构建Scira请求负载
function buildSciraPayload(messages: Message[], model = DEFAULT_MODEL): SciraPayload {
const formattedMessages = formatMessagesForScira(messages);
return {
id: FIXED_CHAT_ID,
messages: formattedMessages,
selectedModel: model,
mcpServers: [],
chatId: FIXED_CHAT_ID,
userId: FIXED_USER_ID
};
}
// 处理模型列表请求
async function handleModelsRequest(): Promise<Response> {
const response = {
object: "list",
data: AVAILABLE_MODELS,
};
return new Response(JSON.stringify(response), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
});
}
// 处理聊天补全请求
async function handleChatCompletionsRequest(req: Request): Promise<Response> {
const requestData = await req.json();
const { messages, model = DEFAULT_MODEL, stream = false } = requestData;
const sciraPayload = buildSciraPayload(messages, model);
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0",
"Accept": "*/*",
"Referer": `https://mcp.scira.ai/chat/${FIXED_CHAT_ID}`,
"Origin": "https://mcp.scira.ai",
},
body: JSON.stringify(sciraPayload),
});
if (stream) {
return handleStreamResponse(response, model);
} else {
return handleRegularResponse(response, model);
}
}
// 处理流式响应
async function handleStreamResponse(response: Response, model: string): Promise<Response> {
const reader = response.body!.getReader();
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const id = `chatcmpl-${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
const createdTime = Math.floor(Date.now() / 1000);
const systemFingerprint = `fp_${Math.random().toString(36).substring(2, 12)}`;
const stream = new ReadableStream({
async start(controller) {
// 发送流式头部
const headerEvent = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: { role: "assistant" },
logprobs: null,
finish_reason: null
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(headerEvent)}\n\n`));
try {
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 解码当前数据块并添加到缓冲区
buffer += decoder.decode(value, { stream: true });
// 处理完整的行
const lines = buffer.split('\n');
// 保留最后一个可能不完整的行
buffer = lines.pop() || "";
// 处理并立即发送每一行
for (const line of lines) {
if (!line.trim()) continue;
if (line.startsWith('g:')) {
// 对于g开头的行,输出reasoning_content
let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
content = content.replace(/\\n/g, "\n");
const event = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: { reasoning_content: content },
logprobs: null,
finish_reason: null
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
} else if (line.startsWith('0:')) {
// 对于0开头的行,输出content
let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
content = content.replace(/\\n/g, "\n");
const event = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: { content: content },
logprobs: null,
finish_reason: null
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
} else if (line.startsWith('e:')) {
// 完成消息
try {
const finishData = JSON.parse(line.slice(2));
const event = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: {},
logprobs: null,
finish_reason: finishData.finishReason || "stop"
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
} catch (error) {
console.error("Error parsing finish data:", error);
}
}
}
}
// 处理缓冲区中剩余的内容(如果有的话)
if (buffer.trim()) {
const line = buffer.trim();
if (line.startsWith('g:')) {
let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
content = content.replace(/\\n/g, "\n");
const event = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: { reasoning_content: content },
logprobs: null,
finish_reason: null
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
} else if (line.startsWith('0:')) {
let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
content = content.replace(/\\n/g, "\n");
const event = {
id: id,
object: "chat.completion.chunk",
created: createdTime,
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
delta: { content: content },
logprobs: null,
finish_reason: null
}]
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
}
}
} catch (error) {
console.error("Stream error:", error);
} finally {
// 确保发送 "data: [DONE]"
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
}
}
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": "*",
},
});
}
// 处理非流式响应
async function handleRegularResponse(response: Response, model: string): Promise<Response> {
const text = await response.text();
const lines = text.split('\n');
let content = "";
let reasoning_content = "";
let usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
let finish_reason = "stop";
for (const line of lines) {
if (!line.trim()) continue;
if (line.startsWith('0:')) {
// 常规内容 - 处理转义的换行符
let lineContent = line.slice(2).replace(/^"/, "").replace(/"$/, "");
lineContent = lineContent.replace(/\\n/g, "\n");
content += lineContent;
} else if (line.startsWith('g:')) {
// 推理内容 - 处理转义的换行符
let lineContent = line.slice(2).replace(/^"/, "").replace(/"$/, "");
lineContent = lineContent.replace(/\\n/g, "\n");
reasoning_content += lineContent;
} else if (line.startsWith('e:')) {
try {
const finishData = JSON.parse(line.slice(2));
if (finishData.finishReason) {
finish_reason = finishData.finishReason;
}
} catch (error) {
console.error("Error parsing finish data:", error);
}
} else if (line.startsWith('d:')) {
try {
const finishData = JSON.parse(line.slice(2));
if (finishData.usage) {
usage.prompt_tokens = finishData.usage.promptTokens || 0;
usage.completion_tokens = finishData.usage.completionTokens || 0;
usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
}
} catch (error) {
console.error("Error parsing usage data:", error);
}
}
}
const systemFingerprint = `fp_${Math.random().toString(36).substring(2, 12)}`;
const id = `chatcmpl-${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
const openAIResponse = {
id: id,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: model,
system_fingerprint: systemFingerprint,
choices: [{
index: 0,
message: {
role: "assistant",
content: content
},
logprobs: null,
finish_reason: finish_reason
}],
usage: usage
};
// 如果存在推理内容,添加到消息中
if (reasoning_content.trim()) {
openAIResponse.choices[0].message.reasoning_content = reasoning_content;
}
return new Response(JSON.stringify(openAIResponse), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
});
}
// 主请求处理函数
async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
// 设置CORS头
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
// 处理OPTIONS请求(CORS预检)
if (req.method === "OPTIONS") {
return new Response(null, {
headers,
status: 204
});
}
try {
// 处理模型列表接口
if (url.pathname === "/v1/models") {
return handleModelsRequest();
}
// 处理聊天补全接口
if (url.pathname === "/v1/chat/completions") {
return handleChatCompletionsRequest(req);
}
// 未找到的路由
return new Response(
JSON.stringify({ error: "Not found" }), {
status: 404,
headers: {
"Content-Type": "application/json",
...headers
},
}
);
} catch (error) {
console.error("Error processing request:", error);
return new Response(
JSON.stringify({ error: error.message || "Internal server error" }),
{
status: 500,
headers: {
"Content-Type": "application/json",
...headers
},
}
);
}
}
// 启动服务器
console.log(`Starting server on port ${PORT}...`);
serve(handler, { port: PORT });