This document provides a comprehensive reference for the BB plugin framework, including plugin structure, tool types, interfaces, implementation patterns, and styling guidelines.
The BB plugin framework provides a structured way to create and distribute tools and datasources that can be used by AI assistants.
Plugins are packaged as .bbplugin directories containing:
- manifest.json: Required plugin metadata
- Tool components: One or more
.tooldirectories - Datasource components: Optional
.datasourcedirectories (future support)
Note: Standalone .tool directories are deprecated but still supported for backward compatibility.
Each tool within a plugin:
- Extends the
LLMToolbase class - Implements specific interfaces
- Provides both browser and console formatting
- Includes comprehensive testing
- Contains tool metadata in info.json
The foundation for all BB tools:
abstract class LLMTool {
constructor(
public name: string,
public description: string,
public toolConfig: LLMToolConfig,
public features: LLMToolFeatures = {},
) {}
abstract get inputSchema(): LLMToolInputSchema;
abstract runTool(
interaction: IConversationInteraction,
toolUse: LLMAnswerToolUse,
projectEditor: IProjectEditor
): Promise<LLMToolRunResult>;
abstract formatLogEntryToolUse(...): LLMToolLogEntryFormattedResult;
abstract formatLogEntryToolResult(...): LLMToolLogEntryFormattedResult;
}Tools can declare supported features:
interface LLMToolFeatures {
mutates?: boolean; // Modifies resources
stateful?: boolean; // Maintains state
async?: boolean; // Runs asynchronously
idempotent?: boolean; // Multiple runs produce same result
resourceIntensive?: boolean; // Needs significant resources
requiresNetwork?: boolean; // Needs internet access
}Tools return structured results:
interface LLMToolRunResult {
toolResults: LLMToolRunResultContent;
toolResponse: LLMToolRunToolResponse;
bbResponse: LLMToolRunBbResponse;
finalizeCallback?: (messageId: string) => void;
}Tools that interact with the project's file system:
- Must validate paths with
isPathWithinProject - Should use
ProjectEditormethods - Need to handle file operations carefully
- Consider impact on project structure
Example:
class FileSystemTool extends LLMTool {
async runTool(
interaction: IConversationInteraction,
toolUse: LLMAnswerToolUse,
projectEditor: IProjectEditor,
): Promise<LLMToolRunResult> {
const { filePath } = toolUse.toolInput;
if (!isPathWithinProject(projectEditor.projectRoot, filePath)) {
throw new Error('Invalid path');
}
// Perform file operations
}
}Tools that process or analyze data:
- Handle data validation
- Consider performance implications
- Implement proper error handling
- Support both synchronous and async operations
Example:
class DataProcessingTool extends LLMTool {
async runTool(
interaction: IConversationInteraction,
toolUse: LLMAnswerToolUse,
projectEditor: IProjectEditor,
): Promise<LLMToolRunResult> {
const { data } = toolUse.toolInput;
// Process data
return {
toolResults: processedData,
toolResponse: 'Data processed successfully',
bbResponse: { data: processedData },
};
}
}Tools that interact with external resources:
- Handle network errors
- Implement timeouts
- Consider rate limiting
- Validate external resources
Example:
class NetworkTool extends LLMTool {
features = {
requiresNetwork: true,
async: true,
};
async runTool(
interaction: IConversationInteraction,
toolUse: LLMAnswerToolUse,
projectEditor: IProjectEditor,
): Promise<LLMToolRunResult> {
const { url } = toolUse.toolInput;
// Fetch external resource
}
}Tools provide JSX-based formatting using TOOL_TAGS_BROWSER:
/** @jsxImportSource preact */
import LLMTool from "@beyondbetter/tools";
formatLogEntryToolUse(toolInput: LLMToolInputSchema): LLMToolLogEntryFormattedResult {
return {
title: LLMTool.TOOL_TAGS_BROWSER.content.title('Tool Use', 'Your Tool'),
subtitle: LLMTool.TOOL_TAGS_BROWSER.content.subtitle('Processing...'),
content: LLMTool.TOOL_TAGS_BROWSER.base.container(
<>
{LLMTool.TOOL_TAGS_BROWSER.base.label('Parameters')}
{LLMTool.TOOL_TAGS_BROWSER.base.list([
<>
{LLMTool.TOOL_TAGS_BROWSER.base.label('Parameter 1:')}
{' '}
{LLMTool.TOOL_TAGS_BROWSER.content.text('value')}
</>,
<>
{LLMTool.TOOL_TAGS_BROWSER.base.label('Status:')}
{' '}
{LLMTool.TOOL_TAGS_BROWSER.content.status('completed', 'Done')}
</>
])}
</>
),
preview: 'Tool execution preview'
};
}Tools provide text-based formatting using TOOL_STYLES_CONSOLE:
import { stripIndents } from 'common-tags';
import LLMTool from "@beyondbetter/tools";
formatLogEntryToolUse(toolInput: LLMToolInputSchema): LLMToolLogEntryFormattedResult {
return {
title: LLMTool.TOOL_STYLES_CONSOLE.content.title('Tool Use', 'Your Tool'),
subtitle: LLMTool.TOOL_STYLES_CONSOLE.content.subtitle('Processing...'),
content: stripIndents`
${LLMTool.TOOL_STYLES_CONSOLE.base.label('Parameters')}
${LLMTool.TOOL_STYLES_CONSOLE.base.listItem(
stripIndents`
${LLMTool.TOOL_STYLES_CONSOLE.base.label('Parameter 1:')}
${LLMTool.TOOL_STYLES_CONSOLE.content.text('value')}`
)}
${LLMTool.TOOL_STYLES_CONSOLE.base.listItem(
stripIndents`
${LLMTool.TOOL_STYLES_CONSOLE.base.label('Status:')}
${LLMTool.TOOL_STYLES_CONSOLE.content.status('completed', 'Done')}`
)}`,
preview: 'Tool execution preview'
};
}-
Base Components
container: Wraps content in a styled containerlabel: Displays a styled labellist: Creates a styled list of itemstext: Basic text styling
-
Content Components
title: Tool title with optional categorysubtitle: Secondary title or descriptionstatus: Status indicators (completed, error, etc.)error: Error message stylingsuccess: Success message stylingfilename: File path stylingurl: URL stylingdate: Date formattingsize: File size formattingboolean: Boolean value formattingregex: Regular expression formatting
-
Base Components
label: Styled text labelslistItem: Indented list itemstext: Basic text styling
-
Content Components
title: Tool title with optional categorysubtitle: Secondary title or descriptionstatus: Status indicatorserror: Error message stylingsuccess: Success message stylingfilename: File path stylingurl: URL stylingdate: Date formattingsize: File size formattingboolean: Boolean value formattingregex: Regular expression formatting
Use JSON Schema for input validation:
get inputSchema(): LLMToolInputSchema {
return {
type: 'object',
properties: {
param1: { type: 'string' },
param2: { type: 'number' }
},
required: ['param1']
};
}Implement consistent error handling:
try {
// Tool operations
} catch (error) {
if (error instanceof SpecificError) {
// Handle specific error
} else {
// Handle unknown error
}
throw error; // Let LLMToolManager handle it
}Clean up resources properly:
async runTool(...): Promise<LLMToolRunResult> {
const resource = await acquireResource();
try {
// Use resource
return result;
} finally {
await releaseResource(resource);
}
}-
Input Validation
- Use comprehensive JSON schemas
- Validate early in tool execution
- Provide clear error messages
- Handle edge cases
-
Error Handling
- Use structured error types
- Provide detailed error messages
- Clean up resources on error
- Handle async errors properly
-
Resource Management
- Track resource usage
- Clean up properly
- Handle cleanup errors
- Use try/finally blocks
-
Performance
- Consider operation cost
- Implement timeouts
- Handle large inputs
- Cache when appropriate
-
Formatting
- Use appropriate styling constants
- Structure content logically
- Handle error states consistently
- Provide clear visual hierarchy
- Use JSX fragments for browser components
- Use stripIndents for console output
- Include meaningful labels
- Format specialized content types
- CREATING_TOOLS.md - Detailed guide for creating tools
- TESTING.md - Testing requirements and guidelines
- README.md - Package overview