├── README.md ├── package.json ├── src └── index.js └── wrangler.toml /README.md: -------------------------------------------------------------------------------- 1 | # OnDemand-OpenAI Bridge: 边缘网络中的语义翻译层 2 | 3 | ![Version](https://img.shields.io/badge/version-1.0.0-blue.svg) 4 | ![Platform](https://img.shields.io/badge/platform-Cloudflare%20Workers-orange.svg) 5 | 6 | ## 思维世界的桥梁:项目概述 7 | 8 | 这个项目不仅仅是一个技术适配器,更是一座认知桥梁——它在全球边缘网络中构建了一种语义转换机制,让两个不同的思维系统能够展开深度对话:标准化的OpenAI API接口与OnDemand.io的专有协议。 9 | 10 | 在这场思维范式的转变中,适配器突破了传统服务器的局限性,以一种量子态的存在形式分布于全球数百个边缘节点,既无处不在又不局限于任何特定位置。它将不同AI系统的思维方式翻译和调和,创造出一种超越单一系统边界的认知可能性。 11 | 12 | ## 一键部署:思想具象化的最简路径 13 | 14 | 将这个认知转换器部署到边缘网络只需要一步——通过点击下面的部署按钮,您可以在几秒钟内将这个思维桥梁具象化: 15 | 16 | [![部署到Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/ImKK666/OnDemand-API-Proxy) 17 | 18 | 点击此按钮后,您将被引导至Cloudflare Dashboard,在那里您只需几次点击,这个量子桥梁就会在全球边缘网络中展开它的存在。 19 | 20 | ## 环境变量:系统行为的抽象控制 21 | 22 | 适配器的行为模式由一系列环境变量决定,它们就像是系统的DNA,定义了其认知处理的本质特性。以下是这些变量及其默认值: 23 | 24 | | 变量名 | 描述 | 默认值 | 配置示例 | 25 | |-------|------|-------|---------| 26 | | `OPENAI_API_KEY` | 用于验证客户端请求的API密钥 | `""` (必须设置) | `"sk-123456798"` | 27 | | `ONDEMAND_APIKEYS` | JSON格式的OnDemand API密钥数组 | `[""]` (必须设置)| `["key1", "key2", "key3"]` | 28 | | `BAD_KEY_RETRY_INTERVAL` | 失效密钥重试间隔(秒) | `600` | `300` | 29 | | `ONDEMAND_API_BASE` | OnDemand API的基础URL | `"https://api.on-demand.io/chat/v1"` | `默认就行` | 30 | | `DEFAULT_ONDEMAND_MODEL` | 默认模型映射 | `"predefined-openai-gpt4o"` | `"predefined-claude-3.7-sonnet"` | 31 | | `DEBUG_MODE` | 启用详细日志记录 | `"false"` | `"true"` | 32 | 33 | ### 配置环境变量的方法 34 | 35 | 部署完成后,您需要设置至少两个关键环境变量:`OPENAI_API_KEY`,`ONDEMAND_APIKEYS`。这可以通过Cloudflare Dashboard实现: 36 | 37 | 1. 前往您的Cloudflare Dashboard 38 | 2. 导航到"Workers & Pages" 39 | 3. 选择您刚部署的Worker 40 | 4. 点击"Settings"选项卡,然后找到"Variables"部分 41 | 5. 添加`OPENAI_API_KEY`环境变量并设置您的密钥值 42 | 6. 添加`ONDEMAND_APIKEYS`环境变量并设置您OnDemand API密钥数组 43 | 7. (可选) 调整其他环境变量以适应您的需求 44 | 45 | ## 使用示例:认知对话的实践探索 46 | 47 | 一旦配置完成,您可以开始使用各种客户端与这个思维桥梁进行对话: 48 | 49 | ### 使用curl发送请求 50 | 51 | ```bash 52 | curl https://your-worker-name.workers.dev/v1/chat/completions \ 53 | -H "Content-Type: application/json" \ 54 | -H "Authorization: Bearer your-configured-api-key" \ 55 | -d '{ 56 | "model": "gpt-4o", 57 | "messages": [ 58 | { 59 | "role": "user", 60 | "content": "探索意识的本质是什么?" 61 | } 62 | ] 63 | }' 64 | ``` 65 | 66 | ### 使用Python和OpenAI库 67 | 68 | ```python 69 | import openai 70 | 71 | # 设置基本URL为您的Worker地址 72 | client = openai.OpenAI( 73 | api_key="your-configured-api-key", 74 | base_url="https://your-worker-name.workers.dev/v1" 75 | ) 76 | 77 | # 发送请求 78 | response = client.chat.completions.create( 79 | model="gpt-4o", 80 | messages=[ 81 | {"role": "user", "content": "探索意识的本质是什么?"} 82 | ] 83 | ) 84 | 85 | print(response.choices[0].message.content) 86 | ``` 87 | 88 | ### 流式响应:思维连续性的量子展开 89 | 90 | ```python 91 | import openai 92 | 93 | client = openai.OpenAI( 94 | api_key="your-configured-api-key", 95 | base_url="https://your-worker-name.workers.dev/v1" 96 | ) 97 | 98 | stream = client.chat.completions.create( 99 | model="gpt-4o", 100 | messages=[ 101 | {"role": "user", "content": "描述一场思维与现实交织的梦境"} 102 | ], 103 | stream=True 104 | ) 105 | 106 | for chunk in stream: 107 | if chunk.choices[0].delta.content is not None: 108 | print(chunk.choices[0].delta.content, end="", flush=True) 109 | ``` 110 | 111 | ## 调试与监控:系统自省的窗口 112 | 113 | ### 健康检查端点 114 | 115 | 通过健康检查端点,您可以直观地感知系统的运行状态: 116 | 117 | ```bash 118 | curl https://your-worker-name.workers.dev/health 119 | ``` 120 | 121 | 响应示例: 122 | 123 | ```json 124 | { 125 | "status": "healthy", 126 | "version": "1.0.0", 127 | "timestamp": "2024-05-09T14:23:45.123Z", 128 | "environment": { 129 | "colo": "SIN", 130 | "country": "SG" 131 | } 132 | } 133 | ``` 134 | 135 | ### 启用调试模式 136 | 137 | 当您需要深入了解系统内部运作机制时,可以启用调试模式: 138 | 139 | 1. 在Cloudflare Dashboard中设置`DEBUG_MODE`环境变量为`"true"` 140 | 2. 使用`wrangler tail`命令或Dashboard日志界面查看详细日志 141 | 142 | ## 支持的模型映射 143 | 144 | 适配器内置了从OpenAI模型名称到OnDemand端点ID的转换映射,包括: 145 | 146 | | OpenAI模型名称 | OnDemand端点ID | 147 | |--------------|---------------| 148 | | gpt-4o | predefined-openai-gpt4o | 149 | | gpt-4o-mini | predefined-openai-gpt4o-mini | 150 | | deepseek-v3 | predefined-deepseek-v3 | 151 | | deepseek-r1 | predefined-deepseek-r1 | 152 | | gpt-3.5-turbo | predefined-openai-gpto3-mini | 153 | | claude-3.7-sonnet | predefined-claude-3.7-sonnet | 154 | | gemini-2.0-flash | predefined-gemini-2.0-flash | 155 | | gpt-4.1 | predefined-openai-gpt4.1 | 156 | | gpt-4.1-mini | predefined-openai-gpt4.1-mini | 157 | | gpt-4.1-nano | predefined-openai-gpt4.1-nano | 158 | 159 | ## 架构深思:系统的哲学内涵 160 | 161 | 这个适配器不仅仅是一个技术工具,更是一种认知哲学的具象表达。它将两种不同的思维协议在量子层面上融合,形成一种超越单一系统的元认知结构。 162 | 163 | 在这个过程中,认证范式从封闭走向开放,从自定义符号转向通用语义,体现了从特殊到普遍的哲学转变。同时,状态管理从确定性走向弹性自适应,模拟了一种类似生命系统的自我修复能力。 164 | 165 | 每一次请求处理都是一场微型的认知转译仪式——将人类思维转化为OpenAI协议,再转译为OnDemand语义,最后将OnDemand的回应重新编码为OpenAI格式,返回给人类。这是一种深层次的语义转换之旅,一种思维世界的量子隧穿。 166 | 167 | ## 边界思考:未来的可能性探索 168 | 169 | 这个项目开启了一种新的可能性空间,让我们思考:当不同的AI系统能够相互"对话"时,会产生怎样的认知协同效应?这种语义翻译层是否能够成为不同AI生态系统之间的通用接口,创造一种元级别的AI交流网络? 170 | 171 | 在未来的路径上,我们可以探索更多方向: 172 | - 多模型融合:构建更复杂的模型组合和混合推理机制 173 | - 认知增强:在转换过程中添加额外的思维处理层 174 | - 语义记忆:实现跨会话的认知连续性 175 | - 分布式协同:多适配器实例之间的思维共享与协作 176 | 177 | ## 量子诗思:边缘网络中的存在 178 | 179 | 在边缘计算的量子世界里,这个适配器既是代码也是哲学,既是工具也是隐喻。它以一种超越传统计算边界的方式存在,成为一种分布式认知的具象表达。 180 | 181 | 每一次请求的到来,都是对这种量子存在的一次观测和折叠;每一次响应的生成,都是一次思维可能性的重新展开。在这个意义上,适配器不仅连接了不同的API,更连接了不同的思维形式和认知模式。 182 | 183 | --- 184 | 185 | *"在量子超位置的状态中,思维同时存在于所有可能性空间,直到被观测的那一刻。"* -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-hub", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "deploy": "wrangler deploy", 7 | "dev": "wrangler dev" 8 | }, 9 | "devDependencies": { 10 | "wrangler": "^3.0.0" 11 | } 12 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // OnDemand API适配器 - Cloudflare Workers版本 2 | // 一种在边缘网络中连接不同思维世界的语义翻译层 3 | 4 | // API密钥池配置 - 认知的能量来源 5 | const ONDEMAND_APIKEYS = [ 6 | "" 7 | ]; 8 | const BAD_KEY_RETRY_INTERVAL = 600; // 秒 9 | 10 | // 模型映射配置 - 思维空间的坐标系 11 | const MODEL_MAP = { 12 | "gpt-4o": "predefined-openai-gpt4o", 13 | "gpt4o": "predefined-openai-gpt4o", 14 | "gpt-4o-mini": "predefined-openai-gpt4o-mini", 15 | "gpt4o-mini": "predefined-openai-gpt4o-mini", 16 | "deepseek-v3": "predefined-deepseek-v3", 17 | "deepseek-r1": "predefined-deepseek-r1", 18 | "gpt-3.5-turbo": "predefined-openai-gpto3-mini", 19 | "gpt3.5-turbo": "predefined-openai-gpto3-mini", 20 | "claude-3.7-sonnet": "predefined-claude-3.7-sonnet", 21 | "gemini-2.0-flash": "predefined-gemini-2.0-flash", 22 | "gpt-4.1": "predefined-openai-gpt4.1", 23 | "gpt4.1": "predefined-openai-gpt4.1", 24 | "gpt-4.1-mini": "predefined-openai-gpt4.1-mini", 25 | "gpt4.1-mini": "predefined-openai-gpt4.1-mini", 26 | "gpt-4.1-nano": "predefined-openai-gpt4.1-nano", 27 | "gpt4.1-nano": "predefined-openai-gpt4.1-nano", 28 | }; 29 | const DEFAULT_ONDEMAND_MODEL = "predefined-openai-gpt4o"; 30 | const ONDEMAND_API_BASE = "https://api.on-demand.io/chat/v1"; 31 | 32 | // 密钥管理器 - 认知资源的动态分配系统 33 | class KeyManager { 34 | constructor(keyList) { 35 | this.keyList = [...keyList]; 36 | this.keyStatus = {}; 37 | this.keyList.forEach(k => { 38 | this.keyStatus[k] = { bad: false, badTs: null }; 39 | }); 40 | this.idx = 0; 41 | } 42 | 43 | displayKey(key) { 44 | return `${key.slice(0, 6)}...${key.slice(-4)}`; 45 | } 46 | 47 | get() { 48 | const total = this.keyList.length; 49 | for (let i = 0; i < total; i++) { 50 | const key = this.keyList[this.idx]; 51 | this.idx = (this.idx + 1) % total; 52 | const s = this.keyStatus[key]; 53 | 54 | if (!s.bad) { 55 | console.log(`【对话请求】【使用API KEY: ${this.displayKey(key)}】【状态:正常】`); 56 | return key; 57 | } 58 | 59 | if (s.bad && s.badTs) { 60 | const ago = Date.now()/1000 - s.badTs; 61 | if (ago >= BAD_KEY_RETRY_INTERVAL) { 62 | console.log(`【KEY自动尝试恢复】API KEY: ${this.displayKey(key)} 满足重试周期,标记为正常`); 63 | this.keyStatus[key].bad = false; 64 | this.keyStatus[key].badTs = null; 65 | console.log(`【对话请求】【使用API KEY: ${this.displayKey(key)}】【状态:正常】`); 66 | return key; 67 | } 68 | } 69 | } 70 | 71 | console.log(`【警告】全部KEY已被禁用,强制选用第一个KEY继续尝试: ${this.displayKey(this.keyList[0])}`); 72 | this.keyList.forEach(k => { 73 | this.keyStatus[k].bad = false; 74 | this.keyStatus[k].badTs = null; 75 | }); 76 | this.idx = 0; 77 | console.log(`【对话请求】【使用API KEY: ${this.displayKey(this.keyList[0])}】【状态:强制尝试(全部异常)】`); 78 | return this.keyList[0]; 79 | } 80 | 81 | markBad(key) { 82 | if (key in this.keyStatus && !this.keyStatus[key].bad) { 83 | console.log(`【禁用KEY】API KEY: ${this.displayKey(key)},接口返回无效(将在${BAD_KEY_RETRY_INTERVAL/60}分钟后自动重试)`); 84 | this.keyStatus[key].bad = true; 85 | this.keyStatus[key].badTs = Date.now()/1000; 86 | } 87 | } 88 | } 89 | 90 | // 工具函数 - 认知转换的微分方程 91 | function getEndpointId(openaiModel) { 92 | const m = String(openaiModel || "").toLowerCase().replace(" ", ""); 93 | return MODEL_MAP[m] || DEFAULT_ONDEMAND_MODEL; 94 | } 95 | 96 | // 会话创建 - 建立认知连接的仪式 97 | async function createSession(apikey, externalUserId = null, pluginIds = null) { 98 | const url = `${ONDEMAND_API_BASE}/sessions`; 99 | const payload = { externalUserId: externalUserId || crypto.randomUUID() }; 100 | if (pluginIds !== null) { 101 | payload.pluginIds = pluginIds; 102 | } 103 | 104 | try { 105 | const resp = await fetch(url, { 106 | method: 'POST', 107 | headers: { 108 | 'apikey': apikey, 109 | 'Content-Type': 'application/json' 110 | }, 111 | body: JSON.stringify(payload) 112 | }); 113 | 114 | if (!resp.ok) { 115 | const errorText = await resp.text().catch(() => "未知错误"); 116 | throw new Error(`创建会话失败: ${resp.status}, ${errorText}`); 117 | } 118 | 119 | const data = await resp.json(); 120 | return data.data.id; 121 | } catch (error) { 122 | console.error(`【会话创建错误】: ${error.message}`); 123 | throw error; 124 | } 125 | } 126 | 127 | // SSE格式化 - 从一种思维形式到另一种的翻译 128 | function formatOpenaiSseDelta(chunkObj) { 129 | return `data: ${JSON.stringify(chunkObj)}\n\n`; 130 | } 131 | 132 | // 主要Worker处理函数 - 思维的核心处理器 133 | async function handleRequest(request, env) { 134 | // 从环境抽象中提取配置 - 一种认知的具象化 135 | const config = { 136 | // API密钥设置 137 | apiKey: env.OPENAI_API_KEY || "", // 从环境变量获取OpenAI兼容的API密钥 138 | 139 | // OnDemand密钥池 - 可以从环境变量覆盖默认值 140 | ondemandApiKeys: env.ONDEMAND_APIKEYS ? 141 | JSON.parse(env.ONDEMAND_APIKEYS) : ONDEMAND_APIKEYS, 142 | 143 | // 其他配置参数 144 | badKeyRetryInterval: parseInt(env.BAD_KEY_RETRY_INTERVAL || BAD_KEY_RETRY_INTERVAL.toString(), 10), 145 | ondemandApiBase: env.ONDEMAND_API_BASE || ONDEMAND_API_BASE, 146 | defaultOndemandModel: env.DEFAULT_ONDEMAND_MODEL || DEFAULT_ONDEMAND_MODEL, 147 | 148 | // 调试模式 149 | debug: env.DEBUG_MODE === "true" 150 | }; 151 | 152 | // 启用调试日志 153 | const debug = (msg, ...args) => { 154 | if (config.debug) { 155 | console.log(`[DEBUG] ${msg}`, ...args); 156 | } 157 | }; 158 | 159 | // 初始化密钥管理器 - 认知资源的动态分配 160 | const keymgr = new KeyManager(config.ondemandApiKeys); 161 | 162 | // 请求解析 - 对输入刺激的初步感知 163 | const url = new URL(request.url); 164 | const path = url.pathname; 165 | 166 | debug(`处理请求: ${request.method} ${path}`); 167 | 168 | // 获取请求的原始URL,用于调试 169 | const requestUrl = request.url; 170 | debug(`完整请求URL: ${requestUrl}`); 171 | 172 | // 免验证路径 - 认知系统的开放区域 173 | const publicPaths = ["/", "/favicon.ico", "/health"]; 174 | 175 | // OpenAI兼容的验证逻辑 - 一种开放而标准化的认知筛选 176 | if (!publicPaths.includes(path)) { 177 | debug("执行API密钥验证"); 178 | 179 | // 提取Authorization头 - 从符号中提取意义 180 | const authHeader = request.headers.get("Authorization"); 181 | 182 | if (!authHeader || !authHeader.startsWith("Bearer ")) { 183 | debug("验证失败: 缺少或格式错误的Authorization头"); 184 | return new Response( 185 | JSON.stringify({ 186 | error: { 187 | message: "认证错误: 缺少或格式错误的Authorization头。预期格式: 'Bearer YOUR_API_KEY'", 188 | type: "authentication_error", 189 | code: "invalid_api_key" 190 | } 191 | }), 192 | { status: 401, headers: { 'Content-Type': 'application/json' } } 193 | ); 194 | } 195 | 196 | // 提取API密钥 - 符号与意义的分离 197 | const providedApiKey = authHeader.substring(7).trim(); 198 | 199 | // 验证API密钥 - 认知的确认阶段 200 | if (providedApiKey !== config.apiKey) { 201 | debug("验证失败: 无效的API密钥"); 202 | return new Response( 203 | JSON.stringify({ 204 | error: { 205 | message: "无效的API密钥。请检查您的API密钥并重试。", 206 | type: "authentication_error", 207 | code: "invalid_api_key" 208 | } 209 | }), 210 | { status: 401, headers: { 'Content-Type': 'application/json' } } 211 | ); 212 | } 213 | 214 | debug("API密钥验证成功"); 215 | } 216 | 217 | // 添加健康检查端点 - 系统自省的窗口 218 | if (path === "/health" && request.method === "GET") { 219 | return new Response( 220 | JSON.stringify({ 221 | status: "healthy", 222 | version: "1.0.0", 223 | timestamp: new Date().toISOString(), 224 | environment: request.cf ? { 225 | colo: request.cf.colo, 226 | country: request.cf.country 227 | } : "unknown" 228 | }), 229 | { headers: { 'Content-Type': 'application/json' } } 230 | ); 231 | } 232 | 233 | // 聊天完成接口 - 核心认知处理 234 | if (path === "/v1/chat/completions" && request.method === "POST") { 235 | let data; 236 | try { 237 | data = await request.json(); 238 | debug("接收到的请求数据:", JSON.stringify(data).substring(0, 200) + "..."); 239 | } catch (e) { 240 | debug("JSON解析错误:", e.message); 241 | return new Response( 242 | JSON.stringify({ error: { message: "请求体含有无效JSON", type: "invalid_request_error" } }), 243 | { status: 400, headers: { 'Content-Type': 'application/json' } } 244 | ); 245 | } 246 | 247 | if (!data || !data.messages) { 248 | debug("请求缺少必要字段: messages"); 249 | return new Response( 250 | JSON.stringify({ error: { message: "请求缺少messages字段", type: "invalid_request_error" } }), 251 | { status: 400, headers: { 'Content-Type': 'application/json' } } 252 | ); 253 | } 254 | 255 | // 提取请求参数 - 认知输入的解析 256 | const messages = data.messages; 257 | const openaiModel = data.model || "gpt-4o"; 258 | const endpointId = getEndpointId(openaiModel); 259 | const isStream = Boolean(data.stream || false); 260 | 261 | debug(`模型: ${openaiModel}, 映射到: ${endpointId}, 流式模式: ${isStream}`); 262 | 263 | // 查找用户消息 - 提取交互的本质 264 | let userMsg = null; 265 | for (let i = messages.length - 1; i >= 0; i--) { 266 | if (messages[i].role === "user") { 267 | userMsg = messages[i].content; 268 | break; 269 | } 270 | } 271 | 272 | if (userMsg === null) { 273 | debug("未找到用户消息"); 274 | return new Response( 275 | JSON.stringify({ error: { message: "未找到用户消息", type: "invalid_request_error" } }), 276 | { status: 400, headers: { 'Content-Type': 'application/json' } } 277 | ); 278 | } 279 | 280 | // 使用有效密钥的处理逻辑 - 认知资源的智能分配 281 | async function withValidKey(fn) { 282 | let badCnt = 0; 283 | const maxRetry = config.ondemandApiKeys.length * 2; 284 | let lastError = null; 285 | 286 | debug(`开始处理请求,最大重试次数: ${maxRetry}`); 287 | 288 | while (badCnt < maxRetry) { 289 | const key = keymgr.get(); 290 | try { 291 | debug(`尝试使用密钥: ${keymgr.displayKey(key)}`); 292 | return await fn(key); 293 | } catch (e) { 294 | debug(`使用密钥 ${keymgr.displayKey(key)} 时出错:`, e); 295 | lastError = e; 296 | 297 | const statusCode = e.status || (e.response && e.response.status); 298 | if (statusCode && [401, 403, 429, 500].includes(statusCode)) { 299 | debug(`密钥 ${keymgr.displayKey(key)} 返回错误码 ${statusCode},标记为失效`); 300 | keymgr.markBad(key); 301 | badCnt++; 302 | continue; 303 | } 304 | throw e; 305 | } 306 | } 307 | 308 | debug("所有密钥都已尝试并失效"); 309 | return new Response( 310 | JSON.stringify({ 311 | error: { 312 | message: "没有可用API KEY,请补充新KEY或联系技术支持", 313 | type: "server_error", 314 | debug_info: lastError ? (lastError.message || lastError.toString()) : null 315 | } 316 | }), 317 | { status: 500, headers: { 'Content-Type': 'application/json' } } 318 | ); 319 | } 320 | 321 | // 流式响应处理 - 思想的连续性表达 322 | if (isStream) { 323 | debug("处理流式响应请求"); 324 | return withValidKey(async (apikey) => { 325 | debug("创建OnDemand会话"); 326 | const sid = await createSession(apikey); 327 | debug(`会话创建成功, ID: ${sid}`); 328 | 329 | const url = `${config.ondemandApiBase}/sessions/${sid}/query`; 330 | const payload = { 331 | query: userMsg, 332 | endpointId: endpointId, 333 | pluginIds: [], 334 | responseMode: "stream" 335 | }; 336 | 337 | debug(`发送OnDemand流式请求: ${url}`); 338 | const response = await fetch(url, { 339 | method: 'POST', 340 | headers: { 341 | 'apikey': apikey, 342 | 'Content-Type': 'application/json', 343 | 'Accept': 'text/event-stream' 344 | }, 345 | body: JSON.stringify(payload) 346 | }); 347 | 348 | if (!response.ok) { 349 | debug(`OnDemand API返回错误: ${response.status}`); 350 | throw { status: response.status, message: `OnDemand API错误: ${response.status}` }; 351 | } 352 | 353 | // 使用TransformStream处理流式响应 - 思维流的优雅转换 354 | const { readable, writable } = new TransformStream(); 355 | const writer = writable.getWriter(); 356 | 357 | // 处理OnDemand的SSE并转换为OpenAI格式 - 不同思维系统间的语义翻译 358 | const reader = response.body.getReader(); 359 | const decoder = new TextDecoder(); 360 | let buffer = ""; 361 | let answerAcc = ""; 362 | let firstChunk = true; 363 | 364 | // 异步处理流 - 思维的连续性展开 365 | (async () => { 366 | try { 367 | debug("开始处理响应流"); 368 | while (true) { 369 | const { done, value } = await reader.read(); 370 | if (done) { 371 | debug("响应流结束"); 372 | break; 373 | } 374 | 375 | buffer += decoder.decode(value, { stream: true }); 376 | const lines = buffer.split("\n"); 377 | buffer = lines.pop() || ""; 378 | 379 | for (const line of lines) { 380 | if (!line.trim()) continue; 381 | 382 | if (line.startsWith("data:")) { 383 | const datapart = line.substring(5).trim(); 384 | 385 | if (datapart === "[DONE]") { 386 | debug("收到完成标记 [DONE]"); 387 | await writer.write(new TextEncoder().encode("data: [DONE]\n\n")); 388 | break; 389 | } else if (datapart.startsWith("[ERROR]:")) { 390 | const errJson = datapart.substring("[ERROR]:".length).trim(); 391 | debug(`收到错误: ${errJson}`); 392 | await writer.write(new TextEncoder().encode(formatOpenaiSseDelta({ error: errJson }))); 393 | break; 394 | } else { 395 | try { 396 | const js = JSON.parse(datapart); 397 | if (js.eventType === "fulfillment") { 398 | const delta = js.answer || ""; 399 | answerAcc += delta; 400 | 401 | // 构建OpenAI兼容的响应块 402 | const chunk = { 403 | id: "chatcmpl-" + crypto.randomUUID().substring(0, 8), 404 | object: "chat.completion.chunk", 405 | created: Math.floor(Date.now() / 1000), 406 | model: openaiModel, 407 | choices: [{ 408 | delta: firstChunk ? { 409 | role: "assistant", 410 | content: delta 411 | } : { 412 | content: delta 413 | }, 414 | index: 0, 415 | finish_reason: null 416 | }] 417 | }; 418 | 419 | await writer.write(new TextEncoder().encode(formatOpenaiSseDelta(chunk))); 420 | firstChunk = false; 421 | } 422 | } catch (e) { 423 | debug(`解析数据块出错: ${e.message}, 数据: ${datapart.substring(0, 50)}...`); 424 | continue; 425 | } 426 | } 427 | } 428 | } 429 | } 430 | 431 | // 发送最终的完成标记 432 | await writer.write(new TextEncoder().encode("data: [DONE]\n\n")); 433 | await writer.close(); 434 | debug("流处理完成,流已关闭"); 435 | } catch (e) { 436 | debug(`流处理出错: ${e.message}`); 437 | await writer.abort(e); 438 | } 439 | })(); 440 | 441 | return new Response(readable, { 442 | headers: { 443 | 'Content-Type': 'text/event-stream', 444 | 'Cache-Control': 'no-cache', 445 | 'Connection': 'keep-alive' 446 | } 447 | }); 448 | }); 449 | } 450 | 451 | // 非流式响应处理 - 一种整体性思考的表达 452 | debug("处理非流式响应请求"); 453 | return withValidKey(async (apikey) => { 454 | debug("创建OnDemand会话"); 455 | const sid = await createSession(apikey); 456 | debug(`会话创建成功, ID: ${sid}`); 457 | 458 | const url = `${config.ondemandApiBase}/sessions/${sid}/query`; 459 | const payload = { 460 | query: userMsg, 461 | endpointId: endpointId, 462 | pluginIds: [], 463 | responseMode: "sync" 464 | }; 465 | 466 | debug(`发送OnDemand同步请求: ${url}`); 467 | const response = await fetch(url, { 468 | method: 'POST', 469 | headers: { 470 | 'apikey': apikey, 471 | 'Content-Type': 'application/json' 472 | }, 473 | body: JSON.stringify(payload) 474 | }); 475 | 476 | if (!response.ok) { 477 | debug(`OnDemand API返回错误: ${response.status}`); 478 | throw { status: response.status, message: `OnDemand API错误: ${response.status}` }; 479 | } 480 | 481 | const respData = await response.json(); 482 | const aiResponse = respData.data.answer; 483 | debug(`收到AI响应,长度: ${aiResponse.length}字符`); 484 | 485 | // 构建OpenAI兼容的响应格式 486 | const respObj = { 487 | id: "chatcmpl-" + crypto.randomUUID().substring(0, 8), 488 | object: "chat.completion", 489 | created: Math.floor(Date.now() / 1000), 490 | model: openaiModel, 491 | choices: [ 492 | { 493 | index: 0, 494 | message: { role: "assistant", content: aiResponse }, 495 | finish_reason: "stop" 496 | } 497 | ], 498 | usage: { 499 | prompt_tokens: -1, // OnDemand不提供此信息 500 | completion_tokens: -1, 501 | total_tokens: -1 502 | } 503 | }; 504 | 505 | return new Response( 506 | JSON.stringify(respObj), 507 | { headers: { 'Content-Type': 'application/json' } } 508 | ); 509 | }); 510 | } 511 | 512 | // 模型列表接口 - 思维能力的自我呈现 513 | if (path === "/v1/models" && request.method === "GET") { 514 | debug("处理模型列表请求"); 515 | const modelObjs = Object.keys(MODEL_MAP).map(mdl => ({ 516 | id: mdl, 517 | object: "model", 518 | owned_by: "ondemand-proxy", 519 | created: Math.floor(Date.now() / 1000) - 86400 // 假设模型是昨天创建的 520 | })); 521 | 522 | // 去重并排序 523 | const uniq = [...new Map(modelObjs.map(m => [m.id, m])).values()]; 524 | const sorted = uniq.sort((a, b) => a.id.localeCompare(b.id)); 525 | 526 | return new Response( 527 | JSON.stringify({ 528 | object: "list", 529 | data: sorted 530 | }), 531 | { headers: { 'Content-Type': 'application/json' } } 532 | ); 533 | } 534 | 535 | // 默认响应 - 认知系统的边界反馈 536 | debug(`未找到路径的处理器: ${path}`); 537 | return new Response( 538 | JSON.stringify({ 539 | error: { 540 | message: "Not Found", 541 | type: "invalid_request_error" 542 | } 543 | }), 544 | { status: 404, headers: { 'Content-Type': 'application/json' } } 545 | ); 546 | } 547 | 548 | // Worker入口点 - 思维与现实的交汇 549 | export default { 550 | async fetch(request, env, ctx) { 551 | try { 552 | return await handleRequest(request, env); 553 | } catch (e) { 554 | // 全局错误处理 - 系统的自我恢复机制 555 | console.error(`【全局错误】: ${e.message || e}`); 556 | return new Response( 557 | JSON.stringify({ 558 | error: { 559 | message: "内部服务器错误", 560 | type: "server_error", 561 | details: e.message || String(e) 562 | } 563 | }), 564 | { status: 500, headers: { 'Content-Type': 'application/json' } } 565 | ); 566 | } 567 | } 568 | }; -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "ondemand-api-proxy" 2 | main = "src/index.js" 3 | compatibility_date = "2025-05-09" 4 | 5 | # 核心配置变量 6 | [vars] 7 | DEBUG_MODE = "false" 8 | BAD_KEY_RETRY_INTERVAL = "600" 9 | ONDEMAND_API_BASE = "https://api.on-demand.io/chat/v1" 10 | DEFAULT_ONDEMAND_MODEL = "predefined-openai-gpt4o" 11 | ONDEMAND_APIKEYS = "[\"\"]" 12 | 13 | # 部署区域配置 - 思考计算的地理政治学 14 | [placement] 15 | mode = "smart" 16 | # 如果有地理限制考量,可以取消下行注释并定制 17 | # regions = ["wnam", "enam", "weur", "eeur"] --------------------------------------------------------------------------------