Skip to content

Commit 47c6ae4

Browse files
committed
Optional baseAgent and subagents fields for codebuff.json
1 parent b9a2db2 commit 47c6ae4

File tree

11 files changed

+106
-26
lines changed

11 files changed

+106
-26
lines changed

backend/src/main-prompt.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export const mainPrompt = async (
102102
}
103103
}
104104

105-
// Determine agent type - prioritize CLI agent selection over cost mode
105+
// Determine agent type - prioritize CLI agent selection, then config base agent, then cost mode
106106
let agentType: AgentTemplateType
107107
const { agentRegistry } = await getAllAgentTemplates({ fileContext })
108108

@@ -126,16 +126,36 @@ export const mainPrompt = async (
126126
`Using CLI-specified agent: ${agentId}`
127127
)
128128
} else {
129-
// Fall back to cost mode mapping
130-
agentType = (
131-
{
132-
ask: AgentTemplateTypes.ask,
133-
lite: AgentTemplateTypes.base_lite,
134-
normal: AgentTemplateTypes.base,
135-
max: AgentTemplateTypes.base_max,
136-
experimental: AgentTemplateTypes.base_experimental,
137-
} satisfies Record<CostMode, AgentTemplateType>
138-
)[costMode]
129+
// Check for base agent in config
130+
const configBaseAgent = fileContext.codebuffConfig?.baseAgent
131+
if (configBaseAgent) {
132+
if (!(configBaseAgent in agentRegistry)) {
133+
const availableAgents = Object.keys(agentRegistry)
134+
throw new Error(
135+
`Invalid base agent in config: "${configBaseAgent}". Available agents: ${availableAgents.join(', ')}`
136+
)
137+
}
138+
agentType = configBaseAgent
139+
logger.info(
140+
{
141+
configBaseAgent,
142+
promptParams,
143+
prompt: prompt?.slice(0, 50),
144+
},
145+
`Using config-specified base agent: ${configBaseAgent}`
146+
)
147+
} else {
148+
// Fall back to cost mode mapping
149+
agentType = (
150+
{
151+
ask: AgentTemplateTypes.ask,
152+
lite: AgentTemplateTypes.base_lite,
153+
normal: AgentTemplateTypes.base,
154+
max: AgentTemplateTypes.base_max,
155+
experimental: AgentTemplateTypes.base_experimental,
156+
} satisfies Record<CostMode, AgentTemplateType>
157+
)[costMode]
158+
}
139159
}
140160

141161
mainAgentState.agentType = agentType

backend/src/templates/agent-registry.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,20 @@ export async function getAllAgentTemplates({
3535
'claude4_gemini_thinking',
3636
'ask',
3737
]
38+
39+
const configuredBaseAgent = fileContext.codebuffConfig?.baseAgent
40+
const configuredSubagents = fileContext.codebuffConfig?.subagents
41+
3842
for (const baseType of baseAgentTypes) {
39-
if (agentRegistry[baseType]) {
43+
if (agentRegistry[baseType] || baseType === configuredBaseAgent) {
4044
const baseTemplate = agentRegistry[baseType]
41-
// Add user-defined agents to the base agent's subagents list
42-
const updatedSubagents = [
45+
46+
// Use configured subagents list if present, otherwise use the base agent's subagents list plus user-defined agents
47+
const updatedSubagents = configuredSubagents ?? [
4348
...baseTemplate.subagents,
4449
...userDefinedAgentTypes,
4550
]
51+
4652
agentRegistry[baseType] = {
4753
...baseTemplate,
4854
subagents: updatedSubagents as any[],

codebuff.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
{
22
"description": "Template configuration for this project. See https://www.codebuff.com/config for all options.",
3+
"baseAgent": "base_lite",
4+
"subagents": [
5+
"researcher",
6+
"thinker",
7+
"reviewer"
8+
],
39
"startupProcesses": [
410
{
511
"name": "drizzle",

common/src/json-config/__tests__/__snapshots__/stringify-schema.test.ts.snap

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,16 @@ exports[`stringifySchema should correctly stringify CodebuffConfigSchema 1`] = `
7777
] | undefined,
7878
7979
// (optional): Maximum number of turns agent will take before being forced to end, default: 12
80-
"maxAgentSteps": number
80+
"maxAgentSteps": number,
81+
82+
// (optional): Specify default base agent
83+
"baseAgent": string | undefined,
84+
85+
// (optional): Specify complete list of subagents for the base agent
86+
"subagents": [
87+
88+
string
89+
] | undefined
8190
}"
8291
`;
8392

common/src/json-config/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ export const CodebuffConfigSchema = z
8484
.describe(
8585
'Maximum number of turns agent will take before being forced to end'
8686
),
87+
baseAgent: z
88+
.string()
89+
.optional()
90+
.describe('Specify default base agent'),
91+
subagents: z
92+
.array(z.string())
93+
.optional()
94+
.describe(
95+
'Specify complete list of subagents for the base agent'
96+
),
8797
})
8898
.describe(
8999
`Defines the overall Codebuff configuration file (e.g., ${codebuffConfigFile}). This schema defines the top-level structure of the configuration. This schema can be found at https://www.codebuff.com/config`

common/src/json-config/default.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ export function getDefaultConfig(): CodebuffConfig {
66
startupProcesses: [],
77
fileChangeHooks: [],
88
maxAgentSteps: 12,
9+
baseAgent: undefined,
10+
subagents: undefined,
911
}
1012
}

common/src/util/file.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as os from 'os'
33
import * as path from 'path'
44
import { z } from 'zod'
55
import { DynamicAgentTemplateSchema } from '../types/dynamic-agent-template'
6+
import { CodebuffConfigSchema } from '../json-config/constants'
67

78
export const FileTreeNodeSchema: z.ZodType<FileTreeNode> = z.object({
89
name: z.string(),
@@ -47,6 +48,7 @@ export const ProjectFileContextSchema = z.object({
4748
knowledgeFiles: z.record(z.string(), z.string()),
4849
userKnowledgeFiles: z.record(z.string(), z.string()).optional(),
4950
agentTemplates: z.record(z.string(), DynamicAgentTemplateSchema).default({}),
51+
codebuffConfig: CodebuffConfigSchema.optional(),
5052
gitChanges: z.object({
5153
status: z.string(),
5254
diff: z.string(),
@@ -92,6 +94,7 @@ export const getStubProjectFileContext = (): ProjectFileContext => ({
9294
knowledgeFiles: {},
9395
userKnowledgeFiles: {},
9496
agentTemplates: {},
97+
codebuffConfig: undefined,
9598
gitChanges: {
9699
status: '',
97100
diff: '',

npm-app/src/agents/load-agents.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as fs from 'fs'
88
import * as path from 'path'
99
import { cyan, green } from 'picocolors'
1010
import { getProjectRoot } from '../project-files'
11+
import { CodebuffConfig } from '@codebuff/common/json-config/constants'
1112

1213
export let loadedAgents: Record<string, DynamicAgentTemplate> = {}
1314

@@ -66,7 +67,9 @@ export async function loadLocalAgents({
6667
loadedAgents[file.slice(0, -'.ts'.length)] = {
6768
...typedAgentConfig,
6869
systemPrompt: loadFileContents(typedAgentConfig.systemPrompt),
69-
instructionsPrompt: loadFileContents(typedAgentConfig.instructionsPrompt),
70+
instructionsPrompt: loadFileContents(
71+
typedAgentConfig.instructionsPrompt
72+
),
7073
stepPrompt: loadFileContents(typedAgentConfig.stepPrompt),
7174

7275
handleSteps: handleStepsString,
@@ -89,15 +92,30 @@ export function getLoadedAgentNames(): Record<string, string> {
8992
/**
9093
* Display loaded agents to the user
9194
*/
92-
export function displayLoadedAgents() {
93-
if (Object.keys(loadedAgents).length === 0) {
94-
return
95+
export function displayLoadedAgents(codebuffConfig: CodebuffConfig) {
96+
const baseAgent = codebuffConfig.baseAgent
97+
if (baseAgent) {
98+
console.log(`\n${green('Configured base agent:')} ${cyan(baseAgent)}`)
99+
}
100+
101+
const subagents = codebuffConfig.subagents
102+
if (subagents) {
103+
console.log(
104+
`${green('Configured subagents:')} ${subagents
105+
.map((name) => cyan(name))
106+
.join(', ')}\n`
107+
)
108+
} else if (Object.keys(loadedAgents).length > 0) {
109+
const loadedAgentNames = Object.values(getLoadedAgentNames())
110+
console.log(
111+
`\n${green('Found custom agents:')} ${loadedAgentNames
112+
.map((name) => cyan(name))
113+
.join(', ')}\n`
114+
)
115+
} else if (baseAgent) {
116+
// One more new line.
117+
console.log()
95118
}
96-
console.log(
97-
`\n${green('Found custom agents:')} ${Object.values(getLoadedAgentNames())
98-
.map((name) => cyan(name))
99-
.join(', ')}\n`
100-
)
101119
}
102120

103121
export function loadFileContents(promptField: PromptField | undefined): string {

npm-app/src/cli.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,6 @@ export class CLI {
832832
clearScreen()
833833

834834
// from index.ts
835-
const config = loadCodebuffConfig()
836835
await killAllBackgroundProcesses()
837836
const processStartPromise = logAndHandleStartup()
838837
const initFileContextPromise = initProjectFileContextWithWorker(

npm-app/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { CliOptions } from './types'
2626
import { initAnalytics, trackEvent } from './utils/analytics'
2727
import { findGitRoot } from './utils/git'
2828
import { logger } from './utils/logger'
29+
import { loadCodebuffConfig } from './json-config/parser'
2930

3031
async function codebuff({
3132
initialInput,
@@ -74,7 +75,8 @@ async function codebuff({
7475
print,
7576
trace,
7677
})
77-
await loadLocalAgents({ verbose: true }).then(() => displayLoadedAgents())
78+
const codebuffConfig = loadCodebuffConfig()
79+
await loadLocalAgents({ verbose: true }).then(() => displayLoadedAgents(codebuffConfig))
7880
const cli = CLI.getInstance()
7981

8082
await cli.printInitialPrompt({ initialInput, runInitFlow })

0 commit comments

Comments
 (0)