@m4trix/core 0.5.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.
@@ -0,0 +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"]}
@@ -0,0 +1,92 @@
1
+ import { BaseMessage } from '@langchain/core/messages';
2
+ import { Effect } from 'effect';
3
+
4
+ declare enum FormatType {
5
+ Concise = "concise",
6
+ Verbose = "verbose",
7
+ RedactAi = "redact-ai",
8
+ RedactHuman = "redact-human",
9
+ JSON = "json"
10
+ }
11
+
12
+ type MessageFilter = (message: BaseMessage, tags?: Array<string>) => boolean;
13
+ declare enum MessageFilterType {
14
+ HumanAndAI = "HumanAndAI",
15
+ HumanOnly = "HumanOnly",
16
+ AIOnly = "AIOnly",
17
+ IncludingTags = "IncludingTags",
18
+ ExcludingTags = "ExcludingTags"
19
+ }
20
+
21
+ /**
22
+ * # Transform Messages
23
+ * In order to manage the context size often you want to slice messages or only pass certain types of messages.
24
+ * This class is a helper to do that.
25
+ *
26
+ * ## Example
27
+ * ```ts
28
+ * const messages = [
29
+ * new HumanMessage('Hello, how are you?'),
30
+ * new AIMessage('I am good, thank you!'),
31
+ * ];
32
+ *
33
+ * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);
34
+ *
35
+ * ```
36
+ */
37
+ declare class TransformMessages {
38
+ private effect;
39
+ private constructor();
40
+ /**
41
+ * Create a new TransformMessages from an array of messages.
42
+ */
43
+ static from(messages: Array<BaseMessage>): TransformMessages;
44
+ /**
45
+ * Filter messages based on a predicate function
46
+ */
47
+ filter(predicate: MessageFilter | MessageFilterType, tags?: Array<string>): TransformMessages;
48
+ /**
49
+ * Take only the last n messages, but safely.
50
+ * Tool calls should not be separated from the last human message.
51
+ * Ensures all tool call conversations in the last n messages are complete.
52
+ */
53
+ safelyTakeLast(n: number, pruneAfterNOvershootingMessages?: number): TransformMessages;
54
+ /**
55
+ * Take only the last n messages
56
+ */
57
+ last(n: number): TransformMessages;
58
+ /**
59
+ * Take only the first n messages
60
+ */
61
+ first(n: number): TransformMessages;
62
+ /**
63
+ * Skip the first n messages
64
+ */
65
+ skip(n: number): TransformMessages;
66
+ /**
67
+ * Reverse the order of messages
68
+ */
69
+ reverse(): TransformMessages;
70
+ /**
71
+ * Map over messages with a transformation function
72
+ */
73
+ map<T extends BaseMessage>(fn: (message: BaseMessage) => T): TransformMessages;
74
+ /**
75
+ * Format messages according to the specified format type
76
+ */
77
+ format(formatType: FormatType): Effect.Effect<string, never, never>;
78
+ /**
79
+ * Convert to array - runs the effect and returns the result
80
+ */
81
+ toArray(): Effect.Effect<Array<BaseMessage>, never, never>;
82
+ /**
83
+ * Convert to string - runs the effect and returns JSON string
84
+ */
85
+ toString(): Effect.Effect<string, never, never>;
86
+ /**
87
+ * Get the count of messages
88
+ */
89
+ count(): Effect.Effect<number, never, never>;
90
+ }
91
+
92
+ export { FormatType, MessageFilterType, TransformMessages };
@@ -0,0 +1,92 @@
1
+ import { BaseMessage } from '@langchain/core/messages';
2
+ import { Effect } from 'effect';
3
+
4
+ declare enum FormatType {
5
+ Concise = "concise",
6
+ Verbose = "verbose",
7
+ RedactAi = "redact-ai",
8
+ RedactHuman = "redact-human",
9
+ JSON = "json"
10
+ }
11
+
12
+ type MessageFilter = (message: BaseMessage, tags?: Array<string>) => boolean;
13
+ declare enum MessageFilterType {
14
+ HumanAndAI = "HumanAndAI",
15
+ HumanOnly = "HumanOnly",
16
+ AIOnly = "AIOnly",
17
+ IncludingTags = "IncludingTags",
18
+ ExcludingTags = "ExcludingTags"
19
+ }
20
+
21
+ /**
22
+ * # Transform Messages
23
+ * In order to manage the context size often you want to slice messages or only pass certain types of messages.
24
+ * This class is a helper to do that.
25
+ *
26
+ * ## Example
27
+ * ```ts
28
+ * const messages = [
29
+ * new HumanMessage('Hello, how are you?'),
30
+ * new AIMessage('I am good, thank you!'),
31
+ * ];
32
+ *
33
+ * const transformedMessages = TransformMessages.from(messages).filter(HumanAndAI).last(10).format(FormatType.Concise);
34
+ *
35
+ * ```
36
+ */
37
+ declare class TransformMessages {
38
+ private effect;
39
+ private constructor();
40
+ /**
41
+ * Create a new TransformMessages from an array of messages.
42
+ */
43
+ static from(messages: Array<BaseMessage>): TransformMessages;
44
+ /**
45
+ * Filter messages based on a predicate function
46
+ */
47
+ filter(predicate: MessageFilter | MessageFilterType, tags?: Array<string>): TransformMessages;
48
+ /**
49
+ * Take only the last n messages, but safely.
50
+ * Tool calls should not be separated from the last human message.
51
+ * Ensures all tool call conversations in the last n messages are complete.
52
+ */
53
+ safelyTakeLast(n: number, pruneAfterNOvershootingMessages?: number): TransformMessages;
54
+ /**
55
+ * Take only the last n messages
56
+ */
57
+ last(n: number): TransformMessages;
58
+ /**
59
+ * Take only the first n messages
60
+ */
61
+ first(n: number): TransformMessages;
62
+ /**
63
+ * Skip the first n messages
64
+ */
65
+ skip(n: number): TransformMessages;
66
+ /**
67
+ * Reverse the order of messages
68
+ */
69
+ reverse(): TransformMessages;
70
+ /**
71
+ * Map over messages with a transformation function
72
+ */
73
+ map<T extends BaseMessage>(fn: (message: BaseMessage) => T): TransformMessages;
74
+ /**
75
+ * Format messages according to the specified format type
76
+ */
77
+ format(formatType: FormatType): Effect.Effect<string, never, never>;
78
+ /**
79
+ * Convert to array - runs the effect and returns the result
80
+ */
81
+ toArray(): Effect.Effect<Array<BaseMessage>, never, never>;
82
+ /**
83
+ * Convert to string - runs the effect and returns JSON string
84
+ */
85
+ toString(): Effect.Effect<string, never, never>;
86
+ /**
87
+ * Get the count of messages
88
+ */
89
+ count(): Effect.Effect<number, never, never>;
90
+ }
91
+
92
+ export { FormatType, MessageFilterType, TransformMessages };
@@ -0,0 +1,251 @@
1
+ import { ToolMessage, AIMessage, HumanMessage } from '@langchain/core/messages';
2
+ import { Effect, pipe } from 'effect';
3
+
4
+ // src/helper/transform-messages/TransformMessages.ts
5
+ var humanAndAI = (message) => message instanceof HumanMessage || message instanceof AIMessage;
6
+ var humanOnly = (message) => message instanceof HumanMessage;
7
+ var aiOnly = (message) => message instanceof AIMessage;
8
+ var includingTags = (message, tags) => {
9
+ if (tags) {
10
+ return tags.some(
11
+ (tag) => Array.isArray(message.additional_kwargs?.tags) ? message.additional_kwargs?.tags.includes(tag) : false
12
+ );
13
+ }
14
+ return true;
15
+ };
16
+ var excludingTags = (message, tags) => {
17
+ if (tags) {
18
+ return !tags.some(
19
+ (tag) => Array.isArray(message.additional_kwargs?.tags) ? message.additional_kwargs?.tags.includes(tag) : false
20
+ );
21
+ }
22
+ return true;
23
+ };
24
+ var typeOnFilter = {
25
+ ["HumanAndAI" /* HumanAndAI */]: humanAndAI,
26
+ ["HumanOnly" /* HumanOnly */]: humanOnly,
27
+ ["AIOnly" /* AIOnly */]: aiOnly,
28
+ ["IncludingTags" /* IncludingTags */]: includingTags,
29
+ ["ExcludingTags" /* ExcludingTags */]: excludingTags
30
+ };
31
+ function concise(messages) {
32
+ return messages.map((message) => {
33
+ const prefix = message instanceof AIMessage ? "AI" : "Human";
34
+ return `${prefix}: ${message.content}`;
35
+ }).join("\n");
36
+ }
37
+ function verbose(messages) {
38
+ return messages.map((message) => {
39
+ const prefix = message instanceof AIMessage ? "AI" : "Human";
40
+ return `${prefix}:
41
+ ${message.content}`;
42
+ }).join("\n-------------------\n");
43
+ }
44
+ function redactAi(messages) {
45
+ return messages.map((message) => {
46
+ const prefix = message instanceof AIMessage ? "AI" : "Human";
47
+ const content = message instanceof AIMessage ? "[...]" : message.content;
48
+ return `${prefix}: ${content}`;
49
+ }).join("\n");
50
+ }
51
+ function redactHuman(messages) {
52
+ return messages.map((message) => {
53
+ const prefix = message instanceof AIMessage ? "AI" : "Human";
54
+ const content = message instanceof AIMessage ? "[...]" : message.content;
55
+ return `${prefix}: ${content}`;
56
+ }).join("\n");
57
+ }
58
+ var typeOnFormatter = {
59
+ ["concise" /* Concise */]: concise,
60
+ ["verbose" /* Verbose */]: verbose,
61
+ ["redact-ai" /* RedactAi */]: redactAi,
62
+ ["redact-human" /* RedactHuman */]: redactHuman
63
+ };
64
+
65
+ // src/helper/transform-messages/TransformMessages.ts
66
+ var TransformMessages = class _TransformMessages {
67
+ constructor(effect) {
68
+ this.effect = effect;
69
+ }
70
+ /**
71
+ * Create a new TransformMessages from an array of messages.
72
+ */
73
+ static from(messages) {
74
+ return new _TransformMessages(Effect.succeed(messages));
75
+ }
76
+ /**
77
+ * Filter messages based on a predicate function
78
+ */
79
+ filter(predicate, tags) {
80
+ let finalPredicate;
81
+ if (typeof predicate === "string") {
82
+ finalPredicate = typeOnFilter[predicate];
83
+ } else {
84
+ finalPredicate = predicate;
85
+ }
86
+ return new _TransformMessages(
87
+ pipe(
88
+ this.effect,
89
+ Effect.map(
90
+ (messages) => messages.filter((message) => finalPredicate(message, tags))
91
+ )
92
+ )
93
+ );
94
+ }
95
+ /**
96
+ * Take only the last n messages, but safely.
97
+ * Tool calls should not be separated from the last human message.
98
+ * Ensures all tool call conversations in the last n messages are complete.
99
+ */
100
+ safelyTakeLast(n, pruneAfterNOvershootingMessages = 0) {
101
+ return new _TransformMessages(
102
+ pipe(
103
+ this.effect,
104
+ Effect.map((messages) => {
105
+ const total = messages.length;
106
+ if (n <= 0 || total === 0)
107
+ return [];
108
+ const start = Math.max(0, total - n);
109
+ const end = total;
110
+ const lastSlice = messages.slice(start, end);
111
+ if (lastSlice[0] instanceof ToolMessage && lastSlice[0].tool_call_id) {
112
+ let messagesToInclude = [];
113
+ const remainingMessages = messages.slice(0, start);
114
+ for (let i = remainingMessages.length - 1; i >= 0; i--) {
115
+ const msg = remainingMessages[i];
116
+ if (pruneAfterNOvershootingMessages > 0 && messagesToInclude.length - 1 >= pruneAfterNOvershootingMessages) {
117
+ messagesToInclude = [];
118
+ const filteredSlice = [];
119
+ let foundFirstNonToolMessage = false;
120
+ for (let i2 = 0; i2 < lastSlice.length; i2++) {
121
+ const msg2 = lastSlice[i2];
122
+ if (msg2 instanceof ToolMessage) {
123
+ if (foundFirstNonToolMessage) {
124
+ filteredSlice.push(msg2);
125
+ }
126
+ } else {
127
+ foundFirstNonToolMessage = true;
128
+ filteredSlice.push(msg2);
129
+ }
130
+ }
131
+ return filteredSlice;
132
+ }
133
+ if (msg instanceof AIMessage && Array.isArray(msg.tool_calls)) {
134
+ messagesToInclude.push(msg);
135
+ break;
136
+ } else if (msg instanceof ToolMessage) {
137
+ messagesToInclude.push(msg);
138
+ } else {
139
+ throw new Error(
140
+ "Messages array invalid no adjacent AI message found"
141
+ );
142
+ }
143
+ }
144
+ return [...messagesToInclude.reverse(), ...lastSlice];
145
+ } else {
146
+ return lastSlice;
147
+ }
148
+ })
149
+ )
150
+ );
151
+ }
152
+ /**
153
+ * Take only the last n messages
154
+ */
155
+ last(n) {
156
+ return new _TransformMessages(
157
+ pipe(
158
+ this.effect,
159
+ Effect.map((messages) => messages.slice(-n))
160
+ )
161
+ );
162
+ }
163
+ /**
164
+ * Take only the first n messages
165
+ */
166
+ first(n) {
167
+ return new _TransformMessages(
168
+ pipe(
169
+ this.effect,
170
+ Effect.map((messages) => messages.slice(0, n))
171
+ )
172
+ );
173
+ }
174
+ /**
175
+ * Skip the first n messages
176
+ */
177
+ skip(n) {
178
+ return new _TransformMessages(
179
+ pipe(
180
+ this.effect,
181
+ Effect.map((messages) => messages.slice(n))
182
+ )
183
+ );
184
+ }
185
+ /**
186
+ * Reverse the order of messages
187
+ */
188
+ reverse() {
189
+ return new _TransformMessages(
190
+ pipe(
191
+ this.effect,
192
+ Effect.map((messages) => [...messages].reverse())
193
+ )
194
+ );
195
+ }
196
+ /**
197
+ * Map over messages with a transformation function
198
+ */
199
+ map(fn) {
200
+ return new _TransformMessages(
201
+ pipe(
202
+ this.effect,
203
+ Effect.map((messages) => messages.map(fn))
204
+ )
205
+ );
206
+ }
207
+ /**
208
+ * Format messages according to the specified format type
209
+ */
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
+ })
220
+ );
221
+ }
222
+ // Sink methods
223
+ /**
224
+ * Convert to array - runs the effect and returns the result
225
+ */
226
+ toArray() {
227
+ return this.effect;
228
+ }
229
+ /**
230
+ * Convert to string - runs the effect and returns JSON string
231
+ */
232
+ toString() {
233
+ return pipe(
234
+ this.effect,
235
+ Effect.map((messages) => JSON.stringify(messages, null, 2))
236
+ );
237
+ }
238
+ /**
239
+ * Get the count of messages
240
+ */
241
+ count() {
242
+ return pipe(
243
+ this.effect,
244
+ Effect.map((messages) => messages.length)
245
+ );
246
+ }
247
+ };
248
+
249
+ export { TransformMessages };
250
+ //# sourceMappingURL=out.js.map
251
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}