feat: harden workflow gates and expose token usage
This commit is contained in:
@@ -38,6 +38,26 @@ test("text_delta accumulates into transcript", () => {
|
||||
expect(useSessionStore.getState().transcript.at(-1)?.text).toBe("Analisi");
|
||||
});
|
||||
|
||||
test("usage events accumulate billed token categories but keep the latest context occupancy", () => {
|
||||
const store = useSessionStore.getState();
|
||||
store.applyEvent({
|
||||
type: "usage",
|
||||
usage: { input: 60_000, cacheRead: 400_000, output: 20_000, totalTokens: 80_000, contextWindow: 200_000 },
|
||||
});
|
||||
store.applyEvent({
|
||||
type: "usage",
|
||||
usage: { input: 50_348, cacheRead: 233_344, output: 13_652, totalTokens: 55_935, contextWindow: 200_000 },
|
||||
});
|
||||
|
||||
expect(useSessionStore.getState().tokenUsage).toEqual({
|
||||
input: 110_348,
|
||||
cacheRead: 633_344,
|
||||
output: 33_652,
|
||||
totalTokens: 55_935,
|
||||
contextWindow: 200_000,
|
||||
});
|
||||
});
|
||||
|
||||
test("folds prompt, lifecycle, streams, tool updates, and gates in chronological order", () => {
|
||||
const store = useSessionStore.getState();
|
||||
store.setPhase("F1");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from "zustand";
|
||||
import type { ActivityEntry, StreamEvent, WidgetDescriptor } from "../api/types";
|
||||
import type { ActivityEntry, StreamEvent, TokenUsage, WidgetDescriptor } from "../api/types";
|
||||
|
||||
interface Entry {
|
||||
role: "assistant";
|
||||
@@ -21,6 +21,7 @@ interface SessionState {
|
||||
// system event — the only end-of-turn signal on the final workflow step, where no
|
||||
// follow-up gate arrives to release the spinner.
|
||||
agentActive: boolean;
|
||||
tokenUsage: TokenUsage | null;
|
||||
applyEvent: (e: StreamEvent) => void;
|
||||
clearPending: () => void;
|
||||
resetSession: () => void;
|
||||
@@ -65,6 +66,7 @@ const empty = {
|
||||
phaseError: null as string | null,
|
||||
seenGateIds: new Set<string>(),
|
||||
agentActive: false,
|
||||
tokenUsage: null as TokenUsage | null,
|
||||
};
|
||||
|
||||
export const useSessionStore = create<SessionState>((set) => ({
|
||||
@@ -145,6 +147,17 @@ export const useSessionStore = create<SessionState>((set) => ({
|
||||
}
|
||||
return { activityLog, agentActive: true };
|
||||
}
|
||||
if (e.type === "usage") {
|
||||
return {
|
||||
tokenUsage: {
|
||||
input: (st.tokenUsage?.input ?? 0) + e.usage.input,
|
||||
cacheRead: (st.tokenUsage?.cacheRead ?? 0) + e.usage.cacheRead,
|
||||
output: (st.tokenUsage?.output ?? 0) + e.usage.output,
|
||||
totalTokens: e.usage.totalTokens,
|
||||
contextWindow: e.usage.contextWindow,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (e.type === "info") {
|
||||
const level = e.level ?? "info";
|
||||
const stepMessages = [...st.stepMessages, { level, text: e.text }];
|
||||
|
||||
Reference in New Issue
Block a user