Compare commits

...

2 Commits

Author SHA1 Message Date
continue[bot]
9550af1fe6 docs: add tool prompt override documentation for .continuerc.json
Add documentation for the new tools array feature that allows customizing
built-in tool descriptions and behavior at the repository level.

Updates:
- Configuration guide with examples of tool overrides
- JSON reference with full property documentation and examples
- Used Mintlify components (AccordionGroup, Tabs) for better organization

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
Co-authored-by: nate <nate@continue.dev>
2025-12-25 02:13:40 +00:00
shanevcantwell
8671adf6fa feat: add tool prompt override support in .continuerc.json
Adds a `tools` array to config that allows users to override tool
descriptions at the repo level, following the existing pattern for
models, contextProviders, and slashCommands.

This enables teams to customize tool prompts (e.g., standardizing
path format instructions) without forking the codebase.

Example .continuerc.json:
```json
{
  "tools": [
    {
      "name": "read_file",
      "description": "Custom description here"
    }
  ]
}
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 19:09:08 -07:00
8 changed files with 468 additions and 5 deletions

View File

@@ -109,6 +109,7 @@ const configMergeKeys = {
},
slashCommands: (a: any, b: any) => a.name === b.name,
customCommands: (a: any, b: any) => a.name === b.name,
tools: (a: any, b: any) => a.name === b.name,
};
function loadSerializedConfig(
@@ -504,6 +505,7 @@ async function intermediateToFinalConfig({
...config,
contextProviders,
tools: getBaseToolDefinitions(),
toolOverrides: config.tools, // Pass through tool overrides from config
mcpServerStatuses: [],
slashCommands: [],
modelsByRole: {

View File

@@ -30,6 +30,7 @@ import { TeamAnalytics } from "../../control-plane/TeamAnalytics.js";
import ContinueProxy from "../../llm/llms/stubs/ContinueProxy";
import { initSlashCommand } from "../../promptFiles/initPrompt";
import { getConfigDependentToolDefinitions } from "../../tools";
import { applyToolOverrides } from "../../tools/applyToolOverrides";
import { encodeMCPToolUri } from "../../tools/callTool";
import { getMCPToolName } from "../../tools/mcpToolName";
import { GlobalContext } from "../../util/GlobalContext";
@@ -309,6 +310,18 @@ export default async function doLoadConfig(options: {
}),
);
// Apply tool overrides from config (e.g., .continuerc.json)
if (newConfig.toolOverrides?.length) {
const toolOverridesResult = applyToolOverrides(
newConfig.tools,
newConfig.toolOverrides,
);
newConfig.tools = toolOverridesResult.tools;
errors.push(...toolOverridesResult.errors);
// Clear toolOverrides after applying (not needed at runtime)
delete newConfig.toolOverrides;
}
// Detect duplicate tool names
const counts: Record<string, number> = {};
newConfig.tools.forEach((tool) => {

View File

@@ -1133,7 +1133,31 @@ declare global {
url?: string;
clientKey?: string;
}
/**
* Configuration for overriding built-in tool prompts.
* Allows customization of tool descriptions and behavior at the repo level.
*/
export interface ToolOverride {
/** Tool name to override (matches function.name, e.g., "read_file", "run_terminal_command") */
name: string;
/** Override the tool's description shown to the LLM */
description?: string;
/** Override the display title shown in UI */
displayTitle?: string;
/** Override the action phrases */
wouldLikeTo?: string;
isCurrently?: string;
hasAlready?: string;
/** Override system message description for non-native tool calling */
systemMessageDescription?: {
prefix?: string;
exampleArgs?: Array<[string, string | number]>;
};
/** Set to true to disable this tool */
disabled?: boolean;
}
// config.json
export interface SerializedContinueConfig {
env?: string[];
@@ -1156,8 +1180,10 @@ declare global {
experimental?: ExperimentalConfig;
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
/** Tool overrides for customizing built-in tool prompts at the repo level */
tools?: ToolOverride[];
}
export type ConfigMergeType = "merge" | "overwrite";
export type ContinueRcJson = Partial<SerializedContinueConfig> & {
@@ -1208,8 +1234,10 @@ declare global {
experimental?: ExperimentalConfig;
/** Analytics configuration */
analytics?: AnalyticsConfig;
/** Tool overrides for customizing built-in tool prompts */
tools?: ToolOverride[];
}
// in the actual Continue source code
export interface ContinueConfig {
allowAnonymousTelemetry?: boolean;
@@ -1231,8 +1259,10 @@ declare global {
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
tools: Tool[];
/** Tool overrides from config, applied after all tools are loaded */
toolOverrides?: ToolOverride[];
}
export interface BrowserSerializedContinueConfig {
allowAnonymousTelemetry?: boolean;
models: ModelDescription[];

30
core/index.d.ts vendored
View File

@@ -1139,6 +1139,30 @@ export interface Tool {
) => ToolPolicy;
}
/**
* Configuration for overriding built-in tool prompts.
* Allows customization of tool descriptions and behavior at the repo level.
*/
export interface ToolOverride {
/** Tool name to override (matches function.name, e.g., "read_file", "run_terminal_command") */
name: string;
/** Override the tool's description shown to the LLM */
description?: string;
/** Override the display title shown in UI */
displayTitle?: string;
/** Override the action phrases */
wouldLikeTo?: string;
isCurrently?: string;
hasAlready?: string;
/** Override system message description for non-native tool calling */
systemMessageDescription?: {
prefix?: string;
exampleArgs?: Array<[string, string | number]>;
};
/** Set to true to disable this tool */
disabled?: boolean;
}
interface ToolChoice {
type: "function";
function: {
@@ -1721,6 +1745,8 @@ export interface SerializedContinueConfig {
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
data?: DataDestination[];
/** Tool overrides for customizing built-in tool prompts at the repo level */
tools?: ToolOverride[];
}
export type ConfigMergeType = "merge" | "overwrite";
@@ -1775,6 +1801,8 @@ export interface Config {
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
data?: DataDestination[];
/** Tool overrides for customizing built-in tool prompts */
tools?: ToolOverride[];
}
// in the actual Continue source code
@@ -1794,6 +1822,8 @@ export interface ContinueConfig {
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
tools: Tool[];
/** Tool overrides from config, applied after all tools are loaded */
toolOverrides?: ToolOverride[];
mcpServerStatuses: MCPServerStatus[];
rules: RuleWithSource[];
modelsByRole: Record<ModelRole, ILLM[]>;

View File

@@ -0,0 +1,184 @@
import { Tool, ToolOverride } from "..";
import { applyToolOverrides } from "./applyToolOverrides";
const mockTool = (name: string, description: string): Tool => ({
type: "function",
displayTitle: name,
readonly: true,
group: "test",
function: { name, description },
});
describe("applyToolOverrides", () => {
it("should return tools unchanged when no overrides provided", () => {
const tools = [mockTool("read_file", "Read a file")];
const result = applyToolOverrides(tools, undefined);
expect(result.tools).toEqual(tools);
expect(result.errors).toHaveLength(0);
});
it("should return tools unchanged when empty overrides array provided", () => {
const tools = [mockTool("read_file", "Read a file")];
const result = applyToolOverrides(tools, []);
expect(result.tools).toEqual(tools);
expect(result.errors).toHaveLength(0);
});
it("should override description when specified", () => {
const tools = [mockTool("read_file", "Original description")];
const overrides: ToolOverride[] = [
{ name: "read_file", description: "New description" },
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].function.description).toBe("New description");
expect(result.errors).toHaveLength(0);
});
it("should override displayTitle when specified", () => {
const tools = [mockTool("read_file", "Read a file")];
const overrides: ToolOverride[] = [
{ name: "read_file", displayTitle: "Custom Read File" },
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].displayTitle).toBe("Custom Read File");
});
it("should override action phrases when specified", () => {
const tools = [mockTool("read_file", "Read a file")];
tools[0].wouldLikeTo = "read {{{ filepath }}}";
tools[0].isCurrently = "reading {{{ filepath }}}";
tools[0].hasAlready = "read {{{ filepath }}}";
const overrides: ToolOverride[] = [
{
name: "read_file",
wouldLikeTo: "open {{{ filepath }}}",
isCurrently: "opening {{{ filepath }}}",
hasAlready: "opened {{{ filepath }}}",
},
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].wouldLikeTo).toBe("open {{{ filepath }}}");
expect(result.tools[0].isCurrently).toBe("opening {{{ filepath }}}");
expect(result.tools[0].hasAlready).toBe("opened {{{ filepath }}}");
});
it("should disable tools when disabled: true", () => {
const tools = [
mockTool("read_file", "Read"),
mockTool("write_file", "Write"),
];
const overrides: ToolOverride[] = [{ name: "read_file", disabled: true }];
const result = applyToolOverrides(tools, overrides);
expect(result.tools).toHaveLength(1);
expect(result.tools[0].function.name).toBe("write_file");
expect(result.errors).toHaveLength(0);
});
it("should warn when override references unknown tool", () => {
const tools = [mockTool("read_file", "Read")];
const overrides: ToolOverride[] = [
{ name: "unknown_tool", description: "test" },
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools).toHaveLength(1);
expect(result.errors).toHaveLength(1);
expect(result.errors[0].message).toContain("unknown_tool");
expect(result.errors[0].fatal).toBe(false);
});
it("should preserve unmodified fields", () => {
const tools = [mockTool("read_file", "Original")];
tools[0].readonly = true;
tools[0].group = "Built-In";
const overrides: ToolOverride[] = [
{ name: "read_file", description: "New description" },
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].readonly).toBe(true);
expect(result.tools[0].group).toBe("Built-In");
expect(result.tools[0].displayTitle).toBe("read_file");
});
it("should override systemMessageDescription", () => {
const tools = [mockTool("read_file", "Read")];
tools[0].systemMessageDescription = {
prefix: "old prefix",
exampleArgs: [["filepath", "/old/path"]],
};
const overrides: ToolOverride[] = [
{
name: "read_file",
systemMessageDescription: {
prefix: "new prefix",
exampleArgs: [["filepath", "/new/path"]],
},
},
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].systemMessageDescription?.prefix).toBe("new prefix");
expect(result.tools[0].systemMessageDescription?.exampleArgs).toEqual([
["filepath", "/new/path"],
]);
});
it("should partially override systemMessageDescription", () => {
const tools = [mockTool("read_file", "Read")];
tools[0].systemMessageDescription = {
prefix: "old prefix",
exampleArgs: [["filepath", "/old/path"]],
};
const overrides: ToolOverride[] = [
{
name: "read_file",
systemMessageDescription: {
prefix: "new prefix",
// exampleArgs not specified - should preserve original
},
},
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools[0].systemMessageDescription?.prefix).toBe("new prefix");
expect(result.tools[0].systemMessageDescription?.exampleArgs).toEqual([
["filepath", "/old/path"],
]);
});
it("should apply multiple overrides", () => {
const tools = [
mockTool("read_file", "Read"),
mockTool("write_file", "Write"),
mockTool("delete_file", "Delete"),
];
const overrides: ToolOverride[] = [
{ name: "read_file", description: "Custom read" },
{ name: "write_file", disabled: true },
{ name: "delete_file", displayTitle: "Remove File" },
];
const result = applyToolOverrides(tools, overrides);
expect(result.tools).toHaveLength(2);
expect(result.tools[0].function.description).toBe("Custom read");
expect(result.tools[1].displayTitle).toBe("Remove File");
expect(result.errors).toHaveLength(0);
});
it("should not mutate original tools array", () => {
const tools = [mockTool("read_file", "Original")];
const originalDescription = tools[0].function.description;
const overrides: ToolOverride[] = [
{ name: "read_file", description: "New description" },
];
const result = applyToolOverrides(tools, overrides);
// Original should be unchanged
expect(tools[0].function.description).toBe(originalDescription);
// Result should have new description
expect(result.tools[0].function.description).toBe("New description");
});
});

View File

@@ -0,0 +1,69 @@
import { ConfigValidationError } from "@continuedev/config-yaml";
import { Tool, ToolOverride } from "..";
export interface ApplyToolOverridesResult {
tools: Tool[];
errors: ConfigValidationError[];
}
/**
* Applies tool overrides from config to the list of tools.
* Overrides can modify tool descriptions, display titles, action phrases,
* system message descriptions, or disable tools entirely.
*/
export function applyToolOverrides(
tools: Tool[],
overrides: ToolOverride[] | undefined,
): ApplyToolOverridesResult {
if (!overrides?.length) {
return { tools, errors: [] };
}
const errors: ConfigValidationError[] = [];
const toolsByName = new Map(tools.map((t) => [t.function.name, t]));
for (const override of overrides) {
const tool = toolsByName.get(override.name);
if (!tool) {
errors.push({
fatal: false,
message: `Tool override "${override.name}" does not match any known tool. Available tools: ${Array.from(toolsByName.keys()).join(", ")}`,
});
continue;
}
if (override.disabled) {
toolsByName.delete(override.name);
continue;
}
const updatedTool: Tool = {
...tool,
function: {
...tool.function,
description: override.description ?? tool.function.description,
},
displayTitle: override.displayTitle ?? tool.displayTitle,
wouldLikeTo: override.wouldLikeTo ?? tool.wouldLikeTo,
isCurrently: override.isCurrently ?? tool.isCurrently,
hasAlready: override.hasAlready ?? tool.hasAlready,
};
if (override.systemMessageDescription) {
updatedTool.systemMessageDescription = {
prefix:
override.systemMessageDescription.prefix ??
tool.systemMessageDescription?.prefix ??
"",
exampleArgs:
override.systemMessageDescription.exampleArgs ??
tool.systemMessageDescription?.exampleArgs,
};
}
toolsByName.set(override.name, updatedTool);
}
return { tools: Array.from(toolsByName.values()), errors };
}

View File

@@ -44,7 +44,7 @@ See the full reference for `config.yaml` [here](/reference).
The format of `.continuerc.json` is the same as `config.json`, plus one _additional_ property `mergeBehavior`, which can be set to either "merge" or "overwrite". If set to "merge" (the default), `.continuerc.json` will be applied on top of `config.json` (arrays and objects are merged). If set to "overwrite", then every top-level property of `.continuerc.json` will overwrite that property from `config.json`.
Example
#### Basic Example
```json title=".continuerc.json"
{
@@ -55,6 +55,61 @@ Example
}
```
#### Customizing Tool Prompts
You can customize built-in tool descriptions and behavior at the repository level using the `tools` array:
<AccordionGroup>
<Accordion title="Override tool descriptions">
```json title=".continuerc.json"
{
"mergeBehavior": "merge",
"tools": [
{
"name": "read_file",
"description": "Read file contents. Use relative paths from workspace root."
},
{
"name": "multi_edit",
"description": "Edit files. Use the same path format you used to read the file."
}
]
}
```
</Accordion>
<Accordion title="Disable specific tools">
```json title=".continuerc.json"
{
"mergeBehavior": "merge",
"tools": [
{
"name": "run_terminal_command",
"disabled": true
}
]
}
```
</Accordion>
<Accordion title="Available tool override properties">
Each tool override can include:
- `name` **(required)** - Tool name to override (e.g., `"read_file"`, `"run_terminal_command"`)
- `description` - Override the tool's description shown to the LLM
- `displayTitle` - Override the display name shown in the UI
- `wouldLikeTo` / `isCurrently` / `hasAlready` - Override action status messages
- `systemMessageDescription` - Override system message format for non-native tool calling
- `prefix` - Description prefix
- `exampleArgs` - Example arguments array
- `disabled` - Set to `true` to completely disable the tool
</Accordion>
</AccordionGroup>
<Note>
Tool overrides only work with built-in tools. They are applied when the config is loaded and merged by name.
</Note>
### How to Use `config.ts` for Advanced Configuration
`config.yaml` or `config.json` can handle the vast majority of necessary configuration, so we recommend using it whenever possible. However, if you need to programmatically extend Continue configuration, you can use a `config.ts` file, placed at `~/.continue/config.ts` (MacOS / Linux) or `%USERPROFILE%\.continue\config.ts` (Windows).

View File

@@ -463,10 +463,90 @@ config.json
}
```
## `tools`
Customize built-in tool descriptions and behavior at the repository level. This is particularly useful in `.continuerc.json` for standardizing tool usage across a team.
**Properties:**
Each tool override object can include:
- `name` (**required**): Tool name to override (e.g., `"read_file"`, `"run_terminal_command"`, `"multi_edit"`)
- `description`: Override the tool's description shown to the LLM
- `displayTitle`: Override the display name shown in the UI
- `wouldLikeTo`: Override the action phrase for pending state (e.g., "read {{{ filepath }}}")
- `isCurrently`: Override the action phrase for in-progress state
- `hasAlready`: Override the action phrase for completed state
- `systemMessageDescription`: Override system message format for non-native tool calling
- `prefix`: Description prefix
- `exampleArgs`: Array of example argument tuples
- `disabled`: Set to `true` to completely disable this tool
<Warning>
Tool overrides only apply to built-in tools. Referencing non-existent tool names will generate a non-fatal warning.
</Warning>
<Tabs>
<Tab title="Basic Override">
```json title=".continuerc.json"
{
"mergeBehavior": "merge",
"tools": [
{
"name": "read_file",
"description": "Read file contents. Always use relative paths from the workspace root."
}
]
}
```
</Tab>
<Tab title="Multiple Overrides">
```json title=".continuerc.json"
{
"mergeBehavior": "merge",
"tools": [
{
"name": "read_file",
"description": "Read file contents. Use relative paths from workspace root.",
"displayTitle": "Read File"
},
{
"name": "multi_edit",
"description": "Edit files. Use the same path format you used to read the file."
}
]
}
```
</Tab>
<Tab title="Disable Tool">
```json title=".continuerc.json"
{
"mergeBehavior": "merge",
"tools": [
{
"name": "run_terminal_command",
"disabled": true
}
]
}
```
</Tab>
</Tabs>
## `userToken`
An optional token that identifies the user, primarily for authenticated services.
Example:
```json
{
"userToken": "your-token-here"
}
```
## `systemMessage`
Defines a system message that appears before every response from the language model, providing guidance or context.