├── .dockerignore ├── .env.template ├── .github └── FUNDING.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README_CN.md ├── app.js ├── docker-compose.yml ├── package.json ├── pictures ├── api.png ├── token.png └── usage.png ├── pnpm-lock.yaml └── vercel.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .env 4 | .git 5 | .gitignore 6 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | BOT_ID=default_bot_id 3 | BOT_CONFIG={"model_name_1": "bot_id_1", "model_name_2": "bot_id_2", "model_name_3": "bot_id_3"} 4 | COZE_API_BASE=api.coze.com 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fatwang2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 使用Node.js官方镜像作为基础镜像 2 | FROM node:18-alpine 3 | 4 | # 设置环境变量配置镜像源 5 | ENV NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/ 6 | ENV PNPM_REGISTRY=https://registry.npmmirror.com/ 7 | 8 | # 设置工作目录 9 | WORKDIR /app 10 | 11 | # 复制package.json和pnpm-lock.yaml(如果存在) 12 | COPY package.json pnpm-lock.yaml* ./ 13 | 14 | # 安装pnpm 15 | RUN npm install -g pnpm 16 | 17 | # 安装依赖 18 | RUN pnpm install 19 | 20 | # 复制项目文件 21 | COPY . . 22 | 23 | # 暴露端口 24 | EXPOSE 3000 25 | 26 | # 启动应用 27 | CMD ["pnpm", "start"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [fatwang2] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## C2O 2 | **English** · [中文](README_CN.md) 3 | 4 | **Use Coze on your favorite OpenAI client.** 5 | 6 | This project converts the Coze API to the OpenAI API format, giving you access to [Coze](https://www.coze.com) LLMs, knowledge base, plugins, and workflows within your preferred OpenAI clients. 7 | 8 | ## Features 9 | - Convert Coze API into an OpenAI API 10 | - Support streaming and blocking 11 | - Supports multi-bot switching 12 | 13 | ## Preparation 14 | 1. Register with [coze.com](https://www.coze.com) or [coze.cn](https://www.coze.cn)and obtain your API token 15 | ![cozeapitoken](pictures/token.png) 16 | 17 | 2. Create your bot and publish it to the API 18 | ![cozeapi](pictures/api.png) 19 | 20 | 3. Obtain the bot's ID,the number after the bot parameter, and configure it as an environment variable 21 | ```bash 22 | https://www.coze.com/space/73428668341****/bot/73428668***** 23 | ``` 24 | 25 | ## Deployment 26 | ### Zeabur 27 | [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/BZ515Z?referralCode=fatwang2) 28 | 29 | ### Vercel 30 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/fatwang2/coze2openai&env=BOT_ID&envDescription=COZE_BOT_ID) 31 | 32 | **Note:** Vercel's serverless functions have a 10-second timeout limit. 33 | 34 | ### Railway 35 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/yM5tQL?referralCode=mDim7U) 36 | 37 | ### Docker Deployment 38 | 39 | 1. Ensure Docker and Docker Compose are installed on your machine. 40 | 41 | 2. Clone the project repository: 42 | ``` 43 | git clone https://github.com/your-username/coze2openai.git 44 | cd coze2openai 45 | ``` 46 | 47 | 3. Create and configure the `.env` file: 48 | ``` 49 | cp .env.template .env 50 | ``` 51 | Edit the `.env` file and fill in your BOT_ID and other necessary configurations. 52 | 53 | 4. Build and start the Docker container: 54 | ``` 55 | docker-compose up -d 56 | ``` 57 | 58 | 5. Visit `http://localhost:3000` to confirm that the service is running correctly. 59 | 60 | To stop the service, run: 61 | ``` 62 | docker-compose down 63 | ``` 64 | 65 | Note: The Dockerfile uses Taobao NPM mirror, you can comment out or replace other different mirror: 66 | ```Dockerfile 67 | ENV NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/ 68 | ENV PNPM_REGISTRY=https://registry.npmmirror.com/ 69 | ``` 70 | 71 | ### Local Deployment 72 | 1. Set the environment variable on `.env` file 73 | ```bash 74 | BOT_ID=xxxx 75 | ``` 76 | 77 | 2. Install dependencies 78 | ```bash 79 | pnpm install 80 | ``` 81 | 82 | 3. Run the project 83 | ```bash 84 | pnpm start 85 | ``` 86 | 87 | ## Usage 88 | 1. OpenAI Clients 89 | 90 | ![botgem](pictures/usage.png) 91 | 92 | 2. Code 93 | 94 | ```JavaScript 95 | const response = await fetch('http://localhost:3000/v1/chat/completions', { 96 | method: 'POST', 97 | headers: { 98 | 'Content-Type': 'application/json', 99 | 'Authorization': 'Bearer YOUR_COZE_API_KEY', 100 | }, 101 | body: JSON.stringify({ 102 | model: 'model_name', 103 | messages: [ 104 | { role: 'system', content: 'You are a helpful assistant.' }, 105 | { role: 'user', content: 'Hello, how are you?' }, 106 | ], 107 | }), 108 | }); 109 | 110 | const data = await response.json(); 111 | console.log(data); 112 | ``` 113 | ## Environment Variable 114 | This project provides some additional configuration items set with environment variables: 115 | 116 | | Environment Variable | Required | Description | Example | 117 | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | 118 | | `BOT_ID` | Yes | The ID of the bot. Obtain it from the Develop page URL of your bot in Coze. The number after the bot parameter is the bot ID.| `73428668*****`| 119 | | `BOT_CONFIG` | No | Configure different models to correspond to different bot ids to enable fast bot switching on the client side. Models that are not included will request the default BOT_ID | `{"model_name_1": "bot_id_1", "model_name_2": "bot_id_2", "model_name_3": "bot_id_3"}`| 120 | | `COZE_API_BASE` | No | Choose coze.com or coze.cn | `api.coze.com, api.coze.cn`| 121 | 122 | ## Roadmap 123 | **Coming Soon** 124 | * Image support 125 | * Audio-to-text 126 | * Text-to-audio 127 | 128 | **Available Now** 129 | * Coze.cn 130 | * Multi-bot switching 131 | * Workflow, Plugins, Knowledge base 132 | * Continuous dialogue with the history of chat 133 | * Zeabur & Vercel & Railway deployment 134 | * Streaming & Blocking 135 | * Docker deployment 136 | 137 | ## Contact 138 | Feel free to reach out for any questions or feedback 139 | 140 | [X](https://sum4all.site/twitter)\ 141 | [telegram](https://sum4all.site/telegram) 142 | 143 | Buy Me A Coffee 144 | 145 | ## License 146 | This project is licensed under the MIT License. 147 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # C2O 2 | [English](README.md) · **中文** 3 | 4 | **在您喜爱的 OpenAI 客户端上使用 Coze.** 5 | 6 | 该项目将 Coze API 转换为 OpenAI API 格式,使您可以在您喜爱的 OpenAI 客户端中访问 [Coze](https://www.coze.com) 的LLMs、知识库、插件和工作流程. 7 | 8 | # 功能 9 | - 支持 Coze API 转换为 OpenAI API 格式 10 | - 支持流式、非流式输出 11 | - 支持多机器人快速切换 12 | 13 | # 准备工作 14 | 1. 在 [coze.com](https://www.coze.com)或 [coze.cn](https://www.coze.cn)注册并获取您的 API 令牌 15 | ![cozeapitoken](pictures/token.png) 16 | 17 | 2. 创建您的机器人并发布到 API 18 | ![cozeapi](pictures/api.png) 19 | 20 | 3. 获取机器人的 ID,即机器人参数后面的数字,并将其配置为环境变量 21 | ```bash 22 | https://www.coze.com/space/73428668341****/bot/73428668***** 23 | ``` 24 | 25 | # 部署 26 | ## Zeabur 27 | [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/BZ515Z?referralCode=fatwang2) 28 | 29 | ## Vercel 30 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/fatwang2/coze2openai&env=BOT_ID&envDescription=COZE_BOT_ID) 31 | 32 | **注意:** Vercel 的无服务器函数有 10 秒的超时限制 33 | 34 | ## Railway 35 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/yM5tQL?referralCode=mDim7U) 36 | 37 | ## Docker部署 38 | 39 | 1. 确保您的机器上安装了Docker和Docker Compose。 40 | 41 | 2. 克隆项目仓库: 42 | ``` 43 | git clone https://github.com/your-username/coze2openai.git 44 | cd coze2openai 45 | ``` 46 | 47 | 3. 创建并配置`.env`文件: 48 | ``` 49 | cp .env.template .env 50 | ``` 51 | 编辑`.env`文件,填入您的BOT_ID和其他必要的配置。 52 | 53 | 4. 构建并启动Docker容器: 54 | ``` 55 | docker-compose up -d 56 | ``` 57 | 58 | 5. 访问`http://localhost:3000`来确认服务是否正常运行。 59 | 60 | 要停止服务,运行: 61 | ``` 62 | docker-compose down 63 | ``` 64 | 65 | 注意:Dockerfile中使用了淘宝NPM镜像源,你可以注释掉或替换其他源: 66 | ```Dockerfile 67 | ENV NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/ 68 | ENV PNPM_REGISTRY=https://registry.npmmirror.com/ 69 | ``` 70 | 71 | # 本地部署 72 | 1. 首先把`.env.template`文件复制改名为`.env` 73 | 74 | 2. 在 .env 文件上设置环境变量 75 | ```bash 76 | BOT_ID=xxxx 77 | ``` 78 | 79 | 3. 安装依赖项 80 | ```bash 81 | pnpm install 82 | ``` 83 | 84 | 4.运行项目 85 | ```bash 86 | pnpm start 87 | ``` 88 | 89 | # 用法 90 | 1. OpenAI 三方客户端 91 | 92 | ![botgem](pictures/usage.png) 93 | 94 | 2. 代码里直接调用 95 | 96 | ```JavaScript 97 | const response = await fetch('http://localhost:3000/v1/chat/completions', { 98 | method: 'POST', 99 | headers: { 100 | 'Content-Type': 'application/json', 101 | 'Authorization': 'Bearer YOUR_COZE_API_KEY', 102 | }, 103 | body: JSON.stringify({ 104 | model: 'model_name', 105 | messages: [ 106 | { role: 'system', content: 'You are a helpful assistant.' }, 107 | { role: 'user', content: 'Hello, how are you?' }, 108 | ], 109 | }), 110 | }); 111 | 112 | const data = await response.json(); 113 | console.log(data); 114 | ``` 115 | # 环境变量 116 | 该项目提供了一些额外的配置项,通过环境变量设置: 117 | 118 | | 环境变量 | 必须的 | 描述 | 例子 | 119 | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | 120 | | `BOT_ID` | Yes | 机器人的 ID。从 Coze 中机器人的开发页面 URL 获取它。 bot参数后面的数字是bot id.| `73428668*****`| 121 | | `BOT_CONFIG` | No | 配置模型和机器人ID的对应关系,实现在客户端切换模型来调用不同的机器人的效果。如果调用不在配置文件的模型,则走默认的BOT_ID| `{"model_name_1": "bot_id_1", "model_name_2": "bot_id_2", "model_name_3": "bot_id_3"}`| 122 | | `COZE_API_BASE` | No | 选择coze.com或者coze.cn| `api.coze.com, api.coze.cn`| 123 | 124 | 125 | # 路线图 126 | **即将推出** 127 | * 图像支持 128 | * 音频转文字 129 | * 文本转语音 130 | 131 | **现在可用** 132 | * 支持 coze.cn 133 | * 多机器人切换 134 | * 连续对话,有对话历史 135 | * Zeabur&Vercel&Railway 部署 136 | * 流式和非流式传输 137 | * Workflow、插件、知识库 138 | * Docker 部署 139 | 140 | # 联系 141 | 如有任何问题或反馈,请随时联系 142 | 143 | [X](https://sum4all.site/twitter)\ 144 | [telegram](https://sum4all.site/telegram) 145 | 146 | Buy Me A Coffee 147 | 148 | # 许可证 149 | 该项目在 MIT 许可证下获得许可. 150 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bodyParser from "body-parser"; 3 | import dotenv from "dotenv"; 4 | import fetch from "node-fetch"; 5 | dotenv.config(); 6 | 7 | const app = express(); 8 | app.use(bodyParser.json()); 9 | const coze_api_base = process.env.COZE_API_BASE || "api.coze.com"; 10 | const default_bot_id = process.env.BOT_ID || ""; 11 | const botConfig = process.env.BOT_CONFIG ? JSON.parse(process.env.BOT_CONFIG) : {}; 12 | var corsHeaders = { 13 | "Access-Control-Allow-Origin": "*", 14 | "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 15 | "Access-Control-Allow-Headers": 16 | "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization", 17 | "Access-Control-Max-Age": "86400", 18 | }; 19 | 20 | app.use((req, res, next) => { 21 | res.set(corsHeaders); 22 | if (req.method === 'OPTIONS') { 23 | return res.status(204).end(); 24 | } 25 | console.log('Request Method:', req.method); 26 | console.log('Request Path:', req.path); 27 | next(); 28 | }); 29 | app.get("/", (req, res) => { 30 | res.send(` 31 | 32 | 33 | COZE2OPENAI 34 | 35 | 36 |

Coze2OpenAI

37 |

Congratulations! Your project has been successfully deployed.

38 | 39 | 40 | `); 41 | }); 42 | 43 | app.post("/v1/chat/completions", async (req, res) => { 44 | const authHeader = 45 | req.headers["authorization"] || req.headers["Authorization"]; 46 | if (!authHeader) { 47 | return res.status(401).json({ 48 | code: 401, 49 | errmsg: "Unauthorized.", 50 | }); 51 | } else { 52 | const token = authHeader.split(" ")[1]; 53 | if (!token) { 54 | return res.status(401).json({ 55 | code: 401, 56 | errmsg: "Unauthorized.", 57 | }); 58 | } 59 | } 60 | try { 61 | const data = req.body; 62 | const messages = data.messages; 63 | const model = data.model; 64 | const user = data.user !== undefined ? data.user : "apiuser"; 65 | const chatHistory = []; 66 | for (let i = 0; i < messages.length - 1; i++) { 67 | const message = messages[i]; 68 | const role = message.role; 69 | const content = message.content; 70 | 71 | chatHistory.push({ 72 | role: role, 73 | content: content, 74 | content_type: "text" 75 | }); 76 | } 77 | 78 | const lastMessage = messages[messages.length - 1]; 79 | const queryString = lastMessage.content; 80 | const stream = data.stream !== undefined ? data.stream : false; 81 | let requestBody; 82 | const bot_id = model && botConfig[model] ? botConfig[model] : default_bot_id; 83 | 84 | requestBody = { 85 | query: queryString, 86 | stream: stream, 87 | conversation_id: "", 88 | user: user, 89 | bot_id: bot_id, 90 | chat_history: chatHistory 91 | }; 92 | const coze_api_url = `https://${coze_api_base}/open_api/v2/chat`; 93 | const resp = await fetch(coze_api_url, { 94 | method: "POST", 95 | headers: { 96 | "Content-Type": "application/json", 97 | Authorization: `Bearer ${authHeader.split(" ")[1]}`, 98 | }, 99 | body: JSON.stringify(requestBody), 100 | }); 101 | if (stream) { 102 | res.setHeader("Content-Type", "text/event-stream"); 103 | const stream = resp.body; 104 | let buffer = ""; 105 | 106 | stream.on("data", (chunk) => { 107 | buffer += chunk.toString(); 108 | let lines = buffer.split("\n"); 109 | 110 | for (let i = 0; i < lines.length - 1; i++) { 111 | let line = lines[i].trim(); 112 | 113 | if (!line.startsWith("data:")) continue; 114 | line = line.slice(5).trim(); 115 | let chunkObj; 116 | try { 117 | if (line.startsWith("{")) { 118 | chunkObj = JSON.parse(line); 119 | } else { 120 | continue; 121 | } 122 | } catch (error) { 123 | console.error("Error parsing chunk:", error); 124 | continue; 125 | } 126 | if (chunkObj.event === "message") { 127 | if ( 128 | chunkObj.message.role === "assistant" && 129 | chunkObj.message.type === "answer" 130 | ) { 131 | let chunkContent = chunkObj.message.content; 132 | 133 | if (chunkContent !== "") { 134 | const chunkId = `chatcmpl-${Date.now()}`; 135 | const chunkCreated = Math.floor(Date.now() / 1000); 136 | res.write( 137 | "data: " + 138 | JSON.stringify({ 139 | id: chunkId, 140 | object: "chat.completion.chunk", 141 | created: chunkCreated, 142 | model: data.model, 143 | choices: [ 144 | { 145 | index: 0, 146 | delta: { 147 | content: chunkContent, 148 | }, 149 | finish_reason: null, 150 | }, 151 | ], 152 | }) + 153 | "\n\n" 154 | ); 155 | } 156 | } 157 | } else if (chunkObj.event === "done") { 158 | const chunkId = `chatcmpl-${Date.now()}`; 159 | const chunkCreated = Math.floor(Date.now() / 1000); 160 | res.write( 161 | "data: " + 162 | JSON.stringify({ 163 | id: chunkId, 164 | object: "chat.completion.chunk", 165 | created: chunkCreated, 166 | model: data.model, 167 | choices: [ 168 | { 169 | index: 0, 170 | delta: {}, 171 | finish_reason: "stop", 172 | }, 173 | ], 174 | }) + 175 | "\n\n" 176 | ); 177 | res.write("data: [DONE]\n\n"); 178 | res.end(); 179 | } else if (chunkObj.event === "ping") { 180 | } else if (chunkObj.event === "error") { 181 | let errorMsg = chunkObj.code + " " + chunkObj.message; 182 | 183 | if(chunkObj.error_information) { 184 | errorMsg = chunkObj.error_information.err_msg; 185 | } 186 | 187 | console.error('Error: ', errorMsg); 188 | 189 | res.write( 190 | `data: ${JSON.stringify({ error: { 191 | error: "Unexpected response from Coze API.", 192 | message: errorMsg 193 | } 194 | })}\n\n` 195 | ); 196 | res.write("data: [DONE]\n\n"); 197 | res.end(); 198 | } 199 | } 200 | 201 | buffer = lines[lines.length - 1]; 202 | }); 203 | } else { 204 | resp 205 | .json() 206 | .then((data) => { 207 | if (data.code === 0 && data.msg === "success") { 208 | const messages = data.messages; 209 | const answerMessage = messages.find( 210 | (message) => 211 | message.role === "assistant" && message.type === "answer" 212 | ); 213 | 214 | if (answerMessage) { 215 | const result = answerMessage.content.trim(); 216 | const usageData = { 217 | prompt_tokens: 100, 218 | completion_tokens: 10, 219 | total_tokens: 110, 220 | }; 221 | const chunkId = `chatcmpl-${Date.now()}`; 222 | const chunkCreated = Math.floor(Date.now() / 1000); 223 | 224 | const formattedResponse = { 225 | id: chunkId, 226 | object: "chat.completion", 227 | created: chunkCreated, 228 | model: req.body.model, 229 | choices: [ 230 | { 231 | index: 0, 232 | message: { 233 | role: "assistant", 234 | content: result, 235 | }, 236 | logprobs: null, 237 | finish_reason: "stop", 238 | }, 239 | ], 240 | usage: usageData, 241 | system_fingerprint: "fp_2f57f81c11", 242 | }; 243 | const jsonResponse = JSON.stringify(formattedResponse, null, 2); 244 | res.set("Content-Type", "application/json"); 245 | res.send(jsonResponse); 246 | } else { 247 | res.status(500).json({ error: "No answer message found." }); 248 | } 249 | } else { 250 | console.error("Error:", data.msg); 251 | res 252 | .status(500) 253 | .json({ error: { 254 | error: "Unexpected response from Coze API.", 255 | message: data.msg 256 | } 257 | }); 258 | } 259 | }) 260 | .catch((error) => { 261 | console.error("Error parsing JSON:", error); 262 | res.status(500).json({ error: "Error parsing JSON response." }); 263 | }); 264 | } 265 | } catch (error) { 266 | console.error("Error:", error); 267 | } 268 | }); 269 | 270 | const server = app.listen(process.env.PORT || 3000, function () { 271 | let port = server.address().port 272 | console.log('Ready! Listening all IP, port: %s. Example: at http://localhost:%s', port, port) 273 | }); 274 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | ports: 5 | - "3000:3000" 6 | environment: 7 | - PORT=3000 8 | - BOT_ID=${BOT_ID} 9 | - BOT_CONFIG=${BOT_CONFIG} 10 | - COZE_API_BASE=${COZE_API_BASE} 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coze2openai", 3 | "version": "1.0.0", 4 | "description": "turn coze api into openai", 5 | "main": "app.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node app.js" 10 | }, 11 | "keywords": [], 12 | "author": "fatwang2", 13 | "license": "MIT", 14 | "dependencies": { 15 | "body-parser": "^1.20.2", 16 | "dotenv": "^16.3.1", 17 | "express": "^4.18.2", 18 | "node-fetch": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pictures/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatwang2/coze2openai/d0dab84e1d90edf8245cfe7b20bd31d4955f1350/pictures/api.png -------------------------------------------------------------------------------- /pictures/token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatwang2/coze2openai/d0dab84e1d90edf8245cfe7b20bd31d4955f1350/pictures/token.png -------------------------------------------------------------------------------- /pictures/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatwang2/coze2openai/d0dab84e1d90edf8245cfe7b20bd31d4955f1350/pictures/usage.png -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | body-parser: 12 | specifier: ^1.20.2 13 | version: 1.20.2 14 | dotenv: 15 | specifier: ^16.3.1 16 | version: 16.3.1 17 | express: 18 | specifier: ^4.18.2 19 | version: 4.18.2 20 | node-fetch: 21 | specifier: ^3.3.2 22 | version: 3.3.2 23 | 24 | packages: 25 | 26 | accepts@1.3.8: 27 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 28 | engines: {node: '>= 0.6'} 29 | 30 | array-flatten@1.1.1: 31 | resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} 32 | 33 | body-parser@1.20.1: 34 | resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} 35 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 36 | 37 | body-parser@1.20.2: 38 | resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} 39 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 40 | 41 | bytes@3.1.2: 42 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 43 | engines: {node: '>= 0.8'} 44 | 45 | call-bind@1.0.2: 46 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 47 | 48 | content-disposition@0.5.4: 49 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} 50 | engines: {node: '>= 0.6'} 51 | 52 | content-type@1.0.5: 53 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 54 | engines: {node: '>= 0.6'} 55 | 56 | cookie-signature@1.0.6: 57 | resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} 58 | 59 | cookie@0.5.0: 60 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 61 | engines: {node: '>= 0.6'} 62 | 63 | data-uri-to-buffer@4.0.1: 64 | resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} 65 | engines: {node: '>= 12'} 66 | 67 | debug@2.6.9: 68 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 69 | peerDependencies: 70 | supports-color: '*' 71 | peerDependenciesMeta: 72 | supports-color: 73 | optional: true 74 | 75 | depd@2.0.0: 76 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 77 | engines: {node: '>= 0.8'} 78 | 79 | destroy@1.2.0: 80 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 81 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 82 | 83 | dotenv@16.3.1: 84 | resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} 85 | engines: {node: '>=12'} 86 | 87 | ee-first@1.1.1: 88 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 89 | 90 | encodeurl@1.0.2: 91 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} 92 | engines: {node: '>= 0.8'} 93 | 94 | escape-html@1.0.3: 95 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 96 | 97 | etag@1.8.1: 98 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 99 | engines: {node: '>= 0.6'} 100 | 101 | express@4.18.2: 102 | resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} 103 | engines: {node: '>= 0.10.0'} 104 | 105 | fetch-blob@3.2.0: 106 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 107 | engines: {node: ^12.20 || >= 14.13} 108 | 109 | finalhandler@1.2.0: 110 | resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} 111 | engines: {node: '>= 0.8'} 112 | 113 | formdata-polyfill@4.0.10: 114 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 115 | engines: {node: '>=12.20.0'} 116 | 117 | forwarded@0.2.0: 118 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 119 | engines: {node: '>= 0.6'} 120 | 121 | fresh@0.5.2: 122 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 123 | engines: {node: '>= 0.6'} 124 | 125 | function-bind@1.1.1: 126 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 127 | 128 | get-intrinsic@1.2.1: 129 | resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} 130 | 131 | has-proto@1.0.1: 132 | resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 133 | engines: {node: '>= 0.4'} 134 | 135 | has-symbols@1.0.3: 136 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 137 | engines: {node: '>= 0.4'} 138 | 139 | has@1.0.3: 140 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 141 | engines: {node: '>= 0.4.0'} 142 | 143 | http-errors@2.0.0: 144 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 145 | engines: {node: '>= 0.8'} 146 | 147 | iconv-lite@0.4.24: 148 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 149 | engines: {node: '>=0.10.0'} 150 | 151 | inherits@2.0.4: 152 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 153 | 154 | ipaddr.js@1.9.1: 155 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 156 | engines: {node: '>= 0.10'} 157 | 158 | media-typer@0.3.0: 159 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 160 | engines: {node: '>= 0.6'} 161 | 162 | merge-descriptors@1.0.1: 163 | resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} 164 | 165 | methods@1.1.2: 166 | resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} 167 | engines: {node: '>= 0.6'} 168 | 169 | mime-db@1.52.0: 170 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 171 | engines: {node: '>= 0.6'} 172 | 173 | mime-types@2.1.35: 174 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 175 | engines: {node: '>= 0.6'} 176 | 177 | mime@1.6.0: 178 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 179 | engines: {node: '>=4'} 180 | hasBin: true 181 | 182 | ms@2.0.0: 183 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 184 | 185 | ms@2.1.3: 186 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 187 | 188 | negotiator@0.6.3: 189 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 190 | engines: {node: '>= 0.6'} 191 | 192 | node-domexception@1.0.0: 193 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 194 | engines: {node: '>=10.5.0'} 195 | 196 | node-fetch@3.3.2: 197 | resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} 198 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 199 | 200 | object-inspect@1.12.3: 201 | resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} 202 | 203 | on-finished@2.4.1: 204 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 205 | engines: {node: '>= 0.8'} 206 | 207 | parseurl@1.3.3: 208 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 209 | engines: {node: '>= 0.8'} 210 | 211 | path-to-regexp@0.1.7: 212 | resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} 213 | 214 | proxy-addr@2.0.7: 215 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} 216 | engines: {node: '>= 0.10'} 217 | 218 | qs@6.11.0: 219 | resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} 220 | engines: {node: '>=0.6'} 221 | 222 | range-parser@1.2.1: 223 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 224 | engines: {node: '>= 0.6'} 225 | 226 | raw-body@2.5.1: 227 | resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} 228 | engines: {node: '>= 0.8'} 229 | 230 | raw-body@2.5.2: 231 | resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} 232 | engines: {node: '>= 0.8'} 233 | 234 | safe-buffer@5.2.1: 235 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 236 | 237 | safer-buffer@2.1.2: 238 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 239 | 240 | send@0.18.0: 241 | resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} 242 | engines: {node: '>= 0.8.0'} 243 | 244 | serve-static@1.15.0: 245 | resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} 246 | engines: {node: '>= 0.8.0'} 247 | 248 | setprototypeof@1.2.0: 249 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 250 | 251 | side-channel@1.0.4: 252 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 253 | 254 | statuses@2.0.1: 255 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 256 | engines: {node: '>= 0.8'} 257 | 258 | toidentifier@1.0.1: 259 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 260 | engines: {node: '>=0.6'} 261 | 262 | type-is@1.6.18: 263 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 264 | engines: {node: '>= 0.6'} 265 | 266 | unpipe@1.0.0: 267 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 268 | engines: {node: '>= 0.8'} 269 | 270 | utils-merge@1.0.1: 271 | resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} 272 | engines: {node: '>= 0.4.0'} 273 | 274 | vary@1.1.2: 275 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 276 | engines: {node: '>= 0.8'} 277 | 278 | web-streams-polyfill@3.2.1: 279 | resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} 280 | engines: {node: '>= 8'} 281 | 282 | snapshots: 283 | 284 | accepts@1.3.8: 285 | dependencies: 286 | mime-types: 2.1.35 287 | negotiator: 0.6.3 288 | 289 | array-flatten@1.1.1: {} 290 | 291 | body-parser@1.20.1: 292 | dependencies: 293 | bytes: 3.1.2 294 | content-type: 1.0.5 295 | debug: 2.6.9 296 | depd: 2.0.0 297 | destroy: 1.2.0 298 | http-errors: 2.0.0 299 | iconv-lite: 0.4.24 300 | on-finished: 2.4.1 301 | qs: 6.11.0 302 | raw-body: 2.5.1 303 | type-is: 1.6.18 304 | unpipe: 1.0.0 305 | transitivePeerDependencies: 306 | - supports-color 307 | 308 | body-parser@1.20.2: 309 | dependencies: 310 | bytes: 3.1.2 311 | content-type: 1.0.5 312 | debug: 2.6.9 313 | depd: 2.0.0 314 | destroy: 1.2.0 315 | http-errors: 2.0.0 316 | iconv-lite: 0.4.24 317 | on-finished: 2.4.1 318 | qs: 6.11.0 319 | raw-body: 2.5.2 320 | type-is: 1.6.18 321 | unpipe: 1.0.0 322 | transitivePeerDependencies: 323 | - supports-color 324 | 325 | bytes@3.1.2: {} 326 | 327 | call-bind@1.0.2: 328 | dependencies: 329 | function-bind: 1.1.1 330 | get-intrinsic: 1.2.1 331 | 332 | content-disposition@0.5.4: 333 | dependencies: 334 | safe-buffer: 5.2.1 335 | 336 | content-type@1.0.5: {} 337 | 338 | cookie-signature@1.0.6: {} 339 | 340 | cookie@0.5.0: {} 341 | 342 | data-uri-to-buffer@4.0.1: {} 343 | 344 | debug@2.6.9: 345 | dependencies: 346 | ms: 2.0.0 347 | 348 | depd@2.0.0: {} 349 | 350 | destroy@1.2.0: {} 351 | 352 | dotenv@16.3.1: {} 353 | 354 | ee-first@1.1.1: {} 355 | 356 | encodeurl@1.0.2: {} 357 | 358 | escape-html@1.0.3: {} 359 | 360 | etag@1.8.1: {} 361 | 362 | express@4.18.2: 363 | dependencies: 364 | accepts: 1.3.8 365 | array-flatten: 1.1.1 366 | body-parser: 1.20.1 367 | content-disposition: 0.5.4 368 | content-type: 1.0.5 369 | cookie: 0.5.0 370 | cookie-signature: 1.0.6 371 | debug: 2.6.9 372 | depd: 2.0.0 373 | encodeurl: 1.0.2 374 | escape-html: 1.0.3 375 | etag: 1.8.1 376 | finalhandler: 1.2.0 377 | fresh: 0.5.2 378 | http-errors: 2.0.0 379 | merge-descriptors: 1.0.1 380 | methods: 1.1.2 381 | on-finished: 2.4.1 382 | parseurl: 1.3.3 383 | path-to-regexp: 0.1.7 384 | proxy-addr: 2.0.7 385 | qs: 6.11.0 386 | range-parser: 1.2.1 387 | safe-buffer: 5.2.1 388 | send: 0.18.0 389 | serve-static: 1.15.0 390 | setprototypeof: 1.2.0 391 | statuses: 2.0.1 392 | type-is: 1.6.18 393 | utils-merge: 1.0.1 394 | vary: 1.1.2 395 | transitivePeerDependencies: 396 | - supports-color 397 | 398 | fetch-blob@3.2.0: 399 | dependencies: 400 | node-domexception: 1.0.0 401 | web-streams-polyfill: 3.2.1 402 | 403 | finalhandler@1.2.0: 404 | dependencies: 405 | debug: 2.6.9 406 | encodeurl: 1.0.2 407 | escape-html: 1.0.3 408 | on-finished: 2.4.1 409 | parseurl: 1.3.3 410 | statuses: 2.0.1 411 | unpipe: 1.0.0 412 | transitivePeerDependencies: 413 | - supports-color 414 | 415 | formdata-polyfill@4.0.10: 416 | dependencies: 417 | fetch-blob: 3.2.0 418 | 419 | forwarded@0.2.0: {} 420 | 421 | fresh@0.5.2: {} 422 | 423 | function-bind@1.1.1: {} 424 | 425 | get-intrinsic@1.2.1: 426 | dependencies: 427 | function-bind: 1.1.1 428 | has: 1.0.3 429 | has-proto: 1.0.1 430 | has-symbols: 1.0.3 431 | 432 | has-proto@1.0.1: {} 433 | 434 | has-symbols@1.0.3: {} 435 | 436 | has@1.0.3: 437 | dependencies: 438 | function-bind: 1.1.1 439 | 440 | http-errors@2.0.0: 441 | dependencies: 442 | depd: 2.0.0 443 | inherits: 2.0.4 444 | setprototypeof: 1.2.0 445 | statuses: 2.0.1 446 | toidentifier: 1.0.1 447 | 448 | iconv-lite@0.4.24: 449 | dependencies: 450 | safer-buffer: 2.1.2 451 | 452 | inherits@2.0.4: {} 453 | 454 | ipaddr.js@1.9.1: {} 455 | 456 | media-typer@0.3.0: {} 457 | 458 | merge-descriptors@1.0.1: {} 459 | 460 | methods@1.1.2: {} 461 | 462 | mime-db@1.52.0: {} 463 | 464 | mime-types@2.1.35: 465 | dependencies: 466 | mime-db: 1.52.0 467 | 468 | mime@1.6.0: {} 469 | 470 | ms@2.0.0: {} 471 | 472 | ms@2.1.3: {} 473 | 474 | negotiator@0.6.3: {} 475 | 476 | node-domexception@1.0.0: {} 477 | 478 | node-fetch@3.3.2: 479 | dependencies: 480 | data-uri-to-buffer: 4.0.1 481 | fetch-blob: 3.2.0 482 | formdata-polyfill: 4.0.10 483 | 484 | object-inspect@1.12.3: {} 485 | 486 | on-finished@2.4.1: 487 | dependencies: 488 | ee-first: 1.1.1 489 | 490 | parseurl@1.3.3: {} 491 | 492 | path-to-regexp@0.1.7: {} 493 | 494 | proxy-addr@2.0.7: 495 | dependencies: 496 | forwarded: 0.2.0 497 | ipaddr.js: 1.9.1 498 | 499 | qs@6.11.0: 500 | dependencies: 501 | side-channel: 1.0.4 502 | 503 | range-parser@1.2.1: {} 504 | 505 | raw-body@2.5.1: 506 | dependencies: 507 | bytes: 3.1.2 508 | http-errors: 2.0.0 509 | iconv-lite: 0.4.24 510 | unpipe: 1.0.0 511 | 512 | raw-body@2.5.2: 513 | dependencies: 514 | bytes: 3.1.2 515 | http-errors: 2.0.0 516 | iconv-lite: 0.4.24 517 | unpipe: 1.0.0 518 | 519 | safe-buffer@5.2.1: {} 520 | 521 | safer-buffer@2.1.2: {} 522 | 523 | send@0.18.0: 524 | dependencies: 525 | debug: 2.6.9 526 | depd: 2.0.0 527 | destroy: 1.2.0 528 | encodeurl: 1.0.2 529 | escape-html: 1.0.3 530 | etag: 1.8.1 531 | fresh: 0.5.2 532 | http-errors: 2.0.0 533 | mime: 1.6.0 534 | ms: 2.1.3 535 | on-finished: 2.4.1 536 | range-parser: 1.2.1 537 | statuses: 2.0.1 538 | transitivePeerDependencies: 539 | - supports-color 540 | 541 | serve-static@1.15.0: 542 | dependencies: 543 | encodeurl: 1.0.2 544 | escape-html: 1.0.3 545 | parseurl: 1.3.3 546 | send: 0.18.0 547 | transitivePeerDependencies: 548 | - supports-color 549 | 550 | setprototypeof@1.2.0: {} 551 | 552 | side-channel@1.0.4: 553 | dependencies: 554 | call-bind: 1.0.2 555 | get-intrinsic: 1.2.1 556 | object-inspect: 1.12.3 557 | 558 | statuses@2.0.1: {} 559 | 560 | toidentifier@1.0.1: {} 561 | 562 | type-is@1.6.18: 563 | dependencies: 564 | media-typer: 0.3.0 565 | mime-types: 2.1.35 566 | 567 | unpipe@1.0.0: {} 568 | 569 | utils-merge@1.0.1: {} 570 | 571 | vary@1.1.2: {} 572 | 573 | web-streams-polyfill@3.2.1: {} 574 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "app.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/v1/(.*)", 12 | "dest": "/app.js", 13 | "methods": ["OPTIONS", "POST"] 14 | }, 15 | { 16 | "src": "/", 17 | "dest": "/app.js", 18 | "methods": ["GET"] 19 | } 20 | ] 21 | } 22 | --------------------------------------------------------------------------------