d105376b21
* refactor(tools): discovery also scans tools/<pkg>/tool.py
A tool family that is a whole package had no way to register: discovery
globbed tools/*.py only and derived the module name from the filename.
Now the candidate list is tools/*.py plus tools/*/tool.py, merged and
sorted once so import order does not depend on depth (register() lets a
same-name duplicate overwrite silently), and the module name comes from
the path relative to tools/. Only tool.py is scanned inside a package,
so its siblings are libraries by construction. A package without an
__init__.py is skipped with a warning rather than registering from a
checkout and vanishing from the installed wheel.
The AST prefilter and the (mtime, size) disk cache are per absolute path
and work unchanged. The two hand-rolled tools/*.py enumerators in tests
now use the same candidate helper.
* refactor(connectors): one package for the connector domain, tools/connectors/
The connector code was spread across six flat files and a root-level
module that was a sibling of model_tools.py only by address:
tools/connections_tool.py -> tools/connectors/tool.py (schema, register, dispatcher)
tools/connectors/managed.py (the managed leg, split out)
tools/connections_tool_mcp.py -> tools/connectors/mcp.py (validation split out ->)
tools/connectors/targets.py (normalize_targets, validate_action)
tools/connections_tool_operation.py -> tools/connectors/operation.py
tools/connector_search.py -> tools/connectors/search.py
model_tools_connectors.py -> tools/connectors/dispatch.py
tools/tool_gateway/ -> tools/connectors/gateway/
Move only; every function body is unchanged. tools/connectors/__init__.py
is the door: nine names, the whole cross-package surface. model_tools and
tool_search deep-import a few helpers past it on purpose and the docstring
says so. The two split files make the import graph one-directional
(tool -> mcp -> targets, tool -> managed) where the old layout had
connections_tool importing validation out of the MCP file.
One behaviour-neutral seam change: the _connectors_available try/except
wrapper is gone. connectors_available() already fails closed, and both
the registry handler and the inline executor now read it as a module
attribute (gateway.config.connectors_available), so tests patch it in
one place instead of two. _default_client lives in managed.py, the only
module that calls it.
tools/managed_tool_gateway.py and tools/managed_gateway_auth.py stay:
they are gateway identity shared by tts, transcription, image and modal.
Test files follow their modules. No docs referenced the old paths; no
compat pointer is added (in-tree moves get none).
* ci: retrigger (zero-job dispatch on 142466de6b)
74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""Connector tool-gateway package: typed client-side plumbing for remote tools.
|
|
|
|
This package owns everything hermes-agent needs to talk to the managed tool
|
|
gateway's connector routes (search / schemas / execute / connections) and to
|
|
merge remote execute results back into ``tool_call`` result arrays.
|
|
|
|
Layering rules (enforced by review, not imports — keep them true):
|
|
|
|
- ``wire.py`` is a leaf: pydantic v2 models for the four gateway routes plus
|
|
route path constants. No pydantic model may escape the wire/client layer;
|
|
everything above sees plain dicts and frozen dataclasses.
|
|
- ``errors.py`` is stdlib-only: the ``ToolGatewayError`` family, the ONE
|
|
gateway error-envelope parser, and the single producer of the
|
|
connection-required payload shown to the model.
|
|
- ``names.py`` is stdlib-only: the ``connectors__<connector>__<tool>`` name
|
|
codec. Parsing never raises — a malformed name is a per-entry error and
|
|
sibling calls still run.
|
|
- ``config.py``: the ``tools.connectors`` config gate. Availability fails
|
|
closed: config flag AND the managed Nous tools entitlement.
|
|
- ``merge.py`` is PURE: partition / splice / render with no I/O and no
|
|
exceptions. Position in the original ``calls[]`` array is the only
|
|
correlation key — the wire ``index`` field is never trusted.
|
|
- ``client.py`` / ``bridge.py``: the HTTP client and the only module core
|
|
imports. Every bridge entry point is TOTAL — it catches its own
|
|
exceptions, because the bridge branch bypasses the registry's catch-wrap.
|
|
Approval is settled by the core BEFORE the bridge is called; denied
|
|
entries never reach it.
|
|
|
|
Core reaches this package through ``tools.connectors.dispatch.py``, which
|
|
dispatches one gateway request per connector entry via ``bridge.run_remote``
|
|
and re-enters core dispatch for each entry so per-tool policy fires against
|
|
the composed ``connectors__`` name.
|
|
"""
|
|
|
|
from tools.connectors.gateway.config import (
|
|
MAX_CALLS_PER_DISPATCH,
|
|
ConnectorConfig,
|
|
connectors_available,
|
|
)
|
|
from tools.connectors.gateway.errors import (
|
|
GatewayAuthError,
|
|
GatewayUnavailable,
|
|
IdempotencyConflict,
|
|
ToolGatewayError,
|
|
parse_gateway_error,
|
|
render_connection_required,
|
|
)
|
|
from tools.connectors.gateway.names import (
|
|
CONNECTOR_BATCH_SENTINEL,
|
|
CONNECTOR_NAME_PREFIX,
|
|
ConnectorName,
|
|
format_connector_name,
|
|
is_connector_name,
|
|
parse_connector_name,
|
|
)
|
|
|
|
__all__ = [
|
|
"CONNECTOR_BATCH_SENTINEL",
|
|
"CONNECTOR_NAME_PREFIX",
|
|
"ConnectorConfig",
|
|
"ConnectorName",
|
|
"GatewayAuthError",
|
|
"GatewayUnavailable",
|
|
"IdempotencyConflict",
|
|
"MAX_CALLS_PER_DISPATCH",
|
|
"ToolGatewayError",
|
|
"connectors_available",
|
|
"format_connector_name",
|
|
"is_connector_name",
|
|
"parse_connector_name",
|
|
"parse_gateway_error",
|
|
"render_connection_required",
|
|
]
|