Files
hermes-agent/hermes_cli/plugin_command_dispatch.py
T
Teknium 5f274827ff fix(plugins): keep a context keyword whose name only a positional-only first parameter shares
`def h(source, /, **kwargs)` and `def h(*session_id, **kwargs)` cannot take
that name by keyword, so the context value belongs in kwargs. (Greptile
re-review on #140997.)
2026-10-18 01:27:20 -07:00

33 lines
1.6 KiB
Python

"""Compatibility-aware call boundary for plugin slash-command handlers."""
from __future__ import annotations
import inspect
from typing import Any, Callable
_KEYWORD_KINDS = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
def invoke_plugin_command(handler: Callable, raw_args: str, **context: Any) -> Any:
"""Call *handler* once with *raw_args* plus the *context* keywords it declares.
Every surface passes the same context (``source``, ``session_id``, ``session_key``,
``platform``); a handler opts in by naming any of them as keyword-capable parameters, or
takes them all with ``**kwargs``. Legacy ``fn(raw_args)`` handlers and opaque signatures keep
the one-argument call. Handler exceptions propagate; they are never retried as a legacy call.
"""
try:
parameters = list(inspect.signature(handler).parameters.values())
except (TypeError, ValueError):
return handler(raw_args)
# The first positional parameter receives raw_args whatever its name (``def h(source, **kw)``
# must not get ``source`` twice).
if parameters and parameters[0].kind is not inspect.Parameter.KEYWORD_ONLY:
if parameters[0].kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
context = {k: v for k, v in context.items() if k != parameters[0].name}
parameters = parameters[1:]
if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters):
return handler(raw_args, **context)
accepted = {p.name for p in parameters if p.kind in _KEYWORD_KINDS}
return handler(raw_args, **{k: v for k, v in context.items() if k in accepted})