Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/context-usage.spec.ts

typescript213 行查看原文
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ElectronApplication } from "@playwright/test";
import { chunk, deferred, done, fakeServer, send } from "../fake-server.ts";
import { runCleanup } from "../helpers/cleanup.ts";
import { launchDesktop } from "../helpers/desktop.ts";
import { expect, test } from "../helpers/test.ts";

test("context indicator hides after compaction and returns when the next response reports usage", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-context-compaction-"));
  await mkdir(join(dir, "agent"));
  await writeFile(join(dir, "agent/settings.json"), JSON.stringify({ compaction: { keepRecentTokens: 0 } }));
  const next = deferred();
  const server = await fakeServer(async (body, response) => {
    const messages = body.messages as unknown as { role: string; content: string }[];
    const user = messages.findLast((message) => message.role === "user")?.content;
    send(response, chunk({ content: "response" }));
    if (user === "after compaction") await next.promise;
    send(response, {
      ...chunk({}),
      choices: [],
      usage: { prompt_tokens: 2048, completion_tokens: 8, total_tokens: 2056 },
    });
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: server.url });
    const page = await app.firstWindow();
    const indicator = page.getByRole("button", { name: "上下文占用", exact: true });
    await expect(indicator).toHaveCount(0);
    for (const [index, message] of ["first", "second"].entries()) {
      await page.getByLabel("消息", { exact: true }).fill(message);
      await page.getByLabel("发送", { exact: true }).click();
      await expect(page.getByTestId("run")).toHaveCount(index + 1);
      await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    }
    await expect(indicator).toBeVisible();
    await page.getByLabel("消息", { exact: true }).fill("/compact");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("compaction-divider")).toHaveText("上下文已压缩");
    await expect(indicator).toHaveCount(0);
    await page.reload();
    await expect(page.getByTestId("run")).toHaveCount(2);
    await expect(indicator).toHaveCount(0);
    await page.getByLabel("消息", { exact: true }).fill("/compact");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("compaction-divider")).toHaveCount(2);
    await expect(page.getByTestId("compaction-divider").last()).toHaveText("上下文已是最新,无需压缩");
    await expect(indicator).toHaveCount(0);
    await page.getByLabel("消息", { exact: true }).fill("after compaction");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "running");
    await expect(indicator).toHaveCount(0);
    next.resolve();
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await expect(indicator).toBeVisible();
  } finally {
    next.resolve();
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("context indicator appears after the first response and keeps measured usage across model switches and restart", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-context-indicator-"));
  const first = deferred();
  const second = deferred();
  const server = await fakeServer(async (body, response) => {
    const messages = body.messages as unknown as { role: string; content: string }[];
    const user = messages.findLast((message) => message.role === "user")?.content;
    send(response, chunk({ content: `response ${user}` }));
    await (user === "first" ? first.promise : second.promise);
    send(response, {
      ...chunk({}),
      choices: [],
      usage: {
        prompt_tokens: body.model === "context-other" ? 1024 : 2048,
        completion_tokens: 8,
        total_tokens: body.model === "context-other" ? 1032 : 2056,
        prompt_tokens_details: { cached_tokens: body.model === "context-other" ? 512 : 1536 },
      },
    });
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: server.url });
    let page = await app.firstWindow();
    await page.evaluate(async (url) => {
      const result = await window.zpi.saveProvider({
        id: "context-other",
        name: "Other provider",
        baseUrl: url,
        apiKey: "isolated-key",
        models: [{ id: "context-other", reasoning: false, contextWindow: 4096, maxTokens: 512 }],
      });
      if (!result.ok) throw new Error(result.error.message);
    }, server.url);
    await page.reload();
    const choose = async (model: string) => {
      await page.getByLabel("模型选择", { exact: true }).click();
      await page.getByRole("menuitem", { name: model, exact: true }).hover();
      await page.getByRole("menuitem", { name: "关闭", exact: true }).click();
      await expect(page.getByLabel("模型选择", { exact: true })).toContainText(model);
    };
    const indicator = () => page.getByRole("button", { name: "上下文占用", exact: true });
    const tooltip = () => page.getByRole("tooltip");
    await expect(page.getByLabel("消息", { exact: true })).toBeVisible();
    await expect(indicator()).toHaveCount(0);
    await page.getByLabel("消息", { exact: true }).fill("first");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.locator(".answer")).toContainText("response first");
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "running");
    await expect(indicator()).toHaveCount(0);
    first.resolve();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    await expect(indicator()).toBeVisible();
    await indicator().hover();
    await expect(tooltip()).toContainText("75.0%");
    const breakdown = await page.getByTestId("context-breakdown").innerText();
    await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.setSize(1200, 900));
    await page.getByLabel("展开右侧栏", { exact: true }).click();
    const rightHandle = page.getByRole("separator", { name: "右侧栏宽度", exact: true });
    for (let i = 0; i < 9; i++) await rightHandle.press("ArrowLeft");
    const panel = page.locator(".workspace-surface > main");
    await expect.poll(async () => (await panel.boundingBox())?.width).toBeLessThan(400);
    const expectUnclippedTooltip = async () => {
      await expect(tooltip()).toBeVisible();
      await expect
        .poll(async () => {
          const popup = await tooltip().boundingBox();
          const main = await panel.boundingBox();
          return Boolean(
            popup &&
              main &&
              popup.x >= main.x + 8 &&
              popup.x + popup.width <= main.x + main.width - 8 &&
              popup.y >= main.y + 8 &&
              popup.y + popup.height <= main.y + main.height - 8,
          );
        })
        .toBe(true);
      expect(
        await tooltip().evaluate((el) => {
          const box = el.getBoundingClientRect();
          return [
            [box.left + 8, box.top + 8],
            [box.right - 8, box.top + 8],
            [box.left + 8, box.bottom - 8],
            [box.right - 8, box.bottom - 8],
          ].every(([x, y]) => el.contains(document.elementFromPoint(x, y)));
        }),
      ).toBe(true);
    };
    await indicator().hover();
    await expectUnclippedTooltip();
    await page.screenshot({ path: "test-results/context-indicator-narrow-panel.png" });
    // Radix's hover grace area closes on movement after pointerleave.
    await page.mouse.move(0, 0, { steps: 12 });
    await expect(tooltip()).toBeHidden();
    await indicator().focus();
    await expectUnclippedTooltip();
    await indicator().press("Escape");
    await expect(tooltip()).toBeHidden();
    await page.getByLabel("收起右侧栏", { exact: true }).click();
    await choose("context-other");
    await indicator().hover();
    await expect(tooltip()).not.toContainText("未知");
    await expect(page.getByTestId("context-breakdown")).toHaveText(breakdown, { useInnerText: true });
    await expect(tooltip().getByRole("progressbar")).toHaveAttribute("aria-valuenow", "50");
    await page.getByLabel("消息", { exact: true }).fill("second");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "running");
    await expect(indicator()).toBeVisible();
    await indicator().hover();
    await expect(tooltip()).not.toContainText("未知");
    await expect(page.getByTestId("context-breakdown")).toHaveText(breakdown, { useInnerText: true });
    second.resolve();
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await indicator().hover();
    await expect(tooltip().getByRole("progressbar")).toHaveAttribute("aria-valuenow", "25");
    await page.screenshot({ path: "test-results/context-indicator.png" });
    await app.close();
    app = await launchDesktop({ dir, url: server.url });
    page = await app.firstWindow();
    await expect(page.getByTestId("run")).toHaveCount(2);
    await expect(indicator()).toBeVisible();
    await indicator().hover();
    await expect(tooltip().getByRole("progressbar")).toHaveAttribute("aria-valuenow", "25");
    const restoredBreakdown = await page.getByTestId("context-breakdown").innerText();
    await choose("fake");
    await indicator().hover();
    await expect(tooltip()).not.toContainText("未知");
    await expect(page.getByTestId("context-breakdown")).toHaveText(restoredBreakdown, { useInnerText: true });
    await page.getByLabel("新建任务", { exact: true }).first().click();
    await expect(page.getByTestId("run")).toHaveCount(0);
    await expect(indicator()).toHaveCount(0);
  } finally {
    first.resolve();
    second.resolve();
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});