Skip to content

Commit 50e89dc

Browse files
committed
Unified programmatic agent #1: Remove ProgrammaticAgentTemplate. Add handeStep field.
1 parent a749e36 commit 50e89dc

23 files changed

+134
-216
lines changed

backend/src/run-agent-step.ts

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ import { asyncAgentManager } from './async-agent-manager'
2323
import { getFileReadingUpdates } from './get-file-reading-updates'
2424
import { checkLiveUserInput } from './live-user-inputs'
2525
import { getAgentStreamFromTemplate } from './prompt-agent-stream'
26-
import { runProgrammaticAgent } from './run-programmatic-agent'
26+
import { runProgrammaticStep } from './run-programmatic-step'
2727
import { additionalSystemPrompts } from './system-prompt/prompts'
2828
import { saveAgentRequest } from './system-prompt/save-agent-request'
2929
import { agentTemplates } from './templates/agent-list'
3030
import { processAgentOverrides } from './templates/agent-overrides'
3131
import { agentRegistry } from './templates/agent-registry'
3232
import { formatPrompt, getAgentPrompt } from './templates/strings'
33-
import { AgentTemplateUnion } from './templates/types'
33+
import { AgentTemplate } from './templates/types'
3434
import { processStreamWithTools } from './tools/stream-parser'
3535
import { logger } from './util/logger'
3636
import {
@@ -70,7 +70,7 @@ export interface AgentOptions {
7070
async function getAgentTemplateWithOverrides(
7171
agentType: AgentTemplateType,
7272
fileContext: ProjectFileContext
73-
): Promise<AgentTemplateUnion> {
73+
): Promise<AgentTemplate> {
7474
// Initialize registry if needed
7575
await agentRegistry.initialize(fileContext)
7676

@@ -82,11 +82,6 @@ async function getAgentTemplateWithOverrides(
8282
)
8383
}
8484

85-
if (baseTemplate.implementation === 'programmatic') {
86-
// Programmatic agents cannot be overridden.
87-
return baseTemplate
88-
}
89-
9085
return processAgentOverrides(baseTemplate, fileContext)
9186
}
9287

@@ -106,12 +101,12 @@ export const runAgentStep = async (
106101
onResponseChunk,
107102
fileContext,
108103
agentType,
109-
agentState,
110104
prompt,
111105
params,
112106
assistantMessage,
113107
assistantPrefix,
114108
} = options
109+
let agentState = options.agentState
115110

116111
const { agentContext } = agentState
117112

@@ -131,16 +126,13 @@ export const runAgentStep = async (
131126
)
132127
}
133128

134-
if (agentTemplate.implementation === 'programmatic') {
135-
const agentState = await runProgrammaticAgent(agentTemplate, {
129+
const { handleStep } = agentTemplate
130+
if (handleStep) {
131+
agentState = await runProgrammaticStep(agentState, {
136132
...options,
137133
ws,
134+
template: agentTemplate,
138135
})
139-
return {
140-
agentState,
141-
shouldEndTurn: true,
142-
fullResponse: '',
143-
}
144136
}
145137

146138
const { model } = agentTemplate
@@ -569,15 +561,7 @@ export const loopAgentSteps = async (
569561
initialAssistantPrefix,
570562
stepAssistantMessage,
571563
stepAssistantPrefix,
572-
} =
573-
agentTemplate.implementation === 'llm'
574-
? agentTemplate
575-
: {
576-
initialAssistantMessage: undefined,
577-
initialAssistantPrefix: undefined,
578-
stepAssistantMessage: undefined,
579-
stepAssistantPrefix: undefined,
580-
}
564+
} = agentTemplate
581565
let isFirstStep = true
582566
let currentPrompt = prompt
583567
let currentParams = params

backend/src/run-programmatic-agent.ts renamed to backend/src/run-programmatic-step.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import {
22
AgentState,
33
AgentTemplateType,
4+
ToolResult,
45
} from '@codebuff/common/types/session-state'
56
import { ProjectFileContext } from '@codebuff/common/util/file'
67
import { WebSocket } from 'ws'
78
import { runTool } from './run-tool'
8-
import {
9-
ProgrammaticAgentContext,
10-
ProgrammaticAgentTemplate,
11-
} from './templates/types'
9+
import { AgentTemplate, StepGenerator } from './templates/types'
1210
import { CodebuffToolCall } from './tools/constants'
1311
import { logger } from './util/logger'
1412
import { getRequestContext } from './websockets/request-context'
1513

14+
// Maintains generator state for all agents. Generator state can't be serialized, so we store it in memory.
15+
const agentIdToGenerator: Record<string, StepGenerator | undefined> = {}
16+
1617
// Function to handle programmatic agents
17-
export async function runProgrammaticAgent(
18-
template: ProgrammaticAgentTemplate,
19-
options: {
18+
export async function runProgrammaticStep(
19+
agentState: AgentState,
20+
params: {
21+
template: AgentTemplate
2022
userId: string | undefined
2123
userInputId: string
2224
clientSessionId: string
2325
fingerprintId: string
2426
onResponseChunk: (chunk: string) => void
2527
agentType: AgentTemplateType
2628
fileContext: ProjectFileContext
27-
agentState: AgentState
2829
prompt: string | undefined
2930
params: Record<string, any> | undefined
3031
assistantMessage: string | undefined
@@ -33,38 +34,55 @@ export async function runProgrammaticAgent(
3334
}
3435
): Promise<AgentState> {
3536
const {
36-
agentState,
37+
template,
3738
onResponseChunk,
3839
ws,
3940
userId,
4041
userInputId,
4142
clientSessionId,
4243
fingerprintId,
4344
fileContext,
44-
} = options
45+
} = params
4546

4647
logger.info(
4748
{
4849
template: template.id,
49-
agentType: options.agentType,
50-
prompt: options.prompt,
51-
params: options.params,
50+
agentType: params.agentType,
51+
prompt: params.prompt,
52+
params: params.params,
5253
},
53-
'Running programmatic agent'
54+
'Running programmatic step'
5455
)
55-
// Create context for the programmatic agent
56-
const context: ProgrammaticAgentContext = {
57-
prompt: options.prompt || '',
58-
params: options.params || {},
56+
57+
let generator = agentIdToGenerator[agentState.agentId]
58+
if (!generator) {
59+
if (!template.handleStep) {
60+
throw new Error('No step handler found for agent template ' + template.id)
61+
}
62+
generator = template.handleStep(agentState)
63+
agentIdToGenerator[agentState.agentId] = generator
5964
}
6065

66+
if (Math.random() <= 1) {
67+
throw new Error('Not implemented yet!')
68+
}
69+
70+
let toolResult: ToolResult | undefined
71+
6172
try {
62-
// Run the generator function and handle tool calls
63-
const generator = template.handler(context)
64-
let result = generator.next()
73+
do {
74+
let result = generator.next(toolResult)
75+
if (result.done) {
76+
break
77+
}
78+
if (result.value === 'STEP') {
79+
break
80+
}
81+
if (result.value === 'STEP_ALL') {
82+
break
83+
}
6584

66-
// Process tool calls yielded by the generator
67-
while (!result.done) {
85+
// Process tool calls yielded by the generator
6886
const toolCallWithoutId = result.value
6987
const toolCall = {
7088
...toolCallWithoutId,
@@ -79,7 +97,7 @@ export async function runProgrammaticAgent(
7997
const repoId = requestContext?.processedRepoId
8098

8199
// Execute the tool call using the simplified wrapper
82-
const toolResult = await runTool(toolCall, {
100+
toolResult = await runTool(toolCall, {
83101
ws,
84102
userId,
85103
userInputId,
@@ -93,13 +111,10 @@ export async function runProgrammaticAgent(
93111
agentState,
94112
})
95113

96-
// Send the tool result back to the generator
97-
result = generator.next(toolResult)
98-
99114
if (result.value && result.value.toolName === 'end_turn') {
100115
break
101116
}
102-
}
117+
} while (true)
103118

104119
logger.info(
105120
{ report: agentState.report },

backend/src/run-tool.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { searchWeb } from './llm-apis/linkup-api'
2323
import { PROFIT_MARGIN } from './llm-apis/message-cost-tracker'
2424
import { getSearchSystemPrompt } from './system-prompt/search-system-prompt'
2525
import { agentRegistry } from './templates/agent-registry'
26-
import { AgentTemplate, ProgrammaticAgentTemplate } from './templates/types'
26+
import { AgentTemplate } from './templates/types'
2727
import { ClientToolCall, CodebuffToolCall } from './tools/constants'
2828
import { logger } from './util/logger'
2929
import { renderReadFilesResult } from './util/parse-tool-call-xml'
@@ -43,7 +43,7 @@ export interface RunToolOptions {
4343
agentStepId: string
4444
fileContext: ProjectFileContext
4545
messages: CoreMessage[]
46-
agentTemplate: AgentTemplate | ProgrammaticAgentTemplate
46+
agentTemplate: AgentTemplate
4747
repoId?: string
4848
// Additional context for update_report
4949
agentState?: AgentState
@@ -681,10 +681,7 @@ export async function runToolInner(
681681
const agentTemplate = allTemplates[agentState.agentType!]
682682
let report = ''
683683

684-
if (
685-
agentTemplate.implementation === 'programmatic' ||
686-
agentTemplate.outputMode === 'report'
687-
) {
684+
if (agentTemplate.outputMode === 'report') {
688685
report = JSON.stringify(result.value.agentState.report, null, 2)
689686
} else if (agentTemplate.outputMode === 'last_message') {
690687
const { agentState } = result.value

backend/src/templates/__tests__/agent-overrides.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ describe('processAgentOverrides', () => {
2323
systemPrompt: 'Base system prompt',
2424
userInputPrompt: 'Base user input prompt',
2525
agentStepPrompt: 'Base agent step prompt',
26-
implementation: 'llm',
2726
}
2827

2928
const mockFileContext: ProjectFileContext = {

0 commit comments

Comments
 (0)