UNPKG

@langchain/core

Version:
118 lines (117 loc) 4.05 kB
//#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