fix: update OpenCode proxy to v3 API and dynamic env read (#361)

Reload OpenCode password from env on every request to support hot reload
Adopt v3.x proxy callbacks and forward Basic Auth header when available

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
gsxdsm
2026-02-09 03:23:14 +02:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 5b0a97d170
commit 6d0419149a
+42 -33
View File
@@ -2278,6 +2278,7 @@ async function isOpenCodeProcessHealthy() {
try { try {
const response = await fetch(`http://127.0.0.1:${openCodePort}/session`, { const response = await fetch(`http://127.0.0.1:${openCodePort}/session`, {
method: 'GET', method: 'GET',
headers: getOpenCodeAuthHeaders(),
signal: AbortSignal.timeout(2000), signal: AbortSignal.timeout(2000),
}); });
return response.ok; return response.ok;
@@ -2313,10 +2314,14 @@ const ENV_OPENCODE_SERVER_PASSWORD = (() => {
* Uses Basic Auth with username "opencode" and the password from the env variable. * Uses Basic Auth with username "opencode" and the password from the env variable.
*/ */
function getOpenCodeAuthHeaders() { function getOpenCodeAuthHeaders() {
if (!ENV_OPENCODE_SERVER_PASSWORD) { // Re-read from env each time in case it wasn't set at module load (HMR issue)
const password = ENV_OPENCODE_SERVER_PASSWORD || process.env.OPENCODE_SERVER_PASSWORD;
if (!password) {
return {}; return {};
} }
const credentials = Buffer.from(`opencode:${ENV_OPENCODE_SERVER_PASSWORD}`).toString('base64');
const credentials = Buffer.from(`opencode:${password}`).toString('base64');
return { Authorization: `Basic ${credentials}` }; return { Authorization: `Basic ${credentials}` };
} }
@@ -4047,6 +4052,7 @@ function setupProxy(app) {
next(); next();
}); });
const proxyMiddleware = createProxyMiddleware({ const proxyMiddleware = createProxyMiddleware({
target: openCodePort ? `http://localhost:${openCodePort}` : 'http://127.0.0.1:0', target: openCodePort ? `http://localhost:${openCodePort}` : 'http://127.0.0.1:0',
router: () => { router: () => {
@@ -4066,40 +4072,43 @@ function setupProxy(app) {
return suffix; return suffix;
}, },
ws: false, ws: false,
onError: (err, req, res) => { // v3.x API: callbacks go in 'on' object
console.error(`Proxy error: ${err.message}`); on: {
if (!res.headersSent) { error: (err, req, res) => {
res.status(503).json({ error: 'OpenCode service unavailable' }); console.error(`Proxy error: ${err.message}`);
} if (!res.headersSent) {
}, res.status(503).json({ error: 'OpenCode service unavailable' });
onProxyReq: (proxyReq, req, res) => { }
console.log(`Proxying ${req.method} ${req.path} to OpenCode`); },
proxyReq: (proxyReq, req, res) => {
console.log(`Proxying ${req.method} ${req.path} to OpenCode`);
const authHeaders = getOpenCodeAuthHeaders();
if (authHeaders.Authorization) {
proxyReq.setHeader('Authorization', authHeaders.Authorization);
}
const authHeaders = getOpenCodeAuthHeaders(); if (req.headers.accept && req.headers.accept.includes('text/event-stream')) {
if (authHeaders.Authorization) { proxyReq.setHeader('Accept', 'text/event-stream');
proxyReq.setHeader('Authorization', authHeaders.Authorization); proxyReq.setHeader('Cache-Control', 'no-cache');
} proxyReq.setHeader('Connection', 'keep-alive');
}
},
proxyRes: (proxyRes, req, res) => {
// Strip WWW-Authenticate to prevent browser's native Basic Auth popup
if (proxyRes.headers['www-authenticate']) {
delete proxyRes.headers['www-authenticate'];
}
if (req.headers.accept && req.headers.accept.includes('text/event-stream')) { if (req.url?.includes('/event')) {
console.log(`[SSE] Setting up SSE proxy for ${req.method} ${req.path}`); proxyRes.headers['Access-Control-Allow-Origin'] = '*';
proxyReq.setHeader('Accept', 'text/event-stream'); proxyRes.headers['Access-Control-Allow-Headers'] = 'Cache-Control, Accept';
proxyReq.setHeader('Cache-Control', 'no-cache'); proxyRes.headers['Content-Type'] = 'text/event-stream';
proxyReq.setHeader('Connection', 'keep-alive'); proxyRes.headers['Cache-Control'] = 'no-cache';
proxyRes.headers['Connection'] = 'keep-alive';
proxyRes.headers['X-Accel-Buffering'] = 'no';
proxyRes.headers['X-Content-Type-Options'] = 'nosniff';
}
} }
},
onProxyRes: (proxyRes, req, res) => {
if (req.url?.includes('/event')) {
console.log(`[SSE] Proxy response for ${req.method} ${req.url} - Status: ${proxyRes.statusCode}`);
proxyRes.headers['Access-Control-Allow-Origin'] = '*';
proxyRes.headers['Access-Control-Allow-Headers'] = 'Cache-Control, Accept';
proxyRes.headers['Content-Type'] = 'text/event-stream';
proxyRes.headers['Cache-Control'] = 'no-cache';
proxyRes.headers['Connection'] = 'keep-alive';
proxyRes.headers['X-Accel-Buffering'] = 'no';
proxyRes.headers['X-Content-Type-Options'] = 'nosniff';
}
} }
}); });