Plugins - AI Tools


An AI Tool Plugin is a Plugin the AI agent can call. Set a Plugin's run location to AI Tool and it stops being a piece of UI: it has no container, no HTML, and no button. Instead it is offered to the agent alongside Navigator's built-in tools, and the agent decides when to call it and with what arguments.

This is the way to teach the agent something only your account knows: a lookup against a site system, a calculation over your own attributes, a rule about your own data. You write the function, and the agent works out when it is the right one to reach for.

Invoke, not Run

A UI Plugin exports Run(params) and returns a dispose function. An AI Tool exports Invoke(args, context) and returns a result. There is nothing to dispose, because there is nothing on screen.

/**
 * Called by the agent.
 * args is model-generated and matches Settings.tool.inputSchema.
 * The return value is serialized to JSON and handed back to the agent, so return data rather than a sentence.
 */
async function Invoke(args, context) {
    // Per-account values, from the Plugin's Settings.config.
    const config = context.config;

    // The Project View that was active when the agent called.
    const viewId = context.viewId;

    // Aborts when the declared timeout elapses.
    const signal = context.signal;

    return {
        answer: 42
    };
}

Both forms may live in the same file. Only the one the location calls for is used, so a Plugin can present a panel and expose the same capability to the agent.

Declaring the tool

The agent cannot see your code, only what the Plugin declares. That declaration lives in the Plugin's Settings under a tool key, and Operator refuses to save an AI Tool without it.


Settings
index.js
{
    "tool": {
        "description": "Returns the maintenance backlog for a building, newest first. Use when asked what work is outstanding on a building.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "buildingId": {
                    "type": "string",
                    "description": "Entity ID of the building to report on."
                },
                "limit": {
                    "type": "integer",
                    "description": "Maximum records to return. Defaults to 20."
                }
            },
            "required": ["buildingId"],
            "additionalProperties": false
        },
        "annotations": {
            "sideEffect": "none",
            "timeoutMs": 20000
        }
    },
    "config": {
        "entityTypeId": "maintenance_task"
    }
}

Note what an AI Tool is not given. There is no pluginParams, so no viewer, no visual register, and no BModels or BEngine: those are passed to a UI Plugin by its host, and an AI Tool has no host. An AI Tool sees its arguments, its context, and the browser's own APIs. Anything it needs to reach an API with, a base URL, a header name and a token, belongs in Settings.config, which is also what keeps one Plugin working across accounts without a code change.

Rules the platform enforces

These are refused at save time rather than at call time, so a mistake here shows up in Operator, not in a conversation with the agent.

RuleWhy
The Plugin ID becomes the tool nameThe agent calls it as plugin_<pluginId>. The ID may hold letters, numbers, hyphens and underscores only.
A description is requiredThe tool name is an opaque ID and carries no meaning, so the description is the only thing the agent can select on. Without one the tool is registered and never chosen. Set tool.description, or fill in the Plugin's Name and Description. Say what it returns and when to use it.
inputSchema must be an inline object schemaIts type must be object, and $ref, $defs, allOf and not are refused: each silently drops the tool out of strict mode, so the arguments stop being structurally guaranteed and nothing reports that it happened. Inline the schema instead.
annotations.sideEffectEither none, or external for a reviewed read-only external request. AI Tools are read-only in this release, so a tool that writes is refused.
annotations.timeoutMsA positive number of milliseconds. Defaults to 15000 and is clamped to 120000, so no tool can pin a call open. The whole operation is inside that budget, source fetch included.
The result must be JSON and under 100,000 charactersA result that cannot be serialized, or that is larger than the cap, is reported to the agent as a failure rather than truncated. Return a summary and let the agent ask again for detail.

What to expect at call time

The Plugin's code runs in the user's own browser with the user's own permissions, and the Plugin record is re-read server side with that user's session immediately before the call is forwarded. A Plugin the user cannot see is a Plugin the agent cannot call on their behalf, regardless of what any browser has cached.

Timeouts free the caller, not the Plugin. Honouring context.signal is up to you, and aborting a signal cannot stop JavaScript that is already blocking the browser thread: a Plugin that blocks stalls Navigator itself. Thread the signal into every request you make.

async function Invoke(args, context) {
    // Passing the signal through is what makes a timeout actually stop work.
    const res = await fetch(context.config?.endpoint, {
        signal: context.signal
    });

    // A long loop should check it too, since nothing will interrupt one.
    for (const item of bigList) {
        if (context.signal?.aborted) {
            throw new Error("Aborted.");
        }
    }

    return await res.json();
}

Throwing is the correct way to report a problem. The failure is turned into a standard envelope carrying a reason the agent can act on, so an Errorwhose message says what was wrong with the arguments lets the agent correct itself and call again.

Before enabling one in production

An AI Tool is the one Plugin location where nobody clicks a button to run the code. Review it as you would review anything triggered automatically:

  • Validate every argument. They are model-generated, they are not trusted, and a Plugin is the last thing standing between them and your API.
  • Keep the tool narrow. One tool that answers one question is chosen correctly far more often than one that does six things behind a mode argument.
  • Return only what the answer needs, both because the cap is real and because everything returned is spent as agent context.
  • Restrict access where the data warrants it, through the Plugin's login and permission settings, rather than assuming the agent is a safe caller.