@zhushanwen/pi-smart-context 0.1.3 → 0.2.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-smart-context",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "xyz-agent": {
@@ -36,8 +36,8 @@
36
36
  "pi-package"
37
37
  ],
38
38
  "dependencies": {
39
- "@zhushanwen/pi-extension-logger": "0.4.0",
40
- "@zhushanwen/pi-llm-shared": "0.5.0"
39
+ "@zhushanwen/pi-extension-logger": "0.4.1",
40
+ "@zhushanwen/pi-llm-shared": "0.6.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@vitest/coverage-v8": "^4.1.9",
@@ -43,14 +43,17 @@ function makeEvent(overrides?: Partial<BeforeCompactLikeEvent["preparation"]>):
43
43
  };
44
44
  }
45
45
 
46
- function makeCtx(): ExtensionContext {
46
+ function makeCtx(crossModel?: { provider: string; id: string } | null): ExtensionContext {
47
47
  return {
48
48
  model: { provider: "zai", id: "glm" },
49
49
  getSystemPrompt: () => "sys",
50
50
  sessionManager: { getSessionId: () => "s1", getSessionFile: () => "/tmp/s1.jsonl" },
51
51
  modelRegistry: {
52
52
  getApiKeyAndHeaders: async () => ({ ok: true, apiKey: "k" }),
53
- find: () => null,
53
+ // resolveRef 精确匹配:仅 (provider,id) 同时命中时返回模型(配合 hasConfiguredAuth 放行)
54
+ find: (provider: string, id: string) =>
55
+ crossModel && provider === crossModel.provider && id === crossModel.id ? crossModel : null,
56
+ hasConfiguredAuth: () => crossModel != null,
54
57
  },
55
58
  } as unknown as ExtensionContext;
56
59
  }
@@ -84,11 +87,12 @@ describe("session_before_compact 接管 handler", () => {
84
87
  expect(mockedCall).not.toHaveBeenCalled();
85
88
  });
86
89
 
87
- it("same-model 成功 → compaction 带 engine/mode 标记 + preamble + fileOps 清单 + transcript 指针", async () => {
90
+ it("same-model 成功 → compaction 带 engine/mode/model 标记 + preamble + fileOps 清单 + transcript 指针", async () => {
88
91
  mockedCall.mockResolvedValue({ ok: true, text: "summary body", usage: { input: 1, output: 2 } });
89
92
  const { handler } = makeHandler(normalizeSmartContextConfig({ compactModel: { type: "ref", ref: "" } }));
90
93
  const decision = await handler(makeEvent(), makeCtx());
91
- expect(decision.compaction?.details).toEqual({ engine: "smart-context", mode: "same-model" });
94
+ // details.model = `${ctx.model.provider}/${ctx.model.id}`(§3.3 ①,用量归属数据源)
95
+ expect(decision.compaction?.details).toEqual({ engine: "smart-context", mode: "same-model", model: "zai/glm" });
92
96
  expect(decision.compaction?.summary).toContain("summary body");
93
97
  // D13-9 preamble 在最前;D11-2 fileOps;D13-4 transcript
94
98
  expect(decision.compaction?.summary.startsWith("This is an automatically generated checkpoint")).toBe(true);
@@ -115,6 +119,32 @@ describe("session_before_compact 接管 handler", () => {
115
119
  expect(mockedNative).not.toHaveBeenCalled();
116
120
  });
117
121
 
122
+ it("cross-model 成功 → details.model=compactModel 解析结果,native details 字段保留", async () => {
123
+ mockedNative.mockResolvedValue({
124
+ summary: "native checkpoint",
125
+ firstKeptEntryId: "kept-1",
126
+ tokensBefore: 500_000,
127
+ usage: {
128
+ input: 5, output: 6, cacheRead: 0, cacheWrite: 0, totalTokens: 11,
129
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
130
+ },
131
+ details: { nativeMarker: "keep" },
132
+ });
133
+ const { handler } = makeHandler(
134
+ normalizeSmartContextConfig({ compactModel: { type: "ref", ref: "xiaomi/mimo" } }),
135
+ );
136
+ const decision = await handler(makeEvent(), makeCtx({ provider: "xiaomi", id: "mimo" }));
137
+ // details.model = `${resolveModel(...).provider}/${id}`(§3.3 ①);native details 字段原样保留(spread 增量,不改变其他字段)
138
+ expect(decision.compaction?.details).toEqual({
139
+ engine: "smart-context",
140
+ mode: "cross-model",
141
+ model: "xiaomi/mimo",
142
+ nativeMarker: "keep",
143
+ });
144
+ // nativeCompact 收到的 model 就是解析结果(details.model 与实际执行模型同源)
145
+ expect(mockedNative.mock.calls[0][1]).toEqual({ provider: "xiaomi", id: "mimo" });
146
+ });
147
+
118
148
  it("收缩校验失败(摘要 ≥ 被压段)→ 拒绝 + 同段记录不重试(D13-1)", async () => {
119
149
  mockedCall.mockResolvedValue({ ok: true, text: "z".repeat(10_000) });
120
150
  const { handler, state } = makeHandler(normalizeSmartContextConfig({}));
@@ -6,7 +6,9 @@
6
6
  * - same-model 模式:完整上下文 + 会话原 system prompt + tools + 末尾追加压缩指令(kv-cache 前缀命中)
7
7
  * - cross-model 模式:直接调用包导出的 compact(preparation, 压缩Model, ...)(原生组装零复刻,R4 结论)
8
8
  *
9
- * 输出 CompactionResult.details 携带 {engine:"smart-context", mode} 标记(D1 entry 标记)。
9
+ * 输出 CompactionResult.details 携带 {engine:"smart-context", mode, model} 标记(D1 entry 标记)。
10
+ * model = `${model.provider}/${model.id}`(执行压缩的实际模型)——用量页 compaction 归属数据源
11
+ * (usage-page-fixes §3.3 ①);pi appendCompaction 对 details 逐字透传落盘(pi-semantics PS-28)。
10
12
  */
11
13
 
12
14
  import { buildSessionContext, compact as nativeCompact, convertToLlm } from "@earendil-works/pi-coding-agent";
@@ -93,6 +95,8 @@ function warnLog(message: string, data?: unknown): void {
93
95
  export interface SmartContextDetails {
94
96
  engine: "smart-context";
95
97
  mode: "same-model" | "cross-model";
98
+ /** 执行压缩的模型 `${provider}/${modelId}`(用量归属数据源,usage-page-fixes §3.3 ①)。 */
99
+ model: string;
96
100
  }
97
101
 
98
102
  /** sessionManager 上会话文件字段/方法的宽松形状(D13-4,消费侧可选链兜底)。 */
@@ -219,7 +223,11 @@ async function generateSameMode(
219
223
  firstKeptEntryId: event.preparation.firstKeptEntryId,
220
224
  tokensBefore: event.preparation.tokensBefore,
221
225
  usage: result.usage as CompactionResult["usage"],
222
- details: { engine: "smart-context", mode: "same-model" } satisfies SmartContextDetails,
226
+ details: {
227
+ engine: "smart-context",
228
+ mode: "same-model",
229
+ model: `${model.provider}/${model.id}`,
230
+ } satisfies SmartContextDetails,
223
231
  };
224
232
  }
225
233
 
@@ -266,7 +274,12 @@ async function generateCrossMode(
266
274
  // D13-2:原生 compact 内部对截断的处理沿用原生语义;此处补 engine 标记
267
275
  return {
268
276
  ...result,
269
- details: { ...(result.details as object | undefined), engine: "smart-context", mode: "cross-model" } as SmartContextDetails,
277
+ details: {
278
+ ...(result.details as object | undefined),
279
+ engine: "smart-context",
280
+ mode: "cross-model",
281
+ model: `${model.provider}/${model.id}`,
282
+ } as SmartContextDetails,
270
283
  };
271
284
  }
272
285
 
package/vitest.config.ts CHANGED
@@ -2,6 +2,8 @@ import { defineConfig } from "vitest/config";
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
5
+ reporters: ["default", "junit"],
6
+ outputFile: { junit: "./test-results/vitest-junit.xml" },
5
7
  include: ["src/__tests__/**/*.test.ts"],
6
8
  },
7
9
  });