PAIPAI

useClientTool()

Register a browser tool on the current thread.

A client tool runs in the app shell: pick a file, read the current form, call a native API. The server still records the call; the browser performs it.

The model sees a client tool only while it is mounted. Unmount the page, and the next turn cannot call it.

Assistant.useClientTool("getSelection", {
  execute: async () => ({
    text: window.getSelection()?.toString() ?? "",
  }),
});

Register the same tool on the binding when every page should expose it:

export const Assistant = Pai.agent("main", {
  clientTools: {
    getSelection: {
      execute: async () => ({
        text: window.getSelection()?.toString() ?? "",
      }),
    },
  },
});

Import

Assistant.useClientTool("getSelection", { execute });

Signature

Server-declared client-routed tool (no execute on defineTool):

function useClientTool<TName extends ClientToolName<TContract>>(
  toolName: TName,
  options: ClientToolOptions<TContract, TName>,
): ClientToolRegistrationState;

Browser-only tool:

function useClientTool(
  definition: ClientDefinedToolOptions<TContract>,
): ClientToolRegistrationState;

Identity helper for a clientTools map value:

function clientTool(
  definition: ClientDefinedToolOptions<TContract>,
): ClientDefinedToolOptions<TContract>;
function ClientTool(props: ClientToolProps<TContract>): null;

Parameters

toolName: ClientToolName<TContract>

Registration name of a server-declared client-routed tool.

options: ClientToolOptions

execute: (ctx) => output | Promise<output>

Browser implementation.

render?: ToolRenderFn

Card for this client tool.

key?: string

definition: ClientDefinedToolOptions

Frontend-only tool.

name: string

description: string

inputSchema: z.ZodType

outputSchema: z.ZodType

execute: (ctx) => output | Promise<output>

render?: ToolRenderFn

dataSchemas?: Record<string, z.ZodType>

Returns

A ClientToolRegistrationState.

Mount/unmount bookkeeping. The tool is visible to the model only while mounted.

Package-root useClientTool / clientTool / ClientTool follow the nearest ThreadProvider. Bound Assistant.useClientTool keeps that agent's contract.

Examples

Server-declared tool

The server defineTool omits execute. The browser supplies it:

Assistant.useClientTool("getSelection", {
  execute: async () => ({
    text: window.getSelection()?.toString() ?? "",
  }),
});

Browser-only tool

Assistant.useClientTool({
  name: "getSelection",
  inputSchema: z.object({}),
  outputSchema: z.object({ text: z.string() }),
  execute: async () => ({
    text: window.getSelection()?.toString() ?? "",
  }),
});

Learn more

On this page