fix(coding-agent): add entry renderers for session entries

This commit is contained in:
Mario Zechner
2026-07-01 11:00:14 +02:00
parent 8c9436407c
commit ba10b60b51
18 changed files with 507 additions and 158 deletions
+41 -8
View File
@@ -944,9 +944,10 @@ Read-only access to session state. See [Session Format](session-format.md) for t
For `tool_call`, this state is synchronized through the current assistant message before handlers run. In parallel tool execution mode it is still not guaranteed to include sibling tool results from the same assistant message.
```typescript
ctx.sessionManager.getEntries() // All entries
ctx.sessionManager.getBranch() // Current branch
ctx.sessionManager.getLeafId() // Current leaf entry ID
ctx.sessionManager.getEntries() // All entries
ctx.sessionManager.getBranch() // Current branch
ctx.sessionManager.buildContextEntries() // Active branch entries with compaction applied
ctx.sessionManager.getLeafId() // Current leaf entry ID
```
### ctx.modelRegistry / ctx.model
@@ -1352,7 +1353,7 @@ pi.registerTool({
### pi.sendMessage(message, options?)
Inject a custom message into the session.
Inject a custom message into the session. Custom messages participate in LLM context. For durable TUI-only content that should not be sent to the LLM, use [`pi.appendEntry()`](#piappendentrycustomtype-data) with [`pi.registerEntryRenderer()`](#piregisterentryrenderercustomtype-renderer).
```typescript
pi.sendMessage({
@@ -1403,10 +1404,11 @@ See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a co
### pi.appendEntry(customType, data?)
Persist extension state (does NOT participate in LLM context).
Persist extension data. Custom entries do NOT participate in LLM context. In interactive mode, they can also render inside the chat transcript when paired with `pi.registerEntryRenderer()`.
```typescript
pi.appendEntry("my-state", { count: 42 });
pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
// Restore on reload
pi.on("session_start", async (_event, ctx) => {
@@ -1524,7 +1526,27 @@ mode and would not execute if sent via `prompt`.
### pi.registerMessageRenderer(customType, renderer)
Register a custom TUI renderer for messages with your `customType`. See [Custom UI](#custom-ui).
Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `pi.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
### pi.registerEntryRenderer(customType, renderer)
Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `pi.appendEntry()` and do not participate in LLM context.
```typescript
import { Box, Text } from "@earendil-works/pi-tui";
pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
const data = entry.data as { title: string; count: number };
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));
if (expanded) {
box.addChild(new Text(theme.fg("dim", JSON.stringify(data, null, 2))));
}
return box;
});
pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
```
### pi.registerShortcut(shortcut, options)
@@ -2528,9 +2550,9 @@ ctx.ui.setEditorComponent((tui, theme, keybindings) =>
See [tui.md](tui.md) Pattern 7 for a complete example with mode indicator.
### Message Rendering
### Message and Entry Rendering
Register a custom renderer for messages with your `customType`:
Register a custom renderer for messages with your `customType`. Use message renderers for content that should participate in LLM context:
```typescript
import { Text } from "@earendil-works/pi-tui";
@@ -2559,6 +2581,16 @@ pi.sendMessage({
});
```
For TUI-only content that should not be sent to the LLM, render custom entries instead:
```typescript
pi.registerEntryRenderer("my-card", (entry, options, theme) => {
return new Text(theme.fg("accent", JSON.stringify(entry.data)));
});
pi.appendEntry("my-card", { status: "done" });
```
### Theme Colors
All render functions receive a `theme` object. See [themes.md](themes.md) for creating custom themes and the full color palette.
@@ -2685,6 +2717,7 @@ All examples in [examples/extensions/](../examples/extensions/).
| `custom-provider-gitlab-duo/` | GitLab Duo integration | `registerProvider` with OAuth |
| **Messages & Communication** |||
| `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
| `entry-renderer.ts` | TUI-only custom entry rendering | `registerEntryRenderer`, `appendEntry` |
| `event-bus.ts` | Inter-extension events | `pi.events` |
| **Session Metadata** |||
| `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
+18 -8
View File
@@ -255,7 +255,7 @@ Extension state persistence. Does NOT participate in LLM context.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}
```
Use `customType` to identify your extension's entries on reload.
Use `customType` to identify your extension's entries on reload. Interactive mode can render custom entries via `pi.registerEntryRenderer(customType, renderer)`, but they still do not participate in LLM context.
### CustomMessageEntry
@@ -306,15 +306,24 @@ Entries form a tree:
## Context Building
`buildSessionContext()` walks from the current leaf to the root, producing the message list for the LLM:
`buildContextEntries()` walks from the current leaf to the root, producing the active entry list while honoring compaction:
1. Collects all entries on the path
2. Extracts current model and thinking level settings
3. If a `CompactionEntry` is on the path:
- Emits the summary first
- Then messages from `firstKeptEntryId` to compaction
- Then messages after compaction
4. Converts `BranchSummaryEntry` and `CustomMessageEntry` to appropriate message formats
2. If a `CompactionEntry` is on the path:
- Includes the compaction entry first
- Then entries from `firstKeptEntryId` to compaction
- Then entries after compaction
3. Preserves non-message entries in the selected range so interactive mode can render them
`buildSessionContext()` builds on that entry list to produce the message list for the LLM:
1. Extracts current model and thinking level settings from the full path
2. Converts selected entries to messages:
- `message` -> stored `AgentMessage`
- `compaction` -> `compactionSummary`
- `branch_summary` -> `branchSummary`
- `custom_message` -> `CustomMessage`
- `custom` -> no context message
## Parsing Example
@@ -401,6 +410,7 @@ Key methods for working with sessions programmatically.
- `branchWithSummary(entryId, summary, details?, fromHook?)` - Branch with context summary
### Instance Methods - Context & Info
- `buildContextEntries()` - Get active branch entries with compaction applied
- `buildSessionContext()` - Get messages, thinkingLevel, and model for LLM
- `getEntries()` - All entries (excluding header)
- `getHeader()` - Session header metadata