请参阅 插件指南 ,了解设置、能力和完整示例。本页包含一个端到端插件以及生成的 @ampcode/plugin 类型参考。
这个单一插件演示了所有插件能力:事件、命令、工具、UI 和 AI 辅助函数。将其保存为 .amp/plugins/kitchen-sink.ts,然后运行 plugins: reload 命令面板中的命令。
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}`
},
})
}
可以用以下提示词和命令试用:
kitchen-sink: show kitchen sink notification。kitchen-sink: run kitchen sink UI 来测试通知、输入框、选择框、确认框和追加对话。kitchen-sink: open kitchen sink docs 来测试 ctx.system.open(...)。kitchen-sink: show kitchen sink runtime 来测试配置、shell 执行和系统元数据。kitchen-sink: append kitchen sink message。Use the kitchen_sink_tool with message hello.kitchen sink this turn.kitchen sink continue.导出函数体演示了插件加载时的初始化。UI 命令演示了 ctx.ui.notify、ctx.ui.input、ctx.ui.select、ctx.ui.confirm 和 ctx.thread?.append(...)。运行时命令演示了 amp.configuration.get()、amp.$ 和 amp.system。工具提示词演示了已注册工具以及它周围的 tool.call 和 tool.result 钩子。kitchen sink this turn 提示词演示了 agent.start 和 amp.ai.ask。最后一个提示词演示了 agent.end,它会自动启动一个后续回合。
@ampcode/plugin 类型参考/**
* # 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
}
/** Platform UI capabilities */
ui: PluginUI
/**
* 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>
}