Skip to content

Commit b2393eb

Browse files
authored
Merge pull request #3261 from mcowger/mcowger/smallToolFixes
fix(tools): improve tool calling consistency
2 parents ccf62b7 + bae048f commit b2393eb

File tree

9 files changed

+128
-39
lines changed

9 files changed

+128
-39
lines changed

.changeset/grumpy-snails-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
Improve native tool calling consistency
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
export function markdownFormattingSection(): string {
1+
import { ToolUseStyle } from "../../../../packages/types/src/kilocode/native-function-calling"
2+
3+
export function markdownFormattingSection(
4+
toolUseStyle: ToolUseStyle, // kilocode_change
5+
): string {
26
return `====
37
48
MARKDOWN RULES
59
6-
ALL responses MUST show ANY \`language construct\` OR filename reference as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>`
10+
ALL responses MUST show ANY \`language construct\` OR filename reference as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in ${toolUseStyle === "json" ? "attempt_completion" : "<attempt_completion>" /*kilocode_change*/}`
711
}

src/core/prompts/sections/modes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ ${allModes
3838

3939
// kilocode_change: toolUseStyle
4040
modesContent += `
41-
If the user asks you to create or edit a new mode for this project, you should read the instructions${toolUseStyle !== "json" ? " by using the fetch_instructions tool, like this:\n<fetch_instructions>\n<task>create_mode</task>\n</fetch_instructions>" : "."}`
41+
If the user asks you to create or edit a new mode for this project, you should read the instructions by using the fetch_instructions tool${toolUseStyle !== "json" ? ", like this:\n<fetch_instructions>\n<task>create_mode</task>\n</fetch_instructions>" : "."}`
4242

4343
return modesContent
4444
}

src/core/prompts/sections/rules.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CodeIndexManager } from "../../../services/code-index/manager"
55
import { getFastApplyEditingInstructions } from "../tools/edit-file"
66
import { type ClineProviderState } from "../../webview/ClineProvider"
77
import { getFastApplyModelType, isFastApplyAvailable } from "../../tools/editFileTool"
8+
import { ToolUseStyle } from "../../../../packages/types/src/kilocode/native-function-calling"
89
// kilocode_change end
910

1011
function getEditingInstructions(diffStrategy?: DiffStrategy): string {
@@ -57,6 +58,7 @@ export function getRulesSection(
5758
diffStrategy?: DiffStrategy,
5859
codeIndexManager?: CodeIndexManager,
5960
clineProviderState?: ClineProviderState, // kilocode_change
61+
toolUseStyle?: ToolUseStyle, // kilocode_change
6062
): string {
6163
const isCodebaseSearchAvailable =
6264
codeIndexManager &&
@@ -70,12 +72,15 @@ export function getRulesSection(
7072

7173
const kiloCodeUseMorph = isFastApplyAvailable(clineProviderState)
7274

73-
return `====
75+
let rulesContent = ""
76+
rulesContent += `====
7477
7578
RULES
7679
7780
- The project base directory is: ${cwd.toPosix()}
78-
- All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to <execute_command>.
81+
`
82+
rulesContent += `- All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to ${toolUseStyle === "json" ? '"execute_command"' : "<execute_command>" /*kilocode_change*/}.`
83+
rulesContent += `
7984
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
8085
- Do not use the ~ character or $HOME to refer to the home directory.
8186
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
@@ -106,4 +111,5 @@ ${kiloCodeUseMorph ? getFastApplyEditingInstructions(getFastApplyModelType(cline
106111
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
107112
: ""
108113
}`
114+
return rulesContent
109115
}
Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { ToolUseStyle } from "../../../../packages/types/src" // kilocode_change
22

3-
const basicInfo = `====
3+
export function getSharedToolUseSection(
4+
toolUseStyle?: ToolUseStyle, // kilocode_change
5+
): string {
6+
return `====
47
58
TOOL USE
69
7-
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
8-
`
10+
You have access to a set of tools that are executed upon the user's approval. You ${toolUseStyle === "json" ? "MUST USE" : "must use" /*kilocode_change*/} exactly one tool per message, and ${toolUseStyle === "json" ? "EVERY" : "every" /*kilocode_change*/} assistant message ${toolUseStyle === "json" ? "MUST" : "must" /*kilocode_change*/} include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.${
11+
toolUseStyle === "json" // kilocode_change
12+
? ""
13+
: `
914
10-
// kilocode_change: xml-specifix instruction split off
11-
const xmlInfo = `
1215
# Tool Use Formatting
1316
1417
Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure:
@@ -20,11 +23,5 @@ Tool uses are formatted using XML-style tags. The tool name itself becomes the X
2023
</actual_tool_name>
2124
2225
Always use the actual tool name as the XML tag name for proper parsing and execution.`
23-
24-
export function getSharedToolUseSection(toolUseStyle?: ToolUseStyle): string {
25-
if (toolUseStyle === "json") {
26-
return `${basicInfo}`
27-
} else {
28-
return `${basicInfo}${xmlInfo}`
29-
}
26+
}`
3027
}

src/core/prompts/system.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async function generatePrompt(
9999

100100
const basePrompt = `${roleDefinition}
101101
102-
${markdownFormattingSection()}
102+
${markdownFormattingSection(toolUseStyle ?? "xml" /*kilocode_change*/)}
103103
104104
${getSharedToolUseSection(toolUseStyle /*kilocode_change*/)}
105105
@@ -132,7 +132,7 @@ ${getCapabilitiesSection(cwd, supportsComputerUse, shouldIncludeMcp ? mcpHub : u
132132
133133
${modesSection}
134134
135-
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, codeIndexManager, clineProviderState /* kilocode_change */)}
135+
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, codeIndexManager, clineProviderState, toolUseStyle /* kilocode_change */)}
136136
137137
${getSystemInfoSection(cwd)}
138138
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { describe, it, expect } from "vitest"
2+
import { getAllowedJSONToolsForMode } from "../getAllowedJSONToolsForMode"
3+
import { Mode } from "../../../../../shared/modes"
4+
import { ClineProviderState } from "../../../../webview/ClineProvider"
5+
import { apply_diff_multi_file, apply_diff_single_file } from "../apply_diff"
6+
7+
describe("getAllowedJSONToolsForMode", () => {
8+
const mockCodeIndexManager = {
9+
isFeatureEnabled: true,
10+
isFeatureConfigured: true,
11+
isInitialized: true,
12+
} as any
13+
14+
const baseProviderState: Partial<ClineProviderState> = {
15+
apiConfiguration: {
16+
diffEnabled: true,
17+
},
18+
experiments: {},
19+
}
20+
21+
it("should use single file diff when multiFileApplyDiff experiment is disabled", () => {
22+
const providerState: Partial<ClineProviderState> = {
23+
...baseProviderState,
24+
apiConfiguration: {
25+
diffEnabled: true,
26+
},
27+
experiments: {
28+
multiFileApplyDiff: false,
29+
},
30+
}
31+
32+
const tools = getAllowedJSONToolsForMode(
33+
"code" as Mode,
34+
mockCodeIndexManager,
35+
providerState as ClineProviderState,
36+
true,
37+
false,
38+
)
39+
40+
const applyDiffTool = tools.find((tool) => "function" in tool && tool.function.name === "apply_diff")
41+
expect(applyDiffTool).toBeDefined()
42+
expect(applyDiffTool).toEqual(apply_diff_single_file)
43+
expect(applyDiffTool).not.toEqual(apply_diff_multi_file)
44+
})
45+
46+
it("should use multi file diff when multiFileApplyDiff experiment is enabled", () => {
47+
const providerState: Partial<ClineProviderState> = {
48+
...baseProviderState,
49+
apiConfiguration: {
50+
diffEnabled: true,
51+
},
52+
experiments: {
53+
multiFileApplyDiff: true,
54+
},
55+
}
56+
57+
const tools = getAllowedJSONToolsForMode(
58+
"code" as Mode,
59+
mockCodeIndexManager,
60+
providerState as ClineProviderState,
61+
true,
62+
false,
63+
)
64+
65+
const applyDiffTool = tools.find((tool) => "function" in tool && tool.function.name === "apply_diff")
66+
expect(applyDiffTool).toBeDefined()
67+
expect(applyDiffTool).toEqual(apply_diff_multi_file)
68+
expect(applyDiffTool).not.toEqual(apply_diff_single_file)
69+
})
70+
})

src/core/prompts/tools/native-tools/getAllowedJSONToolsForMode.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import { apply_diff_multi_file, apply_diff_single_file } from "./apply_diff"
1010

1111
export function getAllowedJSONToolsForMode(
1212
mode: Mode,
13-
codeIndexManager?: CodeIndexManager,
14-
clineProviderState?: ClineProviderState,
15-
supportsImages?: boolean,
13+
codeIndexManager: CodeIndexManager | undefined,
14+
clineProviderState: ClineProviderState | undefined,
15+
diffEnabled: boolean,
16+
supportsImages: boolean,
1617
): OpenAI.Chat.ChatCompletionTool[] {
1718
const config = getModeConfig(mode, clineProviderState?.customModes)
1819

@@ -79,27 +80,32 @@ export function getAllowedJSONToolsForMode(
7980
}
8081

8182
// Create a map of tool names to native tool definitions for quick lookup
82-
const nativeToolsMap = new Map<string, OpenAI.Chat.ChatCompletionTool>()
83-
nativeTools.forEach((tool) => {
84-
nativeToolsMap.set(tool.function.name, tool)
85-
})
83+
// Exclude apply_diff tools as they are handled specially below
84+
const allowedTools: OpenAI.Chat.ChatCompletionTool[] = []
8685

87-
if (clineProviderState?.apiConfiguration.diffEnabled) {
88-
if (clineProviderState?.experiments.multiFileApplyDiff) {
89-
nativeToolsMap.set("apply_diff", apply_diff_multi_file)
90-
} else {
91-
nativeToolsMap.set("apply_diff", apply_diff_single_file)
86+
let isApplyDiffToolAllowedForMode = false
87+
for (const nativeTool of nativeTools) {
88+
const toolName = nativeTool.function.name
89+
90+
// If the tool is in the allowed set, add it.
91+
if (tools.has(toolName)) {
92+
if (toolName === "apply_diff") {
93+
isApplyDiffToolAllowedForMode = true
94+
} else {
95+
allowedTools.push(nativeTool)
96+
}
9297
}
9398
}
9499

95-
// Map allowed tools to their native definitions
96-
const allowedTools: OpenAI.Chat.ChatCompletionTool[] = []
97-
tools.forEach((toolName) => {
98-
const nativeTool = nativeToolsMap.get(toolName)
99-
if (nativeTool) {
100-
allowedTools.push(nativeTool)
100+
// Handle the "apply_diff" logic separately because the same tool has different
101+
// implementations depending on whether multi-file diffs are enabled, but the same name is used.
102+
if (isApplyDiffToolAllowedForMode && diffEnabled) {
103+
if (clineProviderState?.experiments.multiFileApplyDiff) {
104+
allowedTools.push(apply_diff_multi_file)
105+
} else {
106+
allowedTools.push(apply_diff_single_file)
101107
}
102-
})
108+
}
103109

104110
return allowedTools
105111
}

src/core/task/Task.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2941,7 +2941,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
29412941
mode,
29422942
undefined, // codeIndexManager is private, not accessible here
29432943
providerState,
2944-
this.api?.getModel()?.info?.supportsImages,
2944+
this.diffEnabled,
2945+
this.api?.getModel()?.info?.supportsImages ?? false,
29452946
)
29462947

29472948
metadata.allowedTools = allowedTools

0 commit comments

Comments
 (0)