src/core/prompts/system-prompt/README.md
The system prompt architecture provides a modular, composable system for building AI assistant prompts. It supports multiple model variants, dynamic component composition, flexible tool configuration, and template-based prompt generation.
To generate snapshots for each variants added to the unit test in src/core/prompts/system-prompt/tests/integration.test.ts:
npm run test:unit
src/core/prompts/system-prompt/
├── registry/
│ ├── ClineToolSet.ts # Tool set management & registry
│ ├── PromptRegistry.ts # Singleton registry for loading/managing prompts
│ ├── PromptBuilder.ts # Builds final prompts with template resolution
│ └── utils.ts # Model family detection utilities
├── components/ # Reusable prompt components
│ ├── agent_role.ts # Agent role and identity section
│ ├── system_info.ts # System information section
│ ├── mcp.ts # MCP servers section
│ ├── todo.ts # Todo management section
│ ├── user_instructions.ts # User custom instructions
│ ├── tool_use.ts # Tool usage instructions
│ ├── editing_files.ts # File editing guidelines
│ ├── capabilities.ts # Agent capabilities section
│ ├── rules.ts # Behavioral rules section
│ ├── objective.ts # Task objective section
│ ├── act_vs_plan.ts # Action vs planning mode
│ ├── feedback.ts # Feedback and improvement section
│ └── index.ts # Component registry
├── templates/ # Template engine and placeholders
│ ├── TemplateEngine.ts # {{placeholder}} resolution engine
│ └── placeholders.ts # Standard placeholder definitions
├── tools/ # Individual tool definitions
│ ├── spec.ts # Tool specification interface
│ ├── register.ts # Tool registration system
│ ├── index.ts # Tool exports
│ └── [tool-name].ts # Individual tool implementations
├── variants/ # Model-specific prompt variants
│ ├── generic/
│ │ ├── config.ts # Generic fallback configuration
│ │ └── template.ts # Base prompt template
│ ├── next-gen/
│ │ ├── config.ts # Next-gen model configuration
│ │ └── template.ts # Advanced model template
│ ├── xs/
│ │ ├── config.ts # Small model configuration
│ │ └── template.ts # Optimized template
│ └── index.ts # Variant registry exports
├── types.ts # Core type definitions
└── README.md # This documentation
The PromptRegistry is the central manager for all prompt variants and components. It provides a singleton interface for loading and accessing prompts.
class PromptRegistry {
private static instance: PromptRegistry;
private variants: Map<string, PromptVariant> = new Map();
private components: ComponentRegistry = {};
private loaded: boolean = false;
static getInstance(): PromptRegistry {
if (!this.instance) {
this.instance = new PromptRegistry();
}
return this.instance;
}
// Load all prompts and components on initialization
async load(): Promise<void> {
if (this.loaded) return;
await Promise.all([
this.loadVariants(), // Load from variants/ directory
this.loadComponents() // Load from components/ directory
]);
this.loaded = true;
}
/**
* Get prompt by model ID with fallback to generic
*/
async get(context: SystemPromptContext): Promise<string> {
await this.load()
// Try model family fallback (e.g., "claude-4" -> "claude")
const modelFamily = this.getModelFamily(context.providerInfo)
const variant = this.variants.get(modelFamily ?? ModelFamily.GENERIC)
if (!variant) {
throw new Error(
`No prompt variant found for model '${context.providerInfo.model.id}' and no generic fallback available`,
)
}
const builder = new PromptBuilder(variant, context, this.components)
return await builder.build()
}
// Get specific version of a prompt
async getVersion(modelId: string, version: number, context: SystemPromptContext, isNextGenModelFamily?: boolean): Promise<string> {
// Supports next-gen model family prioritization
}
// Get prompt by tag/label
async getByTag(modelId: string, tag?: string, label?: string, context?: SystemPromptContext, isNextGenModelFamily?: boolean): Promise<string> {
// Supports tag and label-based retrieval with next-gen prioritization
}
}
The PromptVariant interface defines the configuration for each model-specific prompt variant:
interface PromptVariant {
id: string; // Model family ID (e.g., "next-gen", "generic")
version: number; // Version number
family: ModelFamily; // Model family enum
tags: string[]; // ["production", "beta", "experimental"]
labels: { [key: string]: number }; // {"staging": 2, "prod": 1}
description: string; // Brief description of the variant
// Prompt configuration
config: PromptConfig; // Model-specific config
baseTemplate: string; // Main prompt template with placeholders
componentOrder: SystemPromptSection[]; // Ordered list of components to include
componentOverrides: { [K in SystemPromptSection]?: ConfigOverride }; // Component customizations
placeholders: { [key: string]: string }; // Default placeholder values
// Tool configuration
tools?: ClineDefaultTool[]; // Ordered list of tools to include
toolOverrides?: { [K in ClineDefaultTool]?: ConfigOverride }; // Tool-specific customizations
}
interface PromptConfig {
modelName?: string;
temperature?: number;
maxTokens?: number;
tools?: ClineToolSpec[];
[key: string]: any; // Additional arbitrary config
}
interface ConfigOverride {
template?: string; // Custom template for the component/tool
enabled?: boolean; // Whether the component/tool is enabled
order?: number; // Override the order
}
The PromptBuilder orchestrates the construction of the final prompt by combining templates, components, and placeholders:
class PromptBuilder {
private templateEngine: TemplateEngine;
constructor(
private variant: PromptVariant,
private context: SystemPromptContext,
private components: ComponentRegistry
) {
this.templateEngine = new TemplateEngine();
}
async build(): Promise<string> {
// 1. Build all components in specified order
const componentSections = await this.buildComponents();
// 2. Prepare all placeholder values
const placeholderValues = this.preparePlaceholders(componentSections);
// 3. Resolve template placeholders
const prompt = this.templateEngine.resolve(this.variant.baseTemplate, placeholderValues);
// 4. Apply final post-processing
return this.postProcess(prompt);
}
private async buildComponents(): Promise<Record<string, string>> {
const sections: Record<string, string> = {};
// Process components sequentially to maintain order
for (const componentId of this.variant.componentOrder) {
const componentFn = this.components[componentId];
if (!componentFn) {
console.warn(`Warning: Component '${componentId}' not found`);
continue;
}
try {
const result = await componentFn(this.variant, this.context);
if (result?.trim()) {
sections[componentId] = result;
}
} catch (error) {
console.warn(`Warning: Failed to build component '${componentId}':`, error);
}
}
return sections;
}
private preparePlaceholders(componentSections: Record<string, string>): Record<string, unknown> {
const placeholders: Record<string, unknown> = {};
// Add variant placeholders
Object.assign(placeholders, this.variant.placeholders);
// Add standard system placeholders
placeholders[STANDARD_PLACEHOLDERS.CWD] = this.context.cwd || process.cwd();
placeholders[STANDARD_PLACEHOLDERS.SUPPORTS_BROWSER] = this.context.supportsBrowserUse || false;
placeholders[STANDARD_PLACEHOLDERS.MODEL_FAMILY] = this.variant.family;
placeholders[STANDARD_PLACEHOLDERS.CURRENT_DATE] = new Date().toISOString().split("T")[0];
// Add all component sections
Object.assign(placeholders, componentSections);
// Add runtime placeholders with highest priority
const runtimePlaceholders = (this.context as any).runtimePlaceholders;
if (runtimePlaceholders) {
Object.assign(placeholders, runtimePlaceholders);
}
return placeholders;
}
private postProcess(prompt: string): string {
if (!prompt) return "";
// Combine multiple regex operations for better performance
return prompt
.replace(/\n\s*\n\s*\n/g, "\n\n") // Remove multiple consecutive empty lines
.trim() // Remove leading/trailing whitespace
.replace(/====+\s*$/, "") // Remove trailing ==== after trim
.replace(/\n====+\s*\n+\s*====+\n/g, "\n====\n") // Remove empty sections between separators
.replace(/====\n([^\n])/g, "====\n\n$1") // Ensure proper section separation
.replace(/([^\n])\n====/g, "$1\n\n====");
}
}
The template system uses {{PLACEHOLDER}} syntax for dynamic content injection:
class TemplateEngine {
resolve(template: string, placeholders: Record<string, unknown>): string {
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const trimmedKey = key.trim();
// Support nested object access using dot notation
const value = this.getNestedValue(placeholders, trimmedKey);
if (value !== undefined && value !== null) {
return typeof value === "string" ? value : JSON.stringify(value);
}
// Keep placeholder if not found (allows for partial resolution)
return match;
});
}
extractPlaceholders(template: string): string[] {
const placeholders: string[] = [];
const regex = /\{\{([^}]+)\}\}/g;
let match: RegExpExecArray | null = null;
match = regex.exec(template);
while (match !== null) {
const placeholder = match[1].trim();
if (!placeholders.includes(placeholder)) {
placeholders.push(placeholder);
}
match = regex.exec(template);
}
return placeholders;
}
}
Base Template Example:
You are Cline, a highly skilled software engineer...
====
{{TOOL_USE_SECTION}}
====
{{MCP_SECTION}}
====
{{USER_INSTRUCTIONS_SECTION}}
====
{{SYSTEM_INFO_SECTION}}
====
{{TODO_SECTION}}
Components are reusable functions that generate specific sections of the prompt:
type ComponentFunction = (
variant: PromptVariant,
context: SystemPromptContext
) => Promise<string | undefined>;
// Example component
export async function getSystemInfo(
variant: PromptVariant,
context: SystemPromptContext,
): Promise<string> {
const info = await getSystemEnv();
// Support component overrides
const template = variant.componentOverrides?.SYSTEM_INFO_SECTION?.template || `
Operating System: {{os}}
Default Shell: {{shell}}
Home Directory: {{homeDir}}
Current Working Directory: {{workingDir}}
`;
return new TemplateEngine().resolve(template, {
os: info.os,
shell: info.shell,
homeDir: info.homeDir,
workingDir: info.workingDir
});
}
Tools are managed through the ClineToolSet and can be configured per variant:
class ClineToolSet {
private static variants: Map<ModelFamily, Set<ClineToolSet>> = new Map();
static register(config: ClineToolSpec): ClineToolSet {
return new ClineToolSet(config.id, config);
}
static getTools(variant: ModelFamily): ClineToolSet[] {
const toolsSet = ClineToolSet.variants.get(variant) || new Set();
const defaultSet = ClineToolSet.variants.get(ModelFamily.GENERIC) || new Set();
return toolsSet ? Array.from(toolsSet) : Array.from(defaultSet);
}
}
// Tool generation in PromptBuilder
public static async getToolsPrompts(variant: PromptVariant, context: SystemPromptContext) {
const tools = ClineToolSet.getTools(variant.family);
// Filter and sort tools based on variant configuration
const enabledTools = tools.filter((tool) =>
!tool.config.contextRequirements || tool.config.contextRequirements(context)
);
let sortedEnabledTools = enabledTools;
if (variant?.tools?.length) {
const toolOrderMap = new Map(variant.tools.map((id, index) => [id, index]));
sortedEnabledTools = enabledTools.sort((a, b) => {
const orderA = toolOrderMap.get(a.config.id);
const orderB = toolOrderMap.get(b.config.id);
if (orderA !== undefined && orderB !== undefined) {
return orderA - orderB;
}
if (orderA !== undefined) return -1;
if (orderB !== undefined) return 1;
return a.config.id.localeCompare(b.config.id);
});
}
const ids = sortedEnabledTools.map((tool) => tool.config.id);
return Promise.all(sortedEnabledTools.map((tool) => PromptBuilder.tool(tool.config, ids)));
}
// variants/generic/config.ts
import { ModelFamily } from "@/shared/prompts";
import { ClineDefaultTool } from "@/shared/tools";
import { SystemPromptSection } from "../../templates/placeholders";
import { validateVariant } from "../../validation/VariantValidator";
import { createVariant } from "../builder";
import { baseTemplate } from "./template";
// Type-safe variant configuration using the builder pattern
export const config = createVariant(ModelFamily.GENERIC)
.description("The fallback prompt for generic use cases and models.")
.version(1)
.tags("fallback", "stable")
.labels({
stable: 1,
fallback: 1,
})
.template(baseTemplate)
.components(
SystemPromptSection.AGENT_ROLE,
SystemPromptSection.TOOL_USE,
SystemPromptSection.MCP,
SystemPromptSection.EDITING_FILES,
SystemPromptSection.ACT_VS_PLAN,
SystemPromptSection.TODO,
SystemPromptSection.CAPABILITIES,
SystemPromptSection.RULES,
SystemPromptSection.SYSTEM_INFO,
SystemPromptSection.OBJECTIVE,
SystemPromptSection.USER_INSTRUCTIONS,
)
.tools(
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
ClineDefaultTool.FILE_NEW,
ClineDefaultTool.FILE_EDIT,
ClineDefaultTool.SEARCH,
ClineDefaultTool.LIST_FILES,
ClineDefaultTool.LIST_CODE_DEF,
ClineDefaultTool.BROWSER,
ClineDefaultTool.MCP_USE,
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
)
.placeholders({
MODEL_FAMILY: "generic",
})
.config({})
.build();
// Compile-time validation
const validationResult = validateVariant({ ...config, id: "generic" }, { strict: true });
if (!validationResult.isValid) {
console.error("Generic variant configuration validation failed:", validationResult.errors);
throw new Error(`Invalid generic variant configuration: ${validationResult.errors.join(", ")}`);
}
// Export type information for better IDE support
export type GenericVariantConfig = typeof config;
// variants/next-gen/config.ts
import { ModelFamily } from "@/shared/prompts";
import { ClineDefaultTool } from "@/shared/tools";
import { SystemPromptSection } from "../../templates/placeholders";
import { validateVariant } from "../../validation/VariantValidator";
import { createVariant } from "../builder";
import { baseTemplate, rules_template } from "./template";
// Type-safe variant configuration using the builder pattern
export const config = createVariant(ModelFamily.NEXT_GEN)
.description("Prompt tailored to newer frontier models with smarter agentic capabilities.")
.version(1)
.tags("next-gen", "advanced", "production")
.labels({
stable: 1,
production: 1,
advanced: 1,
})
.template(baseTemplate)
.components(
SystemPromptSection.AGENT_ROLE,
SystemPromptSection.TOOL_USE,
SystemPromptSection.MCP,
SystemPromptSection.EDITING_FILES,
SystemPromptSection.ACT_VS_PLAN,
SystemPromptSection.TODO,
SystemPromptSection.CAPABILITIES,
SystemPromptSection.FEEDBACK, // Additional component for next-gen
SystemPromptSection.RULES,
SystemPromptSection.SYSTEM_INFO,
SystemPromptSection.OBJECTIVE,
SystemPromptSection.USER_INSTRUCTIONS,
)
.tools(
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
ClineDefaultTool.FILE_NEW,
ClineDefaultTool.FILE_EDIT,
ClineDefaultTool.SEARCH,
ClineDefaultTool.LIST_FILES,
ClineDefaultTool.LIST_CODE_DEF,
ClineDefaultTool.BROWSER,
ClineDefaultTool.WEB_FETCH, // Additional tool for next-gen
ClineDefaultTool.MCP_USE,
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
)
.placeholders({
MODEL_FAMILY: ModelFamily.NEXT_GEN,
})
.config({})
// Override the RULES component with custom template
.overrideComponent(SystemPromptSection.RULES, {
template: rules_template,
})
.build();
// Compile-time validation
const validationResult = validateVariant({ ...config, id: "next-gen" }, { strict: true });
if (!validationResult.isValid) {
console.error("Next-gen variant configuration validation failed:", validationResult.errors);
throw new Error(`Invalid next-gen variant configuration: ${validationResult.errors.join(", ")}`);
}
// Export type information for better IDE support
export type NextGenVariantConfig = typeof config;
// variants/xs/config.ts
import { ModelFamily } from "@/shared/prompts";
import { ClineDefaultTool } from "@/shared/tools";
import { SystemPromptSection } from "../../templates/placeholders";
import { validateVariant } from "../../validation/VariantValidator";
import { createVariant } from "../builder";
import { xsComponentOverrides } from "./overrides";
import { baseTemplate } from "./template";
// Type-safe variant configuration using the builder pattern
export const config = createVariant(ModelFamily.XS)
.description("Prompt for models with a small context window.")
.version(1)
.tags("local", "xs", "compact")
.labels({
stable: 1,
production: 1,
advanced: 1,
})
.template(baseTemplate)
.components(
SystemPromptSection.AGENT_ROLE,
SystemPromptSection.RULES,
SystemPromptSection.ACT_VS_PLAN,
SystemPromptSection.CAPABILITIES,
SystemPromptSection.EDITING_FILES,
SystemPromptSection.OBJECTIVE,
SystemPromptSection.SYSTEM_INFO,
SystemPromptSection.USER_INSTRUCTIONS,
)
.tools(
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
ClineDefaultTool.FILE_NEW,
ClineDefaultTool.FILE_EDIT,
ClineDefaultTool.SEARCH,
ClineDefaultTool.LIST_FILES,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_USE,
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.MCP_DOCS,
)
.placeholders({
MODEL_FAMILY: ModelFamily.XS,
})
.config({})
.build();
// Apply component overrides after building the base configuration
// This is necessary because the builder pattern doesn't support bulk overrides
Object.assign(config.componentOverrides, xsComponentOverrides);
// Compile-time validation
const validationResult = validateVariant({ ...config, id: "xs" }, { strict: true });
if (!validationResult.isValid) {
console.error("XS variant configuration validation failed:", validationResult.errors);
throw new Error(`Invalid XS variant configuration: ${validationResult.errors.join(", ")}`);
}
// Export type information for better IDE support
export type XsVariantConfig = typeof config;
The VariantBuilder class provides a fluent, type-safe API for creating variant configurations:
import { createVariant } from "../VariantBuilder";
const config = createVariant(ModelFamily.GENERIC)
.description("Brief description of this variant") // Required
.version(1) // Required, defaults to 1
.tags("tag1", "tag2", "tag3") // Optional, can be chained
.labels({ stable: 1, production: 1 }) // Optional
.template(baseTemplate) // Required
.components( // Required, type-safe component selection
SystemPromptSection.AGENT_ROLE,
SystemPromptSection.TOOL_USE,
// ... more components
)
.tools( // Optional, type-safe tool selection
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
// ... more tools
)
.placeholders({ // Optional
MODEL_FAMILY: "generic",
CUSTOM_PLACEHOLDER: "value",
})
.config({ // Optional, model-specific config
temperature: 0.7,
maxTokens: 4096,
})
.overrideComponent(SystemPromptSection.RULES, { // Optional, component overrides
template: customRulesTemplate,
})
.overrideTool(ClineDefaultTool.BASH, { // Optional, tool overrides
enabled: false,
})
.build(); // Returns Omit<PromptVariant, "id">
// Initialize registry (done once at startup)
const registry = PromptRegistry.getInstance();
await registry.load();
// Get prompt for specific model
const prompt = await registry.get("claude-3-5-sonnet-20241022", context);
// Get prompt for next-gen model (automatically detects model family)
const prompt = await registry.get("claude-4-20250101", context);
// Get specific version
const prompt = await registry.getVersion("next-gen", 2, context);
// Get by tag/label with next-gen prioritization
const prompt = await registry.getByTag("claude-4", "production", undefined, context, true);
// Get by label
const prompt = await registry.getByTag("generic", undefined, "stable", context);
// Add runtime placeholders to context
context.runtimePlaceholders = {
"USER_NAME": "John",
"PROJECT_TYPE": "React",
"CUSTOM_INSTRUCTION": "Focus on TypeScript best practices"
};
const prompt = await registry.get("next-gen", context);
The system automatically detects model families based on model IDs:
function getModelFamily(modelId: string): ModelFamily {
// Check for next-gen models first
if (isNextGenModel(modelId)) {
return ModelFamily.NEXT_GEN;
}
if (modelId.includes("qwen")) {
return ModelFamily.XS;
}
// Default fallback
return ModelFamily.GENERIC;
}
function isNextGenModel(modelId: string): boolean {
return (
isClaude4ModelFamily(mockApiHandlerModel) ||
isGemini2dot5ModelFamily(mockApiHandlerModel) ||
isGrok4ModelFamily(mockApiHandlerModel) ||
isGPT5ModelFamily(mockApiHandlerModel)
);
}
The system includes the following built-in components:
AGENT_ROLE_SECTION: Agent identity and role definitionTOOL_USE_SECTION: Tool usage instructions and available toolsMCP_SECTION: MCP server information and capabilitiesEDITING_FILES_SECTION: File editing guidelines and best practicesACT_VS_PLAN_SECTION: Action vs planning mode instructionsTODO_SECTION: Todo management and task trackingCAPABILITIES_SECTION: Agent capabilities and limitationsFEEDBACK_SECTION: Feedback and improvement instructions (next-gen only)RULES_SECTION: Behavioral rules and constraintsSYSTEM_INFO_SECTION: System environment informationOBJECTIVE_SECTION: Current task objectiveUSER_INSTRUCTIONS_SECTION: User-provided custom instructionsThe system supports the following tools (mapped to ClineDefaultTool enum):
BASH: Execute shell commandsFILE_READ: Read file contentsFILE_NEW: Create new filesFILE_EDIT: Edit existing filesSEARCH: Search through filesLIST_FILES: List directory contentsLIST_CODE_DEF: List code definitionsBROWSER: Browser automation (conditional)WEB_FETCH: Web content fetching (next-gen only)MCP_USE: Use MCP toolsMCP_ACCESS: Access MCP resourcesASK: Ask follow-up questionsATTEMPT: Attempt task completionNEW_TASK: Create new tasksPLAN_MODE: Plan mode responsesMCP_DOCS: Load MCP documentationTODO: Todo managementEach tool in Cline follows a specific structure with variants for different model families. Here's the anatomy of a tool:
// src/core/prompts/system-prompt/tools/my_new_tool.ts
import { ModelFamily } from "@/shared/prompts"
import { ClineDefaultTool } from "@/shared/tools"
import type { ClineToolSpec } from "../spec"
const id = ClineDefaultTool.MY_NEW_TOOL // Add to enum first
const generic: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id,
name: "my_new_tool",
description: "Description of what this tool does and when to use it",
parameters: [
{
name: "required_param",
required: true,
instruction: "Description of this parameter and how to use it",
usage: "Example value or placeholder text",
},
{
name: "optional_param",
required: false,
instruction: "Description of optional parameter",
usage: "Optional example (optional)",
dependencies: [ClineDefaultTool.SOME_OTHER_TOOL], // Only show if dependency exists
},
],
}
// Create variants for different model families if needed
const nextGen = { ...generic, variant: ModelFamily.NEXT_GEN }
const gpt = { ...generic, variant: ModelFamily.GPT }
const gemini = { ...generic, variant: ModelFamily.GEMINI }
export const my_new_tool_variants = [generic, nextGen, gpt, gemini]
First, add your tool ID to the ClineDefaultTool enum:
// src/shared/tools.ts
export enum ClineDefaultTool {
// ... existing tools
MY_NEW_TOOL = "my_new_tool",
}
Create a new file in src/core/prompts/system-prompt/tools/ following the naming convention {tool_name}.ts:
// src/core/prompts/system-prompt/tools/my_new_tool.ts
import { ModelFamily } from "@/shared/prompts"
import { ClineDefaultTool } from "@/shared/tools"
import type { ClineToolSpec } from "../spec"
const id = ClineDefaultTool.MY_NEW_TOOL
const generic: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id,
name: "my_new_tool",
description: "Comprehensive description of the tool's purpose, when to use it, and what it accomplishes. Be specific about use cases and limitations.",
parameters: [
{
name: "input_parameter",
required: true,
instruction: "Clear instruction on what this parameter expects and how to format it",
usage: "Example input here",
},
{
name: "options",
required: false,
instruction: "Optional configuration or settings for the tool",
usage: "Configuration options (optional)",
},
],
}
// Export variants array - this is crucial for registration
export const my_new_tool_variants = [generic]
Add your tool export to the tools index file:
// src/core/prompts/system-prompt/tools/index.ts
export * from "./my_new_tool"
Add your tool to the registration function:
// src/core/prompts/system-prompt/tools/init.ts
import { my_new_tool_variants } from "./my_new_tool"
export function registerClineToolSets(): void {
const allToolVariants = [
// ... existing tool variants
...my_new_tool_variants,
]
allToolVariants.forEach((v) => {
ClineToolSet.register(v)
})
}
Create the actual tool implementation in the appropriate handler:
// In your tool handler class (e.g., ClineProvider)
async handleMyNewTool(args: { input_parameter: string; options?: string }) {
// Implement your tool logic here
const result = await performToolOperation(args.input_parameter, args.options)
return {
type: "tool_result" as const,
content: result,
}
}
Tools can be conditionally enabled based on context:
const contextAwareTool: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id: ClineDefaultTool.CONTEXT_TOOL,
name: "context_tool",
description: "Tool that only appears in certain contexts",
contextRequirements: (context: SystemPromptContext) => {
// Only show this tool if browser support is available
return context.supportsBrowserUse === true
},
parameters: [
// ... parameters
],
}
Create different tool behaviors for different model families:
const claude: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id: ClineDefaultTool.MODEL_SPECIFIC_TOOL,
name: "model_specific_tool",
description: "Tool optimized for Claude models with detailed instructions",
parameters: [
{
name: "detailed_input",
required: true,
instruction: "Provide comprehensive details as Claude handles complex instructions well",
usage: "Detailed input with context and examples",
},
],
}
const gpt: ClineToolSpec = {
...claude,
variant: ModelFamily.GPT,
description: "Tool optimized for GPT models with concise instructions",
parameters: [
{
name: "detailed_input",
required: true,
instruction: "Provide concise, structured input",
usage: "Brief, structured input",
},
],
}
export const model_specific_tool_variants = [claude, gpt]
Tools can have parameters that only appear when other tools are available:
const dependentTool: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id: ClineDefaultTool.DEPENDENT_TOOL,
name: "dependent_tool",
description: "Tool with conditional parameters",
parameters: [
{
name: "always_present",
required: true,
instruction: "This parameter is always available",
usage: "Standard input",
},
{
name: "conditional_param",
required: false,
instruction: "This parameter only appears if TODO tool is available",
usage: "Conditional input (optional)",
dependencies: [ClineDefaultTool.TODO],
},
],
}
create_file, search_code)Create unit tests for your tool specification:
// src/core/prompts/system-prompt/tools/__tests__/my_new_tool.test.ts
import { my_new_tool_variants } from "../my_new_tool"
import { ModelFamily } from "@/shared/prompts"
describe("my_new_tool", () => {
it("should have correct structure", () => {
const generic = my_new_tool_variants.find(v => v.variant === ModelFamily.GENERIC)
expect(generic).toBeDefined()
expect(generic?.name).toBe("my_new_tool")
expect(generic?.parameters).toHaveLength(2)
})
})
Add your tool to the integration test suite:
// src/core/prompts/system-prompt/__tests__/integration.test.ts
// The test will automatically pick up your tool if properly registered
npm run test:unitHere's a complete example of adding a new "analyze_file" tool:
// 1. Add to src/shared/tools.ts
export enum ClineDefaultTool {
// ... existing tools
ANALYZE_FILE = "analyze_file",
}
// 2. Create src/core/prompts/system-prompt/tools/analyze_file.ts
import { ModelFamily } from "@/shared/prompts"
import { ClineDefaultTool } from "@/shared/tools"
import type { ClineToolSpec } from "../spec"
const id = ClineDefaultTool.ANALYZE_FILE
const generic: ClineToolSpec = {
variant: ModelFamily.GENERIC,
id,
name: "analyze_file",
description: "Analyze a file's structure, dependencies, and potential issues. Use this when you need to understand a file's architecture, identify problems, or assess code quality before making changes.",
parameters: [
{
name: "file_path",
required: true,
instruction: "The path to the file you want to analyze (relative to current working directory)",
usage: "src/components/MyComponent.tsx",
},
{
name: "analysis_type",
required: false,
instruction: "Type of analysis to perform: 'structure', 'dependencies', 'quality', or 'all'",
usage: "all (optional)",
},
{
name: "include_suggestions",
required: false,
instruction: "Whether to include improvement suggestions in the analysis",
usage: "true (optional)",
},
],
}
const nextGen: ClineToolSpec = {
...generic,
variant: ModelFamily.NEXT_GEN,
description: "Perform comprehensive file analysis including structure, dependencies, code quality, and improvement suggestions. Ideal for code review and refactoring planning.",
}
export const analyze_file_variants = [generic, nextGen]
// 3. Add to src/core/prompts/system-prompt/tools/index.ts
export * from "./analyze_file"
// 4. Add to src/core/prompts/system-prompt/tools/init.ts
import { analyze_file_variants } from "./analyze_file"
export function registerClineToolSets(): void {
const allToolVariants = [
// ... existing variants
...analyze_file_variants,
]
// ... rest of function
}
This comprehensive guide should help developers understand both the architecture and practical steps needed to extend Cline with new tools.
{{placeholder}} support with runtime resolution