Spaces:
Sleeping
Sleeping
| import { serve } from "https://deno.land/std@0.220.1/http/server.ts"; | |
| // 定义常量 | |
| const BASE_URL = "https://api.codegpt.co/api"; | |
| const PORT = 7860; | |
| interface CodegptModel { | |
| id: string; | |
| org_id: string; | |
| name: string; | |
| model: string; | |
| } | |
| // 从请求头中获取API_KEY | |
| function getApiKeyFromRequest(req: Request): string | null { | |
| const authHeader = req.headers.get("authorization"); | |
| if (!authHeader) return null; | |
| // 支持 "Bearer sk-xxx" 或直接 "sk-xxx" 格式 | |
| const match = authHeader.match(/^(Bearer\s+)?(sk-[a-zA-Z0-9-]+)$/); | |
| return match ? match[2] : null; | |
| } | |
| // 获取模型列表 | |
| async function getModels(apiKey: string): Promise<CodegptModel[]> { | |
| try { | |
| const response = await fetch( | |
| `${BASE_URL}/v1/agent`, | |
| { | |
| method: "GET", | |
| headers: { | |
| "Accept": "application/json", | |
| "Authorization": `Bearer ${apiKey}`, | |
| }, | |
| }, | |
| ); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| if (errorData.message === "Invalid credentials") { | |
| throw new Error("🚀 ~ Your API key or access token is invalid.!"); | |
| } | |
| throw new Error(`Failed to get models: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| // console.log("🚀 ~ codegpt agents 响应:", data); | |
| return data; | |
| } catch (error) { | |
| console.error("Error getting models:", error); | |
| throw error; | |
| } | |
| } | |
| // 将 codegpt agent 转换为 OpenAI model 格式 | |
| function transformModelsToOpenAIFormat(models: CodegptModel[]) { | |
| return { | |
| object: "list", | |
| data: models.map((model) => ({ | |
| id: model.id, | |
| name: model.model, | |
| object: "model", | |
| created: Date.now(), | |
| owned_by: "codegpt", | |
| permission: [{ | |
| id: `modelperm-${model.model}`, | |
| object: "model_permission", | |
| created: Date.now(), | |
| 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: model.model, | |
| parent: null, | |
| })), | |
| }; | |
| } | |
| // 处理聊天完成请求 | |
| async function handleChatCompletions( | |
| apiKey: string, | |
| requestBody: any, | |
| stream: boolean, | |
| ) { | |
| const CodegptBody = { | |
| stream: stream, | |
| agentId: requestBody.model, | |
| messages: requestBody.messages, | |
| format: "json", | |
| }; | |
| // console.log("🚀 ~ 内部实际请求: ", CodegptBody); | |
| const response = await fetch( | |
| `${BASE_URL}/v1/chat/completions`, | |
| { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": `Bearer ${apiKey}`, | |
| "Accept": stream ? "text/event-stream" : "application/json", | |
| }, | |
| body: JSON.stringify(CodegptBody), | |
| }, | |
| ); | |
| // console.log("🚀 ~ 内部请求实际响应: \n", response); | |
| if (!response.ok) { | |
| throw new Error(`Chat completion failed: ${response.status}`); | |
| } | |
| return response; | |
| } | |
| // 转换流式响应 | |
| async function* transformStreamResponse( | |
| readableStream: ReadableStream<Uint8Array>, | |
| ) { | |
| const reader = readableStream.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ""; | |
| try { | |
| 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() === "" || !line.startsWith("data:")) continue; | |
| const data = line.substring(5).trim(); | |
| if (data === "[DONE]") { | |
| yield "data: [DONE]\n\n"; | |
| continue; | |
| } | |
| try { | |
| const CodegptEvent = JSON.parse(data); | |
| // 转换为OpenAI格式的事件 | |
| const openAIEvent = { | |
| id: CodegptEvent.agentName || `chatcmpl-${Date.now()}`, | |
| object: "chat.completion.chunk", | |
| created: Math.floor(Date.now() / 1000), | |
| model: CodegptEvent.model || CodegptEvent.agentName, | |
| choices: [ | |
| { | |
| index: 0, | |
| delta: { | |
| reasoning_content: CodegptEvent.choices?.[0]?.delta?.contents?.[0]?.type === "think" ? CodegptEvent.choices[0].delta.contents[0].content || "" : "", | |
| content: CodegptEvent.choices?.[0]?.delta?.content || "", | |
| }, | |
| finish_reason: CodegptEvent.choices?.[0]?.finish_reason || null, | |
| }, | |
| ], | |
| }; | |
| yield `data: ${JSON.stringify(openAIEvent)}\n\n`; | |
| } catch (e) { | |
| console.error("Error parsing event:", e, "Line:", line); | |
| } | |
| } | |
| } | |
| } finally { | |
| reader.releaseLock(); | |
| } | |
| } | |
| // 转换非流式响应 | |
| async function transformNonStreamResponse(response: Response) { | |
| const CodegptResponse = await response.json(); | |
| return { | |
| id: CodegptResponse.id || `chatcmpl-${Date.now()}`, | |
| object: "chat.completion", | |
| created: Math.floor(Date.now() / 1000), | |
| model: CodegptResponse.model || CodegptResponse.agentName, | |
| choices: [ | |
| { | |
| index: 0, | |
| message: { | |
| role: "assistant", | |
| content: CodegptResponse.choices?.[0]?.message?.content || "", | |
| }, | |
| finish_reason: CodegptResponse.choices?.[0]?.finish_reason || "stop", | |
| }, | |
| ], | |
| usage: CodegptResponse.usage || { | |
| prompt_tokens: 0, | |
| completion_tokens: 0, | |
| total_tokens: 0, | |
| }, | |
| }; | |
| } | |
| // 主处理函数 | |
| async function handler(req: Request): Promise<Response> { | |
| const url = new URL(req.url); | |
| const path = url.pathname; | |
| const apiKey = getApiKeyFromRequest(req); | |
| if (!apiKey) { | |
| return new Response( | |
| // 没有填写 apikey 时返回的错误信息 | |
| JSON.stringify({ error: "Unauthorized", message: "Your API key or access token is invalid." }), | |
| { | |
| status: 401, | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| } | |
| ); | |
| } | |
| // CORS预检请求处理 | |
| if (req.method === "OPTIONS") { | |
| return new Response(null, { | |
| status: 204, | |
| headers: { | |
| "Access-Control-Allow-Origin": "*", | |
| "Access-Control-Allow-Methods": "GET, POST, OPTIONS", | |
| "Access-Control-Allow-Headers": "Content-Type, Authorization", | |
| "Access-Control-Max-Age": "86400", | |
| }, | |
| }); | |
| } | |
| try { | |
| // 模型列表接口 | |
| if (path === "/v1/models" && req.method === "GET") { | |
| const models = await getModels(apiKey); | |
| const openAIModels = transformModelsToOpenAIFormat(models); | |
| // console.log("🚀 ~ openAI格式models: ", openAIModels); | |
| return new Response(JSON.stringify(openAIModels), { | |
| status: 200, | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| }); | |
| } | |
| // 聊天完成接口 | |
| else if (path === "/v1/chat/completions" && req.method === "POST") { | |
| const requestBody = await req.json(); | |
| // console.log("🚀 ~ 外部客户端请求: ", requestBody); | |
| const stream = requestBody.stream === true; | |
| const codegptResponse = await handleChatCompletions(apiKey, requestBody, stream); | |
| if (stream) { | |
| const transformedStream = new ReadableStream({ | |
| async start(controller) { | |
| try { | |
| for await (const chunk of transformStreamResponse(codegptResponse.body!)) { | |
| controller.enqueue(new TextEncoder().encode(chunk)); | |
| } | |
| controller.close(); | |
| } catch (error) { | |
| console.error("Stream transformation error:", error); | |
| controller.error(error); | |
| } | |
| }, | |
| }); | |
| return new Response(transformedStream, { | |
| headers: { | |
| "Content-Type": "text/event-stream", | |
| "Cache-Control": "no-cache", | |
| "Connection": "keep-alive", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| }); | |
| } else { | |
| const transformedResponse = await transformNonStreamResponse(codegptResponse); | |
| return new Response(JSON.stringify(transformedResponse), { | |
| status: 200, | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| }); | |
| } | |
| } | |
| // 未找到路由 | |
| else { | |
| return new Response( | |
| JSON.stringify({ error: "Not found", message: "Endpoint not supported" }), | |
| { | |
| status: 404, | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| }, | |
| ); | |
| } | |
| } catch (error) { | |
| console.error("Request handling error:", error); | |
| return new Response( | |
| JSON.stringify({ error: "Internal server error", message: error.message }), | |
| { | |
| status: 500, | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Access-Control-Allow-Origin": "*", | |
| }, | |
| }, | |
| ); | |
| } | |
| } | |
| console.log(`Starting server on port ${PORT}...`); | |
| serve(handler, { port: PORT }); |