From 135a8d943af7bf6b4c16520f9fcf5b920f5761ed Mon Sep 17 00:00:00 2001 From: Bob Du Date: Fri, 25 Apr 2025 12:35:50 +0800 Subject: [PATCH 1/3] refactor: remove unofficial api support Signed-off-by: Bob Du --- service/package.json | 1 - service/pnpm-lock.yaml | 7 ------ service/src/chatgpt/index.ts | 47 ++++------------------------------- service/src/storage/config.ts | 3 --- service/src/storage/mongo.ts | 11 -------- service/src/types.ts | 27 -------------------- 6 files changed, 5 insertions(+), 91 deletions(-) diff --git a/service/package.json b/service/package.json index 7bcfa902..2e63b982 100644 --- a/service/package.json +++ b/service/package.json @@ -38,7 +38,6 @@ "gpt-token": "^0.0.5", "https-proxy-agent": "^5.0.1", "jsonwebtoken": "^9.0.0", - "jwt-decode": "^3.1.2", "mongodb": "^6.6.2", "multer": "1.4.5-lts.1", "node-fetch": "^3.3.0", diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml index 4ca3d6c2..8b9adabf 100644 --- a/service/pnpm-lock.yaml +++ b/service/pnpm-lock.yaml @@ -35,9 +35,6 @@ dependencies: jsonwebtoken: specifier: ^9.0.0 version: 9.0.0 - jwt-decode: - specifier: ^3.1.2 - version: 3.1.2 mongodb: specifier: ^6.6.2 version: 6.6.2 @@ -2672,10 +2669,6 @@ packages: safe-buffer: 5.2.1 dev: false - /jwt-decode@3.1.2: - resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} - dev: false - /keyv@4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: diff --git a/service/src/chatgpt/index.ts b/service/src/chatgpt/index.ts index ce2bb0d0..2d092398 100644 --- a/service/src/chatgpt/index.ts +++ b/service/src/chatgpt/index.ts @@ -1,10 +1,9 @@ import * as dotenv from 'dotenv' import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt' -import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt' +import { ChatGPTAPI } from 'chatgpt' import { SocksProxyAgent } from 'socks-proxy-agent' import httpsProxyAgent from 'https-proxy-agent' import fetch from 'node-fetch' -import jwt_decode from 'jwt-decode' import type { AuditConfig, KeyConfig, UserInfo } from '../storage/model' import { Status } from '../storage/model' import { convertImageUrl } from '../utils/image' @@ -13,8 +12,8 @@ import { textAuditServices } from '../utils/textAudit' import { getCacheApiKeys, getCacheConfig, getOriginConfig } from '../storage/config' import { sendResponse } from '../utils' import { hasAnyRole, isNotEmptyString } from '../utils/is' -import type { ChatContext, ChatGPTUnofficialProxyAPIOptions, JWT, ModelConfig } from '../types' -import { getChatByMessageId, updateRoomAccountId, updateRoomChatModel } from '../storage/mongo' +import type { ChatContext, ModelConfig } from '../types' +import { getChatByMessageId, updateRoomChatModel } from '../storage/mongo' import type { RequestOptions } from './types' const { HttpsProxyAgent } = httpsProxyAgent @@ -81,22 +80,6 @@ export async function initApi(key: KeyConfig, chatModel: string, maxContextCount return new ChatGPTAPI({ ...options }) } - else { - const options: ChatGPTUnofficialProxyAPIOptions = { - accessToken: key.key, - apiReverseProxyUrl: isNotEmptyString(key.baseUrl) - ? key.baseUrl - : isNotEmptyString(config.reverseProxy) - ? config.reverseProxy - : 'https://ai.fakeopen.com/api/conversation', - model, - debug: !config.apiDisableDebug, - } - - await setupProxy(options) - - return new ChatGPTUnofficialProxyAPI({ ...options }) - } } const processThreads: { userId: string; abort: AbortController; messageId: string }[] = [] async function chatReplyProcess(options: RequestOptions) { @@ -108,14 +91,6 @@ async function chatReplyProcess(options: RequestOptions) { if (key == null || key === undefined) throw new Error('没有对应的apikeys配置。请再试一次 | No available apikeys configuration. Please try again.') - if (key.keyModel === 'ChatGPTUnofficialProxyAPI') { - if (!options.room.accountId) - updateRoomAccountId(userId, options.room.roomId, getAccountId(key.key)) - - if (options.lastContext && ((options.lastContext.conversationId && !options.lastContext.parentMessageId) || (!options.lastContext.conversationId && options.lastContext.parentMessageId))) - throw new Error('无法在一个房间同时使用 AccessToken 以及 Api,请联系管理员,或新开聊天室进行对话 | Unable to use AccessToken and Api at the same time in the same room, please contact the administrator or open a new chat room for conversation') - } - // Add Chat Record updateRoomChatModel(userId, options.room.roomId, model) @@ -235,7 +210,7 @@ async function chatConfig() { }) } -async function setupProxy(options: ChatGPTAPIOptions | ChatGPTUnofficialProxyAPIOptions) { +async function setupProxy(options: ChatGPTAPIOptions) { const config = await getCacheConfig() if (isNotEmptyString(config.socksProxy)) { const agent = new SocksProxyAgent({ @@ -344,24 +319,12 @@ async function randomKeyConfig(keys: KeyConfig[]): Promise { } async function getRandomApiKey(user: UserInfo, chatModel: string, accountId?: string): Promise { - let keys = (await getCacheApiKeys()).filter(d => hasAnyRole(d.userRoles, user.roles)) + const keys = (await getCacheApiKeys()).filter(d => hasAnyRole(d.userRoles, user.roles)) .filter(d => d.chatModels.includes(chatModel)) - if (accountId) - keys = keys.filter(d => d.keyModel === 'ChatGPTUnofficialProxyAPI' && getAccountId(d.key) === accountId) return randomKeyConfig(keys) } -function getAccountId(accessToken: string): string { - try { - const jwt = jwt_decode(accessToken) as JWT - return jwt['https://api.openai.com/auth'].user_id - } - catch (error) { - return '' - } -} - export type { ChatContext, ChatMessage } export { chatReplyProcess, chatConfig, containsSensitiveWords } diff --git a/service/src/storage/config.ts b/service/src/storage/config.ts index b9b8c265..37b3c796 100644 --- a/service/src/storage/config.ts +++ b/service/src/storage/config.ts @@ -170,9 +170,6 @@ export async function getApiKeys() { if (config.apiModel === 'ChatGPTAPI') result.keys.push(await upsertKey(new KeyConfig(config.apiKey, 'ChatGPTAPI', [], [], ''))) - if (config.apiModel === 'ChatGPTUnofficialProxyAPI') - result.keys.push(await upsertKey(new KeyConfig(config.accessToken, 'ChatGPTUnofficialProxyAPI', [], [], ''))) - result.total++ } result.keys.forEach((key) => { diff --git a/service/src/storage/mongo.ts b/service/src/storage/mongo.ts index 2f24d8df..74065591 100644 --- a/service/src/storage/mongo.ts +++ b/service/src/storage/mongo.ts @@ -171,17 +171,6 @@ export async function updateRoomUsingContext(userId: string, roomId: number, usi return result.modifiedCount > 0 } -export async function updateRoomAccountId(userId: string, roomId: number, accountId: string) { - const query = { userId, roomId } - const update = { - $set: { - accountId, - }, - } - const result = await roomCol.updateOne(query, update) - return result.modifiedCount > 0 -} - export async function updateRoomChatModel(userId: string, roomId: number, chatModel: string) { const query = { userId, roomId } const update = { diff --git a/service/src/types.ts b/service/src/types.ts index 1177330e..f0f821fb 100644 --- a/service/src/types.ts +++ b/service/src/types.ts @@ -1,4 +1,3 @@ -import type { FetchFn } from 'chatgpt' import type { JwtPayload } from 'jsonwebtoken' export interface RequestProps { @@ -18,15 +17,6 @@ export interface ChatContext { parentMessageId?: string } -export interface ChatGPTUnofficialProxyAPIOptions { - accessToken: string - apiReverseProxyUrl?: string - model?: string - debug?: boolean - headers?: Record - fetch?: FetchFn -} - export interface ModelConfig { apiModel?: APIMODEL reverseProxy?: string @@ -41,23 +31,6 @@ export interface ModelConfig { export type APIMODEL = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined -export interface JWT { - 'https://api.openai.com/profile': { - 'email': string - 'email_verified': boolean - } - 'https://api.openai.com/auth': { - 'user_id': string - } - 'iss': string - 'sub': string - 'aud': [] - 'iat': number - 'exp': number - 'azp': string - 'scope': string -} - export interface AuthJwtPayload extends JwtPayload { name: string avatar: string From 922a5b37737dd7c268156d55b2e45104fd834ff8 Mon Sep 17 00:00:00 2001 From: Bob Du Date: Wed, 30 Apr 2025 15:12:26 +0800 Subject: [PATCH 2/3] refactor: use openai-ts sdk Signed-off-by: Bob Du --- service/package.json | 10 +- service/pnpm-lock.yaml | 452 +++++++++++++---------------------- service/src/chatgpt/index.ts | 306 ++++++++++++------------ service/src/chatgpt/types.ts | 24 +- service/src/routes/chat.ts | 48 +--- service/src/storage/model.ts | 6 +- service/src/storage/mongo.ts | 3 +- service/src/utils/image.ts | 12 +- 8 files changed, 358 insertions(+), 503 deletions(-) diff --git a/service/package.json b/service/package.json index 2e63b982..1741252a 100644 --- a/service/package.json +++ b/service/package.json @@ -17,7 +17,7 @@ }, "scripts": { "start": "tsx ./src/index.ts", - "dev": "tsx watch ./src/index.ts", + "dev": "tsx watch --inspect ./src/index.ts", "prod": "node --import tsx/esm ./build/index.js", "prod-node18": "node --loader tsx/esm ./build/index.js", "build": "pnpm clean && tsc && pnpm copy", @@ -29,21 +29,19 @@ }, "dependencies": { "axios": "^1.8.4", - "chatgpt": "npm:@chatgptweb/chatgpt-api@5.3.0", "dayjs": "^1.11.7", "dotenv": "^16.0.3", "express": "^5.1.0", "express-rate-limit": "^6.7.0", "file-type": "^19.0.0", - "gpt-token": "^0.0.5", - "https-proxy-agent": "^5.0.1", + "https-proxy-agent": "^7.0.6", "jsonwebtoken": "^9.0.0", "mongodb": "^6.6.2", "multer": "1.4.5-lts.1", "node-fetch": "^3.3.0", "nodemailer": "^6.9.13", + "openai": "^4.96.0", "request-ip": "^3.3.0", - "socks-proxy-agent": "^7.0.0", "speakeasy": "^2.0.0", "tsx": "^4.7.0" }, @@ -52,7 +50,7 @@ "@types/express": "^5.0.1", "@types/jsonwebtoken": "^9.0.5", "@types/multer": "^1.4.11", - "@types/node": "^18.14.6", + "@types/node": "^22.15.3", "@types/nodemailer": "^6.4.14", "@types/request-ip": "^0.0.41", "@types/speakeasy": "^2.0.10", diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml index 8b9adabf..857294c7 100644 --- a/service/pnpm-lock.yaml +++ b/service/pnpm-lock.yaml @@ -8,9 +8,6 @@ dependencies: axios: specifier: ^1.8.4 version: 1.8.4 - chatgpt: - specifier: npm:@chatgptweb/chatgpt-api@5.3.0 - version: /@chatgptweb/chatgpt-api@5.3.0 dayjs: specifier: ^1.11.7 version: 1.11.7 @@ -26,12 +23,9 @@ dependencies: file-type: specifier: ^19.0.0 version: 19.0.0 - gpt-token: - specifier: ^0.0.5 - version: 0.0.5 https-proxy-agent: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^7.0.6 + version: 7.0.6 jsonwebtoken: specifier: ^9.0.0 version: 9.0.0 @@ -47,12 +41,12 @@ dependencies: nodemailer: specifier: ^6.9.13 version: 6.9.13 + openai: + specifier: ^4.96.0 + version: 4.96.0 request-ip: specifier: ^3.3.0 version: 3.3.0 - socks-proxy-agent: - specifier: ^7.0.0 - version: 7.0.0 speakeasy: specifier: ^2.0.0 version: 2.0.0 @@ -74,8 +68,8 @@ devDependencies: specifier: ^1.4.11 version: 1.4.11 '@types/node': - specifier: ^18.14.6 - version: 18.14.6 + specifier: ^22.15.3 + version: 22.15.3 '@types/nodemailer': specifier: ^6.4.14 version: 6.4.14 @@ -209,10 +203,12 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 + dev: true /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} @@ -226,22 +222,7 @@ packages: '@babel/helper-validator-identifier': 7.19.1 chalk: 2.4.2 js-tokens: 4.0.0 - - /@chatgptweb/chatgpt-api@5.3.0: - resolution: {integrity: sha512-p7WBUBQ9TY8NSNHAwkCddCy+ajavRNEv2V6rO3jWTb1LycmI2qhoKoYxvVjD8hguSYv5VmN/xUKfi6EVCdBBLA==} - engines: {node: '>=14'} - hasBin: true - dependencies: - cac: 6.7.14 - conf: 11.0.1 - eventsource-parser: 1.0.0 - js-tiktoken: 1.0.7 - keyv: 4.5.2 - p-timeout: 6.1.1 - quick-lru: 6.1.1 - read-pkg-up: 9.1.0 - uuid: 9.0.0 - dev: false + dev: true /@es-joy/jsdoccomment@0.41.0: resolution: {integrity: sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==} @@ -609,19 +590,19 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/express-serve-static-core@5.0.6: resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -650,7 +631,7 @@ packages: /@types/jsonwebtoken@9.0.5: resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/mdast@3.0.10: @@ -669,18 +650,33 @@ packages: '@types/express': 5.0.1 dev: true - /@types/node@18.14.6: - resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==} - dev: true + /@types/node-fetch@2.6.12: + resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + dependencies: + '@types/node': 22.15.3 + form-data: 4.0.0 + dev: false + + /@types/node@18.19.87: + resolution: {integrity: sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==} + dependencies: + undici-types: 5.26.5 + dev: false + + /@types/node@22.15.3: + resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} + dependencies: + undici-types: 6.21.0 /@types/nodemailer@6.4.14: resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true /@types/qs@6.9.18: resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} @@ -693,7 +689,7 @@ packages: /@types/request-ip@0.0.41: resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/semver@7.3.13: @@ -708,21 +704,21 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/serve-static@1.15.7: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 18.14.6 + '@types/node': 22.15.3 '@types/send': 0.17.4 dev: true /@types/speakeasy@2.0.10: resolution: {integrity: sha512-QVRlDW5r4yl7p7xkNIbAIC/JtyOcClDIIdKfuG7PWdDT1MmyhtXSANsildohy0K+Lmvf/9RUtLbNLMacvrVwxA==} dependencies: - '@types/node': 18.14.6 + '@types/node': 22.15.3 dev: true /@types/unist@2.0.6: @@ -937,6 +933,13 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + /accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -973,24 +976,16 @@ packages: hasBin: true dev: true - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color + /agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} dev: false - /ajv-formats@2.1.1(ajv@8.12.0): - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true + /agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} dependencies: - ajv: 8.12.0 + humanize-ms: 1.2.1 dev: false /ajv@6.12.6: @@ -1002,15 +997,6 @@ packages: uri-js: 4.4.1 dev: true - /ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: false - /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1026,6 +1012,7 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 + dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -1061,13 +1048,6 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false - /atomically@2.0.1: - resolution: {integrity: sha512-sxBhVZUFBFhqSAsYMM3X2oaUi2NVDJ8U026FsIusM8gYXls9AYs/eXzgGrufs1Qjpkxi9zunds+75QUFz+m7UQ==} - dependencies: - stubborn-fs: 1.2.4 - when-exit: 2.1.0 - dev: false - /axios@1.8.4: resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: @@ -1086,10 +1066,6 @@ packages: resolution: {integrity: sha512-EGHIRiegFa62/SsA1J+Xs2tIzludPdzM064N9wjbiEgHnGnJ1V0WEpA4pEwCYT5nDvZk3ubf0shqaCS7k6xeUQ==} dev: false - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false - /body-parser@2.2.0: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} @@ -1167,11 +1143,6 @@ packages: engines: {node: '>= 0.8'} dev: false - /cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - dev: false - /call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1200,6 +1171,7 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 + dev: true /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1245,6 +1217,7 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 + dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1255,6 +1228,7 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1286,20 +1260,6 @@ packages: typedarray: 0.0.6 dev: false - /conf@11.0.1: - resolution: {integrity: sha512-WlLiQboEjKx0bYx2IIRGedBgNjLAxtwPaCSnsjWPST5xR0DB4q8lcsO/bEH9ZRYNcj63Y9vj/JG/5Fg6uWzI0Q==} - engines: {node: '>=14.16'} - dependencies: - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - atomically: 2.0.1 - debounce-fn: 5.1.2 - dot-prop: 7.2.0 - env-paths: 3.0.0 - json-schema-typed: 8.0.1 - semver: 7.6.0 - dev: false - /content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} @@ -1362,13 +1322,6 @@ packages: resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} dev: false - /debounce-fn@5.1.2: - resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==} - engines: {node: '>=12'} - dependencies: - mimic-fn: 4.0.0 - dev: false - /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1390,6 +1343,7 @@ packages: optional: true dependencies: ms: 2.1.2 + dev: true /debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} @@ -1465,13 +1419,6 @@ packages: domhandler: 5.0.3 dev: true - /dot-prop@7.2.0: - resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - type-fest: 2.19.0 - dev: false - /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -1518,15 +1465,11 @@ packages: engines: {node: '>=0.12'} dev: true - /env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false - /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true /es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} @@ -1588,6 +1531,7 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -2032,9 +1976,9 @@ packages: engines: {node: '>= 0.6'} dev: false - /eventsource-parser@1.0.0: - resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==} - engines: {node: '>=14.18'} + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} dev: false /express-rate-limit@6.7.0(express@5.1.0): @@ -2083,6 +2027,7 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} @@ -2170,14 +2115,6 @@ packages: path-exists: 4.0.0 dev: true - /find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - dev: false - /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2208,6 +2145,10 @@ packages: signal-exit: 4.1.0 dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2217,6 +2158,14 @@ packages: mime-types: 2.1.35 dev: false + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -2357,12 +2306,6 @@ packages: engines: {node: '>= 0.4'} dev: false - /gpt-token@0.0.5: - resolution: {integrity: sha512-1wufsRy0AEOIvEUqbfzGlWsNitnTjhSKx6TSj5lithIkmovDfEzzOepJRJkiTjGJqxcsGQj08m8OKQSE/hy/sw==} - dependencies: - js-tiktoken: 1.0.7 - dev: false - /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true @@ -2370,6 +2313,7 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -2386,6 +2330,7 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 + dev: true /hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} @@ -2397,13 +2342,6 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - dev: false - /htmlparser2@8.0.1: resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} dependencies: @@ -2424,16 +2362,22 @@ packages: toidentifier: 1.0.1 dev: false - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} + /https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} dependencies: - agent-base: 6.0.2 - debug: 4.3.4 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color dev: false + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2478,10 +2422,6 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} - dev: false - /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2500,6 +2440,7 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true /is-builtin-module@3.2.1: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} @@ -2512,6 +2453,7 @@ packages: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: hasown: 2.0.0 + dev: true /is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} @@ -2579,14 +2521,9 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true - /js-tiktoken@1.0.7: - resolution: {integrity: sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==} - dependencies: - base64-js: 1.5.1 - dev: false - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} @@ -2611,25 +2548,14 @@ packages: hasBin: true dev: true - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: false - /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: false - - /json-schema-typed@8.0.1: - resolution: {integrity: sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==} - dev: false - /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true @@ -2669,12 +2595,6 @@ packages: safe-buffer: 5.2.1 dev: false - /keyv@4.5.2: - resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} - dependencies: - json-buffer: 3.0.1 - dev: false - /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2685,6 +2605,7 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} @@ -2705,13 +2626,6 @@ packages: p-locate: 5.0.0 dev: true - /locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-locate: 6.0.0 - dev: false - /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -2817,11 +2731,6 @@ packages: mime-db: 1.54.0 dev: false - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: false - /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -2903,6 +2812,7 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2934,6 +2844,18 @@ packages: engines: {node: '>=10.5.0'} dev: false + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-fetch@3.3.0: resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2964,16 +2886,6 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.13.1 - semver: 7.6.0 - validate-npm-package-license: 3.0.4 - dev: false - /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -3002,6 +2914,29 @@ packages: dependencies: wrappy: 1.0.2 + /openai@4.96.0: + resolution: {integrity: sha512-dKoW56i02Prv2XQolJ9Rl9Svqubqkzg3QpwEOBuSVZLk05Shelu7s+ErRTwFc1Bs3JZ2qBqBfVpXQiJhwOGG8A==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + dependencies: + '@types/node': 18.19.87 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -3028,13 +2963,6 @@ packages: yocto-queue: 0.1.0 dev: true - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - yocto-queue: 1.0.0 - dev: false - /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -3049,18 +2977,6 @@ packages: p-limit: 3.1.0 dev: true - /p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-limit: 4.0.0 - dev: false - - /p-timeout@6.1.1: - resolution: {integrity: sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w==} - engines: {node: '>=14.16'} - dev: false - /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3092,6 +3008,7 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -3103,11 +3020,6 @@ packages: engines: {node: '>=8'} dev: true - /path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false - /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -3198,11 +3110,6 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /quick-lru@6.1.1: - resolution: {integrity: sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==} - engines: {node: '>=12'} - dev: false - /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3227,15 +3134,6 @@ packages: type-fest: 0.8.1 dev: true - /read-pkg-up@9.1.0: - resolution: {integrity: sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - find-up: 6.3.0 - read-pkg: 7.1.0 - type-fest: 2.19.0 - dev: false - /read-pkg@5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} @@ -3246,16 +3144,6 @@ packages: type-fest: 0.6.0 dev: true - /read-pkg@7.1.0: - resolution: {integrity: sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==} - engines: {node: '>=12.20'} - dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 3.0.3 - parse-json: 5.2.0 - type-fest: 2.19.0 - dev: false - /readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} dependencies: @@ -3313,11 +3201,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: false - /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3510,30 +3393,6 @@ packages: engines: {node: '>=8'} dev: true - /smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - dev: false - - /socks-proxy-agent@7.0.0: - resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} - engines: {node: '>= 10'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - socks: 2.7.1 - transitivePeerDependencies: - - supports-color - dev: false - - /socks@2.7.1: - resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} - engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} - dependencies: - ip: 2.0.1 - smart-buffer: 4.2.0 - dev: false - /sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} requiresBuild: true @@ -3546,15 +3405,18 @@ packages: dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.12 + dev: true /spdx-exceptions@2.3.0: resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.3.0 spdx-license-ids: 3.0.12 + dev: true /spdx-expression-parse@4.0.0: resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} @@ -3565,6 +3427,7 @@ packages: /spdx-license-ids@3.0.12: resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true /speakeasy@2.0.0: resolution: {integrity: sha512-lW2A2s5LKi8rwu77ewisuUOtlCydF/hmQSOJjpTqTj1gZLkNgTaYnyvfxy2WBr4T/h+9c4g8HIITfj83OkFQFw==} @@ -3644,15 +3507,12 @@ packages: peek-readable: 5.0.0 dev: false - /stubborn-fs@1.2.4: - resolution: {integrity: sha512-KRa4nIRJ8q6uApQbPwYZVhOof8979fw4xbajBWa5kPJFa4nyY3aFaMWVyIVCDnkNCCG/3HLipUZ4QaNlYsmX1w==} - dev: false - /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} dependencies: has-flag: 3.0.0 + dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -3697,6 +3557,10 @@ packages: ieee754: 1.2.1 dev: false + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tr46@4.1.1: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} @@ -3760,11 +3624,6 @@ packages: engines: {node: '>=8'} dev: true - /type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - dev: false - /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3792,6 +3651,13 @@ packages: hasBin: true dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: false + + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + /unist-util-stringify-position@2.0.3: resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} dependencies: @@ -3812,20 +3678,17 @@ packages: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 + dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - dev: false - /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: spdx-correct: 3.1.1 spdx-expression-parse: 3.0.1 + dev: true /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} @@ -3855,6 +3718,15 @@ packages: engines: {node: '>= 8'} dev: false + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -3868,8 +3740,11 @@ packages: webidl-conversions: 7.0.0 dev: false - /when-exit@2.1.0: - resolution: {integrity: sha512-H85ulNwUBU1e6PGxkWUDgxnbohSXD++ah6Xw1VHAN7CtypcbZaC4aYjQ+C2PMVaDkURDuOinNAT+Lnz3utWXxQ==} + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 dev: false /which@2.0.2: @@ -3954,8 +3829,3 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true - - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: false diff --git a/service/src/chatgpt/index.ts b/service/src/chatgpt/index.ts index 2d092398..333cb5ce 100644 --- a/service/src/chatgpt/index.ts +++ b/service/src/chatgpt/index.ts @@ -1,22 +1,17 @@ import * as dotenv from 'dotenv' -import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt' -import { ChatGPTAPI } from 'chatgpt' -import { SocksProxyAgent } from 'socks-proxy-agent' -import httpsProxyAgent from 'https-proxy-agent' -import fetch from 'node-fetch' +import OpenAI from 'openai' +import { HttpsProxyAgent } from 'https-proxy-agent' import type { AuditConfig, KeyConfig, UserInfo } from '../storage/model' -import { Status } from '../storage/model' +import { Status, UsageResponse } from '../storage/model' import { convertImageUrl } from '../utils/image' import type { TextAuditService } from '../utils/textAudit' import { textAuditServices } from '../utils/textAudit' import { getCacheApiKeys, getCacheConfig, getOriginConfig } from '../storage/config' import { sendResponse } from '../utils' import { hasAnyRole, isNotEmptyString } from '../utils/is' -import type { ChatContext, ModelConfig } from '../types' +import type { ModelConfig } from '../types' import { getChatByMessageId, updateRoomChatModel } from '../storage/mongo' -import type { RequestOptions } from './types' - -const { HttpsProxyAgent } = httpsProxyAgent +import type { ChatMessage, RequestOptions } from './types' dotenv.config() @@ -32,59 +27,30 @@ const ErrorCodeMessage: Record = { let auditService: TextAuditService const _lockedKeys: { key: string; lockedTime: number }[] = [] -export async function initApi(key: KeyConfig, chatModel: string, maxContextCount: number) { - // More Info: https://github.com/transitive-bullshit/chatgpt-api - +export async function initApi(key: KeyConfig) { const config = await getCacheConfig() - const model = chatModel as string - - if (key.keyModel === 'ChatGPTAPI') { - const OPENAI_API_BASE_URL = isNotEmptyString(key.baseUrl) ? key.baseUrl : config.apiBaseUrl - - let contextCount = 0 - const options: ChatGPTAPIOptions = { - apiKey: key.key, - completionParams: { model }, - debug: !config.apiDisableDebug, - messageStore: undefined, - getMessageById: async (id) => { - if (contextCount++ >= maxContextCount) - return null - return await getMessageById(id) - }, - } - - // Set the token limits based on the model's type. This is because different models have different token limits. - // The token limit includes the token count from both the message array sent and the model response. + const openaiBaseUrl = isNotEmptyString(key.baseUrl) ? key.baseUrl : config.apiBaseUrl - if (model.toLowerCase().includes('gpt-4.1')) { - // https://platform.openai.com/docs/models/gpt-4.1 - options.maxModelTokens = 1047576 - options.maxResponseTokens = 32768 - } - else if (model.toLowerCase().includes('gpt-4o')) { - // https://platform.openai.com/docs/models/gpt-4o - options.maxModelTokens = 128000 - options.maxResponseTokens = 16384 - } - // If none of the above, use the default values - else { - options.maxModelTokens = 1047576 - options.maxResponseTokens = 32768 - } - - if (isNotEmptyString(OPENAI_API_BASE_URL)) - options.apiBaseUrl = `${OPENAI_API_BASE_URL}/v1` - - await setupProxy(options) - - return new ChatGPTAPI({ ...options }) + let httpAgent: HttpsProxyAgent | undefined + if (isNotEmptyString(config.httpsProxy)) { + const httpsProxy = config.httpsProxy + if (httpsProxy) + httpAgent = new HttpsProxyAgent(httpsProxy) } + + const client = new OpenAI({ + baseURL: openaiBaseUrl, + apiKey: key.key, + httpAgent, + }) + return client } + const processThreads: { userId: string; abort: AbortController; messageId: string }[] = [] + async function chatReplyProcess(options: RequestOptions) { const model = options.room.chatModel - const key = await getRandomApiKey(options.user, model, options.room.accountId) + const key = await getRandomApiKey(options.user, model) const userId = options.user._id.toString() const maxContextCount = options.user.advanced.maxContextCount ?? 20 const messageId = options.messageId @@ -94,70 +60,116 @@ async function chatReplyProcess(options: RequestOptions) { // Add Chat Record updateRoomChatModel(userId, options.room.roomId, model) - const { message, uploadFileKeys, lastContext, process, systemMessage, temperature, top_p } = options - let content: string | { - type: string - text?: string - image_url?: { - url: string - } - }[] = message - if (uploadFileKeys && uploadFileKeys.length > 0) { - content = [ - { - type: 'text', - text: message, - }, - ] - for (const uploadFileKey of uploadFileKeys) { - content.push({ - type: 'image_url', - image_url: { - url: await convertImageUrl(uploadFileKey), - }, - }) - } - } + const { message, uploadFileKeys, parentMessageId, process, systemMessage, temperature, top_p } = options try { - const timeoutMs = (await getCacheConfig()).timeoutMs - let options: SendMessageOptions = { timeoutMs } + // Initialize OpenAI client + const openai = await initApi(key) + + // Create abort controller for cancellation + const abort = new AbortController() + processThreads.push({ userId, abort, messageId }) - if (key.keyModel === 'ChatGPTAPI') { - if (isNotEmptyString(systemMessage)) - options.systemMessage = systemMessage - options.completionParams = { model, temperature, top_p } + // Prepare messages array for the chat completion + const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [] + + // Add previous messages from conversation history. + await addPreviousMessages(parentMessageId, maxContextCount, messages) + + // Add system message if provided + if (isNotEmptyString(systemMessage)) { + messages.unshift({ + role: 'system', + content: systemMessage, + }) } - if (lastContext != null) { - if (key.keyModel === 'ChatGPTAPI') - options.parentMessageId = lastContext.parentMessageId - else - options = { ...lastContext } + // Prepare the user message content (text and images) + let content: string | OpenAI.Chat.ChatCompletionContentPart[] = message + + // Handle image uploads if present + if (uploadFileKeys && uploadFileKeys.length > 0) { + content = [ + { + type: 'text', + text: message, + }, + ] + for (const uploadFileKey of uploadFileKeys) { + content.push({ + type: 'image_url', + image_url: { + url: await convertImageUrl(uploadFileKey), + }, + }) + } } - const api = await initApi(key, model, maxContextCount) - const abort = new AbortController() - options.abortSignal = abort.signal - processThreads.push({ userId, abort, messageId }) - const response = await api.sendMessage(content, { - ...options, - onProgress: (partialResponse) => { - process?.(partialResponse) + // Add the user message + messages.push({ + role: 'user', + content, + }) + + // Create the chat completion with streaming + const stream = await openai.chat.completions.create({ + model, + messages, + temperature: temperature ?? undefined, + top_p: top_p ?? undefined, + stream: true, + stream_options: { + include_usage: true, }, + }, { + signal: abort.signal, }) + + // Process the stream + let responseText = '' + let responseId = '' + const usage = new UsageResponse() + + for await (const chunk of stream) { + // Extract the content from the chunk + const content = chunk.choices[0]?.delta?.content || '' + responseText += content + responseId = chunk.id + + const finish_reason = chunk.choices[0]?.finish_reason + + // Build response object similar to the original implementation + const responseChunk = { + id: chunk.id, + text: responseText, + role: 'assistant', + finish_reason, + } + + // Call the process callback if provided + process?.(responseChunk) + + if (chunk?.usage?.total_tokens) { + usage.total_tokens = chunk?.usage?.total_tokens + usage.prompt_tokens = chunk?.usage?.prompt_tokens + usage.completion_tokens = chunk?.usage?.completion_tokens + } + } + + // Final response object + const response = { + id: responseId || messageId, + text: responseText, + role: 'assistant', + detail: { + usage, + }, + } + return sendResponse({ type: 'Success', data: response }) } catch (error: any) { - const code = error.statusCode - if (code === 429 && (error.message.includes('Too Many Requests') || error.message.includes('Rate limit'))) { - // access token Only one message at a time - if (options.tryCount++ < 3) { - _lockedKeys.push({ key: key.key, lockedTime: Date.now() }) - await new Promise(resolve => setTimeout(resolve, 2000)) - return await chatReplyProcess(options) - } - } + const code = error.status || error.statusCode globalThis.console.error(error) if (Reflect.has(ErrorCodeMessage, code)) return sendResponse({ type: 'Fail', message: ErrorCodeMessage[code] }) @@ -210,33 +222,6 @@ async function chatConfig() { }) } -async function setupProxy(options: ChatGPTAPIOptions) { - const config = await getCacheConfig() - if (isNotEmptyString(config.socksProxy)) { - const agent = new SocksProxyAgent({ - hostname: config.socksProxy.split(':')[0], - port: Number.parseInt(config.socksProxy.split(':')[1]), - userId: isNotEmptyString(config.socksAuth) ? config.socksAuth.split(':')[0] : undefined, - password: isNotEmptyString(config.socksAuth) ? config.socksAuth.split(':')[1] : undefined, - - }) - options.fetch = (url, options) => { - return fetch(url, { agent, ...options }) - } - } - else { - if (isNotEmptyString(config.httpsProxy)) { - const httpsProxy = config.httpsProxy - if (httpsProxy) { - const agent = new HttpsProxyAgent(httpsProxy) - options.fetch = (url, options) => { - return fetch(url, { agent, ...options }) - } - } - } - } -} - async function getMessageById(id: string): Promise { const isPrompt = id.startsWith('prompt_') const chatInfo = await getChatByMessageId(isPrompt ? id.substring(7) : id) @@ -253,13 +238,7 @@ async function getMessageById(id: string): Promise { } else { if (isPrompt) { // prompt - let content: string | { - type: string - text?: string - image_url?: { - url: string - } - }[] = chatInfo.prompt + let content: string | OpenAI.Chat.ChatCompletionContentPart[] = chatInfo.prompt if (chatInfo.images && chatInfo.images.length > 0) { content = [ { @@ -268,29 +247,30 @@ async function getMessageById(id: string): Promise { }, ] for (const image of chatInfo.images) { - content.push({ - type: 'image_url', - image_url: { - url: await convertImageUrl(image), - }, - }) + const imageUrlBase64 = await convertImageUrl(image) + if (imageUrlBase64) { + content.push({ + type: 'image_url', + image_url: { + url: await convertImageUrl(image), + }, + }) + } } } return { id, - conversationId: chatInfo.options.conversationId, parentMessageId, role: 'user', - text: content, + content, } } else { return { // completion id, - conversationId: chatInfo.options.conversationId, parentMessageId, role: 'assistant', - text: chatInfo.response, + content: chatInfo.response, } } } @@ -318,13 +298,31 @@ async function randomKeyConfig(keys: KeyConfig[]): Promise { return thisKey } -async function getRandomApiKey(user: UserInfo, chatModel: string, accountId?: string): Promise { +async function getRandomApiKey(user: UserInfo, chatModel: string): Promise { const keys = (await getCacheApiKeys()).filter(d => hasAnyRole(d.userRoles, user.roles)) .filter(d => d.chatModels.includes(chatModel)) return randomKeyConfig(keys) } -export type { ChatContext, ChatMessage } +// Helper function to add previous messages to the conversation context +async function addPreviousMessages(parentMessageId: string, maxContextCount: number, messages: OpenAI.Chat.ChatCompletionMessageParam[]): Promise { + // Recursively get previous messages + let currentMessageId = parentMessageId + + while (currentMessageId) { + const currentChatMessage: ChatMessage | undefined = await getMessageById(currentMessageId) + + messages.unshift({ + content: currentChatMessage.content, + role: currentChatMessage.role, + } as OpenAI.Chat.ChatCompletionMessage) + + currentMessageId = currentChatMessage?.parentMessageId + + if (messages.length >= maxContextCount) + break + } +} export { chatReplyProcess, chatConfig, containsSensitiveWords } diff --git a/service/src/chatgpt/types.ts b/service/src/chatgpt/types.ts index a8d9db5d..4efc3ffb 100644 --- a/service/src/chatgpt/types.ts +++ b/service/src/chatgpt/types.ts @@ -1,17 +1,33 @@ -import type { ChatMessage } from 'chatgpt' +import type OpenAI from 'openai' import type { ChatRoom, UserInfo } from 'src/storage/model' +export interface ChatMessage { + id: string + content: string | OpenAI.Chat.ChatCompletionContentPart[] + role: OpenAI.Chat.ChatCompletionRole + name?: string + delta?: string + detail?: string + parentMessageId?: string +} + +export interface ResponseChunk { + id: string + text: string + role: string + finish_reason: string +} + export interface RequestOptions { message: string uploadFileKeys?: string[] - lastContext?: { conversationId?: string; parentMessageId?: string } - process?: (chat: ChatMessage) => void + parentMessageId?: string + process?: (chunk: ResponseChunk) => void systemMessage?: string temperature?: number top_p?: number user: UserInfo messageId: string - tryCount: number room: ChatRoom } diff --git a/service/src/routes/chat.ts b/service/src/routes/chat.ts index a9aac7f3..5ec764d8 100644 --- a/service/src/routes/chat.ts +++ b/service/src/routes/chat.ts @@ -1,14 +1,13 @@ +import * as console from 'node:console' import { ObjectId } from 'mongodb' -import type { TiktokenModel } from 'gpt-token' -import { textTokens } from 'gpt-token' import Router from 'express' -import type { ChatMessage } from 'chatgpt' +import type { ResponseChunk } from '../chatgpt/types' import { limiter } from '../middleware/limiter' import { abortChatProcess, chatReplyProcess, containsSensitiveWords } from '../chatgpt' import { auth } from '../middleware/auth' import { getCacheConfig } from '../storage/config' -import type { ChatInfo, ChatOptions, UserInfo } from '../storage/model' -import { Status, UsageResponse, UserRole } from '../storage/model' +import type { ChatInfo, ChatOptions, UsageResponse, UserInfo } from '../storage/model' +import { Status, UserRole } from '../storage/model' import { clearChat, deleteAllChatRooms, @@ -91,14 +90,12 @@ router.get('/chat-history', auth, async (req, res) => { responseCount: (c.previousResponse?.length ?? 0) + 1, conversationOptions: { parentMessageId: c.options.messageId, - conversationId: c.options.conversationId, }, requestOptions: { prompt: c.prompt, parentMessageId: c.options.parentMessageId, options: { parentMessageId: c.options.messageId, - conversationId: c.options.conversationId, }, }, usage, @@ -153,14 +150,12 @@ router.get('/chat-response-history', auth, async (req, res) => { responseCount: (chat.previousResponse?.length ?? 0) + 1, conversationOptions: { parentMessageId: response.options.messageId, - conversationId: response.options.conversationId, }, requestOptions: { prompt: chat.prompt, parentMessageId: response.options.parentMessageId, options: { parentMessageId: response.options.messageId, - conversationId: response.options.conversationId, }, }, usage, @@ -263,25 +258,11 @@ router.post('/chat-process', [auth, limiter], async (req, res) => { result = await chatReplyProcess({ message: prompt, uploadFileKeys, - lastContext: options, - process: (chat: ChatMessage) => { - lastResponse = chat - const chuck = { - id: chat.id, - conversationId: chat.conversationId, - text: chat.text, - detail: { - choices: [ - { - finish_reason: undefined, - }, - ], - }, - } - if (chat.detail && chat.detail.choices.length > 0) - chuck.detail.choices[0].finish_reason = chat.detail.choices[0].finish_reason + parentMessageId: options?.parentMessageId, + process: (chunk: ResponseChunk) => { + lastResponse = chunk - res.write(firstChunk ? JSON.stringify(chuck) : `\n${JSON.stringify(chuck)}`) + res.write(firstChunk ? JSON.stringify(chunk) : `\n${JSON.stringify(chunk)}`) firstChunk = false }, systemMessage, @@ -289,20 +270,9 @@ router.post('/chat-process', [auth, limiter], async (req, res) => { top_p, user, messageId: message._id.toString(), - tryCount: 0, room, }) // return the whole response including usage - if (!result.data.detail?.usage) { - if (!result.data.detail) - result.data.detail = {} - result.data.detail.usage = new UsageResponse() - // if no usage data, calculate using Tiktoken library - result.data.detail.usage.prompt_tokens = textTokens(prompt, model as TiktokenModel) - result.data.detail.usage.completion_tokens = textTokens(result.data.text, model as TiktokenModel) - result.data.detail.usage.total_tokens = result.data.detail.usage.prompt_tokens + result.data.detail.usage.completion_tokens - result.data.detail.usage.estimated = true - } res.write(`\n${JSON.stringify(result.data)}`) } catch (error) { @@ -327,7 +297,6 @@ router.post('/chat-process', [auth, limiter], async (req, res) => { await updateChat(message._id as unknown as string, result.data.text, result.data.id, - result.data.conversationId, model, result.data.detail?.usage as UsageResponse, previousResponse as []) @@ -336,7 +305,6 @@ router.post('/chat-process', [auth, limiter], async (req, res) => { await updateChat(message._id as unknown as string, result.data.text, result.data.id, - result.data.conversationId, model, result.data.detail?.usage as UsageResponse) } diff --git a/service/src/storage/model.ts b/service/src/storage/model.ts index 0e581a89..d4b1cd7e 100644 --- a/service/src/storage/model.ts +++ b/service/src/storage/model.ts @@ -98,15 +98,13 @@ export class ChatRoom { export class ChatOptions { parentMessageId?: string messageId?: string - conversationId?: string prompt_tokens?: number completion_tokens?: number total_tokens?: number estimated?: boolean - constructor(parentMessageId?: string, messageId?: string, conversationId?: string) { + constructor(parentMessageId?: string, messageId?: string) { this.parentMessageId = parentMessageId this.messageId = messageId - this.conversationId = conversationId } } @@ -287,4 +285,4 @@ export class UserPrompt { } } -export type APIMODEL = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' +export type APIMODEL = 'ChatGPTAPI' diff --git a/service/src/storage/mongo.ts b/service/src/storage/mongo.ts index 74065591..a48693e4 100644 --- a/service/src/storage/mongo.ts +++ b/service/src/storage/mongo.ts @@ -93,14 +93,13 @@ export async function getChatByMessageId(messageId: string) { return await chatCol.findOne({ 'options.messageId': messageId }) } -export async function updateChat(chatId: string, response: string, messageId: string, conversationId: string, model: string, usage: UsageResponse, previousResponse?: []) { +export async function updateChat(chatId: string, response: string, messageId: string, model: string, usage: UsageResponse, previousResponse?: []) { const query = { _id: new ObjectId(chatId) } const update = { $set: { 'response': response, 'model': model || '', 'options.messageId': messageId, - 'options.conversationId': conversationId, 'options.prompt_tokens': usage?.prompt_tokens, 'options.completion_tokens': usage?.completion_tokens, 'options.total_tokens': usage?.total_tokens, diff --git a/service/src/utils/image.ts b/service/src/utils/image.ts index a7e04093..e39b2288 100644 --- a/service/src/utils/image.ts +++ b/service/src/utils/image.ts @@ -1,4 +1,5 @@ import fs from 'node:fs/promises' +import type { Buffer } from 'node:buffer' import * as fileType from 'file-type' fs.mkdir('uploads').then(() => { @@ -11,8 +12,15 @@ fs.mkdir('uploads').then(() => { globalThis.console.error('Error creating directory uploads, ', e) }) -export async function convertImageUrl(uploadFileKey: string): Promise { - const imageData = await fs.readFile(`uploads/${uploadFileKey}`) +export async function convertImageUrl(uploadFileKey: string): Promise { + let imageData: Buffer + try { + imageData = await fs.readFile(`uploads/${uploadFileKey}`) + } + catch (e) { + globalThis.console.error(`Error open uploads file ${uploadFileKey}, ${e.message}`) + return + } // 判断文件格式 const imageType = await fileType.fileTypeFromBuffer(imageData) const mimeType = imageType.mime From 2c1cf725713f16fe9c23e51c050ac71df194f386 Mon Sep 17 00:00:00 2001 From: Bob Du Date: Tue, 6 May 2025 18:05:56 +0800 Subject: [PATCH 3/3] docs: update readme remove unofficial api support Signed-off-by: Bob Du --- README.en.md | 39 ++++++++------------------------------- README.md | 49 ++++++++++++++++++++----------------------------- 2 files changed, 28 insertions(+), 60 deletions(-) diff --git a/README.en.md b/README.en.md index 22203434..15a15c1c 100644 --- a/README.en.md +++ b/README.en.md @@ -79,33 +79,15 @@ Some unique features have been added: ## Introduction -Supports dual models, provides two unofficial `ChatGPT API` methods: +Uses the official `OpenAI API` to access `ChatGPT`: -| Method | Free? | Reliability | Quality | -| --------------------------------------------- | ------ | ----------- | ------- | -| `ChatGPTAPI(gpt-3.5-turbo-0301)` | No | Reliable | Relatively clumsy | -| `ChatGPTUnofficialProxyAPI(Web accessToken)` | Yes | Relatively unreliable | Smart | - -Comparison: -1. `ChatGPTAPI` uses `gpt-3.5-turbo-0301` to simulate `ChatGPT` through the official `OpenAI` completion `API` (the most reliable method, but it is not free and does not use models specifically tuned for chat). -2. `ChatGPTUnofficialProxyAPI` accesses `ChatGPT`'s backend `API` via an unofficial proxy server to bypass `Cloudflare` (uses the real `ChatGPT`, is very lightweight, but depends on third-party servers and has rate limits). +`ChatGPTAPI` uses `gpt-4.1` through the official `OpenAI` completion `API` (requires an API key). [Details](https://github.com/Chanzhaoyu/chatgpt-web/issues/138) -Switching Methods: +Setup: 1. Go to the `service/.env.example` file and copy the contents to the `service/.env` file. -2. For `OpenAI API Key`, fill in the `OPENAI_API_KEY` field [(Get apiKey)](https://platform.openai.com/overview). -3. For `Web API`, fill in the `OPENAI_ACCESS_TOKEN` field [(Get accessToken)](https://chat.openai.com/api/auth/session). -4. When both are present, `OpenAI API Key` takes precedence. - -Reverse Proxy: - -Available when using `ChatGPTUnofficialProxyAPI`.[Details](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) - -```shell -# service/.env -API_REVERSE_PROXY= -``` +2. Fill in the `OPENAI_API_KEY` field with your OpenAI API Key [(Get apiKey)](https://platform.openai.com/overview). Environment Variables: @@ -152,18 +134,15 @@ If you have not installed `pnpm` before: npm install pnpm -g ``` -### Fill in the Keys +### Fill in the API Key -Get `Openai Api Key` or `accessToken` and fill in the local environment variables [jump](#introduction) +Get your `OpenAI API Key` and fill in the local environment variables [jump](#introduction) ``` # service/.env file # OpenAI API Key - https://platform.openai.com/overview OPENAI_API_KEY= - -# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response -OPENAI_ACCESS_TOKEN= ``` ## Install Dependencies @@ -205,11 +184,9 @@ pnpm dev #### Docker Parameter Example -- `OPENAI_API_KEY` one of two -- `OPENAI_ACCESS_TOKEN` one of two, `OPENAI_API_KEY` takes precedence when both are present +- `OPENAI_API_KEY` required - `OPENAI_API_BASE_URL` optional, available when `OPENAI_API_KEY` is set -- `OPENAI_API_MODEL` `ChatGPTAPI` OR `ChatGPTUnofficialProxyAPI` -- `API_REVERSE_PROXY` optional, available when `OPENAI_ACCESS_TOKEN` is set [Reference](#introduction) +- `OPENAI_API_MODEL` optional, specify the model to use - `AUTH_SECRET_KEY` Access Password,optional - `TIMEOUT_MS` timeout, in milliseconds, optional - `SOCKS_PROXY_HOST` optional, effective with SOCKS_PROXY_PORT diff --git a/README.md b/README.md index 6667d731..2e606a81 100644 --- a/README.md +++ b/README.md @@ -80,29 +80,17 @@ - [License](#license) ## 介绍 -支持双模型,提供了两种非官方 `ChatGPT API` 方法 +使用官方 `OpenAI API` 访问 `ChatGPT`: -| 方式 | 免费? | 可靠性 | 质量 | -| --------------------------------------------- | ------ | ---------- | ---- | -| `ChatGPTAPI(gpt-3.5-turbo-0301)` | 否 | 可靠 | 相对较笨 | -| `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是 | 相对不可靠 | 聪明 | - -对比: -1. `ChatGPTAPI` 使用 `gpt-3.5-turbo` 通过 `OpenAI` 官方 `API` 调用 `ChatGPT` -2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(依赖于第三方服务器,并且有速率限制) +`ChatGPTAPI` 使用 `gpt-4.1` 通过 `OpenAI` 官方 `API` 调用 `ChatGPT`(需要 API 密钥)。 警告: -1. 你应该首先使用 `API` 方式 -2. 使用 `API` 时,如果网络不通,那是国内被墙了,你需要自建代理,绝对不要使用别人的公开代理,那是危险的。 -3. 使用 `accessToken` 方式时反向代理将向第三方暴露您的访问令牌,这样做应该不会产生任何不良影响,但在使用这种方法之前请考虑风险。 -4. 使用 `accessToken` 时,不管你是国内还是国外的机器,都会使用代理。默认代理为 [pengzhile](https://github.com/pengzhile) 大佬的 `https://ai.fakeopen.com/api/conversation`,这不是后门也不是监听,除非你有能力自己翻过 `CF` 验证,用前请知悉。[社区代理](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)(注意:只有这两个是推荐,其他第三方来源,请自行甄别) -5. 把项目发布到公共网络时,你应该设置 `AUTH_SECRET_KEY` 变量添加你的密码访问权限,你也应该修改 `index.html` 中的 `title`,防止被关键词搜索到。 +1. 使用 `API` 时,如果网络不通,那是国内被墙了,你需要自建代理,绝对不要使用别人的公开代理,那是危险的。 +2. 把项目发布到公共网络时,你应该设置 `AUTH_SECRET_KEY` 变量添加你的密码访问权限,你也应该修改 `index.html` 中的 `title`,防止被关键词搜索到。 -切换方式: +设置方式: 1. 进入 `service/.env.example` 文件,复制内容到 `service/.env` 文件 -2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview) -3. 使用 `Web API` 请填写 `OPENAI_ACCESS_TOKEN` 字段 [(获取 accessToken)](https://chat.openai.com/api/auth/session) -4. 同时存在时以 `OpenAI API Key` 优先 +2. 填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview) 环境变量: @@ -149,17 +137,14 @@ node -v npm install pnpm -g ``` -### 填写密钥 -获取 `Openai Api Key` 或 `accessToken` 并填写本地环境变量 [跳转](#介绍) +### 填写API密钥 +获取 `OpenAI API Key` 并填写本地环境变量 [跳转](#介绍) ``` # service/.env 文件 # OpenAI API Key - https://platform.openai.com/overview OPENAI_API_KEY= - -# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response -OPENAI_ACCESS_TOKEN= ``` ## 安装依赖 @@ -199,15 +184,10 @@ pnpm dev `API` 可用: -- `OPENAI_API_KEY` 和 `OPENAI_ACCESS_TOKEN` 二选一 +- `OPENAI_API_KEY` 必填 - `OPENAI_API_BASE_URL` 设置接口地址,可选,默认:`https://api.openai.com` - `OPENAI_API_DISABLE_DEBUG` 设置接口关闭 debug 日志,可选,默认:empty 不关闭 -`ACCESS_TOKEN` 可用: - -- `OPENAI_ACCESS_TOKEN` 和 `OPENAI_API_KEY` 二选一,同时存在时,`OPENAI_API_KEY` 优先 -- `API_REVERSE_PROXY` 设置反向代理,可选,默认:`https://ai.fakeopen.com/api/conversation`,[社区](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)(注意:只有这两个是推荐,其他第三方来源,请自行甄别) - 通用: - `AUTH_SECRET_KEY` 访问权限密钥,可选 @@ -223,6 +203,17 @@ pnpm dev #### Docker 参数示例 +- `OPENAI_API_KEY` 必填 +- `OPENAI_API_BASE_URL` 可选,设置接口地址,默认:`https://api.openai.com` +- `OPENAI_API_MODEL` 可选,指定使用的模型 +- `AUTH_SECRET_KEY` 访问密码,可选 +- `TIMEOUT_MS` 超时,单位毫秒,可选 +- `SOCKS_PROXY_HOST` 可选,与 SOCKS_PROXY_PORT 一起使用 +- `SOCKS_PROXY_PORT` 可选,与 SOCKS_PROXY_HOST 一起使用 +- `SOCKS_PROXY_USERNAME` 可选,与 SOCKS_PROXY_HOST 和 SOCKS_PROXY_PORT 一起使用 +- `SOCKS_PROXY_PASSWORD` 可选,与 SOCKS_PROXY_HOST 和 SOCKS_PROXY_PORT 一起使用 +- `HTTPS_PROXY` 可选,支持 http,https, socks5 + ![docker](./docs/docker.png) #### Docker build & Run