ccccccc / src /index.ts
bingn's picture
Upload 12 files
5844451 verified
/**
* 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;