File size: 5,923 Bytes
5844451 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /**
* Cursor2API v2 - ๅ
ฅๅฃ
*
* ๅฐ Cursor ๆๆกฃ้กตๅ
่ดน AI ๆฅๅฃไปฃ็ไธบ Anthropic Messages API
* ้่ฟๆ็คบ่ฏๆณจๅ
ฅ่ฎฉ Claude Code ๆฅๆๅฎๆดๅทฅๅ
ท่ฐ็จ่ฝๅ
*/
import 'dotenv/config';
import { createRequire } from 'module';
import express from 'express';
import { getConfig } from './config.js';
import { handleMessages, listModels, countTokens } from './handler.js';
import { handleOpenAIChatCompletions, handleOpenAIResponses } from './openai-handler.js';
// ไป package.json ่ฏปๅ็ๆฌๅท๏ผ็ปไธๆฅๆบ๏ผ้ฟๅ
ๅคๅค็กฌ็ผ็
const require = createRequire(import.meta.url);
const { version: VERSION } = require('../package.json') as { version: string };
const app = express();
const config = getConfig();
// ่งฃๆ JSON body๏ผๅขๅคง้ๅถไปฅๆฏๆ base64 ๅพ็๏ผๅๅผ ๅพ็ๅฏ่พพ 10MB+๏ผ
app.use(express.json({ limit: '50mb' }));
// CORS
app.use((_req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', '*');
if (_req.method === 'OPTIONS') {
res.sendStatus(200);
return;
}
next();
});
// Optional API Key auth (recommended for public deployments)
// - Enabled only when API_KEY is set
// - Accepts either:
// 1) x-api-key: <API_KEY> (Anthropic-style)
// 2) Authorization: Bearer <API_KEY> (OpenAI-style)
const API_KEY = (process.env.API_KEY || '').trim();
if (API_KEY) {
app.use((req, res, next) => {
// Public endpoints
if (req.path === '/' || req.path === '/health') return next();
const headerKey = (req.header('x-api-key') || '').trim();
const auth = (req.header('authorization') || '').trim();
let provided = headerKey;
if (!provided && auth.toLowerCase().startsWith('bearer ')) {
provided = auth.slice('bearer '.length).trim();
}
if (provided && provided === API_KEY) return next();
res.status(401).json({ error: { message: 'Unauthorized', type: 'auth_error' } });
});
}
// ==================== ่ทฏ็ฑ ====================
// Anthropic Messages API
app.post('/v1/messages', handleMessages);
app.post('/messages', handleMessages);
// OpenAI Chat Completions API๏ผๅ
ผๅฎน๏ผ
app.post('/v1/chat/completions', handleOpenAIChatCompletions);
app.post('/chat/completions', handleOpenAIChatCompletions);
// OpenAI Responses API๏ผCursor IDE Agent ๆจกๅผ๏ผ
app.post('/v1/responses', handleOpenAIResponses);
app.post('/responses', handleOpenAIResponses);
// Token ่ฎกๆฐ
app.post('/v1/messages/count_tokens', countTokens);
app.post('/messages/count_tokens', countTokens);
// OpenAI ๅ
ผๅฎนๆจกๅๅ่กจ
app.get('/v1/models', listModels);
// ๅฅๅบทๆฃๆฅ
app.get('/health', (_req, res) => {
res.json({ status: 'ok', version: VERSION });
});
// ๆ น่ทฏๅพ
app.get('/', (_req, res) => {
res.json({
name: 'cursor2api',
version: VERSION,
description: 'Cursor Docs AI โ Anthropic & OpenAI & Cursor IDE API Proxy',
endpoints: {
anthropic_messages: 'POST /v1/messages',
openai_chat: 'POST /v1/chat/completions',
openai_responses: 'POST /v1/responses',
models: 'GET /v1/models',
health: 'GET /health',
},
auth: API_KEY
? { required: true, headers: ['x-api-key', 'Authorization: Bearer'] }
: { required: false },
usage: {
claude_code: 'export ANTHROPIC_BASE_URL=http://localhost:' + config.port,
openai_compatible: 'OPENAI_BASE_URL=http://localhost:' + config.port + '/v1',
cursor_ide: 'OPENAI_BASE_URL=http://localhost:' + config.port + '/v1 (้็จ Claude ๆจกๅ)',
},
});
});
// ==================== ๅฏๅจ ====================
const server = app.listen(config.port, '0.0.0.0', () => {
console.log('');
console.log(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
console.log(` โ Cursor2API v${VERSION.padEnd(21)}โ`);
console.log(' โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ');
console.log(` โ Server: http://localhost:${config.port} โ`);
console.log(' โ Model: ' + config.cursorModel.padEnd(26) + 'โ');
console.log(' โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ');
console.log(' โ API Endpoints: โ');
console.log(' โ โข Anthropic: /v1/messages โ');
console.log(' โ โข OpenAI: /v1/chat/completions โ');
console.log(' โ โข Cursor: /v1/responses โ');
console.log(' โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ');
console.log(' โ Claude Code: โ');
console.log(` โ export ANTHROPIC_BASE_URL= โ`);
console.log(` โ http://localhost:${config.port} โ`);
console.log(' โ OpenAI / Cursor IDE: โ');
console.log(` โ OPENAI_BASE_URL= โ`);
console.log(` โ http://localhost:${config.port}/v1 โ`);
console.log(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
console.log('');
});
// ่งฃ้ค Node.js HTTP Server ็้ป่ฎค่ถ
ๆถ้ๅถ๏ผ้ฒๆญข้ฟๆถ AI ๆตๅผ่พๅบ่ขซๆฌๅฐๆๆญ
server.timeout = 0;
server.keepAliveTimeout = 120 * 1000;
server.headersTimeout = 125 * 1000;
|