# How Pi Web Handles Session File (.jsonl) Entry Types: From Raw JSON to UI Messages

> Learn how Pi Web processes session file entries. Discover its method for converting raw JSON lines to UI messages, handling various entry types for normalized tool calls and custom messages.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-10

---

**Pi Web converts every line in a `.jsonl` session file into UI-ready messages using the `entryToUiMessage` function in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts), which discriminates on the `type` field to normalize tool calls, handle compaction summaries, and preserve custom plugin messages.**

Pi Web, the open-source chat interface in the `agegr/pi-web` repository, stores complete conversation histories as line-delimited JSON files. Understanding how Pi Web handles session file entry types is essential for developers building plugins or debugging conversation flows, as each entry undergoes specific transformations before reaching the UI.

## The Entry-to-UI Conversion Pipeline

At the heart of Pi Web's session handling is **[`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts)**. The **`entryToUiMessage`** function (lines 99-152) acts as a type discriminator, routing each **session entry** based on its `type` field through specialized handlers. This ensures every entry transforms into the correct `AgentMessage` shape expected by the frontend components.

### Message Entries (`type: "message"`)

The most common entry type contains actual chat content—whether from the user, assistant, tool results, or bash executions. According to the source code (lines 99-115), Pi Web first passes these through **`normalizeToolCalls`** from [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) (lines 21-32) to reconcile field name variations like `toolCallId` versus `id` and `input` versus `arguments`.

For assistant messages containing **thinking blocks**, the code checks if thinking is deferred. When `deferThinking` is false, thinking blocks are cleared but marked as deferred to prevent UI clutter. Additionally, large base-64 encoded images in tool results are stripped via **`omitToolResultBase64Images`** (lines 69-90) and replaced with placeholder text to maintain performance.

### Compaction Entries (`type: "compaction"`)

When the Pi SDK compacts older conversation history, it writes a `compaction` entry. Pi Web renders this as a custom message type (lines 119-129), displaying the summary text while exposing metadata including **tokens removed** and the **first kept entry ID**. This gives users visibility into automatic history truncation without breaking conversation flow.

### Branch Summary Entries (`type: "branch_summary"`)

If a user briefly explores an alternate conversation branch, the SDK emits a `branch_summary` entry. Pi Web converts this into a user-facing message (lines 131-138) that prefixes the summary with explanatory text, ensuring context switches remain transparent in the chat history.

### Custom Message Entries (`type: "custom_message"`)

Plugins and skills can inject arbitrary data via `custom_message` entries. Pi Web passes these through unchanged (lines 140-148), preserving the custom type, content, display flags, and additional details. This provides extensibility for third-party integrations without requiring core UI changes.

### Unknown and Metadata Types

Any entry lacking a recognized `type` value—including metadata entries or future SDK features—returns `null` from `entryToUiMessage` (lines 150-152). These entries are silently omitted from the message list, ensuring forward compatibility and preventing UI errors from unrecognized data.

## Supporting Processing Functions

Beyond type discrimination, Pi Web applies two critical normalizations to session file entries:

**Tool-Call Normalization**: The `normalizeToolCalls` function in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) (lines 7-18) standardizes tool call fields across different SDK versions. This ensures the UI receives consistent `toolCallId`, `toolName`, and `input` properties regardless of whether the source uses legacy `id`, `name`, or `arguments` fields.

**Base-64 Image Omission**: To prevent performance degradation from large payloads, `omitToolResultBase64Images` (lines 69-90) strips embedded base-64 images from tool results, substituting descriptive placeholder text that informs users of the omitted content.

## Practical Implementation Examples

```typescript
import { entryToUiMessage } from "./lib/session-reader";

// Processing a standard assistant message
const messageEntry = {
  type: "message",
  id: "msg_123",
  message: {
    role: "assistant",
    content: [{ type: "text", text: "Analysis complete." }]
  }
};

const uiMessage = entryToUiMessage(messageEntry, {
  deferThinking: false,
  deferToolResultImages: true
});
// Returns: { role: "assistant", content: [...], ... }

```

```typescript
// Handling history compaction
const compactionEntry = {
  type: "compaction",
  id: "compact_456",
  summary: "Removed 12 old messages to save context window",
  tokensBefore: 3400,
  firstKeptEntryId: "msg_123"
};

const compactionUi = entryToUiMessage(compactionEntry, {});
// Returns: { role: "custom", customType: "compaction", content: "Removed 12...", ... }

```

## Key Files in the Entry Pipeline

- **[`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts)**: Core conversion logic and `entryToUiMessage` implementation
- **[`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts)**: Tool-call field standardization via `normalizeToolCalls`
- **[`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts)**: TypeScript definitions for `SessionEntry` and `AgentMessage`
- **[`lib/session-path.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-path.ts)** and **[`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts)**: Session file resolution utilities

## Summary

- Pi Web uses the **`entryToUiMessage`** function in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) to transform every `.jsonl` line into UI-ready messages based on the `type` field.
- **Message** entries undergo tool-call normalization and optional thinking block/base-64 image processing.
- **Compaction** entries render as custom messages showing history truncation details.
- **Branch summary** entries appear as user messages explaining context switches.
- **Custom messages** pass through unchanged for plugin extensibility.
- Unknown types return `null` and are omitted from the UI, ensuring robustness against future SDK changes.

## Frequently Asked Questions

### What happens if a session file contains an unknown entry type?

Pi Web silently ignores unrecognized entry types. The `entryToUiMessage` function returns `null` for any `type` value not explicitly handled (lines 150-152 in [`session-reader.ts`](https://github.com/agegr/pi-web/blob/main/session-reader.ts)), and the UI filters these out. This design ensures backward compatibility when older Pi Web versions encounter entries from newer SDK releases.

### How does Pi Web handle large images in tool results?

Large base-64 encoded images are automatically stripped from tool result messages via `omitToolResultBase64Images` (lines 69-90). The function replaces image data with placeholder text describing the omitted content, preventing memory issues while maintaining user awareness of the tool's output.

### Why do tool calls need normalization in Pi Web?

Different versions of the Pi SDK use varying field names for tool invocations—some use `toolCallId` while others use `id`, and `input` versus `arguments`. The `normalizeToolCalls` function in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) (lines 7-18) reconciles these differences, providing the UI with a consistent `AgentMessage` shape regardless of the SDK version that created the session file.

### Can plugins create their own entry types in Pi Web?

Yes, through `custom_message` entries. Pi Web passes these entries through unchanged (lines 140-148 in [`session-reader.ts`](https://github.com/agegr/pi-web/blob/main/session-reader.ts)), preserving the custom type, content, and display flags. This allows plugins and skills to inject specialized data into the conversation history without modifying the core Pi Web codebase.