Skip to content

Commit 9adf876

Browse files
authored
fix(server-hono): support Zod v4 records in Swagger docs (#1220)
1 parent 8b8c238 commit 9adf876

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@voltagent/server-hono": patch
3+
---
4+
5+
fix(server-hono): support Zod v4 record schemas in Swagger docs
6+
7+
The built-in tool OpenAPI schemas now use explicit record key and value schemas so Zod v4 does not
8+
produce undefined record value types during Swagger document generation.

packages/server-hono/src/app-factory.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { TOOL_ROUTES } from "@voltagent/server-core";
12
import { cors } from "hono/cors";
23
import { describe, expect, it } from "vitest";
4+
import { z as zodV4 } from "zod/v4";
35
import { createApp } from "./app-factory";
6+
import { OpenApiGeneratorV31 } from "./vendor/zod-to-openapi/v4";
47

58
const createDeps = () =>
69
({
@@ -270,6 +273,17 @@ describe("app-factory CORS configuration", () => {
270273
expect(defaultRes.headers.get("access-control-allow-credentials")).toBe("true");
271274
});
272275

276+
it("should generate OpenAPI docs for built-in tool routes", async () => {
277+
const { app } = await createApp(createDeps(), {});
278+
279+
const res = await app.request("/doc");
280+
const doc = await res.json();
281+
282+
expect(res.status).toBe(200);
283+
expect(doc.paths[TOOL_ROUTES.listTools.path].get).toBeDefined();
284+
expect(doc.paths[TOOL_ROUTES.executeTool.path.replace(":name", "{name}")].post).toBeDefined();
285+
});
286+
273287
it("should keep custom routes public in opt-in mode (default)", async () => {
274288
const mockAuthProvider = {
275289
verifyToken: async (token: string) => {
@@ -326,3 +340,41 @@ describe("app-factory CORS configuration", () => {
326340
expect(executionOrder).toEqual(["auth-middleware", "custom-route"]);
327341
});
328342
});
343+
344+
describe("Zod v4 OpenAPI compatibility", () => {
345+
it("should generate docs for record schemas with explicit key and value types", () => {
346+
const schema = zodV4.object({
347+
parameters: zodV4.record(zodV4.string(), zodV4.any()).optional(),
348+
context: zodV4.record(zodV4.string(), zodV4.any()).optional().default({}),
349+
});
350+
const route = {
351+
method: "post",
352+
path: "/tools/{name}/execute",
353+
request: {
354+
body: {
355+
content: {
356+
"application/json": {
357+
schema,
358+
},
359+
},
360+
},
361+
},
362+
responses: {
363+
200: {
364+
description: "Successful response",
365+
},
366+
},
367+
};
368+
369+
const doc = new OpenApiGeneratorV31([{ type: "route", route }]).generateDocument({
370+
openapi: "3.1.0",
371+
info: { title: "Zod v4 regression", version: "1.0.0" },
372+
});
373+
374+
const requestSchema =
375+
doc.paths["/tools/{name}/execute"].post.requestBody.content["application/json"].schema;
376+
377+
expect(requestSchema.properties.parameters.additionalProperties).toEqual({});
378+
expect(requestSchema.properties.context.additionalProperties).toEqual({});
379+
});
380+
});

packages/server-hono/src/routes/tool.routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const ToolDefinitionSchema = z.object({
1616
id: z.string().optional(),
1717
name: z.string(),
1818
description: z.string().optional(),
19-
parameters: z.record(z.any()).optional(),
19+
parameters: z.record(z.string(), z.any()).optional(),
2020
status: z.string().optional(),
2121
agents: z
2222
.array(
@@ -37,7 +37,7 @@ const ToolListResponseSchema = z.object({
3737
const ToolExecuteRequestSchema = z
3838
.object({
3939
input: z.any().optional().default({}),
40-
context: z.record(z.any()).optional().default({}),
40+
context: z.record(z.string(), z.any()).optional().default({}),
4141
userId: z.string().optional(),
4242
conversationId: z.string().optional(),
4343
})

0 commit comments

Comments
 (0)