@jsm-mit/basic-tools-package
v0.4.0
Published
Basic, app-independent tools an LLM agent can call: today's date and time, a place name to GPS coordinates, the distance between two points. No dependencies — the tool shape is structural, so any host that speaks the chat-agent-package Tool interface can
Readme
basic-tools-package
Tools any agent can use, whatever app it serves. No dependencies: the tool shape is
declared structurally and matches @jsm-mit/chat-agent-package's Tool, so a host hands
these straight to an agent.
import { BASIC_TOOLS, BASIC_TOOL_CATALOG } from "@jsm-mit/basic-tools-package";
// A host merges the factories into its own registry; a stored agent configuration then
// names the tool and binds its parameters:
// { "tool": "get_current_date", "paramsJson": "{\"timeZone\":\"Europe/Warsaw\"}" }
const factory = BASIC_TOOLS["get_current_date"];
const tool = factory({ timeZone: "Europe/Warsaw" });
await tool.execute({});
// { date: "2026-09-16", time: "08:52", weekday: "środa",
// readable: "16 września 2026", timeZone: "Europe/Warsaw", utc: "..." }BASIC_TOOL_CATALOG is the same set for a host's UI: icon, label, description and the
parameters to ask a person for.
The tools
| Tool | What it answers | Bound parameters |
| --- | --- | --- |
| get_current_date | today's date (ISO), the weekday (English + ISO number) and the time where the agent's people live — language-neutral: the model writes them in the language of the conversation | timeZone (IANA, default Europe/Warsaw); an old locale is ignored |
| distance_between | the straight-line distance between two GPS points (haversine): metres, kilometres and a text like 8,3 km | locale (default pl-PL) |
| geocode_place | GPS coordinates of a place named in words — up to three candidates, best first | countryCodes (e.g. pl), language (default pl), userAgent, geocoderUrl (default: the public Nominatim) |
Why a date tool. A model has no clock. Asked what day it is, it answers from its training data — wrong, and stated with confidence. Give an agent this tool and it can check instead of guess. Note what a tool cannot do: the model still has to decide to call it, so a host that cares about dates should also put the current time in the system prompt and let the tool be the precise answer.
Why a geocoding tool. People say "near Kolejowa in Warsaw"; a tool that searches by
position wants lat and lng. The model calls geocode_place first and hands the numbers
on — the tools do not know about each other.
await BASIC_TOOLS["geocode_place"]({ countryCodes: "pl" }).execute({ query: "Kolejowa, Warszawa" });
// { found: true, places: [{ lat: 52.2264301, lng: 20.9843274, kind: "road",
// displayName: "Kolejowa, Czyste, Wola, Warszawa, województwo mazowieckie, 00-965, Polska" }, …] }
// { found: false, message: "…" } when nothing matches or the geocoder does not answer — it never throws.It is the one tool that leaves the process: it asks a Nominatim-compatible geocoder with
the global fetch (no key, still no npm dependency). The public instance allows one
request a second and wants a User-Agent that says who asks — the package queues requests
per geocoder host, remembers answers for a day, and a host with real traffic binds
geocoderUrl to its own instance. Results are © OpenStreetMap contributors (ODbL).
Why a distance tool. A model asked "how far is it" does spherical trigonometry in its
head — fluently and wrong. distance_between is the arithmetic: fromLat, fromLng,
toLat, toLng in, { ok: true, meters, kilometers, readable } out. It is the distance as
the crow flies, not a route, and the tool's description tells the model to say so. Pure: no
clock, no network.
Adding a tool
One file under src/tools/, exporting a factory and a BasicToolInfo; register both in
src/registry.ts; test it with a frozen clock (deps.now) and a stand-in network (deps.fetch), never the
real ones.
Scripts
npm run build, npm test, npm run typecheck.
npm run publish-public is the release: bump the version in package.json by hand, leave
it uncommitted, and run it. It refuses unless you are on master, master equals
origin/master, nothing else is uncommitted and the version is new on the registry; then
it builds, tests, publishes and leaves one "Version bump to " commit, pushed. A
failed publish leaves your bump alone.
