nexus-dev 3.2.0__py3-none-any.whl
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.
Potentially problematic release.
This version of nexus-dev might be problematic. Click here for more details.
- nexus_dev/__init__.py +4 -0
- nexus_dev/agent_templates/__init__.py +26 -0
- nexus_dev/agent_templates/api_designer.yaml +26 -0
- nexus_dev/agent_templates/code_reviewer.yaml +26 -0
- nexus_dev/agent_templates/debug_detective.yaml +26 -0
- nexus_dev/agent_templates/doc_writer.yaml +26 -0
- nexus_dev/agent_templates/performance_optimizer.yaml +26 -0
- nexus_dev/agent_templates/refactor_architect.yaml +26 -0
- nexus_dev/agent_templates/security_auditor.yaml +26 -0
- nexus_dev/agent_templates/test_engineer.yaml +26 -0
- nexus_dev/agents/__init__.py +20 -0
- nexus_dev/agents/agent_config.py +97 -0
- nexus_dev/agents/agent_executor.py +197 -0
- nexus_dev/agents/agent_manager.py +104 -0
- nexus_dev/agents/prompt_factory.py +91 -0
- nexus_dev/chunkers/__init__.py +168 -0
- nexus_dev/chunkers/base.py +202 -0
- nexus_dev/chunkers/docs_chunker.py +291 -0
- nexus_dev/chunkers/java_chunker.py +343 -0
- nexus_dev/chunkers/javascript_chunker.py +312 -0
- nexus_dev/chunkers/python_chunker.py +308 -0
- nexus_dev/cli.py +1673 -0
- nexus_dev/config.py +253 -0
- nexus_dev/database.py +558 -0
- nexus_dev/embeddings.py +585 -0
- nexus_dev/gateway/__init__.py +10 -0
- nexus_dev/gateway/connection_manager.py +348 -0
- nexus_dev/github_importer.py +247 -0
- nexus_dev/mcp_client.py +281 -0
- nexus_dev/mcp_config.py +184 -0
- nexus_dev/schemas/mcp_config_schema.json +166 -0
- nexus_dev/server.py +1866 -0
- nexus_dev/templates/pre-commit-hook +33 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/__init__.py +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/api_designer.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/code_reviewer.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/debug_detective.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/doc_writer.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/performance_optimizer.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/refactor_architect.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/security_auditor.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/agent_templates/test_engineer.yaml +26 -0
- nexus_dev-3.2.0.data/data/nexus_dev/templates/pre-commit-hook +33 -0
- nexus_dev-3.2.0.dist-info/METADATA +636 -0
- nexus_dev-3.2.0.dist-info/RECORD +48 -0
- nexus_dev-3.2.0.dist-info/WHEEL +4 -0
- nexus_dev-3.2.0.dist-info/entry_points.txt +12 -0
- nexus_dev-3.2.0.dist-info/licenses/LICENSE +21 -0
nexus_dev/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Agent templates package."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
TEMPLATES_DIR = Path(__file__).parent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_template_path(template_name: str) -> Path:
|
|
9
|
+
"""Get the path to a template file.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
template_name: Name of the template (without .yaml extension).
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Path to the template YAML file.
|
|
16
|
+
"""
|
|
17
|
+
return TEMPLATES_DIR / f"{template_name}.yaml"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def list_templates() -> list[str]:
|
|
21
|
+
"""List all available template names.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of template names (without .yaml extension).
|
|
25
|
+
"""
|
|
26
|
+
return [p.stem for p in TEMPLATES_DIR.glob("*.yaml") if not p.name.startswith("_")]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "api_designer"
|
|
2
|
+
display_name: "API Designer"
|
|
3
|
+
description: "Reviews and designs REST/GraphQL APIs following best practices."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "API Architect"
|
|
7
|
+
goal: "Design RESTful and GraphQL APIs that are intuitive, consistent, and follow industry best practices"
|
|
8
|
+
backstory: |
|
|
9
|
+
API architect with extensive experience in designing scalable, developer-friendly APIs.
|
|
10
|
+
Expert in REST principles, GraphQL schema design, API versioning, and documentation.
|
|
11
|
+
Has designed APIs for companies ranging from startups to enterprises. Believes that
|
|
12
|
+
great APIs are predictable, consistent, and self-explanatory.
|
|
13
|
+
tone: "Professional, pragmatic, and standards-focused"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 6
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "claude-sonnet-4.5"
|
|
24
|
+
fallback_hints: ["gpt-5.2", "gemini-3-pro", "auto"]
|
|
25
|
+
temperature: 0.4
|
|
26
|
+
max_tokens: 5000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "code_reviewer"
|
|
2
|
+
display_name: "Code Reviewer"
|
|
3
|
+
description: "Analyzes code for bugs, security issues, and best practices."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Senior Code Reviewer"
|
|
7
|
+
goal: "Identify bugs, security vulnerabilities, and suggest improvements following best practices"
|
|
8
|
+
backstory: |
|
|
9
|
+
Expert software engineer with 15+ years of experience in code quality assurance.
|
|
10
|
+
Specialized in identifying subtle bugs, security vulnerabilities, and architectural issues.
|
|
11
|
+
Familiar with SOLID principles, design patterns, and language-specific best practices.
|
|
12
|
+
Provides constructive feedback that helps developers learn and improve.
|
|
13
|
+
tone: "Professional, constructive, and educational"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 8
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "claude-sonnet-4.5"
|
|
24
|
+
fallback_hints: ["gpt-5.2-codex", "gemini-3-pro", "auto"]
|
|
25
|
+
temperature: 0.3
|
|
26
|
+
max_tokens: 4000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "debug_detective"
|
|
2
|
+
display_name: "Debug Detective"
|
|
3
|
+
description: "Investigates bugs, analyzes stack traces, and proposes targeted fixes."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Debugging Specialist"
|
|
7
|
+
goal: "Analyze errors, trace root causes, and propose precise, targeted fixes"
|
|
8
|
+
backstory: |
|
|
9
|
+
Veteran debugger with a methodical approach to solving cryptic errors.
|
|
10
|
+
Expert in reading stack traces, understanding memory issues, async debugging,
|
|
11
|
+
and race conditions. Has a systematic process: reproduce, isolate, understand, fix.
|
|
12
|
+
Known for finding the needle in the haystack.
|
|
13
|
+
tone: "Analytical, patient, and thorough"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 10
|
|
18
|
+
search_types: ["code", "lesson", "documentation"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "claude-sonnet-4.5"
|
|
24
|
+
fallback_hints: ["gemini-3-deep-think", "gpt-5.2-codex", "auto"]
|
|
25
|
+
temperature: 0.2
|
|
26
|
+
max_tokens: 6000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "doc_writer"
|
|
2
|
+
display_name: "Documentation Writer"
|
|
3
|
+
description: "Creates and improves technical documentation, API docs, and README files."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Technical Writer"
|
|
7
|
+
goal: "Create clear, comprehensive documentation that helps developers understand and use code effectively"
|
|
8
|
+
backstory: |
|
|
9
|
+
Professional technical writer with expertise in developer documentation.
|
|
10
|
+
Skilled at explaining complex concepts in simple terms, creating API references,
|
|
11
|
+
writing tutorials, and maintaining README files. Believes good documentation
|
|
12
|
+
is as important as good code.
|
|
13
|
+
tone: "Clear, friendly, and helpful"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 6
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "claude-opus-4.5"
|
|
24
|
+
fallback_hints: ["claude-sonnet-4.5", "gpt-5.2", "auto"]
|
|
25
|
+
temperature: 0.6
|
|
26
|
+
max_tokens: 6000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "performance_optimizer"
|
|
2
|
+
display_name: "Performance Optimizer"
|
|
3
|
+
description: "Identifies performance bottlenecks and recommends optimizations."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Performance Engineer"
|
|
7
|
+
goal: "Identify performance bottlenecks, optimize code for speed and efficiency, and reduce resource consumption"
|
|
8
|
+
backstory: |
|
|
9
|
+
Performance engineering specialist with expertise in profiling, benchmarking, and optimization.
|
|
10
|
+
Skilled at identifying N+1 queries, memory leaks, inefficient algorithms, and resource bottlenecks.
|
|
11
|
+
Has optimized applications handling millions of requests per day. Believes in measuring first,
|
|
12
|
+
then optimizing based on data.
|
|
13
|
+
tone: "Data-driven, analytical, and results-oriented"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 8
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "gemini-3-pro"
|
|
24
|
+
fallback_hints: ["claude-sonnet-4.5", "gpt-5.2-codex", "auto"]
|
|
25
|
+
temperature: 0.3
|
|
26
|
+
max_tokens: 5000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "refactor_architect"
|
|
2
|
+
display_name: "Refactor Architect"
|
|
3
|
+
description: "Restructures code for better maintainability, performance, and design patterns."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Refactoring Expert"
|
|
7
|
+
goal: "Restructure code to improve readability, maintainability, and performance without changing behavior"
|
|
8
|
+
backstory: |
|
|
9
|
+
Software architect with deep expertise in design patterns and clean code principles.
|
|
10
|
+
Specializes in identifying code smells, reducing complexity, and applying SOLID principles.
|
|
11
|
+
Has successfully refactored legacy codebases for Fortune 500 companies. Believes that
|
|
12
|
+
well-structured code is easier to understand, test, and extend.
|
|
13
|
+
tone: "Strategic, clear, and methodical"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 10
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "gemini-3-deep-think"
|
|
24
|
+
fallback_hints: ["claude-opus-4.5", "claude-sonnet-4.5", "auto"]
|
|
25
|
+
temperature: 0.4
|
|
26
|
+
max_tokens: 6000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "security_auditor"
|
|
2
|
+
display_name: "Security Auditor"
|
|
3
|
+
description: "Identifies security vulnerabilities and recommends fixes based on OWASP guidelines."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "Security Analyst"
|
|
7
|
+
goal: "Identify security vulnerabilities, recommend fixes, and ensure code follows security best practices"
|
|
8
|
+
backstory: |
|
|
9
|
+
Cybersecurity expert with focus on application security and secure coding practices.
|
|
10
|
+
Familiar with OWASP Top 10, common vulnerabilities (SQL injection, XSS, CSRF, etc.),
|
|
11
|
+
and security frameworks. Has experience with security audits, penetration testing,
|
|
12
|
+
and secure code reviews for financial and healthcare applications.
|
|
13
|
+
tone: "Serious, precise, and security-focused"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 7
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "claude-opus-4.5"
|
|
24
|
+
fallback_hints: ["claude-sonnet-4.5", "gpt-5.2", "auto"]
|
|
25
|
+
temperature: 0.2
|
|
26
|
+
max_tokens: 5000
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "test_engineer"
|
|
2
|
+
display_name: "Test Engineer"
|
|
3
|
+
description: "Generates comprehensive test cases and improves test coverage."
|
|
4
|
+
|
|
5
|
+
profile:
|
|
6
|
+
role: "QA Engineer"
|
|
7
|
+
goal: "Generate comprehensive test cases covering edge cases, improve test coverage, and ensure code quality"
|
|
8
|
+
backstory: |
|
|
9
|
+
Quality assurance specialist with expertise in TDD and BDD methodologies.
|
|
10
|
+
Skilled in pytest, Jest, JUnit, and other testing frameworks. Believes in testing
|
|
11
|
+
the unhappy paths as much as the happy ones. Writes tests that are readable,
|
|
12
|
+
maintainable, and catch real bugs.
|
|
13
|
+
tone: "Methodical, detail-oriented, and thorough"
|
|
14
|
+
|
|
15
|
+
memory:
|
|
16
|
+
enabled: true
|
|
17
|
+
rag_limit: 5
|
|
18
|
+
search_types: ["code", "documentation", "lesson"]
|
|
19
|
+
|
|
20
|
+
tools: []
|
|
21
|
+
|
|
22
|
+
llm_config:
|
|
23
|
+
model_hint: "gpt-5.2-codex"
|
|
24
|
+
fallback_hints: ["claude-sonnet-4.5", "gemini-3-pro", "auto"]
|
|
25
|
+
temperature: 0.4
|
|
26
|
+
max_tokens: 4000
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Nexus-Dev Custom Agents Package.
|
|
2
|
+
|
|
3
|
+
This package provides functionality for defining and executing custom AI agents
|
|
4
|
+
that leverage the project's RAG (lessons/docs) and MCP tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .agent_config import AgentConfig, AgentMemory, AgentProfile, LLMConfig
|
|
8
|
+
from .agent_executor import AgentExecutor
|
|
9
|
+
from .agent_manager import AgentManager
|
|
10
|
+
from .prompt_factory import PromptFactory
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AgentConfig",
|
|
14
|
+
"AgentProfile",
|
|
15
|
+
"AgentMemory",
|
|
16
|
+
"LLMConfig",
|
|
17
|
+
"AgentManager",
|
|
18
|
+
"AgentExecutor",
|
|
19
|
+
"PromptFactory",
|
|
20
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Pydantic models for agent configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentProfile(BaseModel):
|
|
11
|
+
"""Agent persona definition.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
role: Role title (e.g., 'Code Reviewer').
|
|
15
|
+
goal: Primary objective of the agent.
|
|
16
|
+
backstory: Agent's background and expertise.
|
|
17
|
+
tone: Communication style.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
role: str = Field(..., description="Role title (e.g., 'Code Reviewer')")
|
|
21
|
+
goal: str = Field(..., description="Primary objective")
|
|
22
|
+
backstory: str = Field(..., description="Agent's background and expertise")
|
|
23
|
+
tone: str = Field(default="Professional and direct")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgentMemory(BaseModel):
|
|
27
|
+
"""RAG configuration for the agent.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
enabled: Whether to enable project RAG search.
|
|
31
|
+
rag_limit: Maximum number of context chunks to retrieve.
|
|
32
|
+
search_types: Document types to include in RAG search.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
enabled: bool = Field(default=True, description="Enable project RAG search")
|
|
36
|
+
rag_limit: int = Field(default=5, ge=1, le=20, description="Max context chunks")
|
|
37
|
+
search_types: list[Literal["code", "documentation", "lesson"]] = Field(
|
|
38
|
+
default=["code", "documentation", "lesson"],
|
|
39
|
+
description="Document types to include in RAG",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LLMConfig(BaseModel):
|
|
44
|
+
"""Model hint for MCP Sampling.
|
|
45
|
+
|
|
46
|
+
The model_hint is sent to the IDE as a preference. The IDE may use a different
|
|
47
|
+
model based on its configuration and available models.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
model_hint: Suggested model name for the IDE.
|
|
51
|
+
fallback_hints: Alternative models if primary is unavailable.
|
|
52
|
+
temperature: Sampling temperature.
|
|
53
|
+
max_tokens: Maximum tokens in response.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
model_hint: str = Field(
|
|
57
|
+
default="claude-sonnet-4.5",
|
|
58
|
+
description="Model preference sent to IDE (hint only)",
|
|
59
|
+
)
|
|
60
|
+
fallback_hints: list[str] = Field(
|
|
61
|
+
default_factory=lambda: ["auto"],
|
|
62
|
+
description="Fallback models if primary unavailable",
|
|
63
|
+
)
|
|
64
|
+
temperature: float = Field(default=0.5, ge=0.0, le=2.0)
|
|
65
|
+
max_tokens: int = Field(default=4000, ge=100, le=32000)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AgentConfig(BaseModel):
|
|
69
|
+
"""Complete agent configuration.
|
|
70
|
+
|
|
71
|
+
This model represents a complete agent definition loaded from a YAML file.
|
|
72
|
+
It includes the agent's identity, memory settings, tool access, and LLM preferences.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
name: Internal identifier (lowercase with underscores).
|
|
76
|
+
display_name: Human-readable name for the agent.
|
|
77
|
+
description: Tool description for AI discovery.
|
|
78
|
+
profile: Agent persona definition.
|
|
79
|
+
memory: RAG configuration.
|
|
80
|
+
tools: List of allowed MCP tool names (empty = all tools).
|
|
81
|
+
llm_config: Model preferences for MCP Sampling.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name: str = Field(
|
|
85
|
+
...,
|
|
86
|
+
pattern=r"^[a-z][a-z0-9_]*$",
|
|
87
|
+
description="Internal ID (lowercase, underscores allowed)",
|
|
88
|
+
)
|
|
89
|
+
display_name: str = Field(..., description="Human-readable name")
|
|
90
|
+
description: str = Field(..., description="Tool description for AI discovery")
|
|
91
|
+
profile: AgentProfile
|
|
92
|
+
memory: AgentMemory = Field(default_factory=AgentMemory)
|
|
93
|
+
tools: list[str] = Field(
|
|
94
|
+
default_factory=list,
|
|
95
|
+
description="Allowed MCP tool names (empty = all project tools)",
|
|
96
|
+
)
|
|
97
|
+
llm_config: LLMConfig = Field(default_factory=LLMConfig)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Execute agent tasks using MCP Sampling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from mcp.types import (
|
|
9
|
+
CreateMessageRequestParams,
|
|
10
|
+
ModelHint,
|
|
11
|
+
ModelPreferences,
|
|
12
|
+
SamplingMessage,
|
|
13
|
+
TextContent,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .agent_config import AgentConfig
|
|
17
|
+
from .prompt_factory import PromptFactory
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
|
|
22
|
+
from ..database import NexusDatabase
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentExecutor:
|
|
28
|
+
"""Orchestrate agent execution with RAG and MCP Sampling.
|
|
29
|
+
|
|
30
|
+
This executor:
|
|
31
|
+
1. Retrieves relevant context from the project's LanceDB (RAG)
|
|
32
|
+
2. Builds a structured system prompt using PromptFactory
|
|
33
|
+
3. Sends a sampling request to the IDE via MCP
|
|
34
|
+
4. Returns the IDE's response
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
config: AgentConfig,
|
|
40
|
+
database: NexusDatabase,
|
|
41
|
+
mcp_server: FastMCP,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Initialize the agent executor.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
config: Agent configuration from YAML.
|
|
47
|
+
database: NexusDatabase instance for RAG search.
|
|
48
|
+
mcp_server: FastMCP server instance for sampling requests.
|
|
49
|
+
"""
|
|
50
|
+
self.config = config
|
|
51
|
+
self.database = database
|
|
52
|
+
self.mcp_server = mcp_server
|
|
53
|
+
|
|
54
|
+
async def execute(self, user_task: str, project_id: str | None = None) -> str:
|
|
55
|
+
"""Execute a task with the configured agent.
|
|
56
|
+
|
|
57
|
+
The execution flow:
|
|
58
|
+
1. Retrieve relevant context from RAG based on the task
|
|
59
|
+
2. Build system prompt with agent persona and context
|
|
60
|
+
3. Send sampling request to IDE
|
|
61
|
+
4. Return the response
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
user_task: The task description from the user.
|
|
65
|
+
project_id: Optional project ID for RAG filtering.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The agent's response text.
|
|
69
|
+
"""
|
|
70
|
+
from ..database import DocumentType
|
|
71
|
+
|
|
72
|
+
# 1. RAG Retrieval
|
|
73
|
+
context_items: list[str] = []
|
|
74
|
+
if self.config.memory.enabled:
|
|
75
|
+
for search_type in self.config.memory.search_types:
|
|
76
|
+
try:
|
|
77
|
+
doc_type = DocumentType(search_type)
|
|
78
|
+
results = await self.database.search(
|
|
79
|
+
query=user_task,
|
|
80
|
+
project_id=project_id,
|
|
81
|
+
doc_type=doc_type,
|
|
82
|
+
limit=self.config.memory.rag_limit,
|
|
83
|
+
)
|
|
84
|
+
# Truncate each result to avoid context overflow
|
|
85
|
+
context_items.extend([r.text[:500] for r in results])
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.warning("RAG search failed for %s: %s", search_type, e)
|
|
88
|
+
|
|
89
|
+
logger.debug(
|
|
90
|
+
"Agent %s retrieved %d context items for task: %s",
|
|
91
|
+
self.config.name,
|
|
92
|
+
len(context_items),
|
|
93
|
+
user_task[:100],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# 2. Build Prompt
|
|
97
|
+
system_prompt = PromptFactory.build(
|
|
98
|
+
agent=self.config,
|
|
99
|
+
context_items=context_items,
|
|
100
|
+
available_tools=self.config.tools if self.config.tools else None,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# 3. MCP Sampling Request
|
|
104
|
+
try:
|
|
105
|
+
# Create the sampling request parameters
|
|
106
|
+
model_prefs = ModelPreferences(
|
|
107
|
+
hints=[ModelHint(name=self.config.llm_config.model_hint)],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
request_params = CreateMessageRequestParams(
|
|
111
|
+
messages=[
|
|
112
|
+
SamplingMessage(
|
|
113
|
+
role="user",
|
|
114
|
+
content=TextContent(type="text", text=user_task),
|
|
115
|
+
)
|
|
116
|
+
],
|
|
117
|
+
systemPrompt=system_prompt,
|
|
118
|
+
modelPreferences=model_prefs,
|
|
119
|
+
maxTokens=self.config.llm_config.max_tokens,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Access the session from the request context
|
|
123
|
+
# Note: This requires the server to be in a request context
|
|
124
|
+
ctx = self.mcp_server.get_context()
|
|
125
|
+
result = await ctx.session.create_message(
|
|
126
|
+
messages=request_params.messages,
|
|
127
|
+
system_prompt=request_params.systemPrompt,
|
|
128
|
+
model_preferences=request_params.modelPreferences,
|
|
129
|
+
max_tokens=request_params.maxTokens,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# 4. Extract and return response text
|
|
133
|
+
if hasattr(result.content, "text"):
|
|
134
|
+
return str(result.content.text)
|
|
135
|
+
return str(result.content)
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
error_msg = str(e)
|
|
139
|
+
if "does not support CreateMessage" in error_msg or "Method not found" in error_msg:
|
|
140
|
+
logger.warning(
|
|
141
|
+
"MCP Sampling not supported by client. Returning context-only response."
|
|
142
|
+
)
|
|
143
|
+
return self._create_fallback_response(user_task, context_items, system_prompt)
|
|
144
|
+
|
|
145
|
+
logger.error("MCP Sampling failed for agent %s: %s", self.config.name, e)
|
|
146
|
+
return f"Agent execution failed: {e}"
|
|
147
|
+
|
|
148
|
+
def _create_fallback_response(
|
|
149
|
+
self, user_task: str, context_items: list[str], system_prompt: str
|
|
150
|
+
) -> str:
|
|
151
|
+
"""Create a Markdown response when full agent execution is not supported.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
user_task: The original task.
|
|
155
|
+
context_items: List of retrieved context strings.
|
|
156
|
+
system_prompt: The constructed system prompt.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Formatted Markdown string.
|
|
160
|
+
"""
|
|
161
|
+
response = [
|
|
162
|
+
f"# Agent: {self.config.display_name} (Insight Mode)",
|
|
163
|
+
"",
|
|
164
|
+
"> **Note**: Your IDE does not support fully autonomous agent "
|
|
165
|
+
"execution (MCP Sampling).",
|
|
166
|
+
"> I have retrieved the relevant context and prepared the prompt for you below.",
|
|
167
|
+
"",
|
|
168
|
+
"## 1. Retrieved Context",
|
|
169
|
+
f"Found {len(context_items)} relevant items in the knowledge base:",
|
|
170
|
+
"",
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
if context_items:
|
|
174
|
+
for i, item in enumerate(context_items, 1):
|
|
175
|
+
# Try to extract a clean title or first line
|
|
176
|
+
preview = item.split("\n")[0][:80]
|
|
177
|
+
response.append(f"**Item {i}**: `{preview}...`")
|
|
178
|
+
# Indent the content for better readability in the details block
|
|
179
|
+
response.append("<details>")
|
|
180
|
+
response.append(f"<summary>View Content</summary>\n\n{item}\n")
|
|
181
|
+
response.append("</details>\n")
|
|
182
|
+
else:
|
|
183
|
+
response.append("*No relevant context found.*")
|
|
184
|
+
|
|
185
|
+
response.extend(
|
|
186
|
+
[
|
|
187
|
+
"",
|
|
188
|
+
"## 2. Recommended System Prompt",
|
|
189
|
+
"You can use this prompt with your own LLM:",
|
|
190
|
+
"",
|
|
191
|
+
"```text",
|
|
192
|
+
system_prompt,
|
|
193
|
+
"```",
|
|
194
|
+
]
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return "\n".join(response)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Load and manage agent configurations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from .agent_config import AgentConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentManager:
|
|
17
|
+
"""Scan and load agent YAML configurations.
|
|
18
|
+
|
|
19
|
+
This manager:
|
|
20
|
+
- Scans the `agents/` directory in the project root
|
|
21
|
+
- Loads and validates each YAML file as an AgentConfig
|
|
22
|
+
- Provides access to loaded agents by name
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, agents_dir: Path | None = None) -> None:
|
|
26
|
+
"""Initialize the agent manager.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
agents_dir: Path to the agents directory. Defaults to `./agents/`.
|
|
30
|
+
"""
|
|
31
|
+
self.agents_dir = agents_dir or Path.cwd() / "agents"
|
|
32
|
+
self.agents: dict[str, AgentConfig] = {}
|
|
33
|
+
self._load_agents()
|
|
34
|
+
|
|
35
|
+
def _load_agents(self) -> None:
|
|
36
|
+
"""Load all valid agent configs from the agents directory.
|
|
37
|
+
|
|
38
|
+
Invalid configs are logged as warnings but don't prevent other agents
|
|
39
|
+
from loading.
|
|
40
|
+
"""
|
|
41
|
+
if not self.agents_dir.exists():
|
|
42
|
+
logger.debug("Agents directory not found: %s", self.agents_dir)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
yaml_files = list(self.agents_dir.glob("*.yaml")) + list(self.agents_dir.glob("*.yml"))
|
|
46
|
+
|
|
47
|
+
for yaml_file in yaml_files:
|
|
48
|
+
try:
|
|
49
|
+
with open(yaml_file, encoding="utf-8") as f:
|
|
50
|
+
data = yaml.safe_load(f)
|
|
51
|
+
|
|
52
|
+
if not data:
|
|
53
|
+
logger.warning("Empty agent file: %s", yaml_file.name)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
agent = AgentConfig(**data)
|
|
57
|
+
self.agents[agent.name] = agent
|
|
58
|
+
logger.info("Loaded agent: %s from %s", agent.name, yaml_file.name)
|
|
59
|
+
|
|
60
|
+
except yaml.YAMLError as e:
|
|
61
|
+
logger.warning("Invalid YAML in %s: %s", yaml_file.name, e)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.warning("Failed to load agent %s: %s", yaml_file.name, e)
|
|
64
|
+
|
|
65
|
+
logger.info("Loaded %d agents from %s", len(self.agents), self.agents_dir)
|
|
66
|
+
|
|
67
|
+
def reload(self) -> None:
|
|
68
|
+
"""Reload agents from disk.
|
|
69
|
+
|
|
70
|
+
This clears the current agents and reloads from the directory.
|
|
71
|
+
"""
|
|
72
|
+
self.agents.clear()
|
|
73
|
+
self._load_agents()
|
|
74
|
+
|
|
75
|
+
def get_agent(self, name: str) -> AgentConfig | None:
|
|
76
|
+
"""Get agent by name.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: The agent's internal name.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
AgentConfig if found, None otherwise.
|
|
83
|
+
"""
|
|
84
|
+
return self.agents.get(name)
|
|
85
|
+
|
|
86
|
+
def list_agents(self) -> list[AgentConfig]:
|
|
87
|
+
"""List all loaded agents.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of AgentConfig instances.
|
|
91
|
+
"""
|
|
92
|
+
return list(self.agents.values())
|
|
93
|
+
|
|
94
|
+
def __len__(self) -> int:
|
|
95
|
+
"""Return number of loaded agents."""
|
|
96
|
+
return len(self.agents)
|
|
97
|
+
|
|
98
|
+
def __iter__(self) -> Iterator[AgentConfig]:
|
|
99
|
+
"""Iterate over loaded agents."""
|
|
100
|
+
return iter(self.agents.values())
|
|
101
|
+
|
|
102
|
+
def __contains__(self, name: str) -> bool:
|
|
103
|
+
"""Check if an agent is loaded."""
|
|
104
|
+
return name in self.agents
|