@slashfi/agents-sdk 0.33.2 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,16 +14,16 @@ export declare const SerializedToolSchema: z.ZodObject<{
14
14
  name: z.ZodString;
15
15
  description: z.ZodString;
16
16
  inputSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
17
- outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
17
+ outputSchema: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
18
18
  }, "strip", z.ZodTypeAny, {
19
19
  name: string;
20
20
  description: string;
21
21
  inputSchema: Record<string, unknown>;
22
- outputSchema?: Record<string, unknown> | undefined;
22
+ outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
23
23
  }, {
24
24
  name: string;
25
25
  description: string;
26
- outputSchema?: Record<string, unknown> | undefined;
26
+ outputSchema?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
27
27
  inputSchema?: Record<string, unknown> | undefined;
28
28
  }>;
29
29
  export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
@@ -32,7 +32,7 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
32
32
  description: z.ZodDefault<z.ZodString>;
33
33
  version: z.ZodDefault<z.ZodString>;
34
34
  visibility: z.ZodDefault<z.ZodEnum<["public", "private"]>>;
35
- auth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
35
+ auth: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
36
36
  serverSource: z.ZodOptional<z.ZodString>;
37
37
  serverInfo: z.ZodOptional<z.ZodObject<{
38
38
  name: z.ZodString;
@@ -48,16 +48,16 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
48
48
  name: z.ZodString;
49
49
  description: z.ZodString;
50
50
  inputSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
51
- outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
51
+ outputSchema: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
52
52
  }, "strip", z.ZodTypeAny, {
53
53
  name: string;
54
54
  description: string;
55
55
  inputSchema: Record<string, unknown>;
56
- outputSchema?: Record<string, unknown> | undefined;
56
+ outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
57
57
  }, {
58
58
  name: string;
59
59
  description: string;
60
- outputSchema?: Record<string, unknown> | undefined;
60
+ outputSchema?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
61
61
  inputSchema?: Record<string, unknown> | undefined;
62
62
  }>, "many">;
63
63
  generatedAt: z.ZodOptional<z.ZodString>;
@@ -68,7 +68,7 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
68
68
  description: z.ZodDefault<z.ZodString>;
69
69
  version: z.ZodDefault<z.ZodString>;
70
70
  visibility: z.ZodDefault<z.ZodEnum<["public", "private"]>>;
71
- auth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
71
+ auth: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
72
72
  serverSource: z.ZodOptional<z.ZodString>;
73
73
  serverInfo: z.ZodOptional<z.ZodObject<{
74
74
  name: z.ZodString;
@@ -84,16 +84,16 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
84
84
  name: z.ZodString;
85
85
  description: z.ZodString;
86
86
  inputSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
87
- outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
87
+ outputSchema: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
88
88
  }, "strip", z.ZodTypeAny, {
89
89
  name: string;
90
90
  description: string;
91
91
  inputSchema: Record<string, unknown>;
92
- outputSchema?: Record<string, unknown> | undefined;
92
+ outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
93
93
  }, {
94
94
  name: string;
95
95
  description: string;
96
- outputSchema?: Record<string, unknown> | undefined;
96
+ outputSchema?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
97
97
  inputSchema?: Record<string, unknown> | undefined;
98
98
  }>, "many">;
99
99
  generatedAt: z.ZodOptional<z.ZodString>;
@@ -104,7 +104,7 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
104
104
  description: z.ZodDefault<z.ZodString>;
105
105
  version: z.ZodDefault<z.ZodString>;
106
106
  visibility: z.ZodDefault<z.ZodEnum<["public", "private"]>>;
107
- auth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
107
+ auth: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
108
108
  serverSource: z.ZodOptional<z.ZodString>;
109
109
  serverInfo: z.ZodOptional<z.ZodObject<{
110
110
  name: z.ZodString;
@@ -120,16 +120,16 @@ export declare const SerializedAgentDefinitionSchema: z.ZodObject<{
120
120
  name: z.ZodString;
121
121
  description: z.ZodString;
122
122
  inputSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
123
- outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
123
+ outputSchema: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
124
124
  }, "strip", z.ZodTypeAny, {
125
125
  name: string;
126
126
  description: string;
127
127
  inputSchema: Record<string, unknown>;
128
- outputSchema?: Record<string, unknown> | undefined;
128
+ outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
129
129
  }, {
130
130
  name: string;
131
131
  description: string;
132
- outputSchema?: Record<string, unknown> | undefined;
132
+ outputSchema?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
133
133
  inputSchema?: Record<string, unknown> | undefined;
134
134
  }>, "many">;
135
135
  generatedAt: z.ZodOptional<z.ZodString>;
package/dist/validate.js CHANGED
@@ -19,7 +19,7 @@ export const SerializedToolSchema = z.object({
19
19
  inputSchema: z
20
20
  .record(z.unknown())
21
21
  .default({ type: "object", properties: {} }),
22
- outputSchema: z.record(z.unknown()).optional(),
22
+ outputSchema: z.object({}).passthrough().optional(),
23
23
  });
24
24
  // ============================================
25
25
  // Agent Definition Schema
@@ -31,7 +31,7 @@ export const SerializedAgentDefinitionSchema = z
31
31
  description: z.string().default(""),
32
32
  version: z.string().default("1.0.0"),
33
33
  visibility: z.enum(["public", "private"]).default("public"),
34
- auth: z.record(z.unknown()).optional(),
34
+ auth: z.object({}).passthrough().optional(),
35
35
  serverSource: z.string().optional(),
36
36
  serverInfo: z
37
37
  .object({
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slashfi/agents-sdk",
3
- "version": "0.33.2",
3
+ "version": "0.34.1",
4
4
  "author": "Slash Financial",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,306 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { z } from "zod";
3
+ import {
4
+ stripNulls,
5
+ nullTolerant,
6
+ zodToOpenAiJsonSchema,
7
+ callAgentValidationSchema,
8
+ callAgentRequestSchema,
9
+ listAgentsValidationSchema,
10
+ } from "./call-agent-schema";
11
+
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // stripNulls
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ describe("stripNulls", () => {
17
+ test("converts null to undefined", () => {
18
+ expect(stripNulls(null)).toBe(undefined);
19
+ });
20
+
21
+ test("passes through primitives", () => {
22
+ expect(stripNulls("hello")).toBe("hello");
23
+ expect(stripNulls(42)).toBe(42);
24
+ expect(stripNulls(true)).toBe(true);
25
+ expect(stripNulls(undefined)).toBe(undefined);
26
+ });
27
+
28
+ test("strips null from flat objects", () => {
29
+ expect(stripNulls({ a: null, b: "hello" })).toEqual({ b: "hello" });
30
+ });
31
+
32
+ test("strips null from nested objects", () => {
33
+ expect(stripNulls({ a: { b: null, c: "ok" }, d: null })).toEqual({
34
+ a: { c: "ok" },
35
+ });
36
+ });
37
+
38
+ test("strips null from arrays", () => {
39
+ expect(stripNulls([1, null, 3])).toEqual([1, undefined, 3]);
40
+ });
41
+
42
+ test("handles deeply nested structures", () => {
43
+ const input = {
44
+ action: "execute_tool",
45
+ path: "/agents/@test",
46
+ tool: "my_tool",
47
+ params: null,
48
+ callerId: null,
49
+ callerType: null,
50
+ metadata: { nested: null, keep: "this" },
51
+ };
52
+ const result = stripNulls(input) as Record<string, unknown>;
53
+ expect(result.action).toBe("execute_tool");
54
+ expect(result.path).toBe("/agents/@test");
55
+ expect(result.params).toBe(undefined);
56
+ expect(result.callerId).toBe(undefined);
57
+ expect(result.callerType).toBe(undefined);
58
+ expect(result.metadata).toEqual({ keep: "this" });
59
+ });
60
+
61
+ test("handles empty objects and arrays", () => {
62
+ expect(stripNulls({})).toEqual({});
63
+ expect(stripNulls([])).toEqual([]);
64
+ });
65
+ });
66
+
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+ // nullTolerant
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+
71
+ describe("nullTolerant", () => {
72
+ const schema = z.object({
73
+ name: z.string().optional(),
74
+ count: z.number().optional(),
75
+ tags: z.array(z.string()).optional(),
76
+ nested: z
77
+ .object({
78
+ value: z.string().optional(),
79
+ })
80
+ .optional(),
81
+ });
82
+
83
+ test("original schema rejects null", () => {
84
+ expect(() => schema.parse({ name: null })).toThrow();
85
+ expect(() => schema.parse({ tags: null })).toThrow();
86
+ expect(() => schema.parse({ count: null })).toThrow();
87
+ });
88
+
89
+ test("tolerant schema accepts null for optional string", () => {
90
+ const tolerant = nullTolerant(schema);
91
+ const result = tolerant.parse({ name: null });
92
+ expect(result).toEqual({});
93
+ });
94
+
95
+ test("tolerant schema accepts null for optional array", () => {
96
+ const tolerant = nullTolerant(schema);
97
+ const result = tolerant.parse({ tags: null });
98
+ expect(result).toEqual({});
99
+ });
100
+
101
+ test("tolerant schema accepts null for optional number", () => {
102
+ const tolerant = nullTolerant(schema);
103
+ const result = tolerant.parse({ count: null });
104
+ expect(result).toEqual({});
105
+ });
106
+
107
+ test("tolerant schema accepts null for nested optional", () => {
108
+ const tolerant = nullTolerant(schema);
109
+ const result = tolerant.parse({ nested: { value: null } });
110
+ expect(result).toEqual({ nested: {} });
111
+ });
112
+
113
+ test("tolerant schema still validates real values", () => {
114
+ const tolerant = nullTolerant(schema);
115
+ const result = tolerant.parse({
116
+ name: "test",
117
+ count: 5,
118
+ tags: ["a", "b"],
119
+ });
120
+ expect(result).toEqual({ name: "test", count: 5, tags: ["a", "b"] });
121
+ });
122
+
123
+ test("tolerant schema still rejects invalid types", () => {
124
+ const tolerant = nullTolerant(schema);
125
+ expect(() => tolerant.parse({ name: 123 })).toThrow();
126
+ expect(() => tolerant.parse({ count: "not a number" })).toThrow();
127
+ expect(() => tolerant.parse({ tags: "not an array" })).toThrow();
128
+ });
129
+
130
+ test("all nulls at once", () => {
131
+ const tolerant = nullTolerant(schema);
132
+ const result = tolerant.parse({
133
+ name: null,
134
+ count: null,
135
+ tags: null,
136
+ nested: null,
137
+ });
138
+ expect(result).toEqual({});
139
+ });
140
+ });
141
+
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+ // callAgentValidationSchema — real-world scenarios
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+
146
+ describe("callAgentValidationSchema", () => {
147
+ test("accepts execute_tool with null optional fields (the original bug)", () => {
148
+ const input = {
149
+ request: {
150
+ action: "execute_tool",
151
+ path: "/agents/@librarian",
152
+ tool: "search_skill",
153
+ params: { query: "agent registry" },
154
+ callerId: null,
155
+ callerType: null,
156
+ metadata: null,
157
+ },
158
+ };
159
+ const result = callAgentValidationSchema.safeParse(input);
160
+ expect(result.success).toBe(true);
161
+ });
162
+
163
+ test("accepts describe_tools with tools: null (the original bug)", () => {
164
+ const input = {
165
+ request: {
166
+ action: "describe_tools",
167
+ path: "/agents/@librarian",
168
+ tools: null,
169
+ callerId: null,
170
+ callerType: null,
171
+ metadata: null,
172
+ },
173
+ };
174
+ const result = callAgentValidationSchema.safeParse(input);
175
+ expect(result.success).toBe(true);
176
+ });
177
+
178
+ test("accepts invoke with null sessionId and branchAttributes", () => {
179
+ const input = {
180
+ request: {
181
+ action: "invoke",
182
+ path: "/agents/@compactor",
183
+ prompt: "Hello",
184
+ sessionId: null,
185
+ branchAttributes: null,
186
+ callerId: null,
187
+ callerType: null,
188
+ metadata: null,
189
+ },
190
+ };
191
+ const result = callAgentValidationSchema.safeParse(input);
192
+ expect(result.success).toBe(true);
193
+ });
194
+
195
+ test("accepts ask with null optional fields", () => {
196
+ const input = {
197
+ request: {
198
+ action: "ask",
199
+ path: "/agents/@worker",
200
+ prompt: "What time is it?",
201
+ sessionId: null,
202
+ branchAttributes: null,
203
+ callerId: null,
204
+ callerType: null,
205
+ metadata: null,
206
+ },
207
+ };
208
+ const result = callAgentValidationSchema.safeParse(input);
209
+ expect(result.success).toBe(true);
210
+ });
211
+
212
+ test("accepts list_resources with null optional fields", () => {
213
+ const input = {
214
+ request: {
215
+ action: "list_resources",
216
+ path: "/agents/@agent-fs",
217
+ callerId: null,
218
+ callerType: null,
219
+ metadata: null,
220
+ },
221
+ };
222
+ const result = callAgentValidationSchema.safeParse(input);
223
+ expect(result.success).toBe(true);
224
+ });
225
+
226
+ test("accepts read_resources with null optional fields", () => {
227
+ const input = {
228
+ request: {
229
+ action: "read_resources",
230
+ path: "/agents/@agent-fs",
231
+ uris: ["AUTH.md"],
232
+ callerId: null,
233
+ callerType: null,
234
+ metadata: null,
235
+ },
236
+ };
237
+ const result = callAgentValidationSchema.safeParse(input);
238
+ expect(result.success).toBe(true);
239
+ });
240
+
241
+ test("still rejects truly invalid input", () => {
242
+ const result = callAgentValidationSchema.safeParse({
243
+ request: {
244
+ action: "execute_tool",
245
+ // missing required 'path' and 'tool'
246
+ },
247
+ });
248
+ expect(result.success).toBe(false);
249
+ });
250
+
251
+ test("still rejects unknown action", () => {
252
+ const result = callAgentValidationSchema.safeParse({
253
+ request: {
254
+ action: "unknown_action",
255
+ path: "/agents/@test",
256
+ },
257
+ });
258
+ expect(result.success).toBe(false);
259
+ });
260
+ });
261
+
262
+ // ─────────────────────────────────────────────────────────────────────────────
263
+ // listAgentsValidationSchema
264
+ // ─────────────────────────────────────────────────────────────────────────────
265
+
266
+ describe("listAgentsValidationSchema", () => {
267
+ test("accepts all-null optional fields", () => {
268
+ const result = listAgentsValidationSchema.parse({
269
+ query: null,
270
+ limit: null,
271
+ cursor: null,
272
+ });
273
+ expect(result).toEqual({});
274
+ });
275
+
276
+ test("accepts mix of null and real values", () => {
277
+ const result = listAgentsValidationSchema.parse({
278
+ query: "notion",
279
+ limit: null,
280
+ cursor: null,
281
+ });
282
+ expect(result).toEqual({ query: "notion" });
283
+ });
284
+ });
285
+
286
+ // ─────────────────────────────────────────────────────────────────────────────
287
+ // zodToOpenAiJsonSchema
288
+ // ─────────────────────────────────────────────────────────────────────────────
289
+
290
+ describe("zodToOpenAiJsonSchema", () => {
291
+ test("produces JSON schema with nullable optional fields", () => {
292
+ const schema = z.object({
293
+ required: z.string(),
294
+ optional: z.string().optional(),
295
+ });
296
+ const jsonSchema = zodToOpenAiJsonSchema(schema) as any;
297
+
298
+ // Required field should be in required array
299
+ expect(jsonSchema.required).toContain("required");
300
+ // Optional field should also be required (openAi convention)
301
+ expect(jsonSchema.required).toContain("optional");
302
+ // Optional field should allow null
303
+ const optProp = jsonSchema.properties.optional;
304
+ expect(optProp.anyOf || optProp.type).toBeTruthy();
305
+ });
306
+ });
@@ -12,7 +12,102 @@
12
12
  * Response types live in types.ts (they're output shapes, not validated input).
13
13
  */
14
14
 
15
- import { z } from "zod";
15
+ import { z, type ZodTypeAny } from "zod";
16
+ import { zodToJsonSchema } from "zod-to-json-schema";
17
+
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ // OpenAI null-tolerance transform
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Recursively strip null values from an object, converting them to undefined.
24
+ * This is the inverse of zod-to-json-schema's openAi target behavior, which
25
+ * converts .optional() fields to nullable+required in JSON Schema.
26
+ *
27
+ * Used as a z.preprocess() step so that Zod's .optional() (which accepts
28
+ * undefined but not null) works correctly with LLM outputs that send null
29
+ * for "no value" per the OpenAI function calling convention.
30
+ */
31
+ export function stripNulls(obj: unknown): unknown {
32
+ if (obj === null) return undefined;
33
+ if (Array.isArray(obj)) return obj.map(stripNulls);
34
+ if (typeof obj === "object") {
35
+ return Object.fromEntries(
36
+ Object.entries(obj as Record<string, unknown>).map(([k, v]) => [
37
+ k,
38
+ stripNulls(v),
39
+ ]),
40
+ );
41
+ }
42
+ return obj;
43
+ }
44
+
45
+ /**
46
+ * Wrap a Zod schema with a preprocess step that converts null → undefined.
47
+ * This makes the schema "null-tolerant" — matching what the OpenAI JSON Schema
48
+ * target promises to LLMs (nullable fields) while keeping Zod's .optional()
49
+ * semantics internally.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const schema = z.object({ name: z.string().optional() });
54
+ * const tolerant = nullTolerant(schema);
55
+ * tolerant.parse({ name: null }); // { name: undefined } — no error
56
+ * ```
57
+ */
58
+ export function nullTolerant<T extends ZodTypeAny>(schema: T) {
59
+ return z.preprocess(stripNulls, schema) as unknown as T;
60
+ }
61
+
62
+ /**
63
+ * Convert a Zod schema to JSON Schema using the OpenAI target,
64
+ * which makes all optional fields nullable+required.
65
+ *
66
+ * This is the standard way to generate input schemas for MCP tools
67
+ * that will be called by LLMs.
68
+ */
69
+ /**
70
+ * Recursively normalize a JSON schema for Anthropic compatibility.
71
+ * Replaces `additionalProperties: {}` (empty schema = any) with `true`,
72
+ * which is semantically equivalent but accepted by Anthropic's validator.
73
+ */
74
+ function normalizeJsonSchema(schema: Record<string, unknown>): Record<string, unknown> {
75
+ if (typeof schema !== "object" || schema === null) return schema;
76
+
77
+ const result: Record<string, unknown> = {};
78
+ for (const [key, value] of Object.entries(schema)) {
79
+ if (
80
+ key === "additionalProperties" &&
81
+ typeof value === "object" &&
82
+ value !== null &&
83
+ Object.keys(value).length === 0
84
+ ) {
85
+ result[key] = true;
86
+ } else if (Array.isArray(value)) {
87
+ result[key] = value.map((item) =>
88
+ typeof item === "object" && item !== null
89
+ ? normalizeJsonSchema(item as Record<string, unknown>)
90
+ : item,
91
+ );
92
+ } else if (typeof value === "object" && value !== null) {
93
+ result[key] = normalizeJsonSchema(value as Record<string, unknown>);
94
+ } else {
95
+ result[key] = value;
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+
101
+ export function zodToOpenAiJsonSchema(
102
+ schema: ZodTypeAny,
103
+ ): Record<string, unknown> {
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ const raw = zodToJsonSchema(schema as any, { target: "openAi" }) as Record<
106
+ string,
107
+ unknown
108
+ >;
109
+ return normalizeJsonSchema(raw);
110
+ }
16
111
 
17
112
  // ─────────────────────────────────────────────────────────────────────────────
18
113
  // Base schemas
@@ -24,7 +119,7 @@ const callAgentBaseSchema = z.object({
24
119
  path: z.string().describe("Agent path (e.g., '@my-agent')"),
25
120
  callerId: z.string().optional().describe("Caller ID for access control"),
26
121
  callerType: callerTypeSchema.optional().describe("Caller type"),
27
- metadata: z.record(z.unknown()).optional().describe("Additional metadata"),
122
+ metadata: z.object({}).passthrough().optional().describe("Additional metadata"),
28
123
  });
29
124
 
30
125
  // ─────────────────────────────────────────────────────────────────────────────
@@ -64,7 +159,7 @@ export const executeToolActionSchema = callAgentBaseSchema.extend({
64
159
  action: z.literal("execute_tool"),
65
160
  tool: z.string().describe("Tool name to call"),
66
161
  params: z
67
- .record(z.unknown())
162
+ .object({}).passthrough()
68
163
  .optional()
69
164
  .describe("Parameters for the tool"),
70
165
  });
@@ -85,12 +180,20 @@ export const loadActionSchema = callAgentBaseSchema.extend({
85
180
 
86
181
  /** List resources: discover available resources on an agent */
87
182
  export const listResourcesActionSchema = callAgentBaseSchema.extend({
88
- action: z.literal("list_resources").describe("List all resources available on an agent — docs, auth instructions, config schemas, etc."),
183
+ action: z
184
+ .literal("list_resources")
185
+ .describe(
186
+ "List all resources available on an agent — docs, auth instructions, config schemas, etc.",
187
+ ),
89
188
  });
90
189
 
91
190
  /** Read resources: fetch one or more resources by URI */
92
191
  export const readResourcesActionSchema = callAgentBaseSchema.extend({
93
- action: z.literal("read_resources").describe("Fetch one or more resources by URI. Use list_resources first to discover available URIs."),
192
+ action: z
193
+ .literal("read_resources")
194
+ .describe(
195
+ "Fetch one or more resources by URI. Use list_resources first to discover available URIs.",
196
+ ),
94
197
  uris: z
95
198
  .array(z.string())
96
199
  .describe("Resource URIs to read (e.g., ['AUTH.md'])"),
@@ -140,15 +243,13 @@ export type CallerType = z.infer<typeof callerTypeSchema>;
140
243
  /** All supported action strings as a const array. */
141
244
  export const CALL_AGENT_ACTIONS: AgentAction[] =
142
245
  callAgentRequestSchema.options.map(
143
- (s) => (s.shape as { action: z.ZodLiteral<AgentAction> }).action.value
246
+ (s) => (s.shape as { action: z.ZodLiteral<AgentAction> }).action.value,
144
247
  );
145
248
 
146
249
  // ─────────────────────────────────────────────────────────────────────────────
147
250
  // JSON Schema for MCP (derived from zod)
148
251
  // ─────────────────────────────────────────────────────────────────────────────
149
252
 
150
- import { zodToJsonSchema } from "zod-to-json-schema";
151
-
152
253
  /**
153
254
  * Zod schema for the full MCP tool input (wraps request in an outer object).
154
255
  * This is the schema that gets converted to JSON Schema for the LLM.
@@ -163,10 +264,17 @@ export const callAgentToolInputSchema = z.object({
163
264
  *
164
265
  * Fully derived from the zod schemas — no hand-written JSON Schema.
165
266
  */
166
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
- export const callAgentInputSchema = zodToJsonSchema(
168
- callAgentToolInputSchema as any,
169
- { target: "openAi" }
267
+ export const callAgentInputSchema = zodToOpenAiJsonSchema(
268
+ callAgentToolInputSchema,
269
+ );
270
+
271
+ /**
272
+ * Null-tolerant validation schema for `call_agent`.
273
+ * Accepts null values where the JSON Schema promises nullable,
274
+ * converting them to undefined before Zod validation.
275
+ */
276
+ export const callAgentValidationSchema = nullTolerant(
277
+ callAgentToolInputSchema,
170
278
  );
171
279
 
172
280
  // ─────────────────────────────────────────────────────────────────────────────
@@ -183,9 +291,7 @@ export const listAgentsToolInputSchema = z.object({
183
291
  limit: z
184
292
  .number()
185
293
  .optional()
186
- .describe(
187
- "Maximum number of results per page (default: 20)",
188
- ),
294
+ .describe("Maximum number of results per page (default: 20)"),
189
295
  cursor: z
190
296
  .string()
191
297
  .optional()
@@ -196,8 +302,10 @@ export const listAgentsToolInputSchema = z.object({
196
302
 
197
303
  export type ListAgentsInput = z.infer<typeof listAgentsToolInputSchema>;
198
304
 
199
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
- export const listAgentsInputSchema = zodToJsonSchema(
201
- listAgentsToolInputSchema as any,
202
- { target: "openAi" }
305
+ export const listAgentsInputSchema = zodToOpenAiJsonSchema(
306
+ listAgentsToolInputSchema,
307
+ );
308
+
309
+ export const listAgentsValidationSchema = nullTolerant(
310
+ listAgentsToolInputSchema,
203
311
  );