42981154fb
- 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.
97 lines
3.7 KiB
TypeScript
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);
|
|
});
|
|
});
|