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

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, 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, 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.

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, 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:

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 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 and to persisted session data in 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →