Skip to content

Commit 8b6285b

Browse files
committed
Add schema validation to require set_output tool if outputMode is json; require spawn_agents tool if subagents array is non-empty
1 parent bb61b28 commit 8b6285b

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,35 @@ describe('Agent Validation', () => {
760760
}
761761
})
762762

763+
test('should reject set_output tool without json output mode', () => {
764+
const {
765+
DynamicAgentTemplateSchema,
766+
} = require('../types/dynamic-agent-template')
767+
768+
const agentConfig = {
769+
id: 'test-agent',
770+
version: '1.0.0',
771+
displayName: 'Test Agent',
772+
parentPrompt: 'Testing',
773+
model: 'claude-3-5-sonnet-20241022',
774+
outputMode: 'last_message' as const, // Not json
775+
toolNames: ['end_turn', 'set_output'], // Has set_output
776+
subagents: [],
777+
systemPrompt: 'Test',
778+
instructionsPrompt: 'Test',
779+
stepPrompt: 'Test',
780+
}
781+
782+
const result = DynamicAgentTemplateSchema.safeParse(agentConfig)
783+
expect(result.success).toBe(false)
784+
if (!result.success) {
785+
const errorMessage = result.error.issues[0]?.message || ''
786+
expect(errorMessage).toContain(
787+
"'set_output' tool requires outputMode to be 'json'",
788+
)
789+
}
790+
})
791+
763792
test('should validate that handleSteps is a generator function', async () => {
764793
const agentTemplates = {
765794
'test-agent.ts': {

common/src/__tests__/dynamic-agent-template-schema.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,91 @@ describe('DynamicAgentConfigSchema', () => {
281281
const result = DynamicAgentTemplateSchema.safeParse(template)
282282
expect(result.success).toBe(true)
283283
})
284+
285+
it('should reject template with set_output tool but non-json outputMode', () => {
286+
const template = {
287+
...validBaseTemplate,
288+
outputMode: 'last_message' as const,
289+
toolNames: ['end_turn', 'set_output'], // set_output without json mode
290+
}
291+
292+
const result = DynamicAgentTemplateSchema.safeParse(template)
293+
expect(result.success).toBe(false)
294+
if (!result.success) {
295+
const setOutputError = result.error.issues.find((issue) =>
296+
issue.message.includes(
297+
"'set_output' tool requires outputMode to be 'json'",
298+
),
299+
)
300+
expect(setOutputError).toBeDefined()
301+
expect(setOutputError?.message).toContain(
302+
"'set_output' tool requires outputMode to be 'json'",
303+
)
304+
}
305+
})
306+
307+
it('should reject template with set_output tool and all_messages outputMode', () => {
308+
const template = {
309+
...validBaseTemplate,
310+
outputMode: 'all_messages' as const,
311+
toolNames: ['end_turn', 'set_output'], // set_output without json mode
312+
}
313+
314+
const result = DynamicAgentTemplateSchema.safeParse(template)
315+
expect(result.success).toBe(false)
316+
if (!result.success) {
317+
const setOutputError = result.error.issues.find((issue) =>
318+
issue.message.includes(
319+
"'set_output' tool requires outputMode to be 'json'",
320+
),
321+
)
322+
expect(setOutputError).toBeDefined()
323+
}
324+
})
325+
326+
it('should reject template with non-empty subagents but missing spawn_agents tool', () => {
327+
const template = {
328+
...validBaseTemplate,
329+
subagents: ['researcher', 'file-picker'], // Non-empty subagents
330+
toolNames: ['end_turn', 'read_files'], // Missing spawn_agents
331+
}
332+
333+
const result = DynamicAgentTemplateSchema.safeParse(template)
334+
expect(result.success).toBe(false)
335+
if (!result.success) {
336+
const spawnAgentsError = result.error.issues.find((issue) =>
337+
issue.message.includes(
338+
"Non-empty subagents array requires the 'spawn_agents' tool",
339+
),
340+
)
341+
expect(spawnAgentsError).toBeDefined()
342+
expect(spawnAgentsError?.message).toContain(
343+
"Non-empty subagents array requires the 'spawn_agents' tool",
344+
)
345+
}
346+
})
347+
348+
it('should accept template with non-empty subagents and spawn_agents tool', () => {
349+
const template = {
350+
...validBaseTemplate,
351+
subagents: ['researcher', 'file-picker'],
352+
toolNames: ['end_turn', 'spawn_agents'],
353+
}
354+
355+
const result = DynamicAgentTemplateSchema.safeParse(template)
356+
expect(result.success).toBe(true)
357+
})
358+
359+
it('should accept template with empty subagents and no spawn_agents tool', () => {
360+
const template = {
361+
...validBaseTemplate,
362+
subagents: [], // Empty subagents
363+
toolNames: ['end_turn', 'read_files'], // No spawn_agents needed
364+
}
365+
366+
const result = DynamicAgentTemplateSchema.safeParse(template)
367+
expect(result.success).toBe(true)
368+
})
284369
})
285370

286371
describe('Edge Cases', () => {

common/src/types/dynamic-agent-template.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,35 @@ export const DynamicAgentTemplateSchema = DynamicAgentConfigSchema.extend({
179179
path: ['toolNames'],
180180
},
181181
)
182+
.refine(
183+
(data) => {
184+
// If 'set_output' tool is included, outputMode must be 'json'
185+
if (data.toolNames.includes('set_output') && data.outputMode !== 'json') {
186+
return false
187+
}
188+
return true
189+
},
190+
{
191+
message:
192+
"'set_output' tool requires outputMode to be 'json'. Change outputMode to 'json' or remove 'set_output' from toolNames.",
193+
path: ['outputMode'],
194+
},
195+
)
196+
.refine(
197+
(data) => {
198+
// If subagents array is non-empty, 'spawn_agents' tool must be included
199+
if (
200+
data.subagents.length > 0 &&
201+
!data.toolNames.includes('spawn_agents')
202+
) {
203+
return false
204+
}
205+
return true
206+
},
207+
{
208+
message:
209+
"Non-empty subagents array requires the 'spawn_agents' tool. Add 'spawn_agents' to toolNames or remove subagents.",
210+
path: ['toolNames'],
211+
},
212+
)
182213
export type DynamicAgentTemplate = z.infer<typeof DynamicAgentTemplateSchema>

0 commit comments

Comments
 (0)