JavaScript / TypeScript
jangada also exists for JavaScript and TypeScript, as the npm package
jangada-ai. Same idea as the Python
library: switch provider, model and key without changing the rest of your code,
with retry and fallback by error type, structured output, tools, streaming and
cost on every response.
The source is TypeScript, but the published package is JavaScript (ESM and
CommonJS) with the types (.d.ts) included. It works in plain JS projects; in TS
you also get autocomplete and type checking.
Current version: 1.0.2, at parity with the Python library: 11 providers, tools, MCP, agents, RAG, eval, A2A, guardrails, cache and observability (see What ships in the package).
Installation
npm install jangada-aiProvider SDKs are optional dependencies: install only the ones you use. The
library imports an SDK only when its provider is called, so import "jangada-ai"
works with none of them installed.
| Provider | Install |
|---|---|
openai, azure, groq, openrouter, deepseek | npm install openai |
anthropic | npm install @anthropic-ai/sdk |
gemini, vertex, GeminiInteractions | npm install @google/genai (Interactions needs 1.33+) |
bedrock | npm install @aws-sdk/client-bedrock-runtime |
mistral | npm install @mistralai/mistralai |
ollama | nothing (talks HTTP straight to the server) |
| structured output with Zod | npm install zod |
| PDF, DOCX and XLSX in the prompt | npm install unpdf mammoth exceljs (PDF as image: @napi-rs/canvas) |
| MCP (client and server) | npm install @modelcontextprotocol/sdk (Code Mode: quickjs-emscripten) |
| RAG on Postgres / MongoDB | npm install pg / npm install mongodb |
Runs on Node 22 or newer, Bun, Deno and edge runtimes (Cloudflare Workers, Vercel
Edge). The core does not touch the file system. Reading files and .env lives in
the jangada-ai/node subpath.
First call
JavaScript (ESM):
import { LLM } from "jangada-ai";
import "jangada-ai/node"; // loads the project's .env (OPENAI_API_KEY...)
const llm = new LLM("openai", "gpt-4o-mini");
const comp = await llm.complete("Explain what a jangada is in one sentence.");
console.log(comp.text, comp.usage, comp.cost);JavaScript (CommonJS):
const { LLM } = require("jangada-ai");TypeScript is the same, with inferred types.
Switching providers means changing two strings:
new LLM("anthropic", "claude-sonnet-4-5");
new LLM("gemini", "gemini-2.5-flash");
new LLM("groq", "openai/gpt-oss-20b");Templates, params and fallback
const llm = new LLM("openai", "gpt-4o-mini", {
system: "You support {{ company }}.",
temperature: 0.3,
maxRetries: 2, // 429, 5xx and timeouts retry on the same model
fallbacks: [new LLM("anthropic", "claude-haiku-4-5")], // then another provider
});
const comp = await llm.complete("Hi, I'm {{ name }}.", {
variables: { company: "Fresh Bread Bakery", name: "Ana" },
});The canonical params are temperature, maxTokens (default 8192), topP,
topK, stop and seed. Each adapter translates them to the native name and
drops what the provider does not accept. Provider-specific params go in extra.
Every call accepts signal (AbortSignal) to cancel, including pending retries
and fallbacks.
Structured output
With Zod 4.2 or newer, parsed comes validated and typed:
import * as z from "zod";
const Person = z.object({ name: z.string(), age: z.number().int() }).meta({ title: "Person" });
const comp = await llm.parse("Ana is 30 years old.", Person);
comp.parsed; // { name: "Ana", age: 30 }It also accepts plain JSON Schema (jsonSchema(object, { validate })) and other
libraries that follow the Standard Schema spec. JSON that does not match the
schema becomes OutputValidationError, which falls back to the next model.
Tools
import { tool } from "jangada-ai";
const weather = tool({
name: "weather",
description: "Current temperature in a city",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, temperature: 31 }),
});
const comp = await llm.complete("How is the weather in Recife?", { tools: [weather] });
for (const call of comp.toolCalls) {
const args = await weather.parseArgs(call.args);
const result = call.result(await weather.execute(args));
// send back: history = [..., comp.assistantMessage(), { role: "tool", content: [result] }]
}Native tools, executed by the provider: webSearch(), webFetch(),
codeExecution(), fileSearch(), googleMaps(), computerUse(). They work
on OpenAI and Azure (Responses API), Anthropic, Gemini, Groq, Bedrock and
Mistral, each with what the provider offers.
Streaming
for await (const chunk of llm.stream("Count to 5.")) process.stdout.write(chunk);
for await (const ev of llm.streamComplete("A fun fact about Recife.")) {
if (ev.type === "text") process.stdout.write(ev.text);
else console.log(ev.completion.cost); // full response: toolCalls, usage, cost
}Vision and files
import { readImage } from "jangada-ai/node";
await llm.complete("Describe the image.", { images: [await readImage("photo.jpg")] });
await llm.complete("And this one?", { images: ["https://example.com/photo.png"] });In the core (edge), pass Image.fromBytes(...), a data URI or a URL.
Agents
import { Agent, Squad, tool } from "jangada-ai";
const support = new Agent(llm, { role: "Support", tools: [weather] });
const r = await support.run("What's the weather in Recife?");
console.log(r.text, r.cost, r.toolTrace);
const researcher = new Agent(llm, { role: "Researcher", goal: "list short facts" });
const writer = new Agent(llm, { role: "Writer", goal: "write one sentence" });
await new Squad([researcher, writer]).run("jangadas in Ceará");Flow and Graph are there too. In Graph, nodes are linked with connect()
(Python uses then(), which in JS would make the object "thenable").
MCP
import { MCPClient, runAgent } from "jangada-ai/mcp";
import { serveMcp } from "jangada-ai/node";
// serve jangada tools as an MCP server
const srv = await serveMcp([weather], { transport: "http", port: 3333 });
// consume an MCP server
await using mcp = await new MCPClient({ url: "https://my-mcp/mcp" }).connect();
const r = await runAgent(llm, "Use the server tools.", { client: mcp });await using closes the connection at the end of the block. connect() is what
opens it.
RAG
import { RAG, vectorStore } from "jangada-ai/rag";
const store = vectorStore(process.env.DATABASE_URL); // postgresql://, mongodb:// or "memory"
const rag = new RAG(new LLM("openai", "text-embedding-3-small"), store, { chat: llm });
await rag.syncTexts(["The bakery opens at 6am.", "We accept PIX."], { documentId: "faq" });
const answer = await rag.ask("How can I pay?");The Postgres and MongoDB schema is the same as the Python library's, including the chunk hash and the incremental indexing lock. Python and JS can share the same database.
A2A and eval
import { serveA2a } from "jangada-ai/node";
await serveA2a(support, { port: 4000 }); // Agent Card at /.well-known/agent-card.json
import { Dataset, Evaluator, evaluate } from "jangada-ai/eval";
const res = await evaluate(dataset, (ex) => llm.complete(ex.inputs.q), [
Evaluator.fn("exact", (out, ref) => out.text.trim() === ref),
]);On edge, use buildA2aHandler from jangada-ai/a2a: it returns a
(Request) => Promise<Response> handler.
Differences from Python
| Topic | Python | JavaScript / TypeScript |
|---|---|---|
| Async | complete and acomplete | only complete (Promise) |
| Names | max_tokens, tool_calls | maxTokens, toolCalls |
| Template variables | complete(prompt, name="Ana") | complete(prompt, { variables: { name: "Ana" } }) |
| Structured output | Pydantic class | Zod, Standard Schema or JSON Schema |
| Tools | a Python function becomes a tool from its signature | tool({ name, parameters, execute }) |
.env | loaded on import jangada_ai | loaded on import "jangada-ai/node" |
| Cancellation | not available | signal on every call |
| Backoff, TTL and polling | seconds | milliseconds (backoffBaseMs, pollIntervalMs) |
Graph | then() | connect() |
| A2A | Starlette app | Request => Response handler (buildA2aHandler) or serveA2a |
| Files from disk | Document("file.pdf") | readDocument("file.pdf") from jangada-ai/node |
What ships in the package
| Import | What it has |
|---|---|
jangada-ai | LLM (complete, parse, stream, streamComplete, embed, transcribe), templates, retry and fallback, normalized errors, per-response cost, profiles, tools and native tools, vision and documents, exact and semantic cache, guardrails, Flow, Graph, Agent, Squad, stepBack, plan, detectObjects, GeminiInteractions, observability and prompt registry |
jangada-ai/node | file and .env reading, runInSession, serveMcp, serveA2a |
jangada-ai/mcp | MCPClient, runAgent, MCP server and Code Mode |
jangada-ai/rag | RAG, chunking (semantic too), rerank, in-memory, pgvector and MongoDB stores |
jangada-ai/eval | Dataset, Evaluator, evaluate |
jangada-ai/prebuilt | ready tools: calculator, CPF/CNPJ, CEP, Wikipedia, web search, weather, HTTP |
jangada-ai/a2a | web-standard A2A server |
Providers: OpenAI, Azure OpenAI, Groq, OpenRouter, DeepSeek, Anthropic, Gemini, Vertex, Bedrock, Mistral and Ollama. The assumed differences from Python are in the table above.
Getting started with jangada
jangada is a thin layer over the official LLM SDKs (Anthropic, OpenAI, Groq, Gemini). The goal is to swap provider / model / api_key without changing the rest of your code.
Generation parameters and per-model profiles
jangada accepts canonical parameter names and each adapter translates them to the SDK's native name, discarding the unsupported ones.