Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
import { type AssistantMessage, type Model, type Models, normalizeContext, uuidv7 } from "@earendil-works/pi-ai";
|
|
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
import {
|
|
AssistantEntry,
|
|
type ConversationId,
|
|
createRegistry,
|
|
type EntryId,
|
|
Harness,
|
|
MemoryStorage,
|
|
ProviderDoc,
|
|
} from "@earendil-works/pi-durable";
|
|
import { describe, expect, it } from "vitest";
|
|
import { streamSimple as streamSimpleOpenAICodexResponses } from "../../ai/src/api/openai-codex-responses.ts";
|
|
import { resolveApiKey } from "../../ai/test/oauth.ts";
|
|
import { context } from "./session-support.ts";
|
|
|
|
const codexToken = await resolveApiKey("openai-codex");
|
|
|
|
function cacheProbe(nonce: string): string {
|
|
const lines = [
|
|
"This is a real automated prompt-cache test. The repeated context below is intentional.",
|
|
"Read it silently, then reply with exactly: CACHE PROBE READY",
|
|
];
|
|
for (let index = 0; index < 180; index++) {
|
|
lines.push(
|
|
`${nonce} immutable cache record ${String(index).padStart(3, "0")}: alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega.`,
|
|
);
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function assistant(harness: Harness, conversationId: ConversationId, answer: EntryId) {
|
|
return harness
|
|
.commit((tx) => tx.entry(AssistantEntry, answer), context)
|
|
.then((record) => {
|
|
if (record?.conversationId !== conversationId) throw new Error("Answer belongs to another conversation");
|
|
return record.model?.[0] as AssistantMessage | undefined;
|
|
});
|
|
}
|
|
|
|
function cacheHitRate(usage: AssistantMessage["usage"]): number {
|
|
const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
return promptTokens === 0 ? 0 : usage.cacheRead / promptTokens;
|
|
}
|
|
|
|
describe("Durable provider session cache e2e", () => {
|
|
// Live regression coverage for #10424; skipped without ~/.pi/agent/auth.json credentials.
|
|
it.skipIf(!codexToken)(
|
|
"reuses the Codex prompt cache across Durable turns",
|
|
{ retry: 2, timeout: 120_000 },
|
|
async () => {
|
|
const base = builtinModels();
|
|
const forwarded: string[] = [];
|
|
const models = new Proxy(base, {
|
|
get(target, property) {
|
|
if (property === "streamSimple") {
|
|
return (
|
|
model: Parameters<Models["streamSimple"]>[0],
|
|
request: Parameters<Models["streamSimple"]>[1],
|
|
options?: Parameters<Models["streamSimple"]>[2],
|
|
) => {
|
|
forwarded.push(options?.sessionId ?? "");
|
|
return streamSimpleOpenAICodexResponses(
|
|
model as Model<"openai-codex-responses">,
|
|
normalizeContext(request),
|
|
{
|
|
...options,
|
|
apiKey: codexToken,
|
|
transport: "sse",
|
|
},
|
|
);
|
|
};
|
|
}
|
|
const value = Reflect.get(target, property, target);
|
|
return typeof value === "function" ? value.bind(target) : value;
|
|
},
|
|
}) as Models;
|
|
const harness = await Harness.open(
|
|
new MemoryStorage(),
|
|
{
|
|
models,
|
|
registry: createRegistry(),
|
|
settings: {
|
|
stream: { transport: "sse" },
|
|
compaction: { enabled: false },
|
|
},
|
|
},
|
|
context,
|
|
);
|
|
try {
|
|
const root = await harness.root(context, {
|
|
agent: { model: { provider: "openai-codex", modelId: "gpt-5.5" } },
|
|
});
|
|
harness.resume();
|
|
const usages: AssistantMessage["usage"][] = [];
|
|
let firstHit: number | undefined;
|
|
for (let turn = 0; turn < 8; turn++) {
|
|
const content = turn === 0 ? cacheProbe(uuidv7()) : `Reply exactly: CACHE PROBE FOLLOW-UP ${turn}`;
|
|
const settled = await (await root.submit({ type: "input", content }, context)).wait(context);
|
|
if (settled.status !== "done" || settled.type !== "input") {
|
|
throw new Error(`Unexpected submission status: ${settled.status}`);
|
|
}
|
|
const message = await assistant(harness, root.id, settled.answer);
|
|
expect(message?.stopReason, message?.errorMessage).not.toBe("error");
|
|
usages.push(message!.usage);
|
|
if (firstHit === undefined && message!.usage.cacheRead > 0) firstHit = usages.length - 1;
|
|
if (firstHit !== undefined && usages.length - firstHit >= 3) break;
|
|
}
|
|
const sessionId = (await harness.snapshot(ProviderDoc, root.id, context))!.sessionId;
|
|
expect(forwarded).toEqual(Array.from({ length: usages.length }, () => sessionId));
|
|
const firstPrompt = usages[0]!.input + usages[0]!.cacheRead + usages[0]!.cacheWrite;
|
|
expect(firstPrompt).toBeGreaterThanOrEqual(4_000);
|
|
expect(usages[0]!.cacheRead).toBe(0);
|
|
expect(firstHit).toBe(1);
|
|
if (firstHit === undefined) throw new Error("Codex reported no prompt-cache hit");
|
|
const cached = usages.slice(firstHit);
|
|
expect(cached).toHaveLength(3);
|
|
for (const usage of cached) expect(cacheHitRate(usage)).toBeGreaterThanOrEqual(0.9);
|
|
} finally {
|
|
await harness.close(context);
|
|
}
|
|
},
|
|
);
|
|
});
|