mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
277 lines
7.1 KiB
TypeScript
277 lines
7.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
projectSessionForAnalytics,
|
|
SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
type SessionAnalyticsRecord,
|
|
} from "../src/core/session-analytics.ts";
|
|
import type { SessionEntry, SessionHeader } from "../src/core/session-manager.ts";
|
|
|
|
const header: SessionHeader = {
|
|
type: "session",
|
|
version: 3,
|
|
id: "session-1",
|
|
timestamp: "2026-01-02T03:04:05.000Z",
|
|
cwd: "/tmp/project",
|
|
parentSession: "/tmp/parent.jsonl",
|
|
};
|
|
|
|
const entries: SessionEntry[] = [
|
|
{
|
|
type: "model_change",
|
|
id: "model-1",
|
|
parentId: null,
|
|
timestamp: "2026-01-02T03:04:06.000Z",
|
|
provider: "anthropic",
|
|
modelId: "claude-sonnet-4-5",
|
|
},
|
|
{
|
|
type: "message",
|
|
id: "user-1",
|
|
parentId: "model-1",
|
|
timestamp: "2026-01-02T03:04:07.000Z",
|
|
message: {
|
|
role: "user",
|
|
content: [
|
|
{ type: "text", text: "secret user prompt" },
|
|
{ type: "image", data: "secret-image-data", mimeType: "image/png" },
|
|
],
|
|
timestamp: 1767323047000,
|
|
},
|
|
},
|
|
{
|
|
type: "message",
|
|
id: "assistant-1",
|
|
parentId: "user-1",
|
|
timestamp: "2026-01-02T03:04:08.000Z",
|
|
message: {
|
|
role: "assistant",
|
|
content: [
|
|
{ type: "text", text: "secret assistant answer" },
|
|
{ type: "thinking", thinking: "secret reasoning" },
|
|
{ type: "thinking", thinking: "", thinkingSignature: "secret-signature", redacted: true },
|
|
{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "secret/path.ts" } },
|
|
],
|
|
api: "anthropic-messages",
|
|
provider: "anthropic",
|
|
model: "claude-sonnet-4-5",
|
|
responseModel: "claude-sonnet-4-5-20260101",
|
|
responseId: "secret-response-id",
|
|
usage: {
|
|
input: 100,
|
|
output: 20,
|
|
cacheRead: 30,
|
|
cacheWrite: 40,
|
|
totalTokens: 190,
|
|
cost: {
|
|
input: 0.1,
|
|
output: 0.2,
|
|
cacheRead: 0.03,
|
|
cacheWrite: 0.04,
|
|
total: 0.37,
|
|
},
|
|
},
|
|
stopReason: "toolUse",
|
|
timestamp: 1767323048000,
|
|
},
|
|
},
|
|
{
|
|
type: "message",
|
|
id: "tool-result-1",
|
|
parentId: "assistant-1",
|
|
timestamp: "2026-01-02T03:04:09.000Z",
|
|
message: {
|
|
role: "toolResult",
|
|
toolCallId: "call-1",
|
|
toolName: "read",
|
|
content: [{ type: "text", text: "secret tool result" }],
|
|
isError: true,
|
|
timestamp: 1767323049000,
|
|
},
|
|
},
|
|
{
|
|
type: "compaction",
|
|
id: "compaction-1",
|
|
parentId: "tool-result-1",
|
|
timestamp: "2026-01-02T03:04:10.000Z",
|
|
summary: "secret compaction summary",
|
|
firstKeptEntryId: "user-1",
|
|
tokensBefore: 1234,
|
|
details: { secret: "compaction details" },
|
|
fromHook: true,
|
|
},
|
|
{
|
|
type: "label",
|
|
id: "label-1",
|
|
parentId: "compaction-1",
|
|
timestamp: "2026-01-02T03:04:11.000Z",
|
|
targetId: "assistant-1",
|
|
label: "secret label",
|
|
},
|
|
{
|
|
type: "session_info",
|
|
id: "session-info-1",
|
|
parentId: "label-1",
|
|
timestamp: "2026-01-02T03:04:12.000Z",
|
|
name: "secret session name",
|
|
},
|
|
];
|
|
|
|
describe("projectSessionForAnalytics", () => {
|
|
it("projects a complete session into ordered analytics records", () => {
|
|
const records = projectSessionForAnalytics(header, entries, {
|
|
modifiedAt: "2026-01-03T04:05:06.000Z",
|
|
hashString: (value) => `hashed:${value}`,
|
|
});
|
|
|
|
expect(records).toEqual([
|
|
{
|
|
recordType: "session",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
version: 3,
|
|
createdAt: "2026-01-02T03:04:05.000Z",
|
|
modifiedAt: "2026-01-03T04:05:06.000Z",
|
|
parentSessionHash: "hashed:/tmp/parent.jsonl",
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "model-1",
|
|
parentEntryId: null,
|
|
entryType: "model_change",
|
|
timestamp: "2026-01-02T03:04:06.000Z",
|
|
provider: "anthropic",
|
|
modelId: "claude-sonnet-4-5",
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "user-1",
|
|
parentEntryId: "model-1",
|
|
entryType: "message",
|
|
timestamp: "2026-01-02T03:04:07.000Z",
|
|
role: "user",
|
|
contentStats: {
|
|
stringContent: false,
|
|
textBlocks: 1,
|
|
imageBlocks: 1,
|
|
thinkingBlocks: 0,
|
|
redactedThinkingBlocks: 0,
|
|
toolCallBlocks: 0,
|
|
otherBlocks: 0,
|
|
},
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "assistant-1",
|
|
parentEntryId: "user-1",
|
|
entryType: "message",
|
|
timestamp: "2026-01-02T03:04:08.000Z",
|
|
role: "assistant",
|
|
api: "anthropic-messages",
|
|
provider: "anthropic",
|
|
model: "claude-sonnet-4-5",
|
|
responseModel: "claude-sonnet-4-5-20260101",
|
|
stopReason: "toolUse",
|
|
hasError: false,
|
|
usage: {
|
|
input: 100,
|
|
output: 20,
|
|
cacheRead: 30,
|
|
cacheWrite: 40,
|
|
totalTokens: 190,
|
|
costInput: 0.1,
|
|
costOutput: 0.2,
|
|
costCacheRead: 0.03,
|
|
costCacheWrite: 0.04,
|
|
costTotal: 0.37,
|
|
},
|
|
contentStats: {
|
|
stringContent: false,
|
|
textBlocks: 1,
|
|
imageBlocks: 0,
|
|
thinkingBlocks: 2,
|
|
redactedThinkingBlocks: 1,
|
|
toolCallBlocks: 1,
|
|
otherBlocks: 0,
|
|
},
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "tool-result-1",
|
|
parentEntryId: "assistant-1",
|
|
entryType: "message",
|
|
timestamp: "2026-01-02T03:04:09.000Z",
|
|
role: "toolResult",
|
|
isError: true,
|
|
contentStats: {
|
|
stringContent: false,
|
|
textBlocks: 1,
|
|
imageBlocks: 0,
|
|
thinkingBlocks: 0,
|
|
redactedThinkingBlocks: 0,
|
|
toolCallBlocks: 0,
|
|
otherBlocks: 0,
|
|
},
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "compaction-1",
|
|
parentEntryId: "tool-result-1",
|
|
entryType: "compaction",
|
|
timestamp: "2026-01-02T03:04:10.000Z",
|
|
firstKeptEntryId: "user-1",
|
|
tokensBefore: 1234,
|
|
fromHook: true,
|
|
hasDetails: true,
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "label-1",
|
|
parentEntryId: "compaction-1",
|
|
entryType: "label",
|
|
timestamp: "2026-01-02T03:04:11.000Z",
|
|
targetId: "assistant-1",
|
|
hasLabel: true,
|
|
},
|
|
{
|
|
recordType: "entry",
|
|
schemaVersion: SESSION_ANALYTICS_SCHEMA_VERSION,
|
|
sessionId: "session-1",
|
|
entryId: "session-info-1",
|
|
parentEntryId: "label-1",
|
|
entryType: "session_info",
|
|
timestamp: "2026-01-02T03:04:12.000Z",
|
|
hasName: true,
|
|
},
|
|
] satisfies SessionAnalyticsRecord[]);
|
|
});
|
|
|
|
it("omits raw private payload fields", () => {
|
|
const serialized = JSON.stringify(projectSessionForAnalytics(header, entries));
|
|
|
|
expect(serialized).not.toContain("secret user prompt");
|
|
expect(serialized).not.toContain("secret assistant answer");
|
|
expect(serialized).not.toContain("secret reasoning");
|
|
expect(serialized).not.toContain("secret-image-data");
|
|
expect(serialized).not.toContain("secret/path.ts");
|
|
expect(serialized).not.toContain("secret tool result");
|
|
expect(serialized).not.toContain("secret compaction summary");
|
|
expect(serialized).not.toContain("compaction details");
|
|
expect(serialized).not.toContain("secret label");
|
|
expect(serialized).not.toContain("secret session name");
|
|
expect(serialized).not.toContain("secret-response-id");
|
|
expect(serialized).not.toContain("/tmp/project");
|
|
});
|
|
});
|