mirror of
https://github.com/cdr/code-server.git
synced 2025-12-08 17:34:01 +01:00
389 lines
13 KiB
JavaScript
389 lines
13 KiB
JavaScript
// Cloudflare Worker for Cursor Full Stack AI IDE Backend
|
|
import { WebSocketDurableObject } from './websocket-do.js';
|
|
|
|
// Export the Durable Object class
|
|
export { WebSocketDurableObject };
|
|
|
|
export default {
|
|
async fetch(request, env, ctx) {
|
|
const url = new URL(request.url);
|
|
|
|
// CORS headers
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': env.CORS_ORIGIN || '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
'Access-Control-Max-Age': '86400',
|
|
};
|
|
|
|
// Handle CORS preflight
|
|
if (request.method === 'OPTIONS') {
|
|
return new Response(null, { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
// Health check endpoint
|
|
if (url.pathname === '/health') {
|
|
return new Response(JSON.stringify({
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
environment: env.ENVIRONMENT || 'production',
|
|
version: '1.0.0'
|
|
}), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// AI Providers endpoint
|
|
if (url.pathname === '/api/providers') {
|
|
return new Response(JSON.stringify({
|
|
providers: [
|
|
{ id: 'openai', name: 'OpenAI', models: ['gpt-4', 'gpt-3.5-turbo', 'gpt-4-turbo'] },
|
|
{ id: 'anthropic', name: 'Anthropic', models: ['claude-3-sonnet', 'claude-3-haiku', 'claude-3-opus'] },
|
|
{ id: 'google', name: 'Google Gemini', models: ['gemini-pro', 'gemini-pro-vision', 'gemini-1.5-pro'] },
|
|
{ id: 'mistral', name: 'Mistral', models: ['mistral-large', 'mistral-medium', 'mistral-small'] },
|
|
{ id: 'openrouter', name: 'OpenRouter', models: ['meta-llama/llama-2-70b-chat', 'meta-llama/llama-2-13b-chat', 'microsoft/wizardlm-13b', 'openai/gpt-4', 'anthropic/claude-3-sonnet'] }
|
|
]
|
|
}), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// Chat endpoint
|
|
if (url.pathname === '/api/chat' && request.method === 'POST') {
|
|
const { message, provider, apiKey, model, useTools = false } = await request.json();
|
|
|
|
if (!message || !provider || !apiKey) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Missing required fields',
|
|
details: 'Please provide message, provider, and apiKey'
|
|
}), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
try {
|
|
const response = await handleAIChat(message, provider, apiKey, model, useTools);
|
|
return new Response(JSON.stringify({
|
|
response,
|
|
provider,
|
|
model: model || 'default'
|
|
}), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
} catch (error) {
|
|
return new Response(JSON.stringify({
|
|
error: 'AI request failed',
|
|
details: error.message
|
|
}), {
|
|
status: 500,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Tools endpoint
|
|
if (url.pathname === '/api/tools' && request.method === 'GET') {
|
|
return new Response(JSON.stringify({
|
|
tools: [
|
|
{ name: 'file_read', description: 'Read contents of a file', parameters: { filePath: { type: 'string' } } },
|
|
{ name: 'file_write', description: 'Write content to a file', parameters: { filePath: { type: 'string' }, content: { type: 'string' } } },
|
|
{ name: 'file_list', description: 'List files in a directory', parameters: { directory: { type: 'string' } } },
|
|
{ name: 'terminal_command', description: 'Execute a terminal command', parameters: { command: { type: 'string' } } },
|
|
{ name: 'git_status', description: 'Get git status', parameters: {} },
|
|
{ name: 'git_commit', description: 'Commit changes to git', parameters: { message: { type: 'string' } } },
|
|
{ name: 'search_code', description: 'Search for code patterns', parameters: { query: { type: 'string' } } },
|
|
{ name: 'create_file', description: 'Create a new file', parameters: { filePath: { type: 'string' }, content: { type: 'string' } } }
|
|
]
|
|
}), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// Tool execution endpoint
|
|
if (url.pathname === '/api/tools/execute' && request.method === 'POST') {
|
|
const { toolName, params } = await request.json();
|
|
|
|
if (!toolName) {
|
|
return new Response(JSON.stringify({ error: 'Tool name is required' }), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
try {
|
|
const result = await executeTool(toolName, params, env);
|
|
return new Response(JSON.stringify(result), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
} catch (error) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Tool execution failed',
|
|
details: error.message
|
|
}), {
|
|
status: 500,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// File operations
|
|
if (url.pathname.startsWith('/api/workspace/')) {
|
|
return handleFileOperations(request, env, corsHeaders);
|
|
}
|
|
|
|
// WebSocket upgrade
|
|
if (request.headers.get('Upgrade') === 'websocket') {
|
|
const durableObjectId = env.WEBSOCKET_DO.idFromName('websocket');
|
|
const durableObject = env.WEBSOCKET_DO.get(durableObjectId);
|
|
return durableObject.fetch(request);
|
|
}
|
|
|
|
return new Response('Not Found', {
|
|
status: 404,
|
|
headers: corsHeaders
|
|
});
|
|
|
|
} catch (error) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Internal server error',
|
|
details: error.message
|
|
}), {
|
|
status: 500,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// AI Chat Handler
|
|
async function handleAIChat(message, provider, apiKey, model, useTools) {
|
|
const providers = {
|
|
openai: async (message, apiKey, model) => {
|
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
model: model || 'gpt-4',
|
|
messages: [{ role: 'user', content: message }],
|
|
max_tokens: 1000
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`OpenAI API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.choices[0]?.message?.content || 'No response generated';
|
|
},
|
|
|
|
anthropic: async (message, apiKey, model) => {
|
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'x-api-key': apiKey,
|
|
'Content-Type': 'application/json',
|
|
'anthropic-version': '2023-06-01'
|
|
},
|
|
body: JSON.stringify({
|
|
model: model || 'claude-3-sonnet-20240229',
|
|
max_tokens: 1000,
|
|
messages: [{ role: 'user', content: message }]
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Anthropic API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.content[0]?.text || 'No response generated';
|
|
},
|
|
|
|
google: async (message, apiKey, model) => {
|
|
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model || 'gemini-pro'}:generateContent?key=${apiKey}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: message }] }]
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Google API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.candidates[0]?.content?.parts[0]?.text || 'No response generated';
|
|
},
|
|
|
|
mistral: async (message, apiKey, model) => {
|
|
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
model: model || 'mistral-large-latest',
|
|
messages: [{ role: 'user', content: message }],
|
|
max_tokens: 1000
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Mistral API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.choices[0]?.message?.content || 'No response generated';
|
|
},
|
|
|
|
openrouter: async (message, apiKey, model) => {
|
|
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'Content-Type': 'application/json',
|
|
'HTTP-Referer': 'https://cursor-fullstack-ai-ide.com',
|
|
'X-Title': 'Cursor Full Stack AI IDE'
|
|
},
|
|
body: JSON.stringify({
|
|
model: model || 'meta-llama/llama-2-70b-chat',
|
|
messages: [{ role: 'user', content: message }],
|
|
max_tokens: 1000
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`OpenRouter API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.choices[0]?.message?.content || 'No response generated';
|
|
}
|
|
};
|
|
|
|
const providerHandler = providers[provider];
|
|
if (!providerHandler) {
|
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
}
|
|
|
|
return await providerHandler(message, apiKey, model);
|
|
}
|
|
|
|
// Tool Execution Handler
|
|
async function executeTool(toolName, params, env) {
|
|
const tools = {
|
|
file_read: async (params) => {
|
|
const { filePath } = params;
|
|
const file = await env.FILE_STORAGE_KV.get(filePath);
|
|
return { success: true, content: file || '', filePath };
|
|
},
|
|
|
|
file_write: async (params) => {
|
|
const { filePath, content } = params;
|
|
await env.FILE_STORAGE_KV.put(filePath, content);
|
|
return { success: true, filePath };
|
|
},
|
|
|
|
file_list: async (params) => {
|
|
const { directory = '' } = params;
|
|
const files = await env.FILE_STORAGE_KV.list({ prefix: directory });
|
|
return { success: true, files: files.objects.map(obj => ({
|
|
name: obj.key.split('/').pop(),
|
|
path: obj.key,
|
|
type: 'file',
|
|
size: obj.size
|
|
})) };
|
|
},
|
|
|
|
search_code: async (params) => {
|
|
const { query } = params;
|
|
const files = await env.FILE_STORAGE_KV.list();
|
|
const results = [];
|
|
|
|
for (const file of files.objects) {
|
|
const content = await env.FILE_STORAGE_KV.get(file.key);
|
|
if (content && content.includes(query)) {
|
|
results.push({
|
|
filePath: file.key,
|
|
content: content.substring(0, 200) + '...'
|
|
});
|
|
}
|
|
}
|
|
|
|
return { success: true, results, query, count: results.length };
|
|
},
|
|
|
|
create_file: async (params) => {
|
|
const { filePath, content } = params;
|
|
await env.FILE_STORAGE_KV.put(filePath, content);
|
|
return { success: true, filePath };
|
|
}
|
|
};
|
|
|
|
const tool = tools[toolName];
|
|
if (!tool) {
|
|
return { success: false, error: `Unknown tool: ${toolName}` };
|
|
}
|
|
|
|
try {
|
|
return await tool(params);
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// File Operations Handler
|
|
async function handleFileOperations(request, env, corsHeaders) {
|
|
const url = new URL(request.url);
|
|
const path = url.pathname.replace('/api/workspace/', '');
|
|
|
|
if (request.method === 'GET' && path === 'files') {
|
|
const files = await env.FILE_STORAGE_KV.list();
|
|
return new Response(JSON.stringify({
|
|
files: files.objects.map(obj => ({
|
|
name: obj.key.split('/').pop(),
|
|
path: obj.key,
|
|
type: 'file',
|
|
size: obj.size
|
|
}))
|
|
}), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
if (request.method === 'GET' && path) {
|
|
const content = await env.FILE_STORAGE_KV.get(path);
|
|
if (!content) {
|
|
return new Response('File not found', {
|
|
status: 404,
|
|
headers: corsHeaders
|
|
});
|
|
}
|
|
return new Response(JSON.stringify({ content }), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
if (request.method === 'POST' && path) {
|
|
const { content } = await request.json();
|
|
await env.FILE_STORAGE_KV.put(path, content);
|
|
return new Response(JSON.stringify({ success: true }), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
return new Response('Not Found', {
|
|
status: 404,
|
|
headers: corsHeaders
|
|
});
|
|
}
|