@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.
- package/dist/call-agent-schema.d.ts +408 -101
- package/dist/call-agent-schema.d.ts.map +1 -1
- package/dist/call-agent-schema.js +101 -9
- package/dist/call-agent-schema.js.map +1 -1
- package/dist/cjs/call-agent-schema.js +105 -10
- package/dist/cjs/call-agent-schema.js.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/server.js +22 -6
- package/dist/cjs/server.js.map +1 -1
- package/dist/cjs/validate.js +2 -2
- package/dist/cjs/validate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +23 -7
- package/dist/server.js.map +1 -1
- package/dist/validate.d.ts +15 -15
- package/dist/validate.js +2 -2
- package/dist/validate.js.map +1 -1
- package/package.json +1 -1
- package/src/call-agent-schema.test.ts +306 -0
- package/src/call-agent-schema.ts +127 -19
- package/src/index.ts +6 -0
- package/src/server.ts +45 -7
- package/src/validate.ts +2 -2
package/dist/validate.d.ts
CHANGED
|
@@ -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.
|
|
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?:
|
|
22
|
+
outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
23
23
|
}, {
|
|
24
24
|
name: string;
|
|
25
25
|
description: string;
|
|
26
|
-
outputSchema?:
|
|
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.
|
|
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.
|
|
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?:
|
|
56
|
+
outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
57
57
|
}, {
|
|
58
58
|
name: string;
|
|
59
59
|
description: string;
|
|
60
|
-
outputSchema?:
|
|
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.
|
|
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.
|
|
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?:
|
|
92
|
+
outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
93
93
|
}, {
|
|
94
94
|
name: string;
|
|
95
95
|
description: string;
|
|
96
|
-
outputSchema?:
|
|
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.
|
|
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.
|
|
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?:
|
|
128
|
+
outputSchema?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
129
129
|
}, {
|
|
130
130
|
name: string;
|
|
131
131
|
description: string;
|
|
132
|
-
outputSchema?:
|
|
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.
|
|
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.
|
|
34
|
+
auth: z.object({}).passthrough().optional(),
|
|
35
35
|
serverSource: z.string().optional(),
|
|
36
36
|
serverInfo: z
|
|
37
37
|
.object({
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+CAA+C;AAC/C,cAAc;AACd,+CAA+C;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,uBAAuB,CAAC;IAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC;SACX,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACnB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|
package/src/call-agent-schema.ts
CHANGED
|
@@ -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.
|
|
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
|
-
.
|
|
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
|
|
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
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
305
|
+
export const listAgentsInputSchema = zodToOpenAiJsonSchema(
|
|
306
|
+
listAgentsToolInputSchema,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
export const listAgentsValidationSchema = nullTolerant(
|
|
310
|
+
listAgentsToolInputSchema,
|
|
203
311
|
);
|