# How Tool Call Normalization Works Between Pi and Pi-Web: A Complete Technical Guide

> Understand tool call normalization in agegr/pi-web. Learn how Pi's JSON-L format converts to TypeScript ToolCallContent for the Pi-Web UI.

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

---

**Tool call normalization in the `agegr/pi-web` repository translates Pi's native JSON-L tool call format into the TypeScript `ToolCallContent` type expected by the Pi-Web UI, mapping fields like `id` to `toolCallId` and `arguments` to `input` via the `normalizeToolCalls` function in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts).**

The `agegr/pi-web` project serves as a web interface for Pi's conversational AI, but the two systems use different schemas to represent tool invocations. Understanding how tool call normalization works between Pi and Pi-Web is essential for developers extending the UI or debugging message flow issues, as it ensures seamless communication between Pi's storage layer and the React frontend.

## The Format Mismatch Between Pi Storage and Pi-Web UI

Pi persists chat history in a JSON-L format where tool calls appear as objects with `id`, `name`, and `arguments` properties. However, the Pi-Web frontend expects a standardized **ToolCallContent** interface defined in [`lib/pi-types.ts`](https://github.com/agegr/pi-web/blob/main/lib/pi-types.ts), which requires fields named `toolCallId`, `toolName`, and `input` to properly render tool invocations.

Without normalization, the UI would fail to recognize tool call data from persisted sessions or live streams, breaking the display of tool inputs and results. The normalization layer bridges this gap by transforming Pi's storage schema into the shape required by React components.

## The Normalization Engine in lib/normalize.ts

At the core of the conversion lies the **`normalizeToolCalls`** function located in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts). This utility inspects incoming `AgentMessage` objects and performs structural transformations when it detects tool-related content.

### Field Mapping Strategy

The normalization process performs a direct property translation to align Pi's format with Pi-Web's TypeScript types:

- `id` → `toolCallId`
- `name` → `toolName`
- `arguments` → `input`

This mapping ensures that tool call data from Pi's JSON-L files can be consumed directly by UI components expecting the `ToolCallContent` interface.

### Type Detection and Pass-Through Behavior

The function examines the `type` field of each message. When encountering `"toolCall"` or the legacy `"toolResult"` string, it applies the transformation. For all other message types—including plain text, assistant replies, or model change notifications—the function returns the original object unchanged, ensuring zero overhead for non-tool traffic.

## Integration Points in the Data Pipeline

To guarantee consistent data shapes regardless of source, Pi-Web applies normalization at two critical architectural boundaries.

### Session Loading via session-reader.ts

When restoring a conversation from disk, [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) iterates through the message history and passes each entry through `normalizeToolCalls`. This ensures that historical tool calls loaded from Pi's JSON-L files immediately conform to the frontend's type requirements before entering the React state.

### Real-Time Streaming in useAgentSession.ts

For live conversations, [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) processes Server-Sent Events (SSE) from the Pi SDK. As tool call messages arrive via the streaming socket, the hook normalizes them before appending to the local message array. This two-stage guard ensures both persisted history and real-time data maintain type consistency.

## Practical Implementation Examples

Working with normalized tool calls requires importing the utility and types from their respective modules.

Loading a session with automatic normalization:

```typescript
import { loadSession } from "@/lib/session-reader";

const session = await loadSession(sessionId);
// All tool call entries inside `session.messages` have already been normalized
// to ToolCallContent format with toolCallId, toolName, and input fields

```

Handling streaming events with manual normalization:

```typescript
import { normalizeToolCalls } from "@/lib/normalize";

socket.on("agent_event", (event) => {
  const msg = normalizeToolCalls(event.message);
  setMessages((prev) => [...prev, msg]);
});

```

Rendering the normalized type in a React component:

```typescript
import type { ToolCallContent } from "@/lib/pi-types";

function renderToolCall(call: ToolCallContent) {
  return (
    <div className="tool-call">
      <strong>{call.toolName}</strong>
      <pre>{JSON.stringify(call.input, null, 2)}</pre>
    </div>
  );
}

```

## Summary

- **Field Translation**: The `normalizeToolCalls` function in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) maps Pi's `id`, `name`, and `arguments` fields to Pi-Web's `toolCallId`, `toolName`, and `input` properties.
- **Dual-Stage Processing**: Normalization occurs both when loading historical sessions in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) and when processing live streams in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts).
- **Type Safety**: The conversion ensures all tool call data conforms to the `ToolCallContent` interface defined in [`lib/pi-types.ts`](https://github.com/agegr/pi-web/blob/main/lib/pi-types.ts), enabling type-safe React component rendering.
- **Non-Destructive**: Messages without tool call types pass through unchanged, preventing performance overhead on standard conversation text.

## Frequently Asked Questions

### What specific field names does Pi use compared to Pi-Web?

Pi stores tool calls using the fields `id` (UUID), `name` (tool identifier), and `arguments` (parameters object). Pi-Web expects `toolCallId`, `toolName`, and `input` respectively, requiring the normalization layer to rename these properties during data ingestion.

### Where is the normalizeToolCalls function implemented?

The core normalization logic resides in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) within the `agegr/pi-web` repository. This module exports the `normalizeToolCalls` function that handles the detection and transformation of tool call messages.

### Does the normalization process affect regular text messages?

No. The `normalizeToolCalls` function checks the message `type` field and only transforms objects with `"toolCall"` or `"toolResult"` types. All other messages, including plain text and assistant replies, return unchanged to avoid unnecessary processing overhead.

### Why normalize tool calls at two different stages in the application?

Pi-Web applies normalization both during session loading ([`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts)) and real-time streaming ([`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts)) to ensure data consistency regardless of source. This dual-stage approach guarantees that historical JSON-L files and live SSE events both conform to the `ToolCallContent` interface before reaching the UI components.