@sting8k/pi-vcc 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -21
- package/src/core/content.ts +1 -0
- package/src/core/normalize.ts +2 -1
- package/src/core/render-entries.ts +9 -2
- package/tests/content.test.ts +31 -0
- package/tests/render-entries.test.ts +22 -0
package/package.json
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sting8k/pi-vcc",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Algorithmic conversation compactor for pi - transcript-preserving structured summaries, no LLM calls",
|
|
5
|
+
"main": "index.ts",
|
|
5
6
|
"keywords": [
|
|
6
|
-
"pi-package"
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"vcc",
|
|
10
|
+
"compact",
|
|
11
|
+
"compaction"
|
|
7
12
|
],
|
|
8
|
-
"homepage": "https://github.com/sting8k/pi-vcc#readme",
|
|
9
|
-
"bugs": {
|
|
10
|
-
"url": "https://github.com/sting8k/pi-vcc/issues"
|
|
11
|
-
},
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
14
15
|
"url": "git+https://github.com/sting8k/pi-vcc.git"
|
|
15
16
|
},
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"main": "index.ts",
|
|
20
|
-
"directories": {
|
|
21
|
-
"test": "tests"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"test": "bun test",
|
|
25
|
-
"bench": "bun run ./scripts/benchmark-real-sessions.ts"
|
|
26
|
-
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@mariozechner/pi-coding-agent": "*"
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
19
|
+
"@sinclair/typebox": "*"
|
|
29
20
|
},
|
|
30
|
-
"devDependencies": {},
|
|
31
21
|
"pi": {
|
|
32
22
|
"extensions": [
|
|
33
23
|
"./index.ts"
|
package/src/core/content.ts
CHANGED
|
@@ -10,6 +10,7 @@ export const firstLine = (text: string, max = 200): string =>
|
|
|
10
10
|
clip(text.split("\n")[0] ?? "", max);
|
|
11
11
|
|
|
12
12
|
export const textParts = (content: Message["content"]): string[] => {
|
|
13
|
+
if (!content) return [];
|
|
13
14
|
if (typeof content === "string") return [content];
|
|
14
15
|
return content
|
|
15
16
|
.filter((part) => part.type === "text")
|
package/src/core/normalize.ts
CHANGED
|
@@ -8,7 +8,7 @@ const normalizeOne = (msg: Message): NormalizedBlock[] => {
|
|
|
8
8
|
const blocks: NormalizedBlock[] = [];
|
|
9
9
|
const text = sanitize(textOf(msg.content));
|
|
10
10
|
if (text) blocks.push({ kind: "user", text });
|
|
11
|
-
if (typeof msg.content !== "string") {
|
|
11
|
+
if (msg.content && typeof msg.content !== "string") {
|
|
12
12
|
for (const part of msg.content) {
|
|
13
13
|
if (part.type === "image") {
|
|
14
14
|
blocks.push({ kind: "user", text: `[image: ${part.mimeType}]` });
|
|
@@ -28,6 +28,7 @@ const normalizeOne = (msg: Message): NormalizedBlock[] => {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (msg.role === "assistant") {
|
|
31
|
+
if (!msg.content) return [];
|
|
31
32
|
if (typeof msg.content === "string") {
|
|
32
33
|
return [{ kind: "assistant", text: sanitize(msg.content) }];
|
|
33
34
|
}
|
|
@@ -11,7 +11,7 @@ export interface RenderedEntry {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const toolCalls = (content: Message["content"]): string => {
|
|
14
|
-
if (typeof content === "string") return "";
|
|
14
|
+
if (!content || typeof content === "string") return "";
|
|
15
15
|
return content
|
|
16
16
|
.filter((c) => c.type === "toolCall")
|
|
17
17
|
.map((c) => `${c.name}(${summarizeToolArgs(c.arguments)})`)
|
|
@@ -19,7 +19,7 @@ const toolCalls = (content: Message["content"]): string => {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const extractFilesFromContent = (content: Message["content"]): string[] => {
|
|
22
|
-
if (typeof content === "string") return [];
|
|
22
|
+
if (!content || typeof content === "string") return [];
|
|
23
23
|
return content
|
|
24
24
|
.filter((c) => c.type === "toolCall")
|
|
25
25
|
.map((c) => extractPath(c.arguments))
|
|
@@ -38,6 +38,13 @@ export const renderMessage = (msg: Message, index: number, full = false): Render
|
|
|
38
38
|
summary: `${prefix}[${msg.toolName}] ${text}`,
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
// bashExecution has command+output instead of content
|
|
42
|
+
if ((msg as any).role === "bashExecution") {
|
|
43
|
+
const cmd = (msg as any).command ?? "";
|
|
44
|
+
const out = (msg as any).output ?? "";
|
|
45
|
+
const text = full ? `$ ${cmd}\n${out}` : clip(`$ ${cmd}\n${out}`, 300);
|
|
46
|
+
return { index, role: "bash", summary: text };
|
|
47
|
+
}
|
|
41
48
|
const text = full ? textOf(msg.content) : clip(textOf(msg.content), 300);
|
|
42
49
|
const tools = toolCalls(msg.content);
|
|
43
50
|
const files = extractFilesFromContent(msg.content);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { textParts, textOf, clip, firstLine } from "../src/core/content";
|
|
3
|
+
|
|
4
|
+
describe("textParts", () => {
|
|
5
|
+
it("returns [] for undefined content", () => {
|
|
6
|
+
expect(textParts(undefined as any)).toEqual([]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns [] for null content", () => {
|
|
10
|
+
expect(textParts(null as any)).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("wraps string content", () => {
|
|
14
|
+
expect(textParts("hello")).toEqual(["hello"]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("extracts text parts from array content", () => {
|
|
18
|
+
const content = [
|
|
19
|
+
{ type: "text" as const, text: "first" },
|
|
20
|
+
{ type: "toolCall" as const, name: "x", id: "1", arguments: {} },
|
|
21
|
+
{ type: "text" as const, text: "second" },
|
|
22
|
+
];
|
|
23
|
+
expect(textParts(content)).toEqual(["first", "second"]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("textOf", () => {
|
|
28
|
+
it("returns empty string for undefined content", () => {
|
|
29
|
+
expect(textOf(undefined as any)).toBe("");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -36,5 +36,27 @@ describe("renderMessage", () => {
|
|
|
36
36
|
const r = renderMessage(userMsg(long), 0);
|
|
37
37
|
expect(r.summary.length).toBeLessThanOrEqual(300);
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
it("renders bashExecution message", () => {
|
|
41
|
+
const msg = { role: "bashExecution", command: "ls -la", output: "total 0\n" } as any;
|
|
42
|
+
const r = renderMessage(msg, 5);
|
|
43
|
+
expect(r.role).toBe("bash");
|
|
44
|
+
expect(r.summary).toContain("$ ls -la");
|
|
45
|
+
expect(r.summary).toContain("total 0");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("renders bashExecution with missing output", () => {
|
|
49
|
+
const msg = { role: "bashExecution", command: "exit 1" } as any;
|
|
50
|
+
const r = renderMessage(msg, 6);
|
|
51
|
+
expect(r.role).toBe("bash");
|
|
52
|
+
expect(r.summary).toContain("$ exit 1");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("handles message with undefined content", () => {
|
|
56
|
+
const msg = { role: "assistant", content: undefined } as any;
|
|
57
|
+
const r = renderMessage(msg, 3);
|
|
58
|
+
expect(r.role).toBe("assistant");
|
|
59
|
+
expect(r.summary).toBe("");
|
|
60
|
+
});
|
|
39
61
|
});
|
|
40
62
|
|