|
|
import express from 'express'; |
|
|
import cors from 'cors'; |
|
|
import { config } from 'dotenv'; |
|
|
import { spawn } from 'child_process'; |
|
|
import fetch from 'node-fetch'; |
|
|
|
|
|
|
|
|
config(); |
|
|
|
|
|
const app = express(); |
|
|
const PORT = process.env.PORT || 7860; |
|
|
const OPENCODE_PORT = 4096; |
|
|
|
|
|
|
|
|
app.use(cors()); |
|
|
app.use(express.json()); |
|
|
app.use(express.static('public')); |
|
|
|
|
|
|
|
|
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}`); |
|
|
}); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
console.log(`β
OpenCode CLI should be running on port ${OPENCODE_PORT}`); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
|
|
|
app.get('/health', async (req, res) => { |
|
|
try { |
|
|
|
|
|
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 |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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 |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/ws', (req, res) => { |
|
|
res.redirect(`http://localhost:${OPENCODE_PORT}/ws`); |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/', (req, res) => { |
|
|
res.sendFile('index.html', { root: 'public' }); |
|
|
}); |
|
|
|
|
|
|
|
|
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/' |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
|
|
|
|
|
|
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; |