pi-distill 1.0.0 → 1.0.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 +5 -7
- package/README.zh-CN.md +6 -8
- package/config.example.json +0 -1
- package/locales/fallback-renderer.json +3 -3
- package/locales/index.json +0 -8
- package/package.json +5 -1
- package/src/fallback-renderer.ts +2 -2
- package/src/index.ts +20 -56
- package/src/output-limit.ts +0 -53
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ The distillation prompt strictly follows the current locale selected by `/pi-lan
|
|
|
33
33
|
- Treats a prompt containing only `RAW` as an explicit request for the original output.
|
|
34
34
|
- Uses the current session model by default, or a configured `provider/model` override.
|
|
35
35
|
- Keeps diagnostic metadata such as status, character counts, compression ratio, duration, and anomalies in the tool result details.
|
|
36
|
-
-
|
|
36
|
+
- Oversized output is no longer written to a file or truncated by pi-distill; it is left to Pi's own output-limiting mechanism.
|
|
37
37
|
- Adds a compact audit card when the active Pi display middleware is available, with a fallback renderer otherwise. The shared display protocol is provided by `pi-extensions-tool-display`.
|
|
38
38
|
|
|
39
39
|
It does not register a second `bash`, `read`, `grep`, or `find` tool.
|
|
@@ -117,7 +117,7 @@ Agent consumes a result suited to the current decision, with auditable diagnosti
|
|
|
117
117
|
| `outputRequest` | Behavior | Use it when |
|
|
118
118
|
| --- | --- | --- |
|
|
119
119
|
| Omitted | Invalid tool call; Pi rejects the call before the underlying tool runs | Never omit it; use `RAW` when no compression is explicitly requested |
|
|
120
|
-
| Exactly `RAW` (case-insensitive) | Skip the distillation model and keep the complete original text; oversized text is
|
|
120
|
+
| Exactly `RAW` (case-insensitive) | Skip the distillation model and keep the complete original text; oversized text is handled by Pi's own output-limiting mechanism | You need to inspect, copy, or verify exact output |
|
|
121
121
|
| Any non-empty value other than `RAW` | Call the model once the output reaches the threshold; the prompt defines what to retain | “Keep errors, warnings, and final status” workflows |
|
|
122
122
|
| Any non-text content such as images or audio | Preserve the result as-is; do not send it to the distillation model or apply text truncation | Image reads, binary results, and mixed text/media results |
|
|
123
123
|
|
|
@@ -138,7 +138,7 @@ The distillation prompt strictly follows the locale selected by `/pi-language`:
|
|
|
138
138
|
- Registers no replacement tools, does not change tool execution semantics, and does not require a separately installed `pi-tool-display` host package.
|
|
139
139
|
- Text distillation is lossy; use `RAW` when completeness matters.
|
|
140
140
|
- Non-text results are a completeness boundary: images, audio, binary data, and mixed content bypass text distillation.
|
|
141
|
-
- Oversized distilled or final text is written to a
|
|
141
|
+
- Oversized distilled or final text is no longer written to a file or truncated by pi-distill; it is left to Pi's own output-limiting mechanism, preventing unbounded context growth.
|
|
142
142
|
- If no model is available, distillation fails open: the original result is retained and Pi can continue running.
|
|
143
143
|
|
|
144
144
|
## Configuration
|
|
@@ -157,7 +157,6 @@ Start from [`config.example.json`](./config.example.json):
|
|
|
157
157
|
"model": "",
|
|
158
158
|
"minChars": 200,
|
|
159
159
|
"maxChars": 100000,
|
|
160
|
-
"maxOutputChars": 10000,
|
|
161
160
|
"timeoutSeconds": 10,
|
|
162
161
|
"missedCompressionRatio": 10,
|
|
163
162
|
"summarizeErrors": true,
|
|
@@ -175,14 +174,13 @@ Configuration-file fields take precedence over environment variables. Unspecifie
|
|
|
175
174
|
| --- | --- |
|
|
176
175
|
| `model` | Optional `provider/model`; empty uses the current Pi session model. |
|
|
177
176
|
| `minChars` | Minimum output size before a summary is requested. |
|
|
178
|
-
| `maxChars` | Maximum
|
|
179
|
-
| `maxOutputChars` | Maximum text size returned to the agent; larger results are written to a file. |
|
|
177
|
+
| `maxChars` | Maximum output budget for the distillation model (about `maxChars / 2` tokens) and a diagnostic reference; no longer used to write files. |
|
|
180
178
|
| `timeoutSeconds` | Maximum time allowed for the distillation model call. |
|
|
181
179
|
| `missedCompressionRatio` | Long-output threshold for a diagnostic when no summary prompt was supplied. |
|
|
182
180
|
| `summarizeErrors` | Whether error results should still be sent to the distillation model. |
|
|
183
181
|
| `render.*` | Controls the audit card, prompt preview, and result preview. |
|
|
184
182
|
|
|
185
|
-
The main environment variables are `PI_DISTILL_MODEL`, `PI_DISTILL_MIN_CHARS`, `PI_DISTILL_MAX_CHARS`, `
|
|
183
|
+
The main environment variables are `PI_DISTILL_MODEL`, `PI_DISTILL_MIN_CHARS`, `PI_DISTILL_MAX_CHARS`, `PI_DISTILL_TIMEOUT_SECONDS`, `PI_DISTILL_MISSED_COMPRESSION_RATIO`, and `PI_DISTILL_SUMMARIZE_ERRORS`. The legacy `maxOutputChars` / `PI_DISTILL_MAX_OUTPUT_CHARS` option is still parsed for backward compatibility but no longer has any effect.
|
|
186
184
|
|
|
187
185
|
## Requirements
|
|
188
186
|
|
package/README.zh-CN.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
- 当提示词严格只有 `RAW` 时,视为明确要求返回原始输出。
|
|
34
34
|
- 默认使用当前会话模型,也可以配置独立的 `provider/model`。
|
|
35
35
|
- 在工具结果 details 中保留状态、字符数、压缩比、耗时和异常等诊断信息。
|
|
36
|
-
-
|
|
36
|
+
- 超长输出不再由 pi-distill 写文件或截断,统一交由 Pi 自身的输出限制机制处理。
|
|
37
37
|
- 当前 Pi 展示中间件可用时显示紧凑审计卡片,否则使用自己的 fallback renderer。展示协议由公共运行库 `pi-extensions-tool-display` 提供。
|
|
38
38
|
|
|
39
39
|
它不会注册第二个 `bash`、`read`、`grep` 或 `find` 工具。
|
|
@@ -103,7 +103,7 @@ Agent 提出处理目标
|
|
|
103
103
|
↓ 通过 outputRequest 传给工具
|
|
104
104
|
工具执行真实操作,返回 stdout / stderr / 文件内容 / 多媒体结果
|
|
105
105
|
↓
|
|
106
|
-
pi-distill
|
|
106
|
+
pi-distill 根据真实结果和配置决定:原样返回,或调用模型提炼
|
|
107
107
|
↓
|
|
108
108
|
Agent 消费更适合当前决策的结果,并获得可审计的处理诊断
|
|
109
109
|
```
|
|
@@ -119,7 +119,7 @@ Agent 消费更适合当前决策的结果,并获得可审计的处理诊断
|
|
|
119
119
|
| `outputRequest` | 行为 | 适用场景 |
|
|
120
120
|
| --- | --- | --- |
|
|
121
121
|
| 未提供 | 工具调用无效;Pi 会在底层工具执行前拒绝该调用 | 不要省略;未明确要求压缩时使用 `RAW` |
|
|
122
|
-
| 严格为 `RAW`(大小写不敏感) |
|
|
122
|
+
| 严格为 `RAW`(大小写不敏感) | 不调用提炼模型,保留完整原始文本;超长时由 Pi 自身的输出限制机制处理 | 逐字核对、复制内容、需要完整日志时 |
|
|
123
123
|
| 任意非空且非 `RAW` | 输出达到阈值后调用模型,具体保留内容由 prompt 决定 | “只保留错误、警告和最终状态”等场景 |
|
|
124
124
|
| 包含图片、音频或其他非文本内容 | 原样保留,不发送给提炼模型,不做文本长度截断 | 图片读取、二进制结果、混合文本与图片结果 |
|
|
125
125
|
|
|
@@ -140,7 +140,7 @@ Agent 消费更适合当前决策的结果,并获得可审计的处理诊断
|
|
|
140
140
|
- 不注册替代工具,不改变原工具的执行语义,也不要求额外安装独立的 `pi-tool-display` 宿主包。
|
|
141
141
|
- 文本提炼是有损操作;完整性要求应使用 `RAW`。
|
|
142
142
|
- 非文本结果是完整性边界:图片、音频、二进制和混合 content 不进入文本提炼链路。
|
|
143
|
-
-
|
|
143
|
+
- 超长输出不再由 pi-distill 写临时文件或截断,统一交由 Pi 自身的输出限制机制处理,避免上下文无限膨胀。
|
|
144
144
|
- 当前会话没有模型时,提炼会失败并保留原始结果,不阻止 Pi 启动。
|
|
145
145
|
|
|
146
146
|
## 配置
|
|
@@ -159,7 +159,6 @@ Agent 消费更适合当前决策的结果,并获得可审计的处理诊断
|
|
|
159
159
|
"model": "",
|
|
160
160
|
"minChars": 200,
|
|
161
161
|
"maxChars": 100000,
|
|
162
|
-
"maxOutputChars": 10000,
|
|
163
162
|
"timeoutSeconds": 10,
|
|
164
163
|
"missedCompressionRatio": 10,
|
|
165
164
|
"summarizeErrors": true,
|
|
@@ -177,14 +176,13 @@ Agent 消费更适合当前决策的结果,并获得可审计的处理诊断
|
|
|
177
176
|
| --- | --- |
|
|
178
177
|
| `model` | 可选的 `provider/model`;为空时使用当前 Pi 会话模型。 |
|
|
179
178
|
| `minChars` | 达到此输出长度后才请求提炼。 |
|
|
180
|
-
| `maxChars` |
|
|
181
|
-
| `maxOutputChars` | 返回给 Agent 的最大文本长度,超出后写入文件。 |
|
|
179
|
+
| `maxChars` | 提炼模型的最大输出预算(约 `maxChars / 2` tokens),同时作为诊断参考;不再用于写文件。 |
|
|
182
180
|
| `timeoutSeconds` | 提炼模型调用的最长等待时间。 |
|
|
183
181
|
| `missedCompressionRatio` | 没有提供摘要 prompt 时,用于长输出诊断的倍数阈值。 |
|
|
184
182
|
| `summarizeErrors` | 工具返回错误时是否仍发送给提炼模型。 |
|
|
185
183
|
| `render.*` | 控制审计卡片、prompt 预览和结果预览。 |
|
|
186
184
|
|
|
187
|
-
主要环境变量包括 `PI_DISTILL_MODEL`、`PI_DISTILL_MIN_CHARS`、`PI_DISTILL_MAX_CHARS`、`
|
|
185
|
+
主要环境变量包括 `PI_DISTILL_MODEL`、`PI_DISTILL_MIN_CHARS`、`PI_DISTILL_MAX_CHARS`、`PI_DISTILL_TIMEOUT_SECONDS`、`PI_DISTILL_MISSED_COMPRESSION_RATIO` 和 `PI_DISTILL_SUMMARIZE_ERRORS`。旧配置中的 `maxOutputChars` / `PI_DISTILL_MAX_OUTPUT_CHARS` 仍会被解析以兼容旧文件,但不再生效。
|
|
188
186
|
|
|
189
187
|
## 要求
|
|
190
188
|
|
package/config.example.json
CHANGED
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"en-US": " • Ctrl+O to expand"
|
|
53
53
|
},
|
|
54
54
|
"header": {
|
|
55
|
-
"zh-CN": "
|
|
56
|
-
"en-US": "
|
|
55
|
+
"zh-CN": "⟡ Distill {status}",
|
|
56
|
+
"en-US": "⟡ Distill {status}"
|
|
57
57
|
},
|
|
58
58
|
"summary": {
|
|
59
59
|
"zh-CN": "摘要",
|
|
@@ -75,4 +75,4 @@
|
|
|
75
75
|
"zh-CN": "outputRequest",
|
|
76
76
|
"en-US": "outputRequest"
|
|
77
77
|
}
|
|
78
|
-
}
|
|
78
|
+
}
|
package/locales/index.json
CHANGED
|
@@ -63,10 +63,6 @@
|
|
|
63
63
|
"zh-CN": "摘要上限:{value} 字符",
|
|
64
64
|
"en-US": "Summary limit: {value} chars"
|
|
65
65
|
},
|
|
66
|
-
"finalLimit": {
|
|
67
|
-
"zh-CN": "最终输出上限:{value} 字符",
|
|
68
|
-
"en-US": "Final output limit: {value} chars"
|
|
69
|
-
},
|
|
70
66
|
"timeout": {
|
|
71
67
|
"zh-CN": "超时:{value}s",
|
|
72
68
|
"en-US": "Timeout: {value}s"
|
|
@@ -99,10 +95,6 @@
|
|
|
99
95
|
"zh-CN": "摘要字符上限",
|
|
100
96
|
"en-US": "Summary character limit"
|
|
101
97
|
},
|
|
102
|
-
"finalLimitTitle": {
|
|
103
|
-
"zh-CN": "最终输出字符上限",
|
|
104
|
-
"en-US": "Final output character limit"
|
|
105
|
-
},
|
|
106
98
|
"timeoutTitle": {
|
|
107
99
|
"zh-CN": "提炼模型超时(秒)",
|
|
108
100
|
"en-US": "Summarizer timeout (seconds)"
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-distill",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Pi tool-output distillation with file-first configuration",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
6
10
|
"files": [
|
|
7
11
|
"index.ts",
|
|
8
12
|
"src",
|
package/src/fallback-renderer.ts
CHANGED
|
@@ -58,8 +58,8 @@ function formatCount(value: number): string {
|
|
|
58
58
|
|
|
59
59
|
function renderDistillAuditLine(audit: DistillAuditView, line: string, index: number, theme: RenderTheme): string {
|
|
60
60
|
if (index === 0) {
|
|
61
|
-
const title = theme.fg("accent", theme.bold("
|
|
62
|
-
const afterTitle = line.slice("
|
|
61
|
+
const title = theme.fg("accent", theme.bold("⟡ Distill"));
|
|
62
|
+
const afterTitle = line.slice("⟡ Distill".length);
|
|
63
63
|
const statusIndex = afterTitle.indexOf(audit.statusLabel);
|
|
64
64
|
if (statusIndex < 0) return `${title}${theme.fg("muted", afterTitle)}`;
|
|
65
65
|
const beforeStatus = afterTitle.slice(0, statusIndex);
|
package/src/index.ts
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 所有工具统一使用 outputRequest:严格传入 RAW 时返回原始输出;其他非空
|
|
8
8
|
* outputRequest 表示调用提炼模型,具体保留内容由 outputRequest 决定。
|
|
9
|
-
*
|
|
9
|
+
* 超长输出由 Pi 自身的 output-limit 机制处理,pi-distill 不再做文件转储或截断。
|
|
10
10
|
*
|
|
11
11
|
* 配置文件优先;旧环境变量继续兼容:
|
|
12
12
|
* - ~/.pi/agent/extensions/pi-distill/config.json
|
|
13
13
|
* - PI_DISTILL_MODEL=provider/model
|
|
14
14
|
* - PI_DISTILL_MIN_CHARS=触发提炼的最小输出字符数,默认 200
|
|
15
|
-
* - PI_DISTILL_MAX_CHARS
|
|
16
|
-
* - PI_DISTILL_MAX_OUTPUT_CHARS=最终返回内容超过此字符数时写入文件,默认 10000
|
|
15
|
+
* - PI_DISTILL_MAX_CHARS=提炼模型最大输出字符数,默认 100000
|
|
17
16
|
* - PI_DISTILL_TIMEOUT_SECONDS=模型调用最长等待秒数,默认 10
|
|
18
17
|
* - PI_DISTILL_MISSED_COMPRESSION_RATIO=长输出提醒倍数,默认 10
|
|
19
18
|
* - 旧 PI_BASH_SUMMARY_* 变量作为兼容回退
|
|
@@ -36,10 +35,9 @@ import {
|
|
|
36
35
|
isDistillToolDisplayMiddlewareActive,
|
|
37
36
|
registerDistillToolDisplayMiddleware,
|
|
38
37
|
} from "./tool-display-bridge.ts";
|
|
39
|
-
import { getTextContent, hasNonTextContent
|
|
38
|
+
import { getTextContent, hasNonTextContent } from "./output-limit.ts";
|
|
40
39
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
41
|
-
import {
|
|
42
|
-
import { dirname, join } from "node:path";
|
|
40
|
+
import { dirname } from "node:path";
|
|
43
41
|
import { createTranslator, loadCatalog } from "pi-extensions-i18n";
|
|
44
42
|
import {
|
|
45
43
|
buildSummaryPrompt,
|
|
@@ -91,7 +89,6 @@ const OUTPUT_REQUEST_SYSTEM_GUIDELINE = i18n.t("outputRequestSystemGuideline");
|
|
|
91
89
|
type SummaryResult = {
|
|
92
90
|
text: string;
|
|
93
91
|
summaryChars: number;
|
|
94
|
-
summaryFilePath?: string;
|
|
95
92
|
summaryModel: string;
|
|
96
93
|
};
|
|
97
94
|
|
|
@@ -229,17 +226,6 @@ async function getCompleteOutput(result: ToolResult): Promise<string> {
|
|
|
229
226
|
return getTextContent(result);
|
|
230
227
|
}
|
|
231
228
|
|
|
232
|
-
async function writeSummaryFile(summary: string): Promise<string> {
|
|
233
|
-
const directory = join(tmpdir(), "pi-distill");
|
|
234
|
-
await mkdir(directory, { recursive: true });
|
|
235
|
-
const filePath = join(
|
|
236
|
-
directory,
|
|
237
|
-
`summary-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`,
|
|
238
|
-
);
|
|
239
|
-
await writeFile(filePath, summary, "utf8");
|
|
240
|
-
return filePath;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
229
|
async function summarizeOutput(
|
|
244
230
|
prompt: string,
|
|
245
231
|
output: string,
|
|
@@ -293,19 +279,9 @@ async function summarizeOutput(
|
|
|
293
279
|
.trim();
|
|
294
280
|
|
|
295
281
|
if (!summary) throw new Error("Summarizer returned no text");
|
|
296
|
-
if (summary.length <= config.maxChars) {
|
|
297
|
-
return {
|
|
298
|
-
text: summary,
|
|
299
|
-
summaryChars: summary.length,
|
|
300
|
-
summaryModel: `${model.provider}/${model.id}`,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const summaryFilePath = await writeSummaryFile(summary);
|
|
305
282
|
return {
|
|
306
|
-
text:
|
|
283
|
+
text: summary,
|
|
307
284
|
summaryChars: summary.length,
|
|
308
|
-
summaryFilePath,
|
|
309
285
|
summaryModel: `${model.provider}/${model.id}`,
|
|
310
286
|
};
|
|
311
287
|
}
|
|
@@ -325,9 +301,6 @@ async function processToolResult(
|
|
|
325
301
|
const loaded = loadDistillConfig();
|
|
326
302
|
const config = loaded.config;
|
|
327
303
|
const outputSummaryRender = { ...loaded.render };
|
|
328
|
-
const maxReturnedChars = config?.maxOutputChars ?? 10_000;
|
|
329
|
-
const finish = (candidate: ToolResult) =>
|
|
330
|
-
limitReturnedToolResult(candidate, maxReturnedChars);
|
|
331
304
|
if (loaded.warnings.length > 0) {
|
|
332
305
|
console.warn(`[pi-distill] ${loaded.warnings.join(" | ")}`);
|
|
333
306
|
}
|
|
@@ -354,12 +327,12 @@ async function processToolResult(
|
|
|
354
327
|
: "Distill is disabled by configuration.",
|
|
355
328
|
};
|
|
356
329
|
const agentDiagnostic = buildAgentDiagnosticText(diagnostics);
|
|
357
|
-
return
|
|
330
|
+
return {
|
|
358
331
|
...attachDiagnostics(result, diagnostics),
|
|
359
332
|
content: agentDiagnostic
|
|
360
333
|
? [...result.content, { type: "text", text: agentDiagnostic }]
|
|
361
334
|
: result.content,
|
|
362
|
-
}
|
|
335
|
+
};
|
|
363
336
|
}
|
|
364
337
|
|
|
365
338
|
let output: string;
|
|
@@ -369,7 +342,7 @@ async function processToolResult(
|
|
|
369
342
|
console.warn(
|
|
370
343
|
`[tool-output-summary] ${context.toolName} could not read the full output; returning the original result: ${error instanceof Error ? error.message : String(error)}`,
|
|
371
344
|
);
|
|
372
|
-
return
|
|
345
|
+
return attachDiagnostics(result, {
|
|
373
346
|
toolExecutionMs,
|
|
374
347
|
outputSummaryPrompt: prompt || undefined,
|
|
375
348
|
outputSummaryRender,
|
|
@@ -378,7 +351,7 @@ async function processToolResult(
|
|
|
378
351
|
summaryTriggerMaxChars: null,
|
|
379
352
|
summaryResultMaxChars: config.maxChars,
|
|
380
353
|
missedCompressionRatio: config.missedCompressionRatio,
|
|
381
|
-
})
|
|
354
|
+
});
|
|
382
355
|
}
|
|
383
356
|
|
|
384
357
|
const decision = decideOutputSummary(prompt, output, config, result.isError === true);
|
|
@@ -404,7 +377,7 @@ async function processToolResult(
|
|
|
404
377
|
? [...result.content, { type: "text", text: agentDiagnostic }]
|
|
405
378
|
: result.content,
|
|
406
379
|
};
|
|
407
|
-
return
|
|
380
|
+
return candidate;
|
|
408
381
|
}
|
|
409
382
|
|
|
410
383
|
const summaryStartedAt = performance.now();
|
|
@@ -451,7 +424,7 @@ async function processToolResult(
|
|
|
451
424
|
...(agentDiagnostic ? [{ type: "text", text: agentDiagnostic }] : []),
|
|
452
425
|
],
|
|
453
426
|
};
|
|
454
|
-
return
|
|
427
|
+
return candidate;
|
|
455
428
|
}
|
|
456
429
|
const compressionDiagnostics = getCompressionDiagnostics(
|
|
457
430
|
decision.intent,
|
|
@@ -465,7 +438,7 @@ async function processToolResult(
|
|
|
465
438
|
};
|
|
466
439
|
const agentDiagnostic = buildAgentDiagnosticText(diagnostics);
|
|
467
440
|
|
|
468
|
-
return
|
|
441
|
+
return {
|
|
469
442
|
// 输出处理参数只影响结果上下文,不改变原工具的业务执行。
|
|
470
443
|
// 异常诊断额外作为文本传给 Agent;普通成功总结不增加噪音。
|
|
471
444
|
content: [
|
|
@@ -486,10 +459,9 @@ async function processToolResult(
|
|
|
486
459
|
missedCompressionRatio: config.missedCompressionRatio,
|
|
487
460
|
summaryModel: summarized.summaryModel,
|
|
488
461
|
summaryText: summarized.text,
|
|
489
|
-
summaryFilePath: summarized.summaryFilePath,
|
|
490
462
|
...diagnostics,
|
|
491
463
|
},
|
|
492
|
-
}
|
|
464
|
+
};
|
|
493
465
|
} catch (error) {
|
|
494
466
|
const summaryDurationMs = Math.round(performance.now() - summaryStartedAt);
|
|
495
467
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -520,7 +492,7 @@ async function processToolResult(
|
|
|
520
492
|
? [...result.content, { type: "text", text: agentDiagnostic }]
|
|
521
493
|
: result.content,
|
|
522
494
|
};
|
|
523
|
-
return
|
|
495
|
+
return candidate;
|
|
524
496
|
} finally {
|
|
525
497
|
clearTimeout(timeout);
|
|
526
498
|
context.signal?.removeEventListener("abort", abortFromParent);
|
|
@@ -575,7 +547,7 @@ function toToolResultEventResult(result: ToolResult): ToolResultEventPatch {
|
|
|
575
547
|
};
|
|
576
548
|
}
|
|
577
549
|
|
|
578
|
-
type DistillUiConfig = Required<Pick<DistillConfigFile, "enabled" | "model" | "minChars" | "maxChars" | "
|
|
550
|
+
type DistillUiConfig = Required<Pick<DistillConfigFile, "enabled" | "model" | "minChars" | "maxChars" | "timeoutSeconds" | "missedCompressionRatio" | "summarizeErrors">> & {
|
|
579
551
|
render: DistillRenderConfig;
|
|
580
552
|
};
|
|
581
553
|
|
|
@@ -589,7 +561,6 @@ function getDistillUiConfig(): DistillUiConfig {
|
|
|
589
561
|
: "",
|
|
590
562
|
minChars: config?.minChars ?? 200,
|
|
591
563
|
maxChars: config?.maxChars ?? 100_000,
|
|
592
|
-
maxOutputChars: config?.maxOutputChars ?? 10_000,
|
|
593
564
|
timeoutSeconds: config?.timeoutSeconds ?? 10,
|
|
594
565
|
missedCompressionRatio: config?.missedCompressionRatio ?? 10,
|
|
595
566
|
summarizeErrors: config?.summarizeErrors ?? true,
|
|
@@ -654,7 +625,6 @@ async function runDistillConfigUi(ctx: ExtensionCommandContext, configPath: stri
|
|
|
654
625
|
i18n.t("model", { value: config.model || i18n.t("currentModel") }),
|
|
655
626
|
i18n.t("minOutput", { value: config.minChars }),
|
|
656
627
|
i18n.t("summaryLimit", { value: config.maxChars }),
|
|
657
|
-
i18n.t("finalLimit", { value: config.maxOutputChars }),
|
|
658
628
|
i18n.t("timeout", { value: config.timeoutSeconds }),
|
|
659
629
|
i18n.t("threshold", { value: config.missedCompressionRatio }),
|
|
660
630
|
i18n.t("summarizeErrors", { value: config.summarizeErrors ? i18n.t("on") : i18n.t("off") }),
|
|
@@ -687,33 +657,27 @@ async function runDistillConfigUi(ctx: ExtensionCommandContext, configPath: stri
|
|
|
687
657
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
688
658
|
}
|
|
689
659
|
} else if (choice === choices[4]) {
|
|
690
|
-
const value = await editDistillNumber(ctx, i18n.t("finalLimitTitle"), config.maxOutputChars);
|
|
691
|
-
if (value !== undefined) {
|
|
692
|
-
config.maxOutputChars = value;
|
|
693
|
-
await saveDistillConfigFile(ctx, config, configPath);
|
|
694
|
-
}
|
|
695
|
-
} else if (choice === choices[5]) {
|
|
696
660
|
const value = await editDistillNumber(ctx, i18n.t("timeoutTitle"), config.timeoutSeconds);
|
|
697
661
|
if (value !== undefined) {
|
|
698
662
|
config.timeoutSeconds = value;
|
|
699
663
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
700
664
|
}
|
|
701
|
-
} else if (choice === choices[
|
|
665
|
+
} else if (choice === choices[5]) {
|
|
702
666
|
const value = await editDistillNumber(ctx, i18n.t("thresholdTitle"), config.missedCompressionRatio);
|
|
703
667
|
if (value !== undefined) {
|
|
704
668
|
config.missedCompressionRatio = value;
|
|
705
669
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
706
670
|
}
|
|
707
|
-
} else if (choice === choices[
|
|
671
|
+
} else if (choice === choices[6]) {
|
|
708
672
|
config.summarizeErrors = !config.summarizeErrors;
|
|
709
673
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
710
|
-
} else if (choice === choices[
|
|
674
|
+
} else if (choice === choices[7]) {
|
|
711
675
|
config.render.enabled = !config.render.enabled;
|
|
712
676
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
713
|
-
} else if (choice === choices[
|
|
677
|
+
} else if (choice === choices[8]) {
|
|
714
678
|
config.render.showPrompt = !config.render.showPrompt;
|
|
715
679
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
716
|
-
} else if (choice === choices[
|
|
680
|
+
} else if (choice === choices[9]) {
|
|
717
681
|
config.render.showResult = !config.render.showResult;
|
|
718
682
|
await saveDistillConfigFile(ctx, config, configPath);
|
|
719
683
|
}
|
package/src/output-limit.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
1
|
export type OutputLimitToolResult = {
|
|
6
2
|
content: Array<{ type?: string; text?: string }>;
|
|
7
3
|
details?: {
|
|
@@ -19,52 +15,3 @@ export function getTextContent(result: OutputLimitToolResult): string {
|
|
|
19
15
|
export function hasNonTextContent(result: OutputLimitToolResult): boolean {
|
|
20
16
|
return result.content.some((content) => content.type !== "text" || typeof content.text !== "string");
|
|
21
17
|
}
|
|
22
|
-
|
|
23
|
-
async function writeSummaryFile(summary: string): Promise<string> {
|
|
24
|
-
const directory = join(tmpdir(), "pi-distill");
|
|
25
|
-
await mkdir(directory, { recursive: true });
|
|
26
|
-
const filePath = join(
|
|
27
|
-
directory,
|
|
28
|
-
`summary-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`,
|
|
29
|
-
);
|
|
30
|
-
await writeFile(filePath, summary, "utf8");
|
|
31
|
-
return filePath;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function limitReturnedToolResult(
|
|
35
|
-
result: OutputLimitToolResult,
|
|
36
|
-
maxChars: number,
|
|
37
|
-
): Promise<OutputLimitToolResult> {
|
|
38
|
-
if (hasNonTextContent(result)) return result;
|
|
39
|
-
|
|
40
|
-
const text = getTextContent(result);
|
|
41
|
-
if (text.length <= maxChars) return result;
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const filePath = await writeSummaryFile(text);
|
|
45
|
-
const pointer = `Output exceeded ${maxChars} chars and was written to: ${filePath}`;
|
|
46
|
-
return {
|
|
47
|
-
...result,
|
|
48
|
-
content: [{ type: "text", text: pointer.slice(0, maxChars) }],
|
|
49
|
-
details: {
|
|
50
|
-
...(result.details ?? {}),
|
|
51
|
-
fullOutputPath: filePath,
|
|
52
|
-
outputTruncated: true,
|
|
53
|
-
outputLimitChars: maxChars,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
} catch (error) {
|
|
57
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
-
console.warn(`[pi-distill] Failed to write oversized output to a temp file; returning a truncated result: ${message}`);
|
|
59
|
-
return {
|
|
60
|
-
...result,
|
|
61
|
-
content: [{ type: "text", text: text.slice(0, maxChars) }],
|
|
62
|
-
details: {
|
|
63
|
-
...(result.details ?? {}),
|
|
64
|
-
outputTruncated: true,
|
|
65
|
-
outputLimitChars: maxChars,
|
|
66
|
-
outputFileError: message,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|