11from codetide import CodeTide
22from ...mcp .tools .patch_code import file_exists , open_file , process_patch , remove_file , write_file , parse_patch_blocks
3- from ...core .defaults import DEFAULT_ENCODING , DEFAULT_STORAGE_PATH
3+ from ...core .defaults import DEFAULT_STORAGE_PATH
44from ...parsers import SUPPORTED_LANGUAGES
55from ...autocomplete import AutoComplete
66from .models import Steps
1313
1414try :
1515 from aicore .llm import Llm
16- from aicore .logger import _logger , SPECIAL_TOKENS
16+ from aicore .logger import _logger
17+ from .streaming .service import custom_logger_fn
1718except ImportError as e :
1819 raise ImportError (
1920 "The 'codetide.agents' module requires the 'aicore' package. "
2930from datetime import date
3031from pathlib import Path
3132from ulid import ulid
32- import aiofiles
3333import asyncio
3434import pygit2
3535import os
3636
37- async def custom_logger_fn (message :str , session_id :str , filepath :str ):
38- if message not in SPECIAL_TOKENS :
39- async with aiofiles .open (filepath , 'a' , encoding = DEFAULT_ENCODING ) as f :
40- await f .write (message )
41-
42- await _logger .log_chunk_to_queue (message , session_id )
43-
4437class AgentTide (BaseModel ):
4538 llm :Llm
4639 tide :CodeTide
@@ -60,6 +53,11 @@ class AgentTide(BaseModel):
6053 _has_patch :bool = False
6154 _direct_mode :bool = False
6255
56+ # Number of previous interactions to remember for context identifiers
57+ CONTEXT_WINDOW_SIZE : int = 3
58+ # Rolling window of identifier sets from previous N interactions
59+ _context_identifier_window : Optional [list ] = None
60+
6361 model_config = ConfigDict (arbitrary_types_allowed = True )
6462
6563 @model_validator (mode = "after" )
@@ -134,23 +132,43 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
134132 await self .tide .check_for_updates (serialize = True , include_cached_ids = True )
135133 self ._clean_history ()
136134
135+ # Initialize the context identifier window if not present
136+ if self ._context_identifier_window is None :
137+ self ._context_identifier_window = []
138+
137139 codeContext = None
138140 if self ._skip_context_retrieval :
139141 ...
140142 else :
141143 autocomplete = AutoComplete (self .tide .cached_ids )
142144 if self ._direct_mode :
143145 self .contextIdentifiers = None
144- exact_matches = autocomplete .extract_words_from_text (self .history [- 1 ], max_matches_per_word = 1 )["all_found_words" ]
146+ # Only extract matches from the last message
147+ last_message = self .history [- 1 ] if self .history else ""
148+ exact_matches = autocomplete .extract_words_from_text (last_message , max_matches_per_word = 1 )["all_found_words" ]
145149 self .modifyIdentifiers = self .tide ._as_file_paths (exact_matches )
146150 codeIdentifiers = self .modifyIdentifiers
147151 self ._direct_mode = False
148-
152+ # Update the context identifier window
153+ self ._context_identifier_window .append (set (exact_matches ))
154+ if len (self ._context_identifier_window ) > self .CONTEXT_WINDOW_SIZE :
155+ self ._context_identifier_window .pop (0 )
149156 else :
150- matches = autocomplete .extract_words_from_text ("\n \n " .join (self .history ), max_matches_per_word = 1 )
151-
152- # --- Begin Unified Identifier Retrieval ---
153- identifiers_accum = set (matches ["all_found_words" ]) if codeIdentifiers is None else set (codeIdentifiers + matches ["all_found_words" ])
157+ # Only extract matches from the last message
158+ last_message = self .history [- 1 ] if self .history else ""
159+ matches = autocomplete .extract_words_from_text (last_message , max_matches_per_word = 1 )["all_found_words" ]
160+ print (f"{ matches = } " )
161+ # Update the context identifier window
162+ self ._context_identifier_window .append (set (matches ))
163+ if len (self ._context_identifier_window ) > self .CONTEXT_WINDOW_SIZE :
164+ self ._context_identifier_window .pop (0 )
165+ # Combine identifiers from the last N interactions
166+ window_identifiers = set ()
167+ for s in self ._context_identifier_window :
168+ window_identifiers .update (s )
169+ # If codeIdentifiers is passed, include them as well
170+ identifiers_accum = set (codeIdentifiers ) if codeIdentifiers else set ()
171+ identifiers_accum .update (window_identifiers )
154172 modify_accum = set ()
155173 reasoning_accum = []
156174 repo_tree = None
@@ -166,57 +184,55 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
166184 repo_history = self .history
167185 if previous_reason :
168186 repo_history += [previous_reason ]
169-
187+
170188 repo_tree = await self .get_repo_tree_from_user_prompt (self .history , include_modules = bool (smart_search_attempts ), expand_paths = expand_paths )
171-
189+
172190 # 2. Single LLM call with unified prompt
173191 # Pass accumulated identifiers for context if this isn't the first iteration
174192 accumulated_context = "\n " .join (
175193 sorted ((identifiers_accum or set ()) | (modify_accum or set ()))
176194 ) if (identifiers_accum or modify_accum ) else ""
177-
195+
178196 unified_response = await self .llm .acomplete (
179197 self .history ,
180198 system_prompt = [GET_CODE_IDENTIFIERS_UNIFIED_PROMPT .format (
181- DATE = TODAY ,
199+ DATE = TODAY ,
182200 SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES ,
183201 IDENTIFIERS = accumulated_context
184202 )],
185203 prefix_prompt = repo_tree ,
186204 stream = False
187205 )
188- print (f"{ unified_response = } " )
189206
190207 # Parse the unified response
191208 contextIdentifiers = parse_blocks (unified_response , block_word = "Context Identifiers" , multiple = False )
192209 modifyIdentifiers = parse_blocks (unified_response , block_word = "Modify Identifiers" , multiple = False )
193- expandPaths = parse_blocks (unified_response , block_word = "Expand Paths" , multiple = False )
194-
210+ expandPaths = parse_blocks (unified_response , block_word = "Expand Paths" , multiple = False )
211+
195212 # Extract reasoning (everything before the first "*** Begin")
196213 reasoning_parts = unified_response .split ("*** Begin" )
197214 if reasoning_parts :
198215 reasoning_accum .append (reasoning_parts [0 ].strip ())
199216 previous_reason = reasoning_accum [- 1 ]
200-
217+
201218 # Accumulate identifiers
202219 if contextIdentifiers :
203220 if smart_search_attempts == 0 :
204- ### clean wrongly mismtatched idenitifers
205221 identifiers_accum = set ()
206222 for ident in contextIdentifiers .splitlines ():
207- if ident := self .get_valid_identifier (autocomplete , ident .strip ()):
223+ if ident := self .get_valid_identifier (autocomplete , ident .strip ()):
208224 identifiers_accum .add (ident )
209-
225+
210226 if modifyIdentifiers :
211227 for ident in modifyIdentifiers .splitlines ():
212228 if ident := self .get_valid_identifier (autocomplete , ident .strip ()):
213229 modify_accum .add (ident .strip ())
214-
230+
215231 if expandPaths :
216232 expand_paths = [
217233 path for ident in expandPaths if (path := self .get_valid_identifier (autocomplete , ident .strip ()))
218234 ]
219-
235+
220236 # Check if we have enough identifiers (unified prompt includes this decision)
221237 if "ENOUGH_IDENTIFIERS: TRUE" in unified_response .upper ():
222238 done = True
@@ -235,7 +251,7 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
235251 self .modifyIdentifiers = self .tide ._as_file_paths (self .modifyIdentifiers )
236252 codeIdentifiers .extend (self .modifyIdentifiers )
237253 # TODO preserve passed identifiers by the user
238- codeIdentifiers += matches [ "all_found_words" ]
254+ codeIdentifiers += matches
239255
240256 # --- End Unified Identifier Retrieval ---
241257 if codeIdentifiers :
@@ -244,7 +260,8 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
244260
245261 if not codeContext :
246262 codeContext = REPO_TREE_CONTEXT_PROMPT .format (REPO_TREE = self .tide .codebase .get_tree_view ())
247- readmeFile = self .tide .get (["README.md" ] + matches ["all_found_words" ] , as_string_list = True )
263+ # Use matches from the last message for README context
264+ readmeFile = self .tide .get (["README.md" ] + (matches if 'matches' in locals () else []), as_string_list = True )
248265 if readmeFile :
249266 codeContext = "\n " .join ([codeContext , README_CONTEXT_PROMPT .format (README = readmeFile )])
250267
0 commit comments