@zhushanwen/pi-rename-session 0.9.5 → 0.11.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/README.md +1 -1
- package/package.json +7 -7
- package/src/__tests__/index.test.ts +1 -1
- package/src/__tests__/llm.test.ts +22 -5
- package/src/__tests__/pure.test.ts +4 -2
- package/src/index.ts +5 -3
- package/src/llm.ts +14 -10
- package/src/pure.ts +4 -7
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ node e2e/run-all.mjs # 顺序全跑:单场景失败不阻断后续,汇总
|
|
|
131
131
|
- harness API 与断言纯函数(单测 `e2e/harness.test.mjs` 随 vitest 跑):`e2e/harness.mjs`
|
|
132
132
|
- A2 的标题记录与人工抽查表(词组形态/语义相关/语言跟随三列):`e2e/RESULTS.md`(run-a2 自动追加,人工填写)
|
|
133
133
|
- 保留现场调试:`E2E_KEEP_TMP=1 node e2e/run-all.mjs`
|
|
134
|
-
-
|
|
134
|
+
- 测试模型缺省 `xiaomi-token-plan-cn/mimo-v2.6-flash`(`E2E_MODEL` 环境变量可覆盖,禁 kimi)
|
|
135
135
|
|
|
136
136
|
## 子 session 自动排除
|
|
137
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-rename-session",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"taiji": {
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"pi-package"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@zhushanwen/pi-ext-guards": "0.4.
|
|
37
|
-
"@zhushanwen/pi-extension-logger": "0.6.
|
|
38
|
-
"@zhushanwen/pi-llm-shared": "0.
|
|
36
|
+
"@zhushanwen/pi-ext-guards": "0.4.3",
|
|
37
|
+
"@zhushanwen/pi-extension-logger": "0.6.2",
|
|
38
|
+
"@zhushanwen/pi-llm-shared": "0.11.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@vitest/coverage-v8": "^4.1.9",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"vitest.config.ts"
|
|
49
49
|
],
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
52
|
-
"@earendil-works/pi-ai": "^0.
|
|
53
|
-
"typebox": "
|
|
51
|
+
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
52
|
+
"@earendil-works/pi-ai": "^1.0.0",
|
|
53
|
+
"typebox": "1.3.27"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -826,7 +826,7 @@ describe("rename_session 工具注册与 execute 守卫(D3)", () => {
|
|
|
826
826
|
expect(setup.registerToolMock).not.toHaveBeenCalled();
|
|
827
827
|
});
|
|
828
828
|
|
|
829
|
-
it("TC-T4: execute 时 live mode 非 agent-tool(load 是 agent-tool、运行中切走)→ isError(经 throw,pi 0.
|
|
829
|
+
it("TC-T4: execute 时 live mode 非 agent-tool(load 是 agent-tool、运行中切走)→ isError(经 throw,pi 1.0.0 实装契约),文案含恢复动作指引", async () => {
|
|
830
830
|
setupWithMode(AGENT_TOOL_CONFIG); // load 时注册
|
|
831
831
|
const tool = setup.registeredTool as RegisteredRenameTool;
|
|
832
832
|
|
|
@@ -380,7 +380,9 @@ const STUB_MODEL: Model<Api> = {
|
|
|
380
380
|
api: "anthropic-messages",
|
|
381
381
|
provider: "stub",
|
|
382
382
|
baseUrl: "https://stub.invalid",
|
|
383
|
-
reasoning
|
|
383
|
+
// reasoning=true:档位按模型 supportedLevels 数据驱动判定(pi-ai getSupportedThinkingLevels),
|
|
384
|
+
// 不支持思考的模型只支持 "off"——用例要断言档位透传,故此处声明支持。
|
|
385
|
+
reasoning: true,
|
|
384
386
|
input: ["text"],
|
|
385
387
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
386
388
|
contextWindow: 128_000,
|
|
@@ -467,7 +469,7 @@ describe("callRenameLLM", () => {
|
|
|
467
469
|
);
|
|
468
470
|
});
|
|
469
471
|
|
|
470
|
-
it("传给 callLLM 的 opts:model/systemPrompt(<200)/maxTokens=2048
|
|
472
|
+
it("传给 callLLM 的 opts:model/systemPrompt(<200)/maxTokens=2048/不传 timeoutMs/signal/sessionId/无 tools,messages 三段式", async () => {
|
|
471
473
|
vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
|
|
472
474
|
vi.mocked(callLLM).mockResolvedValue({ ok: true, content: "标题" });
|
|
473
475
|
await callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE);
|
|
@@ -478,7 +480,7 @@ describe("callRenameLLM", () => {
|
|
|
478
480
|
systemPrompt: string;
|
|
479
481
|
messages: { role: string; content: { type: string; text: string }[] }[];
|
|
480
482
|
maxTokens: number;
|
|
481
|
-
timeoutMs
|
|
483
|
+
timeoutMs?: number;
|
|
482
484
|
signal: AbortSignal;
|
|
483
485
|
sessionId: string;
|
|
484
486
|
tools?: unknown;
|
|
@@ -488,8 +490,8 @@ describe("callRenameLLM", () => {
|
|
|
488
490
|
expect(callOpts.systemPrompt.length).toBeLessThan(200);
|
|
489
491
|
// 输出预算覆盖 thinking+标题(reasoning 模型 thinking 600-1500 tokens 计入预算,64 必截断)
|
|
490
492
|
expect(callOpts.maxTokens).toBe(2048);
|
|
491
|
-
//
|
|
492
|
-
expect(callOpts.timeoutMs).
|
|
493
|
+
// 不传超时(ADR-0122:无包内超时兜底,失败直报)
|
|
494
|
+
expect(callOpts.timeoutMs).toBeUndefined();
|
|
493
495
|
expect(callOpts.signal).toBeInstanceOf(AbortSignal);
|
|
494
496
|
expect(callOpts.sessionId).toBe("test-session-id");
|
|
495
497
|
// 三段式:user(prompt) + assistant(finalText) + user(instruction)
|
|
@@ -672,6 +674,21 @@ describe("callRenameLLM", () => {
|
|
|
672
674
|
const callOpts = vi.mocked(callLLM).mock.calls[0][1] as { reasoning?: unknown };
|
|
673
675
|
expect(callOpts.reasoning).toBe("high");
|
|
674
676
|
});
|
|
677
|
+
|
|
678
|
+
it("thinkingLevel 为该模型不支持的档位 → warn 留痕 + 不传 reasoning(数据驱动判定,不静默换档)", async () => {
|
|
679
|
+
loggerMock.warn.mockClear();
|
|
680
|
+
vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
|
|
681
|
+
vi.mocked(callLLM).mockResolvedValue({ ok: true, content: "标题" });
|
|
682
|
+
// STUB_MODEL(reasoning=true)的 supportedLevels = off/minimal/low/medium/high,
|
|
683
|
+
// "ultra" 词表外 → reasoningLevel undefined → 不传档位
|
|
684
|
+
await callRenameLLM(createCtx(), { ...BASE_CONFIG, thinkingLevel: "ultra" }, FINAL_MESSAGE);
|
|
685
|
+
|
|
686
|
+
const callOpts = vi.mocked(callLLM).mock.calls[0][1] as { reasoning?: unknown };
|
|
687
|
+
expect(callOpts.reasoning).toBeUndefined();
|
|
688
|
+
expect(loggerMock.warn).toHaveBeenCalledWith(
|
|
689
|
+
expect.stringContaining('thinkingLevel "ultra" is not supported'),
|
|
690
|
+
);
|
|
691
|
+
});
|
|
675
692
|
});
|
|
676
693
|
|
|
677
694
|
// ────────────────────────────────────────────────────
|
|
@@ -301,8 +301,10 @@ describe("normalizeRenameConfig", () => {
|
|
|
301
301
|
expect(normalizeRenameConfig({ thinkingLevel: "off" }).thinkingLevel).toBe("off");
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
-
it("thinkingLevel
|
|
305
|
-
|
|
304
|
+
it("thinkingLevel:非空字符串原样采信(含词表外值),非字符串/缺失 → 默认 off", () => {
|
|
305
|
+
// 词表不在本层(合法性归写入侧 UI 与 pi):显式写了未知档位不再被静默换成缺省档
|
|
306
|
+
expect(normalizeRenameConfig({ thinkingLevel: "ultra" }).thinkingLevel).toBe("ultra");
|
|
307
|
+
expect(normalizeRenameConfig({ thinkingLevel: "xhigh" }).thinkingLevel).toBe("xhigh");
|
|
306
308
|
expect(normalizeRenameConfig({ thinkingLevel: 5 }).thinkingLevel).toBe("off");
|
|
307
309
|
expect(normalizeRenameConfig({ thinkingLevel: null }).thinkingLevel).toBe("off");
|
|
308
310
|
// 缺失字段 → 默认 off(旧配置文件兼容:无 thinkingLevel 字段的存量配置自动回填)
|
package/src/index.ts
CHANGED
|
@@ -64,9 +64,11 @@ const RENAME_TOOL_SUBAGENT_GUARD_MESSAGE =
|
|
|
64
64
|
* 子进程同样加载本 extension(全局 mode=agent-tool 时 subagent 也注册本工具),
|
|
65
65
|
* 子会话是临时产物不参与 rename 体系,与 message_end / turn_end 两入口同语义。
|
|
66
66
|
* - execute 内 live 读 config 守卫:mode !== "agent-tool"(mode 切走后本工具残留在
|
|
67
|
-
* 已存活 session 的工具清单——pi 无 unregisterTool)→ throw isError(pi 0.
|
|
68
|
-
* execute
|
|
69
|
-
*
|
|
67
|
+
* 已存活 session 的工具清单——pi 无 unregisterTool)→ throw isError(pi 1.0.0 实装:
|
|
68
|
+
* execute 抛出经 pi-agent-core agent-loop.js executePreparedToolCall 外层 catch 统一转
|
|
69
|
+
* isError:true 的 error tool result〔:581-588 createErrorToolResult(message)〕;统一
|
|
70
|
+
* throw 为纪律选择(单一错误路径),非「返回值 isError 失效」——1.0.0 起返回值上的
|
|
71
|
+
* isError 标记同样被尊重〔:579〕)。
|
|
70
72
|
* - cleanTitle 空值同样 throw isError(空白/纯标点标题不可落库)。
|
|
71
73
|
* - 非空直接 pi.setSessionName,不走 getSessionName() 防覆盖守卫——agent 显式调用
|
|
72
74
|
* 是「代表用户的意图」,语义等同 GUI 手动 rename,允许覆盖任何既有名(含自动名/语义名)。
|
package/src/llm.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
+
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
3
4
|
import type { AssistantMessage, Message, Usage } from "@earendil-works/pi-ai";
|
|
4
5
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import { isRecord } from "@zhushanwen/pi-ext-guards";
|
|
@@ -153,10 +154,7 @@ export function buildTitleMessages(
|
|
|
153
154
|
return messages;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
// ──────────────────────── debug 证据链
|
|
157
|
-
|
|
158
|
-
/** rename LLM 超时(固定值:实测 2048 预算下独立调用 5-12.5s,30s 约 2.4x+ 余量;超时归一 ok:false 静默跳过)。 */
|
|
159
|
-
const RENAME_TIMEOUT_MS = 30_000;
|
|
157
|
+
// ──────────────────────── debug 证据链 ────────────────────────
|
|
160
158
|
|
|
161
159
|
/** title empty warn 的 raw 预览上限(Unicode 码点数;预览是 LLM 标题候选,非对话原文)。 */
|
|
162
160
|
const TITLE_EMPTY_RAW_PREVIEW_CODE_POINTS = 100;
|
|
@@ -268,6 +266,12 @@ export async function callRenameLLM(
|
|
|
268
266
|
logger.warn("model not available, skipping");
|
|
269
267
|
return null;
|
|
270
268
|
}
|
|
269
|
+
const reasoningLevel = getSupportedThinkingLevels(model).find((l) => l === config.thinkingLevel);
|
|
270
|
+
if (config.thinkingLevel !== undefined && reasoningLevel === undefined) {
|
|
271
|
+
logger.warn(
|
|
272
|
+
`configured thinkingLevel "${config.thinkingLevel}" is not supported by ${model.provider}/${model.id}; calling without a thinking level`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
271
275
|
|
|
272
276
|
const userPrompt =
|
|
273
277
|
options?.promptText ??
|
|
@@ -301,14 +305,14 @@ export async function callRenameLLM(
|
|
|
301
305
|
// 输出预算必须覆盖「thinking + 标题」两部分:thinking 计入 maxTokens 的模型
|
|
302
306
|
//(StepFun step-5-preview 实测 thinking 600-1500 tokens 且不可禁用/不可压低,
|
|
303
307
|
// reasoning_effort/thinking 参数均被无视)在 64 预算下正文必然截断为空。
|
|
304
|
-
// 2048 = 实测最大总输出 843 tokens 的 2.4x
|
|
305
|
-
//(4096 ÷ 67 tok/s ≈ 61s)远超 30s 超时线。截断发生时空标题 warn 带 stopReason=length 留痕,
|
|
308
|
+
// 2048 = 实测最大总输出 843 tokens 的 2.4x 余量。截断发生时空标题 warn 带 stopReason=length 留痕,
|
|
306
309
|
// 该 warn 累积出现即上调本常量的数据信号。maxTokens 是截断上限非预扣额度,按实际用量计费。
|
|
307
310
|
maxTokens: 2048,
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
reasoning
|
|
311
|
+
// 不传 timeoutMs(ADR-0122:不设包内超时兜底,LLM 挂死由上层失败直报;调用方显式超时走 llm-shared C2 通道)
|
|
312
|
+
// 档位按「该模型自己的 supportedLevels」判定(pi-ai 数据驱动,本层不自持词表):
|
|
313
|
+
// 配置写了该模型不支持的档位 → 留痕并按「不传档位」处理(不静默换成别的档)。
|
|
314
|
+
// llm-shared 内部会把 "off" 映射为不传 reasoning(provider 默认)。
|
|
315
|
+
reasoning: reasoningLevel,
|
|
312
316
|
// 保留随 session abort 取消调用的语义(旧版 llm.ts 同样透传 ctx.signal)
|
|
313
317
|
signal: ctx.signal,
|
|
314
318
|
sessionId,
|
package/src/pure.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
6
5
|
|
|
7
6
|
import { isEnoentError } from "@zhushanwen/pi-ext-guards";
|
|
8
7
|
import {
|
|
9
|
-
|
|
8
|
+
normalizeThinkingLevel,
|
|
10
9
|
loadConfig,
|
|
11
10
|
normalizeModelSelector,
|
|
12
11
|
saveConfig,
|
|
@@ -40,11 +39,11 @@ export interface RenameSessionConfig {
|
|
|
40
39
|
/** 标题最大长度(Unicode 码点数)。 */
|
|
41
40
|
maxTitleLength: number;
|
|
42
41
|
/**
|
|
43
|
-
* 标题生成 LLM 的 thinking
|
|
42
|
+
* 标题生成 LLM 的 thinking 级别(字符串透传;合法档位按模型 supportedLevels 在调用点判定)。
|
|
44
43
|
* 默认 "off":不传 pi-ai reasoning(provider 默认行为,与旧版本一致);
|
|
45
44
|
* "minimal"~"max" 透传给 SimpleStreamOptions.reasoning(provider 不支持时静默忽略)。
|
|
46
45
|
*/
|
|
47
|
-
thinkingLevel:
|
|
46
|
+
thinkingLevel: string;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// ──────────────────────── 枚举校验 ────────────────────────
|
|
@@ -144,9 +143,7 @@ export function normalizeRenameConfig(raw: unknown): RenameSessionConfig {
|
|
|
144
143
|
// normalizeModelSelector:llm-shared 导出(ext-simplify-17 D6 收口)
|
|
145
144
|
const model = normalizeModelSelector(obj.model) ?? DEFAULT_RENAME_CONFIG.model;
|
|
146
145
|
|
|
147
|
-
const thinkingLevel =
|
|
148
|
-
? obj.thinkingLevel
|
|
149
|
-
: DEFAULT_RENAME_CONFIG.thinkingLevel;
|
|
146
|
+
const thinkingLevel = normalizeThinkingLevel(obj.thinkingLevel) ?? DEFAULT_RENAME_CONFIG.thinkingLevel;
|
|
150
147
|
|
|
151
148
|
return { enabled, model, mode, maxTitleLength, thinkingLevel };
|
|
152
149
|
}
|