The network builtins, callable from your own tool code.
web_search, visit_webpage and fetch_json are MODEL-facing: they are
declared to the LLM and the LLM calls them. Nothing exposed them to the
execute body of a tool the author wrote — and authors kept reaching for
them anyway. Across the starter evals, nine separate runs wrote
ctx.fetch_json(...), ctx.run_code(...) or
import { fetch_json } from "@alexkroman1/aai", most at several call
sites, and each one cost a build round.
That is not a misunderstanding worth correcting with documentation. Someone
writing a tool that needs a REST call reasonably expects the framework's
REST call to be reachable; "which side of the model boundary does this live
on" is framework internals, and the author should not have to hold it. So
the capability is exposed rather than the rule restated.
These are the SAME implementations the builtins use, reached through the
same factories — so URL screening, redirect re-validation, credential-header
stripping, response-size caps and timeouts all apply identically. Whether
the URL is screened at all is builtinFetch's decision, not one made here:
inside a container it is plain pinnedFetch (the container is the
boundary, and tool code has open egress anyway), and on a developer's own
machine under aai dev it is safeFetch, because there the host IS
someone's laptop.
run_code is deliberately NOT here. It exists to run code the MODEL wrote;
tool code that wants to compute something can just compute it.
Two shapes of every call, and a permissive return type, both for the same
reason: the first version of this module took only positional arguments
and returned Promise<unknown>, and in the very next eval EVERY call site
had to cast — const data: any = await fetchJson(url),
(await webSearch(query)) as any[]. That is the mistake useToolResult
and ToolCallInfo.args had already been fixed for, reintroduced in a new
API hours later. A remote JSON body is not knowable by the framework, so
unknown buys no safety here — it only makes correct code fail to compile.
Pass a type argument (fetchJson<Quote>(url)) for real checking.
The object form exists because agents reach for the shape they already
know from the model-facing builtin ({ query, max_results }), and guessing
wrong cost a build round.
The network builtins, callable from your own tool code.
web_search,visit_webpageandfetch_jsonare MODEL-facing: they are declared to the LLM and the LLM calls them. Nothing exposed them to theexecutebody of a tool the author wrote — and authors kept reaching for them anyway. Across the starter evals, nine separate runs wrotectx.fetch_json(...),ctx.run_code(...)orimport { fetch_json } from "@alexkroman1/aai", most at several call sites, and each one cost a build round.That is not a misunderstanding worth correcting with documentation. Someone writing a tool that needs a REST call reasonably expects the framework's REST call to be reachable; "which side of the model boundary does this live on" is framework internals, and the author should not have to hold it. So the capability is exposed rather than the rule restated.
These are the SAME implementations the builtins use, reached through the same factories — so URL screening, redirect re-validation, credential-header stripping, response-size caps and timeouts all apply identically. Whether the URL is screened at all is
builtinFetch's decision, not one made here: inside a container it is plainpinnedFetch(the container is the boundary, and tool code has open egress anyway), and on a developer's own machine underaai devit issafeFetch, because there the host IS someone's laptop.run_codeis deliberately NOT here. It exists to run code the MODEL wrote; tool code that wants to compute something can just compute it.Two shapes of every call, and a permissive return type, both for the same reason: the first version of this module took only positional arguments and returned
Promise<unknown>, and in the very next eval EVERY call site had to cast —const data: any = await fetchJson(url),(await webSearch(query)) as any[]. That is the mistakeuseToolResultandToolCallInfo.argshad already been fixed for, reintroduced in a new API hours later. A remote JSON body is not knowable by the framework, sounknownbuys no safety here — it only makes correct code fail to compile. Pass a type argument (fetchJson<Quote>(url)) for real checking.The object form exists because agents reach for the shape they already know from the model-facing builtin (
{ query, max_results }), and guessing wrong cost a build round.