fix: expose workbench cache observability

This commit is contained in:
lyon
2026-06-22 15:10:26 +08:00
parent 19b6db4254
commit 52182aa0a5
11 changed files with 813 additions and 23 deletions
+29 -2
View File
@@ -300,6 +300,25 @@ function recordWorkbenchTurnReadMetric(options = {}, url, statusCode, body = {},
});
}
function recordWorkbenchCacheMetric(options = {}, url, body = {}, startedAt = nowMs()) {
const performanceStore = options.backendPerformanceStore ?? options.backendPerformance;
if (!performanceStore?.recordWorkbenchCache) return;
const cache = body?.cache && typeof body.cache === "object" ? body.cache : null;
if (!cache) return;
performanceStore.recordWorkbenchCache({
route: url?.pathname ?? "/v1/workbench",
cacheKeyClass: cache.class,
cacheStatus: cache.status,
operation: "read",
durationMs: nowMs() - startedAt,
payloadBytes: cache.payloadBytes,
cacheAgeMs: cache.cacheAgeMs,
dbQueryAvoided: cache.dbQueryAvoided,
freshnessSloMs: cache.freshnessSloMs,
projectionSeq: cache.projectionSeq
});
}
function recordWorkbenchProjectionMetric(options = {}, { result = null, trace = null, projection = null, diagnostic = null } = {}) {
const performanceStore = options.backendPerformanceStore ?? options.backendPerformance;
if (!performanceStore?.recordWorkbenchProjection || !projection) return;
@@ -405,6 +424,7 @@ async function authenticateWorkbenchRead(request, response, options) {
}
async function handleWorkbenchSessionList(request, response, url, options, actor) {
const startedAt = nowMs();
if (url.searchParams.has("projectId") || url.searchParams.has("workspaceId")) return sendJson(response, 400, workbenchError("workbench_authority_removed", "Workbench session list is keyed by sessionId only."));
const limit = boundedSessionListLimit(url.searchParams.get("limit"));
const offset = cursorOffset(url.searchParams.get("cursor") ?? url.searchParams.get("after"));
@@ -421,6 +441,7 @@ async function handleWorkbenchSessionList(request, response, url, options, actor
includeSessionId: includeRouteId ?? null,
actor: { id: actor.id, role: actor.role ?? "user", valuesRedacted: true }
});
recordWorkbenchCacheMetric(options, url, payload, startedAt);
return sendJson(response, 200, payload);
}
@@ -1147,6 +1168,7 @@ async function handleWorkbenchTurnSnapshot(response, url, options, actor, rawTur
contractVersion: "workbench-turn-snapshot-v1",
turn: snapshot,
projection,
cache: result.cache ?? null,
projectionStatus: projection.projectionStatus,
projectionHealth: projection.projectionHealth,
lastProjectedSeq: projection.lastProjectedSeq,
@@ -1162,10 +1184,12 @@ async function handleWorkbenchTurnSnapshot(response, url, options, actor, rawTur
diagnostic: projection
});
recordWorkbenchTurnReadMetric(options, url, 200, body, startedAt);
recordWorkbenchCacheMetric(options, url, body, startedAt);
sendJson(response, 200, body);
}
async function handleWorkbenchTraceEventPage(response, url, options, actor, rawTraceId) {
const startedAt = nowMs();
const traceId = safeTraceId(rawTraceId);
if (!traceId) return sendJson(response, 400, workbenchError("invalid_trace_id", "traceId must start with trc_.", { traceId: rawTraceId }));
const readModel = createWorkbenchReadModel(options, actor);
@@ -1199,7 +1223,7 @@ async function handleWorkbenchTraceEventPage(response, url, options, actor, rawT
page.blocker = blocker;
}
const responseProjection = page.blocker ? blockedTraceEventProjection(projection, page.blocker) : projection;
sendJson(response, 200, {
const body = {
ok: true,
status: page.blocker ? "blocked" : "succeeded",
contractVersion: "workbench-trace-events-v1",
@@ -1208,6 +1232,7 @@ async function handleWorkbenchTraceEventPage(response, url, options, actor, rawT
threadId: safeOpaqueId(session?.threadId) ?? (textValue(session?.threadId) || null),
...page,
projection: responseProjection,
cache: pageResult.cache ?? metadata.cache ?? null,
projectionStatus: responseProjection.projectionStatus,
projectionHealth: responseProjection.projectionHealth,
lastProjectedSeq: responseProjection.lastProjectedSeq,
@@ -1222,7 +1247,9 @@ async function handleWorkbenchTraceEventPage(response, url, options, actor, rawT
durationMs: responseProjection.durationMs,
valuesRedacted: true,
secretMaterialStored: false
});
};
recordWorkbenchCacheMetric(options, url, body, startedAt);
sendJson(response, 200, body);
}
function traceEventPageFromFacts(sourceEvents, options, metadata = {}) {