@zhushanwen/pi-permission 1.4.9 → 1.5.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-permission",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Pi permission extension — four permission modes (yolo/auto/approve/strict) with three-layer pipeline (AST + rules + AI classifier).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"web-tree-sitter": "^0.26.11",
|
|
53
53
|
"@zhushanwen/pi-ext-guards": "0.4.2",
|
|
54
54
|
"@zhushanwen/pi-extension-logger": "0.6.1",
|
|
55
|
-
"@zhushanwen/pi-llm-shared": "0.
|
|
55
|
+
"@zhushanwen/pi-llm-shared": "0.11.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@earendil-works/pi-ai": "^0.84.4",
|
|
@@ -282,4 +282,27 @@ describe("CT5: callLLM opts 构造(model/messages/timeoutMs/signal)", () =>
|
|
|
282
282
|
const opts = getLastOpts();
|
|
283
283
|
expect(opts?.signal).toBe(ac.signal);
|
|
284
284
|
});
|
|
285
|
+
|
|
286
|
+
it("配置 thinkingLevel 不被模型支持 → warn 留痕 + reasoning 不传(不静默降级)", async () => {
|
|
287
|
+
// FIXED_MODEL_OBJ reasoning:false → supportedLevels = ["off"],配置 "high" 判定落空;
|
|
288
|
+
// changeset pi-permission-thinking-level-datadriven 承诺 warn 留痕(同族 rename-session 对齐)
|
|
289
|
+
const onLog = vi.fn();
|
|
290
|
+
const { spy, getLastOpts } = capturingCallLLM(okResult('{"outcome":"allow","risk_level":"low","reasoning":"x","confidence":0.9}'));
|
|
291
|
+
const classifier = createClassifier(makeDeps({ callLLM: spy, onLog }));
|
|
292
|
+
const r = await classifier.classifyRisk(CTX, { ...CONFIG, thinkingLevel: "high" });
|
|
293
|
+
expect(r.outcome).toBe("allow");
|
|
294
|
+
expect(onLog).toHaveBeenCalledWith(
|
|
295
|
+
expect.stringContaining('configured thinkingLevel "high" is not supported by test-co/test-model; calling without a thinking level'),
|
|
296
|
+
);
|
|
297
|
+
expect(getLastOpts()?.reasoning).toBeUndefined();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("配置 thinkingLevel 被模型支持 → 命中透传,无 warn", async () => {
|
|
301
|
+
const onLog = vi.fn();
|
|
302
|
+
const { spy, getLastOpts } = capturingCallLLM(okResult('{"outcome":"allow","risk_level":"low","reasoning":"x","confidence":0.9}'));
|
|
303
|
+
const classifier = createClassifier(makeDeps({ callLLM: spy, onLog }));
|
|
304
|
+
await classifier.classifyRisk(CTX, { ...CONFIG, thinkingLevel: "off" });
|
|
305
|
+
expect(onLog).not.toHaveBeenCalledWith(expect.stringContaining("not supported"));
|
|
306
|
+
expect(getLastOpts()?.reasoning).toBe("off");
|
|
307
|
+
});
|
|
285
308
|
});
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - fail-closed:timeout / 抛错 / 解析失败 / 无可用模型 → 一律 ask
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
20
21
|
import type { Api, Message, Model } from "@earendil-works/pi-ai";
|
|
21
22
|
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
22
23
|
import type { CallLLMOptions, CallLLMResult } from "@zhushanwen/pi-llm-shared";
|
|
@@ -133,15 +134,25 @@ function buildCallOptions(
|
|
|
133
134
|
config: ClassifierConfig,
|
|
134
135
|
timeoutMs: number | undefined,
|
|
135
136
|
signal: AbortSignal | undefined,
|
|
137
|
+
onLog?: (msg: string) => void,
|
|
136
138
|
): CallLLMOptions {
|
|
139
|
+
// 档位按「该模型自己的 supportedLevels」判定(pi-ai 数据驱动,本层不自持词表);
|
|
140
|
+
// 不支持则按「不传档位」处理,不静默换成别的档(clamp),并 warn 留痕
|
|
141
|
+
// (changeset pi-permission-thinking-level-datadriven 的承诺,与 rename-session 同族对齐)。
|
|
142
|
+
// llm-shared 会把 "off" 映射为不传。
|
|
143
|
+
const reasoning = getSupportedThinkingLevels(model).find((l) => l === config.thinkingLevel);
|
|
144
|
+
if (config.thinkingLevel !== undefined && config.thinkingLevel !== "" && reasoning === undefined) {
|
|
145
|
+
onLog?.(
|
|
146
|
+
`[pi-permission] classifier: configured thinkingLevel "${config.thinkingLevel}" is not supported by ${model.provider}/${model.id}; calling without a thinking level`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
137
149
|
return {
|
|
138
150
|
model,
|
|
139
151
|
systemPrompt: CLASSIFIER_SYSTEM_PROMPT,
|
|
140
152
|
messages: buildMessages(ctx),
|
|
141
153
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
142
154
|
...(signal !== undefined ? { signal } : {}),
|
|
143
|
-
|
|
144
|
-
reasoning: config.thinkingLevel,
|
|
155
|
+
reasoning,
|
|
145
156
|
};
|
|
146
157
|
}
|
|
147
158
|
|
|
@@ -223,7 +234,7 @@ export function createClassifier(deps: ClassifierDeps): {
|
|
|
223
234
|
|
|
224
235
|
// 2. 构造 CallLLMOptions
|
|
225
236
|
const timeoutMs = config.timeout > 0 ? config.timeout * MILLIS_PER_SECOND : undefined;
|
|
226
|
-
const callPromise = callLLM(buildCallOptions(model, ctx, config, timeoutMs, signal));
|
|
237
|
+
const callPromise = callLLM(buildCallOptions(model, ctx, config, timeoutMs, signal, onLog));
|
|
227
238
|
|
|
228
239
|
// 3. 等待 callLLM + 外层超时/中止兜底(provider 不支持 timeoutMs/signal 时防永挂)
|
|
229
240
|
let settled: SettledRace;
|
package/src/config.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { isRecord, toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
|
22
22
|
import {
|
|
23
23
|
clearConfigCache,
|
|
24
24
|
getConfigPath as getLlmConfigPath,
|
|
25
|
-
|
|
25
|
+
normalizeThinkingLevel,
|
|
26
26
|
loadConfig,
|
|
27
27
|
saveConfig as saveLlmConfig,
|
|
28
28
|
} from "@zhushanwen/pi-llm-shared";
|
|
@@ -59,10 +59,8 @@ function normalizeClassifierConfig(raw: unknown): ClassifierConfig {
|
|
|
59
59
|
if (record.model !== undefined && !(typeof record.model === "string" && record.model.length > 0)) {
|
|
60
60
|
logger.warn("Ignoring invalid classifier.model (expected string 'auto' or 'provider/model-id'), using default auto");
|
|
61
61
|
}
|
|
62
|
-
// thinkingLevel
|
|
63
|
-
const thinkingLevel =
|
|
64
|
-
? record.thinkingLevel
|
|
65
|
-
: DEFAULT_CLASSIFIER_CONFIG.thinkingLevel;
|
|
62
|
+
// thinkingLevel:非空字符串即采信(合法档位由写入侧 UI 与 pi 判定;本层不再自持词表)
|
|
63
|
+
const thinkingLevel = normalizeThinkingLevel(record.thinkingLevel) ?? DEFAULT_CLASSIFIER_CONFIG.thinkingLevel;
|
|
66
64
|
return {
|
|
67
65
|
enabled: record.enabled !== false,
|
|
68
66
|
model: typeof record.model === "string" && record.model.length > 0 ? record.model : DEFAULT_CLASSIFIER_CONFIG.model,
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* 对应 slice plan 的 DM1-DM5 数据模型。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
9
8
|
|
|
10
9
|
// ──────────────────────── DM1: PermissionMode ────────────────────────
|
|
11
10
|
|
|
@@ -108,11 +107,11 @@ export interface ClassifierConfig {
|
|
|
108
107
|
/** 高风险是否强制 deny(不问人:high+allow 时直接拒绝) */
|
|
109
108
|
autoDenyHighRisk: boolean;
|
|
110
109
|
/**
|
|
111
|
-
* 标题生成 LLM 的 thinking
|
|
110
|
+
* 标题生成 LLM 的 thinking 级别(字符串透传;合法档位按模型 supportedLevels 在调用点判定)。
|
|
112
111
|
* 默认 "off":不传 pi-ai reasoning(provider 默认行为);
|
|
113
112
|
* "minimal"~"max" 透传给 SimpleStreamOptions.reasoning(provider 不支持时静默忽略)。
|
|
114
113
|
*/
|
|
115
|
-
thinkingLevel:
|
|
114
|
+
thinkingLevel: string;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
// ──────────────────────── DM5: PermissionConfig ────────────────────────
|