mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Add support for AGENTS.md files in microagent system (#10528)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -2,7 +2,7 @@ import io
|
||||
import re
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
from typing import ClassVar, Union
|
||||
|
||||
import frontmatter
|
||||
from pydantic import BaseModel
|
||||
@@ -23,6 +23,31 @@ class BaseMicroagent(BaseModel):
|
||||
source: str # path to the file
|
||||
type: MicroagentType
|
||||
|
||||
PATH_TO_THIRD_PARTY_MICROAGENT_NAME: ClassVar[dict[str, str]] = {
|
||||
'.cursorrules': 'cursorrules',
|
||||
'agents.md': 'agents',
|
||||
'agent.md': 'agents',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _handle_third_party(
|
||||
cls, path: Path, file_content: str
|
||||
) -> Union['RepoMicroagent', None]:
|
||||
# Determine the agent name based on file type
|
||||
microagent_name = cls.PATH_TO_THIRD_PARTY_MICROAGENT_NAME.get(path.name.lower())
|
||||
|
||||
# Create RepoMicroagent if we recognized the file type
|
||||
if microagent_name is not None:
|
||||
return RepoMicroagent(
|
||||
name=microagent_name,
|
||||
content=file_content,
|
||||
metadata=MicroagentMetadata(name=microagent_name),
|
||||
source=str(path),
|
||||
type=MicroagentType.REPO_KNOWLEDGE,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def load(
|
||||
cls,
|
||||
@@ -40,11 +65,10 @@ class BaseMicroagent(BaseModel):
|
||||
# Otherwise, we will rely on the name from metadata later
|
||||
derived_name = None
|
||||
if microagent_dir is not None:
|
||||
# Special handling for .cursorrules files which are not in microagent_dir
|
||||
if path.name == '.cursorrules':
|
||||
derived_name = 'cursorrules'
|
||||
else:
|
||||
derived_name = str(path.relative_to(microagent_dir).with_suffix(''))
|
||||
# Special handling for files which are not in microagent_dir
|
||||
derived_name = cls.PATH_TO_THIRD_PARTY_MICROAGENT_NAME.get(
|
||||
path.name.lower()
|
||||
) or str(path.relative_to(microagent_dir).with_suffix(''))
|
||||
|
||||
# Only load directly from path if file_content is not provided
|
||||
if file_content is None:
|
||||
@@ -61,15 +85,10 @@ class BaseMicroagent(BaseModel):
|
||||
type=MicroagentType.REPO_KNOWLEDGE,
|
||||
)
|
||||
|
||||
# Handle .cursorrules files
|
||||
if path.name == '.cursorrules':
|
||||
return RepoMicroagent(
|
||||
name='cursorrules',
|
||||
content=file_content,
|
||||
metadata=MicroagentMetadata(name='cursorrules'),
|
||||
source=str(path),
|
||||
type=MicroagentType.REPO_KNOWLEDGE,
|
||||
)
|
||||
# Handle third-party agent instruction files
|
||||
third_party_agent = cls._handle_third_party(path, file_content)
|
||||
if third_party_agent is not None:
|
||||
return third_party_agent
|
||||
|
||||
file_io = io.StringIO(file_content)
|
||||
loaded = frontmatter.load(file_io)
|
||||
@@ -276,31 +295,44 @@ def load_microagents_from_dir(
|
||||
|
||||
# Load all agents from microagents directory
|
||||
logger.debug(f'Loading agents from {microagent_dir}')
|
||||
if microagent_dir.exists():
|
||||
# Collect .cursorrules file from repo root and .md files from microagents dir
|
||||
cursorrules_files = []
|
||||
if (microagent_dir.parent.parent / '.cursorrules').exists():
|
||||
cursorrules_files = [microagent_dir.parent.parent / '.cursorrules']
|
||||
|
||||
# Always check for .cursorrules and AGENTS.md files in repo root, regardless of whether microagents_dir exists
|
||||
special_files = []
|
||||
repo_root = microagent_dir.parent.parent
|
||||
|
||||
# Check for .cursorrules
|
||||
if (repo_root / '.cursorrules').exists():
|
||||
special_files.append(repo_root / '.cursorrules')
|
||||
|
||||
# Check for AGENTS.md (case-insensitive)
|
||||
for agents_filename in ['AGENTS.md', 'agents.md', 'AGENT.md', 'agent.md']:
|
||||
agents_path = repo_root / agents_filename
|
||||
if agents_path.exists():
|
||||
special_files.append(agents_path)
|
||||
break # Only add the first one found to avoid duplicates
|
||||
|
||||
# Collect .md files from microagents directory if it exists
|
||||
md_files = []
|
||||
if microagent_dir.exists():
|
||||
md_files = [f for f in microagent_dir.rglob('*.md') if f.name != 'README.md']
|
||||
|
||||
# Process all files in one loop
|
||||
for file in chain(cursorrules_files, md_files):
|
||||
try:
|
||||
agent = BaseMicroagent.load(file, microagent_dir)
|
||||
if isinstance(agent, RepoMicroagent):
|
||||
repo_agents[agent.name] = agent
|
||||
elif isinstance(agent, KnowledgeMicroagent):
|
||||
# Both KnowledgeMicroagent and TaskMicroagent go into knowledge_agents
|
||||
knowledge_agents[agent.name] = agent
|
||||
except MicroagentValidationError as e:
|
||||
# For validation errors, include the original exception
|
||||
error_msg = f'Error loading microagent from {file}: {str(e)}'
|
||||
raise MicroagentValidationError(error_msg) from e
|
||||
except Exception as e:
|
||||
# For other errors, wrap in a ValueError with detailed message
|
||||
error_msg = f'Error loading microagent from {file}: {str(e)}'
|
||||
raise ValueError(error_msg) from e
|
||||
# Process all files in one loop
|
||||
for file in chain(special_files, md_files):
|
||||
try:
|
||||
agent = BaseMicroagent.load(file, microagent_dir)
|
||||
if isinstance(agent, RepoMicroagent):
|
||||
repo_agents[agent.name] = agent
|
||||
elif isinstance(agent, KnowledgeMicroagent):
|
||||
# Both KnowledgeMicroagent and TaskMicroagent go into knowledge_agents
|
||||
knowledge_agents[agent.name] = agent
|
||||
except MicroagentValidationError as e:
|
||||
# For validation errors, include the original exception
|
||||
error_msg = f'Error loading microagent from {file}: {str(e)}'
|
||||
raise MicroagentValidationError(error_msg) from e
|
||||
except Exception as e:
|
||||
# For other errors, wrap in a ValueError with detailed message
|
||||
error_msg = f'Error loading microagent from {file}: {str(e)}'
|
||||
raise ValueError(error_msg) from e
|
||||
|
||||
logger.debug(
|
||||
f'Loaded {len(repo_agents) + len(knowledge_agents)} microagents: '
|
||||
|
||||
Reference in New Issue
Block a user