Angular Embedded AnalyticsTools Overview

Version 3.0.0

A tool is how an agent acts on a dashboard. It is a self-contained value: it advertises a JSON schema to the model, and when the model calls it, it runs against live state and reports back.

Tools need no chat panel, no harness, and no agent. api.getAiTools() returns values you can execute yourself, and everything else in this section builds on them.

The example below has no panel, no harness and no LLM. Each button drives Studio through tools and commands directly, and the console shows what each returns.

Anatomy Copy Link

string
The name the LLM calls it by.
descriptionCopy Link
string
What the tool does, as the LLM reads it.
AgAiToolKind
Where the tool runs. See AgAiToolKind.
schemaCopy Link
Function
The current LLM-facing schema, or undefined when uncallable.
executeCopy Link
Function
Carries out one call against current state. Present only on a client tool: a server or provided tool is declared here and run elsewhere, so guard this rather than assuming it.

Three parts of that shape are worth calling out.

schema() is a function, not a value. It is called every turn, and it reads live state. A tool whose parameters include a widget-id enum reports the widgets that exist now.

A tool can be uncallable. When schema() returns undefined, the tool is withheld from that turn entirely - not advertised, not callable. That happens to configure_widget on a page with no widgets: rather than offering the model a choice with no valid values, the tool drops out until there is something to configure.

execute is optional. A client tool runs here. A server or provided tool is declared here and run elsewhere - see External Tools.

Commands: the Layer Below Copy Link

Most tools that change something are built on a command, and the split matters when you author your own.

A command is an action: an input shape plus an execute. It validates its arguments against the shape, applies the change, and returns a discriminated result. It has no name, no description and no idea an LLM exists.

A tool is the LLM-facing wrapper: the name and description the model sees, and the formatting that turns what the command returned into a response the model can read.

// The command: schema + action.
const setTheme = api.defineAiCommand((s) => ({
    input: s.object({ theme: s.enum(['light', 'dark']) }),
    execute: ({ theme }) => {
        applyTheme(theme);
        return { success: true, value: theme };
    },
}));

// The tool: what the model sees.
const setThemeTool = api.defineAiTool({
    name: 'set_theme',
    description: 'Switch the dashboard between light and dark.',
    command: setTheme,
    result: (theme) => ({ response: `Theme is now ${theme}.` }),
});

The command's input shape is the tool's schema, with nothing wrapped around it. Splitting them this way lets the validated action be reused: the same command can back a tool, a button in your own UI, and a scripted migration.

Results Copy Link

A tool returns a discriminated result, never a thrown error:

execute: (args, ctx) => {
    if (!isAllowed(args)) {
        return ctx.error('That region is not available to this user.');
    }
    return ctx.success(`Found ${rows.length} rows.`, { rows });
},

response is the string the model reads. data is an optional structured payload, serialised to the model and handed to the tool's component for rendering. Failures carry messages the model sees, so it can correct itself and try again.

The Four Authoring Paths Copy Link

You wantUsePage
A tool Studio already shipsapi.getAiTools()Built-in Tools
Your own action, validatedapi.defineAiTool({ command, result })Custom Tools
Studio's action, your wrapperapi.defineAiCommand({ type: 'AgX' }) then wrap itCustom Tools
A tool run by a server or the providerapi.defineAiTool({ kind: 'server' | 'provided' })External Tools

Descriptions Are Prompts Copy Link

A tool's description is prompt text, and it is the cheapest lever you have over behaviour. There are two ways to change it without touching the tool:

  • Per listing, for one agent: studio.addWidget({ description: '...' })
  • Globally: the aiText property, keyed tools.<name>.description

Both work for tools you define yourself, under the name the tool advertises. aiText holds prompt text rather than reader-facing wording, so it is not translated - see Localisation.

Without a Harness Copy Link

A harness is only needed to manage a conversation. The tools work on their own, driven by an automated process, an agent of your own, or something else entirely. WebMCP publishes them to the browser's own agent.

The AI module must still be registered, because it carries the tools, the context service and the licence. Tool schemas read live state, so build them per turn rather than once.

Running a Tool Copy Link

A tool takes an invocation and a per-call context, and hands back a discriminated result:

const studio = api.getAiTools();
const tool = studio.executeQuery();

const result = await tool.execute!({ toolCallId: 'call-1', name: tool.name, args: { query } }, createAiToolContext());

An invocation carries the call id, the tool name, and args as a decoded object. If you are relaying a call from a model, parse its arguments first - the tool validates them against its own shape, so a bad payload comes back as issues rather than an exception.

createAiToolContext builds what a harness would normally supply. Pass a signal to make the call cancellable, and a run if a tool needs to scope a resource per conversation.

execute is optional on AgAiTool, because external tools declare a schema without one. Every tool from api.getAiTools() has one, hence the assertion above; guard it instead when the tool could be external.

Running a Command Copy Link

For an automated process with no LLM anywhere, use a command rather than a tool. It validates its input and returns a result:

const addWidget = api.defineAiCommand({ type: 'AgAddWidgetCommand' });

const result = await addWidget.apply({
    widgetId: 'sales',
    type: 'bar-chart-grouped',
    xTrack: 0,
    yTrack: 0,
    xSpan: 6,
    ySpan: 4,
});

if (!result.success) {
    console.error(result.error.message);
}

Custom Tools covers writing commands of your own.

Next Copy Link