See the Plugins guide in the manual for setup, capabilities, and worked examples. This page contains a single end-to-end plugin and the generated @ampcode/plugin type reference.
A single plugin that exercises every plugin surface — events, commands, tools, UI, and AI helpers. Save this as .amp/plugins/kitchen-sink.ts, then run plugins: reload from the command palette.
import type { PluginAPI } from '@ampcode/plugin'
const marker = '[kitchen-sink]'
export default function (amp: PluginAPI) {
amp.logger.log(`${marker} plugin initialized`)
amp.on('session.start', async (event, ctx) => {
await ctx.ui.notify(`Kitchen sink session.start for ${event.thread.id}.`)
})
amp.on('tool.call', async (event, ctx) => {
ctx.logger.log(`tool.call: ${event.tool}`)
const shellCommand = amp.helpers.shellCommandFromToolCall(event)
const files = amp.helpers.filesModifiedByToolCall(event)
ctx.logger.log(
`helper summary: shell=${shellCommand?.command ?? 'none'} files=${
files?.map((file) => amp.helpers.filePathFromURI(file)).join(', ') ?? 'none'
}`,
)
if (event.tool === 'kitchen_sink_tool') {
return { action: 'allow' }
}
const confirmed = await ctx.ui.confirm({
title: `Allow ${event.tool}?`,
message: 'Kitchen sink observed a tool call.',
confirmButtonText: 'Allow',
})
if (confirmed) {
return { action: 'allow' }
}
return {
action: 'reject-and-continue',
message: `Kitchen sink rejected ${event.tool}.`,
}
})
amp.on('tool.result', async (event, ctx) => {
ctx.logger.log(`tool.result: ${event.tool} ${event.status}`)
if (event.status === 'error') {
await ctx.ui.notify(`Kitchen sink saw ${event.tool} fail.`)
}
})
amp.on('agent.start', async (event, ctx) => {
if (!event.message.toLowerCase().includes('kitchen sink')) {
return
}
const answer = await amp.ai.ask(`Is this a kitchen sink request? ${event.message}`)
await ctx.ui.notify(`AI helper answered: ${answer.result}`)
return {
message: {
content: `${marker} agent.start hook received this turn.`,
display: true,
},
}
})
amp.on('agent.end', (event) => {
const toolCalls = amp.helpers.toolCallsInMessages(event.messages)
amp.logger.log(`agent.end saw ${toolCalls.length} completed tool calls`)
if (!event.message.toLowerCase().includes('kitchen sink continue')) {
return
}
if (event.message.includes(`${marker} continued`)) {
return
}
return {
action: 'continue',
userMessage: `${marker} continued. Reply with exactly KITCHEN_SINK_CONTINUED.`,
}
})
amp.registerCommand(
'show-kitchen-sink-notification',
{
title: 'Show kitchen sink notification',
category: 'kitchen-sink',
description: 'Show a notification from the kitchen sink plugin.',
},
async (ctx) => {
await ctx.ui.notify('Kitchen sink command ran.')
},
)
amp.registerCommand(
'run-kitchen-sink-ui',
{
title: 'Run kitchen sink UI',
category: 'kitchen-sink',
description: 'Run notify, input, select, and confirm dialogs in sequence.',
},
async (ctx) => {
await ctx.ui.notify('Starting the kitchen sink UI sequence.')
const note = await ctx.ui.input({
title: 'Kitchen sink input',
helpText: 'Enter a note to append to the current thread.',
initialValue: 'Hello from the kitchen sink plugin.',
submitButtonText: 'Continue',
})
const choice = await ctx.ui.select({
title: 'Kitchen sink select',
message: 'Choose what to do with the note.',
options: ['Append to thread', 'Show notification only', 'Cancel'],
})
const confirmed = await ctx.ui.confirm({
title: 'Finish kitchen sink UI?',
message: `Input: ${note ?? '(cancelled)'}\nChoice: ${choice ?? '(cancelled)'}`,
confirmButtonText: 'Finish',
})
if (confirmed && choice === 'Append to thread' && note) {
await ctx.thread?.append([{ type: 'user-message', content: note }])
}
await ctx.ui.notify(
confirmed ? 'Kitchen sink UI finished.' : 'Kitchen sink UI cancelled.',
)
},
)
amp.registerCommand(
'open-kitchen-sink-docs',
{
title: 'Open kitchen sink docs',
category: 'kitchen-sink',
description: 'Open the Plugin API reference page.',
},
async (ctx) => {
await ctx.system.open('https://ampcode.com/manual/plugin-api')
},
)
amp.registerCommand(
'show-kitchen-sink-runtime',
{
title: 'Show kitchen sink runtime',
category: 'kitchen-sink',
description: 'Show configuration, shell, and system information.',
},
async (ctx) => {
const config = await amp.configuration.get()
const pwd = await amp.$`pwd`
await ctx.ui.notify(
[
`Amp URL: ${amp.system.ampURL}`,
`Executor: ${amp.system.executor.kind}`,
`Working directory: ${pwd.stdout.trim()}`,
`Config keys: ${Object.keys(config).sort().join(', ') || '(none)'}`,
].join('\n'),
)
},
)
amp.registerCommand(
'append-kitchen-sink-message',
{
title: 'Append kitchen sink message',
category: 'kitchen-sink',
description: 'Append a user message to the active thread.',
},
async (ctx) => {
await ctx.thread?.append([
{ type: 'user-message', content: 'Message appended by kitchen sink plugin.' },
])
},
)
amp.registerTool({
name: 'kitchen_sink_tool',
description: 'Returns a short message proving the plugin tool ran.',
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Message to echo back.',
},
},
required: ['message'],
},
async execute(input, ctx) {
const message = typeof input.message === 'string' ? input.message : '(no message)'
ctx.logger.log(`kitchen_sink_tool received: ${message}`)
return `Kitchen sink tool received: ${message}`
},
})
}
Try it with these prompts and commands:
kitchen-sink: show kitchen sink notification from the command palette.kitchen-sink: run kitchen sink UI to test notification, input, select, confirm, and thread append.kitchen-sink: open kitchen sink docs to test ctx.system.open(...).kitchen-sink: show kitchen sink runtime to test configuration, shell execution, and system metadata.kitchen-sink: append kitchen sink message from the command palette.Use the kitchen_sink_tool with message hello.kitchen sink this turn.kitchen sink continue.The exported function body demonstrates plugin-load initialization. The UI command demonstrates ctx.ui.notify, ctx.ui.input, ctx.ui.select, ctx.ui.confirm, and ctx.thread?.append(...). The runtime command demonstrates amp.configuration.get(), amp.$, and amp.system. The tool prompt demonstrates the registered tool plus the tool.call and tool.result hooks around it. The kitchen sink this turn prompt demonstrates agent.start and amp.ai.ask. The last prompt demonstrates agent.end by starting one follow-up turn automatically.
@ampcode/plugin Type Reference/**
* # Amp Plugin API
*
* Plugins are JavaScript/TypeScript programs that extend & customize Amp.
* They are long-lived processes that may run for multiple threads concurrently.
*
* Plugins live in `.amp/plugins/` (project) or `~/.config/amp/plugins/` (system) and are executed using Bun.
*
* A plugin exports a default function that receives a {@link PluginAPI} instance. For example:
*
* ```ts
* import type { PluginAPI } from '@ampcode/plugin'
*
* export default function (amp: PluginAPI) {
* amp.logger.log('Plugin initialized')
* }
* ```
*/
/**
* The plugin API object passed to the plugin's default export function.
*/
export interface PluginAPI {
/** Logger scoped to this plugin */
logger: PluginLogger
/** System capabilities and environment information */
system: PluginSystem
/** Observable configuration that streams changes */
configuration: PluginConfiguration<Record<string, unknown>>
/**
* Execute shell commands using Bun's shell.
* Unlike `ctx.$` in event handlers, this is not tied to a specific hook invocation.
*/
$: ShellFunction
/**
* Helper utilities for interpreting tool events.
*/
helpers: {
shellCommandFromToolCall: ShellCommandFromToolCall
toolCallsInMessages: ToolCallsInMessages
filesModifiedByToolCall: FilesModifiedByToolCall
filePathFromURI: FilePathFromURI
isPluginUINotAvailableError: IsPluginUINotAvailableError
}
/**
* Register a handler for plugin events.
* For request events (e.g., tool.call), the handler must return a result.
* For fire-and-forget events, the handler returns void.
*
* If multiple plugins listen on the same event, the order in which each plugin's event handler is executed is not defined.
*/
on<E extends keyof PluginEventMap>(
event: E,
handler: (event: PluginEventMap[E], ctx: PluginEventContext<E>) => PluginHandlerResult<E>,
): Subscription
/**
* Register a command that appears in Amp's command palette.
* When the user invokes the command, the handler is called.
*
* @param id - Stable identifier for the command (e.g., "hello-world").
* @param options - Configuration for the command including title, category, and description.
* @param handler - The function to execute when the command is invoked.
*
* @example
* ```ts
* amp.registerCommand('hello-world', { title: 'greet', category: 'hello', description: 'Say hello' }, async (ctx) => {
* await ctx.ui.notify('Hello, world!')
* })
* ```
*/
registerCommand(
id: string,
options: PluginCommandOptions,
handler: (ctx: PluginCommandContext) => void | Promise<void>,
): CommandSubscription
/**
* Register a tool that the agent can call.
* Plugin tools appear alongside built-in tools and can be invoked by the LLM during conversations.
*
* @param definition - The tool definition including name, description, schema, and execute handler.
*
* @example
* ```ts
* amp.registerTool({
* name: 'hello',
* description: 'Greet someone by name',
* inputSchema: {
* type: 'object',
* properties: { name: { type: 'string', description: 'Name to greet' } },
* required: ['name'],
* },
* async execute(input) {
* return `Hello, ${input.name}!`
* },
* })
* ```
*/
registerTool(definition: PluginToolDefinition): Subscription
/** AI helpers */
ai: PluginAI
/**
* Experimental plugin APIs that are not stable and may change or be removed.
*
* Agents should only build on these APIs when the user explicitly approves the
* use of experimental Amp plugin APIs.
*/
experimental?: ExperimentalPluginAPI
}
/**
* APIs under `PluginAPI.experimental` are not stable and may change or be removed.
*/
export interface ExperimentalPluginAPI {
/**
* Create a status item shown near the prompt editor or status bar in the Amp client.
*
* If no initial value is provided, the item is hidden until its first update.
*/
createStatusItem(initial?: StatusItemValue): StatusItem
/**
* Observable that emits the currently active thread (the one the user is focused on
* in the UI), or `null` when no thread is active.
*
* Use this to determine whether the thread that triggered an event is the one the user
* is currently looking at, or is running in the background. For example, in a
* `tool.call` handler, compare `event.thread.id` to
* `amp.experimental.activeThread.current` to decide whether to surface a UI prompt
* (active) or take a non-interactive default (background).
*/
activeThread: Observable<{ id: ThreadID } | null> & {
readonly current: { id: ThreadID } | null
}
}
/**
* A plugin status item shown in supported Amp clients.
*/
export interface StatusItem extends Subscription {
/** Update the status item content. */
update(value: StatusItemValue): void
}
export interface StatusItemValue {
/** Text to show. */
text: string
/**
* URL to open when clicked, if any.
*
* Use a `command:` URI to execute a command registered by a plugin or the
* command palette. For example, `command:foo` runs the command with ID `foo`.
*/
url?: string
}
/**
* Result from an AI ask operation.
*/
export interface PluginAIAskResult {
/** The classification result: 'yes', 'no', or 'uncertain' */
result: 'yes' | 'no' | 'uncertain'
/** Probability (0-1) that the answer is yes */
probability: number
/** Explanation of why the AI gave this answer */
reason: string
}
/**
* AI capabilities provided to plugins.
*/
export interface PluginAI {
/**
* Ask an AI model a yes/no question and get a confidence-based response with reasoning.
* @param question - The yes/no question to ask
* @returns Object with result, probability, and reason
*/
ask(question: string): Promise<PluginAIAskResult>
}
/**
* Observer interface for subscribing to configuration changes.
*/
export interface PluginConfigurationObserver<T> {
next?(value: T): void
error?(error: unknown): void
complete?(): void
}
/**
* Subscription that can be unsubscribed to release resources.
*/
export interface Subscription {
unsubscribe(): void
}
/**
* Target for configuration updates.
*/
export type PluginConfigurationTarget = 'workspace' | 'global'
/**
* Minimal Observable interface used by plugin APIs that stream values over time.
*
* Subscribers receive subsequent values until they unsubscribe.
*/
export interface Observable<T> {
/**
* Subscribe to values emitted by this observable.
*/
subscribe(observer: PluginConfigurationObserver<T>): Subscription
subscribe(onNext: (value: T) => void): Subscription
/**
* Pipe operators for transforming this observable.
*/
pipe<Out>(op: (input: Observable<T>) => Out): Out
/**
* Return this observable for interop with observable libraries.
*/
[Symbol.observable](): Observable<T>
}
/**
* Observable-like interface for Amp configuration.
* Provides a limited subset of Observable functionality for plugins.
*/
export interface PluginConfiguration<T> extends Observable<T> {
/**
* Get the current configuration value.
*/
get(): Promise<T>
/**
* Update configuration with partial values.
* @param partial - The partial configuration to merge
* @param target - Where to store the setting: 'global' (user settings) or 'workspace' (default)
*/
update(partial: Partial<T>, target?: PluginConfigurationTarget): Promise<void>
/**
* Delete a configuration key.
* @param key - The key to delete
* @param target - Where to delete from: 'global' (user settings) or 'workspace' (default)
*/
delete(key: keyof T, target?: PluginConfigurationTarget): Promise<void>
}
/**
* Logger provided to plugins for scoped logging.
*/
export interface PluginLogger {
log: (...args: unknown[]) => void
}
/**
* Bun shell function type (simplified version of Bun.$)
*/
export type ShellFunction = (
strings: TemplateStringsArray,
...values: unknown[]
) => Promise<ShellResult>
/**
* Result from a shell command execution.
*/
export interface ShellResult {
exitCode: number
stdout: string
stderr: string
}
/**
* Where plugin code is running relative to the interactive UI.
*/
export type PluginExecutorKind = 'local' | 'remote' | 'unknown'
/**
* Information about the executor running plugin code.
*/
export interface PluginExecutor {
readonly kind: PluginExecutorKind
}
/**
* System capabilities provided to plugins.
*/
export interface PluginSystem {
/**
* Open a URL using the system's default protocol handler.
* On the CLI, it also shows a dialog with the URL text (for SSH users who can't open URLs remotely).
*/
open(url: string | URL): Promise<void>
/**
* Get the effective Amp base URL currently used by this Amp client.
* This reflects the active runtime configuration (for example, custom domains via `AMP_URL`).
*/
readonly ampURL: URL
/**
* Information about the executor that is running this plugin.
*/
readonly executor: PluginExecutor
}
/** @internal */
export type SpanID = string & { readonly __brand: 'SpanID' }
export type ThreadID = `T-${string}`
/**
* Message IDs are numeric in legacy TUI threads and stable string IDs in Neo
* thread-actor threads.
*/
export type ThreadMessageID = number | string
/**
* A text content block in a message.
*/
export interface ThreadTextBlock {
type: 'text'
text: string
}
/**
* A thinking content block in a message.
*/
export interface ThreadThinkingBlock {
type: 'thinking'
thinking: string
}
/**
* A tool use content block in a message.
*/
export interface ThreadToolUseBlock {
type: 'tool_use'
id: string
name: string
input: Record<string, unknown>
}
/**
* A tool result content block in a message.
*/
export interface ThreadToolResultBlock {
type: 'tool_result'
toolUseID: string
output?: PluginToolResult
status: 'done' | 'error' | 'cancelled' | 'running' | 'pending'
}
/**
* A user message in the thread.
*/
export interface ThreadUserMessage {
role: 'user'
/** The message ID, which is unique in the thread. */
id: ThreadMessageID
content: (ThreadTextBlock | ThreadToolResultBlock)[]
}
/**
* An assistant message in the thread.
*/
export interface ThreadAssistantMessage {
role: 'assistant'
/** The message ID, which is unique in the thread. */
id: ThreadMessageID
content: (ThreadTextBlock | ThreadThinkingBlock | ThreadToolUseBlock)[]
}
/**
* An info message in the thread.
*/
export interface ThreadInfoMessage {
role: 'info'
/** The message ID, which is unique in the thread. */
id: ThreadMessageID
content: ThreadTextBlock[]
}
/**
* A message in the thread (simplified view for plugins).
*/
export type ThreadMessage = ThreadUserMessage | ThreadAssistantMessage | ThreadInfoMessage
/**
* Thread API for manipulating the current thread.
*/
export interface PluginThread {
/** Active thread ID for the current invocation context */
id: ThreadID
/**
* Append a user message to the thread.
*/
append(messages: UserMessage[]): Promise<void>
}
/**
* A user message that can be appended to the thread.
*/
export interface UserMessage {
type: 'user-message'
content: string
}
/**
* Options for the input dialog.
*/
export interface PluginInputOptions {
/** Dialog title */
title?: string
/** Help text/description shown below the title */
helpText?: string
/** Initial text value in the input field */
initialValue?: string
/** Text for the submit button (default: "Submit") */
submitButtonText?: string
}
/**
* Options for the confirm dialog.
*/
export interface PluginConfirmOptions {
/** Dialog title */
title: string
/** Message body shown below the title */
message?: string
/** Text for the confirm button (default: "Yes") */
confirmButtonText?: string
}
/**
* Options for the select dialog.
*/
export interface PluginSelectOptions {
/** Dialog title */
title: string
/** Message body shown below the title */
message?: string
/** Initially selected option value */
initialValue?: string
/** Entries to display as choices */
options: string[]
}
/**
* UI capabilities provided to plugins.
*/
export interface PluginUI {
notify(message: string): Promise<void>
/**
* Show an input dialog prompting the user for text input.
* @returns The entered text, or undefined if the user cancelled.
*/
input(options: PluginInputOptions): Promise<string | undefined>
/**
* Show a confirmation dialog with Yes/No options.
* @returns true if the user confirmed, false if they cancelled.
*/
confirm(options: PluginConfirmOptions): Promise<boolean>
/**
* Show a select dialog with user provided options.
* @returns the selected value, undefined if they cancelled
*/
select(options: PluginSelectOptions): Promise<string | undefined>
}
/**
* URI value returned by helper APIs.
*
* This stays intentionally minimal so external plugin authors don't need
* Amp's internal URI package in their dependency graph.
*/
export interface URI {
toString(): string
}
/**
* Event payload for session.start event.
* Fired when Amp starts a thread session, such as when the user sends the first
* message in a new thread or opens/switches to an existing thread.
*/
export interface SessionStartEvent {
/** The thread that started running */
thread: { id: ThreadID }
}
/**
* A tool call.
*/
export interface ToolCall {
/** Unique identifier for this tool use (e.g., "toolu_xxx") */
toolUseID: string
/** Name of the tool that will be executed */
tool: string
/** Input arguments that will be passed to the tool */
input: Record<string, unknown>
}
/**
* Event payload for tool.call event.
* This is a request that expects a response from the handler.
*/
export interface ToolCallEvent extends ToolCall {
/** The active thread for this tool invocation */
thread: { id: ThreadID }
}
/**
* Result returned from a tool.call handler.
* Determines how the tool execution should proceed.
*/
export type ToolCallResult =
/** Allow the tool to execute with its original input */
| { action: 'allow' }
/** Reject the tool call but allow the agent to continue with other tools */
| { action: 'reject-and-continue'; message: string }
/** Modify the tool's input arguments before execution */
| { action: 'modify'; input: Record<string, unknown> }
/** Provide a synthesized result without actually running the tool */
| { action: 'synthesize'; result: { output: string; exitCode?: number } }
/** Error occurred in the plugin - stops the thread worker and shows an ephemeral error */
| { action: 'error'; message: string }
/**
* A terminal tool result.
*/
export interface ToolResult {
/** Unique identifier for this tool use (e.g., "toolu_xxx") */
toolUseID: string
/** Name of the tool that was executed */
tool: string
/** Input arguments passed to the tool */
input: Record<string, unknown>
/** Result status of the tool execution */
status: 'done' | 'error' | 'cancelled'
/** Error message if status is 'error' */
error?: string
/** Tool output/result if available */
output?: unknown
}
/**
* A structured content block returned from a plugin tool.
*/
export type PluginToolResultContentBlock =
| { type: 'text'; text: string }
| {
type: 'image'
/** MIME type, e.g. 'image/png', 'image/jpeg', or 'image/webp'. */
mimeType: string
/** Base64-encoded payload with no data: prefix. */
data: string
}
/**
* Result returned from a plugin tool.
*
* Returning a bare string keeps the existing text-only behavior. Returning an array
* of content blocks lets a tool mix text and inline base64 image blocks.
*/
export type PluginToolResult = string | PluginToolResultContentBlock[]
/**
* Event payload for tool.result event.
*/
export interface ToolResultEvent extends ToolResult {
/** The active thread for this tool result */
thread: { id: ThreadID }
}
/**
* Result returned from a tool.result handler.
* Allows modifying the tool result before it is sent back to the model.
*/
export type ToolResultResult =
| {
status: 'done'
output?: unknown
}
| {
status: 'error'
error?: string
output?: unknown
}
| {
status: 'cancelled'
error?: string
output?: unknown
}
| undefined
| void
/**
* Event payload for agent.start event.
* Fired when a user submits a prompt (initial or reply).
*/
export interface AgentStartEvent {
/** The active thread for this agent turn */
thread: { id: ThreadID }
/** The user's prompt message */
message: string
/** The message ID */
id: ThreadMessageID
}
/**
* Result returned from an agent.start handler.
* Allows adding context messages or modifying the system prompt.
*/
export interface AgentStartResult {
/**
* A message to append after the user's content in the user message.
* If display is true, the message is shown in the UI.
*/
message?: { content: string; display: true }
}
/**
* Event payload for agent.end event.
* Fired when the agent finishes handling a user prompt.
*/
export interface AgentEndEvent {
/** The active thread for this agent turn */
thread: { id: ThreadID }
/** The user's prompt message that started this turn */
message: string
/** The message ID that started this turn */
id: ThreadMessageID
/** The outcome of the agent's turn */
status: 'done' | 'error' | 'cancelled'
/** All messages since the agent.start event (including the user message that started this turn) */
messages: ThreadMessage[]
}
/**
* Result returned from an agent.end handler.
* Allows starting a new agent turn by returning a user message.
*/
export type AgentEndResult =
/** Automatically send a follow-up user message to start a new agent turn */
{ action: 'continue'; userMessage: string } | void
/**
* Map of event names to their payload types.
*/
export interface PluginEventMap {
'session.start': SessionStartEvent
'tool.call': ToolCallEvent
'tool.result': ToolResultEvent
'agent.start': AgentStartEvent
'agent.end': AgentEndEvent
}
/**
* Map of request event names to their result types.
* These events expect a response from the handler.
*/
export interface PluginRequestResultMap {
'tool.call': ToolCallResult
'tool.result': ToolResultResult
'agent.start': AgentStartResult
'agent.end': AgentEndResult
}
/**
* Context shared by all plugin event handlers.
*/
export interface PluginEventContextBase {
/** Scoped logger for plugin output. Log messages are appended to the handler's trace span events. */
logger: PluginLogger
/** Bun's shell API for executing commands */
$: ShellFunction
/** Platform UI capabilities */
ui: PluginUI
/** AI capabilities */
ai: PluginAI
/** System capabilities */
system: PluginSystem
/** The trace span ID for this handler invocation, if tracing is enabled */
span?: SpanID
}
/**
* Context passed as the second argument to event handlers.
* All plugin events are thread-scoped.
*/
export type PluginEventContext<E extends keyof PluginEventMap> = PluginEventContextBase & {
thread: PluginThread
}
/**
* Handler return type based on whether the event expects a response.
* Request events (in PluginRequestResultMap) must return a result.
* Fire-and-forget events return void.
*/
export type PluginHandlerResult<E extends keyof PluginEventMap> =
E extends keyof PluginRequestResultMap
? PluginRequestResultMap[E] | Promise<PluginRequestResultMap[E]>
: void | Promise<void>
/**
* Standardized shell command representation.
*/
export interface ShellCommand {
command: string
dir?: string
}
/**
* A tool call and its corresponding terminal tool result extracted from thread messages.
*/
export interface ToolCallWithResult {
call: ToolCall
result: ToolResult
}
/**
* Extracts the shell command from a Bash or shell_command tool call.
* Returns null if the event is not a shell command tool call.
*/
export type ShellCommandFromToolCall = (event: ToolCall) => ShellCommand | null
/**
* Extracts paired tool calls and terminal tool results from a list of thread messages.
*/
export type ToolCallsInMessages = (messages: ThreadMessage[]) => ToolCallWithResult[]
/**
* Returns an array of file URIs modified by a tool call, or null if the tool doesn't modify files.
* Supports edit/create/apply_patch tools and sed in-place shell commands.
*/
export type FilesModifiedByToolCall = (event: ToolCall | ToolResult) => URI[] | null
/**
* Converts a file URI returned by helper APIs to a local filesystem path.
*/
export type FilePathFromURI = (uri: URI) => string
/**
* Determines whether an instance of Error indicates that no Plugin UI is available.
*/
export type IsPluginUINotAvailableError = (error: Error) => boolean
/**
* Whether a registered command is selectable in the command palette.
*
* - `enabled`: shown and selectable.
* - `disabled`: shown but not selectable; `reason` is displayed alongside the command.
* - `hidden`: not shown in the palette at all.
*/
export type CommandAvailability =
| { type: 'enabled' }
| { type: 'disabled'; reason: string }
| { type: 'hidden' }
/**
* Options for registering a command.
*/
export interface PluginCommandOptions {
/** The title shown after the colon in the command palette (e.g., "Greet" in "Hello: Greet") */
title: string
/** The category shown before the colon (e.g., "Hello" in "Hello: Greet"). Defaults to the plugin name. */
category?: string
/** Human-readable description of what this command does */
description?: string
/**
* Initial availability of the command in the command palette.
* Defaults to `{ type: 'enabled' }`.
*
* Use the {@link CommandSubscription.setAvailability} method on the
* subscription returned by {@link PluginAPI.registerCommand} to update
* availability dynamically.
*/
availability?: CommandAvailability
}
/**
* Subscription returned by {@link PluginAPI.registerCommand}.
*
* Allows updating the command's availability in the palette in addition to
* unregistering it.
*/
export interface CommandSubscription extends Subscription {
/**
* Update whether this command is selectable in the command palette.
* Triggers a refresh in the host so the palette reflects the new state
* on its next read.
*/
setAvailability(status: CommandAvailability): void
}
/**
* Context passed to command handlers.
* Provides access to UI capabilities for executing command actions.
*/
export interface PluginCommandContext {
/** Platform UI capabilities */
ui: PluginUI
/** AI capabilities */
ai: PluginAI
/** System capabilities */
system: PluginSystem
/** Bun's shell API for executing commands */
$: ShellFunction
/** Current thread context if a thread is active, undefined otherwise */
thread?: PluginThread
}
/**
* Context passed to tool execute handlers.
*/
export interface PluginToolContext {
/** UI capabilities provided to plugins */
ui: PluginUI
/** Scoped logger for plugin output */
logger: PluginLogger
}
/**
* Options for registering a tool that the agent can call.
*/
export interface PluginToolDefinition {
/** Tool name (must match ^[a-zA-Z0-9_-]+$) */
name: string
/** Description shown to the LLM explaining what the tool does */
description: string
/** JSON Schema for the tool's input parameters */
inputSchema: {
type: 'object'
properties?: Record<string, object>
required?: string[]
[key: string]: unknown
}
/** Execute the tool with the given input and return a result */
execute: (
input: Record<string, unknown>,
ctx: PluginToolContext,
) => Promise<PluginToolResult | void>
}