Installs into .claude/skills of the current project.
Are you the author of Config?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/bitwikiorg-config)
---
description: Imported skill config from langchain
name: config
signature: 4a7d23d923c15daa6fb47bff2320125d6e01e019c51ea421ca47bb88f80df0da
source: /a0/tmp/skills_research/langchain/libs/deepagents-cli/deepagents_cli/config.py
---
"""Configuration, constants, and model creation for the CLI."""
import os
import re
import sys
import uuid
from dataclasses import dataclass
from pathlib import Path
import dotenv
from rich.console import Console
from deepagents_cli._version import __version__
dotenv.load_dotenv()
# CRITICAL: Override LANGSMITH_PROJECT to route agent traces to separate project
# LangSmith reads LANGSMITH_PROJECT at invocation time, so we override it here
# and preserve the user's original value for shell commands
_deepagents_project = os.environ.get("DEEPAGENTS_LANGSMITH_PROJECT")
_original_langsmith_project = os.environ.get("LANGSMITH_PROJECT")
if _deepagents_project:
# Override LANGSMITH_PROJECT for agent traces
os.environ["LANGSMITH_PROJECT"] = _deepagents_project
# Now safe to import LangChain modules
from langchain_core.language_models import BaseChatModel
# Color scheme
COLORS = {
"primary": "#10b981",
"dim": "#6b7280",
"user": "#ffffff",
"agent": "#10b981",
"thinking": "#34d399",
"tool": "#fbbf24",
}
# ASCII art banner
DEEP_AGENTS_ASCII = f"""
██████╗ ███████╗ ███████╗ ██████╗
██╔══██╗ ██╔════╝ ██╔════╝ ██╔══██╗
██║ ██║ █████╗ █████╗ ██████╔╝
██║ ██║ ██╔══╝ ██╔══╝ ██╔═══╝
██████╔╝ ███████╗ ███████╗ ██║
╚═════╝ ╚══════╝ ╚══════╝ ╚═╝
█████╗ ██████╗ ███████╗ ███╗ ██╗ ████████╗ ███████╗
██╔══██╗ ██╔════╝ ██╔════╝ ████╗ ██║ ╚══██╔══╝ ██╔════╝
███████║ ██║ ███╗ █████╗ ██╔██╗ ██║ ██║ ███████╗
██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ╚════██║
██║ ██║ ╚██████╔╝ ███████╗ ██║ ╚████║ ██║ ███████║
╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═══╝ ╚═╝ ╚══════╝
v{__version__}
"""
# Interactive commands
COMMANDS = {
"clear": "Clear screen and reset conversation",
"help": "Show help information",
"tokens": "Show token usage for current session",
"quit": "Exit the CLI",
"exit": "Exit the CLI",
}
# Maximum argument length for display
MAX_ARG_LENGTH = 150
# Agent configuration
config = {"recursion_limit": 1000}
# Rich console instance
console = Console(highlight=False)
def _find_project_root(start_path: Path | None = None) -> Path | None:
"""Find the project root by looking for .git directory.
Walks up the directory tree from start_path (or cwd) looking for a .git
directory, which indicates the project root.
Args:
start_path: Directory to start searching from. Defaults to current working directory.
Returns:
Path to the project root if found, None otherwise.
"""
current = Path(start_path or Path.cwd()).resolve()
# Walk up the directory tree
for parent in [current, *list(current.parents)]:
git_dir = parent / ".git"
if git_dir.exists():
return parent
return None
def _find_project_agent_md(project_root: Path) -> list[Path]:
"""Find project-specific AGENTS.md file(s).
Checks two locations and returns ALL that exist:
1. project_root/.deepagents/AGENTS.md
2. project_root/AGENTS.md
Both files will be loaded and combined if both exist.
Args:
project_root: Path to the project root directory.
Returns:
List of paths to project AGENTS.md files (may contain 0, 1, or 2 paths).
"""
paths = []
# Check .deepagents/AGENTS.md (preferred)
deepagents_md = project_root / ".deepagents" / "AGENTS.md"
if deepagents_md.exists():
paths.append(deepagents_md)
# Check root AGENTS.md (fallback, but also include if both exist)
root_md = project_root / "AGENTS.md"
if root_md.exists():
paths.append(root_md)
return paths
@dataclass
class Settings:
"""Global settings and environment detection for deepagents-cli.
This class is initialized once at startup and provides access to:
- Available models and API keys
- Current project information
- Tool availability (e.g., Tavily)
- File system paths
Attributes:
project_root: Current project root directory (if in a git project)
openai_api_key: OpenAI API key if available
anthropic_api_key: Anthropic API key if available
tavily_api_key: Tavily API key if available
deepagents_langchain_project: LangSmith project name for deepagents agent tracing
user_langchain_project: Original LANGSMITH_PROJECT from environment (for user code)
"""
# API keys
openai_api_key: str | None
anthropic_api_key: str | None
google_api_key: str | None
tavily_api_key: str | None
# LangSmith configuration
deepagents_langchain_project: str | None # For deepagents agent tracing
user_langchain_project: str | None # Original LANGSMITH_PROJECT for user code
# Model configuration
model_name: str | None = None # Currently active model name
model_provider: str | None = None # Provider (openai, anthropic, google)
# Project information
project_root: Path | None = None
@classmethod
def from_environment(cls, *, start_path: Path | None = None) -> "Settings":
"""Create settings by detecting the current environment.
Args:
start_path: Directory to start project detection from (defaults to cwd)
Returns:
Settings instance with detected configuration
"""
# Detect API keys
openai_key = os.environ.get("OPENAI_API_KEY")
anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
google_key = os.environ.get("GOOGLE_API_KEY")
tavily_key = os.environ.get("TAVILY_API_KEY")
# Detect LangSmith configuration
# DEEPAGENTS_LANGSMITH_PROJECT: Project for deepagents agent tracing
# user_langchain_project: User's ORIGINAL LANGSMITH_PROJECT (before override)
# Note: LANGSMITH_PROJECT was already overridden at module import time (above)
# so we use the saved original value, not the current os.environ value
deepagents_langchain_project = os.environ.get("DEEPAGENTS_LANGSMITH_PROJECT")
user_langchain_project = _original_langsmith_project # Use saved original!
# Detect project
project_root = _find_project_root(start_path)
return cls(
openai_api_key=openai_key,
anthropic_api_key=anthropic_key,
google_api_key=google_key,
tavily_api_key=tavily_key,
deepagents_langchain_project=deepagents_langchain_project,
user_langchain_project=user_langchain_project,
project_root=project_root,
)
@property
def has_openai(self) -> bool:
"""Check if OpenAI API key is configured."""
return self.openai_api_key is not None
@property
def has_anthropic(self) -> bool:
"""Check if Anthropic API key is configured."""
return self.anthropic_api_key is not None
@property
def has_google(self) -> bool:
"""Check if Google API key is configured."""
return self.google_api_key is not None
@property
def has_tavily(self) -> bool:
"""Check if Tavily API key is configured."""
return self.tavily_api_key is not None
@property
def has_deepagents_langchain_project(self) -> bool:
"""Check if deepagents LangChain project name is configured."""
return self.deepagents_langchain_project is not None
@property
def has_project(self) -> bool:
"""Check if currently in a git project."""
return self.project_root is not None
@property
def user_deepagents_dir(self) -> Path:
"""Get the base user-level .deepagents directory.
Returns:
Path to ~/.deepagents
"""
return Path.home() / ".deepagents"
def get_user_agent_md_path(self, agent_name: str) -> Path:
"""Get user-level AGENTS.md path for a specific agent.
Returns path regardless of whether the file exists.
Args:
agent_name: Name of the agent
Returns:
Path to ~/.deepagents/{agent_name}/AGENTS.md
"""
return Path.home() / ".deepagents" / agent_name / "AGENTS.md"
def get_project_agent_md_path(self) -> Path | None:
"""Get project-level AGENTS.md path.
Returns path regardless of whether the file exists.
Returns:
Path to {project_root}/.deepagents/AGENTS.md, or None if not in a project
"""
if not self.project_root:
return None
return self.project_root / ".deepagents" / "AGENTS.md"
@staticmethod
def _is_valid_agent_name(agent_name: str) -> bool:
"""Validate prevent invalid filesystem paths and security issues."""
if not agent_name or not agent_name.strip():
return False
# Allow only alphanumeric, hyphens, underscores, and whitespace
return bool(re.match(r"^[a-zA-Z0-9_\-\s]+$", agent_name))
def get_agent_dir(self, agent_name: str) -> Path:
"""Get the global agent directory path.
Args:
agent_name: Name of the agent
Returns:
Path to ~/.deepagents/{agent_name}
"""
if not self._is_valid_agent_name(agent_name):
msg = (
f"Invalid agent name: {agent_name!r}. "
"Agent names can only contain letters, numbers, hyphens, underscores, and spaces."
)
raise ValueError(msg)
return Path.home() / ".deepagents" / agent_name
def ensure_agent_dir(self, agent_name: str) -> Path:
"""Ensure the global agent directory exists and return its path.
Args:
agent_name: Name of the agent
Returns:
Path to ~/.deepagents/{agent_name}
"""
if not self._is_valid_agent_name(agent_name):
msg = (
f"Invalid agent name: {agent_name!r}. "
"Agent names can only contain letters, numbers, hyphens, underscores, and spaces."
)
raise ValueError(msg)
agent_dir = self.get_agent_dir(agent_name)
agent_dir.mkdir(parents=True, exist_ok=True)
return agent_dir
def ensure_project_deepagents_dir(self) -> Path | None:
"""Ensure the project .deepagents directory exists and return its path.
Returns:
Path to project .deepagents directory, or None if not in a project
"""
if not self.project_root:
return None
project_deepagents_dir = self.project_root / ".deepagents"
project_deepagents_dir.mkdir(parents=True, exist_ok=True)
return project_deepagents_dir
def get_user_skills_dir(self, agent_name: str) -> Path:
"""Get user-level skills directory path for a specific agent.
Args:
agent_name: Name of the agent
Returns:
Path to ~/.deepagents/{agent_name}/skills/
"""
return self.get_agent_dir(agent_name) / "skills"
def ensure_user_skills_dir(self, agent_name: str) -> Path:
"""Ensure user-level skills directory exists and return its path.
Args:
agent_name: Name of the agent
Returns:
Path to ~/.deepagents/{agent_name}/skills/
"""
skills_dir = self.get_user_skills_dir(agent_name)
skills_dir.mkdir(parents=True, exist_ok=True)
return skills_dir
def get_project_skills_dir(self) -> Path | None:
"""Get project-level skills directory path.
Returns:
Path to {project_root}/.deepagents/skills/, or None if not in a project
"""
if not self.project_root:
return None
return self.project_root / ".deepagents" / "skills"
def ensure_project_skills_dir(self) -> Path | None:
"""Ensure project-level skills directory exists and return its path.
Returns:
Path to {project_root}/.deepagents/skills/, or None if not in a project
"""
if not self.project_root:
return None
skills_dir = self.get_project_skills_dir()
skills_dir.mkdir(parents=True, exist_ok=True)
return skills_dir
# Global settings instance (initialized once)
settings = Settings.from_environment()
class SessionState:
"""Holds mutable session state (auto-approve mode, etc)."""
def __init__(self, auto_approve: bool = False, no_splash: bool = False) -> None:
self.auto_approve = auto_approve
self.no_splash = no_splash
self.exit_hint_until: float | None = None
self.exit_hint_handle = None
self.thread_id = str(uuid.uuid4())
def toggle_auto_approve(self) -> bool:
"""Toggle auto-approve and return new state."""
self.auto_approve = not self.auto_approve
return self.auto_approve
def get_default_coding_instructions() -> str:
"""Get the default coding agent instructions.
These are the immutable base instructions that cannot be modified by the agent.
Long-term memory (AGENTS.md) is handled separately by the middleware.
"""
default_prompt_path = Path(__file__).parent / "default_agent_prompt.md"
return default_prompt_path.read_text()
def _detect_provider(model_name: str) -> str | None:
"""Auto-detect provider from model name.
Args:
model_name: Model name to detect provider from
Returns:
Provider name (openai, anthropic, google) or None if can't detect
"""
model_lower = model_name.lower()
if any(x in model_lower for x in ["gpt", "o1", "o3"]):
return "openai"
if "claude" in model_lower:
return "anthropic"
if "gemini" in model_lower:
return "google"
return None
def create_model(model_name_override: str | None = None) -> BaseChatModel:
"""Create the appropriate model based on available API keys.
Uses the global settings instance to determine which model to create.
Args:
model_name_override: Optional model name to use instead of environment variable
Returns:
ChatModel instance (OpenAI, Anthropic, or Google)
Raises:
SystemExit if no API key is configured or model provider can't be determined
"""
# Determine provider and model
if model_name_override:
# Use provided model, auto-detect provider
provider = _detect_provider(model_name_override)
if not provider:
console.print(
f"[bold red]Error:[/bold red] Could not detect provider from model name: {model_name_override}"
)
console.print("\nSupported model name patterns:")
console.print(" - OpenAI: gpt-*, o1-*, o3-*")
console.print(" - Anthropic: claude-*")
console.print(" - Google: gemini-*")
sys.exit(1)
# Check if API key for detected provider is available
if provider == "openai" and not settings.has_openai:
console.print(
f"[bold red]Error:[/bold red] Model '{model_name_override}' requires OPENAI_API_KEY"
)
sys.exit(1)
elif provider == "anthropic" and not settings.has_anthropic:
console.print(
f"[bold red]Error:[/bold red] Model '{model_name_override}' requires ANTHROPIC_API_KEY"
)
sys.exit(1)
elif provider == "google" and not settings.has_google:
console.print(
f"[bold red]Error:[/bold red] Model '{model_name_override}' requires GOOGLE_API_KEY"
)
sys.exit(1)
model_name = model_name_override
# Use environment variable defaults, detect provider by API key priority
elif settings.has_openai:
provider = "openai"
model_name = os.environ.get("OPENAI_MODEL", "gpt-5-mini")
elif settings.has_anthropic:
provider = "anthropic"
model_name = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-5-20250929")
elif settings.has_google:
provider = "google"
model_name = os.environ.get("GOOGLE_MODEL", "gemini-3-pro-preview")
else:
console.print("[bold red]Error:[/bold red] No API key configured.")
console.print("\nPlease set one of the following environment variables:")
console.print(" - OPENAI_API_KEY (for OpenAI models like gpt-5-mini)")
console.print(" - ANTHROPIC_API_KEY (for Claude models)")
console.print(" - GOOGLE_API_KEY (for Google Gemini models)")
console.print("\nExample:")
console.print(" export OPENAI_API_KEY=your_api_key_here")
console.print("\nOr add it to your .env file.")
sys.exit(1)
# Store model info in settings for display
settings.model_name = model_name
settings.model_provider = provider
# Create and return the model
if provider == "openai":
from langchain_openai import ChatOpenAI
return ChatOpenAI(model=model_name)
if provider == "anthropic":
from langchain_anthropic import ChatAnthropic
return ChatAnthropic(
model_name=model_name,
max_tokens=20_000, # type: ignore[arg-type]
)
if provider == "google":
from langchain_google_genai import ChatGoogleGenerativeAI
return ChatGoogleGenerativeAI(
model=model_name,
temperature=0,
max_tokens=None,
)