@@ -142,41 +142,125 @@ def replacement_function(match):
142142 return template
143143
144144
145+ def truncate_content (content : str , max_chars : int , mode : str = "middletruncate" ) -> str :
146+ """Truncate a string to max_chars using the specified mode.
147+
148+ Modes:
149+ - middletruncate: keep beginning and end, join with '...'
150+ - start: keep first max_chars characters
151+ - end: keep last max_chars characters
152+ """
153+ if not content or len (content ) <= max_chars :
154+ return content
155+
156+ if mode == "start" :
157+ return content [:max_chars ]
158+ elif mode == "end" :
159+ return content [- max_chars :]
160+ else : # middletruncate
161+ half = max_chars // 2
162+ return f"{ content [:half ]} ...{ content [- (max_chars - half ):]} "
163+
164+
165+ def apply_content_filter (
166+ messages : list [dict ], filter_str : str
167+ ) -> list [dict ]:
168+ """Apply a content filter to each message's content.
169+
170+ filter_str is like 'middletruncate:500', 'start:200', or 'end:200'.
171+ Returns a new list with truncated content (original messages are not mutated).
172+ """
173+ parts = filter_str .split (":" )
174+ if len (parts ) != 2 :
175+ return messages
176+
177+ mode = parts [0 ].lower ()
178+ try :
179+ max_chars = int (parts [1 ])
180+ except ValueError :
181+ return messages
182+
183+ if mode not in ("middletruncate" , "start" , "end" ):
184+ return messages
185+
186+ result = []
187+ for msg in messages :
188+ new_msg = dict (msg )
189+ if isinstance (new_msg .get ("content" ), str ):
190+ new_msg ["content" ] = truncate_content (new_msg ["content" ], max_chars , mode )
191+ elif isinstance (new_msg .get ("content" ), list ):
192+ new_content = []
193+ for item in new_msg ["content" ]:
194+ if isinstance (item , dict ) and item .get ("type" ) == "text" :
195+ new_item = dict (item )
196+ new_item ["text" ] = truncate_content (
197+ item .get ("text" , "" ), max_chars , mode
198+ )
199+ new_content .append (new_item )
200+ else :
201+ new_content .append (item )
202+ new_msg ["content" ] = new_content
203+ result .append (new_msg )
204+ return result
205+
206+
145207def replace_messages_variable (
146208 template : str , messages : Optional [list [dict ]] = None
147209) -> str :
148210 def replacement_function (match ):
149- full_match = match .group (0 )
150- start_length = match .group (1 )
151- end_length = match .group (2 )
152- middle_length = match .group (3 )
211+ # Groups: (1) filter for bare MESSAGES
212+ # (2) START count, (3) filter for START
213+ # (4) END count, (5) filter for END
214+ # (6) MIDDLE count,(7) filter for MIDDLE
215+ bare_filter = match .group (1 )
216+ start_length = match .group (2 )
217+ start_filter = match .group (3 )
218+ end_length = match .group (4 )
219+ end_filter = match .group (5 )
220+ middle_length = match .group (6 )
221+ middle_filter = match .group (7 )
222+
153223 # If messages is None, handle it as an empty list
154224 if messages is None :
155225 return ""
156226
157- # Process messages based on the number of messages required
158- if full_match == "{{MESSAGES}}" :
159- return get_messages_content (messages )
160- elif start_length is not None :
161- return get_messages_content (messages [: int (start_length )])
227+ # Select messages based on the variant
228+ if start_length is not None :
229+ selected = messages [: int (start_length )]
230+ content_filter = start_filter
162231 elif end_length is not None :
163- return get_messages_content (messages [- int (end_length ) :])
232+ selected = messages [- int (end_length ) :]
233+ content_filter = end_filter
164234 elif middle_length is not None :
165235 mid = int (middle_length )
166-
167236 if len (messages ) <= mid :
168- return get_messages_content (messages )
169- # Handle middle truncation: split to get start and end portions of the messages list
170- half = mid // 2
171- start_msgs = messages [:half ]
172- end_msgs = messages [- half :] if mid % 2 == 0 else messages [- (half + 1 ) :]
173- formatted_start = get_messages_content (start_msgs )
174- formatted_end = get_messages_content (end_msgs )
175- return f"{ formatted_start } \n { formatted_end } "
176- return ""
237+ selected = messages
238+ else :
239+ half = mid // 2
240+ start_msgs = messages [:half ]
241+ end_msgs = (
242+ messages [- half :] if mid % 2 == 0 else messages [- (half + 1 ) :]
243+ )
244+ selected = start_msgs + end_msgs
245+ content_filter = middle_filter
246+ else :
247+ # Bare {{MESSAGES}} or {{MESSAGES|filter}}
248+ selected = messages
249+ content_filter = bare_filter
250+
251+ # Apply content filter if present
252+ if content_filter :
253+ selected = apply_content_filter (selected , content_filter )
254+
255+ return get_messages_content (selected )
177256
178257 template = re .sub (
179- r"{{MESSAGES}}|{{MESSAGES:START:(\d+)}}|{{MESSAGES:END:(\d+)}}|{{MESSAGES:MIDDLETRUNCATE:(\d+)}}" ,
258+ r"(?:"
259+ r"\{\{MESSAGES(?:\|(\w+:\d+))?\}\}"
260+ r"|\{\{MESSAGES:START:(\d+)(?:\|(\w+:\d+))?\}\}"
261+ r"|\{\{MESSAGES:END:(\d+)(?:\|(\w+:\d+))?\}\}"
262+ r"|\{\{MESSAGES:MIDDLETRUNCATE:(\d+)(?:\|(\w+:\d+))?\}\}"
263+ r")" ,
180264 replacement_function ,
181265 template ,
182266 )
0 commit comments