Master seam: plan-block emission + turn usage capture
- src/core/plan-blocks.ts: pure emission builder — full block on successful plan_init, neighborhood block when the derived plan state moved since the newest existing block; the as-of state reads from the block's frozen snapshot (transcript quiz parts mutate in place on answer, so re-derivation is post-mutation blind). Idempotent by construction. - turn-service persistTurn enriches with withPlanBlocks at the canonical write; chat.tsx adopts the record once the server agrees the turn is done (endTurn flips only after persist). - chat-handler: per-request usage collector via agent-level onStepFinish (StepResult.usage), appended at onFinish to ChatRecord.turns via appendTurnUsage, idempotent per response message id.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import type { UIMessage } from "ai";
|
||||
import { withPlanBlocks } from "../core/plan-blocks.ts";
|
||||
import { buildGraphContext } from "../core/graph-context.ts";
|
||||
import { getGraph, pendingQuizAnswers } from "../core/graph.ts";
|
||||
import type { ChatStore, GraphStore } from "../core/ports.ts";
|
||||
@@ -76,7 +77,12 @@ export function withMessageIds(messages: UIMessage[]): UIMessage[] {
|
||||
|
||||
export function persistTurn(chatStore: ChatStore, chatId: string, messages: UIMessage[], isAborted: boolean): void {
|
||||
try {
|
||||
chatStore.saveMessages(chatId, withMessageIds(messages));
|
||||
const deduped = withMessageIds(messages);
|
||||
// Plan-block emission (wave 4): blocks are frozen transcript content —
|
||||
// attached here, once, at the canonical state write. The tree was
|
||||
// saved by the plan tool during the turn; snapshots derive now.
|
||||
const tree = chatStore.getChat(chatId)?.tree;
|
||||
chatStore.saveMessages(chatId, withPlanBlocks(deduped, tree));
|
||||
} catch (err) {
|
||||
console.error(`[chat ${chatId}] persist failed:`, err, { isAborted });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user