158 lines
4.8 KiB
TypeScript
158 lines
4.8 KiB
TypeScript
import path from 'path';
|
|
import { defineConfig, loadEnv } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import type { Connect } from 'vite';
|
|
|
|
// Custom middleware to handle dynamic proxy for /custom-api
|
|
function customApiProxyMiddleware(): Connect.NextHandleFunction {
|
|
return async (req, res, next) => {
|
|
if (!req.url?.startsWith('/custom-api')) {
|
|
return next();
|
|
}
|
|
|
|
const targetUrl = req.headers['x-target-url'] as string;
|
|
if (!targetUrl) {
|
|
res.statusCode = 400;
|
|
res.end(JSON.stringify({ error: 'Missing X-Target-URL header' }));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const url = new URL(targetUrl);
|
|
const targetPath = req.url.replace(/^\/custom-api/, '');
|
|
const fullUrl = `${url.origin}${targetPath}`;
|
|
|
|
console.log('[Custom Proxy] Forwarding:', req.method, req.url, '->', fullUrl);
|
|
|
|
// Collect request body
|
|
const chunks: Buffer[] = [];
|
|
for await (const chunk of req) {
|
|
chunks.push(chunk as Buffer);
|
|
}
|
|
const body = Buffer.concat(chunks);
|
|
|
|
// Forward headers (excluding hop-by-hop headers)
|
|
const forwardHeaders: Record<string, string> = {};
|
|
const skipHeaders = ['host', 'connection', 'x-target-url', 'transfer-encoding'];
|
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
if (!skipHeaders.includes(key.toLowerCase()) && value) {
|
|
forwardHeaders[key] = Array.isArray(value) ? value[0] : value;
|
|
}
|
|
}
|
|
forwardHeaders['host'] = url.host;
|
|
|
|
// Make the request
|
|
const response = await fetch(fullUrl, {
|
|
method: req.method,
|
|
headers: forwardHeaders,
|
|
body: ['GET', 'HEAD'].includes(req.method || '') ? undefined : body,
|
|
});
|
|
|
|
// Forward response status and headers
|
|
res.statusCode = response.status;
|
|
response.headers.forEach((value, key) => {
|
|
if (!['transfer-encoding', 'connection'].includes(key.toLowerCase())) {
|
|
res.setHeader(key, value);
|
|
}
|
|
});
|
|
|
|
// Stream the response body
|
|
if (response.body) {
|
|
const reader = response.body.getReader();
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
res.write(value);
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
}
|
|
res.end();
|
|
|
|
} catch (error: any) {
|
|
console.error('[Custom Proxy] Error:', error.message);
|
|
res.statusCode = 502;
|
|
res.end(JSON.stringify({ error: 'Proxy error', message: error.message }));
|
|
}
|
|
};
|
|
}
|
|
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, '.', '');
|
|
|
|
return {
|
|
server: {
|
|
port: 3000,
|
|
host: '0.0.0.0',
|
|
proxy: {
|
|
// Proxy for OpenAI API
|
|
'/openai/v1': {
|
|
target: 'https://api.openai.com',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
rewrite: (path) => path.replace(/^\/openai\/v1/, '/v1'),
|
|
},
|
|
// Proxy for DeepSeek API
|
|
'/deepseek/v1': {
|
|
target: 'https://api.deepseek.com',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
rewrite: (path) => path.replace(/^\/deepseek\/v1/, '/v1'),
|
|
},
|
|
// Proxy for Anthropic API
|
|
'/anthropic/v1': {
|
|
target: 'https://api.anthropic.com',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
rewrite: (path) => path.replace(/^\/anthropic\/v1/, '/v1'),
|
|
},
|
|
// Proxy for xAI API
|
|
'/xai/v1': {
|
|
target: 'https://api.x.ai',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
rewrite: (path) => path.replace(/^\/xai\/v1/, '/v1'),
|
|
},
|
|
// Proxy for Mistral API
|
|
'/mistral/v1': {
|
|
target: 'https://api.mistral.ai',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
rewrite: (path) => path.replace(/^\/mistral\/v1/, '/v1'),
|
|
},
|
|
// Proxy for Google Gemini API
|
|
'/v1beta/models': {
|
|
target: 'https://generativelanguage.googleapis.com',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
},
|
|
'/v1/models': {
|
|
target: 'https://generativelanguage.googleapis.com',
|
|
changeOrigin: true,
|
|
secure: true,
|
|
},
|
|
}
|
|
},
|
|
plugins: [
|
|
react(),
|
|
{
|
|
name: 'custom-api-proxy',
|
|
configureServer(server) {
|
|
server.middlewares.use(customApiProxyMiddleware());
|
|
},
|
|
},
|
|
],
|
|
define: {
|
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'@': path.resolve(__dirname, '.'),
|
|
}
|
|
}
|
|
};
|
|
});
|