@zhushanwen/pi-cache-probe 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zhushanwen/pi-cache-probe
2
2
 
3
- 前缀稳定性长期数据采集 extension(设计文档:`docs/todo/cache-probe-design.md`)。零行为影响:只在 system prompt / tools 指纹变化时向 session JSONL 写 `cache-probe` custom entry(不进 LLM 上下文),配合 `analyze.py` 把 turn 首笔 cache miss 归因为「前缀变化(可修)」vs「服务端淘汰(不可修)」。
3
+ 前缀稳定性长期数据采集 extension。零行为影响:只在 system prompt / tools 指纹变化时向 session JSONL 写 `cache-probe` custom entry(不进 LLM 上下文),配合 `analyze.py` 把 turn 首笔 cache miss 归因为「前缀变化(可修)」vs「服务端淘汰(不可修)」。
4
4
 
5
5
  ## 组成
6
6
 
@@ -28,7 +28,7 @@ pi --continue --extension <repo>/extensions/universal/cache-probe/index.ts "<pro
28
28
  ## 归因分析
29
29
 
30
30
  ```bash
31
- python3 extensions/universal/cache-probe/analyze.py ~/.pi/agent/sessions ~/.xyz-agent/pi/sessions
31
+ python3 extensions/universal/cache-probe/analyze.py ~/.pi/agent/sessions ~/.taiji/pi/sessions
32
32
  ```
33
33
 
34
34
  输出五部分:扫描概览 / 命中率基线 / 归因矩阵 / 进程边界漂移 / GO-NO-GO 决策建议。
@@ -37,10 +37,10 @@ python3 extensions/universal/cache-probe/analyze.py ~/.pi/agent/sessions ~/.xyz-
37
37
 
38
38
  - pi CLI 启动路径(`--session` / `--continue`)的 session_start reason 恒为 `startup`(`resume` 仅运行时 switch session 出现)——跨进程识别靠 baseline 标记 + 时间戳 gap,漂移检测靠 hash 对比,均不受影响。
39
39
  - 同进程内修改 AGENTS.md 不进 system prompt(pi 启动快照),contextFiles 漂移只出现在跨进程边界——pi 的行为,不是探针缺陷。
40
- - 真实环境实测发现:extension 链(model-switch / subagent-workflow 等)存在每 turn 级的 system prompt 动态注入,由 spFull 单独捕获——这正是它存在的价值。
40
+ - 真实环境实测发现:extension 链(如 subagent-workflow)存在每 turn 级的 system prompt 动态注入,由 spFull 单独捕获——这正是它存在的价值。
41
41
 
42
42
  ## 日常维护
43
43
 
44
44
  - 校验:`cd extensions/universal/cache-probe && pnpm typecheck && pnpm test`(或仓库级 `pnpm extensions:typecheck && pnpm extensions:test`)
45
45
  - 每日检查:跑一次 analyze.py,确认 `sessions_with_probe` 增长、`error entries` 为 0 或已知原因
46
- - 归因矩阵内有效 turn(analyze.py 第 3 部分 core 过滤:排除间隔>30min、模型切换、探针未覆盖的 turn)满 200 后,analyze.py 第 5 部分给出快照方案 GO/NO-GO 倾向;结论与后续动作记录回设计文档
46
+ - 归因矩阵内有效 turn(analyze.py 第 3 部分 core 过滤:排除间隔>30min、模型切换、探针未覆盖的 turn)满 200 后,analyze.py 第 5 部分给出快照方案 GO/NO-GO 倾向
package/analyze.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """cache-probe 归因分析脚本(设计文档 docs/todo/cache-probe-design.md §7.2)。
2
+ """cache-probe 归因分析脚本(设计文档 cache-probe-design §7.2)。
3
3
 
4
4
  输入一个或多个 sessions 目录,输出:
5
5
  1. 扫描概览(含 error entry / 畸形行计数,失败要出声)
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-cache-probe",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Zero-impact Pi probe extension — records system prompt / tools fingerprint hashes into session custom entries (only on change) to attribute LLM prompt-cache miss to prefix drift vs server-side eviction.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
- "xyz-agent": {
7
+ "taiji": {
8
8
  "role": "universal"
9
9
  },
10
10
  "pi": {
@@ -25,6 +25,9 @@
25
25
  "index.ts",
26
26
  "analyze.py"
27
27
  ],
28
+ "dependencies": {
29
+ "@zhushanwen/pi-ext-guards": "0.4.1"
30
+ },
28
31
  "devDependencies": {
29
32
  "@vitest/coverage-v8": "^4.1.9",
30
33
  "vitest": "^4.1.8"
@@ -74,7 +74,7 @@ describe("extractSystem(payload 四口径)", () => {
74
74
  ]);
75
75
  });
76
76
 
77
- it("Google:顶层 systemInstruction 对象", () => {
77
+ it("伪造对象形态:顶层 systemInstruction(实装恒传 string,仅测试构造可达)", () => {
78
78
  expect(extractSystem({ systemInstruction: { role: "system", parts: [] } })).toEqual({
79
79
  role: "system",
80
80
  parts: [],
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cache-probe 指纹纯函数层 —— 设计文档 docs/todo/cache-probe-design.md §7.1。
2
+ * cache-probe 指纹纯函数层 —— 设计文档 cache-probe-design §7.1。
3
3
  *
4
4
  * entry schema v2(长期生效版,数据量精简为长期采集优化):
5
5
  * - hash 用 sha256 前 16 hex(64bit,同 session 内对比,碰撞概率可忽略)
@@ -10,6 +10,8 @@
10
10
 
11
11
  import { createHash } from "node:crypto";
12
12
 
13
+ import { isRecord } from "@zhushanwen/pi-ext-guards";
14
+
13
15
  /** 指纹 hash 长度(hex 字符数)。 */
14
16
  const HASH_LEN = 16;
15
17
 
@@ -41,10 +43,6 @@ export interface ProbeEntryData {
41
43
  error?: string;
42
44
  }
43
45
 
44
- function isRecord(value: unknown): value is Record<string, unknown> {
45
- return value !== null && typeof value === "object";
46
- }
47
-
48
46
  /** 递归 sort keys 的稳定序列化(undefined 归一为 null,防 key 顺序抖动产生假变化)。 */
49
47
  function stableStringify(value: unknown): string {
50
48
  if (value === undefined) return "null";
@@ -63,7 +61,12 @@ export function hashOf(value: unknown): string {
63
61
  * - OpenAI 兼容:messages 内 role 为 system 或 developer
64
62
  * (pi 源码 ai/src/api/openai-completions.ts 的 useDeveloperRole 分支)
65
63
  * - Anthropic:顶层 system(数组)
66
- * - Google:顶层 systemInstruction(对象)
64
+ * - Google:顶层字段 systemInstruction 为对象形态——仅手工伪造的输入可达:
65
+ * pi-ai 0.84.4 实装(google-generative-ai.js:294 / google-vertex.js:368)恒传
66
+ * string(sanitizeSurrogates(context.systemPrompt)),isRecord 分支对 string
67
+ * 恒不命中,故实装流量下 Google 侧返回 null(spFull 观测恒 no-system)。
68
+ * 归因是「提取只认对象形态」而非「Google 请求没有 system」,勿把下方
69
+ * isRecord(systemInstruction) 分支误读为 Google 口径主通路。
67
70
  */
68
71
  export function extractSystem(payload: unknown): unknown | null {
69
72
  if (!isRecord(payload)) return null;
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * cache-probe — pi file-type extension(前缀稳定性长期数据采集探针)。
3
3
  *
4
- * 设计文档:docs/todo/cache-probe-design.md(§7.1 实现机制)。
4
+ * 设计文档:cache-probe-design(§7.1 实现机制)。
5
5
  *
6
6
  * 记录 9 个指纹 hash(schema v2:短 hash + 增量 entry),变化时才向 session JSONL
7
7
  * 写 custom entry(customType = "cache-probe")。custom entry 不进 LLM 上下文,
@@ -9,7 +9,9 @@
9
9
  * - before_agent_start(每 turn 1 次):算 7 个输入侧 hash 暂存 pending
10
10
  * (systemPromptOptions 各字段 + getAllTools 注册表)
11
11
  * - before_provider_request(每笔 LLM 请求,仅消费 turn 首笔):从最终 payload
12
- * 提取 spFull(system 消息,兼容 system/developer/anthropic/google 口径)与
12
+ * 提取 spFull(system 消息,兼容 system/developer/anthropic/google 口径;
13
+ * google 口径实装流量恒 no-system——pi-ai 恒传 string,该分支仅手工伪造
14
+ * 对象输入可达,详见 fingerprint.ts extractSystem 注释)与
13
15
  * toolsSent(tools 数组),与 pending 合并对比,变化/基线时 appendEntry
14
16
  * - agent_end:turn 内无 provider 请求则丢弃 pending(无请求即无归因价值)
15
17
  *
@@ -21,6 +23,7 @@
21
23
  */
22
24
 
23
25
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
26
+ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
24
27
 
25
28
  import {
26
29
  buildProbeEntry,
@@ -60,12 +63,12 @@ export default function (pi: ExtensionAPI): void {
60
63
  // 输入侧 7 hash 计算在 fingerprint.ts 纯函数层(handler 只做编排与 fail-safe)
61
64
  pending = computePendingFingerprint(event?.systemPromptOptions, pi.getAllTools());
62
65
  } catch (err) {
63
- stderr(`before_agent_start failed: ${err instanceof Error ? err.message : String(err)}`);
66
+ stderr(`before_agent_start failed: ${toErrorMessage(err)}`);
64
67
  pending = null;
65
68
  try {
66
- pi.appendEntry(CUSTOM_TYPE, { v: SCHEMA_VERSION, seq, error: String(err instanceof Error ? err.message : err) });
69
+ pi.appendEntry(CUSTOM_TYPE, { v: SCHEMA_VERSION, seq, error: toErrorMessage(err) });
67
70
  } catch (appendErr) {
68
- stderr(`appendEntry failed: ${appendErr instanceof Error ? appendErr.message : String(appendErr)}`);
71
+ stderr(`appendEntry failed: ${toErrorMessage(appendErr)}`);
69
72
  }
70
73
  needsBaseline = true;
71
74
  }
@@ -88,12 +91,12 @@ export default function (pi: ExtensionAPI): void {
88
91
  last = cur;
89
92
  needsBaseline = false;
90
93
  } catch (err) {
91
- const msg = err instanceof Error ? err.message : String(err);
94
+ const msg = toErrorMessage(err);
92
95
  stderr(`before_provider_request failed: ${msg}`);
93
96
  try {
94
97
  pi.appendEntry(CUSTOM_TYPE, { v: SCHEMA_VERSION, seq, error: msg });
95
98
  } catch (appendErr) {
96
- stderr(`appendEntry failed: ${appendErr instanceof Error ? appendErr.message : String(appendErr)}`);
99
+ stderr(`appendEntry failed: ${toErrorMessage(appendErr)}`);
97
100
  }
98
101
  needsBaseline = true;
99
102
  }