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

@niscorp/signal

v0.1.3

Published

Universal LLM abstraction — stateless, immutable, provider-agnostic

Readme

@niscorp/signal

Universal LLM abstraction. Stateless, immutable, provider-agnostic. Structured output with Zod, tool calling, zero hard dependencies.

Install

pnpm add @niscorp/signal zod
# Plus the provider SDK you want to use:
pnpm add openai  # for OpenAI, Groq, OpenRouter, or any OpenAI-compatible API

Quick Example

import { createSignal } from '@niscorp/signal';
import { z } from 'zod';

const signal = createSignal('groq');

// Simple text completion
const { response } = await signal.complete('What is 2+2?');

// Structured output — response is typed
const { response: user } = await signal
  .schema(z.object({ name: z.string(), age: z.number() }))
  .complete('Extract: Alice is 30 years old');
// user.name === 'Alice', user.age === 30

Documentation

  • DOCS.md — Full API reference with examples
  • DESIGN.md — Architecture, design decisions, and trade-offs

API

Every builder method returns a new immutable instance. complete() and stream() run the full Signal pipeline (schema, retries, tool loop). step() and stepStream() are the low-level primitives — one adapter call, no auto tool execution — used by orchestrators like @niscorp/cortex that own their own tool loop.

// Create
const signal = createSignal('groq');               // known provider
const signal = createSignal('groq', { apiKey, model, systemPrompt, retries });

// Configure (each returns a new instance)
signal.model('qwen/qwen3.8-27b')
signal.systemPrompt('You are helpful.')
signal.schema(zodSchema)           // typed structured output
signal.tools([myTool])             // tool calling
signal.history(messages)           // multi-turn
signal.retries(3)                  // validation retries
signal.options({ temperature: 0 }) // sampling / generation options
signal.apiKey(key)                 // override the env key
signal.describe()                  // provider, model, capabilities — from the registry
signal.onRetry(handler)            // retry hook
signal.onToolCall(handler)         // tool call hook

// Execute — high level
const { response, history, meta } = await signal.complete('user message');

// Execute — streaming
for await (const event of signal.stream('user message')) {
  if (event.type === 'text') process.stdout.write(event.text);
  if (event.type === 'done') console.log(event.meta.usage);
}

// Execute — low level (single adapter call, no tool execution)
const { content, toolCalls, usage, finishReason } = await signal.step({
  messages: [...], tools: [{ name, description, parameters }],
});

// Execute — streaming low level (symmetric with step())
for await (const event of signal.stepStream({ messages, tools })) {
  if (event.type === 'text') process.stdout.write(event.text);
  if (event.type === 'done') {
    // event.result is the aggregated StepResult — same shape as step()
  }
}

// Execute — embedding (separate client, embedding model)
const embedder = createSignal('openai').model('text-embedding-3-small');
const vector = await embedder.embed('wireless headphones');             // number[]
const vectors = await embedder.embed(['shoes', 'boots', 'hat']);       // number[][]
const small = await embedder.embed('text', { dimensions: 256 });       // truncated

// Execute — decisions (typed questions about a state; no text is generated)
const jev = createSignal('typesafe');
const { decisions } = await jev.decide({
  state: { message: 'I was charged twice' },
  questions: {
    intent: { type: 'choice', instructions: 'What do they want?', criteria: { refund: 'Money back', other: 'Anything else' } },
    needsHuman: { type: 'noul', instructions: 'Does this need a person?' },
  },
});
decisions.intent.choice;                                               // 'refund' | 'other'

Providers

| Provider | String | Kind | SDK | Embedding | |----------|--------|------|-----|-----------| | Groq | 'groq' | chat | openai | No | | OpenAI | 'openai' | chat | openai | Yes | | OpenRouter | 'openrouter' | chat | openai | No | | Anthropic | 'anthropic' | chat | stub (use OpenRouter) | No | | Google | 'google' | chat | stub (use OpenRouter) | No | | TypeSafe (Jev) | 'typesafe' | decisions | none — one fetch | — |

A chat provider has every verb; decide() runs there by emulation, uncalibrated. A decision provider has decide() only.

API keys are read from environment variables (GROQ_API_KEY, OPENAI_API_KEY, TYPESAFE_API_KEY, etc.) or passed via .apiKey() / options.

License

Apache-2.0