Custom HTTP tools an administrator defines
A row in custom_tools becomes a ToolDef like any built-in, offered beside the thirteen. The registry had to stop being an import-time constant for that: `resolve_tools` now returns the schemas *and* the runners together, carried to the loop on the ToolContext. That closes a hole on the way. `run_tool` looked names up in the global REGISTRY with no reference to what had been offered, so a model naming a tool its chat was gated out of -- a family switched off, a permission the reader lacks -- had it run anyway. The resolved set is now authoritative. Arguments come from a model, so an argument may fill a hole but never move the target: the scheme and host of a URL template are literal, values are escaped for where they land, and the origin is pinned afterwards. Every redirect hop is checked the way services/fetch.py checks one, and the secret is dropped if a hop leaves the origin it was issued for. Also fixes the tool-activity block claiming every library tool had "searched the web", which it has done since the second family landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -57,16 +57,22 @@ MAX_HARNESS_CHARS = 8000
|
||||
MAX_NAMED_DOCUMENTS = 5
|
||||
|
||||
|
||||
def _families(tools: list[dict[str, Any]]) -> list[str]:
|
||||
"""Which families are represented in an offered tool list, in a fixed order."""
|
||||
from lembas.services.tools import FAMILIES, REGISTRY
|
||||
def _families(db: DBSession, tools: list[dict[str, Any]]) -> list[str]:
|
||||
"""Which families are represented in an offered tool list, in a fixed order.
|
||||
|
||||
Resolved against the database rather than the import-time registry, because
|
||||
an administrator-defined tool is a row and would otherwise contribute no
|
||||
family at all -- which is to say its guidance would never be admitted.
|
||||
"""
|
||||
from lembas.services import tools as tools_service
|
||||
|
||||
book = tools_service.registry(db)
|
||||
offered = {
|
||||
REGISTRY[name].family
|
||||
book[name].family
|
||||
for tool in tools
|
||||
if (name := (tool.get("function") or {}).get("name")) in REGISTRY
|
||||
if (name := (tool.get("function") or {}).get("name")) in book
|
||||
}
|
||||
return [family for family in FAMILIES if family in offered]
|
||||
return [family for family in tools_service.families(db) if family in offered]
|
||||
|
||||
|
||||
def _tool_names(tools: list[dict[str, Any]]) -> str:
|
||||
@@ -109,7 +115,7 @@ def context_variables(
|
||||
from lembas.services import tools as tools_service
|
||||
|
||||
offered = tools or []
|
||||
families = _families(offered)
|
||||
families = _families(db, offered)
|
||||
stamp = datetime.now().astimezone()
|
||||
|
||||
values: dict[str, str] = {
|
||||
@@ -185,7 +191,7 @@ def compose(
|
||||
return compose_from(
|
||||
db,
|
||||
variables=context_variables(db, user, offered, chat),
|
||||
families=_families(offered),
|
||||
families=_families(db, offered),
|
||||
has_tools=bool(offered),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user