Multi-provider Jev transport with fail-closed validation, used by Jev Browser and Jev MCP.
One ask() picks a carrier from the environment, sends your typed questions, and validates the answers before you see them. You get the answer plus usage and the effective model, or a typed rejection. It never throws a verdict at you. You can also build on it directly.
Requires Node.js 22 or newer.
npm install @jkudish/jev-agent-toolsask(input, config?) returns Promise<{ ok: true, answer, usage, model, provider } | { ok: false, code, message }> and never throws a verdict. Transport failures return request_failed; invalid responses return specific codes such as answer_id_mismatch, invalid_distribution, invalid_noul, invalid_usage, and invalid_model; configuration errors return configuration_error. Messages never include response bodies or credentials. The two consumers need different error behavior: jev-browser maps !ok to its own exception, while jev-mcp maps !ok to invalid_response.
import { ask } from "@jkudish/jev-agent-tools";
const result = await ask({
state: "A customer asks for a refund of a duplicate charge.",
questions: { refund: { type: "noul", instructions: "Is a refund requested?" } },
model: "jev-latest",
signal: new AbortController().signal,
});
if (!result.ok) console.error(result.code, result.message);
else console.log(result.answer.refund.noul, result.usage, result.model);Auto-selection tries them in this order:
- TypeSafe (
TYPESAFE_API_KEY, optionalTYPESAFE_BASE_URL): direct; sends the model you supply, usuallyjev-latest. - OpenRouter (
OPENROUTER_API_KEY, beginssk-or-): mapsjev-latesttotypesafe/jev-1.13. - Cloudflare (
JEV_CLOUDFLARE_API_TOKENpreferred overCLOUDFLARE_API_TOKEN, plusCLOUDFLARE_ACCOUNT_ID): mapsjev-latesttotypesafe/jev. - Vercel AI Gateway (
AI_GATEWAY_API_KEY): selectstypesafe-ai/jevunless given atypesafe-ai/model.
Set JEV_PROVIDER to typesafe, openrouter, cloudflare, vercel, or auto to select strictly. Unknown names and missing credentials fail rather than falling through. With no provider configured, the diagnostic names every supported credential variable. config.env accepts an injectable environment record, and config.transport accepts a run-bound transport for callers that own one.
Every reply is checked before you see it. Validation requires exactly the requested answer IDs and criterion IDs, finite probabilities in [0,1] summing to within 0.01 of 1, and a selected Choice maximum within a 0.001 tie tolerance. Noul values must be in [0,1], confidence finite or null, usage counters non-negative safe integers, and the effective model nonempty. An invalid answer yields ok: false before any usage is credited, so a malformed response can never become a decision.
The built-in list is a fixed, maintainer-curated set, currently: TypeSafe, OpenRouter, Cloudflare, and Vercel. PRs that add a new built-in carrier are generally not accepted unless sufficient demand is shown. If you want support for a new provider, the supported path is a third-party driver package. I will accept PRs that link third-party providers from the READMEs of this package, jev-browser, and jev-mcp.
ask(input, { transport }) accepts any JevTransport: a name and an ask(input) returning { answers, usage, model }. The validator checks the reply exactly as it checks a built-in carrier. The name is untrusted, so it never appears in error text.
import { ask, type JevTransport } from "@jkudish/jev-agent-tools";
const myGateway: JevTransport = {
name: "my-gateway",
async ask({ state, questions, model, signal }) {
const response = await fetch("https://gw.example.com/v1/systemone", {
method: "POST",
headers: { authorization: `Bearer ${process.env.MY_GATEWAY_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ model, state, questions }),
signal,
});
if (!response.ok) throw new Error(`upstream ${response.status}`);
const body = await response.json();
return {
answers: body.answers,
usage: { input_tokens: body.usage.input_tokens, output_tokens: body.usage.output_tokens },
model: body.model,
};
},
};
const result = await ask(input, { transport: myGateway });Jev Browser exposes the same injection as NavigateOptions.transport. Jev MCP reaches any System One-compatible endpoint with JEV_PROVIDER=compatible, no code needed.
Wrap your carrier in a small npm package that exports a factory, so callers keep their credentials in their own environment:
import { ask } from "@jkudish/jev-agent-tools";
import { createRequestyTransport } from "@example/requesty-jev-driver";
const result = await ask(input, { transport: createRequestyTransport(process.env) });A driver package exports a factory that returns a JevTransport: a name, and an ask that returns { answers, usage, model }. Document the credential environment variables it reads, map jev-latest to the model id your carrier serves, and keep error messages free of response bodies and credentials. Validation still happens here, so a malformed reply from your carrier can never become a decision.
Published a driver package? Open an issue or pull request on any of the three repositories and it will be linked from that README's provider section.
The built-ins are a fixed, maintainer-curated set (TypeSafe, OpenRouter, Cloudflare, Vercel). New built-ins are generally not accepted unless sufficient demand is shown — open an issue first. The mechanics, for when one is accepted:
- Add
src/transports/<name>.tsexporting a driver:name,isConfigured(env),assertConfigured(env), andcreate(env)returning aJevTransport. - Register it in the
driversarray insrc/provider.ts, which widens theBuiltinDrivername union. Pick its auto-detection position deliberately; the order is the documented precedence. - Map
jev-latestto the model id the carrier actually serves, like the OpenRouter and Cloudflare mappings above. - Throw fixed-string errors only. The registry forwards messages that start with
Unknown JEV_PROVIDER, the no-credentials diagnostic, orJEV_PROVIDER=; anything else is replaced by a generic message. Never include response bodies. - Add hermetic tests against a stubbed endpoint. A live smoke behind a real key is welcome but optional.
jev-browser picks new built-ins up automatically through this package. jev-mcp deliberately keeps its own OpenRouter, Cloudflare, and compatible fetch transports so it can keep its retry, deadline, and cancellation rules, so a new carrier lands there as a local change until it needs the shared layer.
- Jev Browser gives an agent a task and a URL and lets Jev pick the actions. The npm package is @jkudish/jev-browser.
- Jev MCP exposes the same judgments as ten MCP tools your agent can call anywhere. The npm package is @jkudish/jev-mcp.
If you find Jev Agent Tools useful, consider becoming a sponsor or donating.
npm install
npm run build
npm test # hermetic tests, no API key needed
npm run test:live # one real judgment; requires TYPESAFE_API_KEYSee CONTRIBUTING.md. To report a vulnerability, see SECURITY.md.