Skip to content

Commit 3589494

Browse files
committed
Fix input schema for tools!
1 parent cf98186 commit 3589494

File tree

1 file changed

+59
-29
lines changed

1 file changed

+59
-29
lines changed

packages/agent-runtime/src/templates/prompts.ts

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,40 +23,66 @@ export function getAgentShortName(agentType: AgentTemplateType): string {
2323
* Builds a flat input schema for an agent tool by combining prompt and params.
2424
* E.g., { prompt?: string, ...paramsFields }
2525
*/
26-
export function buildAgentFlatInputSchema(agentTemplate: AgentTemplate): z.ZodType {
26+
export function buildAgentFlatInputSchema(
27+
agentTemplate: AgentTemplate,
28+
): z.ZodType {
2729
const { inputSchema } = agentTemplate
28-
30+
2931
// Start with an empty object schema
3032
let schemaFields: Record<string, z.ZodType> = {}
31-
33+
3234
// Add prompt field if defined
3335
if (inputSchema?.prompt) {
3436
schemaFields.prompt = inputSchema.prompt.optional()
3537
}
36-
38+
3739
// Merge params fields directly into the schema (flat structure)
3840
if (inputSchema?.params) {
39-
// Get the shape of the params schema if it's an object
40-
const paramsJsonSchema = z.toJSONSchema(inputSchema.params, { io: 'input' })
41-
if (paramsJsonSchema.properties) {
42-
for (const [key, propSchema] of Object.entries(paramsJsonSchema.properties)) {
41+
// Try to get the shape from the params schema directly if it's a ZodObject
42+
// This preserves the full nested structure instead of converting to z.any()
43+
const paramsShape = getZodObjectShape(inputSchema.params)
44+
45+
if (paramsShape) {
46+
// We have the original Zod shape, use it directly
47+
for (const [key, fieldSchema] of Object.entries(paramsShape)) {
4348
// Skip if we already have a prompt field
4449
if (key === 'prompt') continue
45-
46-
// Create a zod schema from the JSON schema property
47-
const isRequired = (paramsJsonSchema.required as string[] | undefined)?.includes(key)
48-
// Use z.any() with description since we can't perfectly reconstruct the original zod type
49-
const fieldSchema = z.any().describe(
50-
(propSchema as any).description || `Parameter: ${key}`
51-
)
52-
schemaFields[key] = isRequired ? fieldSchema : fieldSchema.optional()
50+
schemaFields[key] = fieldSchema as z.ZodType
5351
}
5452
}
5553
}
56-
57-
return z.object(schemaFields).describe(
58-
agentTemplate.spawnerPrompt || `Spawn the ${agentTemplate.displayName} agent`
59-
)
54+
55+
return z
56+
.object(schemaFields)
57+
.describe(
58+
agentTemplate.spawnerPrompt ||
59+
`Spawn the ${agentTemplate.displayName} agent`,
60+
)
61+
}
62+
63+
/**
64+
* Extracts the shape from a Zod schema if it's a ZodObject.
65+
* Handles wrapped types like ZodOptional, ZodNullable, ZodDefault, etc.
66+
*/
67+
function getZodObjectShape(
68+
schema: z.ZodType,
69+
): Record<string, z.ZodType> | null {
70+
// ZodObject has a public .shape property in Zod v4
71+
if (
72+
'shape' in schema &&
73+
typeof schema.shape === 'object' &&
74+
schema.shape !== null
75+
) {
76+
return schema.shape as Record<string, z.ZodType>
77+
}
78+
79+
// Handle wrapped types (optional, nullable, default, etc.) via internal def
80+
const def = (schema as any)?._zod?.def
81+
if (def?.inner) {
82+
return getZodObjectShape(def.inner)
83+
}
84+
85+
return null
6086
}
6187

6288
/**
@@ -74,28 +100,30 @@ export async function buildAgentToolSet(
74100
>,
75101
): Promise<ToolSet> {
76102
const { spawnableAgents, agentTemplates } = params
77-
103+
78104
const toolSet: ToolSet = {}
79-
105+
80106
for (const agentType of spawnableAgents) {
81107
const agentTemplate = await getAgentTemplate({
82108
...params,
83109
agentId: agentType,
84110
localAgentTemplates: agentTemplates,
85111
})
86-
112+
87113
if (!agentTemplate) continue
88-
114+
89115
const shortName = getAgentShortName(agentType)
90116
const inputSchema = buildAgentFlatInputSchema(agentTemplate)
91-
117+
92118
// Use the same structure as other tools in toolParams
93119
toolSet[shortName] = {
94-
description: agentTemplate.spawnerPrompt || `Spawn the ${agentTemplate.displayName} agent`,
120+
description:
121+
agentTemplate.spawnerPrompt ||
122+
`Spawn the ${agentTemplate.displayName} agent`,
95123
inputSchema,
96124
}
97125
}
98-
126+
99127
return toolSet
100128
}
101129

@@ -112,7 +140,7 @@ function buildSingleAgentDescription(
112140
prompt: {"description": "A coding task to complete", "type": "string"}
113141
params: None`
114142
}
115-
143+
116144
const { inputSchema } = agentTemplate
117145
const inputSchemaStr = inputSchema
118146
? [
@@ -164,7 +192,9 @@ export async function buildFullSpawnableAgentsSpec(
164192
)
165193

166194
const agentsDescription = subAgentTypesAndTemplates
167-
.map(([agentType, agentTemplate]) => buildSingleAgentDescription(agentType, agentTemplate))
195+
.map(([agentType, agentTemplate]) =>
196+
buildSingleAgentDescription(agentType, agentTemplate),
197+
)
168198
.filter(Boolean)
169199
.join('\n\n')
170200

0 commit comments

Comments
 (0)