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:
co-authored by
Claude Opus 5
parent
4ee7d3db7d
commit
d4cefb066a
@@ -29,15 +29,20 @@ router = APIRouter(prefix="/admin/prompts", tags=["admin-prompts"])
|
||||
SAMPLE_DOCUMENTS = "report.pdf, notes.txt"
|
||||
|
||||
|
||||
def _families_of(names: list[str]) -> list[str]:
|
||||
"""Keep only real family names, in the registry's order."""
|
||||
def _families_of(db: Db, names: list[str]) -> list[str]:
|
||||
"""Keep only real family names, in the registry's order.
|
||||
|
||||
Read from the database rather than the constant: a family can belong to an
|
||||
administrator-defined tool, and one the preview cannot name is one whose
|
||||
guidance cannot be checked here.
|
||||
"""
|
||||
wanted = set(names)
|
||||
return [family for family in tools_service.FAMILIES if family in wanted]
|
||||
return [family for family in tools_service.families(db) if family in wanted]
|
||||
|
||||
|
||||
def _tool_names(families: list[str]) -> str:
|
||||
def _tool_names(db: Db, families: list[str]) -> str:
|
||||
return ", ".join(
|
||||
name for name, tool in tools_service.REGISTRY.items() if tool.family in families
|
||||
name for name, tool in tools_service.registry(db).items() if tool.family in families
|
||||
)
|
||||
|
||||
|
||||
@@ -67,7 +72,7 @@ def _variables(
|
||||
values.update(
|
||||
{
|
||||
"model_name": model_name,
|
||||
"tool_names": _tool_names(families),
|
||||
"tool_names": _tool_names(db, families),
|
||||
"memories": memories_service.block(db, user) if "memory" in families else "",
|
||||
"skills": skills_service.index_block(db, user) if "skills" in families else "",
|
||||
"knowledge_bases": bases if "knowledge" in families else "",
|
||||
@@ -89,7 +94,7 @@ def _field_context(db: Db, key: str, *, value: str, overridden: bool) -> dict:
|
||||
async def prompts_page(request: Request, db: Db, user: AdminUser, saved: bool = False):
|
||||
stored = prompts_service.stored(db)
|
||||
models = chat_service.available_models(db, user)
|
||||
families = list(tools_service.FAMILIES)
|
||||
families = list(tools_service.families(db))
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -115,7 +120,7 @@ async def prompts_page(request: Request, db: Db, user: AdminUser, saved: bool =
|
||||
"models": models,
|
||||
"families": families,
|
||||
"registry": sorted(
|
||||
tools_service.REGISTRY.values(), key=lambda t: (t.family, t.name)
|
||||
tools_service.registry(db).values(), key=lambda t: (t.family, t.name)
|
||||
),
|
||||
"max_harness_chars": settings_store.get(
|
||||
db, "max_harness_chars", key=settings_store.PROMPTS
|
||||
@@ -164,7 +169,7 @@ async def preview(request: Request, db: Db, user: AdminUser):
|
||||
"""
|
||||
form = await request.form()
|
||||
overrides = _submitted(db, form)
|
||||
families = _families_of([str(value) for value in form.getlist("preview_family")])
|
||||
families = _families_of(db, [str(value) for value in form.getlist("preview_family")])
|
||||
model_name = str(form.get("preview_model") or "")
|
||||
bases = str(form.get("preview_bases") or "").strip()
|
||||
documents = str(form.get("preview_documents") or "").strip()
|
||||
|
||||
Reference in New Issue
Block a user