fix: lighten session list payloads (#1538)
* lighten session list * fetch full session on open * sanitize session list * sanitize global sessions * preserve revert markers in session lists * fix: preserve session metadata in list sanitizers --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
1c274d0a56
commit
9685630436
@@ -93,6 +93,69 @@ export const createSseBoundaryTracker = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const SESSION_LIST_ALLOWED_FIELDS = [
|
||||
'id',
|
||||
'slug',
|
||||
'projectID',
|
||||
'workspaceID',
|
||||
'directory',
|
||||
'path',
|
||||
'parentID',
|
||||
'title',
|
||||
'agent',
|
||||
'model',
|
||||
'version',
|
||||
'time',
|
||||
'cost',
|
||||
'tokens',
|
||||
'share',
|
||||
'metadata',
|
||||
'project',
|
||||
];
|
||||
|
||||
export const sanitizeSessionListItem = (session) => {
|
||||
if (!session || typeof session !== 'object' || Array.isArray(session)) {
|
||||
return session;
|
||||
}
|
||||
|
||||
const sanitized = {};
|
||||
for (const key of SESSION_LIST_ALLOWED_FIELDS) {
|
||||
if (key in session) {
|
||||
sanitized[key] = session[key];
|
||||
}
|
||||
}
|
||||
|
||||
const summary = session.summary;
|
||||
if (summary && typeof summary === 'object' && !Array.isArray(summary)) {
|
||||
const summaryWithoutDiffs = { ...summary };
|
||||
delete summaryWithoutDiffs.diffs;
|
||||
sanitized.summary = summaryWithoutDiffs;
|
||||
}
|
||||
|
||||
const revert = session.revert;
|
||||
if (revert && typeof revert === 'object' && !Array.isArray(revert)) {
|
||||
const revertMarker = {};
|
||||
if (typeof revert.messageID === 'string') {
|
||||
revertMarker.messageID = revert.messageID;
|
||||
}
|
||||
if (typeof revert.partID === 'string') {
|
||||
revertMarker.partID = revert.partID;
|
||||
}
|
||||
if (Object.keys(revertMarker).length > 0) {
|
||||
sanitized.revert = revertMarker;
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
export const sanitizeSessionListPayload = (payload) => {
|
||||
if (!Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
return payload.map((session) => sanitizeSessionListItem(session));
|
||||
};
|
||||
|
||||
export const registerOpenCodeProxy = (app, deps) => {
|
||||
const {
|
||||
fs,
|
||||
@@ -348,6 +411,57 @@ export const registerOpenCodeProxy = (app, deps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const forwardSanitizedSessionListRequest = async (req, res, next, logLabel) => {
|
||||
try {
|
||||
const requestUrl = typeof req.originalUrl === 'string' && req.originalUrl.length > 0
|
||||
? req.originalUrl
|
||||
: (typeof req.url === 'string' ? req.url : '');
|
||||
const upstreamPathRaw = requestUrl.startsWith('/api') ? requestUrl.slice(4) || '/' : requestUrl;
|
||||
const upstreamPath = await canonicalizeDirectoryQuery(upstreamPathRaw);
|
||||
const upstream = await fetch(buildOpenCodeUrl(upstreamPath, ''), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...collectForwardProxyHeaders(req.headers, getOpenCodeAuthHeaders()),
|
||||
accept: 'application/json',
|
||||
'accept-encoding': 'identity',
|
||||
},
|
||||
});
|
||||
|
||||
res.status(upstream.status);
|
||||
applyForwardProxyResponseHeaders(upstream.headers, res);
|
||||
|
||||
const contentType = upstream.headers.get('content-type') || 'application/json; charset=utf-8';
|
||||
const bodyText = await upstream.text();
|
||||
if (!contentType.toLowerCase().includes('application/json')) {
|
||||
res.setHeader('content-type', contentType);
|
||||
res.end(bodyText);
|
||||
return;
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(bodyText);
|
||||
} catch {
|
||||
res.setHeader('content-type', contentType);
|
||||
res.end(bodyText);
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader('content-type', contentType);
|
||||
res.json(sanitizeSessionListPayload(payload));
|
||||
} catch (error) {
|
||||
if (isAbortError(error)) {
|
||||
return;
|
||||
}
|
||||
console.error(`[proxy] OpenCode ${logLabel} proxy error:`, error?.message ?? error);
|
||||
if (!res.headersSent) {
|
||||
res.status(503).json({ error: 'OpenCode service unavailable' });
|
||||
return;
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure API prefix is detected before proxying
|
||||
app.use('/api', (_req, _res, next) => {
|
||||
ensureOpenCodeApiPrefix();
|
||||
@@ -453,7 +567,7 @@ export const registerOpenCodeProxy = (app, deps) => {
|
||||
return bTime - aTime;
|
||||
});
|
||||
console.log(`[SessionMerge] ${globalSessions.length} global + ${extraSessions.length} extra = ${merged.length} total`);
|
||||
return res.json(merged);
|
||||
return res.json(sanitizeSessionListPayload(merged));
|
||||
} catch (error) {
|
||||
console.log(`[SessionMerge] Error: ${error.message}, falling through`);
|
||||
next();
|
||||
@@ -461,9 +575,17 @@ export const registerOpenCodeProxy = (app, deps) => {
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/api/session', (req, res, next) => {
|
||||
return forwardSanitizedSessionListRequest(req, res, next, 'session.list');
|
||||
});
|
||||
|
||||
app.get('/api/global/event', forwardSseRequest);
|
||||
app.get('/api/event', forwardSseRequest);
|
||||
|
||||
app.get('/api/experimental/session', (req, res, next) => {
|
||||
return forwardSanitizedSessionListRequest(req, res, next, 'experimental.session');
|
||||
});
|
||||
|
||||
// Generic proxy for non-SSE OpenCode API routes.
|
||||
const apiProxy = createProxyMiddleware({
|
||||
target: resolveProxyTarget(),
|
||||
|
||||
Reference in New Issue
Block a user