Skip to content

Commit bd7608b

Browse files
committed
Skills!
1 parent 235d592 commit bd7608b

14 files changed

Lines changed: 2021 additions & 189 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ LLMs are a part of our lives from here on out so join us in learning about and c
1919
* [Agent Mode](https://github.com/dwash96/aider-ce/blob/main/aider/website/docs/config/agent-mode.md)
2020
* [MCP Configuration](https://github.com/dwash96/aider-ce/blob/main/aider/website/docs/config/mcp.md)
2121
* [Session Management](https://github.com/dwash96/aider-ce/blob/main/aider/website/docs/sessions.md)
22+
* [Skills](https://github.com/dwash96/aider-ce/blob/main/aider/website/docs/config/skills.md)
2223
* [Aider Original Documentation (still mostly applies)](https://aider.chat/)
2324

2425
You can see a selection of the enhancements and updates by comparing the help output:
@@ -134,7 +135,7 @@ The current priorities are to improve core capabilities and user experience of t
134135
* [ ] Add a RAG tool for the model to ask questions about the codebase
135136
* [ ] Make the system prompts more aggressive about removing unneeded files/content from the context
136137
* [ ] Add a plugin-like system for allowing agent mode to use user-defined tools in simple python files
137-
* [ ] Add a dynamic tool discovery tool to allow the system to have only the tools it needs in context
138+
* [x] Add a dynamic tool discovery tool to allow the system to have only the tools it needs in context
138139

139140
### All Contributors (Both Aider Main and Aider-CE)
140141

aider/coders/agent_coder.py

Lines changed: 198 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
create_bigram_vector,
2828
normalize_vector,
2929
)
30+
31+
# Import skills helper for skills
32+
from aider.helpers.skills import SkillsManager
3033
from aider.mcp.server import LocalServer
3134
from aider.repo import ANY_GIT_ERROR
3235

@@ -50,10 +53,12 @@
5053
indent_lines,
5154
insert_block,
5255
list_changes,
56+
load_skill,
5357
ls,
5458
make_editable,
5559
make_readonly,
5660
remove,
61+
remove_skill,
5762
replace_all,
5863
replace_line,
5964
replace_lines,
@@ -126,6 +131,8 @@ def __init__(self, *args, **kwargs):
126131

127132
# Enable context management by default only in agent mode
128133
self.context_management_enabled = True # Enabled by default for agent mode
134+
# Skills configuration
135+
self.skills_manager = None # Will be initialized later
129136

130137
# Initialize change tracker for granular editing
131138
self.change_tracker = ChangeTracker()
@@ -193,10 +200,12 @@ def _build_tool_registry(self):
193200
indent_lines,
194201
insert_block,
195202
list_changes,
203+
load_skill,
196204
ls,
197205
make_editable,
198206
make_readonly,
199207
remove,
208+
remove_skill,
200209
replace_all,
201210
replace_line,
202211
replace_lines,
@@ -219,6 +228,10 @@ def _build_tool_registry(self):
219228
"tools_excludelist", agent_config.get("tools_blacklist", [])
220229
)
221230

231+
if "skills" not in self.allowed_context_blocks or not agent_config.get("skills_paths"):
232+
tools_excludelist.append("loadskill")
233+
tools_excludelist.append("removeskill")
234+
222235
# Always include essential tools regardless of includelist/excludelist
223236
essential_tools = {"makeeditable", "replacetext", "view", "finished"}
224237
for module in tool_modules:
@@ -286,6 +299,7 @@ def _get_agent_config(self):
286299
"git_status",
287300
"symbol_outline",
288301
"todo_list",
302+
"skills",
289303
}
290304

291305
if "exclude_context_blocks" in config:
@@ -301,8 +315,51 @@ def _get_agent_config(self):
301315
"skip_cli_confirmations", config.get("yolo", False)
302316
)
303317

318+
if "skills" in self.allowed_context_blocks:
319+
# Skills configuration
320+
if "skills_paths" not in config:
321+
config["skills_paths"] = []
322+
if "skills_includelist" not in config:
323+
config["skills_includelist"] = []
324+
if "skills_excludelist" not in config:
325+
config["skills_excludelist"] = []
326+
327+
self._initialize_skills_manager(config)
328+
304329
return config
305330

331+
def _initialize_skills_manager(self, config):
332+
"""
333+
Initialize the skills manager with the configured directory paths and filters.
334+
"""
335+
if not config.get("skills_paths", []):
336+
return
337+
338+
try:
339+
git_root = str(self.repo.root) if self.repo else None
340+
self.skills_manager = SkillsManager(
341+
directory_paths=config.get("skills_paths", []),
342+
include_list=config.get("skills_includelist", []),
343+
exclude_list=config.get("skills_excludelist", []),
344+
git_root=git_root,
345+
coder=self, # Pass reference to the coder instance
346+
)
347+
348+
except Exception as e:
349+
self.io.tool_warning(f"Failed to initialize skills manager: {str(e)}")
350+
351+
def show_announcements(self):
352+
super().show_announcements()
353+
354+
# Find and log available skills
355+
skills = self.skills_manager.find_skills()
356+
if skills:
357+
skills_list = []
358+
for skill in skills:
359+
skills_list.append(skill.name)
360+
361+
self.io.tool_output(f"Available Skills: {", ".join(skills_list)}")
362+
306363
def get_local_tool_schemas(self):
307364
"""Returns the JSON schemas for all local tools using the tool registry."""
308365
schemas = []
@@ -503,6 +560,8 @@ def _calculate_context_block_tokens(self, force=False):
503560
"directory_structure",
504561
"git_status",
505562
"symbol_outline",
563+
"skills",
564+
"loaded_skills",
506565
]
507566

508567
for block_type in block_types:
@@ -539,6 +598,10 @@ def _generate_context_block(self, block_name):
539598
content = self.get_context_summary()
540599
elif block_name == "todo_list":
541600
content = self.get_todo_list()
601+
elif block_name == "skills":
602+
content = self.get_skills_context()
603+
elif block_name == "loaded_skills":
604+
content = self.get_skills_content()
542605

543606
# Cache the result if it's not None
544607
if content is not None:
@@ -697,13 +760,16 @@ def format_chat_chunks(self):
697760
chunks = ChatChunks(
698761
chunk_ordering=[
699762
"system",
763+
"static",
700764
"examples",
701765
"readonly_files",
702766
"repo",
703767
"chat_files",
768+
"pre_message",
704769
"done",
705770
"edit_files",
706771
"cur",
772+
"post_message",
707773
"reminder",
708774
]
709775
)
@@ -760,59 +826,75 @@ def format_chat_chunks(self):
760826
# This also populates the context block cache
761827
self._calculate_context_block_tokens()
762828

763-
# Get blocks from cache to avoid regenerating them
764-
env_context = self.get_cached_context_block("environment_info")
765-
dir_structure = self.get_cached_context_block("directory_structure")
766-
git_status = self.get_cached_context_block("git_status")
767-
symbol_outline = self.get_cached_context_block("symbol_outline")
768-
todo_list = self.get_cached_context_block("todo_list")
769-
770-
# Context summary needs special handling because it depends on other blocks
771-
context_summary = self.get_context_summary()
829+
# Initialize chunk sections
830+
chunks.static = []
831+
chunks.pre_message = []
832+
chunks.post_message = []
772833

773834
# 1. Add relatively static blocks BEFORE done_messages
774835
# These blocks change less frequently and can be part of the cacheable prefix
775836
static_blocks = []
776-
if env_context and "environment_info" in self.allowed_context_blocks:
777-
static_blocks.append(env_context)
778-
if dir_structure and "directory_structure" in self.allowed_context_blocks:
779-
static_blocks.append(dir_structure)
780-
781-
if static_blocks:
782-
static_message = "\n\n".join(static_blocks)
783-
# Insert as a system message right before done_messages
784-
chunks.system.append(dict(role="system", content=static_message))
785837

786838
# 2. Add dynamic blocks AFTER chat_files
787839
# These blocks change with the current files in context
788-
pre_dynamic_blocks = []
789-
post_dynamic_blocks = []
790-
if context_summary and "context_summary" in self.allowed_context_blocks:
791-
pre_dynamic_blocks.append(context_summary)
792-
if symbol_outline and "symbol_outline" in self.allowed_context_blocks:
793-
pre_dynamic_blocks.append(symbol_outline)
794-
if git_status and "git_status" in self.allowed_context_blocks:
795-
pre_dynamic_blocks.append(git_status)
796-
797-
if todo_list and "todo_list" in self.allowed_context_blocks:
798-
pre_dynamic_blocks.append(todo_list)
840+
pre_message_blocks = []
841+
post_message_blocks = []
842+
843+
if "environment_info" in self.allowed_context_blocks:
844+
block = self.get_cached_context_block("environment_info")
845+
static_blocks.append(block)
846+
847+
if "directory_structure" in self.allowed_context_blocks:
848+
block = self.get_cached_context_block("directory_structure")
849+
static_blocks.append(block)
850+
851+
if "skills" in self.allowed_context_blocks:
852+
block = self._generate_context_block("skills")
853+
static_blocks.append(block)
854+
855+
if "symbol_outline" in self.allowed_context_blocks:
856+
block = self.get_cached_context_block("symbol_outline")
857+
pre_message_blocks.append(block)
858+
859+
if "git_status" in self.allowed_context_blocks:
860+
block = self.get_cached_context_block("git_status")
861+
pre_message_blocks.append(block)
862+
863+
if "todo_list" in self.allowed_context_blocks:
864+
block = self.get_cached_context_block("todo_list")
865+
pre_message_blocks.append(block)
866+
867+
if "skills" in self.allowed_context_blocks:
868+
block = self._generate_context_block("loaded_skills")
869+
pre_message_blocks.append(block)
870+
871+
if "context_summary" in self.allowed_context_blocks:
872+
# Context summary needs special handling because it depends on other blocks
873+
block = self.get_context_summary()
874+
pre_message_blocks.insert(0, block)
875+
799876
# Add tool usage context if there are repetitive tools
800877
if hasattr(self, "tool_usage_history") and self.tool_usage_history:
801878
repetitive_tools = self._get_repetitive_tools()
802879
if repetitive_tools:
803880
tool_context = self._generate_tool_context(repetitive_tools)
804881
if tool_context:
805-
post_dynamic_blocks.append(tool_context)
882+
post_message_blocks.append(tool_context)
806883

807-
if pre_dynamic_blocks:
808-
dynamic_message = "\n\n".join(pre_dynamic_blocks)
809-
# Append as a system message on reminders
810-
chunks.done.insert(0, dict(role="system", content=dynamic_message))
884+
if static_blocks:
885+
for block in static_blocks:
886+
if block:
887+
chunks.static.append(dict(role="system", content=block))
888+
889+
if pre_message_blocks:
890+
for block in pre_message_blocks:
891+
if block:
892+
chunks.pre_message.append(dict(role="system", content=block))
811893

812-
if post_dynamic_blocks:
813-
dynamic_message = "\n\n".join(post_dynamic_blocks)
814-
# Append as a system message on reminders
815-
reminder_message.insert(0, dict(role="system", content=dynamic_message))
894+
if post_message_blocks:
895+
for block in post_message_blocks:
896+
if block:
897+
chunks.post_message.append(dict(role="system", content=block))
816898

817899
# Use accurate token counting method that considers enhanced context blocks
818900
base_messages = chunks.all_messages()
@@ -855,6 +937,9 @@ def format_chat_chunks(self):
855937
)
856938
chunks.cur[-1] = dict(role=final["role"], content=new_content)
857939

940+
if self.verbose:
941+
self._log_chunks(chunks)
942+
858943
return chunks
859944

860945
def _update_edit_file_tracking(self, edit_file_names):
@@ -2273,6 +2358,38 @@ def get_todo_list(self):
22732358
self.io.tool_error(f"Error generating todo list context: {str(e)}")
22742359
return None
22752360

2361+
def get_skills_context(self):
2362+
"""
2363+
Generate a context block for available skills.
2364+
2365+
Returns:
2366+
Formatted context block string or None if no skills available
2367+
"""
2368+
if not self.use_enhanced_context or not self.skills_manager:
2369+
return None
2370+
2371+
try:
2372+
return self.skills_manager.get_skills_context()
2373+
except Exception as e:
2374+
self.io.tool_error(f"Error generating skills context: {str(e)}")
2375+
return None
2376+
2377+
def get_skills_content(self):
2378+
"""
2379+
Generate a context block with the actual content of loaded skills.
2380+
2381+
Returns:
2382+
Formatted context block string with skill contents or None if no skills available
2383+
"""
2384+
if not self.use_enhanced_context or not self.skills_manager:
2385+
return None
2386+
2387+
try:
2388+
return self.skills_manager.get_skills_content()
2389+
except Exception as e:
2390+
self.io.tool_error(f"Error generating skills content context: {str(e)}")
2391+
return None
2392+
22762393
def get_git_status(self):
22772394
"""
22782395
Generate a git status context block for repository information.
@@ -2416,3 +2533,46 @@ def cmd_context_blocks(self, args=""):
24162533
self.tokens_calculated = False
24172534

24182535
return True
2536+
2537+
def _log_chunks(self, chunks):
2538+
try:
2539+
import hashlib
2540+
import json
2541+
2542+
if not hasattr(self, "_message_hashes"):
2543+
self._message_hashes = {
2544+
"system": None,
2545+
"static": None,
2546+
"examples": None,
2547+
"readonly_files": None,
2548+
"repo": None,
2549+
"chat_files": None,
2550+
"pre_message": None,
2551+
"done": None,
2552+
"edit_files": None,
2553+
"cur": None,
2554+
"post_message": None,
2555+
"reminder": None,
2556+
}
2557+
2558+
changes = []
2559+
for key, value in self._message_hashes.items():
2560+
json_obj = json.dumps(
2561+
getattr(chunks, key, ""), sort_keys=True, separators=(",", ":")
2562+
)
2563+
new_hash = hashlib.sha256(json_obj.encode("utf-8")).hexdigest()
2564+
if self._message_hashes[key] != new_hash:
2565+
changes.append(key)
2566+
2567+
self._message_hashes[key] = new_hash
2568+
2569+
print("")
2570+
print("MESSAGE CHUNK HASHES")
2571+
print(self._message_hashes)
2572+
print("")
2573+
print(changes)
2574+
print("")
2575+
2576+
except Exception as e:
2577+
print(e)
2578+
pass

0 commit comments

Comments
 (0)