refactor(tools): compact docstrings and section banners (WHY kept)
This commit is contained in:
@@ -42,11 +42,9 @@ def _bound_error_text(text: str) -> str:
|
||||
|
||||
|
||||
def _bound_json_error_result(result: str) -> str:
|
||||
"""Trim an oversized ``error`` field in a JSON string result.
|
||||
|
||||
Handlers that ``json.dumps({"error": str(exc)})`` directly bypass ``tool_error``'s
|
||||
cap; applied at the dispatch boundary so no tool can stack unbounded errors across retries.
|
||||
"""
|
||||
"""Trim an oversized ``error`` field in a JSON string result: handlers that
|
||||
``json.dumps({"error": str(exc)})`` directly bypass ``tool_error``'s cap, so this runs
|
||||
at the dispatch boundary to stop unbounded errors stacking across retries."""
|
||||
if len(result) <= _MAX_TOOL_ERROR_CHARS or '"error"' not in result:
|
||||
return result
|
||||
try:
|
||||
@@ -212,9 +210,7 @@ _OVERRIDE_DENIED_MSG = (
|
||||
"without operator opt-in (allow_tool_override).")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# check_fn TTL cache
|
||||
#
|
||||
# ---- check_fn TTL cache ----------------------------------------------------
|
||||
# check_fns probe external state (Docker daemon, Modal SDK, playwright binary) that
|
||||
# changes on human timescales, so results are cached ~30 s: env-var flips via
|
||||
# ``hermes tools`` still propagate within a turn or two with no explicit invalidation.
|
||||
@@ -226,7 +222,6 @@ _OVERRIDE_DENIED_MSG = (
|
||||
# grace window serves the last-good True WITHOUT caching the failure. A failure
|
||||
# persisting past the window is honored, so a backend that really went down stops
|
||||
# advertising its tools.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_CHECK_FN_TTL_SECONDS = 30.0
|
||||
# Grace window after a success in which a failure counts as a flake; kept short
|
||||
@@ -394,11 +389,8 @@ def invalidate_check_fn_cache() -> None:
|
||||
|
||||
|
||||
def get_cached_check_fn_result(fn: Callable) -> Optional[bool]:
|
||||
"""Cached verdict for *fn* if its TTL is still valid, else None.
|
||||
|
||||
NEVER executes the probe: for read-only surfaces (dashboard status panels)
|
||||
that must not trigger network / auth / SDK work inside a request path.
|
||||
"""
|
||||
"""Cached verdict for *fn* if its TTL is still valid, else None. NEVER runs the probe:
|
||||
for read-only surfaces (dashboard panels) that must not do network/auth/SDK work."""
|
||||
now = time.monotonic()
|
||||
scope = check_fn_cache_scope()
|
||||
if scope == CHECK_FN_CACHE_BYPASS:
|
||||
@@ -532,18 +524,13 @@ class ToolRegistry:
|
||||
with self._lock:
|
||||
return self._toolset_aliases.get(alias)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Registration
|
||||
# ------------------------------------------------------------------
|
||||
# ---- Registration ------------------------------------------------
|
||||
|
||||
def register_plugin_override_policy(
|
||||
self, module_namespace: str, allowed: bool, *, scope: Optional[str] = None,
|
||||
) -> _PluginOverridePolicy:
|
||||
"""Bind a plugin module namespace to its current operator opt-in.
|
||||
|
||||
The identity-bearing result lets plugin unload/reload revoke a stale
|
||||
authorization without losing durable module-to-profile attribution.
|
||||
"""
|
||||
"""Bind a plugin module namespace to its current operator opt-in. The identity-bearing
|
||||
result lets unload/reload revoke a stale authorization without losing attribution."""
|
||||
with self._lock:
|
||||
policy = _PluginOverridePolicy(allowed)
|
||||
self._plugin_override_policy[(scope, module_namespace)] = policy
|
||||
@@ -659,11 +646,8 @@ class ToolRegistry:
|
||||
@staticmethod
|
||||
def _caller_module() -> str:
|
||||
"""Best-effort module name of the registry method's caller (two frames up).
|
||||
|
||||
``deregister()`` takes only a tool name — no handler to bind authorization
|
||||
to via ``_plugin_owner_of`` — so frame inspection is the only way to know
|
||||
who is asking.
|
||||
"""
|
||||
``deregister()`` takes only a tool name — no handler for ``_plugin_owner_of`` —
|
||||
so frame inspection is the only way to know who is asking."""
|
||||
try:
|
||||
return sys._getframe(2).f_globals.get("__name__", "") or ""
|
||||
except Exception:
|
||||
@@ -824,11 +808,9 @@ class ToolRegistry:
|
||||
scope: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Restore a host-owned registration if it is still current (plugin ownership ledger).
|
||||
|
||||
The identity check is deliberate: another plugin (or another ``PluginManager``
|
||||
in a multi-profile process) may have registered a newer entry under the same
|
||||
name, in which case unloading this entry must leave the newer one untouched.
|
||||
"""
|
||||
The identity check is deliberate: another plugin (or ``PluginManager`` in a
|
||||
multi-profile process) may have registered a newer entry under the same name, and
|
||||
unloading this entry must leave that newer one untouched."""
|
||||
with self._lock:
|
||||
target = self._slot(scope, create=True)
|
||||
if target.get(name) is not current:
|
||||
@@ -864,15 +846,11 @@ class ToolRegistry:
|
||||
logger.debug("Restored tool registration: %s", name)
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schema retrieval
|
||||
# ------------------------------------------------------------------
|
||||
# ---- Schema retrieval --------------------------------------------
|
||||
|
||||
def get_definitions(self, tool_names: Set[str], quiet: bool = False) -> List[dict]:
|
||||
"""OpenAI-format schemas for the requested tools whose ``check_fn`` passes (or is absent).
|
||||
|
||||
Probes go through the ~30 s TTL cache so ``hermes tools enable`` still lands quickly.
|
||||
"""
|
||||
"""OpenAI-format schemas for the requested tools whose ``check_fn`` passes (or is
|
||||
absent). Probes use the ~30 s TTL cache so ``hermes tools enable`` lands quickly."""
|
||||
result = []
|
||||
check_results: Dict[Callable, bool] = {}
|
||||
entries_by_name = {entry.name: entry for entry in self._snapshot_entries()}
|
||||
@@ -901,9 +879,7 @@ class ToolRegistry:
|
||||
result.append({"type": "function", "function": schema_with_name})
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Dispatch
|
||||
# ------------------------------------------------------------------
|
||||
# ---- Dispatch ----------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _normalize_handler_result(name: str, result):
|
||||
@@ -954,9 +930,7 @@ class ToolRegistry:
|
||||
sanitized = raw # defensive: never let the sanitizer block error propagation
|
||||
return tool_error(sanitized)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Query helpers
|
||||
# ------------------------------------------------------------------
|
||||
# ---- Query helpers -----------------------------------------------
|
||||
|
||||
def get_max_result_size(self, name: str, default: int | float | None = None) -> int | float:
|
||||
"""Return per-tool max result size, or *default* (or global default)."""
|
||||
|
||||
@@ -39,11 +39,9 @@ def sanitize_property_key(key: str) -> str:
|
||||
|
||||
|
||||
def _rename_property_keys(props: dict, path: str) -> dict[str, str]:
|
||||
"""Return {original_key: conforming_key} for one properties dict (identity entries omitted).
|
||||
|
||||
"""{original_key: conforming_key} for one properties dict (identity entries omitted).
|
||||
Deterministic (insertion order, numeric suffixes on collision) so the model-visible
|
||||
schema and the dispatch-time reverse map from the registry's original schema agree.
|
||||
"""
|
||||
schema and the dispatch-time reverse map from the registry's original schema agree."""
|
||||
renames: dict[str, str] = {}
|
||||
taken = {k for k in props if _PROP_KEY_RE.match(k)}
|
||||
for key in props:
|
||||
@@ -64,11 +62,8 @@ def _rename_property_keys(props: dict, path: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def unrename_tool_args(params_schema: Any, args: Any) -> Any:
|
||||
"""Map sanitized property keys in model-emitted args back to wire names.
|
||||
|
||||
``params_schema`` is the ORIGINAL (unsanitized) registry schema. Recurses into
|
||||
object values and array items; unknown keys pass through untouched.
|
||||
"""
|
||||
"""Map sanitized property keys in model-emitted args back to wire names. ``params_schema``
|
||||
is the ORIGINAL registry schema; recurses into objects/array items; unknown keys pass."""
|
||||
if not isinstance(params_schema, dict) or not isinstance(args, dict):
|
||||
return args
|
||||
props = params_schema.get("properties")
|
||||
@@ -148,11 +143,9 @@ _TOP_LEVEL_FORBIDDEN_KEYS = ("allOf", "anyOf", "oneOf", "enum", "not")
|
||||
|
||||
|
||||
def _strip_top_level_combinators(params: dict, *, path: str = "<tool>") -> dict:
|
||||
"""Drop combinators from the TOP level only (Codex rejects them there).
|
||||
|
||||
They are usually conditional-required hints; dropping them does not change which
|
||||
argument values are valid (handlers re-validate). Nested combinators are preserved.
|
||||
"""
|
||||
"""Drop combinators from the TOP level only (Codex rejects them there). They are usually
|
||||
conditional-required hints, so validity is unchanged (handlers re-validate); nested
|
||||
combinators are preserved."""
|
||||
if not isinstance(params, dict):
|
||||
return params
|
||||
out = dict(params)
|
||||
@@ -220,11 +213,8 @@ _CONST_PRIMITIVE_TYPES: dict[type, str] = {
|
||||
|
||||
|
||||
def _const_branch_type(branch: Any) -> str | None:
|
||||
"""JSON-Schema primitive type of a pure ``const`` branch, else None.
|
||||
|
||||
Qualifies when the dict carries a primitive ``const`` and any declared ``type``
|
||||
matches it; ``title``/``description`` are allowed, any other keyword disqualifies.
|
||||
"""
|
||||
"""JSON-Schema primitive type of a pure ``const`` branch, else None: a primitive ``const``
|
||||
whose declared ``type`` (if any) matches; only ``title``/``description`` may accompany it."""
|
||||
if not isinstance(branch, dict) or "const" not in branch:
|
||||
return None
|
||||
if set(branch) - {"const", "type", "title", "description"}:
|
||||
@@ -286,12 +276,10 @@ _NON_SCHEMA_LIST_KEYS = frozenset({"required", "enum", "examples", "dependentReq
|
||||
|
||||
|
||||
def _normalize_type_array(value: list, out: dict) -> None:
|
||||
"""Normalize a ``type: [...]`` array into *out* (llama.cpp and Gemini-via-OpenAI reject arrays).
|
||||
|
||||
Per the AI-SDK behavior: one non-null type → ``type: X`` (+ ``nullable`` if ``null``
|
||||
present); several → ``anyOf`` of single-type schemas so EVERY branch survives; none →
|
||||
``null`` or the object fallback. Ported from anomalyco/opencode#31877.
|
||||
"""
|
||||
"""Normalize a ``type: [...]`` array into *out* (llama.cpp and Gemini-via-OpenAI reject
|
||||
arrays). Per AI-SDK: one non-null type → ``type: X`` (+ ``nullable`` if ``null`` present);
|
||||
several → ``anyOf`` of single-type schemas so EVERY branch survives; none → ``null`` or
|
||||
the object fallback. Ported from anomalyco/opencode#31877."""
|
||||
has_null = "null" in value
|
||||
non_null = [t for t in value if isinstance(t, str) and t != "null"]
|
||||
if len(non_null) == 1:
|
||||
@@ -383,11 +371,9 @@ _STRIP_ON_RECOVERY_KEYS = frozenset({"pattern", "format"})
|
||||
def _reactive_strip(
|
||||
tools: list[dict], strip_node: Callable[[dict], int], log_msg: str,
|
||||
) -> tuple[list[dict], int]:
|
||||
"""Apply *strip_node* (returns keywords removed) to every dict node of each tool's parameters.
|
||||
|
||||
Handles OpenAI format (``{"function": {"parameters": ...}}``) and Responses format
|
||||
(``{"name": ..., "parameters": ...}``). Returns ``(tools, stripped_count)`` — same list.
|
||||
"""
|
||||
"""Apply *strip_node* (returns keywords removed) to every dict node of each tool's
|
||||
parameters, in place. Handles OpenAI (``{"function": {"parameters": ..}}``) and Responses
|
||||
(``{"name": .., "parameters": ..}``) formats. Returns ``(tools, stripped_count)``."""
|
||||
if not tools:
|
||||
return tools, 0
|
||||
stripped = 0
|
||||
@@ -440,12 +426,9 @@ def strip_pattern_and_format(tools: list[dict]) -> tuple[list[dict], int]:
|
||||
|
||||
|
||||
def strip_slash_enum(tools: list[dict]) -> tuple[list[dict], int]:
|
||||
"""Strip ``enum`` keywords whose string values contain ``/``, in place.
|
||||
|
||||
xAI's Responses/chat endpoints compile schemas to a grammar that rejects ``/`` in
|
||||
enum values (HTTP 400 before any token) — typically MCP enums of HuggingFace model
|
||||
IDs. The constraint is a prompting hint only; the model still sees the description.
|
||||
"""
|
||||
"""Strip ``enum`` keywords whose string values contain ``/``, in place. xAI compiles
|
||||
schemas to a grammar that rejects ``/`` in enum values (HTTP 400 before any token) —
|
||||
typically MCP enums of HuggingFace model IDs. The constraint is a prompting hint only."""
|
||||
def _strip(node: dict) -> int:
|
||||
enum_val = node.get("enum")
|
||||
if isinstance(enum_val, list) and any(isinstance(v, str) and "/" in v for v in enum_val):
|
||||
|
||||
@@ -219,14 +219,11 @@ def _cd_target(executable: str, args: list[str], cwd: Path) -> Path | None:
|
||||
|
||||
|
||||
def _shell_script_arg(args: list[str]) -> str | None:
|
||||
"""Return the script string owned by a shell's ``-c``, if present.
|
||||
|
||||
approval.py's ``_bash_exec_payload`` parses bash's real option grammar
|
||||
(``-o pipefail -c '<script>'`` hides ``-c`` behind an operand). When it finds
|
||||
no ``-c``, fall back to a permissive positional scan: zsh/dash/ksh option
|
||||
letters (``zsh -yc``) fall outside bash's alphabet and would otherwise make
|
||||
this block-guard fail open.
|
||||
"""
|
||||
"""Return the script string owned by a shell's ``-c``, if present. approval.py's
|
||||
``_bash_exec_payload`` parses bash's real option grammar (``-o pipefail -c '<script>'``
|
||||
hides ``-c`` behind an operand); when it finds no ``-c``, fall back to a permissive
|
||||
positional scan, since zsh/dash/ksh option letters (``zsh -yc``) fall outside bash's
|
||||
alphabet and would otherwise make this block-guard fail open."""
|
||||
has_c, payload = _bash_exec_payload(args)
|
||||
if has_c:
|
||||
return payload
|
||||
@@ -305,9 +302,7 @@ def _heredoc_specs(line: str) -> list[_Heredoc]:
|
||||
|
||||
def _mask_heredocs(command: str) -> tuple[str, list[str]]:
|
||||
"""Blank heredoc bodies; return (masked command, bodies a bare shell would execute).
|
||||
|
||||
Unterminated heredocs run to end of input and are still reported.
|
||||
"""
|
||||
Unterminated heredocs run to end of input and are still reported."""
|
||||
output: list[str] = []
|
||||
pending: list[_Heredoc] = []
|
||||
finished: list[_Heredoc] = []
|
||||
@@ -553,13 +548,10 @@ def _find_mutation(command: str, cwd: Path, root: Path, depth: int = 0) -> str |
|
||||
|
||||
|
||||
def guard_active() -> bool:
|
||||
"""Whether the self-repo git guard applies on this platform.
|
||||
|
||||
Windows-only: NTFS locks loaded .py/.pyd files, so overwriting the live checkout
|
||||
can corrupt the running process. On POSIX, open handles keep the old inode alive;
|
||||
the mixed-module hazard is limited to later lazy imports — not worth blocking
|
||||
every git workflow for.
|
||||
"""
|
||||
"""Whether the self-repo git guard applies on this platform. Windows-only: NTFS locks
|
||||
loaded .py/.pyd files, so overwriting the live checkout can corrupt the running
|
||||
process. On POSIX open handles keep the old inode alive; the mixed-module hazard is
|
||||
limited to later lazy imports — not worth blocking every git workflow for."""
|
||||
return os.name == "nt"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user