# How Pi Web Implements Extension UI Context Bindings (select, confirm, input, editor)

> Learn how Pi Web uses context bindings like select, confirm, input, and editor to manage UI interactions, routing JSON-RPC requests for seamless agent execution.

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

---

**Pi Web implements extension UI context bindings by routing JSON-RPC requests from agents through an RPC manager to a React dialog queue, rendering modality-specific interfaces for select, confirm, input, and editor interactions before returning user responses to resume agent execution.**

The `agegr/pi-web` repository provides a browser-based interface for AI agents that requires seamless user interaction without breaking execution flow. This article examines how the codebase implements **extension UI context bindings** to handle four core interaction patterns—select, confirm, input, and editor—allowing running skills to pause, collect user input, and resume operation.

## Extension UI Request Architecture

The system relies on a bidirectional JSON-RPC channel where agents emit **ExtensionUiRequest** messages that the frontend renders as modal dialogs. When a skill or plugin needs user input, it dispatches a request with a specific `method` field—`select`, `confirm`, `input`, or `editor`—containing UI-specific parameters like `title`, `message`, `options`, `placeholder`, or `prefill`.

### Request Creation in Agent Code

Agent-side code constructs these requests and awaits responses through an async wrapper. The payload structure varies by interaction type: select requests include an `options` array, while input and editor requests may specify `placeholder` text or `prefill` content.

```ts
// lib/skill-example.ts
import { sendExtensionUiRequest } from "./rpc-manager";

export async function askUserForName() {
  const request = {
    method: "input",
    title: "Enter your name",
    message: "What should we call you?",
    placeholder: "Your name…",
    prefill: "",
  };
  const { value } = await sendExtensionUiRequest(request);
  return value; // e.g. "Alice"
}

```

### RPC Routing and Session Management

Incoming RPC payloads arrive at [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), which forwards them to the active `AgentSession`. The session handler treats the payload as an `ExtensionUiRequest` and pushes it onto a **dialog queue** maintained in the application state. This queue drives the modal rendering logic in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx).

## Rendering Extension Dialogs in ChatWindow

The `ExtensionDialog` component in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) (lines 667–720) serves as the primary renderer for all four interaction types. It initializes local state from the request properties—using `prefill` content for editor mode or empty strings for other modes—and manages the `onRespond` callback that transmits user actions back to the agent.

```tsx
// components/ChatWindow.tsx (excerpt)
type ExtensionDialogRequest = Extract<
  ExtensionUiRequest,
  { method: "select" | "confirm" | "input" | "editor" }
>;

function ExtensionDialog({
  request,
  onRespond,
}: {
  request: ExtensionDialogRequest;
  onRespond: (
    req: ExtensionDialogRequest,
    resp:
      | { value: string }
      | { confirmed: boolean }
      | { cancelled: true }
  ) => void;
}) {
  const [value, setValue] = useState(
    request.method === "editor" ? request.prefill ?? "" : ""
  );

  const submitValue = () => {
    if (request.method === "confirm") {
      onRespond(request, { confirmed: true });
    } else {
      onRespond(request, { value });
    }
  };
  // ...
}

```

## UI Mode Implementations

Each binding type renders distinct controls but shares the same response pipeline through `onRespond`.

### Select Bindings

**Select** bindings render each option as a full-width button within a CSS grid layout. Clicking an option immediately calls `onRespond(request, { value: option })`, transmitting the selection back through the RPC channel without additional confirmation steps.

```tsx
{request.method === "select" && (
  <div style={{ display: "grid", gap: 8 }}>
    {request.options.map((option) => (
      <button
        key={option}
        onClick={() => onRespond(request, { value: option })}
        style={{
          width: "100%",
          padding: "9px 10px",
          borderRadius: 7,
          border: "1px solid var(--border)",
          background: "var(--bg-panel)",
          color: "var(--text)",
          cursor: "pointer",
          textAlign: "left",
          fontSize: 13,
        }}
      >
        {option}
      </button>
    ))}
  </div>
)}

```

### Confirm Dialogs

For `confirm` requests, the component displays the provided message alongside an **OK** button. When clicked, `onRespond` fires with `{ confirmed: true }`, allowing the agent to proceed with the approved action. The pattern appears in [`components/SessionSidebar.tsx`](https://github.com/agegr/pi-web/blob/main/components/SessionSidebar.tsx) (lines 660–678) for destructive actions like worktree removal.

### Input Fields

**Input** requests generate a single-line text field. The component monitors for **Enter** key presses to trigger `submitValue`, which returns `{ value: string }` to the awaiting agent. This provides a lightweight mechanism for collecting short text responses like filenames or search queries.

### Multi-line Editors

The `editor` method creates a `<textarea>` for multi-line content entry. Unlike single-line input, submission requires **Ctrl+Enter** (or **Cmd+Enter** on macOS) to prevent accidental newlines. The component initializes the text area with `request.prefill` content when available, making it ideal for editing code blocks or configuration files.

## Response Handling and Promise Resolution

Once `onRespond` executes, the callback traverses back through the RPC manager to resolve the original agent's Promise. This unblocks the skill's execution thread, passing the user-supplied data as the return value of the initial `sendExtensionUiRequest` call. The entire flow maintains type safety through TypeScript definitions that shape requests as `ExtensionUiRequest` and responses as discriminated unions of `{ value }`, `{ confirmed }`, or `{ cancelled }` objects.

## Summary

- **Extension UI context bindings** in Pi Web use JSON-RPC to bridge agent execution and React frontend components.
- The [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) file handles request routing and Promise resolution between agents and the UI.
- [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) implements the `ExtensionDialog` component that renders four distinct interaction modes: select, confirm, input, and editor.
- Each method returns a specifically shaped response object—`{ value }` for data entry, `{ confirmed }` for boolean approval, or `{ cancelled }` for dismissals.
- User input triggers `onRespond` callbacks that resolve pending agent Promises, allowing skills to resume with collected data.

## Frequently Asked Questions

### What file handles the routing of extension UI requests in Pi Web?

The [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) module receives JSON-RPC payloads from agents and forwards them to the active `AgentSession`. It maintains the Promise registry that connects user interactions back to the awaiting agent code.

### How does Pi Web distinguish between single-line input and multi-line editor bindings?

The system checks the `method` field of the `ExtensionUiRequest`. When `method` equals `"input"`, [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) renders a single-line `<input>` element that submits on **Enter**. When `method` equals `"editor"`, it renders a `<textarea>` that requires **Ctrl+Enter** (or **Cmd+Enter**) to submit, preventing accidental line breaks during text entry.

### What response format does a select binding return to the agent?

Select bindings return an object shaped as `{ value: string }` where the `value` property contains the exact string of the selected option. The agent awaits this response through the `sendExtensionUiRequest` Promise, which resolves immediately upon user selection.

### Can agents prefill content in editor bindings?

Yes. Editor requests support an optional `prefill` field in the request payload. The `ExtensionDialog` component initializes the textarea state with this value, allowing agents to present existing content for user editing rather than starting with an empty field.