@langchain/core
Version:
Core LangChain.js abstractions and schemas
1 lines • 9.68 kB
Source Map (JSON)
{"version":3,"file":"validation.cjs","names":["obj: Record<string, unknown>","obj: unknown","value: unknown","result: Record<string, unknown>"],"sources":["../../src/load/validation.ts"],"sourcesContent":["/**\n * Sentinel key used to mark escaped user objects during serialization.\n *\n * When a plain object contains 'lc' key (which could be confused with LC objects),\n * we wrap it as `{\"__lc_escaped__\": {...original...}}`.\n */\nexport const LC_ESCAPED_KEY = \"__lc_escaped__\";\n\n/**\n * Check if an object needs escaping to prevent confusion with LC objects.\n *\n * An object needs escaping if:\n * 1. It has an `'lc'` key (could be confused with LC serialization format)\n * 2. It has only the escape key (would be mistaken for an escaped object)\n */\nexport function needsEscaping(obj: Record<string, unknown>): boolean {\n return (\n \"lc\" in obj || (Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj)\n );\n}\n\n/**\n * Wrap an object in the escape marker.\n *\n * @example\n * ```typescript\n * {\"key\": \"value\"} // becomes {\"__lc_escaped__\": {\"key\": \"value\"}}\n * ```\n */\nexport function escapeObject(\n obj: Record<string, unknown>\n): Record<string, unknown> {\n return { [LC_ESCAPED_KEY]: obj };\n}\n\n/**\n * Check if an object is an escaped user object.\n *\n * @example\n * ```typescript\n * {\"__lc_escaped__\": {...}} // is an escaped object\n * ```\n */\nexport function isEscapedObject(obj: Record<string, unknown>): boolean {\n return Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj;\n}\n\n/**\n * Interface for objects that can be serialized.\n * This is a duck-typed interface to avoid circular imports.\n */\ninterface SerializableLike {\n lc_serializable: boolean;\n lc_secrets?: Record<string, string>;\n toJSON(): {\n lc: number;\n type: string;\n id: string[];\n kwargs?: Record<string, unknown>;\n };\n}\n\n/**\n * Check if an object looks like a Serializable instance (duck typing).\n */\nfunction isSerializableLike(obj: unknown): obj is SerializableLike {\n return (\n obj !== null &&\n typeof obj === \"object\" &&\n \"lc_serializable\" in obj &&\n typeof (obj as SerializableLike).toJSON === \"function\"\n );\n}\n\n/**\n * Create a \"not_implemented\" serialization result for objects that cannot be serialized.\n */\nfunction createNotImplemented(obj: unknown): {\n lc: 1;\n type: \"not_implemented\";\n id: string[];\n} {\n let id: string[];\n if (obj !== null && typeof obj === \"object\") {\n if (\"lc_id\" in obj && Array.isArray(obj.lc_id)) {\n id = obj.lc_id as string[];\n } else {\n id = [obj.constructor?.name ?? \"Object\"];\n }\n } else {\n id = [typeof obj];\n }\n return {\n lc: 1,\n type: \"not_implemented\",\n id,\n };\n}\n\n/**\n * Serialize a value with escaping of user objects.\n *\n * Called recursively on kwarg values to escape any plain objects that could be\n * confused with LC objects.\n *\n * @param obj - The value to serialize.\n * @returns The serialized value with user objects escaped as needed.\n */\nexport function serializeValue(obj: unknown): unknown {\n if (isSerializableLike(obj)) {\n // This is an LC object - serialize it properly (not escaped)\n return serializeLcObject(obj);\n }\n\n if (obj !== null && typeof obj === \"object\" && !Array.isArray(obj)) {\n const record = obj as Record<string, unknown>;\n // Check if object needs escaping BEFORE recursing into values.\n // If it needs escaping, wrap it as-is - the contents are user data that\n // will be returned as-is during deserialization (no instantiation).\n // This prevents re-escaping of already-escaped nested content.\n if (needsEscaping(record)) {\n return escapeObject(record);\n }\n // Safe object (no 'lc' key) - recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n result[key] = serializeValue(value);\n }\n return result;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => serializeValue(item));\n }\n\n if (\n typeof obj === \"string\" ||\n typeof obj === \"number\" ||\n typeof obj === \"boolean\" ||\n obj === null\n ) {\n return obj;\n }\n\n // Non-JSON-serializable object (Date, custom objects, etc.)\n return createNotImplemented(obj);\n}\n\n/**\n * Serialize a `Serializable` object with escaping of user data in kwargs.\n *\n * @param obj - The `Serializable` object to serialize.\n * @returns The serialized object with user data in kwargs escaped as needed.\n *\n * @remarks\n * Kwargs values are processed with `serializeValue` to escape user data (like\n * metadata) that contains `'lc'` keys. Secret fields (from `lc_secrets`) are\n * skipped because `toJSON()` replaces their values with secret markers.\n */\nexport function serializeLcObject(obj: SerializableLike): {\n lc: number;\n type: string;\n id: string[];\n kwargs?: Record<string, unknown>;\n} {\n // Secret fields are handled by toJSON() - it replaces values with secret markers\n const secretFields = new Set(Object.keys(obj.lc_secrets ?? {}));\n\n const serialized = { ...obj.toJSON() };\n\n // Process kwargs to escape user data that could be confused with LC objects\n // Skip secret fields - toJSON() already converted them to secret markers\n if (serialized.type === \"constructor\" && serialized.kwargs) {\n const newKwargs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(serialized.kwargs)) {\n if (secretFields.has(key)) {\n newKwargs[key] = value;\n } else {\n newKwargs[key] = serializeValue(value);\n }\n }\n serialized.kwargs = newKwargs;\n }\n\n return serialized;\n}\n\n/**\n * Escape a value if it needs escaping (contains `lc` key).\n *\n * This is a simpler version of `serializeValue` that doesn't handle Serializable\n * objects - it's meant to be called on kwargs values that have already been\n * processed by `toJSON()`.\n *\n * @param value - The value to potentially escape.\n * @returns The value with any `lc`-containing objects wrapped in escape markers.\n */\nexport function escapeIfNeeded(value: unknown): unknown {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n // Preserve Serializable objects - they have their own toJSON() that will be\n // called by JSON.stringify. We don't want to convert them to plain objects.\n if (isSerializableLike(value)) {\n return value;\n }\n const record = value as Record<string, unknown>;\n // Check if object needs escaping BEFORE recursing into values.\n // If it needs escaping, wrap it as-is - the contents are user data that\n // will be returned as-is during deserialization (no instantiation).\n if (needsEscaping(record)) {\n return escapeObject(record);\n }\n // Safe object (no 'lc' key) - recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n result[key] = escapeIfNeeded(val);\n }\n return result;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => escapeIfNeeded(item));\n }\n\n return value;\n}\n\n/**\n * Unescape a value, processing escape markers in object values and arrays.\n *\n * When an escaped object is encountered (`{\"__lc_escaped__\": ...}`), it's\n * unwrapped and the contents are returned AS-IS (no further processing).\n * The contents represent user data that should not be modified.\n *\n * For regular objects and arrays, we recurse to find any nested escape markers.\n *\n * @param obj - The value to unescape.\n * @returns The unescaped value.\n */\nexport function unescapeValue(obj: unknown): unknown {\n if (obj !== null && typeof obj === \"object\" && !Array.isArray(obj)) {\n const record = obj as Record<string, unknown>;\n if (isEscapedObject(record)) {\n // Unwrap and return the user data as-is (no further unescaping).\n // The contents are user data that may contain more escape keys,\n // but those are part of the user's actual data.\n return record[LC_ESCAPED_KEY];\n }\n\n // Regular object - recurse into values to find nested escape markers\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n result[key] = unescapeValue(value);\n }\n return result;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => unescapeValue(item));\n }\n\n return obj;\n}\n"],"mappings":";;;;;;;;AAMA,MAAa,iBAAiB;;;;;;;;AAS9B,SAAgB,cAAcA,KAAuC;AACnE,QACE,QAAQ,OAAQ,OAAO,KAAK,IAAI,CAAC,WAAW,KAAK,kBAAkB;AAEtE;;;;;;;;;AAUD,SAAgB,aACdA,KACyB;AACzB,QAAO,GAAG,iBAAiB,IAAK;AACjC;;;;;;;;;AAUD,SAAgB,gBAAgBA,KAAuC;AACrE,QAAO,OAAO,KAAK,IAAI,CAAC,WAAW,KAAK,kBAAkB;AAC3D;;;;AAoBD,SAAS,mBAAmBC,KAAuC;AACjE,QACE,QAAQ,QACR,OAAO,QAAQ,YACf,qBAAqB,OACrB,OAAQ,IAAyB,WAAW;AAE/C;;;;;;;;;;;AA6HD,SAAgB,eAAeC,OAAyB;AACtD,KAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,EAAE;AAGxE,MAAI,mBAAmB,MAAM,CAC3B,QAAO;EAET,MAAM,SAAS;AAIf,MAAI,cAAc,OAAO,CACvB,QAAO,aAAa,OAAO;EAG7B,MAAMC,SAAkC,CAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,IAAI,IAAI,OAAO,QAAQ,OAAO,EAC7C,OAAO,OAAO,eAAe,IAAI;AAEnC,SAAO;CACR;AAED,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,CAAC,SAAS,eAAe,KAAK,CAAC;AAGlD,QAAO;AACR;;;;;;;;;;;;;AAcD,SAAgB,cAAcF,KAAuB;AACnD,KAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,EAAE;EAClE,MAAM,SAAS;AACf,MAAI,gBAAgB,OAAO,CAIzB,QAAO,OAAO;EAIhB,MAAME,SAAkC,CAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,OAAO,EAC/C,OAAO,OAAO,cAAc,MAAM;AAEpC,SAAO;CACR;AAED,KAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IAAI,IAAI,CAAC,SAAS,cAAc,KAAK,CAAC;AAG/C,QAAO;AACR"}