|
|
|
import path from "path"; |
|
import fs from "fs-extra"; |
|
import minimist from "minimist"; |
|
import _ from "lodash"; |
|
var cmdArgs = minimist(process.argv.slice(2)); |
|
var envVars = process.env; |
|
var Environment = class { |
|
|
|
cmdArgs; |
|
|
|
envVars; |
|
|
|
env; |
|
|
|
name; |
|
|
|
host; |
|
|
|
port; |
|
|
|
package; |
|
constructor(options = {}) { |
|
const { cmdArgs: cmdArgs2, envVars: envVars2, package: _package } = options; |
|
this.cmdArgs = cmdArgs2; |
|
this.envVars = envVars2; |
|
this.env = _.defaultTo(cmdArgs2.env || envVars2.SERVER_ENV, "dev"); |
|
this.name = cmdArgs2.name || envVars2.SERVER_NAME || void 0; |
|
this.host = cmdArgs2.host || envVars2.SERVER_HOST || void 0; |
|
this.port = Number(cmdArgs2.port || envVars2.SERVER_PORT) ? Number(cmdArgs2.port || envVars2.SERVER_PORT) : void 0; |
|
this.package = _package; |
|
} |
|
}; |
|
var environment_default = new Environment({ |
|
cmdArgs, |
|
envVars, |
|
package: JSON.parse(fs.readFileSync(path.join(path.resolve(), "package.json")).toString()) |
|
}); |
|
|
|
|
|
import path3 from "path"; |
|
import fs3 from "fs-extra"; |
|
import yaml from "yaml"; |
|
import _3 from "lodash"; |
|
|
|
|
|
import os from "os"; |
|
import path2 from "path"; |
|
import crypto from "crypto"; |
|
import { Readable, Writable } from "stream"; |
|
import "colors"; |
|
import mime from "mime"; |
|
import axios from "axios"; |
|
import fs2 from "fs-extra"; |
|
import { v1 as uuid } from "uuid"; |
|
import { format as dateFormat } from "date-fns"; |
|
import CRC32 from "crc-32"; |
|
import randomstring from "randomstring"; |
|
import _2 from "lodash"; |
|
import { CronJob } from "cron"; |
|
|
|
|
|
var http_status_codes_default = { |
|
CONTINUE: 100, |
|
|
|
SWITCHING_PROTOCOLS: 101, |
|
|
|
PROCESSING: 102, |
|
|
|
OK: 200, |
|
|
|
CREATED: 201, |
|
|
|
ACCEPTED: 202, |
|
|
|
NON_AUTHORITATIVE_INFO: 203, |
|
|
|
NO_CONTENT: 204, |
|
|
|
RESET_CONTENT: 205, |
|
|
|
PARTIAL_CONTENT: 206, |
|
|
|
MULTIPLE_STATUS: 207, |
|
|
|
MULTIPLE_CHOICES: 300, |
|
|
|
MOVED_PERMANENTLY: 301, |
|
|
|
FOUND: 302, |
|
|
|
SEE_OTHER: 303, |
|
|
|
NOT_MODIFIED: 304, |
|
|
|
USE_PROXY: 305, |
|
|
|
UNUSED: 306, |
|
|
|
TEMPORARY_REDIRECT: 307, |
|
|
|
BAD_REQUEST: 400, |
|
|
|
UNAUTHORIZED: 401, |
|
|
|
PAYMENT_REQUIRED: 402, |
|
|
|
FORBIDDEN: 403, |
|
|
|
NOT_FOUND: 404, |
|
|
|
METHOD_NOT_ALLOWED: 405, |
|
|
|
NO_ACCEPTABLE: 406, |
|
|
|
PROXY_AUTHENTICATION_REQUIRED: 407, |
|
|
|
REQUEST_TIMEOUT: 408, |
|
|
|
CONFLICT: 409, |
|
|
|
GONE: 410, |
|
|
|
LENGTH_REQUIRED: 411, |
|
|
|
PRECONDITION_FAILED: 412, |
|
|
|
REQUEST_ENTITY_TOO_LARGE: 413, |
|
|
|
REQUEST_URI_TOO_LONG: 414, |
|
|
|
UNSUPPORTED_MEDIA_TYPE: 415, |
|
|
|
REQUESTED_RANGE_NOT_SATISFIABLE: 416, |
|
|
|
EXPECTION_FAILED: 417, |
|
|
|
TOO_MANY_CONNECTIONS: 421, |
|
|
|
UNPROCESSABLE_ENTITY: 422, |
|
|
|
FAILED_DEPENDENCY: 424, |
|
|
|
UNORDERED_COLLECTION: 425, |
|
|
|
UPGRADE_REQUIRED: 426, |
|
|
|
RETRY_WITH: 449, |
|
|
|
INTERNAL_SERVER_ERROR: 500, |
|
|
|
NOT_IMPLEMENTED: 501, |
|
|
|
BAD_GATEWAY: 502, |
|
|
|
SERVICE_UNAVAILABLE: 503, |
|
|
|
GATEWAY_TIMEOUT: 504, |
|
|
|
HTTP_VERSION_NOT_SUPPORTED: 505, |
|
|
|
VARIANT_ALSO_NEGOTIATES: 506, |
|
|
|
INSUFFICIENT_STORAGE: 507, |
|
|
|
BANDWIDTH_LIMIT_EXCEEDED: 509, |
|
|
|
NOT_EXTENDED: 510 |
|
|
|
}; |
|
|
|
|
|
var autoIdMap = new Map(); |
|
var util = { |
|
is2DArrays(value) { |
|
return _2.isArray(value) && (!value[0] || _2.isArray(value[0]) && _2.isArray(value[value.length - 1])); |
|
}, |
|
uuid: (separator = true) => separator ? uuid() : uuid().replace(/\-/g, ""), |
|
autoId: (prefix = "") => { |
|
let index = autoIdMap.get(prefix); |
|
if (index > 999999) index = 0; |
|
autoIdMap.set(prefix, (index || 0) + 1); |
|
return `${prefix}${index || 1}`; |
|
}, |
|
ignoreJSONParse(value) { |
|
const result = _2.attempt(() => JSON.parse(value)); |
|
if (_2.isError(result)) return null; |
|
return result; |
|
}, |
|
generateRandomString(options) { |
|
return randomstring.generate(options); |
|
}, |
|
getResponseContentType(value) { |
|
return value.headers ? value.headers["content-type"] || value.headers["Content-Type"] : null; |
|
}, |
|
mimeToExtension(value) { |
|
let extension = mime.getExtension(value); |
|
if (extension == "mpga") return "mp3"; |
|
return extension; |
|
}, |
|
extractURLExtension(value) { |
|
const extname = path2.extname(new URL(value).pathname); |
|
return extname.substring(1).toLowerCase(); |
|
}, |
|
createCronJob(cronPatterns, callback) { |
|
if (!_2.isFunction(callback)) |
|
throw new Error("callback must be an Function"); |
|
return new CronJob( |
|
cronPatterns, |
|
() => callback(), |
|
null, |
|
false, |
|
"Asia/Shanghai" |
|
); |
|
}, |
|
getDateString(format = "yyyy-MM-dd", date = new Date()) { |
|
return dateFormat(date, format); |
|
}, |
|
getIPAddressesByIPv4() { |
|
const interfaces = os.networkInterfaces(); |
|
const addresses = []; |
|
for (let name in interfaces) { |
|
const networks = interfaces[name]; |
|
const results = networks.filter( |
|
(network) => network.family === "IPv4" && network.address !== "127.0.0.1" && !network.internal |
|
); |
|
if (results[0] && results[0].address) addresses.push(results[0].address); |
|
} |
|
return addresses; |
|
}, |
|
getMACAddressesByIPv4() { |
|
const interfaces = os.networkInterfaces(); |
|
const addresses = []; |
|
for (let name in interfaces) { |
|
const networks = interfaces[name]; |
|
const results = networks.filter( |
|
(network) => network.family === "IPv4" && network.address !== "127.0.0.1" && !network.internal |
|
); |
|
if (results[0] && results[0].mac) addresses.push(results[0].mac); |
|
} |
|
return addresses; |
|
}, |
|
generateSSEData(event, data, retry) { |
|
return `event: ${event || "message"} |
|
data: ${(data || "").replace(/\n/g, "\\n").replace(/\s/g, "\\s")} |
|
retry: ${retry || 3e3} |
|
|
|
`; |
|
}, |
|
buildDataBASE64(type, ext, buffer) { |
|
return `data:${type}/${ext.replace("jpg", "jpeg")};base64,${buffer.toString( |
|
"base64" |
|
)}`; |
|
}, |
|
isLinux() { |
|
return os.platform() !== "win32"; |
|
}, |
|
isIPAddress(value) { |
|
return _2.isString(value) && (/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/.test( |
|
value |
|
) || /\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*/.test( |
|
value |
|
)); |
|
}, |
|
isPort(value) { |
|
return _2.isNumber(value) && value > 0 && value < 65536; |
|
}, |
|
isReadStream(value) { |
|
return value && (value instanceof Readable || "readable" in value || value.readable); |
|
}, |
|
isWriteStream(value) { |
|
return value && (value instanceof Writable || "writable" in value || value.writable); |
|
}, |
|
isHttpStatusCode(value) { |
|
return _2.isNumber(value) && Object.values(http_status_codes_default).includes(value); |
|
}, |
|
isURL(value) { |
|
return !_2.isUndefined(value) && /^(http|https)/.test(value); |
|
}, |
|
isSrc(value) { |
|
return !_2.isUndefined(value) && /^\/.+\.[0-9a-zA-Z]+(\?.+)?$/.test(value); |
|
}, |
|
isBASE64(value) { |
|
return !_2.isUndefined(value) && /^[a-zA-Z0-9\/\+]+(=?)+$/.test(value); |
|
}, |
|
isBASE64Data(value) { |
|
return /^data:/.test(value); |
|
}, |
|
extractBASE64DataFormat(value) { |
|
const match = value.trim().match(/^data:(.+);base64,/); |
|
if (!match) return null; |
|
return match[1]; |
|
}, |
|
removeBASE64DataHeader(value) { |
|
return value.replace(/^data:(.+);base64,/, ""); |
|
}, |
|
isDataString(value) { |
|
return /^(base64|json):/.test(value); |
|
}, |
|
isStringNumber(value) { |
|
return _2.isFinite(Number(value)); |
|
}, |
|
isUnixTimestamp(value) { |
|
return /^[0-9]{10}$/.test(`${value}`); |
|
}, |
|
isTimestamp(value) { |
|
return /^[0-9]{13}$/.test(`${value}`); |
|
}, |
|
isEmail(value) { |
|
return /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test( |
|
value |
|
); |
|
}, |
|
isAsyncFunction(value) { |
|
return Object.prototype.toString.call(value) === "[object AsyncFunction]"; |
|
}, |
|
async isAPNG(filePath) { |
|
let head; |
|
const readStream = fs2.createReadStream(filePath, { start: 37, end: 40 }); |
|
const readPromise = new Promise((resolve, reject) => { |
|
readStream.once("end", resolve); |
|
readStream.once("error", reject); |
|
}); |
|
readStream.once("data", (data) => head = data); |
|
await readPromise; |
|
return head.compare(Buffer.from([97, 99, 84, 76])) === 0; |
|
}, |
|
unixTimestamp() { |
|
return parseInt(`${Date.now() / 1e3}`); |
|
}, |
|
timestamp() { |
|
return Date.now(); |
|
}, |
|
urlJoin(...values) { |
|
let url = ""; |
|
for (let i = 0; i < values.length; i++) |
|
url += `${i > 0 ? "/" : ""}${values[i].replace(/^\/*/, "").replace(/\/*$/, "")}`; |
|
return url; |
|
}, |
|
millisecondsToHmss(milliseconds) { |
|
if (_2.isString(milliseconds)) return milliseconds; |
|
milliseconds = parseInt(milliseconds); |
|
const sec = Math.floor(milliseconds / 1e3); |
|
const hours = Math.floor(sec / 3600); |
|
const minutes = Math.floor((sec - hours * 3600) / 60); |
|
const seconds = sec - hours * 3600 - minutes * 60; |
|
const ms = milliseconds % 6e4 - seconds * 1e3; |
|
return `${hours > 9 ? hours : "0" + hours}:${minutes > 9 ? minutes : "0" + minutes}:${seconds > 9 ? seconds : "0" + seconds}.${ms}`; |
|
}, |
|
millisecondsToTimeString(milliseconds) { |
|
if (milliseconds < 1e3) return `${milliseconds}ms`; |
|
if (milliseconds < 6e4) |
|
return `${parseFloat((milliseconds / 1e3).toFixed(2))}s`; |
|
return `${Math.floor(milliseconds / 1e3 / 60)}m${Math.floor( |
|
milliseconds / 1e3 % 60 |
|
)}s`; |
|
}, |
|
rgbToHex(r, g, b) { |
|
return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); |
|
}, |
|
hexToRgb(hex) { |
|
const value = parseInt(hex.replace(/^#/, ""), 16); |
|
return [value >> 16 & 255, value >> 8 & 255, value & 255]; |
|
}, |
|
md5(value) { |
|
return crypto.createHash("md5").update(value).digest("hex"); |
|
}, |
|
crc32(value) { |
|
return _2.isBuffer(value) ? CRC32.buf(value) : CRC32.str(value); |
|
}, |
|
arrayParse(value) { |
|
return _2.isArray(value) ? value : [value]; |
|
}, |
|
booleanParse(value) { |
|
return value === "true" || value === true ? true : false; |
|
}, |
|
encodeBASE64(value) { |
|
return Buffer.from(value).toString("base64"); |
|
}, |
|
decodeBASE64(value) { |
|
return Buffer.from(value, "base64").toString(); |
|
}, |
|
async fetchFileBASE64(url) { |
|
const result = await axios.get(url, { |
|
responseType: "arraybuffer" |
|
}); |
|
return result.data.toString("base64"); |
|
} |
|
}; |
|
var util_default = util; |
|
|
|
|
|
var CONFIG_PATH = path3.join(path3.resolve(), "configs/", environment_default.env, "/service.yml"); |
|
var ServiceConfig = class _ServiceConfig { |
|
|
|
name; |
|
|
|
host; |
|
|
|
port; |
|
|
|
urlPrefix; |
|
|
|
bindAddress; |
|
constructor(options) { |
|
const { name, host, port, urlPrefix, bindAddress } = options || {}; |
|
this.name = _3.defaultTo(name, "deepseek-free-api"); |
|
this.host = _3.defaultTo(host, "0.0.0.0"); |
|
this.port = _3.defaultTo(port, 5566); |
|
this.urlPrefix = _3.defaultTo(urlPrefix, ""); |
|
this.bindAddress = bindAddress; |
|
} |
|
get addressHost() { |
|
if (this.bindAddress) return this.bindAddress; |
|
const ipAddresses = util_default.getIPAddressesByIPv4(); |
|
for (let ipAddress of ipAddresses) { |
|
if (ipAddress === this.host) |
|
return ipAddress; |
|
} |
|
return ipAddresses[0] || "127.0.0.1"; |
|
} |
|
get address() { |
|
return `${this.addressHost}:${this.port}`; |
|
} |
|
get pageDirUrl() { |
|
return `http://127.0.0.1:${this.port}/page`; |
|
} |
|
get publicDirUrl() { |
|
return `http://127.0.0.1:${this.port}/public`; |
|
} |
|
static load() { |
|
const external = _3.pickBy(environment_default, (v, k) => ["name", "host", "port"].includes(k) && !_3.isUndefined(v)); |
|
if (!fs3.pathExistsSync(CONFIG_PATH)) return new _ServiceConfig(external); |
|
const data = yaml.parse(fs3.readFileSync(CONFIG_PATH).toString()); |
|
return new _ServiceConfig({ ...data, ...external }); |
|
} |
|
}; |
|
var service_config_default = ServiceConfig.load(); |
|
|
|
|
|
import path4 from "path"; |
|
import fs4 from "fs-extra"; |
|
import yaml2 from "yaml"; |
|
import _4 from "lodash"; |
|
var CONFIG_PATH2 = path4.join(path4.resolve(), "configs/", environment_default.env, "/system.yml"); |
|
var SystemConfig = class _SystemConfig { |
|
|
|
requestLog; |
|
|
|
tmpDir; |
|
|
|
logDir; |
|
|
|
logWriteInterval; |
|
|
|
logFileExpires; |
|
|
|
publicDir; |
|
|
|
tmpFileExpires; |
|
|
|
requestBody; |
|
|
|
debug; |
|
constructor(options) { |
|
const { requestLog, tmpDir, logDir, logWriteInterval, logFileExpires, publicDir, tmpFileExpires, requestBody, debug } = options || {}; |
|
this.requestLog = _4.defaultTo(requestLog, false); |
|
this.tmpDir = _4.defaultTo(tmpDir, "./tmp"); |
|
this.logDir = _4.defaultTo(logDir, "./logs"); |
|
this.logWriteInterval = _4.defaultTo(logWriteInterval, 200); |
|
this.logFileExpires = _4.defaultTo(logFileExpires, 262656e4); |
|
this.publicDir = _4.defaultTo(publicDir, "./public"); |
|
this.tmpFileExpires = _4.defaultTo(tmpFileExpires, 864e5); |
|
this.requestBody = Object.assign(requestBody || {}, { |
|
enableTypes: ["json", "form", "text", "xml"], |
|
encoding: "utf-8", |
|
formLimit: "100mb", |
|
jsonLimit: "100mb", |
|
textLimit: "100mb", |
|
xmlLimit: "100mb", |
|
formidable: { |
|
maxFileSize: "100mb" |
|
}, |
|
multipart: true, |
|
parsedMethods: ["POST", "PUT", "PATCH"] |
|
}); |
|
this.debug = _4.defaultTo(debug, true); |
|
} |
|
get rootDirPath() { |
|
return path4.resolve(); |
|
} |
|
get tmpDirPath() { |
|
return path4.resolve(this.tmpDir); |
|
} |
|
get logDirPath() { |
|
return path4.resolve(this.logDir); |
|
} |
|
get publicDirPath() { |
|
return path4.resolve(this.publicDir); |
|
} |
|
static load() { |
|
if (!fs4.pathExistsSync(CONFIG_PATH2)) return new _SystemConfig(); |
|
const data = yaml2.parse(fs4.readFileSync(CONFIG_PATH2).toString()); |
|
return new _SystemConfig(data); |
|
} |
|
}; |
|
var system_config_default = SystemConfig.load(); |
|
|
|
|
|
var Config = class { |
|
|
|
service = service_config_default; |
|
|
|
system = system_config_default; |
|
}; |
|
var config_default = new Config(); |
|
|
|
|
|
import path5 from "path"; |
|
import _util from "util"; |
|
import "colors"; |
|
import _5 from "lodash"; |
|
import fs5 from "fs-extra"; |
|
import { format as dateFormat2 } from "date-fns"; |
|
var isVercelEnv = process.env.VERCEL; |
|
var LogWriter = class { |
|
#buffers = []; |
|
constructor() { |
|
!isVercelEnv && fs5.ensureDirSync(config_default.system.logDirPath); |
|
!isVercelEnv && this.work(); |
|
} |
|
push(content) { |
|
const buffer = Buffer.from(content); |
|
this.#buffers.push(buffer); |
|
} |
|
writeSync(buffer) { |
|
!isVercelEnv && fs5.appendFileSync(path5.join(config_default.system.logDirPath, `/${util_default.getDateString()}.log`), buffer); |
|
} |
|
async write(buffer) { |
|
!isVercelEnv && await fs5.appendFile(path5.join(config_default.system.logDirPath, `/${util_default.getDateString()}.log`), buffer); |
|
} |
|
flush() { |
|
if (!this.#buffers.length) return; |
|
!isVercelEnv && fs5.appendFileSync(path5.join(config_default.system.logDirPath, `/${util_default.getDateString()}.log`), Buffer.concat(this.#buffers)); |
|
} |
|
work() { |
|
if (!this.#buffers.length) return setTimeout(this.work.bind(this), config_default.system.logWriteInterval); |
|
const buffer = Buffer.concat(this.#buffers); |
|
this.#buffers = []; |
|
this.write(buffer).finally(() => setTimeout(this.work.bind(this), config_default.system.logWriteInterval)).catch((err) => console.error("Log write error:", err)); |
|
} |
|
}; |
|
var LogText = class { |
|
|
|
level; |
|
|
|
text; |
|
|
|
source; |
|
|
|
time = new Date(); |
|
constructor(level, ...params) { |
|
this.level = level; |
|
this.text = _util.format.apply(null, params); |
|
this.source = this.#getStackTopCodeInfo(); |
|
} |
|
#getStackTopCodeInfo() { |
|
const unknownInfo = { name: "unknown", codeLine: 0, codeColumn: 0 }; |
|
const stackArray = new Error().stack.split("\n"); |
|
const text = stackArray[4]; |
|
if (!text) |
|
return unknownInfo; |
|
const match = text.match(/at (.+) \((.+)\)/) || text.match(/at (.+)/); |
|
if (!match || !_5.isString(match[2] || match[1])) |
|
return unknownInfo; |
|
const temp = match[2] || match[1]; |
|
const _match = temp.match(/([a-zA-Z0-9_\-\.]+)\:(\d+)\:(\d+)$/); |
|
if (!_match) |
|
return unknownInfo; |
|
const [, scriptPath, codeLine, codeColumn] = _match; |
|
return { |
|
name: scriptPath ? scriptPath.replace(/.js$/, "") : "unknown", |
|
path: scriptPath || null, |
|
codeLine: parseInt(codeLine || 0), |
|
codeColumn: parseInt(codeColumn || 0) |
|
}; |
|
} |
|
toString() { |
|
return `[${dateFormat2(this.time, "yyyy-MM-dd HH:mm:ss.SSS")}][${this.level}][${this.source.name}<${this.source.codeLine},${this.source.codeColumn}>] ${this.text}`; |
|
} |
|
}; |
|
var Logger = class _Logger { |
|
|
|
config = {}; |
|
|
|
static Level = { |
|
Success: "success", |
|
Info: "info", |
|
Log: "log", |
|
Debug: "debug", |
|
Warning: "warning", |
|
Error: "error", |
|
Fatal: "fatal" |
|
}; |
|
|
|
static LevelColor = { |
|
[_Logger.Level.Success]: "green", |
|
[_Logger.Level.Info]: "brightCyan", |
|
[_Logger.Level.Debug]: "white", |
|
[_Logger.Level.Warning]: "brightYellow", |
|
[_Logger.Level.Error]: "brightRed", |
|
[_Logger.Level.Fatal]: "red" |
|
}; |
|
#writer; |
|
constructor() { |
|
this.#writer = new LogWriter(); |
|
} |
|
header() { |
|
this.#writer.writeSync(Buffer.from(` |
|
|
|
===================== LOG START ${dateFormat2(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss.SSS")} ===================== |
|
|
|
`)); |
|
} |
|
footer() { |
|
this.#writer.flush(); |
|
this.#writer.writeSync(Buffer.from(` |
|
|
|
===================== LOG END ${dateFormat2(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss.SSS")} ===================== |
|
|
|
`)); |
|
} |
|
success(...params) { |
|
const content = new LogText(_Logger.Level.Success, ...params).toString(); |
|
console.info(content[_Logger.LevelColor[_Logger.Level.Success]]); |
|
this.#writer.push(content + "\n"); |
|
} |
|
info(...params) { |
|
const content = new LogText(_Logger.Level.Info, ...params).toString(); |
|
console.info(content[_Logger.LevelColor[_Logger.Level.Info]]); |
|
this.#writer.push(content + "\n"); |
|
} |
|
log(...params) { |
|
const content = new LogText(_Logger.Level.Log, ...params).toString(); |
|
console.log(content[_Logger.LevelColor[_Logger.Level.Log]]); |
|
this.#writer.push(content + "\n"); |
|
} |
|
debug(...params) { |
|
if (!config_default.system.debug) return; |
|
const content = new LogText(_Logger.Level.Debug, ...params).toString(); |
|
console.debug(content[_Logger.LevelColor[_Logger.Level.Debug]]); |
|
this.#writer.push(content + "\n"); |
|
} |
|
warn(...params) { |
|
const content = new LogText(_Logger.Level.Warning, ...params).toString(); |
|
console.warn(content[_Logger.LevelColor[_Logger.Level.Warning]]); |
|
this.#writer.push(content + "\n"); |
|
} |
|
error(...params) { |
|
const content = new LogText(_Logger.Level.Error, ...params).toString(); |
|
console.error(content[_Logger.LevelColor[_Logger.Level.Error]]); |
|
this.#writer.push(content); |
|
} |
|
fatal(...params) { |
|
const content = new LogText(_Logger.Level.Fatal, ...params).toString(); |
|
console.error(content[_Logger.LevelColor[_Logger.Level.Fatal]]); |
|
this.#writer.push(content); |
|
} |
|
destory() { |
|
this.#writer.destory(); |
|
} |
|
}; |
|
var logger_default = new Logger(); |
|
|
|
|
|
process.setMaxListeners(Infinity); |
|
process.on("uncaughtException", (err, origin) => { |
|
logger_default.error(`An unhandled error occurred: ${origin}`, err); |
|
}); |
|
process.on("unhandledRejection", (_15, promise) => { |
|
promise.catch((err) => logger_default.error("An unhandled rejection occurred:", err)); |
|
}); |
|
process.on("warning", (warning) => logger_default.warn("System warning: ", warning)); |
|
process.on("exit", () => { |
|
logger_default.info("Service exit"); |
|
logger_default.footer(); |
|
}); |
|
process.on("SIGTERM", () => { |
|
logger_default.warn("received kill signal"); |
|
process.exit(2); |
|
}); |
|
process.on("SIGINT", () => { |
|
process.exit(0); |
|
}); |
|
|
|
|
|
import Koa from "koa"; |
|
import KoaRouter from "koa-router"; |
|
import koaRange from "koa-range"; |
|
import koaCors from "koa2-cors"; |
|
import koaBody from "koa-body"; |
|
import _11 from "lodash"; |
|
|
|
|
|
import _7 from "lodash"; |
|
|
|
|
|
import assert from "assert"; |
|
import _6 from "lodash"; |
|
var Exception = class extends Error { |
|
|
|
errcode; |
|
|
|
errmsg; |
|
|
|
data; |
|
|
|
httpStatusCode; |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(exception, _errmsg) { |
|
assert(_6.isArray(exception), "Exception must be Array"); |
|
const [errcode, errmsg] = exception; |
|
assert(_6.isFinite(errcode), "Exception errcode invalid"); |
|
assert(_6.isString(errmsg), "Exception errmsg invalid"); |
|
super(_errmsg || errmsg); |
|
this.errcode = errcode; |
|
this.errmsg = _errmsg || errmsg; |
|
} |
|
compare(exception) { |
|
const [errcode] = exception; |
|
return this.errcode == errcode; |
|
} |
|
setHTTPStatusCode(value) { |
|
this.httpStatusCode = value; |
|
return this; |
|
} |
|
setData(value) { |
|
this.data = _6.defaultTo(value, null); |
|
return this; |
|
} |
|
}; |
|
|
|
|
|
var APIException = class extends Exception { |
|
|
|
|
|
|
|
|
|
|
|
constructor(exception, errmsg) { |
|
super(exception, errmsg); |
|
} |
|
}; |
|
|
|
|
|
var exceptions_default = { |
|
API_TEST: [-9999, "API\u5F02\u5E38\u9519\u8BEF"], |
|
API_REQUEST_PARAMS_INVALID: [-2e3, "\u8BF7\u6C42\u53C2\u6570\u975E\u6CD5"], |
|
API_REQUEST_FAILED: [-2001, "\u8BF7\u6C42\u5931\u8D25"], |
|
API_TOKEN_EXPIRES: [-2002, "Token\u5DF2\u5931\u6548"], |
|
API_FILE_URL_INVALID: [-2003, "\u8FDC\u7A0B\u6587\u4EF6URL\u975E\u6CD5"], |
|
API_FILE_EXECEEDS_SIZE: [-2004, "\u8FDC\u7A0B\u6587\u4EF6\u8D85\u51FA\u5927\u5C0F"], |
|
API_CHAT_STREAM_PUSHING: [-2005, "\u5DF2\u6709\u5BF9\u8BDD\u6D41\u6B63\u5728\u8F93\u51FA"], |
|
API_CONTENT_FILTERED: [-2006, "\u5185\u5BB9\u7531\u4E8E\u5408\u89C4\u95EE\u9898\u5DF2\u88AB\u963B\u6B62\u751F\u6210"], |
|
API_IMAGE_GENERATION_FAILED: [-2007, "\u56FE\u50CF\u751F\u6210\u5931\u8D25"] |
|
}; |
|
|
|
|
|
var Request = class { |
|
|
|
method; |
|
|
|
url; |
|
|
|
path; |
|
|
|
type; |
|
|
|
headers; |
|
|
|
search; |
|
|
|
query; |
|
|
|
params; |
|
|
|
body; |
|
|
|
files; |
|
|
|
remoteIP; |
|
|
|
time; |
|
constructor(ctx, options = {}) { |
|
const { time } = options; |
|
this.method = ctx.request.method; |
|
this.url = ctx.request.url; |
|
this.path = ctx.request.path; |
|
this.type = ctx.request.type; |
|
this.headers = ctx.request.headers || {}; |
|
this.search = ctx.request.search; |
|
this.query = ctx.query || {}; |
|
this.params = ctx.params || {}; |
|
this.body = ctx.request.body || {}; |
|
this.files = ctx.request.files || {}; |
|
this.remoteIP = this.headers["X-Real-IP"] || this.headers["x-real-ip"] || this.headers["X-Forwarded-For"] || this.headers["x-forwarded-for"] || ctx.ip || null; |
|
this.time = Number(_7.defaultTo(time, util_default.timestamp())); |
|
} |
|
validate(key, fn) { |
|
try { |
|
const value = _7.get(this, key); |
|
if (fn) { |
|
if (fn(value) === false) |
|
throw `[Mismatch] -> ${fn}`; |
|
} else if (_7.isUndefined(value)) |
|
throw "[Undefined]"; |
|
} catch (err) { |
|
logger_default.warn(`Params ${key} invalid:`, err); |
|
throw new APIException(exceptions_default.API_REQUEST_PARAMS_INVALID, `Params ${key} invalid`); |
|
} |
|
return this; |
|
} |
|
}; |
|
|
|
|
|
import mime2 from "mime"; |
|
import _9 from "lodash"; |
|
|
|
|
|
import _8 from "lodash"; |
|
var Body = class _Body { |
|
|
|
code; |
|
|
|
message; |
|
|
|
data; |
|
|
|
statusCode; |
|
constructor(options = {}) { |
|
const { code, message, data, statusCode } = options; |
|
this.code = Number(_8.defaultTo(code, 0)); |
|
this.message = _8.defaultTo(message, "OK"); |
|
this.data = _8.defaultTo(data, null); |
|
this.statusCode = Number(_8.defaultTo(statusCode, 200)); |
|
} |
|
toObject() { |
|
return { |
|
code: this.code, |
|
message: this.message, |
|
data: this.data |
|
}; |
|
} |
|
static isInstance(value) { |
|
return value instanceof _Body; |
|
} |
|
}; |
|
|
|
|
|
var Response = class _Response { |
|
|
|
statusCode; |
|
|
|
type; |
|
|
|
headers; |
|
|
|
redirect; |
|
|
|
body; |
|
|
|
size; |
|
|
|
time; |
|
constructor(body, options = {}) { |
|
const { statusCode, type, headers, redirect, size, time } = options; |
|
this.statusCode = Number(_9.defaultTo(statusCode, Body.isInstance(body) ? body.statusCode : void 0)); |
|
this.type = type; |
|
this.headers = headers; |
|
this.redirect = redirect; |
|
this.size = size; |
|
this.time = Number(_9.defaultTo(time, util_default.timestamp())); |
|
this.body = body; |
|
} |
|
injectTo(ctx) { |
|
this.redirect && ctx.redirect(this.redirect); |
|
this.statusCode && (ctx.status = this.statusCode); |
|
this.type && (ctx.type = mime2.getType(this.type) || this.type); |
|
const headers = this.headers || {}; |
|
if (this.size && !headers["Content-Length"] && !headers["content-length"]) |
|
headers["Content-Length"] = this.size; |
|
ctx.set(headers); |
|
if (Body.isInstance(this.body)) |
|
ctx.body = this.body.toObject(); |
|
else |
|
ctx.body = this.body; |
|
} |
|
static isInstance(value) { |
|
return value instanceof _Response; |
|
} |
|
}; |
|
|
|
|
|
import _10 from "lodash"; |
|
|
|
|
|
var exceptions_default2 = { |
|
SYSTEM_ERROR: [-1e3, "\u7CFB\u7EDF\u5F02\u5E38"], |
|
SYSTEM_REQUEST_VALIDATION_ERROR: [-1001, "\u8BF7\u6C42\u53C2\u6570\u6821\u9A8C\u9519\u8BEF"], |
|
SYSTEM_NOT_ROUTE_MATCHING: [-1002, "\u65E0\u5339\u914D\u7684\u8DEF\u7531"] |
|
}; |
|
|
|
|
|
var FailureBody = class _FailureBody extends Body { |
|
constructor(error, _data) { |
|
let errcode, errmsg, data = _data, httpStatusCode = http_status_codes_default.OK; |
|
; |
|
if (_10.isString(error)) |
|
error = new Exception(exceptions_default2.SYSTEM_ERROR, error); |
|
else if (error instanceof APIException || error instanceof Exception) |
|
({ errcode, errmsg, data, httpStatusCode } = error); |
|
else if (_10.isError(error)) |
|
({ errcode, errmsg, data, httpStatusCode } = new Exception(exceptions_default2.SYSTEM_ERROR, error.message)); |
|
super({ |
|
code: errcode || -1, |
|
message: errmsg || "Internal error", |
|
data, |
|
statusCode: httpStatusCode |
|
}); |
|
} |
|
static isInstance(value) { |
|
return value instanceof _FailureBody; |
|
} |
|
}; |
|
|
|
|
|
var Server = class { |
|
app; |
|
router; |
|
constructor() { |
|
this.app = new Koa(); |
|
this.app.use(koaCors()); |
|
this.app.use(koaRange); |
|
this.router = new KoaRouter({ prefix: config_default.service.urlPrefix }); |
|
this.app.use(async (ctx, next) => { |
|
if (ctx.request.type === "application/xml" || ctx.request.type === "application/ssml+xml") |
|
ctx.req.headers["content-type"] = "text/xml"; |
|
try { |
|
await next(); |
|
} catch (err) { |
|
logger_default.error(err); |
|
const failureBody = new FailureBody(err); |
|
new Response(failureBody).injectTo(ctx); |
|
} |
|
}); |
|
this.app.use(koaBody(_11.clone(config_default.system.requestBody))); |
|
this.app.on("error", (err) => { |
|
if (["ECONNRESET", "ECONNABORTED", "EPIPE", "ECANCELED"].includes(err.code)) return; |
|
logger_default.error(err); |
|
}); |
|
logger_default.success("Server initialized"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
attachRoutes(routes) { |
|
routes.forEach((route) => { |
|
const prefix = route.prefix || ""; |
|
for (let method in route) { |
|
if (method === "prefix") continue; |
|
if (!_11.isObject(route[method])) { |
|
logger_default.warn(`Router ${prefix} ${method} invalid`); |
|
continue; |
|
} |
|
for (let uri in route[method]) { |
|
this.router[method](`${prefix}${uri}`, async (ctx) => { |
|
const { request, response } = await this.#requestProcessing(ctx, route[method][uri]); |
|
if (response != null && config_default.system.requestLog) |
|
logger_default.info(`<- ${request.method} ${request.url} ${response.time - request.time}ms`); |
|
}); |
|
} |
|
} |
|
logger_default.info(`Route ${config_default.service.urlPrefix || ""}${prefix} attached`); |
|
}); |
|
this.app.use(this.router.routes()); |
|
this.app.use((ctx) => { |
|
const request = new Request(ctx); |
|
logger_default.debug(`-> ${ctx.request.method} ${ctx.request.url} request is not supported - ${request.remoteIP || "unknown"}`); |
|
const message = `[\u8BF7\u6C42\u6709\u8BEF]: \u6B63\u786E\u8BF7\u6C42\u4E3A POST -> /v1/chat/completions\uFF0C\u5F53\u524D\u8BF7\u6C42\u4E3A ${ctx.request.method} -> ${ctx.request.url} \u8BF7\u7EA0\u6B63`; |
|
logger_default.warn(message); |
|
const failureBody = new FailureBody(new Error(message)); |
|
const response = new Response(failureBody); |
|
response.injectTo(ctx); |
|
if (config_default.system.requestLog) |
|
logger_default.info(`<- ${request.method} ${request.url} ${response.time - request.time}ms`); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
#requestProcessing(ctx, routeFn) { |
|
return new Promise((resolve) => { |
|
const request = new Request(ctx); |
|
try { |
|
if (config_default.system.requestLog) |
|
logger_default.info(`-> ${request.method} ${request.url}`); |
|
routeFn(request).then((response) => { |
|
try { |
|
if (!Response.isInstance(response)) { |
|
const _response = new Response(response); |
|
_response.injectTo(ctx); |
|
return resolve({ request, response: _response }); |
|
} |
|
response.injectTo(ctx); |
|
resolve({ request, response }); |
|
} catch (err) { |
|
logger_default.error(err); |
|
const failureBody = new FailureBody(err); |
|
const response2 = new Response(failureBody); |
|
response2.injectTo(ctx); |
|
resolve({ request, response: response2 }); |
|
} |
|
}).catch((err) => { |
|
try { |
|
logger_default.error(err); |
|
const failureBody = new FailureBody(err); |
|
const response = new Response(failureBody); |
|
response.injectTo(ctx); |
|
resolve({ request, response }); |
|
} catch (err2) { |
|
logger_default.error(err2); |
|
const failureBody = new FailureBody(err2); |
|
const response = new Response(failureBody); |
|
response.injectTo(ctx); |
|
resolve({ request, response }); |
|
} |
|
}); |
|
} catch (err) { |
|
logger_default.error(err); |
|
const failureBody = new FailureBody(err); |
|
const response = new Response(failureBody); |
|
response.injectTo(ctx); |
|
resolve({ request, response }); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
async listen() { |
|
const host = config_default.service.host; |
|
const port = config_default.service.port; |
|
await Promise.all([ |
|
new Promise((resolve, reject) => { |
|
if (host === "0.0.0.0" || host === "localhost" || host === "127.0.0.1") |
|
return resolve(null); |
|
this.app.listen(port, "localhost", (err) => { |
|
if (err) return reject(err); |
|
resolve(null); |
|
}); |
|
}), |
|
new Promise((resolve, reject) => { |
|
this.app.listen(port, host, (err) => { |
|
if (err) return reject(err); |
|
resolve(null); |
|
}); |
|
}) |
|
]); |
|
logger_default.success(`Server listening on port ${port} (${host})`); |
|
} |
|
}; |
|
var server_default = new Server(); |
|
|
|
|
|
import fs6 from "fs-extra"; |
|
|
|
|
|
import _13 from "lodash"; |
|
|
|
|
|
import { PassThrough } from "stream"; |
|
import _12 from "lodash"; |
|
import AsyncLock from "async-lock"; |
|
import axios2 from "axios"; |
|
import { createParser } from "eventsource-parser"; |
|
var MODEL_NAME = "deepseek-chat"; |
|
var ACCESS_TOKEN_EXPIRES = 3600; |
|
var MAX_RETRY_COUNT = 3; |
|
var RETRY_DELAY = 5e3; |
|
var FAKE_HEADERS = { |
|
Accept: "*/*", |
|
"Accept-Encoding": "gzip, deflate, br, zstd", |
|
"Accept-Language": "zh-CN,zh;q=0.9", |
|
Origin: "https://chat.deepseek.com", |
|
Pragma: "no-cache", |
|
Referer: "https://chat.deepseek.com/", |
|
"Sec-Ch-Ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', |
|
"Sec-Ch-Ua-Mobile": "?0", |
|
"Sec-Ch-Ua-Platform": '"Windows"', |
|
"Sec-Fetch-Dest": "empty", |
|
"Sec-Fetch-Mode": "cors", |
|
"Sec-Fetch-Site": "same-origin", |
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", |
|
"X-App-Version": "20240126.0" |
|
}; |
|
var accessTokenMap = new Map(); |
|
var accessTokenRequestQueueMap = {}; |
|
var chatLock = new AsyncLock(); |
|
async function requestToken(refreshToken) { |
|
if (accessTokenRequestQueueMap[refreshToken]) |
|
return new Promise( |
|
(resolve) => accessTokenRequestQueueMap[refreshToken].push(resolve) |
|
); |
|
accessTokenRequestQueueMap[refreshToken] = []; |
|
logger_default.info(`Refresh token: ${refreshToken}`); |
|
const result = await (async () => { |
|
const result2 = await axios2.get( |
|
"https://chat.deepseek.com/api/v0/users/current", |
|
{ |
|
headers: { |
|
Authorization: `Bearer ${refreshToken}`, |
|
...FAKE_HEADERS |
|
}, |
|
timeout: 15e3, |
|
validateStatus: () => true |
|
} |
|
); |
|
const { token } = checkResult(result2, refreshToken); |
|
return { |
|
accessToken: token, |
|
refreshToken: token, |
|
refreshTime: util_default.unixTimestamp() + ACCESS_TOKEN_EXPIRES |
|
}; |
|
})().then((result2) => { |
|
if (accessTokenRequestQueueMap[refreshToken]) { |
|
accessTokenRequestQueueMap[refreshToken].forEach( |
|
(resolve) => resolve(result2) |
|
); |
|
delete accessTokenRequestQueueMap[refreshToken]; |
|
} |
|
logger_default.success(`Refresh successful`); |
|
return result2; |
|
}).catch((err) => { |
|
if (accessTokenRequestQueueMap[refreshToken]) { |
|
accessTokenRequestQueueMap[refreshToken].forEach( |
|
(resolve) => resolve(err) |
|
); |
|
delete accessTokenRequestQueueMap[refreshToken]; |
|
} |
|
return err; |
|
}); |
|
if (_12.isError(result)) throw result; |
|
return result; |
|
} |
|
async function acquireToken(refreshToken) { |
|
let result = accessTokenMap.get(refreshToken); |
|
if (!result) { |
|
result = await requestToken(refreshToken); |
|
accessTokenMap.set(refreshToken, result); |
|
} |
|
if (util_default.unixTimestamp() > result.refreshTime) { |
|
result = await requestToken(refreshToken); |
|
accessTokenMap.set(refreshToken, result); |
|
} |
|
return result.accessToken; |
|
} |
|
async function clearContext(model, refreshToken) { |
|
const token = await acquireToken(refreshToken); |
|
const result = await axios2.post( |
|
"https://chat.deepseek.com/api/v0/chat/clear_context", |
|
{ |
|
model_class: model, |
|
append_welcome_message: false |
|
}, |
|
{ |
|
headers: { |
|
Authorization: `Bearer ${token}`, |
|
...FAKE_HEADERS |
|
}, |
|
timeout: 15e3, |
|
validateStatus: () => true |
|
} |
|
); |
|
checkResult(result, refreshToken); |
|
} |
|
async function createCompletion(model = MODEL_NAME, messages, refreshToken, retryCount = 0) { |
|
return (async () => { |
|
logger_default.info(messages); |
|
const result = await chatLock.acquire(refreshToken, async () => { |
|
await clearContext(model, refreshToken); |
|
const token = await acquireToken(refreshToken); |
|
return await axios2.post( |
|
"https://chat.deepseek.com/api/v0/chat/completions", |
|
{ |
|
message: messagesPrepare(messages), |
|
stream: true, |
|
model_preference: null, |
|
model_class: model, |
|
temperature: 0 |
|
}, |
|
{ |
|
headers: { |
|
Authorization: `Bearer ${token}`, |
|
...FAKE_HEADERS |
|
}, |
|
|
|
timeout: 12e4, |
|
validateStatus: () => true, |
|
responseType: "stream" |
|
} |
|
); |
|
}); |
|
if (result.headers["content-type"].indexOf("text/event-stream") == -1) { |
|
result.data.on("data", (buffer) => logger_default.error(buffer.toString())); |
|
throw new APIException( |
|
exceptions_default.API_REQUEST_FAILED, |
|
`Stream response Content-Type invalid: ${result.headers["content-type"]}` |
|
); |
|
} |
|
const streamStartTime = util_default.timestamp(); |
|
const answer = await receiveStream(model, result.data); |
|
logger_default.success( |
|
`Stream has completed transfer ${util_default.timestamp() - streamStartTime}ms` |
|
); |
|
return answer; |
|
})().catch((err) => { |
|
if (retryCount < MAX_RETRY_COUNT) { |
|
logger_default.error(`Stream response error: ${err.stack}`); |
|
logger_default.warn(`Try again after ${RETRY_DELAY / 1e3}s...`); |
|
return (async () => { |
|
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); |
|
return createCompletion( |
|
model, |
|
messages, |
|
refreshToken, |
|
retryCount + 1 |
|
); |
|
})(); |
|
} |
|
throw err; |
|
}); |
|
} |
|
async function createCompletionStream(model = MODEL_NAME, messages, refreshToken, retryCount = 0) { |
|
return (async () => { |
|
logger_default.info(messages); |
|
const result = await chatLock.acquire(refreshToken, async () => { |
|
await clearContext(model, refreshToken); |
|
const token = await acquireToken(refreshToken); |
|
return await axios2.post( |
|
"https://chat.deepseek.com/api/v0/chat/completions", |
|
{ |
|
message: messagesPrepare(messages), |
|
stream: true, |
|
model_preference: null, |
|
model_class: model, |
|
temperature: 0 |
|
}, |
|
{ |
|
headers: { |
|
Authorization: `Bearer ${token}`, |
|
...FAKE_HEADERS |
|
}, |
|
|
|
timeout: 12e4, |
|
validateStatus: () => true, |
|
responseType: "stream" |
|
} |
|
); |
|
}); |
|
if (result.headers["content-type"].indexOf("text/event-stream") == -1) { |
|
logger_default.error( |
|
`Invalid response Content-Type:`, |
|
result.headers["content-type"] |
|
); |
|
result.data.on("data", (buffer) => logger_default.error(buffer.toString())); |
|
const transStream = new PassThrough(); |
|
transStream.end( |
|
`data: ${JSON.stringify({ |
|
id: "", |
|
model: MODEL_NAME, |
|
object: "chat.completion.chunk", |
|
choices: [ |
|
{ |
|
index: 0, |
|
delta: { |
|
role: "assistant", |
|
content: "\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u7B2C\u4E09\u65B9\u54CD\u5E94\u9519\u8BEF" |
|
}, |
|
finish_reason: "stop" |
|
} |
|
], |
|
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, |
|
created: util_default.unixTimestamp() |
|
})} |
|
|
|
` |
|
); |
|
return transStream; |
|
} |
|
const streamStartTime = util_default.timestamp(); |
|
return createTransStream(model, result.data, () => { |
|
logger_default.success( |
|
`Stream has completed transfer ${util_default.timestamp() - streamStartTime}ms` |
|
); |
|
}); |
|
})().catch((err) => { |
|
if (retryCount < MAX_RETRY_COUNT) { |
|
logger_default.error(`Stream response error: ${err.stack}`); |
|
logger_default.warn(`Try again after ${RETRY_DELAY / 1e3}s...`); |
|
return (async () => { |
|
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); |
|
return createCompletionStream( |
|
model, |
|
messages, |
|
refreshToken, |
|
retryCount + 1 |
|
); |
|
})(); |
|
} |
|
throw err; |
|
}); |
|
} |
|
function messagesPrepare(messages) { |
|
let content; |
|
if (messages.length < 2) { |
|
content = messages.reduce((content2, message) => { |
|
if (_12.isArray(message.content)) { |
|
return message.content.reduce((_content, v) => { |
|
if (!_12.isObject(v) || v["type"] != "text") return _content; |
|
return _content + (v["text"] || "") + "\n"; |
|
}, content2); |
|
} |
|
return content2 + `${message.content} |
|
`; |
|
}, ""); |
|
logger_default.info("\n\u900F\u4F20\u5185\u5BB9\uFF1A\n" + content); |
|
} else { |
|
content = (messages.reduce((content2, message) => { |
|
if (_12.isArray(message.content)) { |
|
return message.content.reduce((_content, v) => { |
|
if (!_12.isObject(v) || v["type"] != "text") return _content; |
|
return _content + (`${message.role}:` + v["text"] || "") + "\n"; |
|
}, content2); |
|
} |
|
return content2 += `${message.role}:${message.content} |
|
`; |
|
}, "") + "assistant:").replace(/\!\[.+\]\(.+\)/g, ""); |
|
logger_default.info("\n\u5BF9\u8BDD\u5408\u5E76\uFF1A\n" + content); |
|
} |
|
return content; |
|
} |
|
function checkResult(result, refreshToken) { |
|
if (!result.data) return null; |
|
const { code, data, msg } = result.data; |
|
if (!_12.isFinite(code)) return result.data; |
|
if (code === 0) return data; |
|
if (code == 40003) accessTokenMap.delete(refreshToken); |
|
throw new APIException(exceptions_default.API_REQUEST_FAILED, `[\u8BF7\u6C42deepseek\u5931\u8D25]: ${msg}`); |
|
} |
|
async function receiveStream(model, stream) { |
|
return new Promise((resolve, reject) => { |
|
const data = { |
|
id: "", |
|
model, |
|
object: "chat.completion", |
|
choices: [ |
|
{ |
|
index: 0, |
|
message: { role: "assistant", content: "" }, |
|
finish_reason: "stop" |
|
} |
|
], |
|
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, |
|
created: util_default.unixTimestamp() |
|
}; |
|
const parser = createParser((event) => { |
|
try { |
|
if (event.type !== "event") return; |
|
const result = _12.attempt(() => JSON.parse(event.data)); |
|
if (_12.isError(result)) |
|
throw new Error(`Stream response invalid: ${event.data}`); |
|
if (!result.choices || !result.choices[0] || !result.choices[0].delta || !result.choices[0].delta.content || result.choices[0].delta.content == " ") |
|
return; |
|
data.choices[0].message.content += result.choices[0].delta.content; |
|
if (result.choices && result.choices[0] && result.choices[0].finish_reason === "stop") |
|
resolve(data); |
|
} catch (err) { |
|
logger_default.error(err); |
|
reject(err); |
|
} |
|
}); |
|
stream.on("data", (buffer) => parser.feed(buffer.toString())); |
|
stream.once("error", (err) => reject(err)); |
|
stream.once("close", () => resolve(data)); |
|
}); |
|
} |
|
function createTransStream(model, stream, endCallback) { |
|
const created = util_default.unixTimestamp(); |
|
const transStream = new PassThrough(); |
|
!transStream.closed && transStream.write( |
|
`data: ${JSON.stringify({ |
|
id: "", |
|
model, |
|
object: "chat.completion.chunk", |
|
choices: [ |
|
{ |
|
index: 0, |
|
delta: { role: "assistant", content: "" }, |
|
finish_reason: null |
|
} |
|
], |
|
created |
|
})} |
|
|
|
` |
|
); |
|
const parser = createParser((event) => { |
|
try { |
|
if (event.type !== "event") return; |
|
const result = _12.attempt(() => JSON.parse(event.data)); |
|
if (_12.isError(result)) |
|
throw new Error(`Stream response invalid: ${event.data}`); |
|
if (!result.choices || !result.choices[0] || !result.choices[0].delta || !result.choices[0].delta.content || result.choices[0].delta.content == " ") |
|
return; |
|
result.model = model; |
|
transStream.write(`data: ${JSON.stringify({ |
|
id: result.id, |
|
model: result.model, |
|
object: "chat.completion.chunk", |
|
choices: [ |
|
{ |
|
index: 0, |
|
delta: { role: "assistant", content: result.choices[0].delta.content }, |
|
finish_reason: null |
|
} |
|
], |
|
created |
|
})} |
|
|
|
`); |
|
if (result.choices && result.choices[0] && result.choices[0].finish_reason === "stop") { |
|
transStream.write(`data: ${JSON.stringify({ |
|
id: result.id, |
|
model: result.model, |
|
object: "chat.completion.chunk", |
|
choices: [ |
|
{ |
|
index: 0, |
|
delta: { role: "assistant", content: "" }, |
|
finish_reason: "stop" |
|
} |
|
], |
|
created |
|
})} |
|
|
|
`); |
|
!transStream.closed && transStream.end("data: [DONE]\n\n"); |
|
} |
|
} catch (err) { |
|
logger_default.error(err); |
|
!transStream.closed && transStream.end("data: [DONE]\n\n"); |
|
} |
|
}); |
|
stream.on("data", (buffer) => parser.feed(buffer.toString())); |
|
stream.once( |
|
"error", |
|
() => !transStream.closed && transStream.end("data: [DONE]\n\n") |
|
); |
|
stream.once( |
|
"close", |
|
() => !transStream.closed && transStream.end("data: [DONE]\n\n") |
|
); |
|
return transStream; |
|
} |
|
function tokenSplit(authorization) { |
|
return authorization.replace("Bearer ", "").split(","); |
|
} |
|
async function getTokenLiveStatus(refreshToken) { |
|
const token = await acquireToken(refreshToken); |
|
const result = await axios2.get( |
|
"https://chat.deepseek.com/api/v0/users/current", |
|
{ |
|
headers: { |
|
Authorization: `Bearer ${token}`, |
|
...FAKE_HEADERS |
|
}, |
|
timeout: 15e3, |
|
validateStatus: () => true |
|
} |
|
); |
|
try { |
|
const { token: token2 } = checkResult(result, refreshToken); |
|
return !!token2; |
|
} catch (err) { |
|
return false; |
|
} |
|
} |
|
var chat_default = { |
|
createCompletion, |
|
createCompletionStream, |
|
getTokenLiveStatus, |
|
tokenSplit |
|
}; |
|
|
|
|
|
var chat_default2 = { |
|
prefix: "/v1/chat", |
|
post: { |
|
"/completions": async (request) => { |
|
request.validate("body.conversation_id", (v) => _13.isUndefined(v) || _13.isString(v)).validate("body.messages", _13.isArray).validate("headers.authorization", _13.isString); |
|
const tokens = chat_default.tokenSplit(request.headers.authorization); |
|
const token = _13.sample(tokens); |
|
let { model, messages, stream } = request.body; |
|
if (["deepseek_chat", "deepseek_code", "deepseek-chat*", "deepseek-chat", "deepseek-coder"].includes(model)) |
|
model = { |
|
"deepseek-chat*": "deepseek_chat", |
|
"deepseek-chat": "deepseek_chat", |
|
"deepseek-coder": "deepseek_code" |
|
}[model] || model; |
|
else |
|
model = "deepseek_chat"; |
|
if (stream) { |
|
const stream2 = await chat_default.createCompletionStream(model, messages, token); |
|
return new Response(stream2, { |
|
type: "text/event-stream" |
|
}); |
|
} else |
|
return await chat_default.createCompletion(model, messages, token); |
|
} |
|
} |
|
}; |
|
|
|
|
|
var ping_default = { |
|
prefix: "/ping", |
|
get: { |
|
"": async () => "pong" |
|
} |
|
}; |
|
|
|
|
|
import _14 from "lodash"; |
|
var token_default = { |
|
prefix: "/token", |
|
post: { |
|
"/check": async (request) => { |
|
request.validate("body.token", _14.isString); |
|
const live = await chat_default.getTokenLiveStatus(request.body.token); |
|
return { |
|
live |
|
}; |
|
} |
|
} |
|
}; |
|
|
|
|
|
var models_default = { |
|
prefix: "/v1", |
|
get: { |
|
"/models": async () => { |
|
return { |
|
"data": [ |
|
{ |
|
"id": "deepseek-chat", |
|
"object": "model", |
|
"owned_by": "deepseek-free-api" |
|
}, |
|
{ |
|
"id": "deepseek-coder", |
|
"object": "model", |
|
"owned_by": "deepseek-free-api" |
|
} |
|
] |
|
}; |
|
} |
|
} |
|
}; |
|
|
|
|
|
var routes_default = [ |
|
{ |
|
get: { |
|
"/": async () => { |
|
const content = await fs6.readFile("public/welcome.html"); |
|
return new Response(content, { |
|
type: "html", |
|
headers: { |
|
Expires: "-1" |
|
} |
|
}); |
|
} |
|
} |
|
}, |
|
chat_default2, |
|
ping_default, |
|
token_default, |
|
models_default |
|
]; |
|
|
|
|
|
var startupTime = performance.now(); |
|
(async () => { |
|
logger_default.header(); |
|
logger_default.info("<<<< deepseek free server >>>>"); |
|
logger_default.info("Version:", environment_default.package.version); |
|
logger_default.info("Process id:", process.pid); |
|
logger_default.info("Environment:", environment_default.env); |
|
logger_default.info("Service name:", config_default.service.name); |
|
server_default.attachRoutes(routes_default); |
|
await server_default.listen(); |
|
config_default.service.bindAddress && logger_default.success("Service bind address:", config_default.service.bindAddress); |
|
})().then( |
|
() => logger_default.success( |
|
`Service startup completed (${Math.floor(performance.now() - startupTime)}ms)` |
|
) |
|
).catch((err) => console.error(err)); |
|
|