@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 CHANGED
@@ -1,33 +1,23 @@
1
1
  {
2
2
  "name": "@sting8k/pi-vcc",
3
- "version": "0.1.0",
4
- "description": "Structured conversation compactor for pi - replaces default compact summary with an agent-optimized structured format.",
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
- "license": "ISC",
17
- "author": "sting8k",
18
- "type": "commonjs",
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"
@@ -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")
@@ -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