fix(proxy): return actionable errors for bad list_requests/list_sitemap args

When the model passes a malformed argument to the Caido proxy tools, the
agent currently gets back an opaque GraphQL error that references the query
document rather than its own input, so it can't self-correct and repeats
the same bad call. Two cases seen frequently with reasoning models (e.g.
Kimi-K2.7-Code via an OpenAI-compatible endpoint):

- list_requests with an invalid httpql_filter surfaces only
  "Invalid HTTPQL query (line 34, column 3)" — the GraphQL line, not the
  filter. Now it returns a message naming the offending filter and how to
  fix it (omit to list all, or valid HTTPQL examples; note no NOT operator).

- list_sitemap with a non-i32 parent_id/scope_id (a URL/host/UUID) surfaces
  "Invalid ID format, should be an i32". Now it validates up front and tells
  the agent to use a numeric `id` from a prior list_sitemap response.

Both keep the tool call non-fatal and let the agent recover on the next turn.
This commit is contained in:
Mike Mooring 2026-06-15 12:18:22 -07:00
parent cc23eeb65d
commit 7294b6febc
No known key found for this signature in database
GPG key ID: 35DE0074FFB1348D

View file

@ -207,6 +207,22 @@ async def list_requests(
default=str,
)
except Exception as exc: # noqa: BLE001
if "HTTPQL" in str(exc):
return json.dumps(
{
"success": False,
"error": (
f"Invalid httpql_filter: {httpql_filter!r}. The Caido HTTPQL "
"parser rejected it. Fixes: omit httpql_filter entirely to list "
"all captured requests, or use valid HTTPQL — e.g. "
'req.path.cont:"/api", resp.code.eq:200 (ints unquoted, strings '
'quoted), or a bare quoted term like "password" to search both '
"req.raw and resp.raw. There is no NOT operator (use ne/ncont/"
"nlike/nregex)."
),
},
ensure_ascii=False,
)
return _err("list_requests", exc)
@ -440,6 +456,24 @@ async def list_sitemap(
client = _ctx_client(ctx)
if client is None:
return _no_client()
# Caido sitemap/scope IDs are i32 integers, not URLs/hosts/UUIDs. Reject
# non-numeric ids up front with actionable guidance so the model corrects
# itself instead of repeating an opaque "Invalid ID format" GraphQL error.
for _arg_name, _arg_val in (("parent_id", parent_id), ("scope_id", scope_id)):
if _arg_val is not None and not str(_arg_val).strip().lstrip("-").isdigit():
return json.dumps(
{
"success": False,
"error": (
f"Invalid {_arg_name}: {_arg_val!r}. {_arg_name} must be a "
"numeric Caido entry id (i32) taken from a prior list_sitemap "
"response's `id` field — not a URL, host, or UUID. Call "
"list_sitemap with no parent_id first to get root entries, then "
"pass an entry's numeric `id`."
),
},
ensure_ascii=False,
)
try:
payload = await caido_api.list_sitemap_with_client(
client,