@andares/pi-toolkits 0.4.2 → 0.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/README.md +3 -3
- package/package.json +7 -6
- package/src/features/compact/index.ts +11 -9
- package/src/features/compact/prompt.ts +3 -0
- package/src/features/favorites/favorites.test.ts +18 -7
- package/src/features/favorites/patch-cycle.ts +7 -5
- package/src/features/favorites/patch-selector.ts +8 -4
- package/src/features/review/review.test.ts +7 -5
- package/src/lib/model-config.ts +6 -5
- package/src/lib/model-picker.ts +6 -5
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/@andares/pi-toolkits)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://pnpm.io)
|
|
11
|
-
[](https://github.com/earendil-works/pi)
|
|
12
12
|
[](https://github.com/andares/pi-toolkits)
|
|
13
13
|
|
|
14
14
|
一句话介绍:目前包含五个开箱即用的功能——**只读咨询模式** `ask`、**模型收藏** `favorites`、**提示词暂存** `stash`、**第三方代码评审** `third-review`、**独立压缩模型** `compact-model`,后续持续追加。
|
|
15
15
|
Currently ships five ready-to-use capabilities — **ask mode**, **model favorites**, **prompt stash**, **third-party code review** and a **dedicated compaction model** — with more to come.
|
|
16
16
|
|
|
17
|
-
**版本要求 · Requirements**:pi
|
|
17
|
+
**版本要求 · Requirements**:pi `>=0.86.0 <2.0.0`(覆盖最近 4 个中版本线 0.86.x / 0.87.x / 0.99.x / 1.0.x —— 0.88–0.98 从未发布。区间内全部 10 个已发布版本逐版本比对宿主 dist:用到的扩展 API 无差异,favorites 两处运行时 patch 目标 `ModelSelectorComponent` / `AgentSession.cycleModel` 字节级一致;并在 0.86.0、0.86.1、0.87.1、0.99.2、1.0.2 上逐一通过 typecheck + 全量测试,在真实 pi 1.0.2 上冒烟加载无警告)。注:pi ≥ 0.99 的宿主要求 `@earendil-works/pi-tui` 声明为 peerDependencies `"*"`(由宿主提供),本包已按要求声明。
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -191,7 +191,7 @@ pi 的 `/compact` 与 auto-compact 默认**用当前会话模型**做上下文
|
|
|
191
191
|
| auto-compact 阈值触发 | 压缩模型总结 |
|
|
192
192
|
| context overflow 恢复 | 压缩模型总结,重试行为不变 |
|
|
193
193
|
|
|
194
|
-
**接管后与 pi 内建行为完全一致的部分**(逐字同步 0.87.1
|
|
194
|
+
**接管后与 pi 内建行为完全一致的部分**(逐字同步 pi 内建实现,0.87.1 起字节级不变、已核对至 1.0.2,只换执行模型):
|
|
195
195
|
|
|
196
196
|
- **摘要格式** —— Goal / Progress / Key Decisions 结构化 checkpoint;已有摘要时用迭代更新版 prompt,信息延续不丢
|
|
197
197
|
- **切分行为** —— 保留最近 `keepRecentTokens` 的策略、`firstKeptEntryId` / `tokensBefore` 原样回传,不动
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andares/pi-toolkits",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Integrated toolkit extension for pi — ask mode (read-only Q&A), model favorites, prompt stash, third-party code review, and a dedicated compaction model",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,15 +27,16 @@
|
|
|
27
27
|
"toolkit"
|
|
28
28
|
],
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@earendil-works/pi-coding-agent": "
|
|
30
|
+
"@earendil-works/pi-coding-agent": ">=0.86.0 <2.0.0",
|
|
31
|
+
"@earendil-works/pi-tui": "*"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@dreki-gg/pi-command-sandbox": "^0.4.3"
|
|
34
|
-
"@earendil-works/pi-tui": "^0.87.1"
|
|
34
|
+
"@dreki-gg/pi-command-sandbox": "^0.4.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@earendil-works/pi-ai": "^0.
|
|
38
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
37
|
+
"@earendil-works/pi-ai": "^1.0.2",
|
|
38
|
+
"@earendil-works/pi-coding-agent": "^1.0.2",
|
|
39
|
+
"@earendil-works/pi-tui": "^1.0.2",
|
|
39
40
|
"@types/node": "^26.6.2",
|
|
40
41
|
"typescript": "^5.6.0",
|
|
41
42
|
"vitest": "^4.1.10"
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* emits for ALL three trigger reasons (manual / threshold / overflow)
|
|
11
11
|
* before running its own compaction
|
|
12
12
|
* - prompts, message assembly, split-turn handling, output budgets, and the
|
|
13
|
-
* summary file-list tail are verbatim pi
|
|
14
|
-
* the executing model
|
|
13
|
+
* summary file-list tail are verbatim pi built-in behavior (copied from
|
|
14
|
+
* 0.87.1, verified unchanged through 1.0.2) — only the executing model
|
|
15
|
+
* differs
|
|
15
16
|
* - lossless fallback: unset config, unavailable model, empty/failed
|
|
16
17
|
* summary, or user abort → return undefined and pi's default compaction
|
|
17
18
|
* (session model) runs untouched. The extension can never make
|
|
@@ -115,8 +116,8 @@ async function completeSummary(
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
* pi's output-budget formula (0.87.1
|
|
119
|
-
* reserveTokens clamped by the model's own output cap.
|
|
119
|
+
* pi's output-budget formula (compaction.js, 0.87.1 → 1.0.2 unchanged): a
|
|
120
|
+
* fraction of reserveTokens clamped by the model's own output cap.
|
|
120
121
|
*/
|
|
121
122
|
function summarizationBudget(
|
|
122
123
|
fraction: number,
|
|
@@ -131,11 +132,12 @@ function summarizationBudget(
|
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
134
|
* Run the full compaction summarization with the configured model,
|
|
134
|
-
* replicating pi 0.87.1
|
|
135
|
-
* iterative-update prompt, custom
|
|
136
|
-
* turn-prefix checkpoint when the cut point
|
|
137
|
-
* summary with the file-list tail. Returns
|
|
138
|
-
* notifying (pi default compaction then takes
|
|
135
|
+
* replicating pi's compact() (0.87.1, verified unchanged through 1.0.2):
|
|
136
|
+
* history summary (first-connection or iterative-update prompt, custom
|
|
137
|
+
* focus appended), plus a separate turn-prefix checkpoint when the cut point
|
|
138
|
+
* splits a turn, merged into one summary with the file-list tail. Returns
|
|
139
|
+
* undefined on any failure after notifying (pi default compaction then takes
|
|
140
|
+
* over).
|
|
139
141
|
*/
|
|
140
142
|
async function runCompactModelCompaction(
|
|
141
143
|
ctx: ExtensionContext,
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* compaction (dist/core/compaction/compaction.js and utils.js) so that
|
|
6
6
|
* summaries produced by the configured compact model are formatted exactly
|
|
7
7
|
* like pi's own compaction output. Only the executing model differs.
|
|
8
|
+
* Verified byte-identical against pi 1.0.2 (0.88–0.98 were never published;
|
|
9
|
+
* the only 0.87.1→1.0.2 change there is a combineUsage refactor into a
|
|
10
|
+
* shared module, no prompt or budget change).
|
|
8
11
|
*
|
|
9
12
|
* Update these strings whenever pi changes its built-in prompts (diff
|
|
10
13
|
* dist/core/compaction/compaction.js between versions).
|
|
@@ -23,7 +23,15 @@ import {
|
|
|
23
23
|
ModelSelectorComponent,
|
|
24
24
|
type Theme,
|
|
25
25
|
} from "@earendil-works/pi-coding-agent";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
afterAll,
|
|
28
|
+
beforeEach,
|
|
29
|
+
beforeAll,
|
|
30
|
+
describe,
|
|
31
|
+
expect,
|
|
32
|
+
it,
|
|
33
|
+
vi,
|
|
34
|
+
} from "vitest";
|
|
27
35
|
import {
|
|
28
36
|
FavoritesStore,
|
|
29
37
|
getFavoritesStore,
|
|
@@ -102,14 +110,17 @@ describe("FavoritesStore", () => {
|
|
|
102
110
|
it("falls back to empty + defaults on a corrupted file", () => {
|
|
103
111
|
const file = freshStoreFile();
|
|
104
112
|
// Intentionally write invalid JSON — the READ side must tolerate it.
|
|
113
|
+
writeFileSync(file, "{ not json", "utf8");
|
|
114
|
+
// Suppress the designed degradation log; the spy asserts it fired.
|
|
115
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
105
116
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
const store = new FavoritesStore(file);
|
|
118
|
+
expect(store.count()).toBe(0);
|
|
119
|
+
expect(store.getConfig().cycleOnlyFavorites).toBe(true);
|
|
120
|
+
expect(warn).toHaveBeenCalled();
|
|
121
|
+
} finally {
|
|
122
|
+
warn.mockRestore();
|
|
109
123
|
}
|
|
110
|
-
const store = new FavoritesStore(file);
|
|
111
|
-
expect(store.count()).toBe(0);
|
|
112
|
-
expect(store.getConfig().cycleOnlyFavorites).toBe(true);
|
|
113
124
|
});
|
|
114
125
|
|
|
115
126
|
it("loads cycleOnlyFavorites config from disk", () => {
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* favorite, backward to the last (user-confirmed)
|
|
14
14
|
* - with exactly one favorite, already on it → undefined (stock "only one
|
|
15
15
|
* model available" feedback); otherwise jump to it
|
|
16
|
-
* - the apply logic mirrors pi
|
|
17
|
-
* (
|
|
16
|
+
* - the apply logic mirrors pi's _cycleScopedModel/_cycleAvailableModel
|
|
17
|
+
* (byte-identical across every published version in 0.86.0 → 1.0.2:
|
|
18
|
+
* state swap, session persistence, default-model persistence only when
|
|
18
19
|
* `options.persist` is set, thinking-level re-clamp, model_select emit
|
|
19
20
|
* with source "cycle")
|
|
20
21
|
*
|
|
@@ -28,9 +29,10 @@ import {
|
|
|
28
29
|
import { getFavoritesStore, modelKey } from "./store.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
* pi's `Model<any>` — the exact type of `ModelCycleResult.model`
|
|
32
|
-
* pi-coding-agent 0.
|
|
33
|
-
* @earendil-works/pi-ai,
|
|
32
|
+
* pi's `Model<any>` — the exact type of `ModelCycleResult.model`
|
|
33
|
+
* (pi-coding-agent 0.86–1.0; the field is unchanged across that span).
|
|
34
|
+
* Derived here instead of importing `Model` from @earendil-works/pi-ai,
|
|
35
|
+
* whose index.d.ts (0.86 through 1.0.2 alike) re-exports it via an
|
|
34
36
|
* extensioned `"./types.ts"` specifier that not every TS resolver substitutes
|
|
35
37
|
* to `types.d.ts` (tsc 5.9 bundler does; other tsserver builds don't).
|
|
36
38
|
*/
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Version guard: if the prototype no longer has the expected methods (pi
|
|
21
21
|
* upgraded internals), patches are skipped with a warning — the extension
|
|
22
|
-
* keeps loading and ctrl+p falls back to stock behavior.
|
|
22
|
+
* keeps loading and ctrl+p falls back to stock behavior. The prototype
|
|
23
|
+
* shape touched here (handleInput/updateList/filterModels and the instance
|
|
24
|
+
* fields below) is byte-identical across every published version in
|
|
25
|
+
* pi 0.86.0 → 1.0.2.
|
|
23
26
|
*/
|
|
24
27
|
import {
|
|
25
28
|
ModelSelectorComponent,
|
|
@@ -113,8 +116,9 @@ let patched = false;
|
|
|
113
116
|
export function applyModelSelectorPatches(): boolean {
|
|
114
117
|
if (patched) return true;
|
|
115
118
|
// SAFETY: ModelSelectorComponent's members are TS-private (compile-time
|
|
116
|
-
// only); the structural view matches pi 0.
|
|
117
|
-
//
|
|
119
|
+
// only); the structural view matches pi 0.86–1.0's prototype shape
|
|
120
|
+
// (byte-identical across that span) and every touched method is
|
|
121
|
+
// existence-checked below before any patching happens.
|
|
118
122
|
const proto =
|
|
119
123
|
ModelSelectorComponent.prototype as unknown as SelectorPatchTarget;
|
|
120
124
|
if (
|
|
@@ -151,7 +155,7 @@ export function applyModelSelectorPatches(): boolean {
|
|
|
151
155
|
};
|
|
152
156
|
|
|
153
157
|
// ── 2. List rendering: hint row + bold bright yellow favorites ────────
|
|
154
|
-
// Replicates the built-in updateList layout (pi 0.
|
|
158
|
+
// Replicates the built-in updateList layout (pi 0.86–1.0: fixed-width cursor
|
|
155
159
|
// and ✓ current-marker columns, provider badge, `· default` badge, scroll
|
|
156
160
|
// indicator, empty/error states, refresh status, Model Name footer) with
|
|
157
161
|
// one change: favorited model ids render in the favorite style.
|
|
@@ -204,13 +204,15 @@ describe("ReviewStore", () => {
|
|
|
204
204
|
|
|
205
205
|
it("falls back to unset on a corrupted file", () => {
|
|
206
206
|
const file = freshStoreFile();
|
|
207
|
+
writeFileSync(file, "{ not json", "utf8");
|
|
208
|
+
// Suppress the designed degradation log; the spy asserts it fired.
|
|
209
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
207
210
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
expect(new ReviewStore(file).getModel()).toBeUndefined();
|
|
212
|
+
expect(warn).toHaveBeenCalled();
|
|
213
|
+
} finally {
|
|
214
|
+
warn.mockRestore();
|
|
211
215
|
}
|
|
212
|
-
const store = new ReviewStore(file);
|
|
213
|
-
expect(store.getModel()).toBeUndefined();
|
|
214
216
|
});
|
|
215
217
|
|
|
216
218
|
it("ignores a stored value without a slash", () => {
|
package/src/lib/model-config.ts
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* (review, compact-model — favorites keeps its own store-local key logic).
|
|
5
5
|
*
|
|
6
6
|
* Types are derived from pi-coding-agent's own exports (ModelRegistry)
|
|
7
|
-
* rather than imported from @earendil-works/pi-ai: pi-ai
|
|
8
|
-
* re-exports `Model`/`Api` via extensioned
|
|
9
|
-
* tsc 5.9 bundler resolution substitutes to
|
|
10
|
-
* builds do not. Deriving from ModelRegistry keeps
|
|
11
|
-
* agreement with the exact type
|
|
7
|
+
* rather than imported from @earendil-works/pi-ai: pi-ai's index.d.ts (0.87
|
|
8
|
+
* through 1.0.2 alike) re-exports `Model`/`Api` via extensioned
|
|
9
|
+
* `./types.ts` specifiers, which tsc 5.9 bundler resolution substitutes to
|
|
10
|
+
* `.d.ts` but some tsserver builds do not. Deriving from ModelRegistry keeps
|
|
11
|
+
* every resolver in agreement with the exact type
|
|
12
|
+
* `ctx.modelRegistry.getAvailable()` returns.
|
|
12
13
|
*/
|
|
13
14
|
import type {
|
|
14
15
|
ExtensionContext,
|
package/src/lib/model-picker.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The component is driven through a small ModelRuntime facade backed by
|
|
11
11
|
* ctx.modelRegistry (pi's extension-facing wrapper). SAFETY: verified
|
|
12
|
-
* against pi 0.
|
|
13
|
-
* model-catalog-refresh.js
|
|
14
|
-
* getModel, getError and refresh
|
|
15
|
-
* exactly those
|
|
12
|
+
* against pi 0.86–1.0 dist (modes/interactive/components/model-selector.js +
|
|
13
|
+
* model-catalog-refresh.js — byte-identical across that span) — the
|
|
14
|
+
* component only calls getAvailableSnapshot, getModel, getError and refresh
|
|
15
|
+
* on modelRuntime, and ModelRegistry provides exactly those
|
|
16
|
+
* (getAvailable/find/getError/refresh).
|
|
16
17
|
*
|
|
17
18
|
* `current` is passed as the component's currentModel: the built-in selector
|
|
18
19
|
* preselects, ✓-marks and sorts-first its currentModel, so one Enter accepts
|
|
@@ -41,7 +42,7 @@ function modelRuntimeFacade(ctx: ExtensionContext): ModelRuntime {
|
|
|
41
42
|
refresh: (options?: { signal?: AbortSignal }) =>
|
|
42
43
|
ctx.modelRegistry.refresh(options),
|
|
43
44
|
};
|
|
44
|
-
// SAFETY: the selector only touches the four members above (pi 0.
|
|
45
|
+
// SAFETY: the selector only touches the four members above (pi 0.86–1.0
|
|
45
46
|
// model-selector.js); everything else on ModelRuntime is never reached
|
|
46
47
|
// from this component.
|
|
47
48
|
return facade as unknown as ModelRuntime;
|