pi-naming 0.5.0 → 0.5.1
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 +8 -2
- package/README.zh-CN.md +8 -2
- package/SKILL.md +1 -1
- package/config.example.json +3 -1
- package/locales/index.json +20 -0
- package/package.json +4 -4
- package/src/config.ts +24 -1
- package/src/index.ts +26 -2
- package/src/session-name.ts +17 -11
package/README.md
CHANGED
|
@@ -37,7 +37,9 @@ File: `<pi-agent-dir>/extensions/pi-naming/config.json`; respects `PI_CODING_AGE
|
|
|
37
37
|
"preferredLength": 10,
|
|
38
38
|
"language": "auto",
|
|
39
39
|
"instructions": "",
|
|
40
|
-
"timeoutMs": 10000
|
|
40
|
+
"timeoutMs": 10000,
|
|
41
|
+
"maxTokens": 2048,
|
|
42
|
+
"effort": "low"
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
```
|
|
@@ -51,8 +53,12 @@ File: `<pi-agent-dir>/extensions/pi-naming/config.json`; respects `PI_CODING_AGE
|
|
|
51
53
|
| `language` | `"auto"` | Dominant message language, or a language such as `English` or `日本語` |
|
|
52
54
|
| `instructions` | `""` | Additional naming style instructions, not a template or executable code |
|
|
53
55
|
| `timeoutMs` | `10000` | Title request timeout in milliseconds |
|
|
56
|
+
| `maxTokens` | `2048` | Output budget for the title request, capped by the model output limit |
|
|
57
|
+
| `effort` | `"low"` | Thinking effort: `minimal`, `low`, `medium`, `high`, `xhigh` or `max` |
|
|
54
58
|
|
|
55
|
-
Lengths and
|
|
59
|
+
Lengths, timeout and output budget must be positive safe integers; timeout must not exceed `2147483647` milliseconds. Unknown fields and invalid values are reported and disable registration. A missing file uses defaults. Explicit names are not truncated by generation settings; additional instructions cannot bypass generated title normalization or length limits.
|
|
60
|
+
|
|
61
|
+
`maxLength` and `maxTokens` are independent. `maxLength` only truncates the generated title; `maxTokens` only budgets the model response. Reasoning models spend that same budget on thinking and on the title, so on a reasoning model raise `maxTokens` when titles come back empty and lower it to bound cost; `effort` controls how much of the budget thinking is likely to take. No field is derived from another.
|
|
56
62
|
|
|
57
63
|
For longer English titles use `maxLength: 60`, `preferredLength: 40`, `language: "English"`. Model and authentication come from Pi's current selection; title language is independent of UI language.
|
|
58
64
|
|
package/README.zh-CN.md
CHANGED
|
@@ -37,7 +37,9 @@ pi install npm:pi-naming
|
|
|
37
37
|
"preferredLength": 10,
|
|
38
38
|
"language": "auto",
|
|
39
39
|
"instructions": "",
|
|
40
|
-
"timeoutMs": 10000
|
|
40
|
+
"timeoutMs": 10000,
|
|
41
|
+
"maxTokens": 2048,
|
|
42
|
+
"effort": "low"
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
```
|
|
@@ -51,8 +53,12 @@ pi install npm:pi-naming
|
|
|
51
53
|
| `language` | `"auto"` | 跟随消息主要语言,或指定 `English`、`日本語` 等 |
|
|
52
54
|
| `instructions` | `""` | 追加的命名风格要求,不是模板或可执行代码 |
|
|
53
55
|
| `timeoutMs` | `10000` | 标题请求超时(毫秒) |
|
|
56
|
+
| `maxTokens` | `2048` | 标题请求的输出预算,受模型输出上限封顶 |
|
|
57
|
+
| `effort` | `"low"` | 思考档位:`minimal`、`low`、`medium`、`high`、`xhigh`、`max` |
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
长度、超时和输出预算必须是正安全整数,超时不超过 `2147483647` 毫秒。未知字段和非法配置会明确报错,不注册功能。缺少配置文件时使用默认值。显式输入的名称不受生成标题长度限制;补充提示不会绕过生成结果的单行清理和长度限制。
|
|
60
|
+
|
|
61
|
+
`maxLength` 与 `maxTokens` 相互独立:`maxLength` 只负责截断生成的标题,`maxTokens` 只负责模型响应的预算,两者不会互相推导。推理模型的思考和标题共用这份预算,所以在推理模型上命名失败就调大 `maxTokens`,想限制成本就调小;`effort` 决定思考大概会占用多少。
|
|
56
62
|
|
|
57
63
|
例如较长英文标题:`maxLength: 60`、`preferredLength: 40`、`language: "English"`。模型和鉴权直接复用 Pi 当前选择;UI 语言与标题语言独立。
|
|
58
64
|
|
package/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ description: 配置 Pi 统一 /rename、首条消息命名和终端目标归属
|
|
|
10
10
|
Use `/config:naming` to open the normal TUI settings menu; use `reset` for defaults and reload after saving.
|
|
11
11
|
|
|
12
12
|
- `automaticNaming` / `manualNaming` 控制自动输入和 `/rename [名称]`,`targets` 直接分别控制 session/workspace/tab。Control automatic input and `/rename [name]`; `targets` directly selects session/workspace/tab.
|
|
13
|
-
- `title`
|
|
13
|
+
- `title` 控制长度、语言、补充提示、超时、输出预算和思考档位(`maxTokens` 默认 2048、`effort` 默认 low,二者与 `maxLength` 相互独立);模型鉴权复用 Pi。Controls length, language, instructions, timeout, output budget and thinking effort (`maxTokens` default 2048, `effort` default low, both independent from `maxLength`); model authentication comes from Pi.
|
|
14
14
|
- 无终端也可命名 session;终端失败与跳过必须报告。普通会话明确目标路径不受旧 `PI_SUBAGENT_RENAME_*` 变量影响;关闭 target 即使变量为 `1` 也不执行。Session naming works without a terminal; report terminal failures and skips. The ordinary-session explicit-target path ignores legacy `PI_SUBAGENT_RENAME_*` variables; a disabled target never runs even when a variable is `1`.
|
|
15
15
|
- 子进程归属由启动方通过 mux 协议提供,不把 pane 扩大为共享 tab,不修改父 workspace。Launchers grant child targets through the mux protocol; never expand a pane to a shared tab or rename the parent workspace.
|
|
16
16
|
- 子代理需要显式配置 `subagentExtensions` 加载 naming。Child extension isolation requires explicitly loading naming in `subagentExtensions`.
|
package/config.example.json
CHANGED
package/locales/index.json
CHANGED
|
@@ -159,6 +159,14 @@
|
|
|
159
159
|
"zh-CN": "最大标题长度:{value}",
|
|
160
160
|
"en-US": "Maximum title length: {value}"
|
|
161
161
|
},
|
|
162
|
+
"configMaxTokens": {
|
|
163
|
+
"zh-CN": "输出预算:{value} tokens",
|
|
164
|
+
"en-US": "Output budget: {value} tokens"
|
|
165
|
+
},
|
|
166
|
+
"configEffort": {
|
|
167
|
+
"zh-CN": "思考档位:{value}",
|
|
168
|
+
"en-US": "Thinking effort: {value}"
|
|
169
|
+
},
|
|
162
170
|
"configPreferredLength": {
|
|
163
171
|
"zh-CN": "首选标题长度:{value}",
|
|
164
172
|
"en-US": "Preferred title length: {value}"
|
|
@@ -207,6 +215,18 @@
|
|
|
207
215
|
"zh-CN": "最大标题长度(正整数)",
|
|
208
216
|
"en-US": "Maximum title length (positive integer)"
|
|
209
217
|
},
|
|
218
|
+
"configMaxTokensInput": {
|
|
219
|
+
"zh-CN": "输出预算(正整数,思考与标题共享)",
|
|
220
|
+
"en-US": "Output budget (positive integer, shared by thinking and title)"
|
|
221
|
+
},
|
|
222
|
+
"configEffortInput": {
|
|
223
|
+
"zh-CN": "思考档位",
|
|
224
|
+
"en-US": "Thinking effort"
|
|
225
|
+
},
|
|
226
|
+
"configEffortInvalid": {
|
|
227
|
+
"zh-CN": "思考档位无效:{value};可选值:{options}",
|
|
228
|
+
"en-US": "Invalid thinking effort: {value}; allowed: {options}"
|
|
229
|
+
},
|
|
210
230
|
"configPreferredLengthInput": {
|
|
211
231
|
"zh-CN": "首选标题长度(正整数)",
|
|
212
232
|
"en-US": "Preferred title length (positive integer)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-naming",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Configurable Pi session naming and manual terminal workspace/tab renaming",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"workspace"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"pi-extensions-i18n": "^0.
|
|
61
|
-
"pi-terminal-mux": "^0.6.
|
|
60
|
+
"pi-extensions-i18n": "^0.7.0",
|
|
61
|
+
"pi-terminal-mux": "^0.6.4"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@earendil-works/pi-coding-agent": ">=0.80.0",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
69
69
|
"@types/node": "24.12.4",
|
|
70
|
-
"pi-terminal-mux": "^0.6.
|
|
70
|
+
"pi-terminal-mux": "^0.6.4",
|
|
71
71
|
"tsx": "4.23.1",
|
|
72
72
|
"typescript": "5.9.3",
|
|
73
73
|
"@earendil-works/pi-ai": "0.85.1"
|
package/src/config.ts
CHANGED
|
@@ -8,6 +8,16 @@ const PACKAGE_NAME = "pi-naming";
|
|
|
8
8
|
const CONFIG_FILENAME = "config.json";
|
|
9
9
|
const FILE_NOT_FOUND_CODE = "ENOENT";
|
|
10
10
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
11
|
+
|
|
12
|
+
/** 思考档位:与 pi-ai 的 ThinkingLevel 一致,优先级由低到高。 */
|
|
13
|
+
export const TITLE_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
14
|
+
|
|
15
|
+
export type TitleEffort = typeof TITLE_EFFORT_LEVELS[number];
|
|
16
|
+
|
|
17
|
+
/** 标题请求的默认输出预算;推理模型的思考与标题共享该额度。 */
|
|
18
|
+
const DEFAULT_TITLE_MAX_TOKENS = 2048;
|
|
19
|
+
/** 标题请求的默认思考档位;命名不需要高强度推理。 */
|
|
20
|
+
const DEFAULT_TITLE_EFFORT: TitleEffort = "low";
|
|
11
21
|
const NAMING_SWITCHES = ["automaticNaming", "manualNaming"] as const;
|
|
12
22
|
const TARGET_KEYS = ["session", "workspace", "tab"] as const;
|
|
13
23
|
|
|
@@ -17,6 +27,8 @@ export interface TitleConfig {
|
|
|
17
27
|
language: string;
|
|
18
28
|
instructions: string;
|
|
19
29
|
timeoutMs: number;
|
|
30
|
+
maxTokens: number;
|
|
31
|
+
effort: TitleEffort;
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
export const DEFAULT_TITLE_CONFIG: Readonly<TitleConfig> = Object.freeze({
|
|
@@ -25,6 +37,8 @@ export const DEFAULT_TITLE_CONFIG: Readonly<TitleConfig> = Object.freeze({
|
|
|
25
37
|
language: "auto",
|
|
26
38
|
instructions: "",
|
|
27
39
|
timeoutMs: 10_000,
|
|
40
|
+
maxTokens: DEFAULT_TITLE_MAX_TOKENS,
|
|
41
|
+
effort: DEFAULT_TITLE_EFFORT,
|
|
28
42
|
});
|
|
29
43
|
|
|
30
44
|
export interface NamingConfig {
|
|
@@ -51,6 +65,11 @@ function positiveInteger(value: unknown, field: string): number {
|
|
|
51
65
|
return value;
|
|
52
66
|
}
|
|
53
67
|
|
|
68
|
+
/** 判断配置值是否为受支持的思考档位。 */
|
|
69
|
+
export function isTitleEffort(value: unknown): value is TitleEffort {
|
|
70
|
+
return TITLE_EFFORT_LEVELS.some((level) => level === value);
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
/** 校验配置字段与取值,为省略的字段补齐默认值。 */
|
|
55
74
|
export function parseConfig(value: unknown): NamingConfig {
|
|
56
75
|
const raw = object(value, "config");
|
|
@@ -68,7 +87,7 @@ export function parseConfig(value: unknown): NamingConfig {
|
|
|
68
87
|
if (!Object.hasOwn(DEFAULT_TITLE_CONFIG, key)) invalid(`title.${key}`);
|
|
69
88
|
}
|
|
70
89
|
const title: TitleConfig = { ...DEFAULT_TITLE_CONFIG };
|
|
71
|
-
for (const key of ["maxLength", "preferredLength", "timeoutMs"] as const) {
|
|
90
|
+
for (const key of ["maxLength", "preferredLength", "timeoutMs", "maxTokens"] as const) {
|
|
72
91
|
if (titleRaw[key] !== undefined) title[key] = positiveInteger(titleRaw[key], `title.${key}`);
|
|
73
92
|
}
|
|
74
93
|
if (title.preferredLength > title.maxLength) invalid("title.preferredLength");
|
|
@@ -79,6 +98,10 @@ export function parseConfig(value: unknown): NamingConfig {
|
|
|
79
98
|
title[key] = titleRaw[key].trim();
|
|
80
99
|
}
|
|
81
100
|
if (!title.language) invalid("title.language");
|
|
101
|
+
if (titleRaw.effort !== undefined) {
|
|
102
|
+
if (!isTitleEffort(titleRaw.effort)) invalid("title.effort");
|
|
103
|
+
title.effort = titleRaw.effort;
|
|
104
|
+
}
|
|
82
105
|
return {
|
|
83
106
|
automaticNaming: (raw.automaticNaming as boolean | undefined) ?? true,
|
|
84
107
|
manualNaming: (raw.manualNaming as boolean | undefined) ?? true,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { TerminalRenameOutcome, TerminalRenameTarget, ResolveRenameOptions } from "pi-terminal-mux";
|
|
3
|
-
import { configPath, loadConfig, parseConfig, saveConfig, type NamingConfig } from "./config.ts";
|
|
3
|
+
import { configPath, isTitleEffort, loadConfig, parseConfig, saveConfig, TITLE_EFFORT_LEVELS, type NamingConfig } from "./config.ts";
|
|
4
4
|
import { i18n } from "./i18n.ts";
|
|
5
5
|
import { installNoticeRenderer, notifyWithSource, type NoticeColor, type NoticeSource } from "pi-extensions-i18n";
|
|
6
6
|
|
|
@@ -26,11 +26,14 @@ const CONFIG_OPTION = {
|
|
|
26
26
|
language: 7,
|
|
27
27
|
instructions: 8,
|
|
28
28
|
timeout: 9,
|
|
29
|
+
maxTokens: 10,
|
|
30
|
+
effort: 11,
|
|
29
31
|
} as const;
|
|
30
32
|
const MAX_LENGTH_PRESETS = ["15", "30", "60"] as const;
|
|
31
33
|
const PREFERRED_LENGTH_PRESETS = ["10", "20", "40"] as const;
|
|
32
34
|
const LANGUAGE_PRESETS = ["auto", "中文", "English", "日本語"] as const;
|
|
33
35
|
const TIMEOUT_PRESETS = ["5000", "10000", "30000"] as const;
|
|
36
|
+
const MAX_TOKENS_PRESETS = ["1024", "2048", "4096"] as const;
|
|
34
37
|
const MESSAGE_TYPE = "pi-naming";
|
|
35
38
|
|
|
36
39
|
export interface TerminalNamingAdapter {
|
|
@@ -152,6 +155,8 @@ export function registerNamingConfigCommand(
|
|
|
152
155
|
i18n.t("configLanguage", { value: config.title.language }),
|
|
153
156
|
i18n.t("configInstructions", { value: config.title.instructions || i18n.t("configEmpty") }),
|
|
154
157
|
i18n.t("configTimeout", { value: config.title.timeoutMs }),
|
|
158
|
+
i18n.t("configMaxTokens", { value: config.title.maxTokens }),
|
|
159
|
+
i18n.t("configEffort", { value: config.title.effort }),
|
|
155
160
|
doneChoice,
|
|
156
161
|
];
|
|
157
162
|
const selected = await ctx.ui.select(i18n.t("configMenuTitle"), choices);
|
|
@@ -188,6 +193,14 @@ export function registerNamingConfigCommand(
|
|
|
188
193
|
inputTitle = i18n.t("configInstructionsInput");
|
|
189
194
|
inputValue = config.title.instructions;
|
|
190
195
|
options = [];
|
|
196
|
+
} else if (selectedIndex === CONFIG_OPTION.maxTokens) {
|
|
197
|
+
inputTitle = i18n.t("configMaxTokensInput");
|
|
198
|
+
inputValue = String(config.title.maxTokens);
|
|
199
|
+
options = MAX_TOKENS_PRESETS;
|
|
200
|
+
} else if (selectedIndex === CONFIG_OPTION.effort) {
|
|
201
|
+
inputTitle = i18n.t("configEffortInput");
|
|
202
|
+
inputValue = config.title.effort;
|
|
203
|
+
options = TITLE_EFFORT_LEVELS;
|
|
191
204
|
}
|
|
192
205
|
const input = selectedIndex === CONFIG_OPTION.instructions
|
|
193
206
|
? await ctx.ui.input(inputTitle, inputValue)
|
|
@@ -198,7 +211,18 @@ export function registerNamingConfigCommand(
|
|
|
198
211
|
else if (selectedIndex === CONFIG_OPTION.preferredLength) title.preferredLength = Number(input.trim());
|
|
199
212
|
else if (selectedIndex === CONFIG_OPTION.language) title.language = input;
|
|
200
213
|
else if (selectedIndex === CONFIG_OPTION.instructions) title.instructions = input;
|
|
201
|
-
else title.
|
|
214
|
+
else if (selectedIndex === CONFIG_OPTION.maxTokens) title.maxTokens = Number(input.trim());
|
|
215
|
+
else if (selectedIndex === CONFIG_OPTION.effort) {
|
|
216
|
+
// 自定义输入可能不是受支持的档位;明确报错并重开菜单,不静默丢弃也不落到其它字段。
|
|
217
|
+
if (!isTitleEffort(input)) {
|
|
218
|
+
report(pi, ctx, {
|
|
219
|
+
message: i18n.t("configEffortInvalid", { value: input, options: TITLE_EFFORT_LEVELS.join(", ") }),
|
|
220
|
+
level: "error",
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
title.effort = input;
|
|
225
|
+
} else title.timeoutMs = Number(input.trim());
|
|
202
226
|
try { next = parseConfig({ ...config, title }); }
|
|
203
227
|
catch (error) {
|
|
204
228
|
report(pi, ctx, { message: i18n.t("configCommandInvalid", { error: errorMessage(error) }), level: "error" });
|
package/src/session-name.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type {
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
SessionEntry,
|
|
@@ -6,17 +6,25 @@ import type {
|
|
|
6
6
|
import { i18n } from "./i18n.ts";
|
|
7
7
|
import { DEFAULT_TITLE_CONFIG, type TitleConfig } from "./config.ts";
|
|
8
8
|
|
|
9
|
-
const MIN_TITLE_OUTPUT_TOKENS = 64;
|
|
10
|
-
const TOKENS_PER_TITLE_CODE_POINT = 4;
|
|
11
|
-
|
|
12
9
|
/** 生成标题所需的最小 session 上下文;终端消费者可复用该契约。 */
|
|
13
10
|
export type SessionNameContext = Pick<ExtensionContext, "model" | "modelRegistry">;
|
|
14
11
|
|
|
15
12
|
/** 可注入的标题 completion,便于终端消费者和测试复用同一套请求逻辑。 */
|
|
16
|
-
export type SessionNameCompletion = (...args: Parameters<typeof
|
|
13
|
+
export type SessionNameCompletion = (...args: Parameters<typeof completeSimple>) => ReturnType<typeof completeSimple>;
|
|
17
14
|
|
|
18
15
|
type SessionNameModel = Parameters<SessionNameCompletion>[0];
|
|
19
16
|
|
|
17
|
+
/** 计算标题请求的输出预算:取配置的预算与模型输出上限的较小值。 */
|
|
18
|
+
export function resolveTitleMaxTokens(
|
|
19
|
+
model: Pick<SessionNameModel, "maxTokens">,
|
|
20
|
+
configuredMaxTokens: number,
|
|
21
|
+
): number {
|
|
22
|
+
// 推理模型的思考与标题共用同一份输出预算。预算过小时思考会把额度整个吃掉,
|
|
23
|
+
// 响应因 max_output_tokens 截断且不含标题文本,最终报“没有返回有效标题”。
|
|
24
|
+
// 因此预算由 title.maxTokens 统一控制,默认 2048 留出思考余量。
|
|
25
|
+
return Math.min(model.maxTokens, configuredMaxTokens);
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
/** 标题请求参数;userMessages 只应包含待分析的用户输入。 */
|
|
21
29
|
export type SessionNameRequest = {
|
|
22
30
|
userMessages: readonly string[];
|
|
@@ -127,7 +135,7 @@ export async function requestSessionName({
|
|
|
127
135
|
userMessages,
|
|
128
136
|
ctx,
|
|
129
137
|
signal,
|
|
130
|
-
completion =
|
|
138
|
+
completion = completeSimple,
|
|
131
139
|
title = DEFAULT_TITLE_CONFIG,
|
|
132
140
|
}: SessionNameRequest): Promise<string> {
|
|
133
141
|
if (userMessages.length === 0) {
|
|
@@ -166,11 +174,9 @@ export async function requestSessionName({
|
|
|
166
174
|
apiKey: auth.apiKey,
|
|
167
175
|
headers: auth.headers,
|
|
168
176
|
env: auth.env,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Math.max(MIN_TITLE_OUTPUT_TOKENS, title.maxLength * TOKENS_PER_TITLE_CODE_POINT),
|
|
173
|
-
),
|
|
177
|
+
maxTokens: resolveTitleMaxTokens(model, title.maxTokens),
|
|
178
|
+
// 命名不需要高强度推理;档位可配置,降低思考占用预算的波动。
|
|
179
|
+
reasoning: title.effort,
|
|
174
180
|
signal,
|
|
175
181
|
},
|
|
176
182
|
);
|