blob: ca3999463afeaba4fad8c4724418f41a9f82eb7b [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility library for checking if a tool is being run by an automated agent."""
import os
import pathlib
import typing
from importlib.resources import files
try:
from agents import data # type: ignore[attr-defined]
except ImportError:
data = None
# Relative path from this file's directory to tools/devshell/lib/agents.txt in the source tree.
_SOURCE_AGENTS_TXT_RELPATH = (
pathlib.Path("..")
/ ".."
/ ".."
/ "tools"
/ "devshell"
/ "lib"
/ "agents.txt"
)
def _get_source_agents_txt_path(
from_file: str | pathlib.Path,
) -> pathlib.Path:
"""Resolve the path to tools/devshell/lib/agents.txt relative to the given source file."""
base_file = pathlib.Path(from_file).resolve()
return (base_file.parent / _SOURCE_AGENTS_TXT_RELPATH).resolve()
def get_agent_env_vars() -> typing.List[str]:
"""Read the list of agent environment variable names from agents.txt."""
if data is not None:
try:
with files(data).joinpath("agents.txt").open(
"r", encoding="utf-8"
) as f:
return [
line.strip()
for line in f
if line.strip() and not line.strip().startswith("#")
]
except (FileNotFoundError, TypeError):
# importlib.resources.files() raises TypeError if `data` is not a
# package (e.g. when mocked in tests or when imported outside a
# packaged environment), and open() raises FileNotFoundError if
# agents.txt is missing from the package resources. In either case,
# fall through to reading agents.txt directly from the source tree.
pass
# Fallback to source tree location if imported without GN packaging.
agents_txt = _get_source_agents_txt_path(__file__)
if agents_txt.is_file():
with agents_txt.open("r", encoding="utf-8") as f:
return [
line.strip()
for line in f
if line.strip() and not line.strip().startswith("#")
]
raise FileNotFoundError(
f"Could not locate agents.txt in packaged resources or at {agents_txt}"
)
def is_invoked_by_agent(env: typing.Mapping[str, str] | None = None) -> bool:
"""Determine if the given environment (or os.environ) indicates execution by an AI agent.
Args:
env: Optional mapping representing the environment. Defaults to os.environ.
Returns:
bool: True if an agent environment variable is present, False otherwise.
"""
if env is None:
env = os.environ
for var in get_agent_env_vars():
if env.get(var) is not None:
return True
return False