mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 18:59:33 +01:00
perf(story-mode): 304 for an unchanged page, and a longer read for a turn bigger than the tail
GET /story now sends a weak ETag (server start, transcript size and mtime, turn) with Cache-Control: no-cache, and answers 304 before building anything when the transcript has not changed, so a live view's polls cost a stat. A turn can be bigger than the 8 MB tail on its own (tool results carry whole files, ~60 KB per result line on large local transcripts), which cut its first panels. When the window holds no prompt, the reader re-reads a 24 MB, then a 64 MB tail. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d20208bda6
commit
7c21973c54
3 files changed
+93
-20
No files matched your search
@@ -23,6 +23,12 @@ import { findClaudeTranscriptPath } from '../../utils/claude-transcript.js';
|
||||
import { findSessionOrFail } from '../route-helpers.js';
|
||||
import type { ConfigPort, SessionPort } from '../ports/index.js';
|
||||
|
||||
/**
|
||||
* Part of every ETag, so a server restart (possibly with a new page format) never answers 304 for a
|
||||
* page an older server built.
|
||||
*/
|
||||
const SERVER_EPOCH = Date.now().toString(36);
|
||||
|
||||
/** The hook-reported path when it looks like a transcript, else a lookup by conversation id. */
|
||||
async function resolveTranscriptPath(hookPath: string | null, conversationId: string): Promise<string | null> {
|
||||
if (hookPath && hookPath.startsWith('/') && hookPath.endsWith('.jsonl')) return hookPath;
|
||||
@@ -59,13 +65,23 @@ export function registerStoryRoutes(app: FastifyInstance, ctx: SessionPort & Con
|
||||
if (!transcriptPath) return { success: true, data: { supported: true, turns: [], page: null } };
|
||||
|
||||
let entries;
|
||||
let version;
|
||||
try {
|
||||
({ entries } = await readStoryEntries(transcriptPath));
|
||||
({ entries, version } = await readStoryEntries(transcriptPath));
|
||||
} catch {
|
||||
reply.code(500);
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Could not read the session transcript');
|
||||
}
|
||||
|
||||
// A live view refetches every few seconds; while the transcript has not changed, answer 304
|
||||
// before building anything. `no-cache` makes the browser revalidate every time.
|
||||
const etag = `W/"story-${SERVER_EPOCH}-${version}-${encodeURIComponent(typeof turn === 'string' && turn ? turn : 'latest')}"`;
|
||||
reply.header('ETag', etag).header('Cache-Control', 'no-cache');
|
||||
if (req.headers['if-none-match'] === etag) {
|
||||
reply.code(304);
|
||||
return reply.send();
|
||||
}
|
||||
|
||||
const turns = buildStoryTurns(entries);
|
||||
const wanted = typeof turn === 'string' && turn ? turns.find((t) => t.id === turn) : turns[turns.length - 1];
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user