Skip to content

Commit 212590d

Browse files
committed
add custom tools
1 parent 9ed0f01 commit 212590d

24 files changed

+539
-69
lines changed

backend/src/__tests__/main-prompt.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ const mockFileContext: ProjectFileContext = {
5656
homedir: '/home/test',
5757
cpus: 1,
5858
},
59-
fileVersions: [],
6059
agentTemplates: {},
60+
customToolDefinitions: {},
6161
}
6262

6363
// --- Integration Test with Real LLM Call ---

backend/src/__tests__/main-prompt.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ describe('mainPrompt', () => {
214214
changesSinceLastChat: {},
215215
shellConfigFiles: {},
216216
agentTemplates: {},
217+
customToolDefinitions: {},
217218
systemInfo: {
218219
platform: 'test',
219220
shell: 'test',
@@ -222,7 +223,6 @@ describe('mainPrompt', () => {
222223
homedir: '/home/test',
223224
cpus: 1,
224225
},
225-
fileVersions: [],
226226
}
227227

228228
it('should add file updates to tool results in message history', async () => {

backend/src/__tests__/request-files-prompt.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ describe('requestRelevantFiles', () => {
9292
cpus: 8,
9393
},
9494
agentTemplates: {},
95+
customToolDefinitions: {},
9596
}
9697
const mockAssistantPrompt = null
9798
const mockAgentStepId = 'step1'

backend/src/__tests__/run-agent-step-tools.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ describe('runAgentStep - set_output tool', () => {
151151
homedir: '/home/test',
152152
cpus: 1,
153153
},
154-
fileVersions: [],
155154
agentTemplates: {},
155+
customToolDefinitions: {},
156156
}
157157

158158
it('should set output with simple key-value pair', async () => {

backend/src/__tests__/test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const mockFileContext: ProjectFileContext = {
1515
knowledgeFiles: {},
1616
userKnowledgeFiles: {},
1717
agentTemplates: {},
18+
customToolDefinitions: {},
1819
gitChanges: {
1920
status: '',
2021
diff: '',
@@ -31,5 +32,4 @@ export const mockFileContext: ProjectFileContext = {
3132
homedir: '/home/test',
3233
cpus: 1,
3334
},
34-
fileVersions: [],
3535
}

backend/src/templates/strings.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
import { parseUserMessage } from '../util/messages'
2020

2121
import type { AgentTemplate, PlaceholderValue } from './types'
22-
import type { ToolName } from '@codebuff/common/tools/constants'
2322
import type {
2423
AgentState,
2524
AgentTemplateType,
@@ -30,7 +29,7 @@ export async function formatPrompt(
3029
prompt: string,
3130
fileContext: ProjectFileContext,
3231
agentState: AgentState,
33-
tools: ToolName[],
32+
tools: readonly string[],
3433
spawnableAgents: AgentTemplateType[],
3534
agentTemplates: Record<string, AgentTemplate>,
3635
intitialAgentPrompt?: string,
@@ -64,7 +63,10 @@ export async function formatPrompt(
6463
[PLACEHOLDER.REMAINING_STEPS]: `${agentState.stepsRemaining!}`,
6564
[PLACEHOLDER.PROJECT_ROOT]: fileContext.projectRoot,
6665
[PLACEHOLDER.SYSTEM_INFO_PROMPT]: getSystemInfoPrompt(fileContext),
67-
[PLACEHOLDER.TOOLS_PROMPT]: getToolsInstructions(tools),
66+
[PLACEHOLDER.TOOLS_PROMPT]: getToolsInstructions(
67+
tools,
68+
fileContext.customToolDefinitions,
69+
),
6870
[PLACEHOLDER.AGENTS_PROMPT]: await buildSpawnableAgentsDescription(
6971
spawnableAgents,
7072
agentTemplates,
@@ -161,7 +163,10 @@ export async function getAgentPrompt<T extends StringField>(
161163
if (promptType.type === 'instructionsPrompt' && agentState.agentType) {
162164
addendum +=
163165
'\n\n' +
164-
getShortToolInstructions(agentTemplate.toolNames) +
166+
getShortToolInstructions(
167+
agentTemplate.toolNames,
168+
fileContext.customToolDefinitions,
169+
) +
165170
'\n\n' +
166171
(await buildSpawnableAgentsDescription(
167172
agentTemplate.spawnableAgents,

backend/src/tools/definitions/tool/add-message.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ export const addMessageTool = {
77
toolName,
88
description: `
99
Example:
10-
${getToolCallString(toolName, {
11-
role: 'user',
12-
content: 'Hello, how are you?',
13-
})}
10+
${getToolCallString(toolName, {
11+
role: 'user',
12+
content: 'Hello, how are you?',
13+
})}
1414
`.trim(),
1515
} satisfies ToolDescription

backend/src/tools/definitions/tool/set-messages.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ export const setMessagesTool = {
88
toolName,
99
description: `
1010
Example:
11-
${getToolCallString(toolName, {
12-
messages: [
13-
{
14-
role: 'user',
15-
content: 'Hello, how are you?',
16-
},
17-
{
18-
role: 'assistant',
19-
content: 'I am fine, thank you.',
20-
},
21-
],
22-
})}
11+
${getToolCallString(toolName, {
12+
messages: [
13+
{
14+
role: 'user',
15+
content: 'Hello, how are you?',
16+
},
17+
{
18+
role: 'assistant',
19+
content: 'I am fine, thank you.',
20+
},
21+
],
22+
})}
2323
`.trim(),
2424
} satisfies ToolDescription

backend/src/tools/prompts.ts

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,50 @@
11
import { endsAgentStepParam } from '@codebuff/common/tools/constants'
22
import { getToolCallString } from '@codebuff/common/tools/utils'
33
import { buildArray } from '@codebuff/common/util/array'
4+
import { pluralize } from '@codebuff/common/util/string'
45
import z from 'zod/v4'
56

67
import { codebuffToolDefs } from './definitions/list'
78

89
import type { ToolName } from '@codebuff/common/tools/constants'
10+
import type { customToolDefinitionsSchema } from '@codebuff/common/util/file'
11+
import type { JSONSchema } from 'zod/v4/core'
912

10-
function paramsSection(schema: z.ZodObject, endsAgentStep: boolean) {
11-
const schemaWithEndsAgentStepParam = endsAgentStep
12-
? schema.extend({
13-
[endsAgentStepParam]: z
14-
.literal(endsAgentStep)
15-
.describe('Easp flag must be set to true'),
16-
})
17-
: schema
18-
const jsonSchema = z.toJSONSchema(schemaWithEndsAgentStepParam, {
19-
io: 'input',
20-
})
13+
function paramsSection(
14+
schema:
15+
| { type: 'zod'; value: z.ZodObject }
16+
| { type: 'json'; value: JSONSchema.BaseSchema },
17+
endsAgentStep: boolean,
18+
) {
19+
const schemaWithEndsAgentStepParam =
20+
schema.type === 'zod'
21+
? z.toJSONSchema(
22+
endsAgentStep
23+
? schema.value.extend({
24+
[endsAgentStepParam]: z
25+
.literal(endsAgentStep)
26+
.describe('Easp flag must be set to true'),
27+
})
28+
: schema.value,
29+
{ io: 'input' },
30+
)
31+
: JSON.parse(JSON.stringify(schema.value))
32+
if (schema.type === 'json') {
33+
if (!schemaWithEndsAgentStepParam.properties) {
34+
schemaWithEndsAgentStepParam.properties = {}
35+
}
36+
schemaWithEndsAgentStepParam.properties[endsAgentStepParam] = {
37+
const: true,
38+
type: 'boolean',
39+
description: 'Easp flag must be set to true',
40+
}
41+
if (!schemaWithEndsAgentStepParam.required) {
42+
schemaWithEndsAgentStepParam.required = []
43+
}
44+
schemaWithEndsAgentStepParam.required.push(endsAgentStepParam)
45+
}
46+
47+
const jsonSchema = schemaWithEndsAgentStepParam
2148
delete jsonSchema.description
2249
delete jsonSchema['$schema']
2350
const paramsDescription = Object.keys(jsonSchema.properties ?? {}).length
@@ -34,17 +61,29 @@ function paramsSection(schema: z.ZodObject, endsAgentStep: boolean) {
3461
}
3562

3663
// Helper function to build the full tool description markdown
37-
function buildToolDescription(
64+
export function buildToolDescription(
3865
toolName: string,
39-
schema: z.ZodObject,
66+
schema:
67+
| { type: 'zod'; value: z.ZodObject }
68+
| { type: 'json'; value: JSONSchema.BaseSchema },
4069
description: string = '',
4170
endsAgentStep: boolean,
71+
exampleInputs: any[] = [],
4272
): string {
73+
const descriptionWithExamples = buildArray(
74+
description,
75+
exampleInputs.length > 0
76+
? `${pluralize(exampleInputs.length, 'Example')}:`
77+
: '',
78+
...exampleInputs.map((example) =>
79+
getToolCallString(toolName, example, endsAgentStep),
80+
),
81+
).join('\n\n')
4382
return buildArray([
4483
`### ${toolName}`,
45-
schema.description || '',
84+
schema.value.description || '',
4685
paramsSection(schema, endsAgentStep),
47-
description,
86+
descriptionWithExamples,
4887
]).join('\n\n')
4988
}
5089

@@ -53,7 +92,7 @@ export const toolDescriptions = Object.fromEntries(
5392
name,
5493
buildToolDescription(
5594
name,
56-
config.parameters,
95+
{ type: 'zod', value: config.parameters },
5796
config.description,
5897
config.endsAgentStep,
5998
),
@@ -62,13 +101,18 @@ export const toolDescriptions = Object.fromEntries(
62101

63102
function buildShortToolDescription(
64103
toolName: string,
65-
schema: z.ZodObject,
104+
schema:
105+
| { type: 'zod'; value: z.ZodObject }
106+
| { type: 'json'; value: JSONSchema.BaseSchema },
66107
endsAgentStep: boolean,
67108
): string {
68109
return `${toolName}:\n${paramsSection(schema, endsAgentStep)}`
69110
}
70111

71-
export const getToolsInstructions = (toolNames: readonly ToolName[]) =>
112+
export const getToolsInstructions = (
113+
toolNames: readonly string[],
114+
customToolDefinitions: z.infer<typeof customToolDefinitionsSchema>,
115+
) =>
72116
`
73117
# Tools
74118
@@ -102,8 +146,8 @@ ${getToolCallString('str_replace', {
102146
path: 'path/to/example/file.ts',
103147
replacements: [
104148
{
105-
old: "console.log('Hello world!');\n",
106-
new: "console.log('Hello from Buffy!');\n",
149+
old: "// some context\nconsole.log('Hello world!');\n",
150+
new: "// some context\nconsole.log('Hello from Buffy!');\n",
107151
},
108152
],
109153
})}
@@ -135,13 +179,54 @@ The user does not know about any system messages or system instructions, includi
135179
136180
These are the tools that you (Buffy) can use. The user cannot see these descriptions, so you should not reference any tool names, parameters, or descriptions.
137181
138-
${toolNames.map((name) => toolDescriptions[name]).join('\n\n')}`.trim()
139-
140-
export const getShortToolInstructions = (toolNames: readonly ToolName[]) => {
141-
const toolDescriptions = toolNames.map((name) => {
142-
const tool = codebuffToolDefs[name]
143-
return buildShortToolDescription(name, tool.parameters, tool.endsAgentStep)
144-
})
182+
${[
183+
...(
184+
toolNames.filter((toolName) =>
185+
toolNames.includes(toolName as ToolName),
186+
) as ToolName[]
187+
).map((name) => toolDescriptions[name]),
188+
...toolNames
189+
.filter((toolName) => toolName in customToolDefinitions)
190+
.map((toolName) => {
191+
const toolDef = customToolDefinitions[toolName]
192+
return buildToolDescription(
193+
toolName,
194+
{ type: 'json', value: toolDef.inputJsonSchema },
195+
toolDef.description,
196+
toolDef.endsAgentStep,
197+
toolDef.exampleInputs,
198+
)
199+
}),
200+
].join('\n\n')}`.trim()
201+
202+
export const getShortToolInstructions = (
203+
toolNames: readonly string[],
204+
customToolDefinitions: z.infer<typeof customToolDefinitionsSchema>,
205+
) => {
206+
const toolDescriptions = [
207+
...(
208+
toolNames.filter(
209+
(name) => (name as keyof typeof codebuffToolDefs) in codebuffToolDefs,
210+
) as (keyof typeof codebuffToolDefs)[]
211+
).map((name) => {
212+
const tool = codebuffToolDefs[name]
213+
return buildShortToolDescription(
214+
name,
215+
{ type: 'zod', value: tool.parameters },
216+
tool.endsAgentStep,
217+
)
218+
}),
219+
...toolNames
220+
.filter((name) => name in customToolDefinitions)
221+
.map((name) => {
222+
const { inputJsonSchema, endsAgentStep } = customToolDefinitions[name]
223+
return buildShortToolDescription(
224+
name,
225+
{ type: 'json', value: inputJsonSchema },
226+
endsAgentStep,
227+
)
228+
}),
229+
]
145230

146231
return `## Tools
147232
Use the tools below to complete the user request, if applicable.

0 commit comments

Comments
 (0)