@kingingwang/pi-windows-x64 0.86.1 → 0.87.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/bin/CHANGELOG.md +38 -0
- package/bin/README.md +36 -0
- package/bin/docs/compaction.md +23 -4
- package/bin/docs/extensions.md +70 -6
- package/bin/docs/models.md +29 -1
- package/bin/docs/sdk.md +13 -2
- package/bin/docs/session-format.md +15 -2
- package/bin/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/bin/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/bin/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/bin/examples/extensions/gondolin/package-lock.json +2 -2
- package/bin/examples/extensions/gondolin/package.json +1 -1
- package/bin/examples/extensions/sandbox/package-lock.json +2 -2
- package/bin/examples/extensions/sandbox/package.json +1 -1
- package/bin/examples/extensions/with-deps/package-lock.json +2 -2
- package/bin/examples/extensions/with-deps/package.json +1 -1
- package/bin/examples/plugins/pi-example-plugin/src/session.ts +3 -2
- package/bin/export-html/template.js +6 -1
- package/bin/package.json +8 -8
- package/bin/pi.exe +2 -2
- package/package.json +1 -1
package/bin/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.87.0] - 2026-09-21
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **Canonical session context and extension boundaries** — Edit model context without rewriting history and add actionable lifecycle hooks. See [ContextEditEntry](docs/session-format.md#contexteditentry) and [extension events](docs/extensions.md#extension-events).
|
|
8
|
+
- **Full-transcript context extensions** — Use `context_with_system` for per-request system-message transformations. See [`context_with_system`](docs/extensions.md#context_with_system).
|
|
9
|
+
- **Per-model image input limits** — Configure cache-safe image resizing per model for attachments, `read`, and tool-result images. See [Image Input Limits](docs/models.md#image-input-limits).
|
|
10
|
+
|
|
11
|
+
### Breaking Changes
|
|
12
|
+
|
|
13
|
+
- Removed the inherited `shouldStopAfterTurn` agent option. Use `finishTurn` and return `{ action: "end" }` instead. `finishTurn` runs before `turn_end` but applies the decision afterward, and it also receives error and aborted responses; migrate normal-response predicates by returning `undefined` for those hard exits. See the `@earendil-works/pi-agent-core` changelog for a complete before-and-after example.
|
|
14
|
+
- Added `ContextEditEntry` to the exported `SessionEntry` union. TypeScript consumers with exhaustive entry switches must handle `context_edit`; use `replacement: null` for omission and a content replacement otherwise.
|
|
15
|
+
- Made `SessionManager` canonical for `AgentSession` provider context. Assigning `session.agent.state.messages` no longer replaces future request history; restore with `SessionManager.inMemory(cwd, { id }, entries)`, navigate with `session.navigateTree()`, or append through `session.sessionManager` and call `session.refreshContext()`.
|
|
16
|
+
- Expanded `TurnEndEvent` with required boundary fields and added `AgentBeforeSettleEvent` to the exported `ExtensionEvent` union. Consumers constructing events or exhaustively switching on `ExtensionEvent` must handle the new shapes. `ExtensionRunner.emit()` no longer accepts `turn_end`; host integrations dispatch actionable boundaries with `emitBoundary(baseEvent, buildContext)`.
|
|
17
|
+
- Deferred runs requested from `agent_settled` handlers until all settled handlers finish. Handlers still observe `ctx.isIdle() === true`, but no longer see a reentrant `agent_start` during the same notification dispatch.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Added append-only model-context edits. For example, `sessionManager.appendContextEdit(entryId, null)` omits one message from future provider context without changing raw history, usage, or UI history.
|
|
22
|
+
- Added actionable `turn_end` and `agent_before_settle` extension boundaries. Return `{ entries: [...event.entries, draft], continue: true }` to persist structural entries in order and ensure one next provider request without changing steering or follow-up scheduling.
|
|
23
|
+
- Added retain-none compaction input: `sessionManager.appendCompaction(summary, null, tokensBefore)` stores the compaction's own ID as its kept boundary.
|
|
24
|
+
- Added the `context_with_system` extension event, which runs after `context` handlers on the full transcript including system messages and sends its result verbatim. See [`context_with_system`](docs/extensions.md#context_with_system).
|
|
25
|
+
- Added per-model image resize profiles through `inputLimits.images.resize` in `models.json`, applied to file attachments, image reads, and tool-result images ([#9631](https://github.com/earendil-works/pi/issues/9631)).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed string context-edit replacements producing invalid assistant and tool-result message content instead of text blocks.
|
|
30
|
+
- Fixed context-invisible boundary metadata and replacement edits causing newly appended or replaced input to be summarized before its first provider request.
|
|
31
|
+
- Fixed edited-context accounting both discarding valid assistant usage captured after the latest context edit and reusing that usage after a later compaction made it stale.
|
|
32
|
+
- Fixed selected error retries and final length/overflow recovery retaining abandoned model attempts in future provider context; post-run recovery omissions are now persisted without hiding raw transcript history or changing queue scheduling.
|
|
33
|
+
- Fixed `context` handlers that filter or slice messages dropping the prompt and tool declarations, which after extension-driven compaction left requests without built-in tools or made Codex emit raw tool-call text. Handlers no longer see system messages; Pi restores the prompt and tool state after they run. See [`context`](docs/extensions.md#context) ([#9789](https://github.com/earendil-works/pi/issues/9789), [#9822](https://github.com/earendil-works/pi/issues/9822)).
|
|
34
|
+
- Fixed `/bug` allowing uploads in offline mode while preserving local zip exports ([#9841](https://github.com/earendil-works/pi/pull/9841) by [@christianklotz](https://github.com/christianklotz)).
|
|
35
|
+
- Fixed idle prompt-cache warming rebuilding expired caches when its timer or an extension decision is delayed.
|
|
36
|
+
- Improved crash diagnostics with hints identifying loaded extensions that appear in the stack trace.
|
|
37
|
+
- Fixed text files beginning with `GIF` being misclassified as images and omitted from `read` and CLI `@file` input ([#9755](https://github.com/earendil-works/pi/issues/9755)).
|
|
38
|
+
- Fixed malformed prompt template frontmatter being silently ignored instead of reported as a resource warning ([#9830](https://github.com/earendil-works/pi/pull/9830) by [@christianklotz](https://github.com/christianklotz)).
|
|
39
|
+
- Fixed inherited unknown OpenAI-compatible Chat Completions endpoints receiving strict tool schemas unless they explicitly advertise support ([#9816](https://github.com/earendil-works/pi/issues/9816)).
|
|
40
|
+
|
|
3
41
|
## [0.86.1] - 2026-09-20
|
|
4
42
|
|
|
5
43
|
### New Features
|
package/bin/README.md
CHANGED
|
@@ -282,6 +282,15 @@ Long sessions can exhaust context windows. Compaction summarizes older messages
|
|
|
282
282
|
|
|
283
283
|
Compaction is lossy. The full history remains in the JSONL file; use `/tree` to revisit. Customize compaction behavior via [extensions](#extensions). See [docs/compaction.md](docs/compaction.md) for internals.
|
|
284
284
|
|
|
285
|
+
Session model context is projected from append-only history. Extensions can append a `context_edit` to omit or replace an earlier message only for future model requests; raw history and usage remain unchanged:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const assistantId = sessionManager.appendMessage(partialAssistant);
|
|
289
|
+
sessionManager.appendContextEdit(assistantId, null); // Hidden from model context, retained in JSONL.
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
A retain-none compaction uses `appendCompaction(summary, null, tokensBefore)` to make the exact summary the new context root while preserving prior raw entries.
|
|
293
|
+
|
|
285
294
|
---
|
|
286
295
|
|
|
287
296
|
## Settings
|
|
@@ -383,6 +392,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
383
392
|
}
|
|
384
393
|
```
|
|
385
394
|
|
|
395
|
+
`turn_end` and `agent_before_settle` are actionable persistence boundaries. Handlers can append structural entries and request one continuation. Later handlers see earlier proposals:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
let replacedResponse = false;
|
|
399
|
+
pi.on("turn_end", (event) => {
|
|
400
|
+
if (replacedResponse || event.outcome !== "completed" || event.toolResults.length > 0) return;
|
|
401
|
+
replacedResponse = true;
|
|
402
|
+
return {
|
|
403
|
+
entries: [
|
|
404
|
+
...event.entries,
|
|
405
|
+
{ type: "context_edit", targetId: event.messageEntryId, replacement: null },
|
|
406
|
+
{
|
|
407
|
+
type: "custom_message",
|
|
408
|
+
customType: "replacement-instruction",
|
|
409
|
+
content: "Answer again using the persisted user request.",
|
|
410
|
+
display: false,
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
continue: true,
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Continuation is one-shot per boundary result, not per registered handler. It ensures one next provider request: tool-result, steering, or follow-up scheduling can satisfy that request without adding another one; otherwise Pi makes one context-only request. Guard handlers like the example above because an unconditional `continue: true` is evaluated again after the next response and can loop indefinitely. Error and aborted responses remain hard exits.
|
|
419
|
+
|
|
420
|
+
Use `agent_before_settle` for final actions after retries, compaction, and queued input are exhausted. See [docs/extensions.md](docs/extensions.md#agent_start--agent_end--agent_before_settle--agent_settled).
|
|
421
|
+
|
|
386
422
|
The default export can also be `async`. pi waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `pi.registerProvider()`.
|
|
387
423
|
|
|
388
424
|
**What's possible:**
|
package/bin/docs/compaction.md
CHANGED
|
@@ -34,14 +34,16 @@ contextTokens > contextWindow - reserveTokens
|
|
|
34
34
|
|
|
35
35
|
By default, `reserveTokens` is 16384 tokens (configurable in `~/.pi/agent/settings.json` or `<project-dir>/.pi/settings.json`). This leaves room for the LLM's response.
|
|
36
36
|
|
|
37
|
-
During a multi-turn agent run, Pi checks
|
|
37
|
+
During a multi-turn agent run, Pi checks the canonical projected context after tools finish and their results are appended, before starting the next assistant response. If the threshold is crossed, Pi compacts during `prepareNextTurn`, then performs the existing catch-up steering poll before `turn_start`. It skips this between-turn check when the completed tool batch terminates the run and no queued message requires another response. Pi also checks before a new user prompt and performs final-attempt overflow recovery after the low-level run ends.
|
|
38
|
+
|
|
39
|
+
A provider context-overflow error or an early final `stopReason: "length"` can select one compact-and-retry recovery attempt. Length responses with tool calls retain their synthetic failed tool results and follow the ordinary tool/queue scheduler rather than forcing the run to end.
|
|
38
40
|
|
|
39
41
|
You can also trigger manually with `/compact [instructions]`, where optional instructions focus the summary.
|
|
40
42
|
|
|
41
43
|
### How It Works
|
|
42
44
|
|
|
43
|
-
1. **Find cut point**: Walk backwards
|
|
44
|
-
2. **Extract messages**: Collect messages from the previous kept boundary (or session start) up to the cut point
|
|
45
|
+
1. **Find cut point**: Walk backwards through the finalized session projection, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.pi/agent/settings.json` or `<project-dir>/.pi/settings.json`) is reached
|
|
46
|
+
2. **Extract messages**: Collect projected messages from the previous kept boundary (or session start) up to the cut point
|
|
45
47
|
3. **Generate summary**: Call LLM to summarize with structured format, passing the previous summary as iterative context when present
|
|
46
48
|
4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
|
|
47
49
|
5. **Rebuilds context**: Session rebuilds the context for the next request, using summary + messages from `firstKeptEntryId` onwards
|
|
@@ -78,7 +80,22 @@ What the LLM sees:
|
|
|
78
80
|
prompt from cmp messages from firstKeptEntryId
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
On repeated compactions, the summarized span starts at the previous compaction's kept boundary (`firstKeptEntryId`), not at the compaction entry itself, falling back to the entry after the previous compaction if that kept entry cannot be found in the path. This preserves messages that survived the earlier compaction by including them in the next summarization pass as well. Pi also recalculates `tokensBefore` from the rebuilt session
|
|
83
|
+
On repeated compactions, the summarized span starts at the previous compaction's kept boundary (`firstKeptEntryId`), not at the compaction entry itself, falling back to the entry after the previous compaction if that kept entry cannot be found in the path. A retain-none compaction records its own ID as `firstKeptEntryId`; repeated compaction starts after that entry. This preserves messages that survived the earlier compaction by including them in the next summarization pass as well. Pi also recalculates `tokensBefore` from the rebuilt, context-edited session projection before writing the new `CompactionEntry`, so the token count reflects the actual pre-compaction context being replaced. Omitted raw entries remain stored but do not affect cut selection, summaries, checkpoints, or token estimates.
|
|
84
|
+
|
|
85
|
+
### Overflow and Length Recovery Ordering
|
|
86
|
+
|
|
87
|
+
Recovery preserves the existing lifecycle and queue order. The completed attempt remains visible to `turn_end` and `agent_end`; post-run recovery then repairs persisted model context before a fresh retry:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
persist final assistant response
|
|
91
|
+
→ extension/public turn_end
|
|
92
|
+
→ extension/public agent_end
|
|
93
|
+
→ append context_edit omissions for the selected attempt
|
|
94
|
+
→ for overflow/length: run session_before_compact and append compaction on success
|
|
95
|
+
→ start the retry as a fresh run
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If recovery compaction fails or is cancelled, Pi keeps the omission edits, appends no compaction, and schedules no internal retry. Existing queued work remains governed by ordinary steering and follow-up rules. `agent_before_settle` sees the repaired projection after recovery processing. Raw transcript history, exports, billing totals, and history-search extensions can still inspect the omitted attempt.
|
|
82
99
|
|
|
83
100
|
### Split Turns
|
|
84
101
|
|
|
@@ -118,6 +135,8 @@ Valid cut points are:
|
|
|
118
135
|
|
|
119
136
|
Never cut at tool results (they must stay with their tool call).
|
|
120
137
|
|
|
138
|
+
Preparation advances the kept boundary into a context-invisible suffix only when that suffix contains an omitted assistant attempt and no unomitted context-producing entries. Recovery `context_edit` omissions satisfy this rule; intrinsically context-invisible metadata may coexist with them. Metadata alone and newly appended custom messages do not move the cut. A replacement edit affecting the candidate input or summarized prefix also blocks advancement because the omitted assistant answered the pre-edit input; replacements of suffix entries that are ultimately omitted remain safe. This allows an over-budget recovered input to be summarized while retaining the edits that keep the abandoned attempt omitted, without making bookkeeping change whether new model input is preserved verbatim.
|
|
139
|
+
|
|
121
140
|
### CompactionEntry Structure
|
|
122
141
|
|
|
123
142
|
Defined in [`session-manager.ts`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/session-manager.ts):
|
package/bin/docs/extensions.md
CHANGED
|
@@ -294,7 +294,8 @@ user sends prompt ────────────────────
|
|
|
294
294
|
│ ┌─── turn (repeats while LLM calls tools) ───┐ │
|
|
295
295
|
│ │ │ │
|
|
296
296
|
│ ├─► turn_start │ │
|
|
297
|
-
│ ├─► context (can modify messages)
|
|
297
|
+
│ ├─► context (can modify conversation messages) │
|
|
298
|
+
│ ├─► context_with_system (can modify the full transcript)
|
|
298
299
|
│ ├─► before_provider_headers (can mutate headers) |
|
|
299
300
|
│ ├─► before_provider_request (can inspect or replace payload)
|
|
300
301
|
│ ├─► after_provider_response (status + headers, before stream consume)
|
|
@@ -307,9 +308,13 @@ user sends prompt ────────────────────
|
|
|
307
308
|
│ │ └─► tool_execution_end │ │
|
|
308
309
|
│ │ │ │
|
|
309
310
|
│ └─► turn_end │ │
|
|
311
|
+
│ └─► threshold compaction before a naturally required next turn
|
|
310
312
|
│ │
|
|
311
313
|
├─► agent_end │
|
|
312
|
-
|
|
314
|
+
├─► retry backoff or final-attempt recovery (when selected)
|
|
315
|
+
│ └─► fresh agent_start on successful recovery │
|
|
316
|
+
├─► agent_before_settle (can append entries and continue)│
|
|
317
|
+
└─► agent_settled (final, notification only) │
|
|
313
318
|
│
|
|
314
319
|
user sends another prompt ◄────────────────────────────────┘
|
|
315
320
|
|
|
@@ -567,9 +572,9 @@ The `systemPromptOptions` field gives extensions access to the same structured d
|
|
|
567
572
|
|
|
568
573
|
Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.
|
|
569
574
|
|
|
570
|
-
#### agent_start / agent_end / agent_settled
|
|
575
|
+
#### agent_start / agent_end / agent_before_settle / agent_settled
|
|
571
576
|
|
|
572
|
-
`agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Pi may still auto-retry, auto-compact and retry, or continue with queued follow-up messages.
|
|
577
|
+
`agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Pi may still auto-retry, auto-compact and retry, or continue with queued follow-up messages. `agent_before_settle` is the final actionable boundary: it can append session entries and request one continuation. `agent_settled` is final and notification-only; use it for status integrations that need to know Pi will not continue running automatically.
|
|
573
578
|
|
|
574
579
|
```typescript
|
|
575
580
|
pi.on("agent_start", async (_event, ctx) => {});
|
|
@@ -578,11 +583,28 @@ pi.on("agent_end", async (event, ctx) => {
|
|
|
578
583
|
// event.messages - messages from this low-level run
|
|
579
584
|
});
|
|
580
585
|
|
|
586
|
+
let addedReviewReminder = false;
|
|
587
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
588
|
+
if (addedReviewReminder) return;
|
|
589
|
+
addedReviewReminder = true;
|
|
590
|
+
return {
|
|
591
|
+
entries: [...event.entries, {
|
|
592
|
+
type: "custom_message",
|
|
593
|
+
customType: "review-reminder",
|
|
594
|
+
content: "Review the final diff before replying.",
|
|
595
|
+
display: false,
|
|
596
|
+
}],
|
|
597
|
+
continue: true,
|
|
598
|
+
};
|
|
599
|
+
});
|
|
600
|
+
|
|
581
601
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
582
|
-
// ctx.isIdle() is true here
|
|
602
|
+
// ctx.isIdle() is true; runs requested here start after all settled handlers finish.
|
|
583
603
|
});
|
|
584
604
|
```
|
|
585
605
|
|
|
606
|
+
If the run is aborted while `agent_before_settle` handlers are running, valid returned entries are still committed, but requested continuation is suppressed. Work requested from `agent_settled` is deferred until every settled handler completes, so notification dispatch is non-reentrant.
|
|
607
|
+
|
|
586
608
|
#### ui_prompt_start / ui_prompt_end
|
|
587
609
|
|
|
588
610
|
Notification-only lifecycle events for blocking user-facing extension UI prompts. They fire around `ctx.ui.select()`, `ctx.ui.confirm()`, `ctx.ui.input()`, `ctx.ui.editor()`, and `ctx.ui.custom()` so host/status integrations can report "waiting for user" instead of just "running".
|
|
@@ -610,11 +632,34 @@ pi.on("turn_start", async (event, ctx) => {
|
|
|
610
632
|
// event.turnIndex, event.timestamp
|
|
611
633
|
});
|
|
612
634
|
|
|
635
|
+
let replacedResponse = false;
|
|
613
636
|
pi.on("turn_end", async (event, ctx) => {
|
|
614
637
|
// event.turnIndex, event.message, event.toolResults
|
|
638
|
+
// event.entries contains the structural entries proposed so far.
|
|
639
|
+
if (replacedResponse || event.outcome !== "completed" || event.toolResults.length > 0) return;
|
|
640
|
+
replacedResponse = true;
|
|
641
|
+
return {
|
|
642
|
+
entries: [
|
|
643
|
+
...event.entries,
|
|
644
|
+
{ type: "context_edit", targetId: event.messageEntryId, replacement: null },
|
|
645
|
+
{
|
|
646
|
+
type: "custom_message",
|
|
647
|
+
customType: "replacement-instruction",
|
|
648
|
+
content: "Answer again using the persisted user request.",
|
|
649
|
+
display: false,
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
continue: true,
|
|
653
|
+
};
|
|
615
654
|
});
|
|
616
655
|
```
|
|
617
656
|
|
|
657
|
+
`turn_end` runs after the assistant and tool-result messages have been persisted and before the low-level `turn_end` event. Retry backoff and final-attempt recovery still happen after `agent_end`, preserving their existing lifecycle and queue ordering; `agent_before_settle` sees the repaired projection after that work completes. Boundary handlers run in extension load and registration order. Each handler sees prior proposals in `event.entries` and sees `event.context` rebuilt from them. Returning `entries` or `continue` replaces only that field; omitted fields preserve the current proposal. Allowed draft entry types are `custom`, `custom_message`, `context_edit`, and `compaction`. The complete proposal is validated before it is appended in list order after all handlers finish; a handler error is reported and later handlers still run. Validation prevents partially applied semantic errors, but persistence is not transactional.
|
|
658
|
+
|
|
659
|
+
`continue: true` ensures one next provider request for that boundary invocation. If tool results, steering, or a follow-up already cause that request, they satisfy the decision and no additional request is made; otherwise Pi makes one context-only request. Error and aborted responses remain hard exits. `continue: false` never suppresses natural work. Guard continuation conditions: an unconditional `continue: true` is evaluated again after the next response and can create an endless loop. A `custom_message` draft contributes a user-role model message but is extension-authored: it does not run human input hooks, slash commands, skills, or prompt templates.
|
|
660
|
+
|
|
661
|
+
Host integrations that construct `TurnEndEvent` values must now provide `messageEntryId`, `toolResultEntryIds`, `outcome`, `entries`, `continue`, and `context`. `ExtensionEvent` exhaustive switches must also handle `agent_before_settle`. `ExtensionRunner.emit()` excludes actionable turn boundaries; dispatch `turn_end` and `agent_before_settle` through `emitBoundary(baseEvent, buildContext)` so handlers receive chained previews. Other dedicated runner methods still return results for events such as `session_before_*`.
|
|
662
|
+
|
|
618
663
|
#### message_start / message_update / message_end
|
|
619
664
|
|
|
620
665
|
Fired for message lifecycle updates.
|
|
@@ -681,12 +726,31 @@ Fired before each LLM call. Modify messages non-destructively. See [Session Form
|
|
|
681
726
|
|
|
682
727
|
```typescript
|
|
683
728
|
pi.on("context", async (event, ctx) => {
|
|
684
|
-
// event.messages - deep copy, safe to modify
|
|
729
|
+
// event.messages - deep copy without system messages, safe to modify
|
|
685
730
|
const filtered = event.messages.filter(m => !shouldPrune(m));
|
|
686
731
|
return { messages: filtered };
|
|
687
732
|
});
|
|
688
733
|
```
|
|
689
734
|
|
|
735
|
+
`event.messages` holds the conversation without system messages. The prompt and tool declarations belong to Pi and are not part of this hook: when the handler returns a changed list, Pi replays the current prompt sections and tool declarations into one leading system message ahead of the returned messages. Filtering, windowing, or slicing from a compaction summary therefore cannot drop the prompt or the tools. An unchanged list keeps mid-conversation system messages in place, so models that accept them retain their cached prefix. System messages a handler adds are kept after Pi's head. To change the prompt or the tool set durably, use [`before_agent_start`](#before_agent_start) or `pi.setActiveTools()`; to edit system messages for one request, use [`context_with_system`](#context_with_system).
|
|
736
|
+
|
|
737
|
+
#### context_with_system
|
|
738
|
+
|
|
739
|
+
Fired before each LLM call, after every `context` handler has run and Pi has restored the prompt and tool state. `event.messages` is the full transcript, including the leading system message and any mid-conversation prompt or tool patches (see [Session Format](session-format.md#sessionmessageentry)). The returned messages are sent as they are: this hook owns the prompt and tool declarations for the request.
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
import { getCurrentSystemMessage } from "@earendil-works/pi-ai";
|
|
743
|
+
|
|
744
|
+
pi.on("context_with_system", async (event, ctx) => {
|
|
745
|
+
const cut = findCutIndex(event.messages);
|
|
746
|
+
// Fold the dropped prefix so its prompt and tool state survives as the new head.
|
|
747
|
+
const head = getCurrentSystemMessage(event.messages.slice(0, cut));
|
|
748
|
+
return { messages: head ? [head, ...event.messages.slice(cut)] : event.messages.slice(cut) };
|
|
749
|
+
});
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Rules: keep a system message at index 0 (providers read the prompt and initial tool declarations there; Pi reports an error if a handler drops it). Removing a system message removes the tool declarations and section patches it carries. Check your output with `getCurrentSystemPrompt()` and `getCurrentTools()` from `@earendil-works/pi-ai`. Handlers run in extension load order; a `systemPrompt` forced from `before_agent_start` is still projected onto the request afterwards.
|
|
753
|
+
|
|
690
754
|
#### before_provider_headers
|
|
691
755
|
|
|
692
756
|
Fired after the outgoing HTTP headers are assembled. Use it to add, override, or remove request headers.
|
package/bin/docs/models.md
CHANGED
|
@@ -205,6 +205,7 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
|
|
|
205
205
|
| `reasoning` | No | `false` | Supports extended thinking |
|
|
206
206
|
| `thinkingLevelMap` | No | omitted | Maps pi thinking levels to provider values and marks unsupported levels (see below) |
|
|
207
207
|
| `input` | No | `["text"]` | Input types: `["text"]` or `["text", "image"]` |
|
|
208
|
+
| `inputLimits` | No | omitted | Request limits and image preprocessing for this model (see below) |
|
|
208
209
|
| `contextWindow` | No | `128000` | Context window size in tokens |
|
|
209
210
|
| `maxTokens` | No | `16384` | Maximum output tokens |
|
|
210
211
|
| `samplingParams` | No | omitted | Sampling parameters merged verbatim into every request body (see below) |
|
|
@@ -238,6 +239,33 @@ Current behavior:
|
|
|
238
239
|
- `/model`, `--list-models`, and the interactive footer display entries by model `id`.
|
|
239
240
|
- The configured `name` is used for model matching and secondary model detail text. It does not replace the footer/status-bar model id.
|
|
240
241
|
|
|
242
|
+
### Image Input Limits
|
|
243
|
+
|
|
244
|
+
Use `inputLimits.images.resize` to configure how new images are encoded before they enter conversation history:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"id": "vision-model",
|
|
249
|
+
"input": ["text", "image"],
|
|
250
|
+
"inputLimits": {
|
|
251
|
+
"images": {
|
|
252
|
+
"resize": {
|
|
253
|
+
"maxWidth": 1568,
|
|
254
|
+
"maxHeight": 1568,
|
|
255
|
+
"maxBytes": 524288,
|
|
256
|
+
"jpegQuality": 75
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`maxBytes` is the maximum base64-encoded payload size. Omitted resize fields use pi's conservative defaults: 2000×2000, 4.5 MiB encoded, and JPEG quality 80. Built-in vision models carry that profile explicitly so unknown gateways never receive larger images than before.
|
|
264
|
+
|
|
265
|
+
Pi applies the selected model's resize profile to `@file` attachments, the `read` tool, and images returned by tools. Images are encoded once before they enter history; changing models does not rewrite historical images or invalidate the cached conversation prefix. The `images.autoResize` setting can disable resizing globally.
|
|
266
|
+
|
|
267
|
+
The catalog can also record `inputLimits.maxRequestBytes`, `images.maxPerMessage`, and `images.maxPerRequest`. These fields describe hard provider limits; this initial implementation does not yet rewrite or reject conversation history based on them.
|
|
268
|
+
|
|
241
269
|
### Prompt Cache Lifetimes
|
|
242
270
|
|
|
243
271
|
`promptCache` states how long the provider keeps a prompt cache entry alive for each retention tier pi can request (`short` is the default tier; `long` is used when `PI_CACHE_RETENTION=long`). Values are seconds and are estimates: providers publish ranges, so pick the conservative end.
|
|
@@ -374,7 +402,7 @@ Use `modelOverrides` to customize built-in models and matching extension-registe
|
|
|
374
402
|
}
|
|
375
403
|
```
|
|
376
404
|
|
|
377
|
-
`modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `promptCache` (merged per tier), `contextWindow`, `maxTokens`, `samplingParams` (merged per key), `headers`, `compat`.
|
|
405
|
+
`modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `inputLimits` (deep-merged), `cost` (partial), `promptCache` (merged per tier), `contextWindow`, `maxTokens`, `samplingParams` (merged per key), `headers`, `compat`.
|
|
378
406
|
|
|
379
407
|
Use a `promptCache` override to enable cache warming through a proxy whose backing cache you know, for example OpenRouter routed to Anthropic:
|
|
380
408
|
|
package/bin/docs/sdk.md
CHANGED
|
@@ -91,6 +91,8 @@ interface AgentSession {
|
|
|
91
91
|
|
|
92
92
|
// State access
|
|
93
93
|
agent: Agent;
|
|
94
|
+
sessionManager: SessionManager;
|
|
95
|
+
refreshContext(): void;
|
|
94
96
|
model: Model | undefined;
|
|
95
97
|
thinkingLevel: ThinkingLevel;
|
|
96
98
|
messages: AgentMessage[];
|
|
@@ -251,8 +253,8 @@ const state = session.agent.state;
|
|
|
251
253
|
// state.streamingMessage?: AgentMessage - current partial assistant message
|
|
252
254
|
// state.errorMessage?: string - latest assistant error
|
|
253
255
|
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
+
// Model-visible messages are projected from session.sessionManager.
|
|
257
|
+
// agent.state.messages is a refreshed inspection cache; do not assign it for restoration.
|
|
256
258
|
|
|
257
259
|
// Replace tools
|
|
258
260
|
session.agent.state.tools = tools; // copies the top-level array
|
|
@@ -261,6 +263,15 @@ session.agent.state.tools = tools; // copies the top-level array
|
|
|
261
263
|
await session.agent.waitForIdle();
|
|
262
264
|
```
|
|
263
265
|
|
|
266
|
+
Provider requests use `session.sessionManager` as the canonical finalized context. Assigning `session.agent.state.messages` does not replace persisted context and may be overwritten at the next request boundary. Restore externally stored history when constructing the session instead:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const restoredManager = SessionManager.inMemory(process.cwd(), { id: sessionId }, entries);
|
|
270
|
+
const { session } = await createAgentSession({ sessionManager: restoredManager });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For an existing session, use `session.navigateTree(entryId)` to move its active branch. Use `session.sessionManager.appendMessage(...)` plus `session.refreshContext()` only when intentionally appending externally managed entries.
|
|
274
|
+
|
|
264
275
|
### Events
|
|
265
276
|
|
|
266
277
|
Subscribe to events to receive streaming output and lifecycle notifications.
|
|
@@ -274,7 +274,7 @@ Created when context is compacted. Stores a summary of earlier messages and a co
|
|
|
274
274
|
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000,"systemMessage":{"role":"system","content":"You are a coding assistant.","toolsAdded":[],"timestamp":1733235000000}}
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
`firstKeptEntryId` is required. It identifies the first entry retained from before the compaction entry. When rebuilding context, Pi replaces older summarized entries with the compaction summary and keeps the range beginning at this entry.
|
|
277
|
+
`firstKeptEntryId` is required. It identifies the first entry retained from before the compaction entry. When rebuilding context, Pi replaces older summarized entries with the compaction summary and keeps the range beginning at this entry. A retain-none compaction stores its own ID in this field, so no preceding entries are retained.
|
|
278
278
|
|
|
279
279
|
Optional fields:
|
|
280
280
|
- `systemMessage`: The replayed prompt sections and tool declarations at the compaction boundary; it becomes the leading system message of the compacted context, and system messages among the kept entries are dropped in its favor. It is absent on older session entries.
|
|
@@ -282,6 +282,16 @@ Optional fields:
|
|
|
282
282
|
- `details`: Implementation-specific data (e.g., `{ readFiles: string[], modifiedFiles: string[] }` for default, or custom data for extensions)
|
|
283
283
|
- `fromHook`: `true` if generated by an extension, `false`/`undefined` if pi-generated (legacy field name)
|
|
284
284
|
|
|
285
|
+
### ContextEditEntry
|
|
286
|
+
|
|
287
|
+
Append-only edit of one earlier context-producing entry. It changes only future model context; the target entry and its metadata remain unchanged in raw history, UI, exports, and session accounting.
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{"type":"context_edit","id":"g6h7i8j9","parentId":"f6g7h8i9","timestamp":"2024-12-03T14:11:00.000Z","targetId":"c3d4e5f6","replacement":null}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Targets may be user, assistant, tool-result, or custom-message entries. `replacement: null` omits the target from model context. A non-null `replacement` replaces only the target message content. String replacements for assistant and tool-result entries are normalized to one text block because those roles require content arrays. If several edits target the same entry, the latest edit on the active branch wins. Edits are branch-relative: navigating to a point before the edit reveals the target's original contribution again.
|
|
294
|
+
|
|
285
295
|
### BranchSummaryEntry
|
|
286
296
|
|
|
287
297
|
Created when switching branches via `/tree` with an LLM generated summary of the left branch up to the common ancestor. Captures context from the abandoned path.
|
|
@@ -366,7 +376,9 @@ Entries normally form one tree, but navigation APIs can create multiple roots:
|
|
|
366
376
|
- Includes entries after the compaction entry
|
|
367
377
|
3. Preserves non-message entries in the selected range so interactive mode can render them
|
|
368
378
|
|
|
369
|
-
`
|
|
379
|
+
`buildSessionProjection()` then applies the latest `context_edit` for each selected target. It returns the model-visible messages together with their source entries. Omitted targets produce no message; replacements retain the source entry's role and metadata while changing only content. The raw selected entries are not modified.
|
|
380
|
+
|
|
381
|
+
`buildSessionContext()` builds on that projection to produce the message list for the LLM:
|
|
370
382
|
|
|
371
383
|
1. Extracts current model and thinking level settings from the full path
|
|
372
384
|
2. Converts selected entries to messages:
|
|
@@ -374,6 +386,7 @@ Entries normally form one tree, but navigation APIs can create multiple roots:
|
|
|
374
386
|
- `compaction` -> complete system checkpoint followed by `compactionSummary`
|
|
375
387
|
- `branch_summary` -> `branchSummary`
|
|
376
388
|
- `custom_message` -> `CustomMessage`
|
|
389
|
+
- `context_edit` -> no context message of its own
|
|
377
390
|
- `usage` and `custom` -> no context message
|
|
378
391
|
|
|
379
392
|
The compaction summary replaces entries before `firstKeptEntryId`. Pre-compaction system messages are folded into the complete checkpoint rather than replayed from the retained range. Retained non-system entries and all entries after the compaction remain available to the LLM.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.87.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-gondolin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-gondolin",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.87.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@earendil-works/gondolin": "0.12.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-sandbox",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-sandbox",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.17.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "^0.0.26"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.87.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
|
@@ -16,8 +16,9 @@ export default defineFacet({
|
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
env.onActivate(() => {
|
|
19
|
-
workerActivations.
|
|
20
|
-
|
|
19
|
+
workerActivations.change(BACKGROUND_CONTEXT, (draft) => {
|
|
20
|
+
draft.count += 1;
|
|
21
|
+
});
|
|
21
22
|
});
|
|
22
23
|
},
|
|
23
24
|
});
|
|
@@ -357,6 +357,9 @@
|
|
|
357
357
|
case 'thinking_level_change':
|
|
358
358
|
parts.push('thinking', entry.thinkingLevel);
|
|
359
359
|
break;
|
|
360
|
+
case 'context_edit':
|
|
361
|
+
parts.push('context edit', entry.replacement === null ? 'omit' : 'replace', entry.targetId);
|
|
362
|
+
break;
|
|
360
363
|
}
|
|
361
364
|
|
|
362
365
|
return parts.join(' ').toLowerCase();
|
|
@@ -385,7 +388,7 @@
|
|
|
385
388
|
}
|
|
386
389
|
|
|
387
390
|
// Apply filter mode
|
|
388
|
-
const isSettingsEntry = ['label', 'custom', 'model_change', 'thinking_level_change'].includes(entry.type);
|
|
391
|
+
const isSettingsEntry = ['label', 'custom', 'context_edit', 'model_change', 'thinking_level_change'].includes(entry.type);
|
|
389
392
|
let passesFilter = true;
|
|
390
393
|
|
|
391
394
|
switch (filterMode) {
|
|
@@ -696,6 +699,8 @@
|
|
|
696
699
|
return labelHtml + `<span class="tree-muted">[model: ${escapeHtml(entry.modelId)}]</span>`;
|
|
697
700
|
case 'thinking_level_change':
|
|
698
701
|
return labelHtml + `<span class="tree-muted">[thinking: ${escapeHtml(entry.thinkingLevel)}]</span>`;
|
|
702
|
+
case 'context_edit':
|
|
703
|
+
return labelHtml + `<span class="tree-muted">[context ${entry.replacement === null ? 'omit' : 'replace'}: ${escapeHtml(entry.targetId)}]</span>`;
|
|
699
704
|
default:
|
|
700
705
|
return labelHtml + `<span class="tree-muted">[${escapeHtml(entry.type)}]</span>`;
|
|
701
706
|
}
|
package/bin/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-coding-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@earendil-works/chord": "^0.
|
|
53
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
54
|
-
"@earendil-works/pi-ai": "^0.
|
|
55
|
-
"@earendil-works/pi-tui": "^0.
|
|
52
|
+
"@earendil-works/chord": "^0.87.0",
|
|
53
|
+
"@earendil-works/pi-agent-core": "^0.87.0",
|
|
54
|
+
"@earendil-works/pi-ai": "^0.87.0",
|
|
55
|
+
"@earendil-works/pi-tui": "^0.87.0",
|
|
56
56
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
57
57
|
"chalk": "6.0.0",
|
|
58
58
|
"cross-spawn": "7.0.6",
|
|
@@ -73,9 +73,9 @@
|
|
|
73
73
|
"protobufjs": "7.6.6"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@earendil-works/pi-client": "^0.
|
|
77
|
-
"@earendil-works/pi-protocol": "^0.
|
|
78
|
-
"@earendil-works/pi-server": "^0.
|
|
76
|
+
"@earendil-works/pi-client": "^0.87.0",
|
|
77
|
+
"@earendil-works/pi-protocol": "^0.87.0",
|
|
78
|
+
"@earendil-works/pi-server": "^0.87.0",
|
|
79
79
|
"@types/cross-spawn": "6.0.6",
|
|
80
80
|
"@types/hosted-git-info": "3.0.5",
|
|
81
81
|
"@types/node": "22.19.19",
|
package/bin/pi.exe
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/bin/pi.exe
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 109871104 bytes
|
|
4
|
+
sha256: 50d68629570e8b7e9b7c2fc5df9522e3c0f135e693b597e5ad52ba3ec75910e4
|