Files
oh-my-pi/packages/coding-agent/test/session-persistence-reasoning-dedup.test.ts
T
can1357 42981154fb feat(coding-agent): deduplicated thinking signatures during persistence
- Implement cleanup logic to drop `thinkingSignature` values from assistant thinking blocks during persistence.
- Identify and drop signatures only when the underlying reasoning data is already recoverable via the `providerPayload` items.
- Ensure orphaned signatures that cannot be reconstructed from the payload are preserved during serialization.
- Add comprehensive test coverage to verify deduplication safety and edge-case handling for missing payloads.
2026-06-28 17:01:04 +02:00

97 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import type { AssistantMessage, ProviderPayload, Usage } from "@oh-my-pi/pi-ai";
import { BlobStore } from "@oh-my-pi/pi-coding-agent/session/blob-store";
import type { SessionMessageEntry } from "@oh-my-pi/pi-coding-agent/session/session-entries";
import { prepareEntryForPersistence } from "@oh-my-pi/pi-coding-agent/session/session-persistence";
import { TempDir } from "@oh-my-pi/pi-utils";
const usage = (): Usage => ({
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
});
function reasoningItem(id: string, encryptedContent: string): Record<string, unknown> {
return { type: "reasoning", id, encrypted_content: encryptedContent };
}
function assistantEntry(
content: AssistantMessage["content"],
providerPayload: ProviderPayload | undefined,
): SessionMessageEntry {
return {
type: "message",
id: "entry-1",
parentId: null,
timestamp: new Date(0).toISOString(),
message: {
role: "assistant",
content,
api: "openai-codex-responses",
provider: "openai-codex",
model: "gpt-5.2-codex",
usage: usage(),
stopReason: "stop",
...(providerPayload ? { providerPayload } : {}),
timestamp: 2,
},
};
}
function persistedAssistant(entry: SessionMessageEntry, blobStore: BlobStore): AssistantMessage {
const persisted = prepareEntryForPersistence(entry, blobStore);
if (persisted.type !== "message" || persisted.message.role !== "assistant") {
throw new Error("Expected persisted assistant message");
}
return persisted.message;
}
// The happy path — duplicate dropped on disk, durable copy preserved through a
// real reload — is covered end-to-end in signature-persistence.test.ts. These
// cases pin the two safety branches: never drop a signature the payload cannot
// reconstruct, and never touch messages with no replay payload at all.
describe("session reasoning-signature dedup", () => {
it("keeps a thinkingSignature the payload does not cover", () => {
using tempDir = TempDir.createSync("@pi-session-reasoning-keep-");
const blobStore = new BlobStore(tempDir.path());
const covered = reasoningItem("rs_covered", "ENC_COVERED");
const orphanSignature = JSON.stringify(reasoningItem("rs_orphan", "ENC_ORPHAN"));
const message = persistedAssistant(
assistantEntry(
[
{ type: "thinking", thinking: "covered", thinkingSignature: JSON.stringify(covered) },
{ type: "thinking", thinking: "orphan", thinkingSignature: orphanSignature },
],
{ type: "openaiResponsesHistory", provider: "openai-codex", items: [covered] },
),
blobStore,
);
const thinkingBlocks = message.content.filter(block => block.type === "thinking");
expect(thinkingBlocks).toHaveLength(2);
// Covered block: signature dropped. Orphan block: signature kept — its encrypted
// reasoning is not recoverable from the payload, so dropping it would lose data.
expect(thinkingBlocks[0]?.thinkingSignature).toBeUndefined();
expect(thinkingBlocks[1]?.thinkingSignature).toBe(orphanSignature);
});
it("leaves thinkingSignatures untouched when there is no provider payload", () => {
using tempDir = TempDir.createSync("@pi-session-reasoning-nopayload-");
const blobStore = new BlobStore(tempDir.path());
const signature = JSON.stringify(reasoningItem("rs_1", "ENC"));
const message = persistedAssistant(
assistantEntry([{ type: "thinking", thinking: "reasoning", thinkingSignature: signature }], undefined),
blobStore,
);
const thinking = message.content.find(block => block.type === "thinking");
if (thinking?.type !== "thinking") throw new Error("Expected thinking block");
expect(thinking.thinkingSignature).toBe(signature);
});
});