@librechat/agents 3.2.43 → 3.2.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,19 @@ function createRemoveAllMessage() {
23
23
  return new _langchain_core_messages.RemoveMessage({ id: REMOVE_ALL_MESSAGES });
24
24
  }
25
25
  /**
26
+ * Coerce each entry to a {@link BaseMessage} in a single pass, skipping
27
+ * null/undefined entries. Providers can emit empty/partial stream chunks that
28
+ * arrive as `undefined`; passing those to `coerceMessageLikeToMessage` throws
29
+ * "Cannot read properties of undefined (reading 'role')" and crashes the run.
30
+ * Folding the null check into the coercion loop avoids a second pass over the
31
+ * array. Refs LibreChat Discussion #12284.
32
+ */
33
+ function coerceMessages(messages) {
34
+ const coerced = [];
35
+ for (const message of messages) if (message != null) coerced.push((0, _langchain_core_messages.coerceMessageLikeToMessage)(message));
36
+ return coerced;
37
+ }
38
+ /**
26
39
  * Prebuilt reducer that combines returned messages.
27
40
  * Can handle standard messages and special modifiers like {@link RemoveMessage}
28
41
  * instances.
@@ -30,8 +43,8 @@ function createRemoveAllMessage() {
30
43
  function messagesStateReducer(left, right) {
31
44
  const leftArray = Array.isArray(left) ? left : [left];
32
45
  const rightArray = Array.isArray(right) ? right : [right];
33
- const leftMessages = leftArray.map(_langchain_core_messages.coerceMessageLikeToMessage);
34
- const rightMessages = rightArray.map(_langchain_core_messages.coerceMessageLikeToMessage);
46
+ const leftMessages = coerceMessages(leftArray);
47
+ const rightMessages = coerceMessages(rightArray);
35
48
  for (const m of leftMessages) if (m.id === null || m.id === void 0) {
36
49
  m.id = (0, uuid.v4)();
37
50
  m.lc_kwargs.id = m.id;
@@ -1 +1 @@
1
- {"version":3,"file":"reducer.cjs","names":["RemoveMessage","coerceMessageLikeToMessage"],"sources":["../../../src/messages/reducer.ts"],"sourcesContent":["import { v4 } from 'uuid';\nimport {\n BaseMessage,\n RemoveMessage,\n BaseMessageLike,\n coerceMessageLikeToMessage,\n} from '@langchain/core/messages';\n\nexport const REMOVE_ALL_MESSAGES = '__remove_all__';\n\n/**\n * Creates a message that instructs messagesStateReducer to remove ALL\n * existing messages from state. Messages appearing after this one in\n * the array become the new state.\n *\n * Usage (in a node return value):\n * ```ts\n * return { messages: [createRemoveAllMessage(), ...survivingMessages] };\n * ```\n *\n * This works because the reducer checks for `getType() === 'remove'`\n * with `id === REMOVE_ALL_MESSAGES` and discards everything before it.\n *\n * NOTE: Uses RemoveMessage from @langchain/core with a sentinel id so\n * the reducer can distinguish a \"remove-all\" marker from a single-message\n * removal.\n */\nexport function createRemoveAllMessage(): BaseMessage {\n return new RemoveMessage({ id: REMOVE_ALL_MESSAGES });\n}\n\nexport type Messages =\n | Array<BaseMessage | BaseMessageLike>\n | BaseMessage\n | BaseMessageLike;\n\n/**\n * Prebuilt reducer that combines returned messages.\n * Can handle standard messages and special modifiers like {@link RemoveMessage}\n * instances.\n */\nexport function messagesStateReducer(\n left: Messages,\n right: Messages\n): BaseMessage[] {\n const leftArray = Array.isArray(left) ? left : [left];\n const rightArray = Array.isArray(right) ? right : [right];\n // coerce to message\n const leftMessages = (leftArray as BaseMessageLike[]).map(\n coerceMessageLikeToMessage\n );\n const rightMessages = (rightArray as BaseMessageLike[]).map(\n coerceMessageLikeToMessage\n );\n // assign missing ids\n for (const m of leftMessages) {\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n }\n\n let removeAllIdx: number | undefined;\n for (let i = 0; i < rightMessages.length; i += 1) {\n const m = rightMessages[i];\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n\n if (m.getType() === 'remove' && m.id === REMOVE_ALL_MESSAGES) {\n removeAllIdx = i;\n }\n }\n\n if (removeAllIdx != null) return rightMessages.slice(removeAllIdx + 1);\n\n // merge\n const merged = [...leftMessages];\n const mergedById = new Map(merged.map((m, i) => [m.id, i]));\n const idsToRemove = new Set();\n for (const m of rightMessages) {\n const existingIdx = mergedById.get(m.id);\n if (existingIdx !== undefined) {\n if (m.getType() === 'remove') {\n idsToRemove.add(m.id);\n } else {\n idsToRemove.delete(m.id);\n merged[existingIdx] = m;\n }\n } else {\n if (m.getType() === 'remove') {\n throw new Error(\n `Attempting to delete a message with an ID that doesn't exist ('${m.id}')`\n );\n }\n mergedById.set(m.id, merged.length);\n merged.push(m);\n }\n }\n return merged.filter((m) => !idsToRemove.has(m.id));\n}\n"],"mappings":";;;AAQA,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,yBAAsC;CACpD,OAAO,IAAIA,yBAAAA,cAAc,EAAE,IAAI,oBAAoB,CAAC;AACtD;;;;;;AAYA,SAAgB,qBACd,MACA,OACe;CACf,MAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;CACpD,MAAM,aAAa,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAExD,MAAM,eAAgB,UAAgC,IACpDC,yBAAAA,0BACF;CACA,MAAM,gBAAiB,WAAiC,IACtDA,yBAAAA,0BACF;CAEA,KAAK,MAAM,KAAK,cACd,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;EACvC,EAAE,MAAA,GAAA,KAAA,GAAA,CAAQ;EACV,EAAE,UAAU,KAAK,EAAE;CACrB;CAGF,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,GAAG;EAChD,MAAM,IAAI,cAAc;EACxB,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;GACvC,EAAE,MAAA,GAAA,KAAA,GAAA,CAAQ;GACV,EAAE,UAAU,KAAK,EAAE;EACrB;EAEA,IAAI,EAAE,QAAQ,MAAM,YAAY,EAAE,OAAA,kBAChC,eAAe;CAEnB;CAEA,IAAI,gBAAgB,MAAM,OAAO,cAAc,MAAM,eAAe,CAAC;CAGrE,MAAM,SAAS,CAAC,GAAG,YAAY;CAC/B,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAC1D,MAAM,8BAAc,IAAI,IAAI;CAC5B,KAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,WAAW,IAAI,EAAE,EAAE;EACvC,IAAI,gBAAgB,KAAA,GAClB,IAAI,EAAE,QAAQ,MAAM,UAClB,YAAY,IAAI,EAAE,EAAE;OACf;GACL,YAAY,OAAO,EAAE,EAAE;GACvB,OAAO,eAAe;EACxB;OACK;GACL,IAAI,EAAE,QAAQ,MAAM,UAClB,MAAM,IAAI,MACR,kEAAkE,EAAE,GAAG,GACzE;GAEF,WAAW,IAAI,EAAE,IAAI,OAAO,MAAM;GAClC,OAAO,KAAK,CAAC;EACf;CACF;CACA,OAAO,OAAO,QAAQ,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AACpD"}
1
+ {"version":3,"file":"reducer.cjs","names":["RemoveMessage"],"sources":["../../../src/messages/reducer.ts"],"sourcesContent":["import { v4 } from 'uuid';\nimport {\n BaseMessage,\n RemoveMessage,\n BaseMessageLike,\n coerceMessageLikeToMessage,\n} from '@langchain/core/messages';\n\nexport const REMOVE_ALL_MESSAGES = '__remove_all__';\n\n/**\n * Creates a message that instructs messagesStateReducer to remove ALL\n * existing messages from state. Messages appearing after this one in\n * the array become the new state.\n *\n * Usage (in a node return value):\n * ```ts\n * return { messages: [createRemoveAllMessage(), ...survivingMessages] };\n * ```\n *\n * This works because the reducer checks for `getType() === 'remove'`\n * with `id === REMOVE_ALL_MESSAGES` and discards everything before it.\n *\n * NOTE: Uses RemoveMessage from @langchain/core with a sentinel id so\n * the reducer can distinguish a \"remove-all\" marker from a single-message\n * removal.\n */\nexport function createRemoveAllMessage(): BaseMessage {\n return new RemoveMessage({ id: REMOVE_ALL_MESSAGES });\n}\n\nexport type Messages =\n | Array<BaseMessage | BaseMessageLike>\n | BaseMessage\n | BaseMessageLike;\n\n/**\n * Coerce each entry to a {@link BaseMessage} in a single pass, skipping\n * null/undefined entries. Providers can emit empty/partial stream chunks that\n * arrive as `undefined`; passing those to `coerceMessageLikeToMessage` throws\n * \"Cannot read properties of undefined (reading 'role')\" and crashes the run.\n * Folding the null check into the coercion loop avoids a second pass over the\n * array. Refs LibreChat Discussion #12284.\n */\nfunction coerceMessages(\n messages: ReadonlyArray<BaseMessageLike | null | undefined>\n): BaseMessage[] {\n const coerced: BaseMessage[] = [];\n for (const message of messages) {\n if (message != null) {\n coerced.push(coerceMessageLikeToMessage(message));\n }\n }\n return coerced;\n}\n\n/**\n * Prebuilt reducer that combines returned messages.\n * Can handle standard messages and special modifiers like {@link RemoveMessage}\n * instances.\n */\nexport function messagesStateReducer(\n left: Messages,\n right: Messages\n): BaseMessage[] {\n const leftArray = Array.isArray(left) ? left : [left];\n const rightArray = Array.isArray(right) ? right : [right];\n // coerce to message, skipping null/undefined entries in the same pass\n const leftMessages = coerceMessages(leftArray as BaseMessageLike[]);\n const rightMessages = coerceMessages(rightArray as BaseMessageLike[]);\n // assign missing ids\n for (const m of leftMessages) {\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n }\n\n let removeAllIdx: number | undefined;\n for (let i = 0; i < rightMessages.length; i += 1) {\n const m = rightMessages[i];\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n\n if (m.getType() === 'remove' && m.id === REMOVE_ALL_MESSAGES) {\n removeAllIdx = i;\n }\n }\n\n if (removeAllIdx != null) return rightMessages.slice(removeAllIdx + 1);\n\n // merge\n const merged = [...leftMessages];\n const mergedById = new Map(merged.map((m, i) => [m.id, i]));\n const idsToRemove = new Set();\n for (const m of rightMessages) {\n const existingIdx = mergedById.get(m.id);\n if (existingIdx !== undefined) {\n if (m.getType() === 'remove') {\n idsToRemove.add(m.id);\n } else {\n idsToRemove.delete(m.id);\n merged[existingIdx] = m;\n }\n } else {\n if (m.getType() === 'remove') {\n throw new Error(\n `Attempting to delete a message with an ID that doesn't exist ('${m.id}')`\n );\n }\n mergedById.set(m.id, merged.length);\n merged.push(m);\n }\n }\n return merged.filter((m) => !idsToRemove.has(m.id));\n}\n"],"mappings":";;;AAQA,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,yBAAsC;CACpD,OAAO,IAAIA,yBAAAA,cAAc,EAAE,IAAI,oBAAoB,CAAC;AACtD;;;;;;;;;AAeA,SAAS,eACP,UACe;CACf,MAAM,UAAyB,CAAC;CAChC,KAAK,MAAM,WAAW,UACpB,IAAI,WAAW,MACb,QAAQ,MAAA,GAAA,yBAAA,2BAAA,CAAgC,OAAO,CAAC;CAGpD,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,MACA,OACe;CACf,MAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;CACpD,MAAM,aAAa,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAExD,MAAM,eAAe,eAAe,SAA8B;CAClE,MAAM,gBAAgB,eAAe,UAA+B;CAEpE,KAAK,MAAM,KAAK,cACd,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;EACvC,EAAE,MAAA,GAAA,KAAA,GAAA,CAAQ;EACV,EAAE,UAAU,KAAK,EAAE;CACrB;CAGF,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,GAAG;EAChD,MAAM,IAAI,cAAc;EACxB,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;GACvC,EAAE,MAAA,GAAA,KAAA,GAAA,CAAQ;GACV,EAAE,UAAU,KAAK,EAAE;EACrB;EAEA,IAAI,EAAE,QAAQ,MAAM,YAAY,EAAE,OAAA,kBAChC,eAAe;CAEnB;CAEA,IAAI,gBAAgB,MAAM,OAAO,cAAc,MAAM,eAAe,CAAC;CAGrE,MAAM,SAAS,CAAC,GAAG,YAAY;CAC/B,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAC1D,MAAM,8BAAc,IAAI,IAAI;CAC5B,KAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,WAAW,IAAI,EAAE,EAAE;EACvC,IAAI,gBAAgB,KAAA,GAClB,IAAI,EAAE,QAAQ,MAAM,UAClB,YAAY,IAAI,EAAE,EAAE;OACf;GACL,YAAY,OAAO,EAAE,EAAE;GACvB,OAAO,eAAe;EACxB;OACK;GACL,IAAI,EAAE,QAAQ,MAAM,UAClB,MAAM,IAAI,MACR,kEAAkE,EAAE,GAAG,GACzE;GAEF,WAAW,IAAI,EAAE,IAAI,OAAO,MAAM;GAClC,OAAO,KAAK,CAAC;EACf;CACF;CACA,OAAO,OAAO,QAAQ,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AACpD"}
@@ -23,6 +23,19 @@ function createRemoveAllMessage() {
23
23
  return new RemoveMessage({ id: REMOVE_ALL_MESSAGES });
24
24
  }
25
25
  /**
26
+ * Coerce each entry to a {@link BaseMessage} in a single pass, skipping
27
+ * null/undefined entries. Providers can emit empty/partial stream chunks that
28
+ * arrive as `undefined`; passing those to `coerceMessageLikeToMessage` throws
29
+ * "Cannot read properties of undefined (reading 'role')" and crashes the run.
30
+ * Folding the null check into the coercion loop avoids a second pass over the
31
+ * array. Refs LibreChat Discussion #12284.
32
+ */
33
+ function coerceMessages(messages) {
34
+ const coerced = [];
35
+ for (const message of messages) if (message != null) coerced.push(coerceMessageLikeToMessage(message));
36
+ return coerced;
37
+ }
38
+ /**
26
39
  * Prebuilt reducer that combines returned messages.
27
40
  * Can handle standard messages and special modifiers like {@link RemoveMessage}
28
41
  * instances.
@@ -30,8 +43,8 @@ function createRemoveAllMessage() {
30
43
  function messagesStateReducer(left, right) {
31
44
  const leftArray = Array.isArray(left) ? left : [left];
32
45
  const rightArray = Array.isArray(right) ? right : [right];
33
- const leftMessages = leftArray.map(coerceMessageLikeToMessage);
34
- const rightMessages = rightArray.map(coerceMessageLikeToMessage);
46
+ const leftMessages = coerceMessages(leftArray);
47
+ const rightMessages = coerceMessages(rightArray);
35
48
  for (const m of leftMessages) if (m.id === null || m.id === void 0) {
36
49
  m.id = v4();
37
50
  m.lc_kwargs.id = m.id;
@@ -1 +1 @@
1
- {"version":3,"file":"reducer.mjs","names":[],"sources":["../../../src/messages/reducer.ts"],"sourcesContent":["import { v4 } from 'uuid';\nimport {\n BaseMessage,\n RemoveMessage,\n BaseMessageLike,\n coerceMessageLikeToMessage,\n} from '@langchain/core/messages';\n\nexport const REMOVE_ALL_MESSAGES = '__remove_all__';\n\n/**\n * Creates a message that instructs messagesStateReducer to remove ALL\n * existing messages from state. Messages appearing after this one in\n * the array become the new state.\n *\n * Usage (in a node return value):\n * ```ts\n * return { messages: [createRemoveAllMessage(), ...survivingMessages] };\n * ```\n *\n * This works because the reducer checks for `getType() === 'remove'`\n * with `id === REMOVE_ALL_MESSAGES` and discards everything before it.\n *\n * NOTE: Uses RemoveMessage from @langchain/core with a sentinel id so\n * the reducer can distinguish a \"remove-all\" marker from a single-message\n * removal.\n */\nexport function createRemoveAllMessage(): BaseMessage {\n return new RemoveMessage({ id: REMOVE_ALL_MESSAGES });\n}\n\nexport type Messages =\n | Array<BaseMessage | BaseMessageLike>\n | BaseMessage\n | BaseMessageLike;\n\n/**\n * Prebuilt reducer that combines returned messages.\n * Can handle standard messages and special modifiers like {@link RemoveMessage}\n * instances.\n */\nexport function messagesStateReducer(\n left: Messages,\n right: Messages\n): BaseMessage[] {\n const leftArray = Array.isArray(left) ? left : [left];\n const rightArray = Array.isArray(right) ? right : [right];\n // coerce to message\n const leftMessages = (leftArray as BaseMessageLike[]).map(\n coerceMessageLikeToMessage\n );\n const rightMessages = (rightArray as BaseMessageLike[]).map(\n coerceMessageLikeToMessage\n );\n // assign missing ids\n for (const m of leftMessages) {\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n }\n\n let removeAllIdx: number | undefined;\n for (let i = 0; i < rightMessages.length; i += 1) {\n const m = rightMessages[i];\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n\n if (m.getType() === 'remove' && m.id === REMOVE_ALL_MESSAGES) {\n removeAllIdx = i;\n }\n }\n\n if (removeAllIdx != null) return rightMessages.slice(removeAllIdx + 1);\n\n // merge\n const merged = [...leftMessages];\n const mergedById = new Map(merged.map((m, i) => [m.id, i]));\n const idsToRemove = new Set();\n for (const m of rightMessages) {\n const existingIdx = mergedById.get(m.id);\n if (existingIdx !== undefined) {\n if (m.getType() === 'remove') {\n idsToRemove.add(m.id);\n } else {\n idsToRemove.delete(m.id);\n merged[existingIdx] = m;\n }\n } else {\n if (m.getType() === 'remove') {\n throw new Error(\n `Attempting to delete a message with an ID that doesn't exist ('${m.id}')`\n );\n }\n mergedById.set(m.id, merged.length);\n merged.push(m);\n }\n }\n return merged.filter((m) => !idsToRemove.has(m.id));\n}\n"],"mappings":";;;AAQA,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,yBAAsC;CACpD,OAAO,IAAI,cAAc,EAAE,IAAI,oBAAoB,CAAC;AACtD;;;;;;AAYA,SAAgB,qBACd,MACA,OACe;CACf,MAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;CACpD,MAAM,aAAa,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAExD,MAAM,eAAgB,UAAgC,IACpD,0BACF;CACA,MAAM,gBAAiB,WAAiC,IACtD,0BACF;CAEA,KAAK,MAAM,KAAK,cACd,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;EACvC,EAAE,KAAK,GAAG;EACV,EAAE,UAAU,KAAK,EAAE;CACrB;CAGF,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,GAAG;EAChD,MAAM,IAAI,cAAc;EACxB,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;GACvC,EAAE,KAAK,GAAG;GACV,EAAE,UAAU,KAAK,EAAE;EACrB;EAEA,IAAI,EAAE,QAAQ,MAAM,YAAY,EAAE,OAAA,kBAChC,eAAe;CAEnB;CAEA,IAAI,gBAAgB,MAAM,OAAO,cAAc,MAAM,eAAe,CAAC;CAGrE,MAAM,SAAS,CAAC,GAAG,YAAY;CAC/B,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAC1D,MAAM,8BAAc,IAAI,IAAI;CAC5B,KAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,WAAW,IAAI,EAAE,EAAE;EACvC,IAAI,gBAAgB,KAAA,GAClB,IAAI,EAAE,QAAQ,MAAM,UAClB,YAAY,IAAI,EAAE,EAAE;OACf;GACL,YAAY,OAAO,EAAE,EAAE;GACvB,OAAO,eAAe;EACxB;OACK;GACL,IAAI,EAAE,QAAQ,MAAM,UAClB,MAAM,IAAI,MACR,kEAAkE,EAAE,GAAG,GACzE;GAEF,WAAW,IAAI,EAAE,IAAI,OAAO,MAAM;GAClC,OAAO,KAAK,CAAC;EACf;CACF;CACA,OAAO,OAAO,QAAQ,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AACpD"}
1
+ {"version":3,"file":"reducer.mjs","names":[],"sources":["../../../src/messages/reducer.ts"],"sourcesContent":["import { v4 } from 'uuid';\nimport {\n BaseMessage,\n RemoveMessage,\n BaseMessageLike,\n coerceMessageLikeToMessage,\n} from '@langchain/core/messages';\n\nexport const REMOVE_ALL_MESSAGES = '__remove_all__';\n\n/**\n * Creates a message that instructs messagesStateReducer to remove ALL\n * existing messages from state. Messages appearing after this one in\n * the array become the new state.\n *\n * Usage (in a node return value):\n * ```ts\n * return { messages: [createRemoveAllMessage(), ...survivingMessages] };\n * ```\n *\n * This works because the reducer checks for `getType() === 'remove'`\n * with `id === REMOVE_ALL_MESSAGES` and discards everything before it.\n *\n * NOTE: Uses RemoveMessage from @langchain/core with a sentinel id so\n * the reducer can distinguish a \"remove-all\" marker from a single-message\n * removal.\n */\nexport function createRemoveAllMessage(): BaseMessage {\n return new RemoveMessage({ id: REMOVE_ALL_MESSAGES });\n}\n\nexport type Messages =\n | Array<BaseMessage | BaseMessageLike>\n | BaseMessage\n | BaseMessageLike;\n\n/**\n * Coerce each entry to a {@link BaseMessage} in a single pass, skipping\n * null/undefined entries. Providers can emit empty/partial stream chunks that\n * arrive as `undefined`; passing those to `coerceMessageLikeToMessage` throws\n * \"Cannot read properties of undefined (reading 'role')\" and crashes the run.\n * Folding the null check into the coercion loop avoids a second pass over the\n * array. Refs LibreChat Discussion #12284.\n */\nfunction coerceMessages(\n messages: ReadonlyArray<BaseMessageLike | null | undefined>\n): BaseMessage[] {\n const coerced: BaseMessage[] = [];\n for (const message of messages) {\n if (message != null) {\n coerced.push(coerceMessageLikeToMessage(message));\n }\n }\n return coerced;\n}\n\n/**\n * Prebuilt reducer that combines returned messages.\n * Can handle standard messages and special modifiers like {@link RemoveMessage}\n * instances.\n */\nexport function messagesStateReducer(\n left: Messages,\n right: Messages\n): BaseMessage[] {\n const leftArray = Array.isArray(left) ? left : [left];\n const rightArray = Array.isArray(right) ? right : [right];\n // coerce to message, skipping null/undefined entries in the same pass\n const leftMessages = coerceMessages(leftArray as BaseMessageLike[]);\n const rightMessages = coerceMessages(rightArray as BaseMessageLike[]);\n // assign missing ids\n for (const m of leftMessages) {\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n }\n\n let removeAllIdx: number | undefined;\n for (let i = 0; i < rightMessages.length; i += 1) {\n const m = rightMessages[i];\n if (m.id === null || m.id === undefined) {\n m.id = v4();\n m.lc_kwargs.id = m.id;\n }\n\n if (m.getType() === 'remove' && m.id === REMOVE_ALL_MESSAGES) {\n removeAllIdx = i;\n }\n }\n\n if (removeAllIdx != null) return rightMessages.slice(removeAllIdx + 1);\n\n // merge\n const merged = [...leftMessages];\n const mergedById = new Map(merged.map((m, i) => [m.id, i]));\n const idsToRemove = new Set();\n for (const m of rightMessages) {\n const existingIdx = mergedById.get(m.id);\n if (existingIdx !== undefined) {\n if (m.getType() === 'remove') {\n idsToRemove.add(m.id);\n } else {\n idsToRemove.delete(m.id);\n merged[existingIdx] = m;\n }\n } else {\n if (m.getType() === 'remove') {\n throw new Error(\n `Attempting to delete a message with an ID that doesn't exist ('${m.id}')`\n );\n }\n mergedById.set(m.id, merged.length);\n merged.push(m);\n }\n }\n return merged.filter((m) => !idsToRemove.has(m.id));\n}\n"],"mappings":";;;AAQA,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,yBAAsC;CACpD,OAAO,IAAI,cAAc,EAAE,IAAI,oBAAoB,CAAC;AACtD;;;;;;;;;AAeA,SAAS,eACP,UACe;CACf,MAAM,UAAyB,CAAC;CAChC,KAAK,MAAM,WAAW,UACpB,IAAI,WAAW,MACb,QAAQ,KAAK,2BAA2B,OAAO,CAAC;CAGpD,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,MACA,OACe;CACf,MAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;CACpD,MAAM,aAAa,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAExD,MAAM,eAAe,eAAe,SAA8B;CAClE,MAAM,gBAAgB,eAAe,UAA+B;CAEpE,KAAK,MAAM,KAAK,cACd,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;EACvC,EAAE,KAAK,GAAG;EACV,EAAE,UAAU,KAAK,EAAE;CACrB;CAGF,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,GAAG;EAChD,MAAM,IAAI,cAAc;EACxB,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAA,GAAW;GACvC,EAAE,KAAK,GAAG;GACV,EAAE,UAAU,KAAK,EAAE;EACrB;EAEA,IAAI,EAAE,QAAQ,MAAM,YAAY,EAAE,OAAA,kBAChC,eAAe;CAEnB;CAEA,IAAI,gBAAgB,MAAM,OAAO,cAAc,MAAM,eAAe,CAAC;CAGrE,MAAM,SAAS,CAAC,GAAG,YAAY;CAC/B,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAC1D,MAAM,8BAAc,IAAI,IAAI;CAC5B,KAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,WAAW,IAAI,EAAE,EAAE;EACvC,IAAI,gBAAgB,KAAA,GAClB,IAAI,EAAE,QAAQ,MAAM,UAClB,YAAY,IAAI,EAAE,EAAE;OACf;GACL,YAAY,OAAO,EAAE,EAAE;GACvB,OAAO,eAAe;EACxB;OACK;GACL,IAAI,EAAE,QAAQ,MAAM,UAClB,MAAM,IAAI,MACR,kEAAkE,EAAE,GAAG,GACzE;GAEF,WAAW,IAAI,EAAE,IAAI,OAAO,MAAM;GAClC,OAAO,KAAK,CAAC;EACf;CACF;CACA,OAAO,OAAO,QAAQ,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AACpD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.2.43",
3
+ "version": "3.2.44",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -0,0 +1,32 @@
1
+ import { HumanMessage } from '@langchain/core/messages';
2
+ import { messagesStateReducer } from './reducer';
3
+
4
+ describe('messagesStateReducer', () => {
5
+ it('drops null/undefined entries and preserves order', () => {
6
+ const a = new HumanMessage({ id: 'a', content: 'a' });
7
+ const b = new HumanMessage({ id: 'b', content: 'b' });
8
+ const c = new HumanMessage({ id: 'c', content: 'c' });
9
+
10
+ const result = messagesStateReducer(
11
+ [a, null, b] as never,
12
+ [undefined, c] as never
13
+ );
14
+
15
+ expect(result.map((m) => m.id)).toEqual(['a', 'b', 'c']);
16
+ });
17
+
18
+ it('does not throw when every entry is null/undefined', () => {
19
+ expect(() =>
20
+ messagesStateReducer(
21
+ [null, undefined] as never,
22
+ [undefined, null] as never
23
+ )
24
+ ).not.toThrow();
25
+
26
+ const result = messagesStateReducer(
27
+ [null, undefined] as never,
28
+ [undefined, null] as never
29
+ );
30
+ expect(result).toEqual([]);
31
+ });
32
+ });
@@ -34,6 +34,26 @@ export type Messages =
34
34
  | BaseMessage
35
35
  | BaseMessageLike;
36
36
 
37
+ /**
38
+ * Coerce each entry to a {@link BaseMessage} in a single pass, skipping
39
+ * null/undefined entries. Providers can emit empty/partial stream chunks that
40
+ * arrive as `undefined`; passing those to `coerceMessageLikeToMessage` throws
41
+ * "Cannot read properties of undefined (reading 'role')" and crashes the run.
42
+ * Folding the null check into the coercion loop avoids a second pass over the
43
+ * array. Refs LibreChat Discussion #12284.
44
+ */
45
+ function coerceMessages(
46
+ messages: ReadonlyArray<BaseMessageLike | null | undefined>
47
+ ): BaseMessage[] {
48
+ const coerced: BaseMessage[] = [];
49
+ for (const message of messages) {
50
+ if (message != null) {
51
+ coerced.push(coerceMessageLikeToMessage(message));
52
+ }
53
+ }
54
+ return coerced;
55
+ }
56
+
37
57
  /**
38
58
  * Prebuilt reducer that combines returned messages.
39
59
  * Can handle standard messages and special modifiers like {@link RemoveMessage}
@@ -45,13 +65,9 @@ export function messagesStateReducer(
45
65
  ): BaseMessage[] {
46
66
  const leftArray = Array.isArray(left) ? left : [left];
47
67
  const rightArray = Array.isArray(right) ? right : [right];
48
- // coerce to message
49
- const leftMessages = (leftArray as BaseMessageLike[]).map(
50
- coerceMessageLikeToMessage
51
- );
52
- const rightMessages = (rightArray as BaseMessageLike[]).map(
53
- coerceMessageLikeToMessage
54
- );
68
+ // coerce to message, skipping null/undefined entries in the same pass
69
+ const leftMessages = coerceMessages(leftArray as BaseMessageLike[]);
70
+ const rightMessages = coerceMessages(rightArray as BaseMessageLike[]);
55
71
  // assign missing ids
56
72
  for (const m of leftMessages) {
57
73
  if (m.id === null || m.id === undefined) {