@copilotkit/shared
Version:
<div align="center"> <a href="https://copilotkit.ai" target="_blank"> <img src="https://github.com/copilotkit/copilotkit/raw/main/assets/banner.png" alt="CopilotKit Logo"> </a>
510 lines (507 loc) • 17.4 kB
JavaScript
"use strict";
// src/utils/json-schema.test.ts
var import_zod2 = require("zod");
// src/utils/json-schema.ts
var import_zod = require("zod");
function actionParametersToJsonSchema(actionParameters) {
let parameters = {};
for (let parameter of actionParameters || []) {
parameters[parameter.name] = convertAttribute(parameter);
}
let requiredParameterNames = [];
for (let arg of actionParameters || []) {
if (arg.required !== false) {
requiredParameterNames.push(arg.name);
}
}
return {
type: "object",
properties: parameters,
required: requiredParameterNames
};
}
function jsonSchemaToActionParameters(jsonSchema) {
if (jsonSchema.type !== "object" || !jsonSchema.properties) {
return [];
}
const parameters = [];
const requiredFields = jsonSchema.required || [];
for (const [name, schema] of Object.entries(jsonSchema.properties)) {
const parameter = convertJsonSchemaToParameter(name, schema, requiredFields.includes(name));
parameters.push(parameter);
}
return parameters;
}
function convertJsonSchemaToParameter(name, schema, isRequired) {
const baseParameter = {
name,
description: schema.description
};
if (!isRequired) {
baseParameter.required = false;
}
switch (schema.type) {
case "string":
return {
...baseParameter,
type: "string",
...schema.enum && { enum: schema.enum }
};
case "number":
case "boolean":
return {
...baseParameter,
type: schema.type
};
case "object":
if (schema.properties) {
const attributes = [];
const requiredFields = schema.required || [];
for (const [propName, propSchema] of Object.entries(schema.properties)) {
attributes.push(
convertJsonSchemaToParameter(propName, propSchema, requiredFields.includes(propName))
);
}
return {
...baseParameter,
type: "object",
attributes
};
}
return {
...baseParameter,
type: "object"
};
case "array":
if (schema.items.type === "object" && "properties" in schema.items) {
const attributes = [];
const requiredFields = schema.items.required || [];
for (const [propName, propSchema] of Object.entries(schema.items.properties || {})) {
attributes.push(
convertJsonSchemaToParameter(propName, propSchema, requiredFields.includes(propName))
);
}
return {
...baseParameter,
type: "object[]",
attributes
};
} else if (schema.items.type === "array") {
throw new Error("Nested arrays are not supported");
} else {
return {
...baseParameter,
type: `${schema.items.type}[]`
};
}
default:
return {
...baseParameter,
type: "string"
};
}
}
function convertAttribute(attribute) {
var _a, _b, _c;
switch (attribute.type) {
case "string":
return {
type: "string",
description: attribute.description,
...attribute.enum && { enum: attribute.enum }
};
case "number":
case "boolean":
return {
type: attribute.type,
description: attribute.description
};
case "object":
case "object[]":
const properties = (_a = attribute.attributes) == null ? void 0 : _a.reduce(
(acc, attr) => {
acc[attr.name] = convertAttribute(attr);
return acc;
},
{}
);
const required = (_b = attribute.attributes) == null ? void 0 : _b.filter((attr) => attr.required !== false).map((attr) => attr.name);
if (attribute.type === "object[]") {
return {
type: "array",
items: {
type: "object",
...properties && { properties },
...required && required.length > 0 && { required }
},
description: attribute.description
};
}
return {
type: "object",
description: attribute.description,
...properties && { properties },
...required && required.length > 0 && { required }
};
default:
if ((_c = attribute.type) == null ? void 0 : _c.endsWith("[]")) {
const itemType = attribute.type.slice(0, -2);
return {
type: "array",
items: { type: itemType },
description: attribute.description
};
}
return {
type: "string",
description: attribute.description
};
}
}
function convertJsonSchemaToZodSchema(jsonSchema, required) {
if (jsonSchema.type === "object") {
const spec = {};
if (!jsonSchema.properties || !Object.keys(jsonSchema.properties).length) {
return !required ? import_zod.z.object(spec).optional() : import_zod.z.object(spec);
}
for (const [key, value] of Object.entries(jsonSchema.properties)) {
spec[key] = convertJsonSchemaToZodSchema(
value,
jsonSchema.required ? jsonSchema.required.includes(key) : false
);
}
let schema = import_zod.z.object(spec).describe(jsonSchema.description);
return required ? schema : schema.optional();
} else if (jsonSchema.type === "string") {
let schema = import_zod.z.string().describe(jsonSchema.description);
return required ? schema : schema.optional();
} else if (jsonSchema.type === "number") {
let schema = import_zod.z.number().describe(jsonSchema.description);
return required ? schema : schema.optional();
} else if (jsonSchema.type === "boolean") {
let schema = import_zod.z.boolean().describe(jsonSchema.description);
return required ? schema : schema.optional();
} else if (jsonSchema.type === "array") {
let itemSchema = convertJsonSchemaToZodSchema(jsonSchema.items, true);
let schema = import_zod.z.array(itemSchema).describe(jsonSchema.description);
return required ? schema : schema.optional();
}
throw new Error("Invalid JSON schema");
}
// src/utils/json-schema.test.ts
var import_zod_to_json_schema = require("zod-to-json-schema");
describe("convertJsonSchemaToZodSchema", () => {
it("should convert a simple JSON schema to a Zod schema", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" }
},
required: ["name", "age"]
};
const expectedSchema = import_zod2.z.object({
name: import_zod2.z.string(),
age: import_zod2.z.number()
});
const result = convertJsonSchemaToZodSchema(jsonSchema, true);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should convert a JSON schema with nested objects to a Zod schema", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
address: {
type: "object",
properties: {
street: { type: "string" },
city: { type: "string" }
},
required: ["street", "city"]
}
},
required: ["name", "address"]
};
const expectedSchema = import_zod2.z.object({
name: import_zod2.z.string(),
address: import_zod2.z.object({
street: import_zod2.z.string(),
city: import_zod2.z.string()
})
});
const result = convertJsonSchemaToZodSchema(jsonSchema, true);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should convert a JSON schema with arrays to a Zod schema", () => {
const jsonSchema = {
type: "object",
properties: {
names: {
type: "array",
items: { type: "string" }
}
},
required: ["names"]
};
const expectedSchema = import_zod2.z.object({
names: import_zod2.z.array(import_zod2.z.string())
});
const result = convertJsonSchemaToZodSchema(jsonSchema, true);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should convert a JSON schema with optional properties to a Zod schema", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number", required: false }
}
};
const expectedSchema = import_zod2.z.object({
name: import_zod2.z.string().optional(),
age: import_zod2.z.number().optional()
}).optional();
const result = convertJsonSchemaToZodSchema(jsonSchema, false);
console.log(convertJsonSchemaToZodSchema(jsonSchema, false));
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should convert a JSON schema with different types to a Zod schema", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
isAdmin: { type: "boolean" }
},
required: ["name", "age", "isAdmin"]
};
const expectedSchema = import_zod2.z.object({
name: import_zod2.z.string(),
age: import_zod2.z.number(),
isAdmin: import_zod2.z.boolean()
});
const result = convertJsonSchemaToZodSchema(jsonSchema, true);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should handle edge case where JSON schema has no properties", () => {
const jsonSchema = {
type: "object"
};
const expectedSchema = import_zod2.z.object({});
const result = convertJsonSchemaToZodSchema(jsonSchema, true);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
it("should handle edge case where JSON schema has no required properties", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" }
}
};
const expectedSchema = import_zod2.z.object({
name: import_zod2.z.string().optional(),
age: import_zod2.z.number().optional()
}).optional();
const result = convertJsonSchemaToZodSchema(jsonSchema, false);
const resultSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(result);
const expectedSchemaJson = (0, import_zod_to_json_schema.zodToJsonSchema)(expectedSchema);
expect(resultSchemaJson).toStrictEqual(expectedSchemaJson);
});
});
describe("jsonSchemaToActionParameters", () => {
it("should convert a simple JSONSchema to Parameter array", () => {
const jsonSchema = {
type: "object",
properties: {
name: { type: "string", description: "User name" },
age: { type: "number", description: "User age" }
},
required: ["name"]
};
const expectedParameters = [
{ name: "name", type: "string", description: "User name" },
{ name: "age", type: "number", description: "User age", required: false }
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should convert JSONSchema with enum to Parameter array", () => {
const jsonSchema = {
type: "object",
properties: {
role: { type: "string", enum: ["admin", "user", "guest"], description: "User role" }
},
required: ["role"]
};
const expectedParameters = [
{ name: "role", type: "string", enum: ["admin", "user", "guest"], description: "User role" }
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should convert nested object JSONSchema to Parameter array", () => {
const jsonSchema = {
type: "object",
properties: {
user: {
type: "object",
properties: {
name: { type: "string", description: "User name" },
age: { type: "number", description: "User age" }
},
required: ["name"],
description: "User information"
}
},
required: ["user"]
};
const expectedParameters = [
{
name: "user",
type: "object",
description: "User information",
attributes: [
{ name: "name", type: "string", description: "User name" },
{ name: "age", type: "number", description: "User age", required: false }
]
}
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should convert array JSONSchema to Parameter array", () => {
const jsonSchema = {
type: "object",
properties: {
tags: {
type: "array",
items: { type: "string" },
description: "User tags"
}
},
required: ["tags"]
};
const expectedParameters = [
{ name: "tags", type: "string[]", description: "User tags" }
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should convert object array JSONSchema to Parameter array", () => {
const jsonSchema = {
type: "object",
properties: {
addresses: {
type: "array",
items: {
type: "object",
properties: {
street: { type: "string", description: "Street name" },
city: { type: "string", description: "City name" }
},
required: ["street"]
},
description: "User addresses"
}
},
required: ["addresses"]
};
const expectedParameters = [
{
name: "addresses",
type: "object[]",
description: "User addresses",
attributes: [
{ name: "street", type: "string", description: "Street name" },
{ name: "city", type: "string", description: "City name", required: false }
]
}
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should handle boolean types", () => {
const jsonSchema = {
type: "object",
properties: {
isAdmin: { type: "boolean", description: "Is user an admin" }
},
required: ["isAdmin"]
};
const expectedParameters = [
{ name: "isAdmin", type: "boolean", description: "Is user an admin" }
];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should handle empty object schema", () => {
const jsonSchema = {
type: "object"
};
const expectedParameters = [];
const result = jsonSchemaToActionParameters(jsonSchema);
expect(result).toEqual(expectedParameters);
});
it("should throw error for nested arrays", () => {
const jsonSchema = {
type: "object",
properties: {
nestedArray: {
type: "array",
items: {
type: "array",
items: { type: "string" }
},
description: "Matrix of strings"
}
},
required: ["nestedArray"]
};
expect(() => jsonSchemaToActionParameters(jsonSchema)).toThrow(
"Nested arrays are not supported"
);
});
it("should ensure round-trip conversion works", () => {
const originalParameters = [
{ name: "name", type: "string", description: "User name" },
{ name: "age", type: "number", description: "User age", required: false },
{ name: "role", type: "string", enum: ["admin", "user"], description: "User role" },
{
name: "address",
type: "object",
description: "User address",
attributes: [
{ name: "street", type: "string", description: "Street name" },
{ name: "city", type: "string", description: "City name" }
]
},
{
name: "contacts",
type: "object[]",
description: "User contacts",
attributes: [
{ name: "type", type: "string", description: "Contact type" },
{ name: "value", type: "string", description: "Contact value" }
]
}
];
const jsonSchema = actionParametersToJsonSchema(originalParameters);
const roundTripParameters = jsonSchemaToActionParameters(jsonSchema);
expect(roundTripParameters).toEqual(originalParameters);
});
});
//# sourceMappingURL=json-schema.test.js.map