Skip to content

Commit 49b785d

Browse files
committed
Load agent config from db!
1 parent 20714fd commit 49b785d

File tree

4 files changed

+256
-97
lines changed

4 files changed

+256
-97
lines changed

backend/src/templates/agent-registry.ts

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { agentTemplates as staticTemplates } from './agent-list'
99
import {
1010
DynamicAgentValidationError,
1111
validateAgents,
12+
validateSingleAgent,
1213
} from '@codebuff/common/templates/agent-validation'
14+
import { DynamicAgentTemplate } from '@codebuff/common/types/dynamic-agent-template'
1315

1416
export type AgentRegistry = Record<string, AgentTemplate>
1517

@@ -45,20 +47,17 @@ function parseAgentId(fullAgentId: string): {
4547
/**
4648
* Fetch an agent from the database by publisher/agent-id[@version] format
4749
*/
48-
async function fetchAgentFromDatabase(
49-
fullAgentId: string,
50-
): Promise<AgentTemplate | null> {
51-
const parsed = parseAgentId(fullAgentId)
52-
if (!parsed) {
53-
return null
54-
}
55-
56-
const { publisherId, agentId, version } = parsed
50+
async function fetchAgentFromDatabase(parsedAgentId: {
51+
publisherId: string
52+
agentId: string
53+
version?: string
54+
}): Promise<AgentTemplate | null> {
55+
const { publisherId, agentId, version } = parsedAgentId
5756

5857
try {
5958
let agentConfig
6059

61-
if (version) {
60+
if (version && version !== 'latest') {
6261
// Query for specific version
6362
agentConfig = await db
6463
.select()
@@ -94,30 +93,55 @@ async function fetchAgentFromDatabase(
9493
if (!agentConfig) {
9594
logger.debug(
9695
{ publisherId, agentId, version },
97-
'Agent not found in database',
96+
'fetchAgentFromDatabase: Agent not found in database',
9897
)
9998
return null
10099
}
101100

102-
// Convert database agent config to AgentTemplate format
103-
const agentTemplate = agentConfig.data as AgentTemplate
101+
const rawAgentData = agentConfig.data as DynamicAgentTemplate
104102

105-
// Ensure the agent has the full publisher/agent-id as its ID (without version)
106-
const fullAgentTemplate: AgentTemplate = {
107-
...agentTemplate,
108-
id: `${publisherId}/${agentId}`,
103+
// Ensure the agent has the full publisher/agent-id@version as its ID
104+
const agentDataWithId = {
105+
...rawAgentData,
106+
id: `${publisherId}/${agentId}@${agentConfig.version}`,
107+
}
108+
109+
// Use validateSingleAgent to convert to AgentTemplate type
110+
const validationResult = validateSingleAgent(agentDataWithId, {
111+
filePath: `${publisherId}/${agentId}@${agentConfig.version}`,
112+
skipSubagentValidation: true,
113+
})
114+
115+
if (!validationResult.success) {
116+
logger.error(
117+
{
118+
publisherId,
119+
agentId,
120+
version: agentConfig.version,
121+
fullAgentId: agentDataWithId.id,
122+
error: validationResult.error,
123+
},
124+
'fetchAgentFromDatabase: Agent validation failed',
125+
)
126+
return null
109127
}
110128

111129
logger.debug(
112-
{ publisherId, agentId, version: agentConfig.version },
113-
'Successfully loaded agent from database',
130+
{
131+
publisherId,
132+
agentId,
133+
version: agentConfig.version,
134+
fullAgentId: agentDataWithId.id,
135+
agentConfig,
136+
},
137+
'fetchAgentFromDatabase: Successfully loaded and validated agent from database',
114138
)
115139

116-
return fullAgentTemplate
140+
return validationResult.agentTemplate!
117141
} catch (error) {
118142
logger.error(
119143
{ publisherId, agentId, version, error },
120-
'Error fetching agent from database',
144+
'fetchAgentFromDatabase: Error fetching agent from database',
121145
)
122146
return null
123147
}
@@ -143,15 +167,19 @@ export async function getAgentTemplate(
143167
return databaseAgentCache.get(cacheKey) || null
144168
}
145169

170+
const parsed = parseAgentId(agentId)
171+
if (!parsed) {
172+
logger.debug({ agentId }, 'getAgentTemplate: Failed to parse agent ID')
173+
return null
174+
}
175+
146176
// 3. Query database (only for publisher/agent-id format)
147-
if (agentId.includes('/')) {
148-
const dbAgent = await fetchAgentFromDatabase(agentId)
149-
// Cache the result (including null for non-existent agents)
177+
const dbAgent = await fetchAgentFromDatabase(parsed)
178+
if (dbAgent && parsed.version && parsed.version !== 'latest') {
179+
// Cache only specific versions to avoid stale 'latest' results
150180
databaseAgentCache.set(cacheKey, dbAgent)
151-
return dbAgent
152181
}
153-
154-
return null
182+
return dbAgent
155183
}
156184

157185
/**

common/src/__tests__/agent-validation.test.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,7 @@ describe('Agent Validation', () => {
216216
instructionsPrompt: 'Test user prompt',
217217
stepPrompt: 'Test step prompt',
218218
inputSchema: {
219-
prompt: {
220-
type: 'number' as any, // Invalid - should only allow strings
221-
},
219+
prompt: null as any, // Invalid - null schema
222220
},
223221
outputMode: 'last_message',
224222
includeMessageHistory: true,
@@ -232,10 +230,7 @@ describe('Agent Validation', () => {
232230

233231
expect(result.validationErrors).toHaveLength(1)
234232
expect(result.validationErrors[0].message).toContain(
235-
'Invalid inputSchema.prompt',
236-
)
237-
expect(result.validationErrors[0].message).toContain(
238-
'Schema must allow string or undefined values',
233+
'Failed to convert inputSchema.prompt to Zod',
239234
)
240235
expect(result.templates).not.toHaveProperty('invalid_schema_agent')
241236
})
@@ -543,9 +538,7 @@ describe('Agent Validation', () => {
543538
instructionsPrompt: 'Test user prompt',
544539
stepPrompt: 'Test step prompt',
545540
inputSchema: {
546-
prompt: {
547-
type: 'boolean' as any, // Invalid for prompt schema
548-
},
541+
prompt: null as any, // Invalid - null schema
549542
},
550543
outputMode: 'last_message',
551544
includeMessageHistory: true,

common/src/templates/agent-validation.ts

Lines changed: 68 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -73,56 +73,54 @@ export function validateAgents(
7373
}
7474
}
7575

76-
try {
77-
const agentKeys = Object.keys(agentTemplates)
78-
79-
// Pass 1: Collect all agent IDs from template files
80-
const dynamicAgentIds = collectAgentIds(agentTemplates)
81-
82-
// Pass 2: Load and validate each agent template
83-
for (const agentKey of agentKeys) {
84-
try {
85-
const content = agentTemplates[agentKey]
86-
if (!content) {
87-
continue
88-
}
76+
const agentKeys = Object.keys(agentTemplates)
8977

90-
const validationResult = validateSingleAgent(
91-
dynamicAgentIds,
92-
content,
93-
agentKey,
94-
)
78+
// Pass 1: Collect all agent IDs from template files
79+
const dynamicAgentIds = collectAgentIds(agentTemplates)
9580

96-
if (!validationResult.success) {
97-
validationErrors.push({
98-
filePath: agentKey,
99-
message: validationResult.error!,
100-
})
101-
continue
102-
}
81+
// Pass 2: Load and validate each agent template
82+
for (const agentKey of agentKeys) {
83+
try {
84+
const content = agentTemplates[agentKey]
85+
if (!content) {
86+
continue
87+
}
10388

104-
templates[content.id] = validationResult.agentTemplate!
105-
} catch (error) {
106-
const errorMessage =
107-
error instanceof Error ? error.message : 'Unknown error'
89+
const validationResult = validateSingleAgent(content, {
90+
filePath: agentKey,
91+
dynamicAgentIds,
92+
})
10893

94+
if (!validationResult.success) {
10995
validationErrors.push({
11096
filePath: agentKey,
111-
message: `Error in agent template ${agentKey}: ${errorMessage}`,
97+
message: validationResult.error!,
11298
})
99+
continue
100+
}
113101

114-
logger.warn(
115-
{ filePath: agentKey, error: errorMessage },
116-
'Failed to load dynamic agent template',
117-
)
102+
if (templates[content.id]) {
103+
validationErrors.push({
104+
filePath: agentKey,
105+
message: `Duplicate agent ID: ${content.id}`,
106+
})
107+
continue
118108
}
109+
templates[content.id] = validationResult.agentTemplate!
110+
} catch (error) {
111+
const errorMessage =
112+
error instanceof Error ? error.message : 'Unknown error'
113+
114+
validationErrors.push({
115+
filePath: agentKey,
116+
message: `Error in agent template ${agentKey}: ${errorMessage}`,
117+
})
118+
119+
logger.warn(
120+
{ filePath: agentKey, error: errorMessage },
121+
'Failed to load dynamic agent template',
122+
)
119123
}
120-
} catch (error) {
121-
logger.error({ error }, 'Failed to process agent templates')
122-
validationErrors.push({
123-
filePath: 'agentTemplates',
124-
message: 'Failed to process agent templates',
125-
})
126124
}
127125

128126
return {
@@ -137,31 +135,44 @@ export function validateAgents(
137135
*
138136
* @param dynamicAgentIds - Array of all available dynamic agent IDs for validation
139137
* @param template - The dynamic agent template to validate
140-
* @param filePath - Optional file path for error context
138+
* @param options - Optional configuration object
139+
* @param options.filePath - Optional file path for error context
140+
* @param options.skipSubagentValidation - Skip subagent validation when loading from database
141141
* @returns Validation result with either the converted AgentTemplate or an error
142142
*/
143143
export function validateSingleAgent(
144-
dynamicAgentIds: string[],
145144
template: DynamicAgentTemplate,
146-
filePath?: string,
145+
options?: {
146+
dynamicAgentIds?: string[]
147+
filePath?: string
148+
skipSubagentValidation?: boolean
149+
},
147150
): {
148151
success: boolean
149152
agentTemplate?: AgentTemplate
150153
error?: string
151154
} {
155+
const {
156+
filePath,
157+
skipSubagentValidation = false,
158+
dynamicAgentIds = [],
159+
} = options || {}
160+
152161
try {
153-
// Validate subagents
154-
const subagentValidation = validateSubagents(
155-
template.subagents,
156-
dynamicAgentIds,
157-
)
158-
if (!subagentValidation.valid) {
159-
return {
160-
success: false,
161-
error: formatSubagentError(
162-
subagentValidation.invalidAgents,
163-
subagentValidation.availableAgents,
164-
),
162+
// Validate subagents (skip if requested, e.g., for database agents)
163+
if (!skipSubagentValidation) {
164+
const subagentValidation = validateSubagents(
165+
template.subagents,
166+
dynamicAgentIds,
167+
)
168+
if (!subagentValidation.valid) {
169+
return {
170+
success: false,
171+
error: formatSubagentError(
172+
subagentValidation.invalidAgents,
173+
subagentValidation.availableAgents,
174+
),
175+
}
165176
}
166177
}
167178

@@ -254,11 +265,8 @@ export function validateSingleAgent(
254265
*/
255266
function isValidGeneratorFunction(code: string): boolean {
256267
const trimmed = code.trim()
257-
return (
258-
trimmed.startsWith('function*') ||
259-
// Also allow arrow function generators
260-
/^\s*\*\s*\(/.test(trimmed)
261-
)
268+
// Check if it's a generator function (must start with function*)
269+
return trimmed.startsWith('function*')
262270
}
263271

264272
/**

0 commit comments

Comments
 (0)