Jangada AIJangada AI

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-ai

Provider 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.

ProviderInstall
openai, azure, groq, openrouter, deepseeknpm install openai
anthropicnpm install @anthropic-ai/sdk
gemini, vertex, GeminiInteractionsnpm install @google/genai (Interactions needs 1.33+)
bedrocknpm install @aws-sdk/client-bedrock-runtime
mistralnpm install @mistralai/mistralai
ollamanothing (talks HTTP straight to the server)
structured output with Zodnpm install zod
PDF, DOCX and XLSX in the promptnpm 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 / MongoDBnpm 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

TopicPythonJavaScript / TypeScript
Asynccomplete and acompleteonly complete (Promise)
Namesmax_tokens, tool_callsmaxTokens, toolCalls
Template variablescomplete(prompt, name="Ana")complete(prompt, { variables: { name: "Ana" } })
Structured outputPydantic classZod, Standard Schema or JSON Schema
Toolsa Python function becomes a tool from its signaturetool({ name, parameters, execute })
.envloaded on import jangada_ailoaded on import "jangada-ai/node"
Cancellationnot availablesignal on every call
Backoff, TTL and pollingsecondsmilliseconds (backoffBaseMs, pollIntervalMs)
Graphthen()connect()
A2AStarlette appRequest => Response handler (buildA2aHandler) or serveA2a
Files from diskDocument("file.pdf")readDocument("file.pdf") from jangada-ai/node

What ships in the package

ImportWhat it has
jangada-aiLLM (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/nodefile and .env reading, runInSession, serveMcp, serveA2a
jangada-ai/mcpMCPClient, runAgent, MCP server and Code Mode
jangada-ai/ragRAG, chunking (semantic too), rerank, in-memory, pgvector and MongoDB stores
jangada-ai/evalDataset, Evaluator, evaluate
jangada-ai/prebuiltready tools: calculator, CPF/CNPJ, CEP, Wikipedia, web search, weather, HTTP
jangada-ai/a2aweb-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.

On this page