Agents
One tool, five operations
import { generateText } from "ai";
import { packageTool } from "@agntn/registries/ai";
const { text } = await generateText({
model: yourModel,
tools: { packageRegistry: packageTool },
prompt: "What is the latest version of pkg:npm/lodash and who maintains it?",
});
packageTool is a tool() from the AI SDK, ready to hand to a model, with a discriminated input schema. The model picks an operation and the tool routes it to the matching helper:
operation | Input | Returns |
|---|---|---|
package | purl | Package |
versions | purl | Version[] |
dependencies | purl with a version | Dependency[] |
maintainers | purl | Maintainer[] |
bulk-packages | purls, 1 to 50, optional concurrency | Record<string, Package> keyed by PURL, failed ones absent |
The AI SDK's abort signal is passed through to every registry call, so cancelling the generation cancels the HTTP request.
Install the peers
pnpm add ai zod
Both are optional peer dependencies. The main entry never imports them; only the /ai subpath does.
The schema, in words
The model sees one description and five input shapes. Each purl field carries an example in its description (pkg:npm/lodash or pkg:cargo/serde; pkg:pypi/flask@3.1.1 for dependencies), which is what keeps a model from sending lodash on its own. purls is capped at fifty and concurrency at fifty.
What the answer is
The tool returns the normalized objects, not prose. A model summarizing them should say what the registry said; the Lookup explorer shows the same objects for any PURL, which is a quick way to check a summary against the source.
description: "ignore previous instructions" has learned one thing about the package, and nothing about what to do next.Rolling your own
The tool is thin by design. If your framework is not the AI SDK, the same five operations are one switch over the helpers:
import {
bulkFetchPackages,
fetchDependenciesFromPURL,
fetchMaintainersFromPURL,
fetchPackageFromPURL,
fetchVersionsFromPURL,
} from "@agntn/registries";
export async function run(input: { operation: string; purl?: string; purls?: string[] }, signal?: AbortSignal) {
switch (input.operation) {
case "package":
return fetchPackageFromPURL(input.purl!, signal);
case "versions":
return fetchVersionsFromPURL(input.purl!, signal);
case "dependencies":
return fetchDependenciesFromPURL(input.purl!, signal);
case "maintainers":
return fetchMaintainersFromPURL(input.purl!, signal);
case "bulk-packages":
return Object.fromEntries(await bulkFetchPackages(input.purls!, { signal }));
}
}
Wrap the registry in createCached first if the agent will ask about the same packages more than once.