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:
@@ -54,6 +54,27 @@ def test_only_the_guidance_for_offered_tools_appears(db, owner):
|
||||
assert "Skills are procedures" not in text
|
||||
|
||||
|
||||
def test_a_custom_tools_guidance_appears_only_when_it_is_offered(db, owner):
|
||||
"""The row supplies the default, and the fragment is gated on the tool's own
|
||||
family -- which is why the registry had to stop being a module constant."""
|
||||
from lembas.db.models import CustomTool
|
||||
|
||||
db.add(
|
||||
CustomTool(
|
||||
slug="weather",
|
||||
name="Weather",
|
||||
description="Look up the weather.",
|
||||
url_template="https://api.test/{{city}}",
|
||||
guidance="- Check the weather rather than guessing at it.",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
offered = tools_service.registry(db)["weather"].schema
|
||||
assert "Check the weather" in harness.compose(db, owner, [offered])
|
||||
assert "Check the weather" not in harness.compose(db, owner, _tools("web_search"))
|
||||
|
||||
|
||||
def test_the_memory_block_is_included_when_memory_is_offered(db, owner):
|
||||
memories_service.add(db, owner=owner, content="Prefers metric units.")
|
||||
text = harness.compose(db, owner, _tools("memory_add"))
|
||||
|
||||
Reference in New Issue
Block a user