@langchain/core
Version:
Core LangChain.js abstractions and schemas
118 lines (117 loc) • 4.05 kB
JavaScript
//#region src/load/validation.ts
/**
* Sentinel key used to mark escaped user objects during serialization.
*
* When a plain object contains 'lc' key (which could be confused with LC objects),
* we wrap it as `{"__lc_escaped__": {...original...}}`.
*/
const LC_ESCAPED_KEY = "__lc_escaped__";
/**
* Check if an object needs escaping to prevent confusion with LC objects.
*
* An object needs escaping if:
* 1. It has an `'lc'` key (could be confused with LC serialization format)
* 2. It has only the escape key (would be mistaken for an escaped object)
*/
function needsEscaping(obj) {
return "lc" in obj || Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj;
}
/**
* Wrap an object in the escape marker.
*
* @example
* ```typescript
* {"key": "value"} // becomes {"__lc_escaped__": {"key": "value"}}
* ```
*/
function escapeObject(obj) {
return { [LC_ESCAPED_KEY]: obj };
}
/**
* Check if an object is an escaped user object.
*
* @example
* ```typescript
* {"__lc_escaped__": {...}} // is an escaped object
* ```
*/
function isEscapedObject(obj) {
return Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj;
}
/**
* Check if an object looks like a Serializable instance (duck typing).
*/
function isSerializableLike(obj) {
return obj !== null && typeof obj === "object" && "lc_serializable" in obj && typeof obj.toJSON === "function";
}
/**
* Create a "not_implemented" serialization result for objects that cannot be serialized.
*/
function createNotImplemented(obj) {
let id;
if (obj !== null && typeof obj === "object") if ("lc_id" in obj && Array.isArray(obj.lc_id)) id = obj.lc_id;
else id = [obj.constructor?.name ?? "Object"];
else id = [typeof obj];
return {
lc: 1,
type: "not_implemented",
id
};
}
/**
* Escape a value if it needs escaping (contains `lc` key).
*
* This is a simpler version of `serializeValue` that doesn't handle Serializable
* objects - it's meant to be called on kwargs values that have already been
* processed by `toJSON()`.
*
* @param value - The value to potentially escape.
* @param pathSet - WeakSet to track ancestor objects in the current path to detect circular references.
* Objects are removed after processing to allow shared references (same object in
* multiple places) while still detecting true circular references (ancestor in descendant).
* @returns The value with any `lc`-containing objects wrapped in escape markers.
*/
function escapeIfNeeded(value, pathSet = /* @__PURE__ */ new WeakSet()) {
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
if (pathSet.has(value)) return createNotImplemented(value);
if (isSerializableLike(value)) return value;
pathSet.add(value);
const record = value;
if (needsEscaping(record)) {
pathSet.delete(value);
return escapeObject(record);
}
const result = {};
for (const [key, val] of Object.entries(record)) result[key] = escapeIfNeeded(val, pathSet);
pathSet.delete(value);
return result;
}
if (Array.isArray(value)) return value.map((item) => escapeIfNeeded(item, pathSet));
return value;
}
/**
* Unescape a value, processing escape markers in object values and arrays.
*
* When an escaped object is encountered (`{"__lc_escaped__": ...}`), it's
* unwrapped and the contents are returned AS-IS (no further processing).
* The contents represent user data that should not be modified.
*
* For regular objects and arrays, we recurse to find any nested escape markers.
*
* @param obj - The value to unescape.
* @returns The unescaped value.
*/
function unescapeValue(obj) {
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
const record = obj;
if (isEscapedObject(record)) return record[LC_ESCAPED_KEY];
const result = {};
for (const [key, value] of Object.entries(record)) result[key] = unescapeValue(value);
return result;
}
if (Array.isArray(obj)) return obj.map((item) => unescapeValue(item));
return obj;
}
//#endregion
export { escapeIfNeeded, isEscapedObject, unescapeValue };
//# sourceMappingURL=validation.js.map