Problem
When using z.record() in tool parameters, the schema serializes correctly to JSON Schema with additionalProperties, but OpenAI's function calling silently drops the field. The model never sees the parameter.
Works with: Anthropic (Claude)
Broken with: OpenAI (GPT)
Example
const createGist = tool({
id: "github_gists_create",
parameters: z.object({
description: z.string().optional(),
files: z.record(z.string(), z.object({ content: z.string() })), // <-- dropped by OpenAI
public: z.boolean().optional(),
}),
// ...
});
Serializes to:
{
"files": {
"type": "object",
"propertyNames": { "type": "string" },
"additionalProperties": {
"type": "object",
"properties": { "content": { "type": "string" } },
"required": ["content"]
}
}
}
OpenAI only shows description and public to the model - files is missing entirely.
Possible Solutions
- Document limitation - note that
z.record() only works with Anthropic
- Use arrays instead -
z.array(z.object({ key, value })) works everywhere
- Schema normalization layer - auto-convert records to arrays for OpenAI provider
- Warn at runtime - detect
additionalProperties + OpenAI and log a warning
Workaround
Use array format for cross-provider compatibility:
files: z.array(z.object({
filename: z.string(),
content: z.string(),
}))
Then convert to record in the execute function.
Problem
When using
z.record()in tool parameters, the schema serializes correctly to JSON Schema withadditionalProperties, but OpenAI's function calling silently drops the field. The model never sees the parameter.Works with: Anthropic (Claude)
Broken with: OpenAI (GPT)
Example
Serializes to:
{ "files": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "content": { "type": "string" } }, "required": ["content"] } } }OpenAI only shows
descriptionandpublicto the model -filesis missing entirely.Possible Solutions
z.record()only works with Anthropicz.array(z.object({ key, value }))works everywhereadditionalProperties+ OpenAI and log a warningWorkaround
Use array format for cross-provider compatibility:
Then convert to record in the execute function.