@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
168 lines (167 loc) • 6.63 kB
JavaScript
function toJsonSchema(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (key === "$schema") continue;
result[key] = value;
}
return result;
}
function isPropertyCarrier(schema) {
return (typeof schema === "object" || typeof schema === "function") && schema !== null;
}
function isStandardJSONSchema(schema) {
if (!isPropertyCarrier(schema) || !("~standard" in schema)) return false;
const standard = schema["~standard"];
if (typeof standard !== "object" || standard === null || !("version" in standard) || standard.version !== 1 || !("jsonSchema" in standard) || typeof standard.jsonSchema !== "object" || standard.jsonSchema === null || !("input" in standard.jsonSchema)) {
return false;
}
return typeof standard.jsonSchema.input === "function";
}
function isStandardSchema(schema) {
return isPropertyCarrier(schema) && "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && "version" in schema["~standard"] && schema["~standard"].version === 1 && "validate" in schema["~standard"] && typeof schema["~standard"].validate === "function";
}
function pruneMap(map) {
return Object.keys(map).length > 0 ? map : void 0;
}
function makeStructuredOutputCompatible(schema, originalRequired = []) {
const result = { ...schema };
const map = {};
if (result.type === "object" && result.properties) {
const properties = { ...result.properties };
const allPropertyNames = Object.keys(properties);
const propertyMaps = {};
for (const propName of allPropertyNames) {
const prop = properties[propName];
if (!prop) continue;
const wasOptional = !originalRequired.includes(propName);
let widenedHere = false;
let childMap;
if (prop.type === "object" && prop.properties) {
const nested = makeStructuredOutputCompatible(prop, prop.required || []);
properties[propName] = wasOptional ? { ...nested.schema, type: ["object", "null"] } : nested.schema;
widenedHere = wasOptional;
childMap = nested.nullWidening;
} else if (prop.type === "array" && prop.items) {
const items = Array.isArray(prop.items) ? prop.items[0] : prop.items;
const nestedItems = items ? makeStructuredOutputCompatible(items, items.required || []) : void 0;
properties[propName] = {
...prop,
items: nestedItems ? nestedItems.schema : prop.items,
...wasOptional ? { type: ["array", "null"] } : {}
};
widenedHere = wasOptional;
childMap = nestedItems?.nullWidening ? { items: nestedItems.nullWidening } : void 0;
} else if (wasOptional) {
if (prop.type && !Array.isArray(prop.type)) {
properties[propName] = { ...prop, type: [prop.type, "null"] };
widenedHere = true;
} else if (Array.isArray(prop.type) && !prop.type.includes("null")) {
properties[propName] = { ...prop, type: [...prop.type, "null"] };
widenedHere = true;
}
}
if (widenedHere || childMap) {
propertyMaps[propName] = {
...childMap ?? {},
...widenedHere ? { widened: true } : {}
};
}
}
result.properties = properties;
result.required = allPropertyNames;
result.additionalProperties = false;
if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps;
}
if (result.type === "array" && result.items) {
const items = Array.isArray(result.items) ? result.items[0] : result.items;
if (items) {
const nestedItems = makeStructuredOutputCompatible(
items,
items.required || []
);
result.items = nestedItems.schema;
if (nestedItems.nullWidening) map.items = nestedItems.nullWidening;
}
}
return { schema: result, nullWidening: pruneMap(map) };
}
function toTypedJsonSchema(schema) {
if (isStandardJSONSchema(schema)) {
const jsonSchema = schema["~standard"].jsonSchema.input({
target: "draft-07"
});
const result = toJsonSchema(jsonSchema);
if ("properties" in result && !result.type) result.type = "object";
if (result.type === "object" && !("properties" in result)) {
result.properties = {};
}
if (result.type === "object" && !("required" in result)) {
result.required = [];
}
return result;
}
if (isStandardSchema(schema)) {
throw new Error(
"Schema is a Standard Schema validator but does not expose a JSON Schema converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, or wrap a Valibot schema with `toStandardJsonSchema()` from `@valibot/to-json-schema` before passing it as `outputSchema`."
);
}
if (typeof schema !== "object") return schema;
return toJsonSchema(schema);
}
function convertSchemaToJsonSchema(schema, options = {}) {
if (!schema) return void 0;
const { forStructuredOutput = false } = options;
if (!forStructuredOutput && !isStandardJSONSchema(schema) && !isStandardSchema(schema)) {
return schema;
}
const base = toTypedJsonSchema(schema);
if (!base || typeof base !== "object") return base;
if (!forStructuredOutput) return base;
return makeStructuredOutputCompatible(base, base.required || []).schema;
}
function convertSchemaForStructuredOutput(schema) {
if (!schema) return { jsonSchema: void 0, nullWideningMap: void 0 };
const base = toTypedJsonSchema(schema);
if (!base || typeof base !== "object") {
return { jsonSchema: base, nullWideningMap: void 0 };
}
const { schema: jsonSchema, nullWidening } = makeStructuredOutputCompatible(
base,
base.required || []
);
return { jsonSchema, nullWideningMap: nullWidening };
}
class StandardSchemaValidationError extends Error {
name = "StandardSchemaValidationError";
issues;
constructor(issues) {
super(
`Validation failed: ${issues.map((i) => i.message || "Validation failed").join(", ")}`
);
this.issues = issues;
}
}
function parseWithStandardSchema(schema, data) {
if (!isStandardSchema(schema)) {
return data;
}
const result = schema["~standard"].validate(data);
if (result instanceof Promise) {
throw new Error(
"Schema validation returned a Promise. Use validateWithStandardSchema for async validation."
);
}
if (!result.issues) {
return result.value;
}
throw new StandardSchemaValidationError(result.issues);
}
export {
StandardSchemaValidationError,
convertSchemaForStructuredOutput,
convertSchemaToJsonSchema,
isStandardJSONSchema,
isStandardSchema,
parseWithStandardSchema
};
//# sourceMappingURL=schema-converter.js.map