mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-13 07:40:18 +00:00
Show per-session token usage in the composer
Quiet chip (context-fill meter + session total) opening a per-model input/output/cache breakdown popover; accumulation from live events, rebuilt from persisted sidecars on load; unit + e2e coverage.
This commit is contained in:
@@ -46,6 +46,11 @@ const SETTINGS = {
|
||||
"anthropic:claude-opus-4-8": "Claude Opus 4.8 · Anthropic",
|
||||
"zai:glm-5.2": "GLM-5.2 · Z AI",
|
||||
},
|
||||
// Context windows (subset — mirrors /v1/settings.model_context_windows); drives the
|
||||
// composer usage chip's context-fill meter.
|
||||
model_context_windows: {
|
||||
"anthropic:claude-opus-4-8": 200_000,
|
||||
},
|
||||
};
|
||||
|
||||
const PERSONAS = {
|
||||
@@ -704,7 +709,18 @@ export async function mockApi(page: import("@playwright/test").Page) {
|
||||
send("assistant_delta", { text: msg.text });
|
||||
// Echo the model the message carried — pins the model-per-message contract (the
|
||||
// composer's visible model must ride on every user_message; 2026-07-04 fix).
|
||||
send("assistant_message", { text: `Echo: ${msg.text} [model=${msg.model || "none"}]` });
|
||||
// `usage` mirrors the real engine's assistant_message sidecar (OPE-42): fixed
|
||||
// counts per turn so the usage-chip specs can assert exact accumulation.
|
||||
send("assistant_message", {
|
||||
text: `Echo: ${msg.text} [model=${msg.model || "none"}]`,
|
||||
usage: {
|
||||
model: msg.model || "anthropic:claude-opus-4-8",
|
||||
input: 1_000,
|
||||
output: 200,
|
||||
cache_read: 8_000,
|
||||
cache_write: 800,
|
||||
},
|
||||
});
|
||||
send("turn_done");
|
||||
} else if (msg.type === "approval") {
|
||||
if (pendingTool === "run_shell") {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Token-usage chip (OPE-42): after a turn reports usage, a quiet meter+count chip appears
|
||||
// in the composer's bottom row; clicking it opens the per-model breakdown popover with the
|
||||
// context-window fill. The fake agent attaches fixed usage to every echo turn
|
||||
// (input 1k / output 200 / cache_read 8k / cache_write 800 — 10k per turn), and the
|
||||
// settings fixture maps the default model to a 200k context window.
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./fixtures";
|
||||
|
||||
test("usage chip appears after a turn and opens the breakdown popover", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
|
||||
// Fresh session: no usage yet — the chip is hidden entirely.
|
||||
await expect(page.getByTestId("usage-chip")).toHaveCount(0);
|
||||
|
||||
const box = page.getByPlaceholder(/Ask the coworker/);
|
||||
await box.fill("hello");
|
||||
await box.press("Enter");
|
||||
await expect(page.getByText("Echo: hello", { exact: false }).first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
|
||||
// Chip shows the session total (1k + 200 + 8k + 800 = 10k).
|
||||
const chip = page.getByTestId("usage-chip");
|
||||
await expect(chip).toContainText("10k");
|
||||
|
||||
// Popover: context fill (9.8k prompt-side of 200k = 5%) + per-model breakdown.
|
||||
await chip.click();
|
||||
const pop = page.getByTestId("usage-popover");
|
||||
await expect(pop).toBeVisible();
|
||||
await expect(pop).toContainText("Context window");
|
||||
await expect(pop).toContainText("9.8k of 200k · 5%");
|
||||
await expect(pop).toContainText("Claude Opus 4.8 · Anthropic");
|
||||
await expect(pop).toContainText("Input");
|
||||
await expect(pop).toContainText("Cache read");
|
||||
await expect(pop).toContainText("10k tokens");
|
||||
|
||||
// Second turn accumulates (totals double), and the scrim click closes the popover.
|
||||
await page.mouse.click(10, 10);
|
||||
await expect(pop).toHaveCount(0);
|
||||
await box.fill("again");
|
||||
await box.press("Enter");
|
||||
await expect(page.getByText("Echo: again", { exact: false }).first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await expect(chip).toContainText("20k");
|
||||
});
|
||||
|
||||
test("usage resets on a new session", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
const box = page.getByPlaceholder(/Ask the coworker/);
|
||||
await box.fill("hello");
|
||||
await box.press("Enter");
|
||||
await expect(page.getByTestId("usage-chip")).toContainText("10k", { timeout: 10_000 });
|
||||
|
||||
// "+ New session" wipes the transcript — and the usage accumulation with it.
|
||||
await page.getByRole("button", { name: /New session/ }).first().click();
|
||||
await expect(page.getByTestId("usage-chip")).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user