@zhushanwen/pi-context-engineering 0.1.2 → 0.1.4

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,12 +1,12 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-context-engineering",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Progressive context compression for Pi — L0 zero-cost cleanup, L1 rule-based condensation, L2 emergency truncation, with recall mechanism.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "pi": {
8
8
  "extensions": [
9
- "./src/index.ts"
9
+ "./index.ts"
10
10
  ]
11
11
  },
12
12
  "keywords": [
@@ -22,9 +22,13 @@
22
22
  ],
23
23
  "peerDependencies": {
24
24
  "@mariozechner/pi-coding-agent": "*",
25
- "@sinclair/typebox": "*"
25
+ "typebox": "*"
26
26
  },
27
27
  "scripts": {
28
- "typecheck": "npx tsc --noEmit"
28
+ "typecheck": "npx tsc --noEmit",
29
+ "test": "vitest run"
30
+ },
31
+ "devDependencies": {
32
+ "vitest": "^4.1.8"
29
33
  }
30
34
  }
@@ -1,24 +1,25 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, expect,it } from "vitest";
2
+
2
3
  import {
4
+ type AgentMessage,
5
+ type AssistantMessage,
6
+ type BashExecutionMessage,
3
7
  compressContext,
4
- validateToolPairing,
8
+ type ContextUsage,
9
+ findCompactBoundary,
5
10
  getToolResultText,
6
- processMicrocompact,
7
11
  processBudget,
8
- findCompactBoundary,
9
- type AgentMessage,
12
+ processMicrocompact,
13
+ type TextContent,
14
+ type ThinkingContent,
10
15
  type ToolCall,
11
- type AssistantMessage,
12
16
  type ToolResultMessage,
13
- type BashExecutionMessage,
14
17
  type UserMessage,
15
- type TextContent,
16
- type ThinkingContent,
17
- type ContextUsage,
18
+ validateToolPairing,
18
19
  } from "../compressor";
19
- import { createRecallStore } from "../recall-store";
20
- import { createFrozenFreshState } from "../frozen-fresh";
21
20
  import { DEFAULT_CONFIG } from "../config";
21
+ import { createFrozenFreshState } from "../frozen-fresh";
22
+ import { createRecallStore } from "../recall-store";
22
23
 
23
24
  // ── Test helpers ──
24
25
 
@@ -1,4 +1,5 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, expect,it } from "vitest";
2
+
2
3
  import { createFrozenFreshState } from "../frozen-fresh";
3
4
 
4
5
  describe("FrozenFreshState", () => {
@@ -1,22 +1,23 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, expect,it } from "vitest";
2
+
3
+ import { handleContextEngineeringCommand, handleContextStatsCommand } from "../commands";
2
4
  import {
3
- compressContext,
4
- validateToolPairing,
5
- getToolResultText,
6
5
  type AgentMessage,
7
- type ToolCall,
8
6
  type AssistantMessage,
9
- type ToolResultMessage,
10
7
  type BashExecutionMessage,
11
- type UserMessage,
8
+ compressContext,
9
+ type ContextUsage,
10
+ getToolResultText,
12
11
  type TextContent,
13
12
  type ThinkingContent,
14
- type ContextUsage,
13
+ type ToolCall,
14
+ type ToolResultMessage,
15
+ type UserMessage,
16
+ validateToolPairing,
15
17
  } from "../compressor";
16
- import { createRecallStore } from "../recall-store";
17
- import { DEFAULT_CONFIG, type ContextEngineeringConfig } from "../config";
18
+ import { type ContextEngineeringConfig,DEFAULT_CONFIG } from "../config";
18
19
  import { createFrozenFreshState } from "../frozen-fresh";
19
- import { handleContextEngineeringCommand, handleContextStatsCommand } from "../commands";
20
+ import { createRecallStore } from "../recall-store";
20
21
 
21
22
  // ── Helpers ──
22
23
 
package/src/commands.ts CHANGED
@@ -1,9 +1,12 @@
1
+ import type { CompressionStats } from "./compressor";
1
2
  import type { ContextEngineeringConfig } from "./config";
2
3
  import { parseLevelArgs } from "./config";
3
- import type { CompressionStats } from "./compressor";
4
4
 
5
5
  // ── 格式化辅助 ──
6
6
 
7
+ // 百分比转换因子(小数 → 整数百分比)
8
+ const PERCENT_FACTOR = 100;
9
+
7
10
  function formatConfigSummary(config: ContextEngineeringConfig): string {
8
11
  const lines = [
9
12
  "Context Engineering Plugin",
@@ -64,7 +67,7 @@ function formatConfigSummary(config: ContextEngineeringConfig): string {
64
67
  if (config.l2.enabled) {
65
68
  lines.push("L2 (Emergency compression):");
66
69
  lines.push(" Enabled: true");
67
- const thresholdPercent = Math.round(config.l2.emergencyThreshold * 100);
70
+ const thresholdPercent = Math.round(config.l2.emergencyThreshold * PERCENT_FACTOR);
68
71
  lines.push(
69
72
  ` Threshold: ${thresholdPercent}% | Protect recent: ${config.l2.protectRecentTurns} turns`,
70
73
  );
@@ -143,6 +146,8 @@ export function handleContextEngineeringCommand(
143
146
  config.l2.enabled = onOff;
144
147
  return `L2 (Emergency compression) ${action === "on" ? "enabled" : "disabled"}.`;
145
148
  }
149
+ // Defensive fallback — should be unreachable if parseLevelArgs validates correctly
150
+ return USAGE_HELP;
146
151
  }
147
152
 
148
153
  export function handleContextStatsCommand(stats: CompressionStats): string {
package/src/compressor.ts CHANGED
@@ -1,148 +1,54 @@
1
1
  // compressor.ts — L0/L1/L2 compression engine with tool pairing validation
2
2
 
3
- import type { L0Config, L1Config, L2Config, McConfig, BudgetConfig, ContextEngineeringConfig } from "./config.ts";
4
- import type { RecallStore } from "./recall-store.ts";
3
+ import type { BudgetConfig, ContextEngineeringConfig,L0Config, L1Config, L2Config, McConfig } from "./config.ts";
5
4
  import type { FrozenFreshState } from "./frozen-fresh.ts";
5
+ import type { RecallStore } from "./recall-store.ts";
6
+
7
+ // Re-export types from types.ts for backward compatibility
8
+ export type {
9
+ AgentMessage,
10
+ AssistantMessage,
11
+ BashExecutionMessage,
12
+ BudgetStats,
13
+ CompactionSummaryMessage,
14
+ CompressionStats,
15
+ ContextUsage,
16
+ ImageContent,
17
+ L0Stats,
18
+ McStats,
19
+ TextContent,
20
+ ThinkingContent,
21
+ ToolCall,
22
+ ToolResultMessage,
23
+ TurnBoundary,
24
+ UserMessage,
25
+ } from "./types.ts";
26
+
27
+ // Import types for internal use
28
+ import type {
29
+ AgentMessage,
30
+ BudgetStats,
31
+ CompressionStats,
32
+ ContextUsage,
33
+ L0Stats,
34
+ McStats,
35
+ TextContent,
36
+ ThinkingContent,
37
+ ToolResultMessage,
38
+ TurnBoundary,
39
+ } from "./types.ts";
6
40
 
7
41
  // chars→tokens 估算因子和 fallback 上下文窗口大小
8
42
  const CHARS_PER_TOKEN = 4;
9
43
  const DEFAULT_CONTEXT_WINDOW = 200_000;
10
44
 
11
- // ── Message types (structural subset of pi-ai + Pi coding agent) ──
12
-
13
- export interface TextContent {
14
- type: "text";
15
- text: string;
16
- }
17
-
18
- export interface ThinkingContent {
19
- type: "thinking";
20
- thinking: string;
21
- thinkingSignature?: string;
22
- redacted?: boolean;
23
- }
24
-
25
- export interface ImageContent {
26
- type: "image";
27
- data: string;
28
- mimeType: string;
29
- }
30
-
31
- export interface ToolCall {
32
- type: "toolCall";
33
- id: string;
34
- name: string;
35
- arguments: Record<string, unknown>;
36
- }
37
-
38
- export interface UserMessage {
39
- role: "user";
40
- content: string | (TextContent | ImageContent)[];
41
- timestamp: number;
42
- }
43
-
44
- export interface AssistantMessage {
45
- role: "assistant";
46
- content: (TextContent | ThinkingContent | ToolCall)[];
47
- api: string;
48
- provider: string;
49
- model: string;
50
- usage: {
51
- input: number;
52
- output: number;
53
- cacheRead: number;
54
- cacheWrite: number;
55
- totalTokens: number;
56
- cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
57
- };
58
- stopReason: string;
59
- timestamp: number;
60
- }
61
-
62
- export interface ToolResultMessage {
63
- role: "toolResult";
64
- toolCallId: string;
65
- toolName: string;
66
- content: (TextContent | ImageContent)[];
67
- details?: unknown;
68
- isError: boolean;
69
- timestamp: number;
70
- }
71
-
72
- export interface BashExecutionMessage {
73
- role: "bashExecution";
74
- command: string;
75
- output: string;
76
- exitCode: number | undefined;
77
- cancelled: boolean;
78
- truncated: boolean;
79
- fullOutputPath?: string;
80
- timestamp: number;
81
- excludeFromContext?: boolean;
82
- }
83
-
84
- export interface CompactionSummaryMessage {
85
- role: "compactionSummary";
86
- summary: string;
87
- tokensBefore: number;
88
- timestamp: number;
89
- }
90
-
91
- export type AgentMessage =
92
- | UserMessage
93
- | AssistantMessage
94
- | ToolResultMessage
95
- | BashExecutionMessage
96
- | CompactionSummaryMessage;
97
-
98
- export interface ContextUsage {
99
- tokens: number | null;
100
- contextWindow: number;
101
- percent: number | null;
102
- }
103
-
104
- // ── Exported data types ──
105
-
106
- export interface TurnBoundary {
107
- startIndex: number;
108
- endIndex: number; // 不含
109
- timestamp: number;
110
- }
111
-
112
- export interface L0Stats {
113
- expired: number;
114
- truncated: number;
115
- thinkingCleared: number;
116
- }
117
-
118
- export interface CompressionStats {
119
- l0Expired: number;
120
- l0Truncated: number;
121
- l0ThinkingCleared: number;
122
- l1Condensed: number;
123
- l2Triggered: boolean;
124
- validationFailed: boolean;
125
- mcTriggered: boolean;
126
- mcCleared: number;
127
- budgetPersisted: number;
128
- }
129
-
130
- // ── Turn boundary detection ──
131
-
132
45
  // MC: 可被 Microcompact 清理的工具集
133
46
  export const COMPACTABLE_TOOLS = new Set([
134
47
  "read", "bash", "bash_background", "grep", "glob",
135
48
  "web_search", "web_fetch", "edit", "write",
136
49
  ]);
137
50
 
138
- export interface McStats {
139
- triggered: boolean;
140
- cleared: number;
141
- }
142
-
143
- export interface BudgetStats {
144
- persisted: number;
145
- }
51
+ // ── Turn boundary detection ──
146
52
 
147
53
  export function findTurnBoundaries(messages: AgentMessage[]): TurnBoundary[] {
148
54
  if (messages.length === 0) return [];
package/src/config.ts CHANGED
@@ -119,20 +119,21 @@ export function loadConfig(
119
119
  settingsPath?: string,
120
120
  ): ContextEngineeringConfig {
121
121
  const filePath =
122
- settingsPath ?? join(homedir(), ".pi", "agent", "settings.json");
122
+ settingsPath ?? join(homedir(), ".pi", "agent", "extensions", "context-engineering", "config.json");
123
123
 
124
124
  let raw: string;
125
125
  try {
126
126
  raw = readFileSync(filePath, "utf-8");
127
- } catch {
128
- return { ...DEFAULT_CONFIG };
127
+ } catch (err) {
128
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return { ...DEFAULT_CONFIG };
129
+ throw new Error(`Failed to read config ${filePath}: ${(err as Error).message}`);
129
130
  }
130
131
 
131
132
  let parsed: Record<string, unknown>;
132
133
  try {
133
134
  parsed = JSON.parse(raw) as Record<string, unknown>;
134
- } catch {
135
- return { ...DEFAULT_CONFIG };
135
+ } catch (err) {
136
+ throw new Error(`Invalid JSON in ${filePath}: ${(err as Error).message}`);
136
137
  }
137
138
 
138
139
  const override = parsed["context-engineering"];
@@ -148,11 +149,14 @@ export function loadConfig(
148
149
 
149
150
  // ── 命令参数解析 ──
150
151
 
152
+ // 命令参数最小部分数(target + action)
153
+ const MIN_COMMAND_TOKENS = 2;
154
+
151
155
  export function parseLevelArgs(
152
156
  args: string,
153
157
  ): { target: "global" | "l0" | "l1" | "l2" | "mc" | "budget"; action: "on" | "off" } | null {
154
158
  const tokens = args.trim().split(/\s+/);
155
- if (tokens.length < 2) {
159
+ if (tokens.length < MIN_COMMAND_TOKENS) {
156
160
  return null;
157
161
  }
158
162
 
package/src/index.ts CHANGED
@@ -4,15 +4,16 @@ import type {
4
4
  } from "@mariozechner/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
6
 
7
- import { loadConfig, type ContextEngineeringConfig } from "./config";
8
- import { createRecallStore, type RecallStore } from "./recall-store";
9
- import { createFrozenFreshState, type FrozenFreshState } from "./frozen-fresh";
7
+ import { handleContextEngineeringCommand, handleContextStatsCommand } from "./commands";
10
8
  import {
9
+ type AgentMessage as CompressorMessage,
11
10
  compressContext,
12
11
  type CompressionStats,
13
- type AgentMessage as CompressorMessage,
12
+ type ContextUsage as CompressorContextUsage,
14
13
  } from "./compressor";
15
- import { handleContextEngineeringCommand, handleContextStatsCommand } from "./commands";
14
+ import { type ContextEngineeringConfig,loadConfig } from "./config";
15
+ import { createFrozenFreshState, type FrozenFreshState } from "./frozen-fresh";
16
+ import { createRecallStore, type RecallStore } from "./recall-store";
16
17
 
17
18
  const RecallParams = Type.Object({
18
19
  id: Type.String({ description: "Context ID (ctx-xxxxxxxx) to recall" }),
@@ -62,13 +63,13 @@ export default function contextEngineeringExtension(pi: ExtensionAPI): void {
62
63
  frozenFreshState = createFrozenFreshState();
63
64
  });
64
65
 
65
- pi.on("context", (event: any, ctx: any) => {
66
+ pi.on("context", (event: { messages: unknown[] }, ctx: { getContextUsage(): unknown }) => {
66
67
  try {
67
68
  // Pi Extension API types differ from our internal message types.
68
69
  // Both sides define the same shape but TypeScript can't verify across packages.
69
70
  // If Pi's message format changes, compressContext will gracefully fail via the catch below.
70
71
  const msgs = event.messages as unknown as CompressorMessage[];
71
- const result = compressContext(msgs, config, store, ctx.getContextUsage() as unknown as Parameters<typeof compressContext>[3], frozenFreshState);
72
+ const result = compressContext(msgs, config, store, ctx.getContextUsage() as unknown as CompressorContextUsage, frozenFreshState);
72
73
  addStats(cumulativeStats, result.stats);
73
74
  return { messages: result.messages as unknown as (typeof event.messages)[number][] };
74
75
  } catch (err) {
@@ -80,13 +81,17 @@ export default function contextEngineeringExtension(pi: ExtensionAPI): void {
80
81
  }
81
82
  });
82
83
 
84
+ pi.on("session_tree", async () => {
85
+ // 切换分支后,cumulativeStats 将在下次 context 事件时自然更新
86
+ });
87
+
83
88
  pi.registerTool({
84
89
  name: "recall_context",
85
90
  label: "Recall Compressed Context",
86
91
  description: "Recall original content compressed by context engineering. Use when you need the full content of an expired, truncated, or condensed tool result.",
87
92
  promptSnippet: "recall_context(id) — retrieve original content compressed by context engineering",
88
93
  parameters: RecallParams,
89
- execute: async (_tcId: string, params: any, _sig: any, _upd: any, _ctx: any) => recallResult(params.id, store),
94
+ execute: async (_tcId: string, params: { id: string }, _sig: unknown, _upd: unknown, _ctx: unknown) => recallResult(params.id, store),
90
95
  });
91
96
 
92
97
  pi.registerCommand("context-engineering", {
package/src/types.ts ADDED
@@ -0,0 +1,133 @@
1
+ // types.ts — Shared type definitions for context-engineering extension
2
+
3
+ // ── Message content block types ──
4
+
5
+ export interface TextContent {
6
+ type: "text";
7
+ text: string;
8
+ }
9
+
10
+ export interface ThinkingContent {
11
+ type: "thinking";
12
+ thinking: string;
13
+ thinkingSignature?: string;
14
+ redacted?: boolean;
15
+ }
16
+
17
+ export interface ImageContent {
18
+ type: "image";
19
+ data: string;
20
+ mimeType: string;
21
+ }
22
+
23
+ export interface ToolCall {
24
+ type: "toolCall";
25
+ id: string;
26
+ name: string;
27
+ arguments: Record<string, unknown>;
28
+ }
29
+
30
+ // ── Message types (structural subset of pi-ai + Pi coding agent) ──
31
+
32
+ export interface UserMessage {
33
+ role: "user";
34
+ content: string | (TextContent | ImageContent)[];
35
+ timestamp: number;
36
+ }
37
+
38
+ export interface AssistantMessage {
39
+ role: "assistant";
40
+ content: (TextContent | ThinkingContent | ToolCall)[];
41
+ api: string;
42
+ provider: string;
43
+ model: string;
44
+ usage: {
45
+ input: number;
46
+ output: number;
47
+ cacheRead: number;
48
+ cacheWrite: number;
49
+ totalTokens: number;
50
+ cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
51
+ };
52
+ stopReason: string;
53
+ timestamp: number;
54
+ }
55
+
56
+ export interface ToolResultMessage {
57
+ role: "toolResult";
58
+ toolCallId: string;
59
+ toolName: string;
60
+ content: (TextContent | ImageContent)[];
61
+ details?: unknown;
62
+ isError: boolean;
63
+ timestamp: number;
64
+ }
65
+
66
+ export interface BashExecutionMessage {
67
+ role: "bashExecution";
68
+ command: string;
69
+ output: string;
70
+ exitCode: number | undefined;
71
+ cancelled: boolean;
72
+ truncated: boolean;
73
+ fullOutputPath?: string;
74
+ timestamp: number;
75
+ excludeFromContext?: boolean;
76
+ }
77
+
78
+ export interface CompactionSummaryMessage {
79
+ role: "compactionSummary";
80
+ summary: string;
81
+ tokensBefore: number;
82
+ timestamp: number;
83
+ }
84
+
85
+ export type AgentMessage =
86
+ | UserMessage
87
+ | AssistantMessage
88
+ | ToolResultMessage
89
+ | BashExecutionMessage
90
+ | CompactionSummaryMessage;
91
+
92
+ // ── Domain types ──
93
+
94
+ export interface ContextUsage {
95
+ tokens: number | null;
96
+ contextWindow: number;
97
+ percent: number | null;
98
+ }
99
+
100
+ export interface TurnBoundary {
101
+ startIndex: number;
102
+ endIndex: number; // 不含
103
+ timestamp: number;
104
+ }
105
+
106
+ // ── Stats types ──
107
+
108
+ export interface L0Stats {
109
+ expired: number;
110
+ truncated: number;
111
+ thinkingCleared: number;
112
+ }
113
+
114
+ export interface CompressionStats {
115
+ l0Expired: number;
116
+ l0Truncated: number;
117
+ l0ThinkingCleared: number;
118
+ l1Condensed: number;
119
+ l2Triggered: boolean;
120
+ validationFailed: boolean;
121
+ mcTriggered: boolean;
122
+ mcCleared: number;
123
+ budgetPersisted: number;
124
+ }
125
+
126
+ export interface McStats {
127
+ triggered: boolean;
128
+ cleared: number;
129
+ }
130
+
131
+ export interface BudgetStats {
132
+ persisted: number;
133
+ }