summaryrefslogtreecommitdiffhomepage
path: root/packages/transport-http/src/logic.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-11 12:45:21 +0900
committerAdam Malczewski <[email protected]>2026-06-11 12:45:21 +0900
commit27fd0be36b2f6395249de5aacc86e41fe4e0207f (patch)
tree67ae766c1985344878d6a2e71da18834fa73e47d /packages/transport-http/src/logic.ts
parentc2b4c05d91fa88b8d02c055a0e15c22abd8e21f3 (diff)
downloaddispatch-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.ts42
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);
+}