@m4trix/core 0.5.0 → 0.6.0

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.
@@ -71,4 +71,4 @@ declare class SocketIoFactory {
71
71
  private prefixEvent;
72
72
  }
73
73
 
74
- export { type BaseMessage, type Message, type Role, type SocketEventName, SocketIoFactory, type TextMessage, type VoiceMessage };
74
+ export { BaseMessage, Message, Role, SocketEventName, SocketIoFactory, TextMessage, VoiceMessage };
@@ -210,41 +210,67 @@ var TransformMessages = class _TransformMessages {
210
210
  * Format messages according to the specified format type
211
211
  */
212
212
  format(formatType) {
213
- return effect.pipe(
214
- this.effect,
215
- effect.Effect.map((messages) => {
216
- if (formatType === "json" /* JSON */) {
217
- return JSON.stringify(messages, null, 2);
218
- }
219
- const formatter = typeOnFormatter[formatType];
220
- return formatter(messages);
221
- })
213
+ const result = effect.Effect.runSync(
214
+ effect.pipe(
215
+ this.effect,
216
+ effect.Effect.map((messages) => {
217
+ if (formatType === "json" /* JSON */) {
218
+ return JSON.stringify(messages, null, 2);
219
+ }
220
+ const formatter = typeOnFormatter[formatType];
221
+ return formatter(messages);
222
+ })
223
+ )
222
224
  );
225
+ return result;
223
226
  }
224
227
  // Sink methods
225
228
  /**
226
- * Convert to array - runs the effect and returns the result
227
- */
229
+ * Convert to array - runs the effect and returns the result
230
+ return pipe(
231
+ this.effect,
232
+ Effect.map((messages) => {
233
+ if (formatType === FormatType.JSON) {
234
+ return JSON.stringify(messages, null, 2);
235
+ }
236
+
237
+ const formatter = typeOnFormatter[formatType];
238
+ return formatter(messages);
239
+ })
240
+ );
241
+ }
242
+
243
+ // Sink methods
244
+
245
+ /**
246
+ * Convert to array - runs the effect and returns the result
247
+ */
228
248
  toArray() {
229
- return this.effect;
249
+ return effect.Effect.runSync(this.effect);
230
250
  }
231
251
  /**
232
252
  * Convert to string - runs the effect and returns JSON string
233
253
  */
234
254
  toString() {
235
- return effect.pipe(
236
- this.effect,
237
- effect.Effect.map((messages) => JSON.stringify(messages, null, 2))
255
+ const result = effect.Effect.runSync(
256
+ effect.pipe(
257
+ this.effect,
258
+ effect.Effect.map((messages) => JSON.stringify(messages, null, 2))
259
+ )
238
260
  );
261
+ return result;
239
262
  }
240
263
  /**
241
264
  * Get the count of messages
242
265
  */
243
266
  count() {
244
- return effect.pipe(
245
- this.effect,
246
- effect.Effect.map((messages) => messages.length)
267
+ const result = effect.Effect.runSync(
268
+ effect.pipe(
269
+ this.effect,
270
+ effect.Effect.map((messages) => messages.length)
271
+ )
247
272
  );
273
+ return result;
248
274
  }
249
275
  };
250
276
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/helper/transform-messages/TransformMessages.ts","../../src/helper/transform-messages/message-filter.ts","../../src/helper/transform-messages/formatter.ts"],"names":["AIMessage","i","msg"],"mappings":"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","sourcesContent":["import { BaseMessage, AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { Effect, pipe } from 'effect';\nimport {\n MessageFilter,\n MessageFilterType,\n typeOnFilter,\n} from './message-filter';\nimport { FormatType, typeOnFormatter } from './formatter';\n\n/**\n * # Transform Messages\n * In order to manage the context size often you want to slice messages or only pass certain types of messages.\n * This class is a helper to do that.\n *\n * ## Example\n * ```ts\n * const messages = [\n * new HumanMessage('Hello, how are you?'),\n * new AIMessage('I am good, thank you!'),\n * ];\n *\n * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);\n *\n * ```\n */\n\nclass TransformMessages {\n private effect: Effect.Effect<Array<BaseMessage>, never, never>;\n\n private constructor(effect: Effect.Effect<Array<BaseMessage>, never, never>) {\n this.effect = effect;\n }\n\n /**\n * Create a new TransformMessages from an array of messages.\n */\n static from(messages: Array<BaseMessage>): TransformMessages {\n return new TransformMessages(Effect.succeed(messages));\n }\n\n /**\n * Filter messages based on a predicate function\n */\n filter(\n predicate: MessageFilter | MessageFilterType,\n tags?: Array<string>\n ): TransformMessages {\n let finalPredicate: MessageFilter;\n if (typeof predicate === 'string') {\n finalPredicate = typeOnFilter[predicate];\n } else {\n finalPredicate = predicate;\n }\n\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) =>\n messages.filter((message) => finalPredicate(message, tags))\n )\n )\n );\n }\n\n /**\n * Take only the last n messages, but safely.\n * Tool calls should not be separated from the last human message.\n * Ensures all tool call conversations in the last n messages are complete.\n */\n safelyTakeLast(\n n: number,\n pruneAfterNOvershootingMessages: number = 0\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => {\n const total = messages.length;\n if (n <= 0 || total === 0) return [];\n\n // Start with the last n messages\n const start = Math.max(0, total - n);\n const end = total;\n const lastSlice = messages.slice(start, end);\n\n // due to the fact that the calling AI message needs to be adjecent to the succeeding tool call message\n // we just need to check the last n messages for tool call ids\n\n // Check the first message if it is a tool call message\n // if it is iterate backwards until we find the AI message\n if (\n lastSlice[0] instanceof ToolMessage &&\n lastSlice[0].tool_call_id\n ) {\n let messagesToInclude: Array<BaseMessage> = [];\n const remainingMessages = messages.slice(0, start);\n for (let i = remainingMessages.length - 1; i >= 0; i--) {\n const msg = remainingMessages[i];\n if (\n pruneAfterNOvershootingMessages > 0 &&\n messagesToInclude.length - 1 >= pruneAfterNOvershootingMessages\n ) {\n messagesToInclude = [];\n // Return the slice but remove all the tool call messages that are at the beginning of the slice\n const filteredSlice: Array<BaseMessage> = [];\n let foundFirstNonToolMessage = false;\n for (let i = 0; i < lastSlice.length; i++) {\n const msg = lastSlice[i];\n if (msg instanceof ToolMessage) {\n if (foundFirstNonToolMessage) {\n filteredSlice.push(msg);\n }\n } else {\n foundFirstNonToolMessage = true;\n filteredSlice.push(msg);\n }\n }\n return filteredSlice;\n }\n if (msg instanceof AIMessage && Array.isArray(msg.tool_calls)) {\n messagesToInclude.push(msg);\n break;\n } else if (msg instanceof ToolMessage) {\n messagesToInclude.push(msg);\n } else {\n // This should not happen messages invalid\n throw new Error(\n 'Messages array invalid no adjacent AI message found'\n );\n }\n }\n return [...messagesToInclude.reverse(), ...lastSlice];\n } else {\n return lastSlice;\n }\n })\n )\n );\n }\n\n /**\n * Take only the last n messages\n */\n last(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(-n))\n )\n );\n }\n\n /**\n * Take only the first n messages\n */\n first(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(0, n))\n )\n );\n }\n\n /**\n * Skip the first n messages\n */\n skip(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(n))\n )\n );\n }\n\n /**\n * Reverse the order of messages\n */\n reverse(): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => [...messages].reverse())\n )\n );\n }\n\n /**\n * Map over messages with a transformation function\n */\n map<T extends BaseMessage>(\n fn: (message: BaseMessage) => T\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.map(fn))\n )\n );\n }\n\n /**\n * Format messages according to the specified format type\n */\n format(formatType: FormatType): Effect.Effect<string, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n );\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n */\n toArray(): Effect.Effect<Array<BaseMessage>, never, never> {\n return this.effect;\n }\n\n /**\n * Convert to string - runs the effect and returns JSON string\n */\n toString(): Effect.Effect<string, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => JSON.stringify(messages, null, 2))\n );\n }\n\n /**\n * Get the count of messages\n */\n count(): Effect.Effect<number, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => messages.length)\n );\n }\n}\n\nexport { TransformMessages };\n","import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';\n\n// Type for message filters\nexport type MessageFilter = (\n message: BaseMessage,\n tags?: Array<string>\n) => boolean;\n// Predefined filters\nconst humanAndAI: MessageFilter = (message) =>\n message instanceof HumanMessage || message instanceof AIMessage;\nconst humanOnly: MessageFilter = (message) => message instanceof HumanMessage;\nconst aiOnly: MessageFilter = (message) => message instanceof AIMessage;\n\nconst includingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nconst excludingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return !tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nexport enum MessageFilterType {\n HumanAndAI = 'HumanAndAI',\n HumanOnly = 'HumanOnly',\n AIOnly = 'AIOnly',\n IncludingTags = 'IncludingTags',\n ExcludingTags = 'ExcludingTags',\n}\nexport const typeOnFilter = {\n [MessageFilterType.HumanAndAI]: humanAndAI,\n [MessageFilterType.HumanOnly]: humanOnly,\n [MessageFilterType.AIOnly]: aiOnly,\n [MessageFilterType.IncludingTags]: includingTags,\n [MessageFilterType.ExcludingTags]: excludingTags,\n};\n","import { AIMessage, BaseMessage } from '@langchain/core/messages';\n\n// Format types\nexport enum FormatType {\n Concise = 'concise',\n Verbose = 'verbose',\n RedactAi = 'redact-ai',\n RedactHuman = 'redact-human',\n JSON = 'json',\n}\n\n/**\n * Formats messages in a concise markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: I am good, thank you!\n * AI: What is your name?\n * Human: My name is John.\n * AI: What is your favorite color?\n * Human: My favorite color is blue.\n * AI: What is your favorite food?\n * Human: My favorite food is pizza.\n * ```\n */\nfunction concise(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}: ${message.content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a verbose markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI:\n * Hello, how are you?\n * -------------------\n * Human:\n * I am good, thank you!\n * -------------------\n * AI:\n * What is your name?\n * -------------------\n * Human:\n * My name is John.\n * ```\n */\nfunction verbose(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}:\\n${message.content}`;\n })\n .join('\\n-------------------\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting AI messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: [...]\n * Human: Hello, how are you?\n * AI: [...]\n * Human: I am good, thank you!\n * AI: [...]\n * Human: What is your name?\n * AI: [...]\n * Human: My name is John.\n * AI: [...]\n * ```\n */\nfunction redactAi(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting Human messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: [...]\n * AI: What is your name?\n * Human: [...]\n * AI: What is your favorite color?\n * Human: [...]\n * AI: What is your favorite food?\n * Human: [...]\n * ```\n */\nfunction redactHuman(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\nconst typeOnFormatter = {\n [FormatType.Concise]: concise,\n [FormatType.Verbose]: verbose,\n [FormatType.RedactAi]: redactAi,\n [FormatType.RedactHuman]: redactHuman,\n};\n\nexport { typeOnFormatter };\n"]}
1
+ {"version":3,"sources":["../../src/helper/transform-messages/TransformMessages.ts","../../src/helper/transform-messages/message-filter.ts","../../src/helper/transform-messages/formatter.ts"],"names":["AIMessage","i","msg"],"mappings":"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","sourcesContent":["import { BaseMessage, AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { Effect, pipe } from 'effect';\nimport {\n MessageFilter,\n MessageFilterType,\n typeOnFilter,\n} from './message-filter';\nimport { FormatType, typeOnFormatter } from './formatter';\n\n/**\n * # Transform Messages\n * In order to manage the context size often you want to slice messages or only pass certain types of messages.\n * This class is a helper to do that.\n *\n * ## Example\n * ```ts\n * const messages = [\n * new HumanMessage('Hello, how are you?'),\n * new AIMessage('I am good, thank you!'),\n * ];\n *\n * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);\n *\n * ```\n */\n\nclass TransformMessages {\n private effect: Effect.Effect<Array<BaseMessage>, never, never>;\n\n private constructor(effect: Effect.Effect<Array<BaseMessage>, never, never>) {\n this.effect = effect;\n }\n\n /**\n * Create a new TransformMessages from an array of messages.\n */\n static from(messages: Array<BaseMessage>): TransformMessages {\n return new TransformMessages(Effect.succeed(messages));\n }\n\n /**\n * Filter messages based on a predicate function\n */\n filter(\n predicate: MessageFilter | MessageFilterType,\n tags?: Array<string>\n ): TransformMessages {\n let finalPredicate: MessageFilter;\n if (typeof predicate === 'string') {\n finalPredicate = typeOnFilter[predicate];\n } else {\n finalPredicate = predicate;\n }\n\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) =>\n messages.filter((message) => finalPredicate(message, tags))\n )\n )\n );\n }\n\n /**\n * Take only the last n messages, but safely.\n * Tool calls should not be separated from the last human message.\n * Ensures all tool call conversations in the last n messages are complete.\n */\n safelyTakeLast(\n n: number,\n pruneAfterNOvershootingMessages: number = 0\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => {\n const total = messages.length;\n if (n <= 0 || total === 0) return [];\n\n // Start with the last n messages\n const start = Math.max(0, total - n);\n const end = total;\n const lastSlice = messages.slice(start, end);\n\n // due to the fact that the calling AI message needs to be adjecent to the succeeding tool call message\n // we just need to check the last n messages for tool call ids\n\n // Check the first message if it is a tool call message\n // if it is iterate backwards until we find the AI message\n if (\n lastSlice[0] instanceof ToolMessage &&\n lastSlice[0].tool_call_id\n ) {\n let messagesToInclude: Array<BaseMessage> = [];\n const remainingMessages = messages.slice(0, start);\n for (let i = remainingMessages.length - 1; i >= 0; i--) {\n const msg = remainingMessages[i];\n if (\n pruneAfterNOvershootingMessages > 0 &&\n messagesToInclude.length - 1 >= pruneAfterNOvershootingMessages\n ) {\n messagesToInclude = [];\n // Return the slice but remove all the tool call messages that are at the beginning of the slice\n const filteredSlice: Array<BaseMessage> = [];\n let foundFirstNonToolMessage = false;\n for (let i = 0; i < lastSlice.length; i++) {\n const msg = lastSlice[i];\n if (msg instanceof ToolMessage) {\n if (foundFirstNonToolMessage) {\n filteredSlice.push(msg);\n }\n } else {\n foundFirstNonToolMessage = true;\n filteredSlice.push(msg);\n }\n }\n return filteredSlice;\n }\n if (msg instanceof AIMessage && Array.isArray(msg.tool_calls)) {\n messagesToInclude.push(msg);\n break;\n } else if (msg instanceof ToolMessage) {\n messagesToInclude.push(msg);\n } else {\n // This should not happen messages invalid\n throw new Error(\n 'Messages array invalid no adjacent AI message found'\n );\n }\n }\n return [...messagesToInclude.reverse(), ...lastSlice];\n } else {\n return lastSlice;\n }\n })\n )\n );\n }\n\n /**\n * Take only the last n messages\n */\n last(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(-n))\n )\n );\n }\n\n /**\n * Take only the first n messages\n */\n first(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(0, n))\n )\n );\n }\n\n /**\n * Skip the first n messages\n */\n skip(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(n))\n )\n );\n }\n\n /**\n * Reverse the order of messages\n */\n reverse(): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => [...messages].reverse())\n )\n );\n }\n\n /**\n * Map over messages with a transformation function\n */\n map<T extends BaseMessage>(\n fn: (message: BaseMessage) => T\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.map(fn))\n )\n );\n }\n\n /**\n * Format messages according to the specified format type\n */\n format(formatType: FormatType): string {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n )\n );\n return result;\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n return pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n );\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n */\n toArray(): Array<BaseMessage> {\n return Effect.runSync(this.effect);\n }\n\n /**\n * Convert to string - runs the effect and returns JSON string\n */\n toString(): string {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => JSON.stringify(messages, null, 2))\n )\n );\n return result;\n }\n\n /**\n * Get the count of messages\n */\n count(): number {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => messages.length)\n )\n );\n return result;\n }\n}\n\nexport { TransformMessages };\n","import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';\n\n// Type for message filters\nexport type MessageFilter = (\n message: BaseMessage,\n tags?: Array<string>\n) => boolean;\n// Predefined filters\nconst humanAndAI: MessageFilter = (message) =>\n message instanceof HumanMessage || message instanceof AIMessage;\nconst humanOnly: MessageFilter = (message) => message instanceof HumanMessage;\nconst aiOnly: MessageFilter = (message) => message instanceof AIMessage;\n\nconst includingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nconst excludingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return !tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nexport enum MessageFilterType {\n HumanAndAI = 'HumanAndAI',\n HumanOnly = 'HumanOnly',\n AIOnly = 'AIOnly',\n IncludingTags = 'IncludingTags',\n ExcludingTags = 'ExcludingTags',\n}\nexport const typeOnFilter = {\n [MessageFilterType.HumanAndAI]: humanAndAI,\n [MessageFilterType.HumanOnly]: humanOnly,\n [MessageFilterType.AIOnly]: aiOnly,\n [MessageFilterType.IncludingTags]: includingTags,\n [MessageFilterType.ExcludingTags]: excludingTags,\n};\n","import { AIMessage, BaseMessage } from '@langchain/core/messages';\n\n// Format types\nexport enum FormatType {\n Concise = 'concise',\n Verbose = 'verbose',\n RedactAi = 'redact-ai',\n RedactHuman = 'redact-human',\n JSON = 'json',\n}\n\n/**\n * Formats messages in a concise markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: I am good, thank you!\n * AI: What is your name?\n * Human: My name is John.\n * AI: What is your favorite color?\n * Human: My favorite color is blue.\n * AI: What is your favorite food?\n * Human: My favorite food is pizza.\n * ```\n */\nfunction concise(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}: ${message.content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a verbose markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI:\n * Hello, how are you?\n * -------------------\n * Human:\n * I am good, thank you!\n * -------------------\n * AI:\n * What is your name?\n * -------------------\n * Human:\n * My name is John.\n * ```\n */\nfunction verbose(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}:\\n${message.content}`;\n })\n .join('\\n-------------------\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting AI messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: [...]\n * Human: Hello, how are you?\n * AI: [...]\n * Human: I am good, thank you!\n * AI: [...]\n * Human: What is your name?\n * AI: [...]\n * Human: My name is John.\n * AI: [...]\n * ```\n */\nfunction redactAi(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting Human messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: [...]\n * AI: What is your name?\n * Human: [...]\n * AI: What is your favorite color?\n * Human: [...]\n * AI: What is your favorite food?\n * Human: [...]\n * ```\n */\nfunction redactHuman(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\nconst typeOnFormatter = {\n [FormatType.Concise]: concise,\n [FormatType.Verbose]: verbose,\n [FormatType.RedactAi]: redactAi,\n [FormatType.RedactHuman]: redactHuman,\n};\n\nexport { typeOnFormatter };\n"]}
@@ -1,5 +1,4 @@
1
1
  import { BaseMessage } from '@langchain/core/messages';
2
- import { Effect } from 'effect';
3
2
 
4
3
  declare enum FormatType {
5
4
  Concise = "concise",
@@ -74,19 +73,36 @@ declare class TransformMessages {
74
73
  /**
75
74
  * Format messages according to the specified format type
76
75
  */
77
- format(formatType: FormatType): Effect.Effect<string, never, never>;
76
+ format(formatType: FormatType): string;
77
+ /**
78
+ * Convert to array - runs the effect and returns the result
79
+ return pipe(
80
+ this.effect,
81
+ Effect.map((messages) => {
82
+ if (formatType === FormatType.JSON) {
83
+ return JSON.stringify(messages, null, 2);
84
+ }
85
+
86
+ const formatter = typeOnFormatter[formatType];
87
+ return formatter(messages);
88
+ })
89
+ );
90
+ }
91
+
92
+ // Sink methods
93
+
78
94
  /**
79
95
  * Convert to array - runs the effect and returns the result
80
96
  */
81
- toArray(): Effect.Effect<Array<BaseMessage>, never, never>;
97
+ toArray(): Array<BaseMessage>;
82
98
  /**
83
99
  * Convert to string - runs the effect and returns JSON string
84
100
  */
85
- toString(): Effect.Effect<string, never, never>;
101
+ toString(): string;
86
102
  /**
87
103
  * Get the count of messages
88
104
  */
89
- count(): Effect.Effect<number, never, never>;
105
+ count(): number;
90
106
  }
91
107
 
92
108
  export { FormatType, MessageFilterType, TransformMessages };
@@ -208,41 +208,67 @@ var TransformMessages = class _TransformMessages {
208
208
  * Format messages according to the specified format type
209
209
  */
210
210
  format(formatType) {
211
- return pipe(
212
- this.effect,
213
- Effect.map((messages) => {
214
- if (formatType === "json" /* JSON */) {
215
- return JSON.stringify(messages, null, 2);
216
- }
217
- const formatter = typeOnFormatter[formatType];
218
- return formatter(messages);
219
- })
211
+ const result = Effect.runSync(
212
+ pipe(
213
+ this.effect,
214
+ Effect.map((messages) => {
215
+ if (formatType === "json" /* JSON */) {
216
+ return JSON.stringify(messages, null, 2);
217
+ }
218
+ const formatter = typeOnFormatter[formatType];
219
+ return formatter(messages);
220
+ })
221
+ )
220
222
  );
223
+ return result;
221
224
  }
222
225
  // Sink methods
223
226
  /**
224
- * Convert to array - runs the effect and returns the result
225
- */
227
+ * Convert to array - runs the effect and returns the result
228
+ return pipe(
229
+ this.effect,
230
+ Effect.map((messages) => {
231
+ if (formatType === FormatType.JSON) {
232
+ return JSON.stringify(messages, null, 2);
233
+ }
234
+
235
+ const formatter = typeOnFormatter[formatType];
236
+ return formatter(messages);
237
+ })
238
+ );
239
+ }
240
+
241
+ // Sink methods
242
+
243
+ /**
244
+ * Convert to array - runs the effect and returns the result
245
+ */
226
246
  toArray() {
227
- return this.effect;
247
+ return Effect.runSync(this.effect);
228
248
  }
229
249
  /**
230
250
  * Convert to string - runs the effect and returns JSON string
231
251
  */
232
252
  toString() {
233
- return pipe(
234
- this.effect,
235
- Effect.map((messages) => JSON.stringify(messages, null, 2))
253
+ const result = Effect.runSync(
254
+ pipe(
255
+ this.effect,
256
+ Effect.map((messages) => JSON.stringify(messages, null, 2))
257
+ )
236
258
  );
259
+ return result;
237
260
  }
238
261
  /**
239
262
  * Get the count of messages
240
263
  */
241
264
  count() {
242
- return pipe(
243
- this.effect,
244
- Effect.map((messages) => messages.length)
265
+ const result = Effect.runSync(
266
+ pipe(
267
+ this.effect,
268
+ Effect.map((messages) => messages.length)
269
+ )
245
270
  );
271
+ return result;
246
272
  }
247
273
  };
248
274
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/helper/transform-messages/TransformMessages.ts","../../src/helper/transform-messages/message-filter.ts","../../src/helper/transform-messages/formatter.ts"],"names":["AIMessage","i","msg"],"mappings":"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","sourcesContent":["import { BaseMessage, AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { Effect, pipe } from 'effect';\nimport {\n MessageFilter,\n MessageFilterType,\n typeOnFilter,\n} from './message-filter';\nimport { FormatType, typeOnFormatter } from './formatter';\n\n/**\n * # Transform Messages\n * In order to manage the context size often you want to slice messages or only pass certain types of messages.\n * This class is a helper to do that.\n *\n * ## Example\n * ```ts\n * const messages = [\n * new HumanMessage('Hello, how are you?'),\n * new AIMessage('I am good, thank you!'),\n * ];\n *\n * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);\n *\n * ```\n */\n\nclass TransformMessages {\n private effect: Effect.Effect<Array<BaseMessage>, never, never>;\n\n private constructor(effect: Effect.Effect<Array<BaseMessage>, never, never>) {\n this.effect = effect;\n }\n\n /**\n * Create a new TransformMessages from an array of messages.\n */\n static from(messages: Array<BaseMessage>): TransformMessages {\n return new TransformMessages(Effect.succeed(messages));\n }\n\n /**\n * Filter messages based on a predicate function\n */\n filter(\n predicate: MessageFilter | MessageFilterType,\n tags?: Array<string>\n ): TransformMessages {\n let finalPredicate: MessageFilter;\n if (typeof predicate === 'string') {\n finalPredicate = typeOnFilter[predicate];\n } else {\n finalPredicate = predicate;\n }\n\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) =>\n messages.filter((message) => finalPredicate(message, tags))\n )\n )\n );\n }\n\n /**\n * Take only the last n messages, but safely.\n * Tool calls should not be separated from the last human message.\n * Ensures all tool call conversations in the last n messages are complete.\n */\n safelyTakeLast(\n n: number,\n pruneAfterNOvershootingMessages: number = 0\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => {\n const total = messages.length;\n if (n <= 0 || total === 0) return [];\n\n // Start with the last n messages\n const start = Math.max(0, total - n);\n const end = total;\n const lastSlice = messages.slice(start, end);\n\n // due to the fact that the calling AI message needs to be adjecent to the succeeding tool call message\n // we just need to check the last n messages for tool call ids\n\n // Check the first message if it is a tool call message\n // if it is iterate backwards until we find the AI message\n if (\n lastSlice[0] instanceof ToolMessage &&\n lastSlice[0].tool_call_id\n ) {\n let messagesToInclude: Array<BaseMessage> = [];\n const remainingMessages = messages.slice(0, start);\n for (let i = remainingMessages.length - 1; i >= 0; i--) {\n const msg = remainingMessages[i];\n if (\n pruneAfterNOvershootingMessages > 0 &&\n messagesToInclude.length - 1 >= pruneAfterNOvershootingMessages\n ) {\n messagesToInclude = [];\n // Return the slice but remove all the tool call messages that are at the beginning of the slice\n const filteredSlice: Array<BaseMessage> = [];\n let foundFirstNonToolMessage = false;\n for (let i = 0; i < lastSlice.length; i++) {\n const msg = lastSlice[i];\n if (msg instanceof ToolMessage) {\n if (foundFirstNonToolMessage) {\n filteredSlice.push(msg);\n }\n } else {\n foundFirstNonToolMessage = true;\n filteredSlice.push(msg);\n }\n }\n return filteredSlice;\n }\n if (msg instanceof AIMessage && Array.isArray(msg.tool_calls)) {\n messagesToInclude.push(msg);\n break;\n } else if (msg instanceof ToolMessage) {\n messagesToInclude.push(msg);\n } else {\n // This should not happen messages invalid\n throw new Error(\n 'Messages array invalid no adjacent AI message found'\n );\n }\n }\n return [...messagesToInclude.reverse(), ...lastSlice];\n } else {\n return lastSlice;\n }\n })\n )\n );\n }\n\n /**\n * Take only the last n messages\n */\n last(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(-n))\n )\n );\n }\n\n /**\n * Take only the first n messages\n */\n first(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(0, n))\n )\n );\n }\n\n /**\n * Skip the first n messages\n */\n skip(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(n))\n )\n );\n }\n\n /**\n * Reverse the order of messages\n */\n reverse(): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => [...messages].reverse())\n )\n );\n }\n\n /**\n * Map over messages with a transformation function\n */\n map<T extends BaseMessage>(\n fn: (message: BaseMessage) => T\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.map(fn))\n )\n );\n }\n\n /**\n * Format messages according to the specified format type\n */\n format(formatType: FormatType): Effect.Effect<string, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n );\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n */\n toArray(): Effect.Effect<Array<BaseMessage>, never, never> {\n return this.effect;\n }\n\n /**\n * Convert to string - runs the effect and returns JSON string\n */\n toString(): Effect.Effect<string, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => JSON.stringify(messages, null, 2))\n );\n }\n\n /**\n * Get the count of messages\n */\n count(): Effect.Effect<number, never, never> {\n return pipe(\n this.effect,\n Effect.map((messages) => messages.length)\n );\n }\n}\n\nexport { TransformMessages };\n","import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';\n\n// Type for message filters\nexport type MessageFilter = (\n message: BaseMessage,\n tags?: Array<string>\n) => boolean;\n// Predefined filters\nconst humanAndAI: MessageFilter = (message) =>\n message instanceof HumanMessage || message instanceof AIMessage;\nconst humanOnly: MessageFilter = (message) => message instanceof HumanMessage;\nconst aiOnly: MessageFilter = (message) => message instanceof AIMessage;\n\nconst includingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nconst excludingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return !tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nexport enum MessageFilterType {\n HumanAndAI = 'HumanAndAI',\n HumanOnly = 'HumanOnly',\n AIOnly = 'AIOnly',\n IncludingTags = 'IncludingTags',\n ExcludingTags = 'ExcludingTags',\n}\nexport const typeOnFilter = {\n [MessageFilterType.HumanAndAI]: humanAndAI,\n [MessageFilterType.HumanOnly]: humanOnly,\n [MessageFilterType.AIOnly]: aiOnly,\n [MessageFilterType.IncludingTags]: includingTags,\n [MessageFilterType.ExcludingTags]: excludingTags,\n};\n","import { AIMessage, BaseMessage } from '@langchain/core/messages';\n\n// Format types\nexport enum FormatType {\n Concise = 'concise',\n Verbose = 'verbose',\n RedactAi = 'redact-ai',\n RedactHuman = 'redact-human',\n JSON = 'json',\n}\n\n/**\n * Formats messages in a concise markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: I am good, thank you!\n * AI: What is your name?\n * Human: My name is John.\n * AI: What is your favorite color?\n * Human: My favorite color is blue.\n * AI: What is your favorite food?\n * Human: My favorite food is pizza.\n * ```\n */\nfunction concise(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}: ${message.content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a verbose markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI:\n * Hello, how are you?\n * -------------------\n * Human:\n * I am good, thank you!\n * -------------------\n * AI:\n * What is your name?\n * -------------------\n * Human:\n * My name is John.\n * ```\n */\nfunction verbose(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}:\\n${message.content}`;\n })\n .join('\\n-------------------\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting AI messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: [...]\n * Human: Hello, how are you?\n * AI: [...]\n * Human: I am good, thank you!\n * AI: [...]\n * Human: What is your name?\n * AI: [...]\n * Human: My name is John.\n * AI: [...]\n * ```\n */\nfunction redactAi(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting Human messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: [...]\n * AI: What is your name?\n * Human: [...]\n * AI: What is your favorite color?\n * Human: [...]\n * AI: What is your favorite food?\n * Human: [...]\n * ```\n */\nfunction redactHuman(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\nconst typeOnFormatter = {\n [FormatType.Concise]: concise,\n [FormatType.Verbose]: verbose,\n [FormatType.RedactAi]: redactAi,\n [FormatType.RedactHuman]: redactHuman,\n};\n\nexport { typeOnFormatter };\n"]}
1
+ {"version":3,"sources":["../../src/helper/transform-messages/TransformMessages.ts","../../src/helper/transform-messages/message-filter.ts","../../src/helper/transform-messages/formatter.ts"],"names":["AIMessage","i","msg"],"mappings":"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","sourcesContent":["import { BaseMessage, AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { Effect, pipe } from 'effect';\nimport {\n MessageFilter,\n MessageFilterType,\n typeOnFilter,\n} from './message-filter';\nimport { FormatType, typeOnFormatter } from './formatter';\n\n/**\n * # Transform Messages\n * In order to manage the context size often you want to slice messages or only pass certain types of messages.\n * This class is a helper to do that.\n *\n * ## Example\n * ```ts\n * const messages = [\n * new HumanMessage('Hello, how are you?'),\n * new AIMessage('I am good, thank you!'),\n * ];\n *\n * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);\n *\n * ```\n */\n\nclass TransformMessages {\n private effect: Effect.Effect<Array<BaseMessage>, never, never>;\n\n private constructor(effect: Effect.Effect<Array<BaseMessage>, never, never>) {\n this.effect = effect;\n }\n\n /**\n * Create a new TransformMessages from an array of messages.\n */\n static from(messages: Array<BaseMessage>): TransformMessages {\n return new TransformMessages(Effect.succeed(messages));\n }\n\n /**\n * Filter messages based on a predicate function\n */\n filter(\n predicate: MessageFilter | MessageFilterType,\n tags?: Array<string>\n ): TransformMessages {\n let finalPredicate: MessageFilter;\n if (typeof predicate === 'string') {\n finalPredicate = typeOnFilter[predicate];\n } else {\n finalPredicate = predicate;\n }\n\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) =>\n messages.filter((message) => finalPredicate(message, tags))\n )\n )\n );\n }\n\n /**\n * Take only the last n messages, but safely.\n * Tool calls should not be separated from the last human message.\n * Ensures all tool call conversations in the last n messages are complete.\n */\n safelyTakeLast(\n n: number,\n pruneAfterNOvershootingMessages: number = 0\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => {\n const total = messages.length;\n if (n <= 0 || total === 0) return [];\n\n // Start with the last n messages\n const start = Math.max(0, total - n);\n const end = total;\n const lastSlice = messages.slice(start, end);\n\n // due to the fact that the calling AI message needs to be adjecent to the succeeding tool call message\n // we just need to check the last n messages for tool call ids\n\n // Check the first message if it is a tool call message\n // if it is iterate backwards until we find the AI message\n if (\n lastSlice[0] instanceof ToolMessage &&\n lastSlice[0].tool_call_id\n ) {\n let messagesToInclude: Array<BaseMessage> = [];\n const remainingMessages = messages.slice(0, start);\n for (let i = remainingMessages.length - 1; i >= 0; i--) {\n const msg = remainingMessages[i];\n if (\n pruneAfterNOvershootingMessages > 0 &&\n messagesToInclude.length - 1 >= pruneAfterNOvershootingMessages\n ) {\n messagesToInclude = [];\n // Return the slice but remove all the tool call messages that are at the beginning of the slice\n const filteredSlice: Array<BaseMessage> = [];\n let foundFirstNonToolMessage = false;\n for (let i = 0; i < lastSlice.length; i++) {\n const msg = lastSlice[i];\n if (msg instanceof ToolMessage) {\n if (foundFirstNonToolMessage) {\n filteredSlice.push(msg);\n }\n } else {\n foundFirstNonToolMessage = true;\n filteredSlice.push(msg);\n }\n }\n return filteredSlice;\n }\n if (msg instanceof AIMessage && Array.isArray(msg.tool_calls)) {\n messagesToInclude.push(msg);\n break;\n } else if (msg instanceof ToolMessage) {\n messagesToInclude.push(msg);\n } else {\n // This should not happen messages invalid\n throw new Error(\n 'Messages array invalid no adjacent AI message found'\n );\n }\n }\n return [...messagesToInclude.reverse(), ...lastSlice];\n } else {\n return lastSlice;\n }\n })\n )\n );\n }\n\n /**\n * Take only the last n messages\n */\n last(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(-n))\n )\n );\n }\n\n /**\n * Take only the first n messages\n */\n first(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(0, n))\n )\n );\n }\n\n /**\n * Skip the first n messages\n */\n skip(n: number): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.slice(n))\n )\n );\n }\n\n /**\n * Reverse the order of messages\n */\n reverse(): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => [...messages].reverse())\n )\n );\n }\n\n /**\n * Map over messages with a transformation function\n */\n map<T extends BaseMessage>(\n fn: (message: BaseMessage) => T\n ): TransformMessages {\n return new TransformMessages(\n pipe(\n this.effect,\n Effect.map((messages) => messages.map(fn))\n )\n );\n }\n\n /**\n * Format messages according to the specified format type\n */\n format(formatType: FormatType): string {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n )\n );\n return result;\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n return pipe(\n this.effect,\n Effect.map((messages) => {\n if (formatType === FormatType.JSON) {\n return JSON.stringify(messages, null, 2);\n }\n\n const formatter = typeOnFormatter[formatType];\n return formatter(messages);\n })\n );\n }\n\n // Sink methods\n\n /**\n * Convert to array - runs the effect and returns the result\n */\n toArray(): Array<BaseMessage> {\n return Effect.runSync(this.effect);\n }\n\n /**\n * Convert to string - runs the effect and returns JSON string\n */\n toString(): string {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => JSON.stringify(messages, null, 2))\n )\n );\n return result;\n }\n\n /**\n * Get the count of messages\n */\n count(): number {\n const result = Effect.runSync(\n pipe(\n this.effect,\n Effect.map((messages) => messages.length)\n )\n );\n return result;\n }\n}\n\nexport { TransformMessages };\n","import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';\n\n// Type for message filters\nexport type MessageFilter = (\n message: BaseMessage,\n tags?: Array<string>\n) => boolean;\n// Predefined filters\nconst humanAndAI: MessageFilter = (message) =>\n message instanceof HumanMessage || message instanceof AIMessage;\nconst humanOnly: MessageFilter = (message) => message instanceof HumanMessage;\nconst aiOnly: MessageFilter = (message) => message instanceof AIMessage;\n\nconst includingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nconst excludingTags: MessageFilter = (message, tags) => {\n if (tags) {\n return !tags.some((tag) =>\n Array.isArray(message.additional_kwargs?.tags)\n ? message.additional_kwargs?.tags.includes(tag)\n : false\n );\n }\n return true;\n};\n\nexport enum MessageFilterType {\n HumanAndAI = 'HumanAndAI',\n HumanOnly = 'HumanOnly',\n AIOnly = 'AIOnly',\n IncludingTags = 'IncludingTags',\n ExcludingTags = 'ExcludingTags',\n}\nexport const typeOnFilter = {\n [MessageFilterType.HumanAndAI]: humanAndAI,\n [MessageFilterType.HumanOnly]: humanOnly,\n [MessageFilterType.AIOnly]: aiOnly,\n [MessageFilterType.IncludingTags]: includingTags,\n [MessageFilterType.ExcludingTags]: excludingTags,\n};\n","import { AIMessage, BaseMessage } from '@langchain/core/messages';\n\n// Format types\nexport enum FormatType {\n Concise = 'concise',\n Verbose = 'verbose',\n RedactAi = 'redact-ai',\n RedactHuman = 'redact-human',\n JSON = 'json',\n}\n\n/**\n * Formats messages in a concise markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: I am good, thank you!\n * AI: What is your name?\n * Human: My name is John.\n * AI: What is your favorite color?\n * Human: My favorite color is blue.\n * AI: What is your favorite food?\n * Human: My favorite food is pizza.\n * ```\n */\nfunction concise(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}: ${message.content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a verbose markdown format with alternating AI and Human prefixes.\n *\n * ### Example\n * ```markdown\n * AI:\n * Hello, how are you?\n * -------------------\n * Human:\n * I am good, thank you!\n * -------------------\n * AI:\n * What is your name?\n * -------------------\n * Human:\n * My name is John.\n * ```\n */\nfunction verbose(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n return `${prefix}:\\n${message.content}`;\n })\n .join('\\n-------------------\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting AI messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: [...]\n * Human: Hello, how are you?\n * AI: [...]\n * Human: I am good, thank you!\n * AI: [...]\n * Human: What is your name?\n * AI: [...]\n * Human: My name is John.\n * AI: [...]\n * ```\n */\nfunction redactAi(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\n\n/**\n * Formats messages in a concise markdown format, redacting Human messages with [...]\n *\n * ### Example\n * ```markdown\n * AI: Hello, how are you?\n * Human: [...]\n * AI: What is your name?\n * Human: [...]\n * AI: What is your favorite color?\n * Human: [...]\n * AI: What is your favorite food?\n * Human: [...]\n * ```\n */\nfunction redactHuman(messages: Array<BaseMessage>): string {\n return messages\n .map((message) => {\n const prefix = message instanceof AIMessage ? 'AI' : 'Human';\n const content = message instanceof AIMessage ? '[...]' : message.content;\n return `${prefix}: ${content}`;\n })\n .join('\\n');\n}\nconst typeOnFormatter = {\n [FormatType.Concise]: concise,\n [FormatType.Verbose]: verbose,\n [FormatType.RedactAi]: redactAi,\n [FormatType.RedactHuman]: redactHuman,\n};\n\nexport { typeOnFormatter };\n"]}