@lobehub/chat 1.19.36 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.20.1](https://github.com/lobehub/lobe-chat/compare/v1.20.0...v1.20.1)
6
+
7
+ <sup>Released on **2024-09-27**</sup>
8
+
9
+ <br/>
10
+
11
+ <details>
12
+ <summary><kbd>Improvements and Fixes</kbd></summary>
13
+
14
+ </details>
15
+
16
+ <div align="right">
17
+
18
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
19
+
20
+ </div>
21
+
22
+ ## [Version 1.20.0](https://github.com/lobehub/lobe-chat/compare/v1.19.36...v1.20.0)
23
+
24
+ <sup>Released on **2024-09-27**</sup>
25
+
26
+ #### ✨ Features
27
+
28
+ - **misc**: Add Hunyuan(Tencent) model provider.
29
+
30
+ <br/>
31
+
32
+ <details>
33
+ <summary><kbd>Improvements and Fixes</kbd></summary>
34
+
35
+ #### What's improved
36
+
37
+ - **misc**: Add Hunyuan(Tencent) model provider, closes [#4147](https://github.com/lobehub/lobe-chat/issues/4147) ([8ddb41b](https://github.com/lobehub/lobe-chat/commit/8ddb41b))
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ### [Version 1.19.36](https://github.com/lobehub/lobe-chat/compare/v1.19.35...v1.19.36)
6
48
 
7
49
  <sup>Released on **2024-09-27**</sup>
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- ## Base image for all the stages
1
+ ## Base image for all building stages
2
2
  FROM node:20-slim AS base
3
3
 
4
4
  ARG USE_CN_MIRROR
@@ -10,19 +10,22 @@ RUN \
10
10
  if [ "${USE_CN_MIRROR:-false}" = "true" ]; then \
11
11
  sed -i "s/deb.debian.org/mirrors.ustc.edu.cn/g" "/etc/apt/sources.list.d/debian.sources"; \
12
12
  fi \
13
- # Add required package & update base package
13
+ # Add required package
14
14
  && apt update \
15
- && apt install busybox proxychains-ng -qy \
16
- && apt full-upgrade -qy \
17
- && apt autoremove -qy --purge \
18
- && apt clean -qy \
19
- # Configure BusyBox
20
- && busybox --install -s \
21
- # Add nextjs:nodejs to run the app
22
- && addgroup --system --gid 1001 nodejs \
23
- && adduser --system --home "/app" --gid 1001 -uid 1001 nextjs \
24
- # Set permission for nextjs:nodejs
25
- && chown -R nextjs:nodejs "/etc/proxychains4.conf" \
15
+ && apt install ca-certificates proxychains-ng -qy \
16
+ # Prepare required package to distroless
17
+ && mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib \
18
+ # Copy proxychains to distroless
19
+ && cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4 \
20
+ && cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2 \
21
+ && cp /usr/bin/proxychains4 /distroless/bin/proxychains \
22
+ && cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf \
23
+ # Copy node to distroless
24
+ && cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 \
25
+ && cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 \
26
+ && cp /usr/local/bin/node /distroless/bin/node \
27
+ # Copy CA certificates to distroless
28
+ && cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt \
26
29
  # Cleanup temp files
27
30
  && rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*
28
31
 
@@ -80,7 +83,9 @@ COPY . .
80
83
  RUN npm run build:docker
81
84
 
82
85
  ## Application image, copy all the files for production
83
- FROM scratch AS app
86
+ FROM busybox:latest AS app
87
+
88
+ COPY --from=base /distroless/ /
84
89
 
85
90
  COPY --from=builder /app/public /app/public
86
91
 
@@ -90,13 +95,25 @@ COPY --from=builder /app/.next/standalone /app/
90
95
  COPY --from=builder /app/.next/static /app/.next/static
91
96
  COPY --from=builder /deps/node_modules/.pnpm /app/node_modules/.pnpm
92
97
 
98
+ # Copy server launcher
99
+ COPY --from=builder /app/scripts/serverLauncher/startServer.js /app/startServer.js
100
+
101
+ RUN \
102
+ # Add nextjs:nodejs to run the app
103
+ addgroup -S -g 1001 nodejs \
104
+ && adduser -D -G nodejs -H -S -h /app -u 1001 nextjs \
105
+ # Set permission for nextjs:nodejs
106
+ && chown -R nextjs:nodejs /app /etc/proxychains4.conf
107
+
93
108
  ## Production image, copy all the files and run next
94
- FROM base
109
+ FROM scratch
95
110
 
96
111
  # Copy all the files from app, set the correct permission for prerender cache
97
- COPY --from=app --chown=nextjs:nodejs /app /app
112
+ COPY --from=app / /
98
113
 
99
114
  ENV NODE_ENV="production" \
115
+ NODE_OPTIONS="--use-openssl-ca" \
116
+ NODE_EXTRA_CA_CERTS="/etc/ssl/certs/ca-certificates.crt"
100
117
  NODE_TLS_REJECT_UNAUTHORIZED=""
101
118
 
102
119
  # set hostname to localhost
@@ -135,6 +152,8 @@ ENV \
135
152
  GOOGLE_API_KEY="" GOOGLE_PROXY_URL="" \
136
153
  # Groq
137
154
  GROQ_API_KEY="" GROQ_MODEL_LIST="" GROQ_PROXY_URL="" \
155
+ # Hunyuan
156
+ HUNYUAN_API_KEY="" HUNYUAN_MODEL_LIST="" \
138
157
  # Minimax
139
158
  MINIMAX_API_KEY="" \
140
159
  # Mistral
@@ -174,36 +193,6 @@ USER nextjs
174
193
 
175
194
  EXPOSE 3210/tcp
176
195
 
177
- CMD \
178
- if [ -n "$PROXY_URL" ]; then \
179
- # Set regex for IPv4
180
- IP_REGEX="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$"; \
181
- # Set proxychains command
182
- PROXYCHAINS="proxychains -q"; \
183
- # Parse the proxy URL
184
- host_with_port="${PROXY_URL#*//}"; \
185
- host="${host_with_port%%:*}"; \
186
- port="${PROXY_URL##*:}"; \
187
- protocol="${PROXY_URL%%://*}"; \
188
- # Resolve to IP address if the host is a domain
189
- if ! [[ "$host" =~ "$IP_REGEX" ]]; then \
190
- nslookup=$(nslookup -q="A" "$host" | tail -n +3 | grep 'Address:'); \
191
- if [ -n "$nslookup" ]; then \
192
- host=$(echo "$nslookup" | tail -n 1 | awk '{print $2}'); \
193
- fi; \
194
- fi; \
195
- # Generate proxychains configuration file
196
- printf "%s\n" \
197
- 'localnet 127.0.0.0/255.0.0.0' \
198
- 'localnet ::1/128' \
199
- 'proxy_dns' \
200
- 'remote_dns_subnet 224' \
201
- 'strict_chain' \
202
- 'tcp_connect_time_out 8000' \
203
- 'tcp_read_time_out 15000' \
204
- '[ProxyList]' \
205
- "$protocol $host $port" \
206
- > "/etc/proxychains4.conf"; \
207
- fi; \
208
- # Run the server
209
- ${PROXYCHAINS} node "/app/server.js";
196
+ ENTRYPOINT ["/bin/node"]
197
+
198
+ CMD ["/app/startServer.js"]
@@ -1,4 +1,4 @@
1
- ## Base image for all the stages
1
+ ## Base image for all building stages
2
2
  FROM node:20-slim AS base
3
3
 
4
4
  ARG USE_CN_MIRROR
@@ -10,19 +10,22 @@ RUN \
10
10
  if [ "${USE_CN_MIRROR:-false}" = "true" ]; then \
11
11
  sed -i "s/deb.debian.org/mirrors.ustc.edu.cn/g" "/etc/apt/sources.list.d/debian.sources"; \
12
12
  fi \
13
- # Add required package & update base package
13
+ # Add required package
14
14
  && apt update \
15
- && apt install busybox proxychains-ng -qy \
16
- && apt full-upgrade -qy \
17
- && apt autoremove -qy --purge \
18
- && apt clean -qy \
19
- # Configure BusyBox
20
- && busybox --install -s \
21
- # Add nextjs:nodejs to run the app
22
- && addgroup --system --gid 1001 nodejs \
23
- && adduser --system --home "/app" --gid 1001 -uid 1001 nextjs \
24
- # Set permission for nextjs:nodejs
25
- && chown -R nextjs:nodejs "/etc/proxychains4.conf" \
15
+ && apt install ca-certificates proxychains-ng -qy \
16
+ # Prepare required package to distroless
17
+ && mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib \
18
+ # Copy proxychains to distroless
19
+ && cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4 \
20
+ && cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2 \
21
+ && cp /usr/bin/proxychains4 /distroless/bin/proxychains \
22
+ && cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf \
23
+ # Copy node to distroless
24
+ && cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 \
25
+ && cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 \
26
+ && cp /usr/local/bin/node /distroless/bin/node \
27
+ # Copy CA certificates to distroless
28
+ && cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt \
26
29
  # Cleanup temp files
27
30
  && rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*
28
31
 
@@ -84,7 +87,9 @@ COPY . .
84
87
  RUN npm run build:docker
85
88
 
86
89
  ## Application image, copy all the files for production
87
- FROM scratch AS app
90
+ FROM busybox:latest AS app
91
+
92
+ COPY --from=base /distroless/ /
88
93
 
89
94
  COPY --from=builder /app/public /app/public
90
95
 
@@ -103,13 +108,25 @@ COPY --from=builder /app/src/database/server/migrations /app/migrations
103
108
  COPY --from=builder /app/scripts/migrateServerDB/docker.cjs /app/docker.cjs
104
109
  COPY --from=builder /app/scripts/migrateServerDB/errorHint.js /app/errorHint.js
105
110
 
111
+ # Copy server launcher
112
+ COPY --from=builder /app/scripts/serverLauncher/startServer.js /app/startServer.js
113
+
114
+ RUN \
115
+ # Add nextjs:nodejs to run the app
116
+ addgroup -S -g 1001 nodejs \
117
+ && adduser -D -G nodejs -H -S -h /app -u 1001 nextjs \
118
+ # Set permission for nextjs:nodejs
119
+ && chown -R nextjs:nodejs /app /etc/proxychains4.conf
120
+
106
121
  ## Production image, copy all the files and run next
107
- FROM base
122
+ FROM scratch
108
123
 
109
124
  # Copy all the files from app, set the correct permission for prerender cache
110
- COPY --from=app --chown=nextjs:nodejs /app /app
125
+ COPY --from=app / /
111
126
 
112
127
  ENV NODE_ENV="production" \
128
+ NODE_OPTIONS="--use-openssl-ca" \
129
+ NODE_EXTRA_CA_CERTS="/etc/ssl/certs/ca-certificates.crt"
113
130
  NODE_TLS_REJECT_UNAUTHORIZED=""
114
131
 
115
132
  # set hostname to localhost
@@ -167,6 +184,8 @@ ENV \
167
184
  GOOGLE_API_KEY="" GOOGLE_PROXY_URL="" \
168
185
  # Groq
169
186
  GROQ_API_KEY="" GROQ_MODEL_LIST="" GROQ_PROXY_URL="" \
187
+ # Hunyuan
188
+ HUNYUAN_API_KEY="" HUNYUAN_MODEL_LIST="" \
170
189
  # Minimax
171
190
  MINIMAX_API_KEY="" \
172
191
  # Mistral
@@ -206,40 +225,6 @@ USER nextjs
206
225
 
207
226
  EXPOSE 3210/tcp
208
227
 
209
- CMD \
210
- if [ -n "$PROXY_URL" ]; then \
211
- # Set regex for IPv4
212
- IP_REGEX="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$"; \
213
- # Set proxychains command
214
- PROXYCHAINS="proxychains -q"; \
215
- # Parse the proxy URL
216
- host_with_port="${PROXY_URL#*//}"; \
217
- host="${host_with_port%%:*}"; \
218
- port="${PROXY_URL##*:}"; \
219
- protocol="${PROXY_URL%%://*}"; \
220
- # Resolve to IP address if the host is a domain
221
- if ! [[ "$host" =~ "$IP_REGEX" ]]; then \
222
- nslookup=$(nslookup -q="A" "$host" | tail -n +3 | grep 'Address:'); \
223
- if [ -n "$nslookup" ]; then \
224
- host=$(echo "$nslookup" | tail -n 1 | awk '{print $2}'); \
225
- fi; \
226
- fi; \
227
- # Generate proxychains configuration file
228
- printf "%s\n" \
229
- 'localnet 127.0.0.0/255.0.0.0' \
230
- 'localnet ::1/128' \
231
- 'proxy_dns' \
232
- 'remote_dns_subnet 224' \
233
- 'strict_chain' \
234
- 'tcp_connect_time_out 8000' \
235
- 'tcp_read_time_out 15000' \
236
- '[ProxyList]' \
237
- "$protocol $host $port" \
238
- > "/etc/proxychains4.conf"; \
239
- fi; \
240
- # Run migration
241
- node "/app/docker.cjs"; \
242
- if [ "$?" -eq "0" ]; then \
243
- # Run the server
244
- ${PROXYCHAINS} node "/app/server.js"; \
245
- fi;
228
+ ENTRYPOINT ["/bin/node"]
229
+
230
+ CMD ["/app/startServer.js"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.19.36",
3
+ "version": "1.20.1",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,181 @@
1
+ const dns = require('dns').promises;
2
+ const fs = require('fs');
3
+ const tls = require('tls');
4
+ const { spawn } = require('child_process');
5
+
6
+ // Set file paths
7
+ const DB_MIGRATION_SCRIPT_PATH = '/app/docker.cjs';
8
+ const SERVER_SCRIPT_PATH = '/app/server.js';
9
+ const PROXYCHAINS_CONF_PATH = '/etc/proxychains4.conf';
10
+
11
+ // Function to check if a string is a valid IP address
12
+ const isValidIP = (ip, version = 4) => {
13
+ const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/;
14
+ const ipv6Regex = /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:)|fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
15
+
16
+ switch (version) {
17
+ case 4:
18
+ return ipv4Regex.test(ip);
19
+ case 6:
20
+ return ipv6Regex.test(ip);
21
+ default:
22
+ return ipv4Regex.test(ip) || ipv6Regex.test(ip);
23
+ }
24
+ };
25
+
26
+ // Function to check TLS validity of a URL
27
+ const isValidTLS = (url = '') => {
28
+ if (!url) {
29
+ console.log('⚠️ TLS Check: No URL provided. Skipping TLS check. Ensure correct setting ENV.');
30
+ console.log('-------------------------------------');
31
+ return Promise.resolve();
32
+ }
33
+
34
+ const { protocol, host, port } = parseUrl(url);
35
+ if (protocol !== 'https') {
36
+ console.log(`⚠️ TLS Check: Non-HTTPS protocol (${protocol}). Skipping TLS check for ${url}.`);
37
+ console.log('-------------------------------------');
38
+ return Promise.resolve();
39
+ }
40
+
41
+ const options = { host, port, servername: host };
42
+ return new Promise((resolve, reject) => {
43
+ const socket = tls.connect(options, () => {
44
+ if (socket.authorized) {
45
+ console.log(`✅ TLS Check: Valid certificate for ${host}:${port}.`);
46
+ console.log('-------------------------------------');
47
+ resolve();
48
+ }
49
+ socket.end();
50
+ });
51
+
52
+ socket.on('error', (err) => {
53
+ const errMsg = `❌ TLS Check: Error for ${host}:${port}. Details:`;
54
+ switch (err.code) {
55
+ case 'CERT_HAS_EXPIRED':
56
+ case 'DEPTH_ZERO_SELF_SIGNED_CERT':
57
+ console.error(`${errMsg} Certificate is not valid. Consider setting NODE_TLS_REJECT_UNAUTHORIZED="0" or mapping /etc/ssl/certs/ca-certificates.crt.`);
58
+ break;
59
+ case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY':
60
+ console.error(`${errMsg} Unable to verify issuer. Ensure correct mapping of /etc/ssl/certs/ca-certificates.crt.`);
61
+ break;
62
+ default:
63
+ console.error(`${errMsg} Network issue. Check firewall or DNS.`);
64
+ break;
65
+ }
66
+ reject(err);
67
+ });
68
+ });
69
+ };
70
+
71
+ // Function to check TLS connections for OSS and Auth Issuer
72
+ const checkTLSConnections = async () => {
73
+ await Promise.all([
74
+ isValidTLS(process.env.S3_ENDPOINT),
75
+ isValidTLS(process.env.S3_PUBLIC_DOMAIN),
76
+ isValidTLS(getEnvVarsByKeyword('_ISSUER')),
77
+ ]);
78
+ };
79
+
80
+ // Function to get environment variable by keyword
81
+ const getEnvVarsByKeyword = (keyword) => {
82
+ return Object.entries(process.env)
83
+ .filter(([key, value]) => key.includes(keyword) && value)
84
+ .map(([, value]) => value)[0] || null;
85
+ };
86
+
87
+ // Function to parse protocol, host and port from a URL
88
+ const parseUrl = (url) => {
89
+ const { protocol, hostname: host, port } = new URL(url);
90
+ return { protocol: protocol.replace(':', ''), host, port: port || 443 };
91
+ };
92
+
93
+ // Function to resolve host IP via DNS
94
+ const resolveHostIP = async (host, version = 4) => {
95
+ try {
96
+ const { address } = await dns.lookup(host, { family: version });
97
+
98
+ if (!isValidIP(address, version)) {
99
+ console.error(`❌ DNS Error: Invalid resolved IP: ${address}. IP address must be IPv${version}.`);
100
+ process.exit(1);
101
+ }
102
+
103
+ return address;
104
+ } catch (err) {
105
+ console.error(`❌ DNS Error: Could not resolve ${host}. Check DNS server.`, err);
106
+ process.exit(1);
107
+ }
108
+ };
109
+
110
+ // Function to generate proxychains configuration
111
+ const runProxyChainsConfGenerator = async (url) => {
112
+ const { protocol, host, port } = parseUrl(url);
113
+
114
+ if (!['http', 'socks4', 'socks5'].includes(protocol)) {
115
+ console.error(`❌ ProxyChains: Invalid protocol (${protocol}). Protocol must be 'http', 'socks4' and 'socks5'.`);
116
+ process.exit(1);
117
+ }
118
+
119
+ const validPort = parseInt(port, 10);
120
+ if (isNaN(validPort) || validPort <= 0 || validPort > 65535) {
121
+ console.error(`❌ ProxyChains: Invalid port (${port}). Port must be a number between 1 and 65535.`);
122
+ process.exit(1);
123
+ }
124
+
125
+ let ip = isValidIP(host, 4) ? host : await resolveHostIP(host, 4);
126
+
127
+ const configContent = `
128
+ localnet 127.0.0.0/255.0.0.0
129
+ localnet ::1/128
130
+ proxy_dns
131
+ remote_dns_subnet 224
132
+ strict_chain
133
+ tcp_connect_time_out 8000
134
+ tcp_read_time_out 15000
135
+ [ProxyList]
136
+ ${protocol} ${ip} ${port}
137
+ `.trim();
138
+
139
+ fs.writeFileSync(PROXYCHAINS_CONF_PATH, configContent);
140
+ console.log(`✅ ProxyChains: All outgoing traffic routed via ${protocol}://${ip}:${port}.`);
141
+ console.log('-------------------------------------');
142
+ };
143
+
144
+ // Function to execute a script with child process spawn
145
+ const runScript = (scriptPath, useProxy = false) => {
146
+ const command = useProxy ? ['/bin/proxychains', '-q', '/bin/node', scriptPath] : ['/bin/node', scriptPath];
147
+ return new Promise((resolve, reject) => {
148
+ const process = spawn(command.shift(), command, { stdio: 'inherit' });
149
+ process.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`🔴 Process exited with code ${code}`))));
150
+ });
151
+ };
152
+
153
+ // Main function to run the server with optional proxy
154
+ const runServer = async () => {
155
+ const PROXY_URL = process.env.PROXY_URL || ''; // Default empty string to avoid undefined errors
156
+
157
+ if (PROXY_URL) {
158
+ await runProxyChainsConfGenerator(PROXY_URL);
159
+ return runScript(SERVER_SCRIPT_PATH, true);
160
+ }
161
+ return runScript(SERVER_SCRIPT_PATH);
162
+ };
163
+
164
+ // Main execution block
165
+ (async () => {
166
+ console.log('🌐 DNS Server:', dns.getServers());
167
+ console.log('-------------------------------------');
168
+
169
+ if (process.env.DATABASE_DRIVER) {
170
+ try {
171
+ await runScript(DB_MIGRATION_SCRIPT_PATH);
172
+ await checkTLSConnections();
173
+ } catch (err) {
174
+ console.error('❌ Error during DB migration or TLS connection check:', err);
175
+ process.exit(1);
176
+ }
177
+ }
178
+
179
+ // Run the server in either database or non-database mode
180
+ await runServer();
181
+ })();
@@ -9,6 +9,7 @@ import {
9
9
  FireworksAIProviderCard,
10
10
  GoogleProviderCard,
11
11
  GroqProviderCard,
12
+ HunyuanProviderCard,
12
13
  MinimaxProviderCard,
13
14
  MistralProviderCard,
14
15
  MoonshotProviderCard,
@@ -60,6 +61,7 @@ export const useProviderList = (): ProviderItem[] => {
60
61
  Ai21ProviderCard,
61
62
  UpstageProviderCard,
62
63
  QwenProviderCard,
64
+ HunyuanProviderCard,
63
65
  SparkProviderCard,
64
66
  ZhiPuProviderCard,
65
67
  ZeroOneProviderCard,
@@ -244,6 +244,13 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
244
244
 
245
245
  const apiKey = apiKeyManager.pick(payload?.apiKey || AI21_API_KEY);
246
246
 
247
+ return { apiKey };
248
+ }
249
+ case ModelProvider.Hunyuan: {
250
+ const { HUNYUAN_API_KEY } = getLLMConfig();
251
+
252
+ const apiKey = apiKeyManager.pick(payload?.apiKey || HUNYUAN_API_KEY);
253
+
247
254
  return { apiKey };
248
255
  }
249
256
  }
package/src/config/llm.ts CHANGED
@@ -117,6 +117,10 @@ export const getLLMConfig = () => {
117
117
 
118
118
  ENABLED_AI21: z.boolean(),
119
119
  AI21_API_KEY: z.string().optional(),
120
+
121
+ ENABLED_HUNYUAN: z.boolean(),
122
+ HUNYUAN_API_KEY: z.string().optional(),
123
+ HUNYUAN_MODEL_LIST: z.string().optional(),
120
124
  },
121
125
  runtimeEnv: {
122
126
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -231,6 +235,10 @@ export const getLLMConfig = () => {
231
235
 
232
236
  ENABLED_AI21: !!process.env.AI21_API_KEY,
233
237
  AI21_API_KEY: process.env.AI21_API_KEY,
238
+
239
+ ENABLED_HUNYUAN: !!process.env.HUNYUAN_API_KEY,
240
+ HUNYUAN_API_KEY: process.env.HUNYUAN_API_KEY,
241
+ HUNYUAN_MODEL_LIST: process.env.HUNYUAN_MODEL_LIST,
234
242
  },
235
243
  });
236
244
  };
@@ -0,0 +1,137 @@
1
+ import { ModelProviderCard } from '@/types/llm';
2
+
3
+ // ref https://cloud.tencent.com/document/product/1729/104753
4
+ const Hunyuan: ModelProviderCard = {
5
+ chatModels: [
6
+ {
7
+ description: '升级为 MOE 结构,上下文窗口为 256k ,在 NLP,代码,数学,行业等多项评测集上领先众多开源模型。',
8
+ displayName: 'Hunyuan Lite',
9
+ enabled: true,
10
+ id: 'hunyuan-lite',
11
+ maxOutput: 6000,
12
+ pricing: {
13
+ currency: 'CNY',
14
+ input: 0,
15
+ output: 0,
16
+ },
17
+ tokens: 256_000,
18
+ },
19
+ {
20
+ description: '采用更优的路由策略,同时缓解了负载均衡和专家趋同的问题。长文方面,大海捞针指标达到99.9%。MOE-32K 性价比相对更高,在平衡效果、价格的同时,可对实现对长文本输入的处理。',
21
+ displayName: 'Hunyuan Standard',
22
+ enabled: true,
23
+ id: 'hunyuan-standard',
24
+ maxOutput: 2000,
25
+ pricing: {
26
+ currency: 'CNY',
27
+ input: 4.5,
28
+ output: 5,
29
+ },
30
+ tokens: 32_000,
31
+ },
32
+ {
33
+ description: '采用更优的路由策略,同时缓解了负载均衡和专家趋同的问题。长文方面,大海捞针指标达到99.9%。MOE-256K 在长度和效果上进一步突破,极大的扩展了可输入长度。',
34
+ displayName: 'Hunyuan Standard 256K',
35
+ enabled: true,
36
+ id: 'hunyuan-standard-256K',
37
+ maxOutput: 6000,
38
+ pricing: {
39
+ currency: 'CNY',
40
+ input: 15,
41
+ output: 60,
42
+ },
43
+ tokens: 256_000,
44
+ },
45
+ {
46
+ description: '混元全新一代大语言模型的预览版,采用全新的混合专家模型(MoE)结构,相比hunyuan-pro推理效率更快,效果表现更强。',
47
+ displayName: 'Hunyuan Turbo',
48
+ enabled: true,
49
+ functionCall: true,
50
+ id: 'hunyuan-turbo',
51
+ maxOutput: 4000,
52
+ pricing: {
53
+ currency: 'CNY',
54
+ input: 15,
55
+ output: 50,
56
+ },
57
+ tokens: 32_000,
58
+ },
59
+ {
60
+ description: '万亿级参数规模 MOE-32K 长文模型。在各种 benchmark 上达到绝对领先的水平,复杂指令和推理,具备复杂数学能力,支持 functioncall,在多语言翻译、金融法律医疗等领域应用重点优化。',
61
+ displayName: 'Hunyuan Pro',
62
+ enabled: true,
63
+ functionCall: true,
64
+ id: 'hunyuan-pro',
65
+ maxOutput: 4000,
66
+ pricing: {
67
+ currency: 'CNY',
68
+ input: 30,
69
+ output: 100,
70
+ },
71
+ tokens: 32_000,
72
+ },
73
+ {
74
+ description: '混元最新代码生成模型,经过 200B 高质量代码数据增训基座模型,迭代半年高质量 SFT 数据训练,上下文长窗口长度增大到 8K,五大语言代码生成自动评测指标上位居前列;五大语言10项考量各方面综合代码任务人工高质量评测上,性能处于第一梯队',
75
+ displayName: 'Hunyuan Code',
76
+ enabled: true,
77
+ id: 'hunyuan-code',
78
+ maxOutput: 4000,
79
+ pricing: {
80
+ currency: 'CNY',
81
+ input: 4,
82
+ output: 8,
83
+ },
84
+ tokens: 8000,
85
+ },
86
+ {
87
+ description: '混元最新多模态模型,支持图片+文本输入生成文本内容。',
88
+ displayName: 'Hunyuan Vision',
89
+ enabled: true,
90
+ id: 'hunyuan-vision',
91
+ maxOutput: 4000,
92
+ pricing: {
93
+ currency: 'CNY',
94
+ input: 18,
95
+ output: 18,
96
+ },
97
+ tokens: 8000,
98
+ vision: true,
99
+ },
100
+ {
101
+ description: '混元最新 MOE 架构 FunctionCall 模型,经过高质量的 FunctionCall 数据训练,上下文窗口达 32K,在多个维度的评测指标上处于领先。',
102
+ displayName: 'Hunyuan FunctionCall',
103
+ functionCall: true,
104
+ id: 'hunyuan-functioncall',
105
+ maxOutput: 4000,
106
+ pricing: {
107
+ currency: 'CNY',
108
+ input: 4,
109
+ output: 8,
110
+ },
111
+ tokens: 32_000,
112
+ },
113
+ {
114
+ description: '混元最新版角色扮演模型,混元官方精调训练推出的角色扮演模型,基于混元模型结合角色扮演场景数据集进行增训,在角色扮演场景具有更好的基础效果。',
115
+ displayName: 'Hunyuan Role',
116
+ id: 'hunyuan-role',
117
+ maxOutput: 4000,
118
+ pricing: {
119
+ currency: 'CNY',
120
+ input: 4,
121
+ output: 8,
122
+ },
123
+ tokens: 8000,
124
+ },
125
+ ],
126
+ checkModel: 'hunyuan-lite',
127
+ description:
128
+ '由腾讯研发的大语言模型,具备强大的中文创作能力,复杂语境下的逻辑推理能力,以及可靠的任务执行能力',
129
+ disableBrowserRequest: true,
130
+ id: 'hunyuan',
131
+ modelList: { showModelFetcher: true },
132
+ modelsUrl: 'https://cloud.tencent.com/document/product/1729/104753',
133
+ name: 'Hunyuan',
134
+ url: 'https://hunyuan.tencent.com',
135
+ };
136
+
137
+ export default Hunyuan;
@@ -11,6 +11,7 @@ import FireworksAIProvider from './fireworksai';
11
11
  import GithubProvider from './github';
12
12
  import GoogleProvider from './google';
13
13
  import GroqProvider from './groq';
14
+ import HunyuanProvider from './hunyuan';
14
15
  import MinimaxProvider from './minimax';
15
16
  import MistralProvider from './mistral';
16
17
  import MoonshotProvider from './moonshot';
@@ -57,6 +58,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
57
58
  UpstageProvider.chatModels,
58
59
  SparkProvider.chatModels,
59
60
  Ai21Provider.chatModels,
61
+ HunyuanProvider.chatModels,
60
62
  ].flat();
61
63
 
62
64
  export const DEFAULT_MODEL_PROVIDER_LIST = [
@@ -78,6 +80,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
78
80
  Ai21Provider,
79
81
  UpstageProvider,
80
82
  QwenProvider,
83
+ HunyuanProvider,
81
84
  SparkProvider,
82
85
  ZhiPuProvider,
83
86
  ZeroOneProvider,
@@ -110,6 +113,7 @@ export { default as FireworksAIProviderCard } from './fireworksai';
110
113
  export { default as GithubProviderCard } from './github';
111
114
  export { default as GoogleProviderCard } from './google';
112
115
  export { default as GroqProviderCard } from './groq';
116
+ export { default as HunyuanProviderCard } from './hunyuan';
113
117
  export { default as MinimaxProviderCard } from './minimax';
114
118
  export { default as MistralProviderCard } from './mistral';
115
119
  export { default as MoonshotProviderCard } from './moonshot';
@@ -9,6 +9,7 @@ import {
9
9
  GithubProviderCard,
10
10
  GoogleProviderCard,
11
11
  GroqProviderCard,
12
+ HunyuanProviderCard,
12
13
  MinimaxProviderCard,
13
14
  MistralProviderCard,
14
15
  MoonshotProviderCard,
@@ -75,6 +76,10 @@ export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
75
76
  enabled: false,
76
77
  enabledModels: filterEnabledModels(GroqProviderCard),
77
78
  },
79
+ hunyuan: {
80
+ enabled: false,
81
+ enabledModels: filterEnabledModels(HunyuanProviderCard),
82
+ },
78
83
  minimax: {
79
84
  enabled: false,
80
85
  enabledModels: filterEnabledModels(MinimaxProviderCard),
@@ -14,6 +14,7 @@ import { LobeFireworksAI } from './fireworksai';
14
14
  import { LobeGithubAI } from './github';
15
15
  import { LobeGoogleAI } from './google';
16
16
  import { LobeGroq } from './groq';
17
+ import { LobeHunyuanAI } from './hunyuan';
17
18
  import { LobeMinimaxAI } from './minimax';
18
19
  import { LobeMistralAI } from './mistral';
19
20
  import { LobeMoonshotAI } from './moonshot';
@@ -133,6 +134,7 @@ class AgentRuntime {
133
134
  github: Partial<ClientOptions>;
134
135
  google: { apiKey?: string; baseURL?: string };
135
136
  groq: Partial<ClientOptions>;
137
+ hunyuan: Partial<ClientOptions>;
136
138
  minimax: Partial<ClientOptions>;
137
139
  mistral: Partial<ClientOptions>;
138
140
  moonshot: Partial<ClientOptions>;
@@ -300,6 +302,11 @@ class AgentRuntime {
300
302
  runtimeModel = new LobeAi21AI(params.ai21);
301
303
  break;
302
304
  }
305
+
306
+ case ModelProvider.Hunyuan: {
307
+ runtimeModel = new LobeHunyuanAI(params.hunyuan);
308
+ break;
309
+ }
303
310
  }
304
311
 
305
312
  return new AgentRuntime(runtimeModel);
@@ -0,0 +1,255 @@
1
+ // @vitest-environment node
2
+ import OpenAI from 'openai';
3
+ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import {
6
+ ChatStreamCallbacks,
7
+ LobeOpenAICompatibleRuntime,
8
+ ModelProvider,
9
+ } from '@/libs/agent-runtime';
10
+
11
+ import * as debugStreamModule from '../utils/debugStream';
12
+ import { LobeHunyuanAI } from './index';
13
+
14
+ const provider = ModelProvider.Hunyuan;
15
+ const defaultBaseURL = 'https://api.hunyuan.cloud.tencent.com/v1';
16
+
17
+ const bizErrorType = 'ProviderBizError';
18
+ const invalidErrorType = 'InvalidProviderAPIKey';
19
+
20
+ // Mock the console.error to avoid polluting test output
21
+ vi.spyOn(console, 'error').mockImplementation(() => {});
22
+
23
+ let instance: LobeOpenAICompatibleRuntime;
24
+
25
+ beforeEach(() => {
26
+ instance = new LobeHunyuanAI({ apiKey: 'test' });
27
+
28
+ // 使用 vi.spyOn 来模拟 chat.completions.create 方法
29
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
30
+ new ReadableStream() as any,
31
+ );
32
+ });
33
+
34
+ afterEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ describe('LobeHunyuanAI', () => {
39
+ describe('init', () => {
40
+ it('should correctly initialize with an API key', async () => {
41
+ const instance = new LobeHunyuanAI({ apiKey: 'test_api_key' });
42
+ expect(instance).toBeInstanceOf(LobeHunyuanAI);
43
+ expect(instance.baseURL).toEqual(defaultBaseURL);
44
+ });
45
+ });
46
+
47
+ describe('chat', () => {
48
+ describe('Error', () => {
49
+ it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
50
+ // Arrange
51
+ const apiError = new OpenAI.APIError(
52
+ 400,
53
+ {
54
+ status: 400,
55
+ error: {
56
+ message: 'Bad Request',
57
+ },
58
+ },
59
+ 'Error message',
60
+ {},
61
+ );
62
+
63
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
64
+
65
+ // Act
66
+ try {
67
+ await instance.chat({
68
+ messages: [{ content: 'Hello', role: 'user' }],
69
+ model: 'hunyuan-lite',
70
+ temperature: 0,
71
+ });
72
+ } catch (e) {
73
+ expect(e).toEqual({
74
+ endpoint: defaultBaseURL,
75
+ error: {
76
+ error: { message: 'Bad Request' },
77
+ status: 400,
78
+ },
79
+ errorType: bizErrorType,
80
+ provider,
81
+ });
82
+ }
83
+ });
84
+
85
+ it('should throw AgentRuntimeError with NoOpenAIAPIKey if no apiKey is provided', async () => {
86
+ try {
87
+ new LobeHunyuanAI({});
88
+ } catch (e) {
89
+ expect(e).toEqual({ errorType: invalidErrorType });
90
+ }
91
+ });
92
+
93
+ it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
94
+ // Arrange
95
+ const errorInfo = {
96
+ stack: 'abc',
97
+ cause: {
98
+ message: 'api is undefined',
99
+ },
100
+ };
101
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
102
+
103
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
104
+
105
+ // Act
106
+ try {
107
+ await instance.chat({
108
+ messages: [{ content: 'Hello', role: 'user' }],
109
+ model: 'hunyuan-lite',
110
+ temperature: 0,
111
+ });
112
+ } catch (e) {
113
+ expect(e).toEqual({
114
+ endpoint: defaultBaseURL,
115
+ error: {
116
+ cause: { message: 'api is undefined' },
117
+ stack: 'abc',
118
+ },
119
+ errorType: bizErrorType,
120
+ provider,
121
+ });
122
+ }
123
+ });
124
+
125
+ it('should return OpenAIBizError with an cause response with desensitize Url', async () => {
126
+ // Arrange
127
+ const errorInfo = {
128
+ stack: 'abc',
129
+ cause: { message: 'api is undefined' },
130
+ };
131
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
132
+
133
+ instance = new LobeHunyuanAI({
134
+ apiKey: 'test',
135
+
136
+ baseURL: 'https://api.abc.com/v1',
137
+ });
138
+
139
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
140
+
141
+ // Act
142
+ try {
143
+ await instance.chat({
144
+ messages: [{ content: 'Hello', role: 'user' }],
145
+ model: 'hunyuan-lite',
146
+ temperature: 0,
147
+ });
148
+ } catch (e) {
149
+ expect(e).toEqual({
150
+ endpoint: 'https://api.***.com/v1',
151
+ error: {
152
+ cause: { message: 'api is undefined' },
153
+ stack: 'abc',
154
+ },
155
+ errorType: bizErrorType,
156
+ provider,
157
+ });
158
+ }
159
+ });
160
+
161
+ it('should throw an InvalidHunyuanAPIKey error type on 401 status code', async () => {
162
+ // Mock the API call to simulate a 401 error
163
+ const error = new Error('Unauthorized') as any;
164
+ error.status = 401;
165
+ vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);
166
+
167
+ try {
168
+ await instance.chat({
169
+ messages: [{ content: 'Hello', role: 'user' }],
170
+ model: 'hunyuan-lite',
171
+ temperature: 0,
172
+ });
173
+ } catch (e) {
174
+ // Expect the chat method to throw an error with InvalidHunyuanAPIKey
175
+ expect(e).toEqual({
176
+ endpoint: defaultBaseURL,
177
+ error: new Error('Unauthorized'),
178
+ errorType: invalidErrorType,
179
+ provider,
180
+ });
181
+ }
182
+ });
183
+
184
+ it('should return AgentRuntimeError for non-OpenAI errors', async () => {
185
+ // Arrange
186
+ const genericError = new Error('Generic Error');
187
+
188
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(genericError);
189
+
190
+ // Act
191
+ try {
192
+ await instance.chat({
193
+ messages: [{ content: 'Hello', role: 'user' }],
194
+ model: 'hunyuan-lite',
195
+ temperature: 0,
196
+ });
197
+ } catch (e) {
198
+ expect(e).toEqual({
199
+ endpoint: defaultBaseURL,
200
+ errorType: 'AgentRuntimeError',
201
+ provider,
202
+ error: {
203
+ name: genericError.name,
204
+ cause: genericError.cause,
205
+ message: genericError.message,
206
+ stack: genericError.stack,
207
+ },
208
+ });
209
+ }
210
+ });
211
+ });
212
+
213
+ describe('DEBUG', () => {
214
+ it('should call debugStream and return StreamingTextResponse when DEBUG_HUNYUAN_CHAT_COMPLETION is 1', async () => {
215
+ // Arrange
216
+ const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流
217
+ const mockDebugStream = new ReadableStream({
218
+ start(controller) {
219
+ controller.enqueue('Debug stream content');
220
+ controller.close();
221
+ },
222
+ }) as any;
223
+ mockDebugStream.toReadableStream = () => mockDebugStream; // 添加 toReadableStream 方法
224
+
225
+ // 模拟 chat.completions.create 返回值,包括模拟的 tee 方法
226
+ (instance['client'].chat.completions.create as Mock).mockResolvedValue({
227
+ tee: () => [mockProdStream, { toReadableStream: () => mockDebugStream }],
228
+ });
229
+
230
+ // 保存原始环境变量值
231
+ const originalDebugValue = process.env.DEBUG_HUNYUAN_CHAT_COMPLETION;
232
+
233
+ // 模拟环境变量
234
+ process.env.DEBUG_HUNYUAN_CHAT_COMPLETION = '1';
235
+ vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());
236
+
237
+ // 执行测试
238
+ // 运行你的测试函数,确保它会在条件满足时调用 debugStream
239
+ // 假设的测试函数调用,你可能需要根据实际情况调整
240
+ await instance.chat({
241
+ messages: [{ content: 'Hello', role: 'user' }],
242
+ model: 'hunyuan-lite',
243
+ stream: true,
244
+ temperature: 0,
245
+ });
246
+
247
+ // 验证 debugStream 被调用
248
+ expect(debugStreamModule.debugStream).toHaveBeenCalled();
249
+
250
+ // 恢复原始环境变量值
251
+ process.env.DEBUG_HUNYUAN_CHAT_COMPLETION = originalDebugValue;
252
+ });
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,10 @@
1
+ import { ModelProvider } from '../types';
2
+ import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
+
4
+ export const LobeHunyuanAI = LobeOpenAICompatibleFactory({
5
+ baseURL: 'https://api.hunyuan.cloud.tencent.com/v1',
6
+ debug: {
7
+ chatCompletion: () => process.env.DEBUG_HUNYUAN_CHAT_COMPLETION === '1',
8
+ },
9
+ provider: ModelProvider.Hunyuan,
10
+ });
@@ -33,6 +33,7 @@ export enum ModelProvider {
33
33
  Github = 'github',
34
34
  Google = 'google',
35
35
  Groq = 'groq',
36
+ Hunyuan = 'hunyuan',
36
37
  Minimax = 'minimax',
37
38
  Mistral = 'mistral',
38
39
  Moonshot = 'moonshot',
@@ -9,6 +9,7 @@ import {
9
9
  GithubProviderCard,
10
10
  GoogleProviderCard,
11
11
  GroqProviderCard,
12
+ HunyuanProviderCard,
12
13
  NovitaProviderCard,
13
14
  OllamaProviderCard,
14
15
  OpenAIProviderCard,
@@ -49,6 +50,9 @@ export const getServerGlobalConfig = () => {
49
50
  ENABLED_GITHUB,
50
51
  GITHUB_MODEL_LIST,
51
52
 
53
+ ENABLED_HUNYUAN,
54
+ HUNYUAN_MODEL_LIST,
55
+
52
56
  ENABLED_DEEPSEEK,
53
57
  ENABLED_PERPLEXITY,
54
58
  ENABLED_ANTHROPIC,
@@ -160,6 +164,14 @@ export const getServerGlobalConfig = () => {
160
164
  modelString: GROQ_MODEL_LIST,
161
165
  }),
162
166
  },
167
+ hunyuan: {
168
+ enabled: ENABLED_HUNYUAN,
169
+ enabledModels: extractEnabledModels(HUNYUAN_MODEL_LIST),
170
+ serverModelCards: transformToChatModelCards({
171
+ defaultChatModels: HunyuanProviderCard.chatModels,
172
+ modelString: HUNYUAN_MODEL_LIST,
173
+ }),
174
+ },
163
175
  minimax: { enabled: ENABLED_MINIMAX },
164
176
  mistral: { enabled: ENABLED_MISTRAL },
165
177
  moonshot: { enabled: ENABLED_MOONSHOT },
@@ -28,6 +28,7 @@ export interface UserKeyVaults {
28
28
  github?: OpenAICompatibleKeyVault;
29
29
  google?: OpenAICompatibleKeyVault;
30
30
  groq?: OpenAICompatibleKeyVault;
31
+ hunyuan?: OpenAICompatibleKeyVault;
31
32
  lobehub?: any;
32
33
  minimax?: OpenAICompatibleKeyVault;
33
34
  mistral?: OpenAICompatibleKeyVault;