Spaces:
Sleeping
Sleeping
| import { serve } from "https://deno.land/std@0.224.0/http/server.ts"; | |
| // 配置区域 | |
| // 建议通过环境变量传入账号密码,或者在启动时交互式输入 | |
| const PUTER_USERNAME = Deno.env.get("PUTER_USERNAME") || ""; | |
| const PUTER_PASSWORD = Deno.env.get("PUTER_PASSWORD") || ""; | |
| const LOGIN_API_URL = "https://api.puter.com/login"; | |
| const PROXY_API_URL = "https://api.puter.com/drivers/call"; | |
| // 缓存 Token,避免每次请求都重新登录 | |
| let cachedAuthToken: string | null = null; | |
| /** | |
| * [核心功能] 自动登录 Puter 并获取 JWT Token | |
| * 使用纯 HTTP 请求,无浏览器依赖。 | |
| */ | |
| async function getAuthToken(): Promise<string> { | |
| if (cachedAuthToken) { | |
| return cachedAuthToken; | |
| } | |
| if (!PUTER_USERNAME || !PUTER_PASSWORD) { | |
| throw new Error("Missing credentials. Please set PUTER_USERNAME and PUTER_PASSWORD env vars."); | |
| } | |
| console.log(`[Auth] Attempting to login as ${PUTER_USERNAME}...`); | |
| // 发起登录请求 | |
| const response = await fetch(LOGIN_API_URL, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify({ | |
| username: PUTER_USERNAME, | |
| password: PUTER_PASSWORD, | |
| // 2026年的 Puter 可能需要一个 client_id 或 specific flag 来表明这是机器登录 | |
| // 这里我们假设基础的用户名密码流程依然有效 | |
| }), | |
| }); | |
| if (!response.ok) { | |
| const errText = await response.text(); | |
| throw new Error(`Login failed (${response.status}): ${errText}`); | |
| } | |
| const data = await response.json(); | |
| // 解析 Token 字段 | |
| // Puter 通常返回 { token: "..." } 或 { auth_token: "..." } 或 { user: ..., token: ... } | |
| const token = data.token || data.auth_token || data.user?.token; | |
| if (!token) { | |
| console.error("Login response:", data); | |
| throw new Error("Login successful but no token found in response."); | |
| } | |
| console.log("[Auth] Login successful. Token cached."); | |
| cachedAuthToken = token; | |
| return token; | |
| } | |
| serve(async (req: Request): Promise<Response> => { | |
| // 1. 处理 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", | |
| }, | |
| }); | |
| } | |
| const url = new URL(req.url); | |
| // 2. 路由:OpenAI Chat Completions | |
| if (req.method === "POST" && url.pathname === "/v1/chat/completions") { | |
| try { | |
| const openaiBody = await req.json(); | |
| const { messages, model = "gpt-5-nano" } = openaiBody; | |
| // [关键] 动态获取 Token | |
| // 如果 Token 失效,这里可以扩展逻辑来重新登录 | |
| const authToken = await getAuthToken(); | |
| // 3. 构造 Puter 请求 | |
| const puterPayload = { | |
| interface: "puter-chat-completion", | |
| driver: "ai-chat", | |
| method: "complete", | |
| test_mode: false, | |
| args: { | |
| messages: messages, | |
| model: model, | |
| }, | |
| auth_token: authToken, // 注入动态获取的 Token | |
| }; | |
| // 4. 转发请求 | |
| const puterResponse = await fetch(PROXY_API_URL, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| // 某些 API 可能需要在 Header 中也携带 Authorization, | |
| // 但根据你之前的日志,Puter 主要读取 body 中的 auth_token | |
| }, | |
| body: JSON.stringify(puterPayload), | |
| }); | |
| if (!puterResponse.ok) { | |
| // 如果返回 401 或 403,说明 Token 可能过期了 | |
| if (puterResponse.status === 401 || puterResponse.status === 403) { | |
| console.warn("[Auth] Token expired, clearing cache..."); | |
| cachedAuthToken = null; | |
| // 这里可以递归重试一次,但在简单示例中直接报错 | |
| } | |
| const errorText = await puterResponse.text(); | |
| throw new Error(`Puter API Error: ${errorText}`); | |
| } | |
| const puterData = await puterResponse.json(); | |
| // 5. 转换响应回 OpenAI 格式 | |
| const openaiResponse = { | |
| id: `chatcmpl-${crypto.randomUUID()}`, | |
| object: "chat.completion", | |
| created: Math.floor(Date.now() / 1000), | |
| model: model, | |
| choices: [{ | |
| index: puterData.result?.index || 0, | |
| message: { | |
| role: puterData.result?.message?.role || "assistant", | |
| content: puterData.result?.message?.content || "", | |
| }, | |
| finish_reason: puterData.result?.finish_reason || "stop", | |
| }], | |
| usage: { | |
| prompt_tokens: puterData.result?.usage?.prompt_tokens || 0, | |
| completion_tokens: puterData.result?.usage?.completion_tokens || 0, | |
| total_tokens: (puterData.result?.usage?.prompt_tokens || 0) + (puterData.result?.usage?.completion_tokens || 0), | |
| }, | |
| }; | |
| return new Response(JSON.stringify(openaiResponse), { | |
| headers: { "Content-Type": "application/json" }, | |
| }); | |
| } catch (error) { | |
| console.error("Proxy Error:", error); | |
| return new Response( | |
| JSON.stringify({ error: { message: error.message, type: "proxy_error" } }), | |
| { status: 500, headers: { "Content-Type": "application/json" } } | |
| ); | |
| } | |
| } | |
| return new Response("Not Found", { status: 404 }); | |
| }); | |
| console.log(`[2026-Proxy] Server starting...`); | |
| // 在启动前检查一次凭据(可选) | |
| if (!PUTER_USERNAME || !PUTER_PASSWORD) { | |
| console.warn("[WARN] PUTER_USERNAME and PUTER_PASSWORD not set. Auto-login will fail on first request."); | |
| } |