Skip to content

Build your own assistant behavior

Your application decides what the assistant can do. Start with the smallest change that solves your problem; most applications need only configuration and one or two custom actions.

This guide builds an order assistant in small steps. The live examples use the real assistant UI and action execution, with a scripted interpreter running in your browser. They recognize the listed requests and make no model or backend calls. Your production interpreter can understand other wording.

You want to…Start with
Change the suggested requests or chat presentationConfiguration
Open your own details panel or call an application featureA custom action
Offer different tools for the current user or workspaceDynamic actions
Filter and sort rows on your serverA remote executor
Coordinate tool policy, plugin state and cleanup togetherAn optional adapter

1. Start with the behavior you already have

Section titled “1. Start with the behavior you already have”

Complete the quick start first. It installs AiAssistantPlugin, configures your columns and connects an interpret function. Interpretation decides what a request means; execution carries out a validated operation.

Keep a base configuration so each example can extend it:

import {
createAiViewHttpInterpreter,
translateAiText,
type AiAssistantConfig,
type AiAssistantTranslations,
} from '@revolist/revogrid-pro';
const translations: AiAssistantTranslations = {};
const t = (text: string, values?: Record<string, string | number>) =>
translateAiText(text, translations, values);
const baseConfig: AiAssistantConfig = {
interpret: createAiViewHttpInterpreter('/api/ai/interpret'),
translations,
ui: {
mode: 'chat',
suggestions: [t('Only Europe'), t('Largest amounts first')],
maxVisibleSuggestions: 2,
},
};
grid.aiAssistant = baseConfig;

grid is your existing grid element. /api/ai/interpret is a route implemented by your application; see connecting your API client if you already have one. No adapter or report plugin is needed for ordinary filtering and sorting.

Try it: choose Only Europe, then Sort largest amount first. This existing grid example also offers a read and a previewed edit under More.

The demo loads when visible.

Reset demo restores the original sample data and clears its conversation. To use your own prompts, follow the API-client setup guide.

Suggestions are submitted text, not new capabilities. Adding “Open an order” to the suggestions alone does not create an order-opening operation.

Suppose your application already has openOrderDetails(orderId), which opens its own panel. Register a named action that calls it. Your application keeps ownership of the panel and its data.

import { actionInput, defineAiAction } from '@revolist/revogrid-pro';
const openOrder = defineAiAction({
name: 'orders.open',
description: 'Open an order’s details using its explicit order ID.',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', minLength: 1, maxLength: 64 },
},
required: ['orderId'],
additionalProperties: false,
},
parse(input) {
const { orderId } = actionInput(input, ['orderId']);
if (typeof orderId !== 'string' || !orderId.trim() || orderId.length > 64) {
throw new Error(t('Enter a valid order ID.'));
}
return { orderId: orderId.trim() };
},
run({ orderId }) {
openOrderDetails(orderId); // Your application function.
return { message: t('Opened order {id}.', { id: orderId }) };
},
});
grid.aiAssistant = {
...baseConfig,
actions: [openOrder],
ui: {
...baseConfig.ui,
suggestions: [t('Open ORD-0001'), t('Open ORD-0002')],
},
};

The schema describes the input the interpreter may request. parse checks the value at runtime. run calls your application and returns the message shown in chat. Keep action names unique; names such as orders.open are identifiers and stay unchanged across languages.

For “Open ORD-0001”, your interpreter returns:

{
"kind": "action",
"name": "orders.open",
"input": { "orderId": "ORD-0001" }
}

Registering an action tells the interpreter which operation is available; it does not write your backend or invent the details panel. Make sure your backend accepts your approved action descriptors. See the response contract.

Try it: open the first order, then the second. The panel below the grid belongs to the example application. The action opens details without editing any rows.

Try Custom order details

Choose a suggested request below. This scripted demo runs entirely in your browser, with no backend, model key or model calls. It recognizes only the suggested requests.

Try it: Choose Open ORD-0001, then Open ORD-0002. The application-owned details below change; the grid rows stay the same.

Application order detailsChoose an order to see its details here.
The demo loads when visible.

Reset demo restores the original sample data and clears its conversation. To use your own prompts, follow the API-client setup guide.

The live example uses createOrderDetailsAction(openDetails, translations), a reusable version of this action. Its callback checks that the order exists before displaying it. The complete source is available below.

For row changes, prefer the built-in previewed data actions. For a custom service write, use a prepared action, which provides Apply/Cancel before committing.

Use actionsForRequest when the available tools depend on current application state. It runs at the start of each request without clearing the conversation:

grid.aiAssistant = {
...baseConfig,
actionsForRequest: () => (canOpenOrders() ? [openOrder] : []),
};

canOpenOrders() is your permission or feature-availability check. The callback returns the complete action list, rather than appending to actions. Include any other tools you want to keep. It controls actions; filter/sort commands still follow your disclosed field policy and executor.

Availability helps the interpreter choose a valid operation. Your application callback or service must also check permission when the action actually runs. For example, put the check inside openOrderDetails, and enforce it on your server when loading protected data.

You do not need an adapter just to add a tool or change this list.

Choose a remote executor when filtering and sorting belong to your server. The interpreter still translates words into criteria; your data loader fetches the matching rows.

import {
createRemoteAiViewExecutor,
type AiViewSnapshot,
} from '@revolist/revogrid-pro';
import { loadOrders } from './orders-api'; // Your authenticated row loader.
let currentView: AiViewSnapshot = { sort: [] };
const executor = createRemoteAiViewExecutor({
read: () => currentView,
load: (view, { signal }) =>
loadOrders({
filterAst: view.filter,
sort: view.sort,
offset: 0, // Start at the first page when criteria change.
signal,
}),
commit(view, result) {
currentView = view;
grid.source = result.rows;
},
});
grid.aiAssistant = { ...baseConfig, executor, actions: [openOrder] };

loadOrders is your application function, returning { rows, total? }. Keep currentView consistent with any filters or sorting changed elsewhere in your application. Forward the signal to your request. The helper rejects stale/cancelled results before committing them; your server owns authorization and query validation.

You can also supply the small AiViewExecutor contract directly when your application already has its own query controller. See server-side data for paging and shared query configuration.

5. Share your workspace policy through an adapter

Section titled “5. Share your workspace policy through an adapter”

Use an adapter when one application or plugin owner needs to coordinate several decisions: which tools are available, when grid columns describe the source data, and how to subscribe to workspace changes. Simple actions and remote loaders can use the earlier APIs directly.

Here is the application-state contract used by this example:

import type { AiAssistantAdapterFactory } from '@revolist/revogrid-pro';
interface AssistantWorkspace {
allowedActions(): ReadonlySet<string>;
canReadColumns(): boolean;
subscribe(listener: () => void): () => void;
}
function createWorkspaceAdapter(
workspace: AssistantWorkspace,
): AiAssistantAdapterFactory {
return ({ actions }) => ({
actions: () =>
actions().filter((action) => workspace.allowedActions().has(action.name)),
canReadColumns: () => workspace.canReadColumns(),
subscribe: (refresh) => workspace.subscribe(refresh),
});
}
grid.aiAssistant = {
...baseConfig,
actions: [openOrder],
adapter: createWorkspaceAdapter(workspace),
};

workspace is your store or plugin owner implementing those three methods. allowedActions returns action names. canReadColumns is normally true; return false while another plugin temporarily projects different columns, so the assistant retains its previously disclosed source fields. subscribe returns the unsubscribe function. The assistant calls it when reconfigured or destroyed; destroy is also available for resources created by an adapter.

The actions() supplied in the adapter context resolves your configured tools. Your adapter decides which to return; our ViewSwitcher preset is never added automatically. An adapter can also supply its own executor. It should use existing plugin APIs for the actual operation and release only resources it creates.

Try it: open an order, turn off Allow order details, then try opening it again. The application removes orders.open from the available tools and checks permission in the callback too. Only Europe still works because filtering is independent of that custom action. Turn access back on to restore the tool.

Try Your workspace policy

Choose a suggested request below. This scripted demo runs entirely in your browser, with no backend, model key or model calls. It recognizes only the suggested requests.

Try it: Open an order, turn off Allow order details and try again. The tool is unavailable, while Only Europe still filters the grid. Turn it back on to restore access.

Available tool: orders.open
Application order detailsChoose an order to see its details here.
The demo loads when visible.

Reset demo restores the original sample data and clears its conversation. To use your own prompts, follow the API-client setup guide.

Both custom live examples use these exact factories. Copy them into your application and provide your own details callback, workspace and translations:

Source code
custom-behavior.ts
import {
actionInput,
defineAiAction,
translateAiText,
type AiAssistantAdapterFactory,
type AiAssistantTranslations,
} from '@revolist/revogrid-pro';
/** Application UI stays in the callback; the assistant only receives a typed tool. */
export function createOrderDetailsAction(
openDetails: (orderId: string) => void,
translations: AiAssistantTranslations = {},
) {
return defineAiAction({
name: 'orders.open',
description: 'Open an order’s details using its explicit order ID.',
inputSchema: {
type: 'object',
properties: { orderId: { type: 'string', minLength: 1, maxLength: 64 } },
required: ['orderId'],
additionalProperties: false,
},
parse(input) {
const { orderId } = actionInput(input, ['orderId']);
if (
typeof orderId !== 'string' ||
!orderId.trim() ||
orderId.length > 64
) {
throw new Error(
translateAiText('Enter a valid order ID.', translations),
);
}
return { orderId: orderId.trim() };
},
run({ orderId }) {
openDetails(orderId);
return {
message: translateAiText('Opened order {id}.', translations, {
id: orderId,
}),
};
},
});
}
/** Your application/store implements this interface; it is not another grid engine. */
export interface AssistantWorkspace {
allowedActions(): ReadonlySet<string>;
canReadColumns(): boolean;
subscribe(listener: () => void): () => void;
}
/** Optional composition for applications coordinating several owners or surfaces. */
export function createWorkspaceAdapter(
workspace: AssistantWorkspace,
): AiAssistantAdapterFactory {
return ({ actions }) => ({
actions: () =>
actions().filter((action) => workspace.allowedActions().has(action.name)),
canReadColumns: () => workspace.canReadColumns(),
subscribe: (refresh) => workspace.subscribe(refresh),
});
}

For the ready-made Data/Pivot/Charts workspace, install its native plugins and explicitly choose adapter: createViewSwitcherAiAdapter(). That preset handles source-query routing and view-specific tools. See the complete workspace setup. A custom metadata gate alone does not route queries between data owners.

Start a new conversation without changing the workspace

Section titled “Start a new conversation without changing the workspace”

The separate New conversation icon appears when there are messages, a draft or AI context to clear. It clears messages, the draft and retained model context. It also clears the assistant’s request bookkeeping. Filters, sorting, grouping, charts, layout and source data remain as they are. The next conversation starts from that current state.

Chat hides request history by default because the transcript already shows the conversation. Toolbar mode can show an icon-only history popup; set ui.showRequestHistory: false to hide it, or explicitly enable it in chat if your application needs it.

Workspace reset belongs to your application. In these live examples, Reset demo separately restores the sample rows, details and controls. It is independent of New conversation. There is no reset-on-restart policy in the assistant.

Localize custom messages, validation and suggested requests alongside the built-in controls. Preserve interpolation placeholders:

import { createOrderDetailsAction } from './custom-behavior'; // Copy the factories above.
const french: AiAssistantTranslations = {
'Opened order {id}.': 'Commande {id} ouverte.',
'Enter a valid order ID.': 'Saisissez un identifiant de commande valide.',
'Open ORD-0001': 'Ouvrir ORD-0001',
};
grid.aiAssistant = {
...baseConfig,
translations: french,
actions: [createOrderDetailsAction(openOrderDetails, french)],
ui: {
...baseConfig.ui,
suggestions: [translateAiText('Open ORD-0001', french)],
},
};

Model-produced messages use your interpreter’s language policy. See chat and language configuration.

Next: Test your integration without a model, or connect a custom service with approval.