diff options
| author | Adam Malczewski <[email protected]> | 2026-06-11 12:45:21 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-11 12:45:21 +0900 |
| commit | 27fd0be36b2f6395249de5aacc86e41fe4e0207f (patch) | |
| tree | 67ae766c1985344878d6a2e71da18834fa73e47d /packages/transport-http/src/logic.ts | |
| parent | c2b4c05d91fa88b8d02c055a0e15c22abd8e21f3 (diff) | |
| download | dispatch-27fd0be36b2f6395249de5aacc86e41fe4e0207f.tar.gz dispatch-27fd0be36b2f6395249de5aacc86e41fe4e0207f.zip | |
feat(cache-warming): manual POST /chat/warm trigger endpoint
A frontend 'warm now' button (and fast tests) can trigger a warm on demand
instead of waiting for the automatic timer.
- transport-contract: WarmRequest / WarmResponse wire types
- transport-http: POST /chat/warm → cacheWarmHandle.warm(); 200 with cachePct,
409 when the conversation is generating, 400 on missing conversationId
Live-verified vs claude haiku: seed turn cacheWrite=6799 → POST /chat/warm
returns cacheReadTokens=6799 cachePct=100 (100% hit). 760 vitest + 109 bun green.
Diffstat (limited to 'packages/transport-http/src/logic.ts')
| -rw-r--r-- | packages/transport-http/src/logic.ts | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/packages/transport-http/src/logic.ts b/packages/transport-http/src/logic.ts index f1d5b8c..bb827e2 100644 --- a/packages/transport-http/src/logic.ts +++ b/packages/transport-http/src/logic.ts @@ -71,3 +71,45 @@ export function parseSinceSeq(raw: string | undefined): SinceSeqResult { export function isSinceSeqError(result: SinceSeqResult): result is ParseError { return typeof result === "object"; } + +export interface WarmBodyParsed { + readonly conversationId: string; + readonly model?: string; + readonly cwd?: string; +} + +export function parseWarmBody(body: unknown): WarmBodyParsed | ParseError { + if (body === null || typeof body !== "object") { + return { error: "Request body must be a JSON object" }; + } + + const obj = body as Record<string, unknown>; + + const conversationId = obj.conversationId; + if (typeof conversationId !== "string" || conversationId.length === 0) { + return { error: "Field 'conversationId' is required and must be a non-empty string" }; + } + + const result: Record<string, unknown> = { conversationId }; + + if (obj.model !== undefined) { + if (typeof obj.model !== "string") { + return { error: "Field 'model' must be a string" }; + } + result.model = obj.model; + } + + if (obj.cwd !== undefined) { + if (typeof obj.cwd !== "string") { + return { error: "Field 'cwd' must be a string" }; + } + result.cwd = obj.cwd; + } + + return result as unknown as WarmBodyParsed; +} + +export function computeCachePct(inputTokens: number, cacheReadTokens: number): number { + if (inputTokens <= 0) return 0; + return Math.round(Math.max(0, Math.min(1, cacheReadTokens / inputTokens)) * 100); +} |
