npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hanzo/ai

v0.6.0

Published

TypeScript client for the Hanzo AI backend — agents, machines, chat completions, Anthropic messages, models, embeddings, speech, images, answer-engine search/deep-research, and portable chat/message threads. Headless root; optional React hooks at @hanzo/a

Readme

@hanzo/ai

The TypeScript SDK for the Hanzo AI Cloud — agents, models, chat, messages, embeddings, speech, images, portable threads, and router feedback in one headless client.

Pure client + types. No UI, no DOM dependencies beyond the standard fetch / ReadableStream web APIs, and no React in the package root. This is the shared API layer behind hanzo.chat, hanzo.app, the Hanzo desktop app, and the hanzo-dev CLI. Components live in @hanzo/ui; the React hooks that connect them to this client are an optional @hanzo/ai/react entry.

It is the first-party TypeScript surface for the Hanzo AI Cloud (api.hanzo.ai):

  • Agents/v1/agents, the one agent store every Hanzo surface reads and writes: create, configure, list, run, and read the run history.
  • Machines/v1/machines, the org's compute. An agent bound to one is a bot.
  • Chat completions/v1/chat/completions, streaming and not.
  • Messages/v1/messages, streaming and not.
  • Models — the catalog at /v1/models.
  • Embeddings/v1/embeddings, a vector for one text or many.
  • Speech/v1/audio/speech and /v1/audio/transcriptions, both directions.
  • Images/v1/images/generations.
  • Portable chat threads — the cross-surface conversation store (chats + their messages), so a conversation started on one surface resumes on another.
  • Account — the signed-in IAM identity.

Every shape here is checked against the contract the backend publishes: pnpm check:spec reads the live OpenAPI at api.hanzo.ai/v1/openapi.json and fails on any drift, so the types have one home and it is the server's.

Request and response shapes are the familiar chat / messages formats, so bringing existing code across is mechanical.

Install

npm install @hanzo/ai

Usage

Auth is a Hanzo IAM access token (e.g. from @hanzo/iam) sent as Authorization: Bearer <token>.

In a signed-in app, hand it IAM and nothing else — @hanzo/iam owns every credential step, and the client takes a fresh token per request (refreshing when one expires), so token plumbing is never re-implemented per app:

import { createAiClient } from "@hanzo/ai";
import { IAM } from "@hanzo/iam";

const iam = new IAM({ serverUrl: "https://hanzo.id", clientId: "my-app" });
const ai = createAiClient({ auth: iam }); // baseUrl defaults to https://api.hanzo.ai

A signed-out session throws AuthError instead of sending an unauthenticated request. For a server or a test holding a raw bearer (an hk- key, say), pass it directly — and if both are given, IAM wins while it has a session:

const ai = createAiClient({
  token: process.env.HANZO_TOKEN!,
  // or, for rotating tokens:
  // getToken: async () => session.accessToken,
});

Chat completions

const res = await ai.chat.completions.create({
  model: "enso",
  messages: [{ role: "user", content: "Explain MoE routing in one sentence." }],
});
console.log(res.choices[0].message.content);

Streaming:

const stream = await ai.chat.completions.create({
  model: "enso",
  messages: [{ role: "user", content: "Write a haiku about Go." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta.content ?? "");
}

Tool calling works by passing tools / tool_choice.

Messages

const msg = await ai.messages.create({
  model: "enso",
  max_tokens: 512,
  system: "You are concise.",
  messages: [{ role: "user", content: "Summarize the CAP theorem." }],
});

// Streaming yields SSE events:
const events = await ai.messages.create({ /* ...,*/ stream: true } as any);
for await (const ev of events) {
  if (ev.type === "content_block_delta") { /* ev.delta.text */ }
}

Models

const models = await ai.models.list(); // Model[]

Model names such as enso are Zen models — the Hanzo family. You address them directly; the cloud routes each request to the right expert.

Embeddings

import { embed, embedMany } from "@hanzo/ai";

const vector = await embed(ai, "the cat sat", { model: "zen-embed" });
const vectors = await embedMany(ai, ["the cat sat", "on the mat"], { model: "zen-embed" });

embedMany answers the vectors in the order the texts were asked, not the order the provider sent them: each row of the wire carries the input it belongs to, and pairing by array position is silently wrong whenever an upstream reorders.

Speech

import { speak, transcribe } from "@hanzo/ai";

const { audio, type } = await speak(ai, "hello there", { model: "zen-voice", voice: "alloy" });
const { text } = await transcribe(ai, file, { model: "whisper-1" });

speak answers the bytes and the media type the server labelled them with — a provider that cannot make the container you asked for substitutes its own, so the response is what a player must be told, never the request. transcribe takes a Blob/File or raw bytes, and asking for timestamps ("word" / "segment") selects the verbose body those timings ride in.

Images

import { generateImages } from "@hanzo/ai";

const [image] = await generateImages(ai, "a red bicycle", { model: "zen3-image" });
image.url; // or image.b64_json, per what the upstream answered

n is clamped to [1,10] by the server.

Agents

/v1/agents is the one agent store. Every Hanzo surface — chat, app, console, playground, desktop, the CLI — reads and writes these records; none keeps its own. An agent is a model, a system prompt and a set of tool names, scoped to your org by the token.

const agent = await ai.agents.create({
  name: "helper",
  model: "enso-flash",
  instructions: "Answer in exactly one short sentence.",
});

const run = await ai.agents.run("helper", "What is 2+2?");
run.status; // "ok" — or "error", with run.error saying why
run.output; // "2+2 equals 4."

await ai.agents.list();               // Agent[]
await ai.agents.get("helper");        // AgentDetail — prompt + recent runs
await ai.agents.update("helper", { instructions: "be terse" });
await ai.agents.delete("helper");

A ref is the agent_… id or the org-unique name — either resolves the same agent. A run that fails resolves as a run carrying status: "error"; read the status rather than assuming output.

The dashboard reads

Three reads and a fold. Enough that a surface renders an agents dashboard without writing a client or a rollup of its own.

await ai.agents.runs();                              // org-wide feed, newest first
await ai.agents.runs({ limit: 50, status: "error" }); // just the failures
await ai.agents.runs({ agent: "helper", limit: 20 }); // one agent's runs

await ai.agents.metrics({ range: "7D" }); // invocation histogram + resource rollup
await ai.agents.activity();               // the org's activity feed

status and agent cannot be combined, and that is a type error rather than a runtime one: the org-wide feed filters on status, the per-agent route does not (measured — it answers unfiltered), so the pair that would silently return the wrong rows is unrepresentable.

The folds are pure functions over records you already hold — no client, no network:

import { agentStats, runStats, healthBreakdown, deriveActivity } from "@hanzo/ai";

healthBreakdown(agents); // { active: 7, idle: 0, error: 0, draft: 0 }
agentStats(agents);      // …plus total and summed run count
runStats(runs);          // { total, ok, failed, successRate, avgLatencyMs, …tokens }

Two rollups, not one, on purpose. An agent row carries a status and a run count, so agentStats answers what your agents are; success rate, latency and tokens are recorded on runs, so they come from runStats. Every field is null when nothing measured it — never a zero, because "unmeasured" and "zero" are different facts and a dashboard must be able to tell them apart.

deriveActivity(agents) builds a thinner feed from the agents' own timestamps, for a surface that cannot reach activity().

Machines

An agent bound to a machine by computeRef is what a bot is. The list is every machine your org has, provisioned or dialed in via hanzo link.

const machines = await ai.machines.list(); // Machine[]

React

@hanzo/ai/react is the agent-authoring surface every platform mounts. It is hooks and a provider — no components, no DOM — so the same code runs in a web app, in the desktop webview and in React Native. Bring your own components from @hanzo/ui. react is an optional peer dependency; importing the package root pulls in none of it.

import { AiProvider, useAgents, useAgent } from "@hanzo/ai/react";

<AiProvider auth={iam}>
  <Agents />
</AiProvider>;

function Agents() {
  const { agents, loading, create, remove } = useAgents();
  const { agent, runs, run, running } = useAgent(agents[0]?.name ?? null);
  // …render with @hanzo/ui
}

useModels() and useMachines() supply the choices an authoring form offers for model and computeRef.

Portable chat threads

A Chat is a durable thread keyed by "owner/name"; its turns are Messages. This is the store that lets a conversation move between surfaces.

await ai.chats.create({ owner: "hanzo", name: "thread-1", user: "alice", type: "AI" });

await ai.chats.messages.create({
  owner: "hanzo",
  name: "msg-1",
  chat: "thread-1",
  author: "alice",
  text: "Hello",
});

const thread = await ai.chats.get("hanzo/thread-1");
const turns = await ai.chats.messages.list({ chat: "thread-1" });

Account

const me = await ai.account.get();

Feedback (content-free reward signals)

sendFeedback emits a router-training signal keyed ONLY on the response id (the chatcmpl-… / msg_… the routing ledger keys on). It is content-free by construction — the payload can only ever be { request_id, signal, rating? }; no prompt, response, tag, filename or code can transit it. It is fire-and-forget: it never throws, never blocks UX, and is a silent no-op on any failure. This is the single implementation shared by hanzo.chat, hanzo.app and world.hanzo.ai.

import { sendFeedback } from "@hanzo/ai";

// Cross-origin to api.hanzo.ai, with a bearer for per-org attribution:
sendFeedback({ requestId: "chatcmpl-abc", signal: "up" }, { token });

// A 1–3 rating — the ONLY signal that carries a value:
sendFeedback({ requestId: "chatcmpl-abc", signal: "rating", rating: 3 }, { token });

// Same-origin BFF proxy (cookie auth) — prefers navigator.sendBeacon:
sendFeedback({ requestId: id, signal: "regenerate" }, { baseUrl: "" });

Signals: up · down · regenerate · switch · abandon · accept · revert · rating · dismiss. The input is a discriminated union, so it is a type error to attach a rating to any non-rating signal (dismiss included) and a rating value is required for signal: "rating".

Options: baseUrl (absolute = api.hanzo.ai, "" = same-origin BFF), token (static or lazy — forces fetch since a beacon can't set headers), credentials (default "include"), disabled (config opt-out; also honors a HANZO_FEEDBACK=0 env var), fetch, sendBeacon, dedupe (default true).

Errors

All failures throw a subclass of HanzoAIError:

  • APIError — non-2xx HTTP, or a CRUD envelope with status: "error". Carries .status and .body.
  • AuthError — no token was available for an authenticated request.
import { APIError } from "@hanzo/ai";

try {
  await ai.models.list();
} catch (err) {
  if (err instanceof APIError) console.error(err.status, err.message);
}

Advanced

createAiClient accepts:

| option | type | default | | ---------- | -------------------------------------- | ------------------------ | | baseUrl | string | https://api.hanzo.ai | | auth | IamAuth (the @hanzo/iam SDK) | — | | token | string | — | | getToken | () => string \| Promise<string> | — | | fetch | typeof fetch | globalThis.fetch | | headers | Record<string, string> | {} |

The low-level transport (client.http) and the SSE helpers (parseSSE, streamChatCompletion, streamMessage) are exported for custom calls.

License

Apache-2.0

Hanzo — the Open AI Cloud

Open source · every language · on-chain settlement. hanzo.ai · docs.hanzo.ai

SDKs in every languagePython (flagship) · TypeScript · Go · Rust · C++ · Swift · Kotlin · umbrella