diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 50816832cd..4d7076e6a1 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -68,7 +68,7 @@ jobs: uses: actions/cache@v4 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/server-rollup.config.js', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/.github/workflows/config-review.yml b/.github/workflows/config-review.yml index f0cdb2b09f..3d2106195d 100644 --- a/.github/workflows/config-review.yml +++ b/.github/workflows/config-review.yml @@ -70,7 +70,7 @@ jobs: uses: actions/cache@v4 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/server-rollup.config.js', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/.github/workflows/playwright-mock.yml b/.github/workflows/playwright-mock.yml index 9d5d2ae50e..a870dbe757 100644 --- a/.github/workflows/playwright-mock.yml +++ b/.github/workflows/playwright-mock.yml @@ -79,7 +79,7 @@ jobs: uses: actions/cache@v4 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/server-rollup.config.js', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/package-lock.json b/package-lock.json index 8713f81e1c..e726ada4c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44259,6 +44259,7 @@ "rollup-plugin-peer-deps-external": "^2.2.4", "sanitize-html": "^2.13.0", "ts-node": "^10.9.2", + "tsdown": "^0.22.2", "typescript": "^5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yauzl": "^3.2.1" @@ -44322,6 +44323,74 @@ "zod": "^3.22.4" } }, + "packages/api/node_modules/@babel/generator": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.6.tgz", + "integrity": "sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0-rc.6", + "@babel/types": "^8.0.0-rc.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "packages/api/node_modules/@babel/helper-string-parser": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.6.tgz", + "integrity": "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "packages/api/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.6.tgz", + "integrity": "sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "packages/api/node_modules/@babel/parser": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.6.tgz", + "integrity": "sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0-rc.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "packages/api/node_modules/@babel/types": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.6.tgz", + "integrity": "sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0-rc.6", + "@babel/helper-validator-identifier": "^8.0.0-rc.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, "packages/api/node_modules/@keyv/redis": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.6.0.tgz", @@ -44340,6 +44409,339 @@ "keyv": "^5.3.4" } }, + "packages/api/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "packages/api/node_modules/@oxc-project/types": { + "version": "0.134.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.134.0.tgz", + "integrity": "sha512-T0xuRRKrQFmocH8y+jGfpmSkGcheaJExY9lEihmR1Gm2aH+75B8CzgU2rABRQSzzDxLjZ15Sc0bRVLj5lVeNXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "packages/api/node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.0.tgz", + "integrity": "sha512-gCYzGOSkYY6Z034suzd20euvds7lPzMEEla62DJGE/ZAlR4OMBnNbvnBSsIGUCAr52gaWMsloGxP4tVGtN5aCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-JQBD77MNgu+4Z6RAyg69acugdrhhVoWesr3l47zohYZ2YV2fwkWMArkN/2p4l6Ei+Sno7W5q+UsKdVWq5Ens0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.0.tgz", + "integrity": "sha512-p/8cXUTK4Sob604e+xxPhVSbDFf29E6J0l/xESM9rdCfn3aDai3nEs6TnMHUsdD5aNlFz0+gDbiGlozLKGa2YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.0.tgz", + "integrity": "sha512-KbtOSlVv6fElujiZWMcC3aQYhEwLVVf073RcwlSmpGQvIsKZFUqc0ef4sjUuurRwfbiI6JJXji9DQn+86hawmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.0.tgz", + "integrity": "sha512-9fZ9i0o0/MQaw7om6Z6TsT7tfCk0jtbEFtC+aPqZL5RNsGWNcHvn6EHgL3dAprjq+AZzPTAQjg2JtpJaMt+6pg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.0.tgz", + "integrity": "sha512-+tog7T66i+yFyIuuAnjL6xmW182W/qTBOUt6BtQ6lBIM1Eikh/fSMz4HGgvuCp5uU0zuIVWng7kDYthjCMOHcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.0.tgz", + "integrity": "sha512-4b7yruLIIj/oZ3GpcLOvxcLCLDMraohn3IhQfN2hBP4w9UekG0DTIajWguJosRGfySf/+h/NwRUiMKoCpxCrqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.0.tgz", + "integrity": "sha512-QRDOVZd0bhQ5jLsUsCC3dUxDWdTSVY9WMznowZgCGOrZfLLgctWpelhUASEiBwsXfat/JwYnVd1EaxMhqyT+UQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.0.tgz", + "integrity": "sha512-ypxT+Hq76NFG7woFbNbySnGEajFuYuIXeKz/jfCU+lXUoxfi3zLE6OG/ZQNeK3RpZSYJlAe2bokpsQ046CaieQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.0.tgz", + "integrity": "sha512-IdovCmfROFmpTLahdecTDFL74aLERVYN68F/mLZjfVh6LfoplPfI6deyHNMTcVujbokDV5k05XrFO22zfv+qjg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.0.tgz", + "integrity": "sha512-pcA8xlFp2tyk9T2R6Fi/rPe3bQ1MA+sSMDNUU5Ogu80GHOatkE4P8YCreGAvZErm5Ho2YRXnyvNrWiRncfVysQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.0.tgz", + "integrity": "sha512-4+fexHayrLCWpriPh4c6dNvL4an34DEZCG7zOM/FD5QNF6h8DT+bDXzyB/kfC8lDJbaFb7jKShtnjDQFXVQEjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.0.tgz", + "integrity": "sha512-SbL++MNmOw6QamrwIGDMSSfM4ceTzFr+RjbOExJSLLBinScU4WI5OdA413h1qwPw2yH7lVF1+H4svQ+6mSXKTQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.0.tgz", + "integrity": "sha512-+xTE6XC7wBgk0VKRXGG+QAnyW5S9b8vfsFpiMjf0waQTmSQSU8onsH/beyZ8X4aXVveJnotiy7VDjLOaW8bTrg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.0.tgz", + "integrity": "sha512-Ogji1TQNqH3ACLnYr+1Ns1nyrJ0CO2P585u9Hsh02pXvtFiFpgtgT2b3P4PnCOU86VVCvqtAeCN4OftMT8KU4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/api/node_modules/get-tsconfig": { + "version": "5.0.0-beta.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", + "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "packages/api/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "packages/api/node_modules/rimraf": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", @@ -44360,6 +44762,157 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/api/node_modules/rolldown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.0.tgz", + "integrity": "sha512-zpMvlJhs5PkXRTtKc0CaLBVI9AR/VDiJFpM+kx//hgToEca7FgMlGjaRIisXBcb19T76LswgmKECSQ96hjWr5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.134.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.0", + "@rolldown/binding-darwin-arm64": "1.1.0", + "@rolldown/binding-darwin-x64": "1.1.0", + "@rolldown/binding-freebsd-x64": "1.1.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.0", + "@rolldown/binding-linux-arm64-gnu": "1.1.0", + "@rolldown/binding-linux-arm64-musl": "1.1.0", + "@rolldown/binding-linux-ppc64-gnu": "1.1.0", + "@rolldown/binding-linux-s390x-gnu": "1.1.0", + "@rolldown/binding-linux-x64-gnu": "1.1.0", + "@rolldown/binding-linux-x64-musl": "1.1.0", + "@rolldown/binding-openharmony-arm64": "1.1.0", + "@rolldown/binding-wasm32-wasi": "1.1.0", + "@rolldown/binding-win32-arm64-msvc": "1.1.0", + "@rolldown/binding-win32-x64-msvc": "1.1.0" + } + }, + "packages/api/node_modules/tsdown": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.22.2.tgz", + "integrity": "sha512-VX9gsyKXsTnBZjnIM4jsHl9aRv+GfgkE/k1hQslilaBfZMlaw3JuGR+6yhiU0QxWBtOCDnTjwOSoXzgB7Rr50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.1", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.1", + "hookable": "^6.1.1", + "import-without-cache": "^0.4.0", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "rolldown": "~1.1.0", + "rolldown-plugin-dts": "^0.25.2", + "semver": "^7.8.1", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.17", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0" + }, + "bin": { + "tsdown": "dist/run.mjs" + }, + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.22.2", + "@tsdown/exe": "0.22.2", + "@vitejs/devtools": "*", + "publint": "^0.3.8", + "tsx": "*", + "typescript": "^5.0.0 || ^6.0.0", + "unplugin-unused": "^0.5.0", + "unrun": "*" + }, + "peerDependenciesMeta": { + "@arethetypeswrong/core": { + "optional": true + }, + "@tsdown/css": { + "optional": true + }, + "@tsdown/exe": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "publint": { + "optional": true + }, + "tsx": { + "optional": true + }, + "typescript": { + "optional": true + }, + "unplugin-unused": { + "optional": true + }, + "unrun": { + "optional": true + } + } + }, + "packages/api/node_modules/tsdown/node_modules/rolldown-plugin-dts": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.25.2.tgz", + "integrity": "sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "8.0.0-rc.6", + "@babel/helper-validator-identifier": "8.0.0-rc.6", + "@babel/parser": "8.0.0-rc.6", + "ast-kit": "^3.0.0-beta.1", + "birpc": "^4.0.0", + "dts-resolver": "^3.0.0", + "get-tsconfig": "5.0.0-beta.5", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@ts-macro/tsc": "^0.3.6", + "@typescript/native-preview": ">=7.0.0-dev.20260325.1", + "rolldown": "^1.0.0", + "typescript": "^5.0.0 || ^6.0.0", + "vue-tsc": "~3.2.0" + }, + "peerDependenciesMeta": { + "@ts-macro/tsc": { + "optional": true + }, + "@typescript/native-preview": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, "packages/client": { "name": "@librechat/client", "version": "0.4.60", diff --git a/packages/api/package.json b/packages/api/package.json index 85d802aeeb..8bb43c0c9f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -3,25 +3,28 @@ "version": "1.7.31", "type": "commonjs", "description": "MCP services for LibreChat", - "main": "dist/index.js", - "module": "dist/index.es.js", - "types": "./dist/types/index.d.ts", + "main": "dist/index.cjs", + "types": "./dist/index.d.cts", "exports": { ".": { - "require": "./dist/index.js", - "types": "./dist/types/index.d.ts" + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } }, "./telemetry": { - "require": "./dist/telemetry.js", - "types": "./dist/types/telemetry/index.d.ts" + "require": { + "types": "./dist/telemetry.d.cts", + "default": "./dist/telemetry.cjs" + } } }, "scripts": { "clean": "rimraf dist", - "build": "npm run clean && rollup -c --bundleConfigAsCjs", - "build:dev": "npm run clean && NODE_ENV=development rollup -c --bundleConfigAsCjs", - "build:watch": "NODE_ENV=development rollup -c -w --bundleConfigAsCjs", - "build:watch:prod": "rollup -c -w --bundleConfigAsCjs", + "build": "npm run clean && tsdown", + "build:dev": "npm run clean && tsdown", + "build:watch": "tsdown --watch", + "build:watch:prod": "tsdown --watch", "test": "jest --coverage --watch --testPathIgnorePatterns=\"\\.*integration\\.|\\.*helper\\.|__tests__/helpers/|\\.*manual\\.spec\\.\"", "test:ci": "jest --coverage --ci --testPathIgnorePatterns=\"\\.*integration\\.|\\.*helper\\.|__tests__/helpers/|\\.*manual\\.spec\\.\"", "test:cache-integration:core": "jest --testPathPatterns=\"src/cache/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false", @@ -32,8 +35,8 @@ "test:s3-integration": "jest --testPathPatterns=\"src/storage/s3/.*\\.integration\\.spec\\.ts$\" --coverage=false --runInBand", "verify": "npm run test:ci", "b:clean": "bun run rimraf dist", - "b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs", - "b:build:dev": "bun run b:clean && NODE_ENV=development bun run rollup -c --silent --bundleConfigAsCjs", + "b:build": "bun run b:clean && bun run tsdown", + "b:build:dev": "bun run b:clean && bun run tsdown", "start:everything-sse": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/examples/everything/sse.ts", "start:everything": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/demo/everything.ts", "start:filesystem": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/demo/filesystem.ts", @@ -84,6 +87,7 @@ "rollup-plugin-peer-deps-external": "^2.2.4", "sanitize-html": "^2.13.0", "ts-node": "^10.9.2", + "tsdown": "^0.22.2", "typescript": "^5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yauzl": "^3.2.1" diff --git a/packages/api/rollup.config.js b/packages/api/rollup.config.js deleted file mode 100644 index becc482291..0000000000 --- a/packages/api/rollup.config.js +++ /dev/null @@ -1,68 +0,0 @@ -// rollup.config.js -import { readFileSync } from 'fs'; -import json from '@rollup/plugin-json'; -import replace from '@rollup/plugin-replace'; -import commonjs from '@rollup/plugin-commonjs'; -import resolve from '@rollup/plugin-node-resolve'; -import typescript from '@rollup/plugin-typescript'; -import peerDepsExternal from 'rollup-plugin-peer-deps-external'; - -const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')); - -/** - * Check if we're in development mode - */ -const isDevelopment = process.env.NODE_ENV === 'development'; - -const plugins = [ - peerDepsExternal(), - resolve({ - preferBuiltins: true, - skipSelf: true, - }), - replace({ - __IS_DEV__: isDevelopment, - preventAssignment: true, - }), - commonjs({ - transformMixedEsModules: true, - requireReturnsDefault: 'auto', - }), - typescript({ - tsconfig: './tsconfig.build.json', - outDir: './dist', - sourceMap: true, - /** - * Remove inline sourcemaps - they conflict with external sourcemaps - */ - inlineSourceMap: false, - /** - * Always include source content in sourcemaps for better debugging - */ - inlineSources: true, - }), - json(), -]; - -const cjsBuild = { - input: { - index: 'src/index.ts', - telemetry: 'src/telemetry/index.ts', - }, - output: { - dir: 'dist', - format: 'cjs', - sourcemap: true, - exports: 'named', - entryFileNames: '[name].js', - /** - * Always include sources in sourcemap for better debugging - */ - sourcemapExcludeSources: false, - }, - external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})], - preserveSymlinks: true, - plugins, -}; - -export default cjsBuild; diff --git a/packages/api/src/admin/config.ts b/packages/api/src/admin/config.ts index 5d7c739166..d8e1094db0 100644 --- a/packages/api/src/admin/config.ts +++ b/packages/api/src/admin/config.ts @@ -5,8 +5,8 @@ import { INTERFACE_PERMISSION_FIELDS, PERMISSION_SUB_KEYS, } from 'librechat-data-provider'; -import type { TCustomConfig } from 'librechat-data-provider'; import type { AppConfig, ConfigSection, IConfig } from '@librechat/data-schemas'; +import type { TCustomConfig } from 'librechat-data-provider'; import type { Types, ClientSession } from 'mongoose'; import type { Response } from 'express'; import type { CapabilityUser } from '~/middleware/capabilities'; @@ -165,7 +165,17 @@ function getCapabilityUser(req: ServerRequest): CapabilityUser | null { // ── Handler factory ────────────────────────────────────────────────── -export function createAdminConfigHandlers(deps: AdminConfigDeps) { +export function createAdminConfigHandlers(deps: AdminConfigDeps): { + listConfigs: (req: ServerRequest, res: Response) => Promise; + getBaseConfig: (req: ServerRequest, res: Response) => Promise; + getConfig: (req: ServerRequest, res: Response) => Promise; + upsertConfigOverrides: (req: ServerRequest, res: Response) => Promise; + patchConfigField: (req: ServerRequest, res: Response) => Promise; + tombstoneConfigField: (req: ServerRequest, res: Response) => Promise; + deleteConfigField: (req: ServerRequest, res: Response) => Promise; + deleteConfigOverrides: (req: ServerRequest, res: Response) => Promise; + toggleConfig: (req: ServerRequest, res: Response) => Promise; +} { const { listAllConfigs, findConfigByPrincipal, @@ -183,7 +193,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * GET / — List all active config overrides. */ - async function listConfigs(req: ServerRequest, res: Response) { + async function listConfigs(req: ServerRequest, res: Response): Promise { try { const user = getCapabilityUser(req); if (!user) { @@ -206,7 +216,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { * GET /base — Return the raw AppConfig (YAML + DB base merged). * This is the full config structure admins can edit, NOT the startup payload. */ - async function getBaseConfig(req: ServerRequest, res: Response) { + async function getBaseConfig(req: ServerRequest, res: Response): Promise { try { const user = getCapabilityUser(req); if (!user) { @@ -236,7 +246,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * GET /:principalType/:principalId — Get config for a specific principal. */ - async function getConfig(req: ServerRequest, res: Response) { + async function getConfig(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -273,7 +283,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * PUT /:principalType/:principalId — Replace entire overrides for a principal. */ - async function upsertConfigOverrides(req: ServerRequest, res: Response) { + async function upsertConfigOverrides(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -382,7 +392,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * PATCH /:principalType/:principalId/fields — Set individual fields via dot-paths. */ - async function patchConfigField(req: ServerRequest, res: Response) { + async function patchConfigField(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -491,7 +501,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * POST /:principalType/:principalId/fields/tombstone — Suppress an inherited config path. */ - async function tombstoneConfigField(req: ServerRequest, res: Response) { + async function tombstoneConfigField(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -565,7 +575,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * DELETE /:principalType/:principalId/fields?fieldPath=dotted.path */ - async function deleteConfigField(req: ServerRequest, res: Response) { + async function deleteConfigField(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -623,7 +633,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * DELETE /:principalType/:principalId — Delete an entire config override. */ - async function deleteConfigOverrides(req: ServerRequest, res: Response) { + async function deleteConfigOverrides(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; @@ -661,7 +671,7 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps) { /** * PATCH /:principalType/:principalId/active — Toggle isActive. */ - async function toggleConfig(req: ServerRequest, res: Response) { + async function toggleConfig(req: ServerRequest, res: Response): Promise { try { const { principalType, principalId } = req.params as { principalType: string; diff --git a/packages/api/src/admin/grants.ts b/packages/api/src/admin/grants.ts index b7205d6c7c..f5d42ce071 100644 --- a/packages/api/src/admin/grants.ts +++ b/packages/api/src/admin/grants.ts @@ -78,7 +78,13 @@ export interface AdminGrantsDeps { export type GrantPrincipalType = PrincipalType.ROLE; /** Creates admin grant handlers with dependency injection for the /api/admin/grants routes. */ -export function createAdminGrantsHandlers(deps: AdminGrantsDeps) { +export function createAdminGrantsHandlers(deps: AdminGrantsDeps): { + listGrants: (req: ServerRequest, res: Response) => Promise; + getEffectiveCapabilities: (req: ServerRequest, res: Response) => Promise; + getPrincipalGrants: (req: ServerRequest, res: Response) => Promise; + assignGrant: (req: ServerRequest, res: Response) => Promise; + revokeGrant: (req: ServerRequest, res: Response) => Promise; +} { const { listGrants, countGrants, diff --git a/packages/api/src/admin/groups.ts b/packages/api/src/admin/groups.ts index 41dfba6cac..d37ca3d294 100644 --- a/packages/api/src/admin/groups.ts +++ b/packages/api/src/admin/groups.ts @@ -84,7 +84,16 @@ export interface AdminGroupsDeps { }) => Promise; } -export function createAdminGroupsHandlers(deps: AdminGroupsDeps) { +export function createAdminGroupsHandlers(deps: AdminGroupsDeps): { + listGroups: (req: ServerRequest, res: Response) => Promise; + getGroup: (req: ServerRequest, res: Response) => Promise; + createGroup: (req: ServerRequest, res: Response) => Promise; + updateGroup: (req: ServerRequest, res: Response) => Promise; + deleteGroup: (req: ServerRequest, res: Response) => Promise; + getGroupMembers: (req: ServerRequest, res: Response) => Promise; + addGroupMember: (req: ServerRequest, res: Response) => Promise; + removeGroupMember: (req: ServerRequest, res: Response) => Promise; +} { const { listGroups, countGroups, diff --git a/packages/api/src/admin/roles.ts b/packages/api/src/admin/roles.ts index db592c02d0..910e962a5c 100644 --- a/packages/api/src/admin/roles.ts +++ b/packages/api/src/admin/roles.ts @@ -123,7 +123,17 @@ export interface AdminRolesDeps { ) => Promise; } -export function createAdminRolesHandlers(deps: AdminRolesDeps) { +export function createAdminRolesHandlers(deps: AdminRolesDeps): { + listRoles: (req: ServerRequest, res: Response) => Promise; + getRole: (req: ServerRequest, res: Response) => Promise; + createRole: (req: ServerRequest, res: Response) => Promise; + updateRole: (req: ServerRequest, res: Response) => Promise; + updateRolePermissions: (req: ServerRequest, res: Response) => Promise; + deleteRole: (req: ServerRequest, res: Response) => Promise; + getRoleMembers: (req: ServerRequest, res: Response) => Promise; + addRoleMember: (req: ServerRequest, res: Response) => Promise; + removeRoleMember: (req: ServerRequest, res: Response) => Promise; +} { const { listRoles, countRoles, diff --git a/packages/api/src/admin/users.ts b/packages/api/src/admin/users.ts index 6fc13fd97f..d24eb1b759 100644 --- a/packages/api/src/admin/users.ts +++ b/packages/api/src/admin/users.ts @@ -42,7 +42,11 @@ export interface AdminUsersDeps { }) => Promise; } -export function createAdminUsersHandlers(deps: AdminUsersDeps) { +export function createAdminUsersHandlers(deps: AdminUsersDeps): { + listUsers: (req: ServerRequest, res: Response) => Promise; + searchUsers: (req: ServerRequest, res: Response) => Promise; + deleteUser: (req: ServerRequest, res: Response) => Promise; +} { const { findUsers, countUsers, deleteUserById, deleteConfig, deleteAclEntries } = deps; async function listUsersHandler(req: ServerRequest, res: Response) { diff --git a/packages/api/src/agents/chain.ts b/packages/api/src/agents/chain.ts index e00d537325..62eb697d77 100644 --- a/packages/api/src/agents/chain.ts +++ b/packages/api/src/agents/chain.ts @@ -14,7 +14,7 @@ const DEFAULT_PROMPT_TEMPLATE = `Based on the following conversation and analysi */ export async function createSequentialChainEdges( agentIds: string[], - promptTemplate = DEFAULT_PROMPT_TEMPLATE, + promptTemplate: string = DEFAULT_PROMPT_TEMPLATE, ): Promise { const edges: GraphEdge[] = []; diff --git a/packages/api/src/agents/client.ts b/packages/api/src/agents/client.ts index 5b6513499c..f758df9745 100644 --- a/packages/api/src/agents/client.ts +++ b/packages/api/src/agents/client.ts @@ -14,7 +14,7 @@ import type { ServerRequest } from '~/types'; import Tokenizer from '~/utils/tokenizer'; import { logAxiosError } from '~/utils'; -export const omitTitleOptions = new Set([ +export const omitTitleOptions: Set = new Set([ 'stream', 'thinking', 'streaming', @@ -26,7 +26,13 @@ export const omitTitleOptions = new Set([ 'additionalModelRequestFields', ]); -export function payloadParser({ req, endpoint }: { req: ServerRequest; endpoint: string }) { +export function payloadParser({ + req, + endpoint, +}: { + req: ServerRequest; + endpoint: string; +}): Record | undefined { if (isAgentsEndpoint(endpoint)) { return; } @@ -303,7 +309,7 @@ export function countFormattedMessageTokens( export function createTokenCounter(encoding: Parameters[1]) { const isClaude = encoding === 'claude'; const countTokens = (text: string) => Tokenizer.getTokenCount(text, encoding); - return function (message: BaseMessage) { + return function (message: BaseMessage): number { const count = getTokenCountForMessage( message, countTokens, @@ -313,7 +319,7 @@ export function createTokenCounter(encoding: Parameters; /** Avatar schema shared between create and update */ -export const agentAvatarSchema = z.object({ +export const agentAvatarSchema: z.ZodObject< + { + filepath: z.ZodString; + source: z.ZodString; + }, + 'strip' +> = z.object({ filepath: z.string(), source: z.string(), }); /** Base resource schema for tool resources */ -export const agentBaseResourceSchema = z.object({ +export const agentBaseResourceSchema: z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip' +> = z.object({ file_ids: z.array(z.string()).optional(), - files: z.array(z.any()).optional(), // Files are populated at runtime, not from user input + files: z.array(z.unknown()).optional(), // Files are populated at runtime, not from user input }); /** File resource schema extends base with vector_store_ids */ -export const agentFileResourceSchema = agentBaseResourceSchema.extend({ +export const agentFileResourceSchema: z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + } & { + vector_store_ids: z.ZodOptional>; + }, + 'strip' +> = agentBaseResourceSchema.extend({ vector_store_ids: z.array(z.string()).optional(), }); /** Tool resources schema matching AgentToolResources interface */ -export const agentToolResourcesSchema = z +export const agentToolResourcesSchema: z.ZodOptional< + z.ZodObject< + { + image_edit: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + execute_code: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + file_search: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + } & { + vector_store_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + > + >; + context: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + /** @deprecated Use context instead */ + ocr: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + }, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + } + > +> = z .object({ image_edit: agentBaseResourceSchema.optional(), execute_code: agentBaseResourceSchema.optional(), @@ -40,7 +228,24 @@ export const agentToolResourcesSchema = z .optional(); /** Support contact schema for agent */ -export const agentSupportContactSchema = z +export const agentSupportContactSchema: z.ZodOptional< + z.ZodObject< + { + name: z.ZodOptional; + email: z.ZodOptional, z.ZodString]>>; + }, + 'strip', + z.ZodTypeAny, + { + name?: string | undefined; + email?: string | undefined; + }, + { + name?: string | undefined; + email?: string | undefined; + } + > +> = z .object({ name: z.string().optional(), email: z.union([z.literal(''), z.string().email()]).optional(), @@ -48,7 +253,24 @@ export const agentSupportContactSchema = z .optional(); /** Graph edge schema for agent handoffs */ -export const graphEdgeSchema = z.object({ +export const graphEdgeSchema: z.ZodObject< + { + from: z.ZodUnion<[z.ZodString, z.ZodArray]>; + to: z.ZodUnion<[z.ZodString, z.ZodArray]>; + description: z.ZodEffects, string | undefined, string | undefined>; + edgeType: z.ZodOptional>; + prompt: z.ZodEffects< + z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodFunction, z.ZodUnknown>]> + >, + string | ((...args: unknown[]) => unknown) | undefined, + string | ((...args: unknown[]) => unknown) | undefined + >; + excludeResults: z.ZodOptional; + promptKey: z.ZodEffects, string | undefined, string | undefined>; + }, + 'strip' +> = z.object({ from: z.union([z.string(), z.array(z.string())]), to: z.union([z.string(), z.array(z.string())]), description: z @@ -68,13 +290,39 @@ export const graphEdgeSchema = z.object({ }); /** Per-tool options schema (defer_loading, allowed_callers) */ -export const toolOptionsSchema = z.object({ +export const toolOptionsSchema: z.ZodObject< + { + defer_loading: z.ZodOptional; + allowed_callers: z.ZodOptional, 'many'>>; + }, + 'strip' +> = z.object({ defer_loading: z.boolean().optional(), allowed_callers: z.array(z.enum(['direct', 'code_execution'])).optional(), }); /** Agent tool options - map of tool_id to tool options */ -export const agentToolOptionsSchema = z.record(z.string(), toolOptionsSchema).optional(); +export const agentToolOptionsSchema: z.ZodOptional< + z.ZodRecord< + z.ZodString, + z.ZodObject< + { + defer_loading: z.ZodOptional; + allowed_callers: z.ZodOptional, 'many'>>; + }, + 'strip', + z.ZodTypeAny, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + }, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + } + > + > +> = z.record(z.string(), toolOptionsSchema).optional(); /** * Subagent spawning configuration for an agent. `agent_ids` is capped at @@ -82,7 +330,27 @@ export const agentToolOptionsSchema = z.record(z.string(), toolOptionsSchema).op * of `processAgent` calls (DB lookup + permission check + tool loading). * The UI enforces the same cap, so legitimate payloads never hit the bound. */ -export const agentSubagentsSchema = z +export const agentSubagentsSchema: z.ZodOptional< + z.ZodObject< + { + enabled: z.ZodOptional; + allowSelf: z.ZodOptional; + agent_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + }, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + } + > +> = z .object({ enabled: z.boolean().optional(), allowSelf: z.boolean().optional(), @@ -91,7 +359,327 @@ export const agentSubagentsSchema = z .optional(); /** Base agent schema with all common fields */ -export const agentBaseSchema = z.object({ +export const agentBaseSchema: z.ZodObject< + { + name: z.ZodOptional>; + description: z.ZodOptional>; + instructions: z.ZodOptional>; + avatar: z.ZodOptional< + z.ZodNullable< + z.ZodObject< + { + filepath: z.ZodString; + source: z.ZodString; + }, + 'strip', + z.ZodTypeAny, + { + source: string; + filepath: string; + }, + { + source: string; + filepath: string; + } + > + > + >; + model_parameters: z.ZodOptional>; + tools: z.ZodOptional>; + skills: z.ZodOptional>; + skills_enabled: z.ZodOptional; + /** @deprecated Use edges instead */ + agent_ids: z.ZodOptional>; + edges: z.ZodOptional< + z.ZodArray< + z.ZodObject< + { + from: z.ZodUnion<[z.ZodString, z.ZodArray]>; + to: z.ZodUnion<[z.ZodString, z.ZodArray]>; + description: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + edgeType: z.ZodOptional>; + prompt: z.ZodEffects< + z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodFunction, z.ZodUnknown>]> + >, + string | ((...args: unknown[]) => unknown) | undefined, + string | ((...args: unknown[]) => unknown) | undefined + >; + excludeResults: z.ZodOptional; + promptKey: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + }, + 'strip', + z.ZodTypeAny, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + }, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + } + >, + 'many' + > + >; + end_after_tools: z.ZodOptional; + hide_sequential_outputs: z.ZodOptional; + artifacts: z.ZodOptional; + recursion_limit: z.ZodOptional; + conversation_starters: z.ZodOptional>; + tool_resources: z.ZodOptional< + z.ZodObject< + { + image_edit: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + execute_code: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + file_search: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + } & { + vector_store_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + > + >; + context: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + /** @deprecated Use context instead */ + ocr: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + }, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + } + > + >; + tool_options: z.ZodOptional< + z.ZodRecord< + z.ZodString, + z.ZodObject< + { + defer_loading: z.ZodOptional; + allowed_callers: z.ZodOptional< + z.ZodArray, 'many'> + >; + }, + 'strip', + z.ZodTypeAny, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + }, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + } + > + > + >; + subagents: z.ZodOptional< + z.ZodObject< + { + enabled: z.ZodOptional; + allowSelf: z.ZodOptional; + agent_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + }, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + } + > + >; + support_contact: z.ZodOptional< + z.ZodObject< + { + name: z.ZodOptional; + email: z.ZodOptional, z.ZodString]>>; + }, + 'strip', + z.ZodTypeAny, + { + name?: string | undefined; + email?: string | undefined; + }, + { + name?: string | undefined; + email?: string | undefined; + } + > + >; + category: z.ZodOptional; + }, + 'strip' +> = z.object({ name: z.string().nullable().optional(), description: z.string().nullable().optional(), instructions: z.string().nullable().optional(), @@ -116,14 +704,661 @@ export const agentBaseSchema = z.object({ }); /** Create schema extends base with required fields for creation */ -export const agentCreateSchema = agentBaseSchema.extend({ +export const agentCreateSchema: z.ZodObject< + { + name: z.ZodOptional>; + description: z.ZodOptional>; + instructions: z.ZodOptional>; + avatar: z.ZodOptional< + z.ZodNullable< + z.ZodObject< + { + filepath: z.ZodString; + source: z.ZodString; + }, + 'strip', + z.ZodTypeAny, + { + source: string; + filepath: string; + }, + { + source: string; + filepath: string; + } + > + > + >; + model_parameters: z.ZodOptional>; + skills: z.ZodOptional>; + skills_enabled: z.ZodOptional; + agent_ids: z.ZodOptional>; + edges: z.ZodOptional< + z.ZodArray< + z.ZodObject< + { + from: z.ZodUnion<[z.ZodString, z.ZodArray]>; + to: z.ZodUnion<[z.ZodString, z.ZodArray]>; + description: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + edgeType: z.ZodOptional>; + prompt: z.ZodEffects< + z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodFunction, z.ZodUnknown>]> + >, + string | ((...args: unknown[]) => unknown) | undefined, + string | ((...args: unknown[]) => unknown) | undefined + >; + excludeResults: z.ZodOptional; + promptKey: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + }, + 'strip', + z.ZodTypeAny, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + }, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + } + >, + 'many' + > + >; + end_after_tools: z.ZodOptional; + hide_sequential_outputs: z.ZodOptional; + artifacts: z.ZodOptional; + recursion_limit: z.ZodOptional; + conversation_starters: z.ZodOptional>; + tool_resources: z.ZodOptional< + z.ZodObject< + { + image_edit: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + execute_code: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + file_search: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + } & { + vector_store_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + > + >; + context: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + /** @deprecated Use context instead */ + ocr: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + }, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + } + > + >; + tool_options: z.ZodOptional< + z.ZodRecord< + z.ZodString, + z.ZodObject< + { + defer_loading: z.ZodOptional; + allowed_callers: z.ZodOptional< + z.ZodArray, 'many'> + >; + }, + 'strip', + z.ZodTypeAny, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + }, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + } + > + > + >; + subagents: z.ZodOptional< + z.ZodObject< + { + enabled: z.ZodOptional; + allowSelf: z.ZodOptional; + agent_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + }, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + } + > + >; + support_contact: z.ZodOptional< + z.ZodObject< + { + name: z.ZodOptional; + email: z.ZodOptional, z.ZodString]>>; + }, + 'strip', + z.ZodTypeAny, + { + name?: string | undefined; + email?: string | undefined; + }, + { + name?: string | undefined; + email?: string | undefined; + } + > + >; + category: z.ZodOptional; + } & { + provider: z.ZodString; + model: z.ZodNullable; + tools: z.ZodDefault>>; + }, + 'strip' +> = agentBaseSchema.extend({ provider: z.string(), model: z.string().nullable(), tools: z.array(z.string()).optional().default([]), }); /** Update schema extends base with all fields optional and additional update-only fields */ -export const agentUpdateSchema = agentBaseSchema.extend({ +export const agentUpdateSchema: z.ZodObject< + { + name: z.ZodOptional>; + description: z.ZodOptional>; + instructions: z.ZodOptional>; + model_parameters: z.ZodOptional>; + tools: z.ZodOptional>; + skills: z.ZodOptional>; + skills_enabled: z.ZodOptional; + agent_ids: z.ZodOptional>; + edges: z.ZodOptional< + z.ZodArray< + z.ZodObject< + { + from: z.ZodUnion<[z.ZodString, z.ZodArray]>; + to: z.ZodUnion<[z.ZodString, z.ZodArray]>; + description: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + edgeType: z.ZodOptional>; + prompt: z.ZodEffects< + z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodFunction, z.ZodUnknown>]> + >, + string | ((...args: unknown[]) => unknown) | undefined, + string | ((...args: unknown[]) => unknown) | undefined + >; + excludeResults: z.ZodOptional; + promptKey: z.ZodEffects< + z.ZodOptional, + string | undefined, + string | undefined + >; + }, + 'strip', + z.ZodTypeAny, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + }, + { + from: string | string[]; + to: string | string[]; + description?: string | undefined; + prompt?: string | ((...args: unknown[]) => unknown) | undefined; + edgeType?: 'direct' | 'handoff' | undefined; + excludeResults?: boolean | undefined; + promptKey?: string | undefined; + } + >, + 'many' + > + >; + end_after_tools: z.ZodOptional; + hide_sequential_outputs: z.ZodOptional; + artifacts: z.ZodOptional; + recursion_limit: z.ZodOptional; + conversation_starters: z.ZodOptional>; + tool_resources: z.ZodOptional< + z.ZodObject< + { + image_edit: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + execute_code: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + file_search: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + } & { + vector_store_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + > + >; + context: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + /** @deprecated Use context instead */ + ocr: z.ZodOptional< + z.ZodObject< + { + file_ids: z.ZodOptional>; + files: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + }, + { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + }, + { + ocr?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + context?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + execute_code?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + file_search?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + vector_store_ids?: string[] | undefined; + } + | undefined; + image_edit?: + | { + file_ids?: string[] | undefined; + files?: unknown[] | undefined; + } + | undefined; + } + > + >; + tool_options: z.ZodOptional< + z.ZodRecord< + z.ZodString, + z.ZodObject< + { + defer_loading: z.ZodOptional; + allowed_callers: z.ZodOptional< + z.ZodArray, 'many'> + >; + }, + 'strip', + z.ZodTypeAny, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + }, + { + defer_loading?: boolean | undefined; + allowed_callers?: ('direct' | 'code_execution')[] | undefined; + } + > + > + >; + subagents: z.ZodOptional< + z.ZodObject< + { + enabled: z.ZodOptional; + allowSelf: z.ZodOptional; + agent_ids: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + }, + { + enabled?: boolean | undefined; + agent_ids?: string[] | undefined; + allowSelf?: boolean | undefined; + } + > + >; + support_contact: z.ZodOptional< + z.ZodObject< + { + name: z.ZodOptional; + email: z.ZodOptional, z.ZodString]>>; + }, + 'strip', + z.ZodTypeAny, + { + name?: string | undefined; + email?: string | undefined; + }, + { + name?: string | undefined; + email?: string | undefined; + } + > + >; + category: z.ZodOptional; + } & { + avatar: z.ZodOptional< + z.ZodUnion< + [ + z.ZodObject< + { + filepath: z.ZodString; + source: z.ZodString; + }, + 'strip', + z.ZodTypeAny, + { + source: string; + filepath: string; + }, + { + source: string; + filepath: string; + } + >, + z.ZodNull, + ] + > + >; + provider: z.ZodOptional; + model: z.ZodOptional>; + }, + 'strip' +> = agentBaseSchema.extend({ avatar: z.union([agentAvatarSchema, z.null()]).optional(), provider: z.string().optional(), model: z.string().nullable().optional(), diff --git a/packages/api/src/apiKeys/handlers.ts b/packages/api/src/apiKeys/handlers.ts index 61d23ccde9..1e3b12a7d5 100644 --- a/packages/api/src/apiKeys/handlers.ts +++ b/packages/api/src/apiKeys/handlers.ts @@ -1,6 +1,6 @@ +import { logger } from '@librechat/data-schemas'; import type { Request, Response } from 'express'; import type { Types } from 'mongoose'; -import { logger } from '@librechat/data-schemas'; export interface ApiKeyHandlerDependencies { createAgentApiKey: (params: { @@ -49,8 +49,16 @@ interface AuthenticatedRequest extends Request { }; } -export function createApiKeyHandlers(deps: ApiKeyHandlerDependencies) { - async function createApiKey(req: AuthenticatedRequest, res: Response) { +export function createApiKeyHandlers(deps: ApiKeyHandlerDependencies): { + createApiKey: (req: AuthenticatedRequest, res: Response) => Promise; + listApiKeys: (req: AuthenticatedRequest, res: Response) => Promise; + getApiKey: (req: AuthenticatedRequest, res: Response) => Promise; + deleteApiKey: (req: AuthenticatedRequest, res: Response) => Promise; +} { + async function createApiKey( + req: AuthenticatedRequest, + res: Response, + ): Promise { try { const { name, expiresAt } = req.body; @@ -80,7 +88,7 @@ export function createApiKeyHandlers(deps: ApiKeyHandlerDependencies) { } } - async function listApiKeys(req: AuthenticatedRequest, res: Response) { + async function listApiKeys(req: AuthenticatedRequest, res: Response): Promise { try { const keys = await deps.listAgentApiKeys(req.user?.id || ''); res.status(200).json({ keys }); @@ -90,7 +98,10 @@ export function createApiKeyHandlers(deps: ApiKeyHandlerDependencies) { } } - async function getApiKey(req: AuthenticatedRequest, res: Response) { + async function getApiKey( + req: AuthenticatedRequest, + res: Response, + ): Promise { try { const key = await deps.getAgentApiKeyById(req.params.id, req.user?.id || ''); @@ -105,7 +116,10 @@ export function createApiKeyHandlers(deps: ApiKeyHandlerDependencies) { } } - async function deleteApiKey(req: AuthenticatedRequest, res: Response) { + async function deleteApiKey( + req: AuthenticatedRequest, + res: Response, + ): Promise { try { const deleted = await deps.deleteAgentApiKey(req.params.id, req.user?.id || ''); diff --git a/packages/api/src/apiKeys/middleware.ts b/packages/api/src/apiKeys/middleware.ts index 2a50353648..626a8dd5df 100644 --- a/packages/api/src/apiKeys/middleware.ts +++ b/packages/api/src/apiKeys/middleware.ts @@ -36,7 +36,11 @@ export interface RemoteAgentAccessRequest extends ApiKeyAuthRequest { } export function createRequireApiKeyAuth(deps: ApiKeyAuthDependencies) { - return async (req: ApiKeyAuthRequest, res: Response, next: NextFunction) => { + return async ( + req: ApiKeyAuthRequest, + res: Response, + next: NextFunction, + ): Promise => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { @@ -105,7 +109,11 @@ export function createRequireApiKeyAuth(deps: ApiKeyAuthDependencies) { } export function createCheckRemoteAgentAccess(deps: RemoteAgentAccessDependencies) { - return async (req: RemoteAgentAccessRequest, res: Response, next: NextFunction) => { + return async ( + req: RemoteAgentAccessRequest, + res: Response, + next: NextFunction, + ): Promise => { const agentId = req.body?.model || req.params?.model; if (!agentId) { diff --git a/packages/api/src/apiKeys/service.ts b/packages/api/src/apiKeys/service.ts index 30ae0afb53..4038e510fb 100644 --- a/packages/api/src/apiKeys/service.ts +++ b/packages/api/src/apiKeys/service.ts @@ -1,6 +1,11 @@ import { createMethods } from '@librechat/data-schemas'; import { ResourceType, PermissionBits, hasPermissions } from 'librechat-data-provider'; -import type { AllMethods, IUser } from '@librechat/data-schemas'; +import type { + AgentApiKeyListItem, + AgentApiKeyCreateResult, + AllMethods, + IUser, +} from '@librechat/data-schemas'; import type { Types } from 'mongoose'; export interface ApiKeyServiceDependencies { @@ -36,19 +41,25 @@ export class AgentApiKeyService { userId: string | Types.ObjectId; name: string; expiresAt?: Date | null; - }) { + }): Promise { return this.deps.createAgentApiKey(params); } - async listApiKeys(userId: string | Types.ObjectId) { + async listApiKeys(userId: string | Types.ObjectId): Promise { return this.deps.listAgentApiKeys(userId); } - async deleteApiKey(keyId: string | Types.ObjectId, userId: string | Types.ObjectId) { + async deleteApiKey( + keyId: string | Types.ObjectId, + userId: string | Types.ObjectId, + ): Promise { return this.deps.deleteAgentApiKey(keyId, userId); } - async getApiKeyById(keyId: string | Types.ObjectId, userId: string | Types.ObjectId) { + async getApiKeyById( + keyId: string | Types.ObjectId, + userId: string | Types.ObjectId, + ): Promise { return this.deps.getAgentApiKeyById(keyId, userId); } diff --git a/packages/api/src/app/checks.ts b/packages/api/src/app/checks.ts index 50312267ac..019d2a1f56 100644 --- a/packages/api/src/app/checks.ts +++ b/packages/api/src/app/checks.ts @@ -30,7 +30,10 @@ const deprecatedVariables = [ }, ]; -export const deprecatedAzureVariables = [ +export const deprecatedAzureVariables: { + key: string; + description: string; +}[] = [ /* "related to" precedes description text */ { key: 'AZURE_OPENAI_DEFAULT_MODEL', description: 'setting a default model' }, { key: 'AZURE_OPENAI_MODELS', description: 'setting models' }, @@ -59,7 +62,9 @@ export const deprecatedAzureVariables = [ }, ]; -export const conflictingAzureVariables = [ +export const conflictingAzureVariables: { + key: string; +}[] = [ { key: 'INSTANCE_NAME', }, @@ -100,7 +105,7 @@ function checkPasswordReset() { * @param {Function} options.isEnabled - Function to check if a feature is enabled * @param {Function} options.checkEmailConfig - Function to check email configuration */ -export function checkVariables() { +export function checkVariables(): void { let hasDefaultSecrets = false; for (const [key, value] of Object.entries(secretDefaults)) { if (process.env[key] === value) { @@ -134,7 +139,7 @@ export function checkVariables() { * Checks the health of auxiliary API's by attempting a fetch request to their respective `/health` endpoints. * Logs information or warning based on the API's availability and response. */ -export async function checkHealth() { +export async function checkHealth(): Promise { try { const response = await fetch(`${process.env.RAG_API_URL}/health`); if (response?.ok && response?.status === 200) { @@ -169,7 +174,7 @@ function checkAzureVariables() { }); } -export function checkInterfaceConfig(appConfig: AppConfig) { +export function checkInterfaceConfig(appConfig: AppConfig): void { const interfaceConfig = appConfig.interfaceConfig; let i = 0; const logSettings = () => { @@ -220,7 +225,7 @@ export function checkInterfaceConfig(appConfig: AppConfig) { * This should be called during application startup before initializing services. * @param [appConfig] - The application configuration object. */ -export async function performStartupChecks(appConfig?: AppConfig) { +export async function performStartupChecks(appConfig?: AppConfig): Promise { checkVariables(); if (appConfig?.endpoints?.azureOpenAI) { checkAzureVariables(); @@ -244,7 +249,7 @@ export async function performStartupChecks(appConfig?: AppConfig) { * Performs basic checks on the loaded config object. * @param config - The loaded custom configuration. */ -export function checkConfig(config: Partial) { +export function checkConfig(config: Partial): void { if (config.version !== Constants.CONFIG_VERSION) { logger.info( `\nOutdated Config version: ${config.version} @@ -263,7 +268,9 @@ Latest version: ${Constants.CONFIG_VERSION} * Logs debug information for properly configured environment variable references. * @param webSearchConfig - The loaded web search configuration object. */ -export function checkWebSearchConfig(webSearchConfig?: Partial | null) { +export function checkWebSearchConfig( + webSearchConfig?: Partial | null, +): void { if (!webSearchConfig) { return; } diff --git a/packages/api/src/app/limits.ts b/packages/api/src/app/limits.ts index a38a3c6310..1f1ad583f5 100644 --- a/packages/api/src/app/limits.ts +++ b/packages/api/src/app/limits.ts @@ -5,7 +5,7 @@ import type { TCustomConfig } from 'librechat-data-provider'; * * @param rateLimits */ -export const handleRateLimits = (rateLimits?: TCustomConfig['rateLimits']) => { +export const handleRateLimits = (rateLimits?: TCustomConfig['rateLimits']): void => { if (!rateLimits) { return; } diff --git a/packages/api/src/app/service.ts b/packages/api/src/app/service.ts index 658056503f..87556f2567 100644 --- a/packages/api/src/app/service.ts +++ b/packages/api/src/app/service.ts @@ -5,8 +5,8 @@ import { mergeConfigOverrides, BASE_CONFIG_PRINCIPAL_ID, } from '@librechat/data-schemas'; -import type { Types } from 'mongoose'; import type { AppConfig, IConfig } from '@librechat/data-schemas'; +import type { Types } from 'mongoose'; const BASE_CONFIG_KEY = '_BASE_'; @@ -91,7 +91,11 @@ function overrideCacheKey(role?: string, userId?: string, tenantId?: string): st // ── Service factory ────────────────────────────────────────────────── -export function createAppConfigService(deps: AppConfigServiceDeps) { +export function createAppConfigService(deps: AppConfigServiceDeps): { + getAppConfig: (options?: GetAppConfigOptions) => Promise; + clearAppConfigCache: () => Promise; + clearOverrideCache: (tenantId?: string) => Promise; +} { const { loadBaseConfig, setCachedTools, diff --git a/packages/api/src/auth/exchange.ts b/packages/api/src/auth/exchange.ts index 74d882bb09..53db548f68 100644 --- a/packages/api/src/auth/exchange.ts +++ b/packages/api/src/auth/exchange.ts @@ -178,9 +178,9 @@ export async function exchangeAdminCode( } /** PKCE challenge cache TTL: 5 minutes (enough for user to authenticate with IdP) */ -export const PKCE_CHALLENGE_TTL = 5 * 60 * 1000; +export const PKCE_CHALLENGE_TTL: number = 5 * 60 * 1000; /** Regex pattern for valid PKCE challenges: 64 hex characters (SHA-256 hex digest) */ -export const PKCE_CHALLENGE_PATTERN = /^[a-f0-9]{64}$/; +export const PKCE_CHALLENGE_PATTERN: RegExp = /^[a-f0-9]{64}$/; const ADMIN_OAUTH_STRIPPED_QUERY_PARAMS = new Set(['code_challenge', 'redirect_uri', 'redirectTo']); diff --git a/packages/api/src/cache/cacheConfig.ts b/packages/api/src/cache/cacheConfig.ts index cbccffbf59..21fe1e7d03 100644 --- a/packages/api/src/cache/cacheConfig.ts +++ b/packages/api/src/cache/cacheConfig.ts @@ -70,7 +70,77 @@ const getRedisCA = (): string | null => { } }; -const cacheConfig = { +const cacheConfig: { + FORCED_IN_MEMORY_CACHE_NAMESPACES: string[]; + USE_REDIS: boolean; + USE_REDIS_STREAMS: boolean; + REDIS_URI: string | undefined; + REDIS_USERNAME: string | undefined; + REDIS_PASSWORD: string | undefined; + REDIS_CA: string | null; + REDIS_KEY_PREFIX: string; + GLOBAL_PREFIX_SEPARATOR: string; + REDIS_MAX_LISTENERS: number; + REDIS_PING_INTERVAL: number; + /** Max delay between reconnection attempts in ms */ + REDIS_RETRY_MAX_DELAY: number; + /** Max number of reconnection attempts (0 = infinite) */ + REDIS_RETRY_MAX_ATTEMPTS: number; + /** Connection timeout in ms */ + REDIS_CONNECT_TIMEOUT: number; + /** Queue commands when disconnected */ + REDIS_ENABLE_OFFLINE_QUEUE: boolean; + /** flag to modify redis connection by adding dnsLookup this is required when connecting to elasticache for ioredis + * see "Special Note: Aws Elasticache Clusters with TLS" on this webpage: https://www.npmjs.com/package/ioredis **/ + REDIS_USE_ALTERNATIVE_DNS_LOOKUP: boolean; + /** Enable redis cluster without the need of multiple URIs */ + USE_REDIS_CLUSTER: boolean; + /** + * Force cluster-safe (key-by-key) deletion even when connecting as a single-node Redis instance. + * Needed for managed services like ElastiCache Serverless that present a single endpoint + * but shard keys internally, causing CROSSSLOT errors on multi-key DEL commands. + * Has no effect when USE_REDIS_CLUSTER is already true. + */ + REDIS_CLUSTER_SAFE_DELETE: boolean; + CI: boolean; + DEBUG_MEMORY_CACHE: boolean; + BAN_DURATION: number; // 2 hours + /** + * Number of keys to delete in each batch during Redis DEL operations. + * In cluster mode, keys are deleted individually in parallel chunks to avoid CROSSSLOT errors. + * In single-node mode, keys are deleted in batches using DEL with arrays. + * Lower values reduce memory usage but increase number of Redis calls. + * @default 1000 + */ + REDIS_DELETE_CHUNK_SIZE: number; + /** + * Number of keys to update in each batch during Redis SET operations. + * In cluster mode, keys are updated individually in parallel chunks to avoid CROSSSLOT errors. + * In single-node mode, keys are updated in batches using transactions (multi/exec). + * Lower values reduce memory usage but increase number of Redis calls. + * @default 1000 + */ + REDIS_UPDATE_CHUNK_SIZE: number; + /** + * COUNT hint for Redis SCAN operations when scanning keys by pattern. + * This is a hint to Redis about how many keys to scan in each iteration. + * Higher values can reduce round trips but increase memory usage and latency per call. + * Note: Redis may return more or fewer keys than this count depending on internal heuristics. + * @default 1000 + */ + REDIS_SCAN_COUNT: number; + /** + * TTL in milliseconds for MCP registry caches. Used by both: + * - `MCPServersRegistry` read-through caches (`readThroughCache`/`readThroughCacheAll`) + * - `ServerConfigsCacheRedisAggregateKey` local snapshot (avoids redundant Redis GETs) + * + * Both layers use this value, so the effective max cross-instance staleness is up + * to 2× this value in multi-instance deployments. Set to 0 to disable the local + * snapshot entirely (every `getAll()` hits Redis directly). + * @default 5000 (5 seconds) + */ + MCP_REGISTRY_CACHE_TTL: number; +} = { FORCED_IN_MEMORY_CACHE_NAMESPACES, USE_REDIS, USE_REDIS_STREAMS, diff --git a/packages/api/src/cache/keyvFiles.ts b/packages/api/src/cache/keyvFiles.ts index 94fe9b7cab..5652df6314 100644 --- a/packages/api/src/cache/keyvFiles.ts +++ b/packages/api/src/cache/keyvFiles.ts @@ -1,6 +1,6 @@ import { KeyvFile } from 'keyv-file'; -export const logFile = new KeyvFile({ filename: './data/logs.json' }).setMaxListeners(20); -export const violationFile = new KeyvFile({ filename: './data/violations.json' }).setMaxListeners( - 20, -); +export const logFile: KeyvFile = new KeyvFile({ filename: './data/logs.json' }).setMaxListeners(20); +export const violationFile: KeyvFile = new KeyvFile({ + filename: './data/violations.json', +}).setMaxListeners(20); diff --git a/packages/api/src/cache/keyvMongo.ts b/packages/api/src/cache/keyvMongo.ts index 4c0a08bf63..d020cdedc4 100644 --- a/packages/api/src/cache/keyvMongo.ts +++ b/packages/api/src/cache/keyvMongo.ts @@ -271,7 +271,7 @@ class KeyvMongoCustom extends EventEmitter { } } -const keyvMongo = new KeyvMongoCustom({ +const keyvMongo: KeyvMongoCustom = new KeyvMongoCustom({ collection: 'logs', }); diff --git a/packages/api/src/cdn/azure.ts b/packages/api/src/cdn/azure.ts index 247530286c..1a0f96e0e4 100644 --- a/packages/api/src/cdn/azure.ts +++ b/packages/api/src/cdn/azure.ts @@ -47,7 +47,7 @@ export const initializeAzureBlobService = async (): Promise => { const serviceClient = await initializeAzureBlobService(); return serviceClient ? serviceClient.getContainerClient(containerName) : null; diff --git a/packages/api/src/cdn/cloudfront-cookies.ts b/packages/api/src/cdn/cloudfront-cookies.ts index f6c62d0390..eaa4e706b1 100644 --- a/packages/api/src/cdn/cloudfront-cookies.ts +++ b/packages/api/src/cdn/cloudfront-cookies.ts @@ -1,12 +1,10 @@ -import { getSignedCookies } from '@aws-sdk/cloudfront-signer'; import { logger } from '@librechat/data-schemas'; - +import { getSignedCookies } from '@aws-sdk/cloudfront-signer'; import type { NextFunction, Response } from 'express'; - import { INLINE_AVATAR_PATH_PREFIX, INLINE_IMAGE_PATH_PREFIX } from '~/storage/constants'; import { assertPathSegment } from '~/storage/validation'; -import { s3Config } from '~/storage/s3/s3Config'; import { getCloudFrontConfig } from './cloudfront'; +import { s3Config } from '~/storage/s3/s3Config'; const DEFAULT_COOKIE_EXPIRY = 1800; @@ -147,11 +145,17 @@ function getConfiguredCookieExpiry(): number { return config?.cookieExpiry ?? DEFAULT_COOKIE_EXPIRY; } -export function getCloudFrontCookieRefreshWindowSec(cookieExpiry = getConfiguredCookieExpiry()) { +export function getCloudFrontCookieRefreshWindowSec( + cookieExpiry: number = getConfiguredCookieExpiry(), +): number { return Math.min(300, Math.floor(cookieExpiry / 4)); } -export function getCloudFrontCookieTiming() { +export function getCloudFrontCookieTiming(): { + expiresInSec: number; + refreshAfterSec: number; + refreshWindowSec: number; +} { const expiresInSec = getConfiguredCookieExpiry(); const refreshWindowSec = getCloudFrontCookieRefreshWindowSec(expiresInSec); return { diff --git a/packages/api/src/cdn/firebase.ts b/packages/api/src/cdn/firebase.ts index 30fca65b1b..066f295c5d 100644 --- a/packages/api/src/cdn/firebase.ts +++ b/packages/api/src/cdn/firebase.ts @@ -7,7 +7,7 @@ import type { FirebaseApp } from 'firebase/app'; let firebaseInitCount = 0; let firebaseApp: FirebaseApp | null = null; -export const initializeFirebase = () => { +export const initializeFirebase = (): firebase.FirebaseApp | null => { if (firebaseApp) { return firebaseApp; } diff --git a/packages/api/src/cluster/LeaderElection.ts b/packages/api/src/cluster/LeaderElection.ts index 726c56b185..e40a95cde8 100644 --- a/packages/api/src/cluster/LeaderElection.ts +++ b/packages/api/src/cluster/LeaderElection.ts @@ -1,7 +1,7 @@ -import { keyvRedisClient } from '~/cache/redisClients'; -import { cacheConfig as cache } from '~/cache/cacheConfig'; -import { clusterConfig as cluster } from './config'; import { logger } from '@librechat/data-schemas'; +import { cacheConfig as cache } from '~/cache/cacheConfig'; +import { keyvRedisClient } from '~/cache/redisClients'; +import { clusterConfig as cluster } from './config'; /** * Distributed leader election implementation using Redis for coordination across multiple server instances. @@ -21,7 +21,7 @@ import { logger } from '@librechat/data-schemas'; */ export class LeaderElection { // We can't use Keyv namespace here because we need direct Redis access for atomic operations - static readonly LEADER_KEY = `${cache.REDIS_KEY_PREFIX}${cache.GLOBAL_PREFIX_SEPARATOR}LeadingServerUUID`; + static readonly LEADER_KEY: string = `${cache.REDIS_KEY_PREFIX}${cache.GLOBAL_PREFIX_SEPARATOR}LeadingServerUUID`; private static _instance = new LeaderElection(); readonly UUID: string = crypto.randomUUID(); diff --git a/packages/api/src/cluster/config.ts b/packages/api/src/cluster/config.ts index d4a99b3217..44a0aa56f9 100644 --- a/packages/api/src/cluster/config.ts +++ b/packages/api/src/cluster/config.ts @@ -1,6 +1,15 @@ import { math } from '~/utils'; -const clusterConfig = { +const clusterConfig: { + /** Duration in seconds that the leader lease is valid before it expires */ + LEADER_LEASE_DURATION: number; + /** Interval in seconds at which the leader renews its lease */ + LEADER_RENEW_INTERVAL: number; + /** Maximum number of retry attempts when renewing the lease fails */ + LEADER_RENEW_ATTEMPTS: number; + /** Delay in seconds between retry attempts when renewing the lease */ + LEADER_RENEW_RETRY_DELAY: number; +} = { /** Duration in seconds that the leader lease is valid before it expires */ LEADER_LEASE_DURATION: math(process.env.LEADER_LEASE_DURATION, 25), /** Interval in seconds at which the leader renews its lease */ diff --git a/packages/api/src/endpoints/anthropic/llm.ts b/packages/api/src/endpoints/anthropic/llm.ts index 134fbcbd40..7881797f61 100644 --- a/packages/api/src/endpoints/anthropic/llm.ts +++ b/packages/api/src/endpoints/anthropic/llm.ts @@ -52,7 +52,7 @@ function parseCredentials( } /** Known Anthropic parameters that map directly to the client config */ -export const knownAnthropicParams = new Set([ +export const knownAnthropicParams: Set = new Set([ 'model', 'temperature', 'topP', diff --git a/packages/api/src/endpoints/config/endpoints.ts b/packages/api/src/endpoints/config/endpoints.ts index 4bdd999a13..32ec6769a5 100644 --- a/packages/api/src/endpoints/config/endpoints.ts +++ b/packages/api/src/endpoints/config/endpoints.ts @@ -5,8 +5,8 @@ import { orderEndpointsConfig, defaultAgentCapabilities, } from 'librechat-data-provider'; -import type { AppConfig } from '@librechat/data-schemas'; import type { AgentCapabilities, TEndpointsConfig, TConfig } from 'librechat-data-provider'; +import type { AppConfig } from '@librechat/data-schemas'; import type { ServerRequest, TCustomEndpointsConfig } from '~/types'; import { loadCustomEndpointsConfig as defaultLoadCustomEndpoints } from '~/endpoints/custom'; @@ -24,7 +24,10 @@ export interface EndpointsConfigDeps { loadCustomEndpointsConfig?: (custom: unknown) => TCustomEndpointsConfig | undefined; } -export function createEndpointsConfigService(deps: EndpointsConfigDeps) { +export function createEndpointsConfigService(deps: EndpointsConfigDeps): { + getEndpointsConfig: (req: ServerRequest) => Promise; + checkCapability: (req: ServerRequest, capability: AgentCapabilities) => Promise; +} { const { getAppConfig, loadDefaultEndpointsConfig, diff --git a/packages/api/src/endpoints/google/llm.ts b/packages/api/src/endpoints/google/llm.ts index 7ba91b9c9d..af04414543 100644 --- a/packages/api/src/endpoints/google/llm.ts +++ b/packages/api/src/endpoints/google/llm.ts @@ -52,7 +52,7 @@ type GoogleModelOptions = Partial & Partial>; /** Known Google/Vertex AI parameters that map directly to the client config */ -export const knownGoogleParams = new Set([ +export const knownGoogleParams: Set = new Set([ 'model', 'modelName', 'temperature', @@ -320,7 +320,14 @@ export function getGoogleConfig( credentials: string | t.GoogleCredentials | undefined, options: t.GoogleConfigOptions = {}, acceptRawApiKey = false, -) { +): { + /** @type {GoogleAIToolType[]} */ + tools: GoogleAIToolType[]; + /** @type {Providers.GOOGLE | Providers.VERTEXAI} */ + provider: Providers.VERTEXAI | Providers.GOOGLE; + /** @type {GoogleClientOptions | VertexAIClientOptions} */ + llmConfig: VertexAIClientOptions | GoogleClientOptions; +} { let creds: t.GoogleCredentials = {}; if (acceptRawApiKey && typeof credentials === 'string') { creds[AuthKeys.GOOGLE_API_KEY] = credentials; diff --git a/packages/api/src/endpoints/openai/llm.ts b/packages/api/src/endpoints/openai/llm.ts index 94dc412fa5..2894c5a853 100644 --- a/packages/api/src/endpoints/openai/llm.ts +++ b/packages/api/src/endpoints/openai/llm.ts @@ -18,7 +18,7 @@ type OpenAILLMConfig = Omit, 'verbosity'> & verbosity?: string | null; }; -export const knownOpenAIParams = new Set([ +export const knownOpenAIParams: Set = new Set([ // Constructor/Instance Parameters 'model', 'modelName', @@ -407,7 +407,7 @@ export function extractDefaultParams( export function applyDefaultParams( target: Record, defaults: Record, -) { +): void { for (const [key, value] of Object.entries(defaults)) { if (target[key] === undefined) { target[key] = value; diff --git a/packages/api/src/files/code/extract.ts b/packages/api/src/files/code/extract.ts index e49ad61ad6..8d6ee04e7c 100644 --- a/packages/api/src/files/code/extract.ts +++ b/packages/api/src/files/code/extract.ts @@ -4,13 +4,13 @@ import * as fs from 'fs/promises'; import { randomUUID } from 'crypto'; import { logger } from '@librechat/data-schemas'; import type { CodeArtifactCategory } from './classify'; -import { parseDocument } from '~/files/documents/crud'; import { bufferToOfficeHtml, officeHtmlBucket } from '~/files/documents/html'; -import { isBinaryBuffer } from '~/skills/binary'; import { createConcurrencyLimiter, withTimeout } from '~/utils/promise'; +import { parseDocument } from '~/files/documents/crud'; +import { isBinaryBuffer } from '~/skills/binary'; -export const MAX_TEXT_CACHE_BYTES = 512 * 1024; -export const MAX_TEXT_EXTRACT_BYTES = 1024 * 1024; +export const MAX_TEXT_CACHE_BYTES: number = 512 * 1024; +export const MAX_TEXT_EXTRACT_BYTES: number = 1024 * 1024; const DOCUMENT_PARSE_TIMEOUT_MS = 8_000; const OFFICE_HTML_TIMEOUT_MS = 12_000; const TRUNCATION_MARKER = '\n\n…[truncated]'; diff --git a/packages/api/src/files/documents/html.ts b/packages/api/src/files/documents/html.ts index 7307685bbc..96e41d9107 100644 --- a/packages/api/src/files/documents/html.ts +++ b/packages/api/src/files/documents/html.ts @@ -1479,7 +1479,14 @@ async function pptxToSlideListHtmlInternal(buffer: Buffer): Promise { * padded fixtures. Not part of the public API — callers in production * code should always go through `wordDocToHtml` / `pptxToHtml`. */ -export const _internal = { +export const _internal: { + wordDocToHtmlViaCdn: typeof wordDocToHtmlViaCdn; + wordDocToHtmlViaMammoth: typeof wordDocToHtmlViaMammoth; + MAX_DOCX_CDN_BINARY_BYTES: number; + OFFICE_HTML_OUTPUT_CAP: number; + pptxToHtmlViaCdn: typeof pptxToHtmlViaCdn; + MAX_PPTX_CDN_BINARY_BYTES: number; +} = { wordDocToHtmlViaCdn, wordDocToHtmlViaMammoth, MAX_DOCX_CDN_BINARY_BYTES, diff --git a/packages/api/src/files/documents/libreoffice.ts b/packages/api/src/files/documents/libreoffice.ts index aedbf624d1..fff4f62ad7 100644 --- a/packages/api/src/files/documents/libreoffice.ts +++ b/packages/api/src/files/documents/libreoffice.ts @@ -1,7 +1,7 @@ -import { spawn } from 'child_process'; -import { mkdtemp, readFile, rm, writeFile } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; +import { spawn } from 'child_process'; +import { mkdtemp, readFile, rm, writeFile } from 'fs/promises'; /** * LibreOffice-backed office preview pipeline. @@ -196,7 +196,7 @@ export function _resetLibreOfficeProbeCache(): void { export const LIBREOFFICE_TIMEOUT_MS = 30_000; /** Maximum PDF output size; refuse anything larger so a runaway doc can't fill the disk. */ -export const MAX_LIBREOFFICE_PDF_BYTES = 50 * 1024 * 1024; +export const MAX_LIBREOFFICE_PDF_BYTES: number = 50 * 1024 * 1024; /** Tag-distinct error so callers can distinguish "binary missing" from "conversion failed". */ export class LibreOfficeUnavailableError extends Error { diff --git a/packages/api/src/files/parse.ts b/packages/api/src/files/parse.ts index f782f7e5e7..f931ec81e2 100644 --- a/packages/api/src/files/parse.ts +++ b/packages/api/src/files/parse.ts @@ -10,7 +10,7 @@ const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|tiff|svg|webp)$/i; * @returns The basename of the image file from the URL. * Returns an empty string if the URL does not contain a valid image basename. */ -export function getImageBasename(urlString: string) { +export function getImageBasename(urlString: string): string { try { const url = new URL(urlString); const basename = path.basename(url.pathname); @@ -29,7 +29,7 @@ export function getImageBasename(urlString: string) { * @returns The basename of the file from the URL. * Returns an empty string if the URL parsing fails. */ -export function getFileBasename(urlString: string) { +export function getFileBasename(urlString: string): string { try { const url = new URL(urlString); return path.basename(url.pathname); diff --git a/packages/api/src/files/retention.ts b/packages/api/src/files/retention.ts index 58ba0df099..884f56fb2e 100644 --- a/packages/api/src/files/retention.ts +++ b/packages/api/src/files/retention.ts @@ -76,7 +76,7 @@ export const getConversationExpirationDate = ( return Number.isNaN(expiredAt.getTime()) ? null : expiredAt; }; -export const isActiveExpirationDate = (expiredAt: Date, now = new Date()): boolean => +export const isActiveExpirationDate = (expiredAt: Date, now: Date = new Date()): boolean => expiredAt > now; const createRetentionExpiry = ( diff --git a/packages/api/src/files/sweep.ts b/packages/api/src/files/sweep.ts index 29562ffac5..4128fe3aa4 100644 --- a/packages/api/src/files/sweep.ts +++ b/packages/api/src/files/sweep.ts @@ -73,7 +73,7 @@ export type ExpiredFileSweepResult = { }; export function getFileRetentionSweepInterval( - interval = process.env.FILE_RETENTION_SWEEP_INTERVAL_MS, + interval: string | undefined = process.env.FILE_RETENTION_SWEEP_INTERVAL_MS, ): number { if (interval == null || interval.trim() === '') { return DEFAULT_FILE_RETENTION_SWEEP_INTERVAL_MS; @@ -90,7 +90,10 @@ export function getExpiredFileEndpoint(source?: string): string { return source === FileSources.azure ? EModelEndpoint.azureAssistants : EModelEndpoint.assistants; } -export function hasExpiredFileEndpointConfig(appConfig: AppConfig | undefined, source?: string) { +export function hasExpiredFileEndpointConfig( + appConfig: AppConfig | undefined, + source?: string, +): boolean { if (source === FileSources.azure) { return Boolean(appConfig?.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants); } @@ -201,7 +204,7 @@ export async function resolveExpiredFileSweepConfig({ } export async function sweepExpiredFiles( - { appConfig, limit = 100, loadAppConfig }: ExpiredFileSweepOptions = {}, + { appConfig, limit = 100, loadAppConfig }: ExpiredFileSweepOptions | undefined = {}, { getExpiredFiles, processDeleteRequest, logger }: SweepDependencies, ): Promise { const files = (await getExpiredFiles(limit)) ?? []; @@ -254,7 +257,7 @@ export async function sweepExpiredFiles( } export function startExpiredFileSweep( - options: ExpiredFileSweepOptions = {}, + options: ExpiredFileSweepOptions | undefined = {}, { sweepExpiredFiles, runAsSystem, logger }: StartSweepDependencies, ): NodeJS.Timeout | null { const intervalMs = getFileRetentionSweepInterval(); diff --git a/packages/api/src/flow/manager.ts b/packages/api/src/flow/manager.ts index f775222282..1f36dfe98d 100644 --- a/packages/api/src/flow/manager.ts +++ b/packages/api/src/flow/manager.ts @@ -4,7 +4,7 @@ import type { StoredDataNoRaw } from 'keyv'; import type { FlowState, FlowMetadata, FlowManagerOptions } from './types'; import { registerShutdownTask } from '../app/shutdown'; -export const PENDING_STALE_MS = 2 * 60 * 1000; +export const PENDING_STALE_MS: number = 2 * 60 * 1000; const SECONDS_THRESHOLD = 1e10; diff --git a/packages/api/src/mcp/MCPManager.ts b/packages/api/src/mcp/MCPManager.ts index 5a84d03592..3504af21c4 100644 --- a/packages/api/src/mcp/MCPManager.ts +++ b/packages/api/src/mcp/MCPManager.ts @@ -61,7 +61,7 @@ export class MCPManager extends UserConnectionManager { } /** Initializes the MCPManager by setting up server registry and app connections */ - public async initialize(configs: t.MCPServers) { + public async initialize(configs: t.MCPServers): Promise { await MCPServersInitializer.initialize(configs); this.appConnections = new ConnectionsRepository(undefined); } diff --git a/packages/api/src/mcp/UserConnectionManager.ts b/packages/api/src/mcp/UserConnectionManager.ts index 4f35855698..634dddf849 100644 --- a/packages/api/src/mcp/UserConnectionManager.ts +++ b/packages/api/src/mcp/UserConnectionManager.ts @@ -419,7 +419,7 @@ export abstract class UserConnectionManager { } /** Returns all connections for a specific user */ - public getUserConnections(userId: string) { + public getUserConnections(userId: string): Map | undefined { return this.userConnections.get(userId); } diff --git a/packages/api/src/mcp/auth.ts b/packages/api/src/mcp/auth.ts index e10c16b4b8..b25e0ce8ee 100644 --- a/packages/api/src/mcp/auth.ts +++ b/packages/api/src/mcp/auth.ts @@ -16,7 +16,7 @@ export async function getUserMCPAuthMap({ servers?: (string | undefined)[]; toolInstances?: (GenericTool | null)[]; findPluginAuthsByKeys: PluginAuthMethods['findPluginAuthsByKeys']; -}) { +}): Promise>> { let allMcpCustomUserVars: Record> = {}; let mcpPluginKeysToFetch: string[] = []; try { diff --git a/packages/api/src/mcp/connection.ts b/packages/api/src/mcp/connection.ts index 3ccdb432d0..35e619bbba 100644 --- a/packages/api/src/mcp/connection.ts +++ b/packages/api/src/mcp/connection.ts @@ -2,27 +2,27 @@ import { isIP } from 'node:net'; import { EventEmitter } from 'events'; import { logger } from '@librechat/data-schemas'; import { fetch as undiciFetch, Agent, ProxyAgent } from 'undici'; -import { - StdioClientTransport, - getDefaultEnvironment, -} from '@modelcontextprotocol/sdk/client/stdio.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js'; import { ResourceListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; -import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import { + StdioClientTransport, + getDefaultEnvironment, +} from '@modelcontextprotocol/sdk/client/stdio.js'; import type { RequestInit as UndiciRequestInit, RequestInfo as UndiciRequestInfo, Response as UndiciResponse, Dispatcher, } from 'undici'; +import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import type { MCPOAuthTokens } from './oauth/types'; import type * as t from './types'; import { createSSRFSafeUndiciConnect, isSSRFTarget, resolveHostnameSSRF } from '~/auth'; -import { isAddressAllowed } from '~/auth/domain'; import { runOutsideTracing } from '~/utils/tracing'; +import { isAddressAllowed } from '~/auth/domain'; import { sanitizeUrlForLogging } from './utils'; import { withTimeout } from '~/utils/promise'; import { mcpConfig } from './mcpConfig'; @@ -2183,7 +2183,50 @@ export class MCPConnection extends EventEmitter { } } - async fetchTools() { + async fetchTools(): Promise< + { + inputSchema: { + [x: string]: unknown; + type: 'object'; + properties?: Record | undefined; + required?: string[] | undefined; + }; + name: string; + description?: string | undefined; + outputSchema?: + | { + [x: string]: unknown; + type: 'object'; + properties?: Record | undefined; + required?: string[] | undefined; + } + | undefined; + annotations?: + | { + title?: string | undefined; + readOnlyHint?: boolean | undefined; + destructiveHint?: boolean | undefined; + idempotentHint?: boolean | undefined; + openWorldHint?: boolean | undefined; + } + | undefined; + execution?: + | { + taskSupport?: 'optional' | 'required' | 'forbidden' | undefined; + } + | undefined; + _meta?: Record | undefined; + icons?: + | { + src: string; + mimeType?: string | undefined; + sizes?: string[] | undefined; + theme?: 'light' | 'dark' | undefined; + }[] + | undefined; + title?: string | undefined; + }[] + > { try { const { tools } = await this.client.listTools(); return tools; diff --git a/packages/api/src/mcp/errors.ts b/packages/api/src/mcp/errors.ts index 21b249db30..9ac7568313 100644 --- a/packages/api/src/mcp/errors.ts +++ b/packages/api/src/mcp/errors.ts @@ -14,7 +14,7 @@ export type MCPErrorCode = (typeof MCPErrorCodes)[keyof typeof MCPErrorCodes]; * Thrown when a user attempts to connect to an MCP server whose domain is not in the allowlist. */ export class MCPDomainNotAllowedError extends Error { - public readonly code = MCPErrorCodes.DOMAIN_NOT_ALLOWED; + public readonly code: 'MCP_DOMAIN_NOT_ALLOWED' = MCPErrorCodes.DOMAIN_NOT_ALLOWED; public readonly statusCode = 403; public readonly domain: string; @@ -31,7 +31,7 @@ export class MCPDomainNotAllowedError extends Error { * Thrown when attempting to connect/inspect an MCP server fails. */ export class MCPInspectionFailedError extends Error { - public readonly code = MCPErrorCodes.INSPECTION_FAILED; + public readonly code: 'MCP_INSPECTION_FAILED' = MCPErrorCodes.INSPECTION_FAILED; public readonly statusCode = 400; public readonly serverName: string; diff --git a/packages/api/src/mcp/mcpConfig.ts b/packages/api/src/mcp/mcpConfig.ts index a81752e909..824b0fd3fd 100644 --- a/packages/api/src/mcp/mcpConfig.ts +++ b/packages/api/src/mcp/mcpConfig.ts @@ -4,7 +4,27 @@ import { math, isEnabled } from '~/utils'; * Centralized configuration for MCP-related environment variables. * Provides typed access to MCP settings with default values. */ -export const mcpConfig = { +export const mcpConfig: { + OAUTH_ON_AUTH_ERROR: boolean; + OAUTH_DETECTION_TIMEOUT: number; + CONNECTION_CHECK_TTL: number; + /** Idle timeout (ms) after which user connections are disconnected. Default: 15 minutes */ + USER_CONNECTION_IDLE_TIMEOUT: number; + /** Max connect/disconnect cycles before the circuit breaker trips. Default: 7 */ + CB_MAX_CYCLES: number; + /** Sliding window (ms) for counting cycles. Default: 45s */ + CB_CYCLE_WINDOW_MS: number; + /** Cooldown (ms) after the cycle breaker trips. Default: 15s */ + CB_CYCLE_COOLDOWN_MS: number; + /** Max consecutive failed connection rounds before backoff. Default: 3 */ + CB_MAX_FAILED_ROUNDS: number; + /** Sliding window (ms) for counting failed rounds. Default: 120s */ + CB_FAILED_WINDOW_MS: number; + /** Base backoff (ms) after failed round threshold is reached. Default: 30s */ + CB_BASE_BACKOFF_MS: number; + /** Max backoff cap (ms) for exponential backoff. Default: 300s */ + CB_MAX_BACKOFF_MS: number; +} = { OAUTH_ON_AUTH_ERROR: isEnabled(process.env.MCP_OAUTH_ON_AUTH_ERROR ?? true), OAUTH_DETECTION_TIMEOUT: math(process.env.MCP_OAUTH_DETECTION_TIMEOUT ?? 5000), CONNECTION_CHECK_TTL: math(process.env.MCP_CONNECTION_CHECK_TTL ?? 60000), diff --git a/packages/api/src/mcp/oauth/OAuthReconnectionManager.ts b/packages/api/src/mcp/oauth/OAuthReconnectionManager.ts index e0e30d7e14..1adb9b4be0 100644 --- a/packages/api/src/mcp/oauth/OAuthReconnectionManager.ts +++ b/packages/api/src/mcp/oauth/OAuthReconnectionManager.ts @@ -1,10 +1,10 @@ import { logger } from '@librechat/data-schemas'; import type { TokenMethods, IUser } from '@librechat/data-schemas'; import type { MCPOAuthTokens } from './types'; +import { MCPServersRegistry } from '~/mcp/registry/MCPServersRegistry'; import { OAuthReconnectionTracker } from './OAuthReconnectionTracker'; import { FlowStateManager } from '~/flow/manager'; import { MCPManager } from '~/mcp/MCPManager'; -import { MCPServersRegistry } from '~/mcp/registry/MCPServersRegistry'; const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000; // ms const RECONNECT_STAGGER_MS = 500; // ms between each server reconnection @@ -62,7 +62,7 @@ export class OAuthReconnectionManager { return this.reconnectionsTracker.isStillReconnecting(userId, serverName); } - public async reconnectServers(userId: string) { + public async reconnectServers(userId: string): Promise { // Check if MCPManager is available if (this.mcpManager == null) { logger.warn( @@ -138,7 +138,7 @@ export class OAuthReconnectionManager { } } - public clearReconnection(userId: string, serverName: string) { + public clearReconnection(userId: string, serverName: string): void { this.reconnectionsTracker.removeFailed(userId, serverName); this.reconnectionsTracker.removeActive(userId, serverName); } @@ -183,7 +183,11 @@ export class OAuthReconnectionManager { } } - public getTrackerStats() { + public getTrackerStats(): { + usersWithFailedServers: number; + usersWithActiveReconnections: number; + activeTimestamps: number; + } { return this.reconnectionsTracker.getStats(); } diff --git a/packages/api/src/mcp/oauth/events.ts b/packages/api/src/mcp/oauth/events.ts index 9d41cf3921..4aec0db7ea 100644 --- a/packages/api/src/mcp/oauth/events.ts +++ b/packages/api/src/mcp/oauth/events.ts @@ -15,7 +15,10 @@ export type OAuthToolCall = { output?: string; }; -export function getOAuthPromptExpiresAt(options?: OAuthPromptOptions, now = Date.now()): number { +export function getOAuthPromptExpiresAt( + options?: OAuthPromptOptions, + now: number = Date.now(), +): number { return typeof options?.expiresAt === 'number' && Number.isFinite(options.expiresAt) ? options.expiresAt : now + Time.TWO_MINUTES; diff --git a/packages/api/src/mcp/oauth/pending.ts b/packages/api/src/mcp/oauth/pending.ts index 9106c3b450..38ee093e8e 100644 --- a/packages/api/src/mcp/oauth/pending.ts +++ b/packages/api/src/mcp/oauth/pending.ts @@ -30,7 +30,7 @@ export type ReplayablePendingMCPOAuthStartOptions = { export function getReplayablePendingMCPOAuthStartFromFlow( flow: PendingOAuthFlowState | null | undefined, - now = Date.now(), + now: number = Date.now(), ): PendingOAuthStart | undefined { if (flow?.status !== 'PENDING') { return undefined; diff --git a/packages/api/src/mcp/parsers.ts b/packages/api/src/mcp/parsers.ts index 04065f62ad..2a65331495 100644 --- a/packages/api/src/mcp/parsers.ts +++ b/packages/api/src/mcp/parsers.ts @@ -3,7 +3,7 @@ import { Tools } from 'librechat-data-provider'; import type { UIResource } from 'librechat-data-provider'; import type * as t from './types'; -export const DEFAULT_MCP_IMAGE_DATA_MAX_BYTES = 10 * 1024 * 1024; +export const DEFAULT_MCP_IMAGE_DATA_MAX_BYTES: number = 10 * 1024 * 1024; function generateResourceId(text: string): string { return crypto.createHash('sha256').update(text).digest('hex').substring(0, 10); diff --git a/packages/api/src/mcp/registry/cache/RegistryStatusCache.ts b/packages/api/src/mcp/registry/cache/RegistryStatusCache.ts index 41e35ae99e..87d54fc362 100644 --- a/packages/api/src/mcp/registry/cache/RegistryStatusCache.ts +++ b/packages/api/src/mcp/registry/cache/RegistryStatusCache.ts @@ -1,5 +1,6 @@ -import { standardCache } from '~/cache'; +import Keyv from 'keyv'; import { BaseRegistryCache } from './BaseRegistryCache'; +import { standardCache } from '~/cache'; // Status keys const INITIALIZED = 'INITIALIZED'; @@ -19,7 +20,7 @@ type StatusSetOptions = { * This cache is only meant to be used internally by registry management components. */ class RegistryStatusCache extends BaseRegistryCache { - protected readonly cache = standardCache(`${this.PREFIX}::Status`); + protected readonly cache: Keyv = standardCache(`${this.PREFIX}::Status`); public async isInitialized(): Promise { return (await this.get(INITIALIZED)) === true; @@ -65,4 +66,4 @@ class RegistryStatusCache extends BaseRegistryCache { } } -export const registryStatusCache = new RegistryStatusCache(); +export const registryStatusCache: RegistryStatusCache = new RegistryStatusCache(); diff --git a/packages/api/src/mcp/tools.ts b/packages/api/src/mcp/tools.ts index d539cc5bd0..ccdb48e19a 100644 --- a/packages/api/src/mcp/tools.ts +++ b/packages/api/src/mcp/tools.ts @@ -20,7 +20,19 @@ export interface MCPToolCacheDeps { ) => Promise; } -export function createMCPToolCacheService(deps: MCPToolCacheDeps) { +export function createMCPToolCacheService(deps: MCPToolCacheDeps): { + updateMCPServerTools: (params: { + userId: string; + serverName: string; + tools: MCPToolInput[] | null; + }) => Promise; + mergeAppTools: (appTools: LCAvailableTools) => Promise; + cacheMCPServerTools: (params: { + userId: string; + serverName: string; + serverTools: LCAvailableTools; + }) => Promise; +} { const { getCachedTools, setCachedTools } = deps; async function updateMCPServerTools(params: { diff --git a/packages/api/src/mcp/utils.ts b/packages/api/src/mcp/utils.ts index 86efcc919c..f862badc31 100644 --- a/packages/api/src/mcp/utils.ts +++ b/packages/api/src/mcp/utils.ts @@ -1,7 +1,7 @@ import { Constants } from 'librechat-data-provider'; import type { ParsedServerConfig } from '~/mcp/types'; -export const mcpToolPattern = new RegExp(`^.+${Constants.mcp_delimiter}.+$`); +export const mcpToolPattern: RegExp = new RegExp(`^.+${Constants.mcp_delimiter}.+$`); /** Whether a server should use MCP OAuth handling. */ export function isOAuthServer( diff --git a/packages/api/src/mcp/zod.ts b/packages/api/src/mcp/zod.ts index 53cb6e71a8..cb2ffac40f 100644 --- a/packages/api/src/mcp/zod.ts +++ b/packages/api/src/mcp/zod.ts @@ -182,7 +182,7 @@ function convertToZodUnion( export function resolveJsonSchemaRefs>( schema: T, definitions?: Record, - visited = new Set(), + visited: Set = new Set(), ): T { // Handle null, undefined, or non-object values first if (!schema || typeof schema !== 'object') { @@ -568,7 +568,7 @@ export function convertJsonSchemaToZod( export function convertWithResolvedRefs( schema: JsonSchemaType & Record, options?: ConvertJsonSchemaToZodOptions, -) { +): z.ZodType | undefined { const resolved = resolveJsonSchemaRefs(schema); return convertJsonSchemaToZod(resolved, options); } diff --git a/packages/api/src/middleware/admin.ts b/packages/api/src/middleware/admin.ts index d770adfd85..07bbd39218 100644 --- a/packages/api/src/middleware/admin.ts +++ b/packages/api/src/middleware/admin.ts @@ -7,7 +7,11 @@ import type { ServerRequest } from '~/types/http'; * Middleware to check if authenticated user has admin role. * Should be used AFTER authentication middleware (requireJwtAuth, requireLocalAuth, etc.) */ -export const requireAdmin = (req: ServerRequest, res: Response, next: NextFunction) => { +export const requireAdmin = ( + req: ServerRequest, + res: Response, + next: NextFunction, +): Response | undefined => { if (!req.user) { logger.warn('[requireAdmin] No user found in request'); return res.status(401).json({ diff --git a/packages/api/src/middleware/capabilities.ts b/packages/api/src/middleware/capabilities.ts index 01a531106f..6fc9419dbd 100644 --- a/packages/api/src/middleware/capabilities.ts +++ b/packages/api/src/middleware/capabilities.ts @@ -62,7 +62,8 @@ export type HasConfigCapabilityFn = ( * Outside a request context (background jobs, tests), the store is undefined * and every check falls through to the database — correct behavior. */ -export const capabilityStore = new AsyncLocalStorage(); +export const capabilityStore: AsyncLocalStorage = + new AsyncLocalStorage(); export function capabilityContextMiddleware( _req: ServerRequest, diff --git a/packages/api/src/middleware/tenant.ts b/packages/api/src/middleware/tenant.ts index 52f89a02d7..b78ca8a743 100644 --- a/packages/api/src/middleware/tenant.ts +++ b/packages/api/src/middleware/tenant.ts @@ -68,7 +68,7 @@ function hasTenantContext(context: TenantContext): boolean { export function buildTenantContext( req: ContextRequest, - tenantId = req.tenantId ?? req.user?.tenantId, + tenantId: string | undefined = req.tenantId ?? req.user?.tenantId, ): TenantContext { return { tenantId: normalizeContextValue(tenantId), diff --git a/packages/api/src/modelSpecs/index.ts b/packages/api/src/modelSpecs/index.ts index 2ed7b29fdf..4f33f914df 100644 --- a/packages/api/src/modelSpecs/index.ts +++ b/packages/api/src/modelSpecs/index.ts @@ -10,7 +10,14 @@ import { type TUser, } from 'librechat-data-provider'; -export const PRIVATE_MODEL_SPEC_PRESET_FIELDS = [ +export const PRIVATE_MODEL_SPEC_PRESET_FIELDS: readonly [ + 'promptPrefix', + 'instructions', + 'additional_instructions', + 'system', + 'context', + 'examples', +] = [ 'promptPrefix', 'instructions', 'additional_instructions', @@ -23,7 +30,7 @@ export type PrivateModelSpecPresetField = (typeof PRIVATE_MODEL_SPEC_PRESET_FIEL export type ModelSpecParsedBody = Partial & Record; -export const ENFORCED_MODEL_SPEC_REQUEST_FIELDS = [ +export const ENFORCED_MODEL_SPEC_REQUEST_FIELDS: readonly ['chatProjectId'] = [ 'chatProjectId', ] as const satisfies readonly (keyof ModelSpecParsedBody)[]; diff --git a/packages/api/src/oauth/csrf.ts b/packages/api/src/oauth/csrf.ts index d2e0540c5d..802655f2ee 100644 --- a/packages/api/src/oauth/csrf.ts +++ b/packages/api/src/oauth/csrf.ts @@ -3,10 +3,10 @@ import type { Request, Response, NextFunction } from 'express'; import { isEnabled } from '~/utils/common'; export const OAUTH_CSRF_COOKIE = 'oauth_csrf'; -export const OAUTH_CSRF_MAX_AGE = 10 * 60 * 1000; +export const OAUTH_CSRF_MAX_AGE: number = 10 * 60 * 1000; export const OAUTH_SESSION_COOKIE = 'oauth_session'; -export const OAUTH_SESSION_MAX_AGE = 24 * 60 * 60 * 1000; +export const OAUTH_SESSION_MAX_AGE: number = 24 * 60 * 60 * 1000; export const OAUTH_SESSION_COOKIE_PATH = '/api'; /** diff --git a/packages/api/src/oauth/tokens.ts b/packages/api/src/oauth/tokens.ts index 596860bf58..dfdac87bb2 100644 --- a/packages/api/src/oauth/tokens.ts +++ b/packages/api/src/oauth/tokens.ts @@ -1,11 +1,11 @@ import axios from 'axios'; -import { logger, encryptV2, decryptV2 } from '@librechat/data-schemas'; import { TokenExchangeMethodEnum } from 'librechat-data-provider'; -import type { TokenMethods } from '@librechat/data-schemas'; +import { logger, encryptV2, decryptV2 } from '@librechat/data-schemas'; +import type { IToken, TokenMethods } from '@librechat/data-schemas'; import type { AxiosError } from 'axios'; +import { validateActionOAuthEndpoint } from './validation'; import { createSSRFSafeAgents } from '~/auth'; import { logAxiosError } from '~/utils'; -import { validateActionOAuthEndpoint } from './validation'; const actionOAuthAgents = createSSRFSafeAgents(); const actionOAuthAgentsByAddress = new Map>(); @@ -59,7 +59,7 @@ export function createHandleOAuthToken({ expiresIn?: number | string | null; metadata?: Record; type?: string; - }) { + }): Promise { const encrypedToken = await encryptV2(token); let expiresInNumber = 3600; if (typeof expiresIn === 'number') { diff --git a/packages/api/src/projects/handlers.ts b/packages/api/src/projects/handlers.ts index a6aa964d5b..6c05457d07 100644 --- a/packages/api/src/projects/handlers.ts +++ b/packages/api/src/projects/handlers.ts @@ -84,8 +84,15 @@ const createProjectInput = (req: ProjectRequest): CreateChatProjectInput | null }; }; -export function createProjectHandlers(deps: ProjectHandlerDependencies) { - async function listProjects(req: ProjectRequest, res: Response) { +export function createProjectHandlers(deps: ProjectHandlerDependencies): { + listProjects: (req: ProjectRequest, res: Response) => Promise; + createProject: (req: ProjectRequest, res: Response) => Promise; + assignConversationToProject: (req: ProjectRequest, res: Response) => Promise; + getProject: (req: ProjectRequest, res: Response) => Promise; + updateProject: (req: ProjectRequest, res: Response) => Promise; + deleteProject: (req: ProjectRequest, res: Response) => Promise; +} { + async function listProjects(req: ProjectRequest, res: Response): Promise { try { const result = await deps.listChatProjects(getUserId(req), { cursor: queryString(req.query.cursor), @@ -101,7 +108,7 @@ export function createProjectHandlers(deps: ProjectHandlerDependencies) { } } - async function createProject(req: ProjectRequest, res: Response) { + async function createProject(req: ProjectRequest, res: Response): Promise { const input = createProjectInput(req); if (!input) { return res.status(400).json({ error: 'name is required' }); @@ -116,7 +123,10 @@ export function createProjectHandlers(deps: ProjectHandlerDependencies) { } } - async function assignConversationToProject(req: ProjectRequest, res: Response) { + async function assignConversationToProject( + req: ProjectRequest, + res: Response, + ): Promise { const { conversationId } = req.params; const projectId = req.body?.projectId ?? null; @@ -143,7 +153,7 @@ export function createProjectHandlers(deps: ProjectHandlerDependencies) { } } - async function getProject(req: ProjectRequest, res: Response) { + async function getProject(req: ProjectRequest, res: Response): Promise { const { projectId } = req.params; if (!isValidObjectIdString(projectId)) { return res.status(404).json({ error: PROJECT_NOT_FOUND }); @@ -161,7 +171,7 @@ export function createProjectHandlers(deps: ProjectHandlerDependencies) { } } - async function updateProject(req: ProjectRequest, res: Response) { + async function updateProject(req: ProjectRequest, res: Response): Promise { const { projectId } = req.params; if (!isValidObjectIdString(projectId)) { return res.status(404).json({ error: PROJECT_NOT_FOUND }); @@ -191,7 +201,7 @@ export function createProjectHandlers(deps: ProjectHandlerDependencies) { } } - async function deleteProject(req: ProjectRequest, res: Response) { + async function deleteProject(req: ProjectRequest, res: Response): Promise { const { projectId } = req.params; if (!isValidObjectIdString(projectId)) { return res.status(404).json({ error: PROJECT_NOT_FOUND }); diff --git a/packages/api/src/prompts/schemas.ts b/packages/api/src/prompts/schemas.ts index 43cb9d7e94..c9df44bfe7 100644 --- a/packages/api/src/prompts/schemas.ts +++ b/packages/api/src/prompts/schemas.ts @@ -6,7 +6,19 @@ import { Constants } from 'librechat-data-provider'; * Only allows fields that users should be able to modify. * Sensitive fields like author, authorName, _id, productionId, etc. are excluded. */ -export const updatePromptGroupSchema = z +export const updatePromptGroupSchema: z.ZodObject< + { + /** The name of the prompt group */ + name: z.ZodOptional; + /** Short description/oneliner for the prompt group */ + oneliner: z.ZodOptional; + /** Category for organizing prompt groups */ + category: z.ZodOptional; + /** Command shortcut for the prompt group */ + command: z.ZodNullable>; + }, + 'strict' +> = z .object({ /** The name of the prompt group */ name: z.string().min(1).max(255).optional(), @@ -44,6 +56,19 @@ export function validatePromptGroupUpdate(data: unknown): TUpdatePromptGroupSche * @param data - The raw request body to validate * @returns A SafeParseResult with either the validated data or validation errors */ -export function safeValidatePromptGroupUpdate(data: unknown) { +export function safeValidatePromptGroupUpdate(data: unknown): z.SafeParseReturnType< + { + name?: string | undefined; + category?: string | undefined; + command?: string | null | undefined; + oneliner?: string | undefined; + }, + { + name?: string | undefined; + category?: string | undefined; + command?: string | null | undefined; + oneliner?: string | undefined; + } +> { return updatePromptGroupSchema.safeParse(data); } diff --git a/packages/api/src/skills/handlers.ts b/packages/api/src/skills/handlers.ts index 41836b66cd..3dcda1e151 100644 --- a/packages/api/src/skills/handlers.ts +++ b/packages/api/src/skills/handlers.ts @@ -5,19 +5,6 @@ import { PrincipalType, PermissionBits, } from 'librechat-data-provider'; -import type { Response } from 'express'; -import type { Types } from 'mongoose'; -import type { - ISkill, - ISkillFile, - ISkillSummary, - CreateSkillInput, - CreateSkillResult, - UpdateSkillInput, - ListSkillsByAccessResult, - UpdateSkillResult, - ValidationIssue, -} from '@librechat/data-schemas'; import type { TSkill, TSkillFile, @@ -31,6 +18,19 @@ import type { TSkillConflictResponse, TSkillFileContentResponse, } from 'librechat-data-provider'; +import type { + ISkill, + ISkillFile, + ISkillSummary, + CreateSkillInput, + CreateSkillResult, + UpdateSkillInput, + ListSkillsByAccessResult, + UpdateSkillResult, + ValidationIssue, +} from '@librechat/data-schemas'; +import type { Response } from 'express'; +import type { Types } from 'mongoose'; import type { ServerRequest, StrategyFunctions } from '~/types'; import { isBinaryBuffer } from './binary'; @@ -268,7 +268,16 @@ function parseLimit(raw: unknown): number { * deps from `~/models` + `PermissionService`, and wires the returned handlers * onto the Express router. */ -export function createSkillsHandlers(deps: SkillsHandlersDeps) { +export function createSkillsHandlers(deps: SkillsHandlersDeps): { + list: (req: ServerRequest, res: Response) => Promise; + create: (req: ServerRequest, res: Response) => Promise; + get: (req: ServerRequest, res: Response) => Promise; + patch: (req: ServerRequest, res: Response) => Promise; + delete: (req: ServerRequest, res: Response) => Promise; + listFiles: (req: ServerRequest, res: Response) => Promise; + downloadFile: (req: ServerRequest, res: Response) => Promise; + deleteFile: (req: ServerRequest, res: Response) => Promise; +} { const { createSkill, getSkillById, diff --git a/packages/api/src/skills/import.ts b/packages/api/src/skills/import.ts index 190abf3b3d..21a8d190ef 100644 --- a/packages/api/src/skills/import.ts +++ b/packages/api/src/skills/import.ts @@ -1,10 +1,8 @@ -import crypto from 'crypto'; import path from 'path'; import JSZip from 'jszip'; -import { ResourceType, AccessRoleIds, PrincipalType } from 'librechat-data-provider'; +import crypto from 'crypto'; import { logger, stripYamlTrailingComment } from '@librechat/data-schemas'; -import type { Request, Response } from 'express'; -import type { Types } from 'mongoose'; +import { ResourceType, AccessRoleIds, PrincipalType } from 'librechat-data-provider'; import type { ISkill, ISkillFile, @@ -12,6 +10,8 @@ import type { CreateSkillResult, UpsertSkillFileInput, } from '@librechat/data-schemas'; +import type { Request, Response } from 'express'; +import type { Types } from 'mongoose'; import { resolveRequestTenantId } from '~/middleware/tenant'; /** Security limits for zip processing. */ @@ -231,7 +231,7 @@ interface ServerRequest extends Request { * Grants SKILL_OWNER to the uploader. */ export function createImportHandler(deps: ImportSkillDeps) { - return async function importSkillHandler(req: ServerRequest, res: Response) { + return async function importSkillHandler(req: ServerRequest, res: Response): Promise { const { file } = req; if (!file) { return res.status(400).json({ error: 'No file provided' }); diff --git a/packages/api/src/skills/skillStates.ts b/packages/api/src/skills/skillStates.ts index 902debaced..f04ecd6698 100644 --- a/packages/api/src/skills/skillStates.ts +++ b/packages/api/src/skills/skillStates.ts @@ -9,7 +9,7 @@ export const MAX_KEY_LENGTH = 64; * Generous upper bound on raw payload size to reject abusive inputs before * we spend cycles validating or querying the DB for orphan cleanup. */ -export const MAX_RAW_PAYLOAD = MAX_SKILL_STATES * 2; +export const MAX_RAW_PAYLOAD: number = MAX_SKILL_STATES * 2; /** Map of skillId → explicit active state override. */ export type SkillStatesRecord = Record; diff --git a/packages/api/src/storage/s3/s3Config.ts b/packages/api/src/storage/s3/s3Config.ts index 9f7a0ce5eb..5dc374d07d 100644 --- a/packages/api/src/storage/s3/s3Config.ts +++ b/packages/api/src/storage/s3/s3Config.ts @@ -1,6 +1,6 @@ import { logger } from '@librechat/data-schemas'; -import { isEnabled } from '~/utils/common'; import { DEFAULT_BASE_PATH } from '~/storage/constants'; +import { isEnabled } from '~/utils/common'; const MAX_EXPIRY_SECONDS = 7 * 24 * 60 * 60; // 7 days const DEFAULT_EXPIRY_SECONDS = 2 * 60; // 2 minutes @@ -39,7 +39,22 @@ const parseRefreshExpiry = (): number | null => { }; // Internal module config — not part of the public @librechat/api surface -export const s3Config = { +export const s3Config: { + /** AWS region for S3 */ + AWS_REGION: string; + /** S3 bucket name */ + AWS_BUCKET_NAME: string; + /** Custom endpoint URL (for MinIO, R2, etc.) */ + AWS_ENDPOINT_URL: string | undefined; + /** Use path-style URLs instead of virtual-hosted-style */ + AWS_FORCE_PATH_STYLE: boolean; + /** Presigned URL expiry in seconds */ + S3_URL_EXPIRY_SECONDS: number; + /** Custom refresh expiry in milliseconds (null = use default buffer logic) */ + S3_REFRESH_EXPIRY_MS: number | null; + /** Default base path for file storage */ + DEFAULT_BASE_PATH: string; +} = { /** AWS region for S3 */ AWS_REGION: process.env.AWS_REGION ?? '', /** S3 bucket name */ diff --git a/packages/api/src/stream/GenerationJobManager.ts b/packages/api/src/stream/GenerationJobManager.ts index ad41a5e72a..931ec62c0a 100644 --- a/packages/api/src/stream/GenerationJobManager.ts +++ b/packages/api/src/stream/GenerationJobManager.ts @@ -1572,5 +1572,5 @@ class GenerationJobManagerClass { } } -export const GenerationJobManager = new GenerationJobManagerClass(); +export const GenerationJobManager: GenerationJobManagerClass = new GenerationJobManagerClass(); export { GenerationJobManagerClass }; diff --git a/packages/api/src/telemetry.ts b/packages/api/src/telemetry.ts new file mode 100644 index 0000000000..c75db96f11 --- /dev/null +++ b/packages/api/src/telemetry.ts @@ -0,0 +1,6 @@ +/** + * Build entry shim for the `@librechat/api/telemetry` subpath export. + * Re-exports the telemetry barrel under a unique basename so the bundler emits + * stable `dist/telemetry.*` output (see tsdown.config.mjs for details). + */ +export * from './telemetry/index'; diff --git a/packages/api/src/tools/toolkits/gemini.ts b/packages/api/src/tools/toolkits/gemini.ts index 5eb0cec9fd..d147ed6f56 100644 --- a/packages/api/src/tools/toolkits/gemini.ts +++ b/packages/api/src/tools/toolkits/gemini.ts @@ -75,7 +75,15 @@ const geminiImageGenJsonSchema: ExtendedJsonSchema = { required: ['prompt'], }; -export const geminiToolkit = { +export const geminiToolkit: { + readonly gemini_image_gen: { + readonly name: 'gemini_image_gen'; + readonly description: string; + readonly description_for_model: 'Use this tool to generate images from text descriptions using Vertex AI Gemini.\n1. Prompts should be detailed and specific for best results.\n2. One image per function call. Create only 1 image per request.\n3. IMPORTANT: When user asks to "edit", "modify", "change", or "swap" elements in an existing image:\n - ALWAYS include the original image ID in the image_ids array\n - Describe the desired changes clearly in the prompt\n - The tool will generate a new image based on the original image context + your prompt\n4. IMPORTANT: For editing requests, use DIRECT editing instructions:\n - User says "remove the gun" → prompt should be "remove the gun from this image"\n - User says "make it blue" → prompt should be "make this image blue"\n - User says "add sunglasses" → prompt should be "add sunglasses to this image"\n - DO NOT reconstruct or modify the original prompt - use the user\'s editing instruction directly\n - ALWAYS include the image being edited in image_ids array\n5. OPTIONAL: Use image_ids to provide context images that will influence the generation:\n - Include any relevant image IDs from the conversation in the image_ids array\n - These images will be used as visual context/inspiration for the new generation\n - For "editing" requests, always include the image being "edited"\n6. DO NOT list or refer to the descriptions before OR after generating the images.\n7. Always mention the image type (photo, oil painting, watercolor painting, illustration, cartoon, drawing, vector, render, etc.) at the beginning of the prompt.\n8. Use aspectRatio to control the shape of the image:\n - 16:9 or 3:2 for landscape/wide images\n - 9:16 or 2:3 for portrait/tall images\n - 21:9 for ultra-wide/cinematic images\n - 1:1 for square images (default)\n9. Use imageSize to control the resolution: 1K (standard), 2K (high), 4K (maximum quality).\n\nThe prompt should be a detailed paragraph describing every part of the image in concrete, objective detail.'; + readonly schema: ExtendedJsonSchema; + readonly responseFormat: 'content_and_artifact'; + }; +} = { gemini_image_gen: { name: 'gemini_image_gen' as const, description: getGeminiImageGenDescription(), diff --git a/packages/api/src/tools/toolkits/mapping.ts b/packages/api/src/tools/toolkits/mapping.ts index e6cb14d9bc..fc657b98e2 100644 --- a/packages/api/src/tools/toolkits/mapping.ts +++ b/packages/api/src/tools/toolkits/mapping.ts @@ -3,7 +3,9 @@ * When a toolkit key appears in an agent's tool list, * these extra tools should also be included. */ -export const toolkitExpansion = { +export const toolkitExpansion: { + readonly image_gen_oai: readonly ['image_edit_oai']; +} = { image_gen_oai: ['image_edit_oai'], } as const satisfies Readonly>; diff --git a/packages/api/src/tools/toolkits/oai.ts b/packages/api/src/tools/toolkits/oai.ts index 9786b0571d..65c0089124 100644 --- a/packages/api/src/tools/toolkits/oai.ts +++ b/packages/api/src/tools/toolkits/oai.ts @@ -131,7 +131,20 @@ Guidelines: required: ['image_ids', 'prompt'], }; -export const oaiToolkit = { +export const oaiToolkit: { + readonly image_gen_oai: { + readonly name: 'image_gen_oai'; + readonly description: string; + readonly schema: ExtendedJsonSchema; + readonly responseFormat: 'content_and_artifact'; + }; + readonly image_edit_oai: { + readonly name: 'image_edit_oai'; + readonly description: string; + readonly schema: ExtendedJsonSchema; + readonly responseFormat: 'content_and_artifact'; + }; +} = { image_gen_oai: { name: 'image_gen_oai' as const, description: getImageGenDescription(), diff --git a/packages/api/src/utils/axios.ts b/packages/api/src/utils/axios.ts index bab5242d8e..918e841d9e 100644 --- a/packages/api/src/utils/axios.ts +++ b/packages/api/src/utils/axios.ts @@ -1,5 +1,5 @@ -import { Buffer } from 'buffer'; import axios from 'axios'; +import { Buffer } from 'buffer'; import { logger } from '@librechat/data-schemas'; import type { AxiosInstance, AxiosProxyConfig, AxiosError } from 'axios'; @@ -45,7 +45,7 @@ export const logAxiosError = ({ }: { message: string; error: AxiosError | Error | unknown; -}) => { +}): string => { let logMessage = message; try { const stack = diff --git a/packages/api/src/utils/code.ts b/packages/api/src/utils/code.ts index 9ac814a52b..4293ef7f05 100644 --- a/packages/api/src/utils/code.ts +++ b/packages/api/src/utils/code.ts @@ -7,5 +7,5 @@ import https from 'https'; * on Node 19+ (keepAlive: true by default), tainted sockets re-enter the global pool * and kill unrelated requests (e.g., node-fetch in CodeExecutor) after the idle timeout. */ -export const codeServerHttpAgent = new http.Agent({ keepAlive: false }); -export const codeServerHttpsAgent = new https.Agent({ keepAlive: false }); +export const codeServerHttpAgent: http.Agent = new http.Agent({ keepAlive: false }); +export const codeServerHttpsAgent: https.Agent = new https.Agent({ keepAlive: false }); diff --git a/packages/api/src/utils/env.ts b/packages/api/src/utils/env.ts index 61c308682b..316a28afcc 100644 --- a/packages/api/src/utils/env.ts +++ b/packages/api/src/utils/env.ts @@ -503,7 +503,7 @@ export function resolveHeaders(options?: { user?: Partial | { id: string }; body?: RequestBody; customUserVars?: Record; -}) { +}): Record { const { headers, user, body, customUserVars } = options ?? {}; const inputHeaders = headers ?? {}; diff --git a/packages/api/src/utils/generators.ts b/packages/api/src/utils/generators.ts index 902721df34..29ececd4c4 100644 --- a/packages/api/src/utils/generators.ts +++ b/packages/api/src/utils/generators.ts @@ -46,19 +46,23 @@ export function createFetch({ * @param res - The response object to send events to * @returns Object containing handler functions */ -export function createStreamEventHandlers(res: ServerResponse) { +export function createStreamEventHandlers(res: ServerResponse): { + on_run_step: (event: ServerSentEvent) => void; + on_message_delta: (event: ServerSentEvent) => void; + on_reasoning_delta: (event: ServerSentEvent) => void; +} { return { - [GraphEvents.ON_RUN_STEP]: function (event: ServerSentEvent) { + [GraphEvents.ON_RUN_STEP]: function (event: ServerSentEvent): void { if (res) { sendEvent(res, event); } }, - [GraphEvents.ON_MESSAGE_DELTA]: function (event: ServerSentEvent) { + [GraphEvents.ON_MESSAGE_DELTA]: function (event: ServerSentEvent): void { if (res) { sendEvent(res, event); } }, - [GraphEvents.ON_REASONING_DELTA]: function (event: ServerSentEvent) { + [GraphEvents.ON_REASONING_DELTA]: function (event: ServerSentEvent): void { if (res) { sendEvent(res, event); } @@ -67,7 +71,7 @@ export function createStreamEventHandlers(res: ServerResponse) { } export function createHandleLLMNewToken(streamRate: number) { - return async function () { + return async function (): Promise { if (streamRate) { await sleep(streamRate); } diff --git a/packages/api/src/utils/http.ts b/packages/api/src/utils/http.ts index e9f1d62281..9a0b39080b 100644 --- a/packages/api/src/utils/http.ts +++ b/packages/api/src/utils/http.ts @@ -5,7 +5,10 @@ export function normalizeHttpError( err: Error | { status?: number; message?: string } | unknown, fallbackStatus = 400, -) { +): { + status: number; + message: string; +} { let status = fallbackStatus; if (err && typeof err === 'object' && 'status' in err && typeof err.status === 'number') { status = err.status; diff --git a/packages/api/src/utils/memory.ts b/packages/api/src/utils/memory.ts index 214548d14b..4ab8318624 100644 --- a/packages/api/src/utils/memory.ts +++ b/packages/api/src/utils/memory.ts @@ -1,6 +1,6 @@ import { logger } from '@librechat/data-schemas'; -import { GenerationJobManager } from '~/stream'; import { OAuthReconnectionManager } from '~/mcp/oauth/OAuthReconnectionManager'; +import { GenerationJobManager } from '~/stream'; import { MCPManager } from '~/mcp/MCPManager'; type ConnectionStats = ReturnType['getConnectionStats']>; @@ -147,4 +147,10 @@ function stop(): void { logger.info('[MemDiag] Stopped memory diagnostics'); } -export const memoryDiagnostics = { start, stop, forceGC, getSnapshots, collectSnapshot }; +export const memoryDiagnostics: { + start: typeof start; + stop: typeof stop; + forceGC: typeof forceGC; + getSnapshots: typeof getSnapshots; + collectSnapshot: typeof collectSnapshot; +} = { start, stop, forceGC, getSnapshots, collectSnapshot }; diff --git a/packages/api/src/utils/tokenizer.ts b/packages/api/src/utils/tokenizer.ts index 4c638c948e..d66908712a 100644 --- a/packages/api/src/utils/tokenizer.ts +++ b/packages/api/src/utils/tokenizer.ts @@ -45,7 +45,7 @@ class Tokenizer { } } -const TokenizerSingleton = new Tokenizer(); +const TokenizerSingleton: Tokenizer = new Tokenizer(); /** * Counts the number of tokens in a given text using ai-tokenizer with o200k_base encoding. diff --git a/packages/api/src/utils/tokens.ts b/packages/api/src/utils/tokens.ts index fadec1ffe2..4101f7b8c0 100644 --- a/packages/api/src/utils/tokens.ts +++ b/packages/api/src/utils/tokens.ts @@ -361,7 +361,7 @@ const aggregateModels = { ...openAIModels, }; -export const maxTokensMap = { +export const maxTokensMap: Record> = { [EModelEndpoint.azureOpenAI]: openAIModels, [EModelEndpoint.openAI]: aggregateModels, [EModelEndpoint.agents]: aggregateModels, @@ -421,7 +421,7 @@ const deepseekMaxOutputs = { 'deepseek.r1': 64000, }; -export const maxOutputTokensMap = { +export const maxOutputTokensMap: Record> = { [EModelEndpoint.anthropic]: anthropicMaxOutputs, [EModelEndpoint.azureOpenAI]: modelMaxOutputs, [EModelEndpoint.openAI]: { ...modelMaxOutputs, ...deepseekMaxOutputs }, @@ -506,7 +506,7 @@ export function getModelTokenValue( */ export function getModelMaxTokens( modelName: string, - endpoint = EModelEndpoint.openAI, + endpoint: EModelEndpoint = EModelEndpoint.openAI, endpointTokenConfig?: EndpointTokenConfig, ): number | undefined { const tokensMap = endpointTokenConfig ?? maxTokensMap[endpoint as keyof typeof maxTokensMap]; @@ -523,7 +523,7 @@ export function getModelMaxTokens( */ export function getModelMaxOutputTokens( modelName: string, - endpoint = EModelEndpoint.openAI, + endpoint: EModelEndpoint = EModelEndpoint.openAI, endpointTokenConfig?: EndpointTokenConfig, ): number | undefined { const tokensMap = @@ -546,7 +546,7 @@ export function getModelMaxOutputTokens( */ export function matchModelName( modelName: string, - endpoint = EModelEndpoint.openAI, + endpoint: EModelEndpoint = EModelEndpoint.openAI, ): string | undefined { if (typeof modelName !== 'string') { return undefined; @@ -565,7 +565,29 @@ export function matchModelName( return matchedPattern || modelName; } -export const modelSchema = z.object({ +export const modelSchema: z.ZodObject< + { + id: z.ZodString; + pricing: z.ZodObject< + { + prompt: z.ZodString; + completion: z.ZodString; + }, + 'strip', + z.ZodTypeAny, + { + prompt: string; + completion: string; + }, + { + prompt: string; + completion: string; + } + >; + context_length: z.ZodNumber; + }, + 'strip' +> = z.object({ id: z.string(), pricing: z.object({ prompt: z.string(), @@ -574,7 +596,54 @@ export const modelSchema = z.object({ context_length: z.number(), }); -export const inputSchema = z.object({ +export const inputSchema: z.ZodObject< + { + data: z.ZodArray< + z.ZodObject< + { + id: z.ZodString; + pricing: z.ZodObject< + { + prompt: z.ZodString; + completion: z.ZodString; + }, + 'strip', + z.ZodTypeAny, + { + prompt: string; + completion: string; + }, + { + prompt: string; + completion: string; + } + >; + context_length: z.ZodNumber; + }, + 'strip', + z.ZodTypeAny, + { + id: string; + pricing: { + prompt: string; + completion: string; + }; + context_length: number; + }, + { + id: string; + pricing: { + prompt: string; + completion: string; + }; + context_length: number; + } + >, + 'many' + >; + }, + 'strip' +> = z.object({ data: z.array(modelSchema), }); diff --git a/packages/api/src/utils/yaml.ts b/packages/api/src/utils/yaml.ts index 50ea0bd4de..ed237b81dd 100644 --- a/packages/api/src/utils/yaml.ts +++ b/packages/api/src/utils/yaml.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import yaml from 'js-yaml'; -export function loadYaml(filepath: string) { +export function loadYaml(filepath: string): unknown { try { const fileContents = fs.readFileSync(filepath, 'utf8'); return yaml.load(fileContents); diff --git a/packages/api/tsdown.config.mjs b/packages/api/tsdown.config.mjs new file mode 100644 index 0000000000..67a8c12136 --- /dev/null +++ b/packages/api/tsdown.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + // The telemetry entry is a thin shim (`src/telemetry.ts`) rather than the + // `src/telemetry/index.ts` barrel: oxc emits declarations flat into outDir keyed + // by source basename, so two `index.ts` entries would collide (index.d.cts + + // index2.d.cts). Distinct basenames yield stable `index.*` / `telemetry.*` output. + entry: ['src/index.ts', 'src/telemetry.ts'], + format: ['cjs'], + platform: 'node', + dts: { oxc: true }, + outDir: 'dist', + sourcemap: true, + // Externalize every third-party dependency (consumers provide the peers) and bundle + // only first-party code: relative imports and the `~/*` tsconfig alias (-> src). + // `neverBundle` is the 0.22 replacement for the deprecated `external` option. + deps: { + neverBundle: (id) => !id.startsWith('.') && !id.startsWith('~') && !id.startsWith('/'), + }, +});