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

@zhanla/sdk-ts

v0.3.14

Published

TypeScript SDK for the zhanla CLI — define and run AI components locally

Readme

@zhanla/sdk-ts

TypeScript SDK for defining Zhanla components in code.

Export component instances from a TypeScript module, then run or upload them with the main zhanla CLI.

Installation

npm install @zhanla/sdk-ts

Requires Node >=18.

Install a provider SDK only when your component makes LLM calls:

npm install @anthropic-ai/sdk
npm install openai
npm install @google/genai

Install the CLI separately:

pip install zhanla

The package ships an internal helper binary named zhanla-sdk-ts, but user-facing workflows should invoke zhanla.

Quick start

Create components.ts:

import { CodeEval, Tool } from "@zhanla/sdk-ts";

export const priorityTool = new Tool({
  name: "priority_tool",
  description: "Classify support ticket priority.",
  key: "priority-tool",
  inputSchema: {
    type: "object",
    properties: {
      message: { type: "string" },
      customerTier: { type: "string" },
    },
    required: ["message"],
  },
  fn: (kwargs) => {
    const { message, customerTier } = kwargs as {
      message?: string;
      customerTier?: string;
    };
    const urgent =
      message?.toLowerCase().includes("urgent") || customerTier === "enterprise";
    return { priority: urgent ? "high" : "normal" };
  },
  outputSchema: {
    type: "object",
    properties: {
      priority: { type: "string" },
    },
    required: ["priority"],
  },
});

export const priorityEval = new CodeEval({
  name: "priority_eval",
  description: "Check predicted priority.",
  key: "priority-eval",
  fn: ({ model_response, expected_output }) => {
    const response = model_response ? JSON.parse(model_response) : {};
    const expected = expected_output ? JSON.parse(expected_output) : {};
    return {
      score: response.priority === expected.priority ? 1.0 : 0.0,
    };
  },
});

Create tickets.json:

[
  { "_schema": { "message": "string", "expected_output": "object" } },
  { "message": "urgent outage", "expected_output": { "priority": "high" } }
]

Run locally:

zhanla validate components.ts
zhanla run components.ts:priority-tool --dataset tickets.json --eval components.ts:priority-eval --dry-run --yes

After zhanla login, drop --dry-run to sync definitions, dataset rows, and results to the web app.

Authoring rules

  • Export component instances at module scope.
  • Give every component an explicit stable key.
  • Keys must contain only lowercase letters, digits, and hyphens.
  • CLI target suffixes use key, not export variable name or display name.
  • Use JSON Schema objects for inputSchema and outputSchema.
  • Prompt-backed components need model plus exactly one of client or runner for local execution.
  • Skill is prompt-only configuration and cannot be run directly.

Public API

import {
  Tool,
  CodeEval,
  Skill,
  Agent,
  LLMProcessor,
  LLMEval,
  Runner,
  Orchestration,
  Step,
  Conditional,
  Checklist,
  EvalTree,
  Branch,
  Edge,
  Leaf,
  wrap,
  parseJsonResponse,
  runComponent,
  executeComponent,
  flush,
} from "@zhanla/sdk-ts";

Provider-backed components

Use client for normal provider usage:

import Anthropic from "@anthropic-ai/sdk";
import { Agent } from "@zhanla/sdk-ts";

export const supportAgent = new Agent({
  name: "support_agent",
  description: "Respond to support requests.",
  key: "support-agent",
  instructions: 'Answer clearly. Return JSON: {"answer": "..."}',
  model: "claude-sonnet-4-6",
  client: new Anthropic(),
  outputSchema: {
    type: "object",
    properties: { answer: { type: "string" } },
    required: ["answer"],
  },
  jsonRepair: true,
});

client creates a Runner internally. Use runner when you need custom runner behavior or want to share one runner:

import OpenAI from "openai";
import { Runner } from "@zhanla/sdk-ts";

const runner = new Runner({ client: new OpenAI() });

Google Gemini:

import { GoogleGenAI } from "@google/genai";

const geminiClient = new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY });

OpenRouter through the OpenAI-compatible client:

import OpenAI from "openai";

const openRouterRunner = new Runner({
  client: new OpenAI({
    baseURL: "https://openrouter.ai/api/v1",
    apiKey: process.env.OPENROUTER_API_KEY,
  }),
});

Components

Tool

Use a Tool for deterministic TypeScript logic.

export const lookupCustomer = new Tool({
  name: "lookup_customer",
  description: "Fetch a customer record.",
  key: "lookup-customer",
  inputSchema: {
    type: "object",
    properties: { customerId: { type: "string" } },
    required: ["customerId"],
  },
  fn: (kwargs) => {
    const { customerId } = kwargs as { customerId: string };
    return {
      id: customerId,
      email: "[email protected]",
    };
  },
  outputSchema: {
    type: "object",
    properties: {
      id: { type: "string" },
      email: { type: "string" },
    },
    required: ["id", "email"],
  },
});

fn can be sync or async. Local execution passes one kwargs object. Non-object returns are wrapped as { result: value }.

Skill

Use a Skill for reusable prompt instructions.

export const summarizeSkill = new Skill({
  name: "summarize_skill",
  description: "Reusable summarization instructions.",
  key: "summarize-skill",
  instructions: "Summarize the provided text in one short paragraph.",
  tools: [lookupCustomer],
});

Skills can be attached to agents or orchestrations, but they are not top-level local runtime targets.

Agent

Use an Agent for LLM-backed work with optional tools, skills, and nested agents.

export const supportAgent = new Agent({
  name: "support_agent",
  description: "Respond to support requests.",
  key: "support-agent",
  instructions: 'Answer clearly. Return JSON: {"answer": "..."}',
  model: "gpt-4.1",
  runner,
  tools: [lookupCustomer],
  skills: [summarizeSkill],
  outputSchema: {
    type: "object",
    properties: { answer: { type: "string" } },
    required: ["answer"],
  },
});

LLMProcessor

Use an LLMProcessor for one prompt-defined transformation.

export const intentClassifier = new LLMProcessor({
  name: "intent_classifier",
  description: "Classify intent.",
  key: "intent-classifier",
  instructions: 'Return JSON: {"intent": "billing|technical|other"}',
  model: "claude-sonnet-4-6",
  runner,
  outputSchema: {
    type: "object",
    properties: { intent: { type: "string" } },
    required: ["intent"],
  },
});

Orchestration

Use Step to compose components into a DAG.

export const supportPipeline = new Orchestration({
  name: "support_pipeline",
  description: "Classify intent, then reply.",
  key: "support-pipeline",
  steps: [
    new Step({ name: "classify", component: intentClassifier, next: ["reply"] }),
    new Step({ name: "reply", component: supportAgent }),
  ],
});

Use Conditional for routing:

new Step({
  name: "route",
  component: new Conditional({
    condition: (state) => state.intent === "billing",
    ifTrue: "billing_reply",
    ifFalse: "general_reply",
  }),
});

Evals

CodeEval

CodeEval functions receive one kwargs object:

type CodeEvalKwargs = {
  model_input?: string;
  model_response: string;
  expected_output?: string;
};

Correct:

fn: ({ model_response, expected_output }) => {
  const response = model_response ? JSON.parse(model_response) : {};
  const expected = expected_output ? JSON.parse(expected_output) : {};
  return { score: response.priority === expected.priority ? 1.0 : 0.0 };
}

Wrong:

fn: (modelResponse, expectedResponse) => ({ score: 1.0 })

The constructor rejects multi-parameter CodeEval functions. The fields are snake_case, not camelCase.

Non-object returns are wrapped as { score: value }.

modelResponseFormat defaults to "JSON" and can be set to "TEXT" or "YAML" for synced eval metadata. Current local and CLI-managed eval-only execution still passes strings to the function, so parse structured values yourself.

LLMEval

Use either instructions or questions, not both.

export const toneEval = new LLMEval({
  name: "tone_eval",
  description: "Evaluate tone.",
  key: "tone-eval",
  instructions: 'Return JSON: {"score": 0.0, "reason": "..."}',
  model: "gpt-4.1",
  runner,
  outputSchema: {
    type: "object",
    properties: {
      score: { type: "number" },
      reason: { type: "string" },
    },
    required: ["score", "reason"],
  },
});

Checklist and EvalTree

export const answerQuality = new Checklist({
  name: "answer_quality",
  description: "Combine evals.",
  key: "answer-quality",
  evals: [priorityEval, toneEval],
  weights: [0.8, 0.2],
});

CLI usage

zhanla validate workflow.ts
zhanla upload workflow.ts:support-pipeline
zhanla run workflow.ts:support-pipeline --dataset tickets.json --eval evals.ts:answer-quality --dry-run --yes
zhanla run workflow.ts:support-pipeline --dataset tickets.json --web-eval answer-quality --yes

Local JSON datasets must start with a _schema metadata row. CSV datasets use their header row as fields.

Programmatic execution

Run a component in your app with trajectory tracking:

import { runComponent } from "@zhanla/sdk-ts";

const output = await runComponent(supportAgent, { message: "Need help" });

Set ZHANLA_API_KEY="bm_kid_....bm_sec_..." to export production LLM trajectories. The exporter is fail-silent and batches in the background.

Use executeComponent(component, input) for bare dispatch without trajectory export. In serverless environments, call flush() before shutdown.

Observability helpers

Use wrap(client) when calling a provider client directly outside a runner-backed component:

import Anthropic from "@anthropic-ai/sdk";
import { wrap } from "@zhanla/sdk-ts";

const client = wrap(new Anthropic());

Runner wraps its client internally, so do not wrap the same client first.

Use parseJsonResponse(text) to parse bare JSON or fenced JSON from model output.

Utilities

Lower-level helpers:

import {
  collectExportedComponents,
  toManifest,
  executeComponent,
  executeRow,
  TraceContext,
  traceStorage,
} from "@zhanla/sdk-ts";

In normal CLI usage, you do not need these.

Common gotchas

  1. Components must be exported for discovery.
  2. Every component needs an explicit valid key.
  3. CLI target suffixes use key, not export name or display name.
  4. Skill cannot run directly.
  5. Prompt-backed local execution requires model plus client or runner, not both.
  6. Tool.inputSchema must be a JSON Schema object with type: "object" and properties.
  7. Tool should include outputSchema; the CLI requires one for validation/upload.
  8. CodeEval.fn must destructure a single kwargs object.
  9. LLMEval must provide exactly one of instructions or questions.
  10. Local JSON datasets must start with _schema; {"schema": ..., "rows": ...} is not accepted by the CLI.