Part 1
4 tools, one registry
A tool is a schema for its input and a function that runs it. The registry holds both, and each provider maps the schema into its own API format.
Study first
- Tool use on each API: Anthropic's
toolswithinput_schemaand thetool_useandtool_resultblocks; OpenAI's function tools withfunction_callandfunction_call_outputitems. - Zod 4's
z.toJSONSchema, which turns the input schema into the JSON Schema both APIs accept.
Build
src/lib/tools.ts: aTooltype (name,description,inputschema,run) and atool()helper that parses the input beforerunsees it. 4 tools:calculator,read_url,save_note, andweb_searchwhen a Brave key is configured.- The implementations, each its own module with a test:
calculator.ts, a recursive-descent parser (one function per precedence level) for+ - * / % ^and parentheses;web.ts,readPageandsearchWeb;notes.ts, anotestable through the Day 5 query function, with itscreate tableappended to the schema. The curriculum'ssave_notewrites a JSON file; this project already has a database. - Descriptions that say when to call the tool, not only what it does. The calculator's says to call it for any calculation.
Done when
pnpm testcovers the calculator's precedence and errors, the page reader's refusals and byte cap, and the search request's shape.
Technical notes
readPagerefuses the private ranges, the shared range 100.64.0.0/10 and link-local 169.254.0.0/16, where cloud metadata services answer,localhostand.internalnames, and any scheme but http and https. It follows redirects by hand so every hop is checked, and reads at most 512 KB of a body.- The text extractor drops scripts, styles and tags and keeps block boundaries as line breaks. The result is capped at 8,000 characters per page.
web_searchis in the registry only whenBRAVE_SEARCH_KEYis set.- The calculator refuses anything it cannot tokenise, and a non-finite result such as
1 / 0. - No tool today changes anything outside the app. When one does, on Day 27, a confirmation step goes between the model's call and the run.

