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

@genezio/signal

v0.1.1

Published

Node.js SDK for the Signal LLM Infrastructure Platform

Readme

@genezio/signal

Node.js SDK for the Signal LLM Infrastructure Platform.

Installation

npm install @genezio/signal

Quick Start

import { Signal } from '@genezio/signal';

const signal = new Signal({ apiKey: 'sk-sig-...' });

// Execute a prompt
const result = await signal.ask('prompt-id', { variables: { name: 'world' } });
console.log(result.answer);

// Stream a response
for await (const chunk of await signal.ask('prompt-id', { variables: { name: 'world' }, stream: true })) {
  if (chunk.type === 'delta') process.stdout.write(chunk.content ?? '');
  if (chunk.type === 'done') console.log('\nDone:', chunk.answer);
}

Configuration

const signal = new Signal({
  apiKey: 'sk-sig-...',              // Required — your Signal API key
  baseUrl: 'https://custom.url',     // Optional — defaults to https://signal.backend.genezio.ai
  cacheTtl: 10,                      // Optional — cache TTL in seconds (default: 10)
});

API Reference

signal.ask(promptId, options?)

Execute a prompt by ID. Returns the LLM response or a stream of chunks.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | promptId | string | required | The prompt ID to execute. | | options.variables | Record<string, string> | optional | Template variables ({{name}} placeholders). | | options.tag | string | optional | Version tag (defaults to "production"). | | options.labels | string[] | optional | Labels to attach to the interaction (max 50). | | options.stream | boolean | optional | Enable streaming (default: false). | | options.mcpCredentials | Record<string, Record<string, string>> | optional | Per-request MCP server header overrides. Keys are MCP server names (as configured in the Signal UI). Values are objects mapping header names to their values — these merge with/override the headers stored in the server definition. Example: { "github": { "Authorization": "Bearer ghp_..." } } |

Non-streaming:

const result = await signal.ask('prompt-id', {
  variables: { city: 'Paris' },
  tag: 'production',
  labels: ['user-query'],
});
// result: { answer: string, responseId: string | null }

Streaming:

const stream = await signal.ask('prompt-id', {
  variables: { city: 'Paris' },
  stream: true,
});

for await (const chunk of stream) {
  switch (chunk.type) {
    case 'delta':      // chunk.content — text fragment
    case 'tool_call':  // chunk.toolCall — { server, tool, arguments }
    case 'tool_result':// chunk.toolResult — { server, tool, success, latencyMs }
    case 'done':       // chunk.answer, chunk.responseId, chunk.usage
    case 'error':      // chunk.error — error message
  }
}

signal.chat(options)

Execute a raw chat completion without a prompt template.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | options.provider | string | optional | LLM provider (e.g. "openai", "anthropic"). Required unless promptId is specified (inherited from prompt). | | options.model | string | optional | Model identifier (e.g. "gpt-4o"). Required unless promptId is specified (inherited from prompt). | | options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type. Required for providers with multiple types (e.g. OpenAI). Auto-detected when the provider supports only one. Inherited from prompt when promptId is specified. | | options.systemMessage | string | optional | System message (max 100k chars). At least one of systemMessage or userMessage is required. | | options.userMessage | string | optional | User message (max 100k chars). At least one of systemMessage or userMessage is required. | | options.temperature | number | optional | Temperature (0–2). | | options.labels | string[] | optional | Labels to attach. | | options.tags | string[] | optional | Version tags. | | options.promptId | string | optional | Associate with a prompt ID. | | options.previousResponseId | string | optional | Previous response ID for multi-turn. | | options.conversationId | string | optional | Conversation ID. | | options.webSearch | boolean | optional | Enable web search (default: false). | | options.stream | boolean | optional | Enable streaming (default: false). | | options.mcpServers | string[] | optional | MCP server names to use. |

// Non-streaming
const result = await signal.chat({
  provider: 'openai',
  model: 'gpt-4o',
  apiType: 'chat',
  systemMessage: 'You are a helpful assistant.',
  userMessage: 'What is 2+2?',
});
// result: { answer: string, responseId: string | null }

// Streaming
const stream = await signal.chat({
  provider: 'anthropic',
  model: 'claude-sonnet-4-20250514',
  systemMessage: 'You are a helpful assistant.',
  userMessage: 'Write a poem.',
  stream: true,
});
for await (const chunk of stream) {
  if (chunk.type === 'delta') process.stdout.write(chunk.content ?? '');
}

Prompts

signal.prompts.get(promptId, options?)

Get a prompt's configuration. Defaults to the "production" tag if none specified.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | promptId | string | required | The prompt ID. | | options.tag | string | optional | Version tag (defaults to "production"). |

const prompt = await signal.prompts.get('prompt-id');
// prompt: { promptId, promptName, provider, model, apiType, temperature,
//           webSearch, responseFormat, tags, systemTemplate, userTemplate,
//           mcpServers, toolChoice, maxToolRounds, cacheTtl }

// Same struct with a different tag
const staging = await signal.prompts.get('prompt-id', { tag: 'staging' });

signal.prompts.resolve(promptId, options?)

Resolve a prompt's template without executing it. Returns the same shape as get() but with systemMessage/userMessage (rendered) instead of systemTemplate/userTemplate.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | options.variables | Record<string, string> | optional | Template variables. | | options.tag | string | optional | Version tag (defaults to "production"). |

const resolved = await signal.prompts.resolve('prompt-id', {
  variables: { name: 'world' },
  tag: 'production',
});
// resolved: { promptId, promptName, provider, model, apiType, temperature,
//             webSearch, responseFormat, tags, systemMessage, userMessage,
//             cacheTtl }

Prompt Versions

signal.prompts.versions.create(promptId, options)

Create a new version.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | options.provider | string | optional | LLM provider. Inherited from latest version if omitted. | | options.model | string | optional | Model. Inherited from latest version if omitted. | | options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type. Required for providers with multiple types (e.g. OpenAI). Auto-detected when the provider supports only one. | | options.systemTemplate | string | optional | System template with {{var}} placeholders. | | options.userTemplate | string | optional | User template. | | options.temperature | number | optional | Temperature (0–2). Inherited from latest version if omitted. | | options.webSearch | boolean | optional | Enable web search. Inherited from latest version if omitted. | | options.tags | string[] | optional | Tags (auto-moved from other versions). | | options.mcpServers | string[] | optional | MCP servers (max 20). Inherited from latest version if omitted. | | options.toolChoice | 'auto' \| 'required' \| 'none' | optional | Tool choice. Inherited from latest version if omitted. | | options.maxToolRounds | number | optional | Max tool rounds (1–50). Inherited from latest version if omitted. |

const version = await signal.prompts.versions.create('prompt-id', {
  provider: 'openai',
  model: 'gpt-4o',
  apiType: 'chat',
  systemTemplate: 'You are a helpful assistant for {{company}}.',
  userTemplate: '{{question}}',
  tags: ['production'],
});

Interactions

signal.interactions.log(options)

Log an LLM interaction.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | options.conversationId | string | optional | Conversation ID. | | options.interactionId | string | optional | Custom ID (auto-generated if omitted). | | options.responseId | string | optional | Response ID. | | options.previousResponseId | string | optional | Previous response ID. | | options.provider | string | required with model | LLM provider (e.g. "openai"). | | options.model | string | optional | Model used. | | options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type used. | | options.messages | ChatMessage[] | required | Conversation messages. | | options.output | string | optional | Final output text. | | options.tokensInput | number | optional | Input token count. | | options.tokensOutput | number | optional | Output token count. | | options.latencyMs | number | optional | Latency in milliseconds. | | options.promptId | string | optional | Associated prompt ID. | | options.tags | string[] | optional | Version tags (e.g. ["production"]). | | options.labels | string[] | optional | Labels (max 50). | | options.metadata | Record<string, unknown> | optional | Custom metadata. |

const result = await signal.interactions.log({
  messages: [
    { role: 'system', content: 'You are helpful.' },
    { role: 'user', content: 'Hi!' },
    { role: 'assistant', content: 'Hello!' },
  ],
  provider: 'openai',
  model: 'gpt-4o',
  tokensInput: 50,
  tokensOutput: 10,
});
// result: { success: true, interactionId: '...' }

signal.interactions.appendMessages(interactionId, messages)

Append messages to an existing interaction.

await signal.interactions.appendMessages('interaction-id', [
  { role: 'user', content: 'Follow-up question' },
  { role: 'assistant', content: 'Follow-up answer' },
]);

signal.interactions.setOutput(interactionId, options)

Set or update the final output and token counts.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | options.output | string | required | Final output text. | | options.tokensInput | number | optional | Input tokens. | | options.tokensOutput | number | optional | Output tokens. | | options.latencyMs | number | optional | Latency in ms. |

await signal.interactions.setOutput('interaction-id', {
  output: 'Final answer.',
  tokensInput: 100,
  tokensOutput: 25,
  latencyMs: 450,
});

Feedback

signal.feedback.submit(responseId, options)

Submit or update feedback for a response.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | responseId | string | required | The response ID. | | options.score | number | required | Score (1–5). | | options.comment | string | optional | Comment. | | options.amendedAnswer | string | optional | Corrected answer. |

await signal.feedback.submit('response-id', {
  score: 5,
  comment: 'Perfect answer!',
});

signal.feedback.get(responseId)

Get feedback for a response.

const feedback = await signal.feedback.get('response-id');
// feedback: { responseId, score, comment, amendedAnswer, createdAt } | null

Using Signal for Prompt Management Only

If you want to manage prompts in Signal but call the LLM yourself (e.g. with the OpenAI SDK directly), use resolve() to get the rendered messages and then log the interaction back for tracking and feedback.

import { Signal } from '@genezio/signal';
import OpenAI from 'openai';

const signal = new Signal({ apiKey: 'sk-sig-...' });
const openai = new OpenAI({ apiKey: 'sk-...' });

// 1. Resolve the prompt — variables are filled in, config is returned
const resolved = await signal.prompts.resolve('prompt-id', {
  variables: { name: 'Alice', topic: 'weather' },
  tag: 'production',
});

// 2. Call the LLM yourself
const completion = await openai.chat.completions.create({
  model: resolved.model,
  temperature: resolved.temperature ?? undefined,
  messages: [
    { role: 'system', content: resolved.systemMessage },
    { role: 'user', content: resolved.userMessage },
  ],
});

const answer = completion.choices[0].message.content;

// 3. Log the interaction back to Signal for observability and feedback
await signal.interactions.log({
  messages: [
    { role: 'system', content: resolved.systemMessage },
    { role: 'user', content: resolved.userMessage },
    { role: 'assistant', content: answer },
  ],
  output: answer,
  provider: resolved.provider,
  model: resolved.model,
  promptId: resolved.promptId,
  tags: resolved.tags,
  tokensInput: completion.usage?.prompt_tokens,
  tokensOutput: completion.usage?.completion_tokens,
});

Automatic Logging with signal.wrap()

Wrap an OpenAI or Anthropic SDK client to automatically log every LLM call to Signal — no manual interactions.log() needed.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | client | object | required | An OpenAI or Anthropic SDK client instance. | | options.promptId | string | optional | Associate logged interactions with a prompt. | | options.tags | string[] | optional | Version tags. | | options.labels | string[] | optional | Labels. | | options.metadata | object | optional | Custom metadata. | | options.conversationId | string | optional | Conversation ID. |

import { Signal } from '@genezio/signal';
import OpenAI from 'openai';

const signal = new Signal({ apiKey: 'sk-sig-...' });
const openai = signal.wrap(new OpenAI({ apiKey: 'sk-...' }), {
  promptId: 'my-prompt',
  tags: ['production'],
});

// Works exactly like the normal OpenAI SDK — Signal logs it automatically
const completion = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'What is 2+2?' },
  ],
});
console.log(completion.choices[0].message.content);
// Signal automatically logs: messages, model, provider, tokens, latency

Supported clients: OpenAI (chat.completions.create) and Anthropic (messages.create). Streaming calls pass through without logging in v1.


Function Tracing with signal.trace()

Wrap any async function to automatically log its inputs, output, and latency.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | fn | function | required | The async function to wrap. | | options.promptId | string | optional | Associate with a prompt. | | options.tags | string[] | optional | Version tags. | | options.labels | string[] | optional | Labels. | | options.metadata | object | optional | Custom metadata. |

const answerQuestion = signal.trace(async (question: string) => {
  const completion = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{ role: 'user', content: question }],
  });
  return completion.choices[0].message.content;
}, { promptId: 'qa-prompt' });

const answer = await answerQuestion('What is the capital of France?');
// Signal logs: input args, output, latency, function name

Cache

The SDK caches GET requests and prompt resolve calls in memory with a configurable TTL (default: 10 seconds). Streaming responses and write operations are never cached.

// Invalidate cache for a specific prompt (resolve, get, versions)
signal.cache.invalidate('prompt-id');

// Clear the entire cache
signal.cache.clear();

Error Handling

All errors extend SignalError with status and code properties.

| Error Class | Status | When | |-------------|--------|------| | SignalValidationError | 400 | Invalid request body or parameters. | | SignalAuthError | 401 | Missing or invalid API key. | | SignalNotFoundError | 404 | Resource not found. | | SignalRateLimitError | 429 | Too many requests. | | SignalError | other | Any other server error. |

import { SignalNotFoundError } from '@genezio/signal';

try {
  await signal.prompts.get('nonexistent');
} catch (err) {
  if (err instanceof SignalNotFoundError) {
    console.log('Prompt not found');
  }
}

Types

All TypeScript types are exported from the package:

import type {
  SignalConfig,
  AskOptions, AskResponse,
  ChatOptions, ChatResponse,
  StreamChunk,
  PromptConfig, CreateVersionOptions,
  ResolveOptions, ResolvedPrompt,
  LogInteractionOptions, LogInteractionResponse,
  SetOutputOptions, ChatMessage, ToolCall,
  Feedback, SubmitFeedbackOptions,
} from '@genezio/signal';

License

MIT