consolidate workspace to uv monorepo
This commit is contained in:
+2
-12
@@ -218,19 +218,9 @@ class OnlineResearchAgent:
|
|||||||
tool_registry = ToolRegistry()
|
tool_registry = ToolRegistry()
|
||||||
|
|
||||||
# Load MCP servers (always load, needed for tool validation)
|
# Load MCP servers (always load, needed for tool validation)
|
||||||
agent_dir = Path(__file__).parent
|
mcp_config_path = Path(__file__).parent / "mcp_servers.json"
|
||||||
mcp_config_path = agent_dir / "mcp_servers.json"
|
|
||||||
|
|
||||||
if mcp_config_path.exists():
|
if mcp_config_path.exists():
|
||||||
with open(mcp_config_path) as f:
|
tool_registry.load_mcp_config(mcp_config_path)
|
||||||
mcp_servers = json.load(f)
|
|
||||||
|
|
||||||
for server_config in mcp_servers.get("servers", []):
|
|
||||||
# Resolve relative cwd paths
|
|
||||||
cwd = server_config.get("cwd")
|
|
||||||
if cwd and not Path(cwd).is_absolute():
|
|
||||||
server_config["cwd"] = str(agent_dir / cwd)
|
|
||||||
tool_registry.register_mcp_server(server_config)
|
|
||||||
|
|
||||||
llm = None
|
llm = None
|
||||||
if not mock_mode:
|
if not mock_mode:
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd tools
|
cd tools
|
||||||
uv sync --extra dev
|
uv sync --extra dev
|
||||||
uv pip install --python .venv/bin/python -e ../core
|
uv run pytest tests/ -v
|
||||||
uv run --extra dev pytest tests/ -v
|
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
name: Validate Agent Exports
|
name: Validate Agent Exports
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"agent-builder": {
|
"agent-builder": {
|
||||||
"command": ".venv/bin/python",
|
"command": "uv",
|
||||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
"args": ["run", "-m", "framework.mcp.agent_builder_server"],
|
||||||
"cwd": "core",
|
"cwd": "core"
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "../tools/src"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"command": ".venv/bin/python",
|
"command": "uv",
|
||||||
"args": ["mcp_server.py", "--stdio"],
|
"args": ["run", "mcp_server.py", "--stdio"],
|
||||||
"cwd": "tools",
|
"cwd": "tools"
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "src:../core"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def _get_api_key_from_credential_store() -> str | None:
|
|||||||
try:
|
try:
|
||||||
from aden_tools.credentials import CredentialStoreAdapter
|
from aden_tools.credentials import CredentialStoreAdapter
|
||||||
|
|
||||||
creds = CredentialStoreAdapter.with_env_storage()
|
creds = CredentialStoreAdapter.default()
|
||||||
if creds.is_available("anthropic"):
|
if creds.is_available("anthropic"):
|
||||||
return creds.get("anthropic")
|
return creds.get("anthropic")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -411,25 +411,8 @@ class AgentRunner:
|
|||||||
return self._tool_registry.register_mcp_server(server_config)
|
return self._tool_registry.register_mcp_server(server_config)
|
||||||
|
|
||||||
def _load_mcp_servers_from_config(self, config_path: Path) -> None:
|
def _load_mcp_servers_from_config(self, config_path: Path) -> None:
|
||||||
"""
|
"""Load and register MCP servers from a configuration file."""
|
||||||
Load and register MCP servers from a configuration file.
|
self._tool_registry.load_mcp_config(config_path)
|
||||||
|
|
||||||
Args:
|
|
||||||
config_path: Path to mcp_servers.json file
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(config_path) as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
servers = config.get("servers", [])
|
|
||||||
for server_config in servers:
|
|
||||||
try:
|
|
||||||
self._tool_registry.register_mcp_server(server_config)
|
|
||||||
except Exception as e:
|
|
||||||
server_name = server_config.get("name", "unknown")
|
|
||||||
logger.warning(f"Failed to register MCP server '{server_name}': {e}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to load MCP servers config from {config_path}: {e}")
|
|
||||||
|
|
||||||
def set_approval_callback(self, callback: Callable) -> None:
|
def set_approval_callback(self, callback: Callable) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -257,6 +257,34 @@ class ToolRegistry:
|
|||||||
"""
|
"""
|
||||||
self._session_context.update(context)
|
self._session_context.update(context)
|
||||||
|
|
||||||
|
def load_mcp_config(self, config_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
Load and register MCP servers from a config file.
|
||||||
|
|
||||||
|
Resolves relative ``cwd`` paths against the config file's parent
|
||||||
|
directory so callers never need to handle path resolution themselves.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_path: Path to an ``mcp_servers.json`` file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(config_path) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to load MCP config from {config_path}: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
base_dir = config_path.parent
|
||||||
|
for server_config in config.get("servers", []):
|
||||||
|
cwd = server_config.get("cwd")
|
||||||
|
if cwd and not Path(cwd).is_absolute():
|
||||||
|
server_config["cwd"] = str((base_dir / cwd).resolve())
|
||||||
|
try:
|
||||||
|
self.register_mcp_server(server_config)
|
||||||
|
except Exception as e:
|
||||||
|
name = server_config.get("name", "unknown")
|
||||||
|
logger.warning(f"Failed to register MCP server '{name}': {e}")
|
||||||
|
|
||||||
def register_mcp_server(
|
def register_mcp_server(
|
||||||
self,
|
self,
|
||||||
server_config: dict[str, Any],
|
server_config: dict[str, Any],
|
||||||
@@ -309,11 +337,21 @@ class ToolRegistry:
|
|||||||
tool = self._convert_mcp_tool_to_framework_tool(mcp_tool)
|
tool = self._convert_mcp_tool_to_framework_tool(mcp_tool)
|
||||||
|
|
||||||
# Create executor that calls the MCP server
|
# Create executor that calls the MCP server
|
||||||
def make_mcp_executor(client_ref: MCPClient, tool_name: str, registry_ref):
|
def make_mcp_executor(
|
||||||
|
client_ref: MCPClient,
|
||||||
|
tool_name: str,
|
||||||
|
registry_ref,
|
||||||
|
tool_params: set[str],
|
||||||
|
):
|
||||||
def executor(inputs: dict) -> Any:
|
def executor(inputs: dict) -> Any:
|
||||||
try:
|
try:
|
||||||
# Inject session context for tools that need it
|
# Only inject session context params the tool accepts
|
||||||
merged_inputs = {**registry_ref._session_context, **inputs}
|
filtered_context = {
|
||||||
|
k: v
|
||||||
|
for k, v in registry_ref._session_context.items()
|
||||||
|
if k in tool_params
|
||||||
|
}
|
||||||
|
merged_inputs = {**filtered_context, **inputs}
|
||||||
result = client_ref.call_tool(tool_name, merged_inputs)
|
result = client_ref.call_tool(tool_name, merged_inputs)
|
||||||
# MCP tools return content array, extract the result
|
# MCP tools return content array, extract the result
|
||||||
if isinstance(result, list) and len(result) > 0:
|
if isinstance(result, list) and len(result) > 0:
|
||||||
@@ -327,10 +365,11 @@ class ToolRegistry:
|
|||||||
|
|
||||||
return executor
|
return executor
|
||||||
|
|
||||||
|
tool_params = set(mcp_tool.input_schema.get("properties", {}).keys())
|
||||||
self.register(
|
self.register(
|
||||||
mcp_tool.name,
|
mcp_tool.name,
|
||||||
tool,
|
tool,
|
||||||
make_mcp_executor(client, mcp_tool.name, self),
|
make_mcp_executor(client, mcp_tool.name, self, tool_params),
|
||||||
)
|
)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def _get_api_key():
|
|||||||
# 1. Try CredentialStoreAdapter for Anthropic
|
# 1. Try CredentialStoreAdapter for Anthropic
|
||||||
try:
|
try:
|
||||||
from aden_tools.credentials import CredentialStoreAdapter
|
from aden_tools.credentials import CredentialStoreAdapter
|
||||||
creds = CredentialStoreAdapter.with_env_storage()
|
creds = CredentialStoreAdapter.default()
|
||||||
if creds.is_available("anthropic"):
|
if creds.is_available("anthropic"):
|
||||||
return creds.get("anthropic")
|
return creds.get("anthropic")
|
||||||
except (ImportError, KeyError):
|
except (ImportError, KeyError):
|
||||||
@@ -57,7 +57,7 @@ def _get_api_key():
|
|||||||
"""Get API key from CredentialStoreAdapter or environment."""
|
"""Get API key from CredentialStoreAdapter or environment."""
|
||||||
try:
|
try:
|
||||||
from aden_tools.credentials import CredentialStoreAdapter
|
from aden_tools.credentials import CredentialStoreAdapter
|
||||||
creds = CredentialStoreAdapter.with_env_storage()
|
creds = CredentialStoreAdapter.default()
|
||||||
if creds.is_available("anthropic"):
|
if creds.is_available("anthropic"):
|
||||||
return creds.get("anthropic")
|
return creds.get("anthropic")
|
||||||
except (ImportError, KeyError):
|
except (ImportError, KeyError):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ dependencies = [
|
|||||||
"pytest>=8.0",
|
"pytest>=8.0",
|
||||||
"pytest-asyncio>=0.23",
|
"pytest-asyncio>=0.23",
|
||||||
"pytest-xdist>=3.0",
|
"pytest-xdist>=3.0",
|
||||||
|
"tools",
|
||||||
]
|
]
|
||||||
|
|
||||||
# [project.optional-dependencies]
|
# [project.optional-dependencies]
|
||||||
@@ -21,6 +22,9 @@ dependencies = [
|
|||||||
[project.scripts]
|
[project.scripts]
|
||||||
hive = "framework.cli:main"
|
hive = "framework.cli:main"
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
tools = { workspace = true }
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["core", "tools"]
|
||||||
+18
-74
@@ -183,41 +183,26 @@ echo ""
|
|||||||
echo -e "${DIM}This may take a minute...${NC}"
|
echo -e "${DIM}This may take a minute...${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Install framework package from core/
|
# Install all workspace packages (core + tools) from workspace root
|
||||||
echo -n " Installing framework... "
|
echo -n " Installing workspace packages... "
|
||||||
cd "$SCRIPT_DIR/core"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
if [ -f "pyproject.toml" ]; then
|
if [ -f "pyproject.toml" ]; then
|
||||||
if uv sync > /dev/null 2>&1; then
|
if uv sync > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN} ✓ framework package installed${NC}"
|
echo -e "${GREEN} ✓ workspace packages installed${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW} ⚠ framework installation had issues (may be OK)${NC}"
|
echo -e "${RED} ✗ workspace installation failed${NC}"
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}failed (no pyproject.toml)${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install aden_tools package from tools/
|
|
||||||
echo -n " Installing tools... "
|
|
||||||
cd "$SCRIPT_DIR/tools"
|
|
||||||
|
|
||||||
if [ -f "pyproject.toml" ]; then
|
|
||||||
if uv sync > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN} ✓ aden_tools package installed${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED} ✗ aden_tools installation failed${NC}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${RED}failed${NC}"
|
echo -e "${RED}failed (no root pyproject.toml)${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install Playwright browser
|
# Install Playwright browser
|
||||||
echo -n " Installing Playwright browser... "
|
echo -n " Installing Playwright browser... "
|
||||||
if $PYTHON_CMD -c "import playwright" > /dev/null 2>&1; then
|
if uv run python -c "import playwright" > /dev/null 2>&1; then
|
||||||
if $PYTHON_CMD -m playwright install chromium > /dev/null 2>&1; then
|
if uv run python -m playwright install chromium > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}ok${NC}"
|
echo -e "${GREEN}ok${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}⏭${NC}"
|
echo -e "${YELLOW}⏭${NC}"
|
||||||
@@ -236,33 +221,6 @@ echo ""
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
echo -e "${YELLOW}⬢${NC} ${BLUE}${BOLD}Step 3: Configuring LLM provider...${NC}"
|
echo -e "${YELLOW}⬢${NC} ${BLUE}${BOLD}Step 3: Configuring LLM provider...${NC}"
|
||||||
# Install MCP dependencies (in tools venv)
|
|
||||||
echo " Installing MCP dependencies..."
|
|
||||||
TOOLS_PYTHON="$SCRIPT_DIR/tools/.venv/bin/python"
|
|
||||||
uv pip install --python "$TOOLS_PYTHON" mcp fastmcp > /dev/null 2>&1
|
|
||||||
echo -e "${GREEN} ✓ MCP dependencies installed${NC}"
|
|
||||||
|
|
||||||
# Fix openai version compatibility (in tools venv)
|
|
||||||
TOOLS_PYTHON="$SCRIPT_DIR/tools/.venv/bin/python"
|
|
||||||
OPENAI_VERSION=$($TOOLS_PYTHON -c "import openai; print(openai.__version__)" 2>/dev/null || echo "not_installed")
|
|
||||||
if [ "$OPENAI_VERSION" = "not_installed" ]; then
|
|
||||||
echo " Installing openai package..."
|
|
||||||
uv pip install --python "$TOOLS_PYTHON" "openai>=1.0.0" > /dev/null 2>&1
|
|
||||||
echo -e "${GREEN} ✓ openai installed${NC}"
|
|
||||||
elif [[ "$OPENAI_VERSION" =~ ^0\. ]]; then
|
|
||||||
echo " Upgrading openai to 1.x+ for litellm compatibility..."
|
|
||||||
uv pip install --python "$TOOLS_PYTHON" --upgrade "openai>=1.0.0" > /dev/null 2>&1
|
|
||||||
echo -e "${GREEN} ✓ openai upgraded${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${GREEN} ✓ openai $OPENAI_VERSION is compatible${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install click for CLI (in tools venv)
|
|
||||||
TOOLS_PYTHON="$SCRIPT_DIR/tools/.venv/bin/python"
|
|
||||||
uv pip install --python "$TOOLS_PYTHON" click > /dev/null 2>&1
|
|
||||||
echo -e "${GREEN} ✓ click installed${NC}"
|
|
||||||
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -274,42 +232,28 @@ echo ""
|
|||||||
|
|
||||||
IMPORT_ERRORS=0
|
IMPORT_ERRORS=0
|
||||||
|
|
||||||
# Test imports using their respective venvs
|
# Test imports using workspace venv via uv run
|
||||||
CORE_PYTHON="$SCRIPT_DIR/core/.venv/bin/python"
|
if uv run python -c "import framework" > /dev/null 2>&1; then
|
||||||
TOOLS_PYTHON="$SCRIPT_DIR/tools/.venv/bin/python"
|
|
||||||
|
|
||||||
# Test framework import (from core venv)
|
|
||||||
if [ -f "$CORE_PYTHON" ] && $CORE_PYTHON -c "import framework" > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN} ✓ framework imports OK${NC}"
|
echo -e "${GREEN} ✓ framework imports OK${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED} ✗ framework import failed${NC}"
|
echo -e "${RED} ✗ framework import failed${NC}"
|
||||||
IMPORT_ERRORS=$((IMPORT_ERRORS + 1))
|
IMPORT_ERRORS=$((IMPORT_ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test aden_tools import (from tools venv)
|
if uv run python -c "import aden_tools" > /dev/null 2>&1; then
|
||||||
if [ -f "$TOOLS_PYTHON" ] && $TOOLS_PYTHON -c "import aden_tools" > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN} ✓ aden_tools imports OK${NC}"
|
echo -e "${GREEN} ✓ aden_tools imports OK${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED} ✗ aden_tools import failed${NC}"
|
echo -e "${RED} ✗ aden_tools import failed${NC}"
|
||||||
IMPORT_ERRORS=$((IMPORT_ERRORS + 1))
|
IMPORT_ERRORS=$((IMPORT_ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test litellm import (from core venv)
|
if uv run python -c "import litellm" > /dev/null 2>&1; then
|
||||||
if [ -f "$CORE_PYTHON" ] && $CORE_PYTHON -c "import litellm" > /dev/null 2>&1; then
|
echo -e "${GREEN} ✓ litellm imports OK${NC}"
|
||||||
echo -e "${GREEN} ✓ litellm imports OK (core)${NC}"
|
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW} ⚠ litellm import issues in core (may be OK)${NC}"
|
echo -e "${YELLOW} ⚠ litellm import issues (may be OK)${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test litellm import (from tools venv)
|
if uv run python -c "from framework.mcp import agent_builder_server" > /dev/null 2>&1; then
|
||||||
if [ -f "$TOOLS_PYTHON" ] && $TOOLS_PYTHON -c "import litellm" > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN} ✓ litellm imports OK (tools)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW} ⚠ litellm import issues in tools (may be OK)${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test MCP server module (from core venv)
|
|
||||||
if [ -f "$CORE_PYTHON" ] && $CORE_PYTHON -c "from framework.mcp import agent_builder_server" > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN} ✓ MCP server module OK${NC}"
|
echo -e "${GREEN} ✓ MCP server module OK${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED} ✗ MCP server module failed${NC}"
|
echo -e "${RED} ✗ MCP server module failed${NC}"
|
||||||
@@ -647,7 +591,7 @@ ERRORS=0
|
|||||||
|
|
||||||
# Test imports
|
# Test imports
|
||||||
echo -n " ⬡ framework... "
|
echo -n " ⬡ framework... "
|
||||||
if $CORE_PYTHON -c "import framework" > /dev/null 2>&1; then
|
if uv run python -c "import framework" > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}ok${NC}"
|
echo -e "${GREEN}ok${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}failed${NC}"
|
echo -e "${RED}failed${NC}"
|
||||||
@@ -655,7 +599,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n " ⬡ aden_tools... "
|
echo -n " ⬡ aden_tools... "
|
||||||
if $TOOLS_PYTHON -c "import aden_tools" > /dev/null 2>&1; then
|
if uv run python -c "import aden_tools" > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}ok${NC}"
|
echo -e "${GREEN}ok${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}failed${NC}"
|
echo -e "${RED}failed${NC}"
|
||||||
@@ -663,7 +607,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n " ⬡ litellm... "
|
echo -n " ⬡ litellm... "
|
||||||
if $CORE_PYTHON -c "import litellm" > /dev/null 2>&1 || $TOOLS_PYTHON -c "import litellm" > /dev/null 2>&1; then
|
if uv run python -c "import litellm" > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}ok${NC}"
|
echo -e "${GREEN}ok${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}--${NC}"
|
echo -e "${YELLOW}--${NC}"
|
||||||
|
|||||||
+1
-12
@@ -68,18 +68,7 @@ from starlette.responses import PlainTextResponse # noqa: E402
|
|||||||
from aden_tools.credentials import CredentialError, CredentialStoreAdapter # noqa: E402
|
from aden_tools.credentials import CredentialError, CredentialStoreAdapter # noqa: E402
|
||||||
from aden_tools.tools import register_all_tools # noqa: E402
|
from aden_tools.tools import register_all_tools # noqa: E402
|
||||||
|
|
||||||
# Create credential store with access to both env vars AND encrypted store
|
credentials = CredentialStoreAdapter.default()
|
||||||
# This allows using Aden-synced credentials from ~/.hive/credentials
|
|
||||||
try:
|
|
||||||
from framework.credentials import CredentialStore
|
|
||||||
|
|
||||||
store = CredentialStore.with_encrypted_storage() # ~/.hive/credentials
|
|
||||||
credentials = CredentialStoreAdapter(store)
|
|
||||||
logger.info("Using CredentialStoreAdapter with encrypted storage")
|
|
||||||
except Exception as e:
|
|
||||||
# Fall back to env-only adapter if encrypted storage fails
|
|
||||||
credentials = CredentialStoreAdapter.with_env_storage()
|
|
||||||
logger.warning(f"Falling back to env-only CredentialStoreAdapter: {e}")
|
|
||||||
|
|
||||||
# Tier 1: Validate startup-required credentials (if any)
|
# Tier 1: Validate startup-required credentials (if any)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dependencies = [
|
|||||||
"playwright-stealth>=1.0.5",
|
"playwright-stealth>=1.0.5",
|
||||||
"litellm>=1.81.0",
|
"litellm>=1.81.0",
|
||||||
"resend>=2.0.0",
|
"resend>=2.0.0",
|
||||||
|
"framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
@@ -54,6 +55,9 @@ all = [
|
|||||||
"duckdb>=1.0.0",
|
"duckdb>=1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
framework = { workspace = true }
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Usage:
|
|||||||
from aden_tools.credentials import CredentialStoreAdapter
|
from aden_tools.credentials import CredentialStoreAdapter
|
||||||
|
|
||||||
mcp = FastMCP("my-server")
|
mcp = FastMCP("my-server")
|
||||||
credentials = CredentialStoreAdapter.with_env_storage()
|
credentials = CredentialStoreAdapter.default()
|
||||||
register_all_tools(mcp, credentials=credentials)
|
register_all_tools(mcp, credentials=credentials)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ Usage:
|
|||||||
store = CredentialStore.with_encrypted_storage() # defaults to ~/.hive/credentials
|
store = CredentialStore.with_encrypted_storage() # defaults to ~/.hive/credentials
|
||||||
credentials = CredentialStoreAdapter(store)
|
credentials = CredentialStoreAdapter(store)
|
||||||
|
|
||||||
# With env vars only (simple setup)
|
# With composite storage (encrypted primary + env fallback)
|
||||||
credentials = CredentialStoreAdapter.with_env_storage()
|
credentials = CredentialStoreAdapter.default()
|
||||||
|
|
||||||
# In agent runner (validate at agent load time)
|
# In agent runner (validate at agent load time)
|
||||||
credentials.validate_for_tools(["web_search", "file_read"])
|
credentials.validate_for_tools(["web_search", "file_read"])
|
||||||
|
|||||||
@@ -348,6 +348,41 @@ class CredentialStoreAdapter:
|
|||||||
|
|
||||||
# --- Factory Methods ---
|
# --- Factory Methods ---
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(
|
||||||
|
cls,
|
||||||
|
specs: dict[str, CredentialSpec] | None = None,
|
||||||
|
) -> CredentialStoreAdapter:
|
||||||
|
"""Create adapter with encrypted storage primary and env var fallback."""
|
||||||
|
from framework.credentials import CredentialStore
|
||||||
|
from framework.credentials.storage import (
|
||||||
|
CompositeStorage,
|
||||||
|
EncryptedFileStorage,
|
||||||
|
EnvVarStorage,
|
||||||
|
)
|
||||||
|
|
||||||
|
if specs is None:
|
||||||
|
from . import CREDENTIAL_SPECS
|
||||||
|
|
||||||
|
specs = CREDENTIAL_SPECS
|
||||||
|
|
||||||
|
env_mapping = {name: spec.env_var for name, spec in specs.items()}
|
||||||
|
|
||||||
|
try:
|
||||||
|
encrypted = EncryptedFileStorage()
|
||||||
|
env = EnvVarStorage(env_mapping)
|
||||||
|
composite = CompositeStorage(primary=encrypted, fallbacks=[env])
|
||||||
|
store = CredentialStore(storage=composite)
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger(__name__).warning(
|
||||||
|
"Encrypted credential storage unavailable, falling back to env vars: %s", e
|
||||||
|
)
|
||||||
|
store = CredentialStore.with_env_storage(env_mapping)
|
||||||
|
|
||||||
|
return cls(store=store, specs=specs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_testing(
|
def for_testing(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage:
|
|||||||
from aden_tools.credentials import CredentialStoreAdapter
|
from aden_tools.credentials import CredentialStoreAdapter
|
||||||
|
|
||||||
mcp = FastMCP("my-server")
|
mcp = FastMCP("my-server")
|
||||||
credentials = CredentialStoreAdapter.with_env_storage()
|
credentials = CredentialStoreAdapter.default()
|
||||||
register_all_tools(mcp, credentials=credentials)
|
register_all_tools(mcp, credentials=credentials)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ from aden_tools.credentials import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _no_dotenv(tmp_path, monkeypatch):
|
||||||
|
"""Isolate tests from the project .env file.
|
||||||
|
|
||||||
|
EnvVarStorage falls back to reading Path.cwd()/.env when a key is
|
||||||
|
missing from os.environ. Changing cwd to a temp dir ensures
|
||||||
|
monkeypatch.delenv() truly simulates a missing credential.
|
||||||
|
"""
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
class TestCredentialStoreAdapter:
|
class TestCredentialStoreAdapter:
|
||||||
"""Tests for CredentialStoreAdapter class."""
|
"""Tests for CredentialStoreAdapter class."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user