| const express = require('express'); |
| const morgan = require('morgan'); |
| const { createProxyMiddleware } = require('http-proxy-middleware'); |
| const url = require('url'); |
| const app = express(); |
|
|
| app.use(morgan('dev')); |
|
|
| |
| const proxyUrl = process.env.PROXY || ''; |
| console.log(`Proxy configuration: ${proxyUrl ? '已配置' : '未配置'}`); |
|
|
| |
| let proxyConfig = null; |
| if (proxyUrl) { |
| try { |
| const parsedUrl = url.parse(proxyUrl); |
| proxyConfig = { |
| host: parsedUrl.hostname, |
| port: parsedUrl.port || 80, |
| auth: parsedUrl.auth ? { |
| username: parsedUrl.auth.split(':')[0], |
| password: parsedUrl.auth.split(':')[1] |
| } : undefined |
| }; |
| |
| |
| const maskedConfig = { |
| ...proxyConfig, |
| auth: proxyConfig.auth ? { |
| username: proxyConfig.auth.username, |
| password: '******' |
| } : undefined |
| }; |
| console.log('Using proxy:', JSON.stringify(maskedConfig)); |
| } catch (error) { |
| console.error('Failed to parse proxy URL:', error.message); |
| } |
| } |
|
|
| |
| app.get('/hf/v1/models', (req, res) => { |
| const models = { |
| "object": "list", |
| "data": [ |
| { |
| "id": "claude-3.5-sonnet", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-4", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-4o", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3-opus", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-3.5-turbo", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-4-turbo-2024-04-09", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-4o-128k", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gemini-1.5-flash-500k", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3-haiku-200k", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3-5-sonnet-200k", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3-5-sonnet-20241022", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gpt-4o-mini", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "o1-mini", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "o1-preview", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "o1", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3.5-haiku", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gemini-exp-1206", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gemini-2.0-flash-thinking-exp", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "gemini-2.0-flash-exp", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "deepseek-v3", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "deepseek-r1", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| |
| { |
| "id": "claude-3.7-sonnet", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| }, |
| { |
| "id": "claude-3.7-sonnet-thinking", |
| "object": "model", |
| "created": 1706745938, |
| "owned_by": "cursor" |
| } |
| ] |
| }; |
| res.json(models); |
| }); |
|
|
| |
| app.use('/hf/v1/chat/completions', createProxyMiddleware({ |
| target: 'http://localhost:3010/v1/chat/completions', |
| changeOrigin: true, |
| |
| proxy: proxyConfig, |
| |
| onError: (err, req, res) => { |
| console.error('Proxy error:', err); |
| res.status(500).send('Proxy error occurred: ' + err.message); |
| }, |
| onProxyReq: (proxyReq, req, res) => { |
| console.log(`Proxying request to chat completions ${proxyConfig ? 'using proxy' : 'directly'}`); |
| }, |
| onProxyRes: (proxyRes, req, res) => { |
| console.log(`Received response with status: ${proxyRes.statusCode}`); |
| } |
| })); |
|
|
| app.get('/', (req, res) => { |
| const htmlContent = ` |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Cursor To OpenAI</title> |
| <style> |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 20px; |
| line-height: 1.6; |
| } |
| .container { |
| background: #f9f9f9; |
| border-radius: 10px; |
| padding: 20px; |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| } |
| .info-item { |
| margin-bottom: 10px; |
| } |
| .status { |
| background: ${proxyConfig ? '#e1f5e1' : '#fff3cd'}; |
| padding: 10px; |
| border-radius: 5px; |
| margin-top: 20px; |
| border: 1px solid ${proxyConfig ? '#c3e6cb' : '#ffeeba'}; |
| } |
| .models-container { |
| margin-top: 20px; |
| border-top: 1px solid #eee; |
| padding-top: 20px; |
| } |
| .model-list { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| gap: 10px; |
| margin-top: 15px; |
| } |
| .model-item { |
| background: #f0f0f0; |
| padding: 8px 12px; |
| border-radius: 4px; |
| font-size: 0.9em; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>Cursor To OpenAI Server</h1> |
| <div class="info-item"> |
| <strong>聊天来源:</strong> 自定义(兼容 OpenAI) |
| </div> |
| <div class="info-item"> |
| <strong>自定义端点(基本URL):</strong><span id="endpoint-url"></span> |
| </div> |
| <div class="info-item"> |
| <strong>自定义API密钥:</strong>[抓取的Cursor Cookie,格式为user_...] |
| </div> |
| <div class="status"> |
| <strong>代理状态:</strong> ${proxyConfig ? '已启用' : '未启用'} |
| ${proxyConfig ? `<p>代理服务器: ${proxyConfig.host}:${proxyConfig.port}</p>` : ''} |
| </div> |
| |
| <div class="models-container"> |
| <h3>支持的模型</h3> |
| <div id="model-list" class="model-list">加载中...</div> |
| </div> |
| </div> |
| <script> |
| const url = new URL(window.location.href); |
| const link = url.protocol + '//' + url.host + '/hf/v1'; |
| document.getElementById('endpoint-url').textContent = link; |
| |
| // 加载模型列表 |
| fetch('/hf/v1/models') |
| .then(response => response.json()) |
| .then(data => { |
| const modelListEl = document.getElementById('model-list'); |
| modelListEl.innerHTML = ''; |
| |
| data.data.forEach(model => { |
| const modelEl = document.createElement('div'); |
| modelEl.className = 'model-item'; |
| modelEl.textContent = model.id; |
| modelListEl.appendChild(modelEl); |
| }); |
| }) |
| .catch(err => { |
| document.getElementById('model-list').textContent = '加载模型失败: ' + err.message; |
| }); |
| </script> |
| </body> |
| </html> |
| `; |
| res.send(htmlContent); |
| }); |
|
|
| const port = process.env.HF_PORT || 7860; |
| app.listen(port, () => { |
| console.log(`HF Proxy server is running at PORT: ${port}`); |
| console.log(`Proxy status: ${proxyConfig ? 'Enabled' : 'Disabled'}`); |
| }); |
|
|