Genie React
Tool reference

App tools

Tools the app registers itself — domain actions and queries agents call like built-ins.

The JSON examples below show selected fields from the full response.

Apps can register their own tools with useGenieTool. They appear under the app group, named app_<name>, and are called like any built-in. Use them for what only the app can do: log in as a role, seed fixtures, inject API failures, jump a wizard step.

The tools on this page come from the demo apps (apps/vite-demo, apps/router-demo). Your app defines its own. The contract command is always the source of truth:

npx @genie-react/cli tools app

Register a tool

One declaration in a component. The zod schema drives validation, TypeScript types, and the advertised JSON Schema:

The examples import zod; add it to the app directly (pnpm add zod) — genie-react depends on it internally, but package managers do not expose transitive dependencies to your imports.

import { useGenieTool } from 'genie-react'
import { z } from 'zod'

useGenieTool({
  name: 'login_as',
  kind: 'action', // or 'query' — tells the agent read vs mutate
  description: 'Switches the session role and re-gates the UI.',
  input: z.object({ role: z.enum(['guest', 'member', 'admin']) }),
  handler: ({ role }) => switchRole(role),
})

The handler always sees the latest render's state. There is no dependency array. The name, schema, and description are fixed at mount — to change them at runtime, key the hook off enabled or the name.

Register several tools at once

One useGenieTools call registers a whole panel; inline handlers keep the same latest-render guarantee:

import { defineGenieTool, useGenieTools } from 'genie-react'
import { z } from 'zod'

useGenieTools([
  defineGenieTool({
    name: 'session',
    kind: 'query',
    description: 'Active role and what the task UI permits.',
    handler: () => ({ role }),
  }),
  defineGenieTool({
    name: 'login_as',
    kind: 'action',
    description: 'Switches the session role and re-gates the UI.',
    input: z.object({ role: z.enum(['guest', 'member', 'admin']) }),
    handler: ({ role: next }) => setRole(next),
  }),
])

apps/vite-demo/src/TaskPanel.tsx registers its five tools this way.

Register from a store or any module

Outside React, registerGenieTools works from a store, an API client, or module scope. It waits for the genie client if it has not started yet, and returns an unregister function:

import { defineGenieTool, registerGenieTools } from 'genie-react/client'
import { useCartStore } from './cart-store'

const unregister = registerGenieTools(
  defineGenieTool({
    name: 'cart_state',
    kind: 'query',
    description: 'Current cart line items and totals from the zustand store.',
    handler: () => useCartStore.getState().summary(),
  }),
  defineGenieTool({
    name: 'cart_clear',
    kind: 'action',
    destructive: true,
    description: 'Empties the cart store. No undo.',
    handler: () => useCartStore.getState().clear(),
  }),
)

Store handlers read state at call time (getState()), so nothing goes stale. Calling unregister() tombstones the tools instead of deleting them. For tools that should live as long as the app, pass them to the provider instead: <Genie tools={[...]} />.

Options:

  • kind: 'query' marks the tool read-only and safe to retry. kind: 'action' marks a mutation; actions also accept destructive: true and idempotent: true.
  • group: 'checkout' lists the tool under app.checkout for progressive discovery on large surfaces. tools app still covers every subgroup; tools app.checkout narrows to one. Omit it when the app exposes a handful of tools — flat app reads fine.
  • output is an optional zod schema, advertised and drift-checked in dev builds.
  • maxResultBytes raises the 128KB result cap when a large result is intentional.
  • enabled: false gates registration, e.g. behind a feature flag.

Throw GenieToolError for failures the agent should act on:

throw new GenieToolError('cart is empty', { code: 'CART_EMPTY', hint: 'seed with app_seed_tasks' })

The agent sees [CART_EMPTY] cart is empty — hint: seed with app_seed_tasks. Plain throws are wrapped with the tool name so an app bug is not blamed on Genie.

Discover and call

npx @genie-react/cli tools app
npx @genie-react/cli call app_login_as '{"role":"admin"}' --json

Output (selected fields):

{ "role": "admin", "canAdd": true, "canDelete": true }

Arguments are validated in the app by the tool's own schema: per-field errors, defaults applied, unknown keys rejected with the valid keys listed.

Badges in listings state intent before a call: (read-only) is free, (action) mutates, (destructive) has no undo.

Unavailable tools

A tool registered by an unmounted component stays listed instead of vanishing:

npx @genie-react/cli call app_checkout_state '{}'
# Call to app_checkout_state failed [tool-unavailable]: Tool "app_checkout_state" is currently
# unavailable — the code that registers it is not mounted (it was registered at URL path /checkout).
# Drive the app back to that UI, then retry.

The failure carries errorCode: "tool-unavailable" (distinct from a handler throwing), so scripts can branch on it. Recovery is usually one call — router_navigate to the named route, then retry. The listing marks these tools with ✗ unavailable; the detail view carries the reason.

Patterns

PatternDemo toolReplaces
Impersonationapp_login_asDriving a login form for every role
Fixturesapp_seed_tasks, app_reset_tasksHand-building test data per run
Fault injectionapp_chaosHoping an error state happens naturally
Step-jumpingapp_checkout_gotoFilling three wizard forms to test the last

apps/vite-demo/src/TaskPanel.tsx and apps/router-demo/src/routes/checkout.tsx show the full implementations.

Limits

  • Results are capped at 128KB per tool (override with maxResultBytes). Over the cap, the call fails with the size and the top-level keys.
  • Keep synchronous handler work well under a second; a blocked main thread reads as busy.
  • Tools exist only while the registering code runs. In production builds without a Genie client, registration is inert.

On this page