opencode / proxy-server.js
tanbushi's picture
update
f18c7da
import express from 'express';
import cors from 'cors';
import { config } from 'dotenv';
import { spawn } from 'child_process';
import fetch from 'node-fetch';
// Load environment variables
config();
const app = express();
const PORT = process.env.PORT || 7860;
const OPENCODE_PORT = 4096; // Default OpenCode serve port
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// Start OpenCode CLI server
let opencodeProcess = null;
function startOpenCodeServer() {
console.log('πŸš€ Starting OpenCode CLI server...');
const args = ['serve', '--port', OPENCODE_PORT.toString(), '--hostname', '0.0.0.0'];
opencodeProcess = spawn('opencode', args, {
stdio: 'pipe',
env: {
...process.env,
OPENCODE_HOST: '0.0.0.0',
OPENCODE_PORT: OPENCODE_PORT,
OPENCODE_DISABLE_AUTOUPDATE: 'true',
OPENCODE_CLIENT: 'hf-space-proxy'
}
});
opencodeProcess.stdout.on('data', (data) => {
console.log(`OpenCode: ${data.toString()}`);
});
opencodeProcess.stderr.on('data', (data) => {
console.error(`OpenCode Error: ${data.toString()}`);
});
opencodeProcess.on('close', (code) => {
console.log(`OpenCode process exited with code ${code}`);
});
// Wait for OpenCode server to start
setTimeout(() => {
console.log(`βœ… OpenCode CLI should be running on port ${OPENCODE_PORT}`);
}, 3000);
}
// Health check endpoint
app.get('/health', async (req, res) => {
try {
// Check OpenCode server status
const response = await fetch(`http://localhost:${OPENCODE_PORT}/health`);
const isHealthy = response.ok;
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? 'ok' : 'degraded',
timestamp: new Date().toISOString(),
service: 'OpenCode CLI Proxy',
opencode_port: OPENCODE_PORT,
opencode_healthy: isHealthy
});
} catch (error) {
res.status(503).json({
status: 'error',
timestamp: new Date().toISOString(),
service: 'OpenCode CLI Proxy',
error: error.message
});
}
});
// Proxy endpoints for OpenCode API
app.all('/api/*', async (req, res) => {
try {
const targetUrl = `http://localhost:${OPENCODE_PORT}${req.url}`;
const method = req.method;
const headers = { ...req.headers };
delete headers.host; // Remove host header
let body;
if (['POST', 'PUT', 'PATCH'].includes(method)) {
body = JSON.stringify(req.body);
}
const response = await fetch(targetUrl, {
method,
headers,
body,
timeout: 30000
});
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
res.status(response.status).json(data);
} else {
const data = await response.text();
res.status(response.status).send(data);
}
} catch (error) {
console.error('Proxy error:', error);
res.status(502).json({
error: 'Proxy error',
message: error.message
});
}
});
// WebSocket support for streaming responses
app.get('/ws', (req, res) => {
res.redirect(`http://localhost:${OPENCODE_PORT}/ws`);
});
// Static files and main page
app.get('/', (req, res) => {
res.sendFile('index.html', { root: 'public' });
});
// API documentation
app.get('/api', (req, res) => {
res.json({
message: 'OpenCode CLI Proxy API',
version: '1.0.0',
endpoints: {
health: '/health',
api_proxy: '/api/*',
websocket: '/ws',
documentation: 'https://opencode.ai/docs/server/'
},
opencode: {
direct_url: `http://localhost:${OPENCODE_PORT}`,
api_docs: 'https://opencode.ai/docs/server/'
}
});
});
// Start server
app.listen(PORT, '0.0.0.0', () => {
console.log(`🌐 Proxy server running on port ${PORT}`);
console.log(`πŸ“Š Access at: http://0.0.0.0:${PORT}`);
startOpenCodeServer();
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('πŸ›‘ Received SIGTERM, shutting down gracefully...');
if (opencodeProcess) {
opencodeProcess.kill('SIGTERM');
}
process.exit(0);
});
process.on('SIGINT', () => {
console.log('πŸ›‘ Received SIGINT, shutting down gracefully...');
if (opencodeProcess) {
opencodeProcess.kill('SIGINT');
}
process.exit(0);
});
export default app;