# How Pi Web Normalizes Tool-Call Field Names Between the Pi SDK and UI Types

> Discover how Pi Web normalizes tool-call field names. Learn how id, name, and arguments are mapped to toolCallId, toolName, and input for seamless SDK and UI integration.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-07-25

---

**Pi Web bridges incompatible tool-call field names between the Pi SDK and UI components by passing assistant messages through a `normalizeToolCalls` function that maps legacy `id`, `name`, and `arguments` fields to the UI-standard `toolCallId`, `toolName`, and `input` properties.**

The Pi Web repository (agegr/pi-web) receives tool-call payloads from the Pi SDK that may contain either modern or legacy field names. To prevent UI components from handling multiple data shapes, the codebase centralizes field normalization into a single transformation layer. This approach guarantees that React components always receive a predictable `ToolCallContent` structure regardless of SDK version.

## The Core Normalization Logic in lib/normalize.ts

The transformation happens in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts), specifically within the `normalizeToolCalls` function. This utility inspects each content block in an assistant message and rebuilds the object using standardized field names.

According to the source code, the function checks that a block's `type` equals `"toolCall"` before applying transformations. It then constructs a new object with three normalized fields:

- **`toolCallId`**: Uses `toolCallId` if present; otherwise falls back to legacy `id`
- **`toolName`**: Prefers `toolName`, substitutes `name` if absent  
- **`input`**: Prioritizes `input` object; uses `arguments` if missing; defaults to empty object

The implementation resides in lines 7-18 of [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts), where the function maps these fields explicitly to match the `ToolCallContent` type definition.

## Role-Based Filtering and Type Safety

The `normalizeToolCalls` function specifically targets assistant messages. The source code verifies that `role === "assistant"` before processing the content array, as implemented in lines 21-25 of [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts).

This role-gating is critical because:

- User messages and tool-result entries follow different schemas
- Only assistant-generated tool calls require field name translation
- Other message types pass through untouched to avoid unintended mutations

## Where Normalization is Applied in the Pi Web Codebase

Pi Web invokes `normalizeToolCalls` at two key integration points to ensure data consistency.

**Streaming Events in ChatWindow**

The `ChatWindow.handleAgentEvent` method consumes normalized content directly. By receiving standardized `toolCallId`, `toolName`, and `input` fields, the Tool Call UI component renders without defensive checks for legacy property names.

**Session File Loading**

In [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts), the normalization function processes persisted session data when loading `.jsonl` files. This ensures that stored conversations using older SDK field formats display correctly in the UI after deserialization.

## Practical Code Example

The following TypeScript example demonstrates how raw SDK payloads transform into UI-compatible structures:

```typescript
import { normalizeToolCalls } from "./lib/normalize";
import type { AssistantMessage } from "./lib/types";

/* Raw payload using legacy SDK field names */
const rawMessage: AssistantMessage = {
  role: "assistant",
  content: [
    {
      type: "toolCall",
      id: "tc_123",               // legacy field
      name: "search",            // legacy field  
      arguments: { query: "pi web" } // legacy field
    }
  ],
  model: "gpt-4",
  provider: "openai"
};

/* Normalization transforms fields to UI standard */
const normalized = normalizeToolCalls(rawMessage) as AssistantMessage;

/* Result matches ToolCallContent interface */
console.log(normalized.content[0]);
/*
{
  type: "toolCall",
  toolCallId: "tc_123",
  toolName: "search", 
  input: { query: "pi web" }
}
*/

```

When processing payloads that already contain the modern field names (`toolCallId`, `toolName`, `input`), the function passes these values through unchanged, making the normalization layer idempotent and safe to apply universally.

## Summary

- Pi Web centralizes tool-call field normalization in [`lib/normalize.ts`](https://github.com/agegr/pi-web/blob/main/lib/normalize.ts) via the `normalizeToolCalls` function
- The utility maps legacy SDK fields (`id`, `name`, `arguments`) to UI-standard fields (`toolCallId`, `toolName`, `input`) using explicit fallback logic
- Normalization only occurs on assistant messages (`role === "assistant"`), leaving other message types unmodified
- The transformation is applied both to real-time SSE streams in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) and to persisted session data in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts)
- This approach ensures UI components consume a consistent `ToolCallContent` structure regardless of SDK version

## Frequently Asked Questions

### What triggers the toolCall field normalization in Pi Web?

The `normalizeToolCalls` function runs whenever the application processes assistant messages, either from real-time Server-Sent Events (SSE) in the chat interface or when loading historical session data from `.jsonl` files. It specifically checks for `role === "assistant"` before transforming content blocks of type `toolCall`.

### Does Pi Web modify the original message objects during normalization?

The normalization creates a new `ToolCallContent` object with mapped fields rather than mutating the original SDK payload in place. This immutable approach ensures that downstream components receive a clean, predictable structure while preserving the original data shape for debugging or logging purposes.

### Which field names does the Pi SDK use versus the Pi Web UI?

The Pi SDK may emit either modern fields (`toolCallId`, `toolName`, `input`) or legacy fields (`id`, `name`, `arguments`). The Pi Web UI exclusively expects the modern naming convention defined in `ToolCallContent`. The `normalizeToolCalls` function bridges this gap by checking for modern fields first, then falling back to legacy equivalents if absent.

### Is the normalization function safe to run on already-normalized data?

Yes, `normalizeToolCalls` is idempotent. If a tool-call block already contains the target field names (`toolCallId`, `toolName`, `input`), the function passes these values through unchanged. This safety allows the application to apply normalization universally without risking double-transformation or data corruption.