import type { PluginExtensionAddedFunctionConfig } from '@grafana/data';
import type { ComponentType, ReactNode } from 'react';
/**
* Extension point id consumed by the Grafana Assistant.
*
* A contributing app plugin registers a manifest function against this target via
* `AppPlugin.addFunction()` (declared under `extensions.addedFunctions` in its `plugin.json`).
* The Assistant discovers registrations with `usePluginFunctions()` and invokes each function to
* obtain the plugin's {@link AssistantExtensionManifest}.
*
* @experimental This API is under active development and may change in future releases.
*/
export declare const ASSISTANT_EXTENSION_POINT = "grafana-assistant-app/extension/v1";
/**
* Props passed to a `tool-details` chit component registered via {@link ChitContribution}.
*
* These are the normalized props every tool row in the Assistant chat track receives; a
* registered component renders inside the expanded details area of the standard tool card,
* in place of the generic expanded content.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface ToolChitProps {
/** The fully namespaced tool name that produced this card. */
toolName: string;
/** The tool call input, as sent to the tool. */
input: unknown;
/** The tool result, once available. `undefined` while the tool is still running. */
result: unknown;
/** Execution state of the tool call. */
status: 'running' | 'success' | 'error';
}
/**
* A contribution to the Assistant chat track ("chits"): either an expanded-details renderer
* keyed by tool name, or a component rendered in place of a tag inside assistant text.
*
* - `tool-details`: renders inside the expanded details area of the standard tool card for the
* listed tool names, replacing the generic expanded content (Query/Result). The compact tool
* row, its status, and the Allow/Deny confirmation flow are always built in — a plugin never
* replaces the tool row and never renders while a confirmation is pending. A plugin may only
* register details for tools it owns (tool names are namespaced by the tool pipeline and
* checked against the contributing plugin id).
* - `inline`: registers a component rendered in place of a custom tag inside assistant message
* text. The full tag name is the plugin id with non-alphanumerics collapsed to underscores,
* followed by an underscore and `name` — for example plugin `grafana-k6-app` with
* `name: 'result'` renders `…`. Tag children
* and allow-listed attributes are passed through as props. Nothing teaches the model the tag
* exists; teach it from your own prompt contribution or page context.
*
* Component and function references are the only non-JSON-serializable parts of a manifest —
* refer to {@link AssistantExtensionManifest} for the serializability contract.
*
* @experimental This API is under active development and may change in future releases.
*/
export type ChitContribution = {
kind: 'tool-details';
/** Fully namespaced tool names this details renderer applies to. Must be owned by the contributing plugin. */
toolNames: string[];
/** The details component, rendered inside a per-chit error boundary. */
component: ComponentType;
} | {
kind: 'inline';
/** Plugin-local tag suffix: lowercase, `[a-z][a-z0-9]*`. Namespaced by the plugin id into the full tag name. */
name: string;
/** Rendered in place of the tag, inside a per-chit error boundary. Receives tag children and allow-listed attributes as props. */
component: ComponentType<{
children?: ReactNode;
}>;
};
/**
* The artifact file node handed to plugin artifact renderers and toolbars.
*
* Intentionally minimal and forward-compatible: renderers must treat this as the complete
* artifact surface and must not fetch artifact content from anywhere else.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface AssistantArtifactNode {
/** File path of the artifact in the conversation's artifact filesystem. */
path: string;
/** The artifact kind extension, e.g. `.grafana-k6-app.test`. */
ext: string;
/** The artifact content. Shape is owned by the contributing plugin's `validate` contract. */
data: unknown;
/**
* Monotonically increasing per-artifact write counter. Increments on every write to this
* artifact. Suitable for memoization keys, cache-busting, and change detection. NOT a
* timestamp — carries no wall-clock meaning.
*/
revision: number;
}
/**
* Read-only handle over the conversation's artifact filesystem, mirroring the host's read
* surface.
*
* The handle spans the whole conversation: it includes other plugins' and built-in artifacts by
* design — composition is the point. There is no write surface; artifact writes happen only
* through the Assistant's validated `write_artifact` path. Renderers must treat this handle as
* the only artifact-content source besides their own node.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface AssistantArtifactFs {
/** All artifacts in the conversation, in stable path order. */
list(): AssistantArtifactNode[];
/** Read one artifact by exact normalized path. */
read(path: string): AssistantArtifactNode | undefined;
/** Whether an artifact exists at the path. */
has(path: string): boolean;
}
/**
* Props passed to a plugin artifact renderer.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface ArtifactRendererProps {
/** The artifact node to render. */
node: AssistantArtifactNode;
/** Read-only handle over the conversation's artifact filesystem. */
fs: AssistantArtifactFs;
}
/**
* Props passed to an optional plugin artifact toolbar.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface ArtifactToolbarProps {
/** The artifact node the toolbar acts on. */
node: AssistantArtifactNode;
/** Read-only handle over the conversation's artifact filesystem. */
fs: AssistantArtifactFs;
}
/**
* A plugin-contributed artifact kind for the Assistant canvas.
*
* The `ext` is double-dot namespaced as `.{pluginId}.{kind}`, where `pluginId` is your full,
* exact plugin id — for example `.grafana-k6-app.test`, not a shortened slug. This keeps plugin
* kinds out of the built-in single-dot space and keeps orphaned artifacts attributable to their
* owning plugin without a live registry, since the plugin id is readable from the artifact path.
*
* Everything except `validate`, `renderer`, and `toolbar` is JSON-serializable — refer to
* {@link AssistantExtensionManifest} for the serializability contract.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface ArtifactKindContribution {
/** Artifact extension in the form `.{pluginId}.{kind}` (exact plugin id), double-dot namespaced. */
ext: `.${string}.${string}`;
/** Human-readable label shown on artifact tabs and cards. */
label: string;
/** Icon name shown next to the label. */
icon: string;
/**
* Tab promotion order, ascending: LOWER values are promoted and rendered earlier. Built-in
* kinds always occupy the lowest values (`.report` = 0, `.dashboard` = 1, `.query` = 2) and
* cannot be jumped — the host clamps this value to non-negative and offsets it into a range
* after every built-in, so it only orders your kinds relative to other plugin-contributed
* kinds. Ties break by most recently updated artifact first; kinds with no live registration
* sort last.
*/
tabPriority: number;
/** Write-time content contract: returns true when `data` is valid for this kind. */
validate(data: unknown): boolean;
/** Renders the artifact in the canvas. Receives the artifact node and a read-only conversation filesystem handle. */
renderer: ComponentType;
/** Optional toolbar rendered above the artifact. */
toolbar?: ComponentType;
/** Optional one-line hint teaching the model how to write artifacts of this kind. */
promptHint?: string;
}
/**
* Environment conditions evaluated by the Assistant when deciding whether a contributed mode is
* available. Unknown condition strings fail closed (the mode is hidden).
*
* @experimental This API is under active development and may change in future releases.
*/
export type PromptContributionAvailability = 'not-oss' | 'not-trial';
/**
* A plugin-contributed Assistant mode (system prompt variant).
*
* The contribution is exposed in the mode picker as `${pluginId}:${modeId}` and its prompt text
* is always rendered inside an attributed `` wrapper, subject to a
* per-plugin byte budget and an assistant-side allowlist.
*
* All fields are JSON-serializable — refer to {@link AssistantExtensionManifest} for the
* serializability contract.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface PromptContribution {
/** Plugin-local mode id. Exposed to users as `${pluginId}:${modeId}`. */
modeId: string;
/** Human-readable mode label shown in the mode picker. */
label: string;
/** Short description of the mode shown in the mode picker. */
description: string;
/** One line describing when the Assistant should suggest this mode. */
whenToUse: string;
/**
* How `prompt` composes with the base system prompt.
*
* - `prepend` / `append`: `prompt` is added before / after the base guidance, which is
* otherwise kept intact.
* - `replace`: the rest of the base guidance is dropped, but the Assistant's identity section
* is ALWAYS retained — the composed prompt is the identity block, then the current-mode
* block, then the attributed plugin body. A plugin never controls or replaces the
* Assistant's identity.
*
* Prefer `prepend` or `append` unless the mode is a genuinely self-contained specialist that
* needs none of the base guidance.
*/
compose: 'prepend' | 'append' | 'replace';
/**
* The prompt snippet (`prepend`/`append`) or specialist body (`replace`). With `replace`, this
* body is composed after the Assistant's always-retained identity section — refer to
* {@link PromptContribution.compose}.
*/
prompt: string;
/** Optional hidden first message sent when the mode is selected on a fresh chat. */
initialMessage?: string;
/**
* Whether the mode can be switched to mid-conversation. Defaults to true. When false the mode
* is entry-point only: listed on fresh chats and reachable via deep links, but hidden from the
* picker mid-conversation and excluded from mode suggestions.
*/
switchable?: boolean;
/** Environment conditions evaluated by the Assistant; all must hold for the mode to show. */
availability?: PromptContributionAvailability[];
/** Names of the plugin's own tools scoped exclusively to this mode. */
modeToolNames?: string[];
/** Optional icon name shown in the mode picker. */
icon?: string;
/** Optional feature state badge shown next to the mode label. */
featureState?: 'preview' | 'experimental';
}
/**
* A proposed first message shown on fresh chats (empty conversations) on the Assistant chat
* surfaces, visibly attributed to the contributing plugin.
*
* Selecting a starter sends `message` as an ordinary, user-visible user message. Starters are
* never auto-sent, never hidden, and never modify the system prompt — which is why they are part
* of the v1 chat-content surface (master flag + per-plugin kill switch only; no allowlist, no
* prompt-contributions flag). Contrast with {@link PromptContribution.initialMessage}, which is
* hidden, auto-sent on mode selection, and ships behind the v2 prompt-contributions flag.
*
* Enforced bounds (entries beyond a cap are dropped whole by the Assistant): max 3 starters per
* plugin, `message` at most 500 characters, `label` at most 80 characters.
*
* Starters are pure data (fully JSON-serializable), making them ideal for the future static
* `plugin.json` transport — refer to {@link AssistantExtensionManifest} for the serializability
* contract.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface AssistantStarter {
/** Optional short label shown on the starter; defaults to the message itself. Max 80 characters. */
label?: string;
/** The user message sent, visibly and as-is, when the starter is selected. Max 500 characters. */
message: string;
}
/**
* A plugin-contributed skill: a documented workflow the Assistant can load on demand.
*
* A skill is the recommended way for a plugin to teach the Assistant a workflow. By design only
* a bounded catalog line (title, description, handle) is ever resident in the Assistant's
* prompt; `content` enters model context only when the user attaches the skill or the model
* loads it. Contrast with {@link PromptContribution}, which splices resident text into the
* system prompt and ships behind a stricter flag and allowlist tier — skills need neither.
*
* Rollout status: today a registered skill appears read-only on the Assistant's Skills settings
* page, attributed to your plugin. The catalog/load path that puts skills in front of the model
* ships with the Assistant's agent-skills runtime and lights up automatically when it lands —
* no plugin-side change needed.
*
* Each skill is identified by the handle `plugin:{yourPluginId}:{id}`, with the plugin id taken
* from the extension registry — so skills can never collide with built-in or tenant skills, or
* with another plugin's. Plugin skills are never persisted or indexed: they exist only while
* your registration is live, and disappear everywhere at once when it is removed.
*
* Enforced bounds (entries violating any bound are dropped whole by the Assistant, never
* truncated): max 5 skills per plugin; `id` matching `[a-z][a-z0-9-]*`, at most 64 characters;
* `title` at most 80 characters; `description` at most 500 characters; `content` at most
* 32768 UTF-8 bytes.
*
* Skills are pure data (fully JSON-serializable), making them ideal for the future static
* `plugin.json` transport — refer to {@link AssistantExtensionManifest} for the serializability
* contract.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface SkillContribution {
/** Plugin-local skill id: `[a-z][a-z0-9-]*`, max 64 characters. Namespaced by the plugin id into the catalog handle. */
id: string;
/** Human-readable skill title shown in the catalog and in settings. Max 80 characters. */
title: string;
/** One or two sentences describing what the skill does and when to use it. Max 500 characters. */
description: string;
/** The skill body, loaded only on invocation — never resident in the system prompt. Max 32768 UTF-8 bytes. */
content: string;
}
/**
* The manifest a Grafana app plugin returns from its assistant extension function to contribute
* chits, starters, skills, artifact kinds, and prompt/mode variants to the Grafana Assistant.
*
* Serializability contract: every field of the manifest is JSON-serializable **except**
* component and function references (`ChitContribution.component` / `detect` / `render`,
* `ArtifactKindContribution.validate` / `renderer` / `toolbar`). This constraint is deliberate:
* it enables a future transport where the static halves of a manifest are declared in signed
* `plugin.json` metadata and only component references are resolved at runtime.
*
* Unknown fields and unknown contribution kinds are ignored by the Assistant, so manifests
* produced by newer SDK versions degrade gracefully on older Assistant versions.
*
* @experimental This API is under active development and may change in future releases.
*/
export interface AssistantExtensionManifest {
/** Manifest schema version. Always 1 for this SDK version. */
schemaVersion: 1;
/** Chat-track contributions: tool-details renderers and inline components. */
chits?: ChitContribution[];
/**
* Proposed first messages offered on fresh chats, attributed to the plugin. Max 3.
*
* When to use which: use `provideQuestions` to suggest questions on pages YOUR plugin already
* runs on (URL-scoped, page-context registry, requires your module to be loaded on that page,
* unattributed); use manifest `starters` for proposed first messages in the Assistant's fresh
* chats ANYWHERE (extension-point transport, loads your module on demand, plugin-attributed,
* governed by the Assistant's per-plugin kill switch and caps, statically declarable in the
* future). They intentionally share the same rendering surface and send path.
*/
starters?: AssistantStarter[];
/**
* Skills teaching the Assistant plugin-specific workflows, loaded on demand. Max 5.
*
* When to use which: use `skills` to document a workflow the Assistant should follow when a
* conversation enters your plugin's territory — only a one-line catalog entry is resident, and
* the body loads on invocation. Use `promptContributions` only for a full mode whose resident
* prompt text must always be present; that surface sits behind a stricter flag and allowlist
* tier. Skill content reaches the model only where the Assistant's agent-skills runtime is
* enabled; the read-only settings listing needs no additional flag.
*/
skills?: SkillContribution[];
/** Canvas artifact kinds. */
artifactKinds?: ArtifactKindContribution[];
/** Assistant mode / system prompt contributions. */
promptContributions?: PromptContribution[];
}
/**
* Builds the `AppPlugin.addFunction()` configuration that registers an assistant extension
* manifest with the Grafana Assistant.
*
* Purity contract: the registered function MUST be pure and stable — invoked any number of
* times, it must return the same (deep-equal) manifest for the same environment. The Assistant
* dedups deep-equal results; an unstable manifest churns downstream consumers and invalidates
* prompt caching. Gate on your own feature flags *before* registering rather than returning a
* varying manifest.
*
* @example
* ```typescript
* import { AppPlugin } from '@grafana/data';
* import { getAssistantExtensionConfig } from '@grafana/assistant';
*
* export const plugin = new AppPlugin().addFunction(
* getAssistantExtensionConfig({
* schemaVersion: 1,
* chits: [{ kind: 'tool-details', toolNames: ['myplugin_run_check'], component: MyToolDetails }],
* })
* );
* ```
*
* @experimental This API is under active development and may change in future releases.
*/
export declare function getAssistantExtensionConfig(manifest: AssistantExtensionManifest): PluginExtensionAddedFunctionConfig<() => AssistantExtensionManifest>;
//# sourceMappingURL=extension.d.ts.map