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

@audacity/sdk

v0.5.1

Published

Audacity Investments SDK — Bedrock-compatible client for the Audacity LLM gateway

Readme

@audacity/sdk

TypeScript/JavaScript client for the Audacity LLM gateway — modelled after the AWS SDK v3 Bedrock Converse API so teams migrating off Bedrock can swap client construction and keep the rest of their code.

  • Zero runtime dependencies
  • Node ≥ 18 (global fetch + Web Streams)
  • Dual ESM + CJS build with full .d.ts types
  • Full type coverage for every shape in the spec

Installation

npm install @audacity/sdk
# or
bun add @audacity/sdk

Quickstart

Command-style (AWS SDK v3 parity)

import { AudacityRuntimeClient, ConverseCommand, ConverseStreamCommand } from "@audacity/sdk";

const client = new AudacityRuntimeClient({ apiKey: process.env.AUDACITY_API_KEY });

// Non-streaming
const response = await client.send(
  new ConverseCommand({
    modelId: "gpt-5.4-mini",
    messages: [{ role: "user", content: [{ text: "Hello!" }] }],
    inferenceConfig: { maxTokens: 500, temperature: 0.2 },
  })
);
console.log(response.output?.message?.content?.[0]?.text);
// response.stopReason, response.usage, response.metrics.latencyMs also populated

// Streaming
const { stream } = await client.send(
  new ConverseStreamCommand({
    modelId: "gpt-5.4-mini",
    messages: [{ role: "user", content: [{ text: "Tell me a story" }] }],
  })
);
for await (const event of stream) {
  if ("contentBlockDelta" in event) {
    const delta = event.contentBlockDelta.delta;
    if ("text" in delta) process.stdout.write(delta.text);
  }
}

Convenience class

import { Audacity } from "@audacity/sdk";

const client = new Audacity(); // reads AUDACITY_API_KEY from env

const response = await client.converse({
  modelId: "gpt-5.4-mini",
  messages: [{ role: "user", content: [{ text: "Hello!" }] }],
});

const { stream } = await client.converseStream({
  modelId: "gpt-5.4-mini",
  messages: [{ role: "user", content: [{ text: "Stream this" }] }],
});
for await (const event of stream) { /* ... */ }

Migrating from @aws-sdk/client-bedrock-runtime

Most code stays the same — only the import and client construction change.

-import {
-  BedrockRuntimeClient,
-  ConverseCommand,
-  ConverseStreamCommand,
-} from "@aws-sdk/client-bedrock-runtime";
+import {
+  AudacityRuntimeClient as BedrockRuntimeClient,
+  ConverseCommand,
+  ConverseStreamCommand,
+} from "@audacity/sdk";

-const client = new BedrockRuntimeClient({ region: "us-east-1" });
+const client = new BedrockRuntimeClient({ apiKey: process.env.AUDACITY_API_KEY });

 const response = await client.send(
   new ConverseCommand({
     modelId: "gpt-5.4-mini",
     messages: [{ role: "user", content: [{ text: "Hi" }] }],
   })
 );
 console.log(response.output?.message?.content?.[0]?.text);

 const { stream } = await client.send(new ConverseStreamCommand({ modelId: "gpt-5.4-mini", messages: [...] }));
 for await (const event of stream) {
   if (event.contentBlockDelta?.delta?.text) {
     process.stdout.write(event.contentBlockDelta.delta.text);
   }
 }

Images (vision models)

Bedrock-style image content blocks are supported in user messages. Pass raw bytes as a Uint8Array (base64-encoded for you) or a URL (Audacity extension):

import { readFile } from "node:fs/promises";

const imageBytes = new Uint8Array(await readFile("chart.png"));

const response = await client.send(
  new ConverseCommand({
    modelId: "gpt-5.5",
    messages: [
      {
        role: "user",
        content: [
          { text: "What does this chart show?" },
          { image: { format: "png", source: { bytes: imageBytes } } },
        ],
      },
    ],
  })
);

// Or reference a hosted image directly (not available in Bedrock):
// { image: { format: "jpeg", source: { url: "https://example.com/photo.jpg" } } }

format is one of png, jpeg, gif, webp. Use a vision-capable model.


Video (Gemini models)

Bedrock-style video content blocks are supported in user messages. Video input is only available on the Gemini family (gemini-2.5-flash, gemini-2.5-pro, gemini-3-flash-preview); every other model rejects video with an HTTP 400.

import { readFile } from "node:fs/promises";

const videoBytes = new Uint8Array(await readFile("demo.mp4"));

const response = await client.send(
  new ConverseCommand({
    modelId: "gemini-2.5-flash",
    messages: [
      {
        role: "user",
        content: [
          { text: "Summarize what happens in this video." },
          { video: { format: "mp4", source: { bytes: videoBytes } } },
        ],
      },
    ],
  })
);

format is one of mp4, mov, mkv, webm, flv, mpeg, mpg, wmv, three_gp. Inline (bytes) video is base64-encoded into the request — keep those files under ~20 MB.

For larger files (up to 1 GB), upload once and reference by URI:

const videoBytes = new Uint8Array(await readFile("large-demo.mp4"));

const upload = await client.files.upload({ data: videoBytes, contentType: "video/mp4" });

const response = await client.send(
  new ConverseCommand({
    modelId: "gemini-2.5-flash",
    messages: [
      {
        role: "user",
        content: [
          { text: "Summarize this video." },
          { video: { format: "mp4", source: { uri: upload.uri } } },
        ],
      },
    ],
  })
);

files.upload returns { fileId, uploadUrl, uri, expiresAt }. Uploads are resumable: the helper streams the file in 8 MB chunks over a GCS resumable session and automatically resumes from the last confirmed byte after a network drop (bounded retries). Uploaded files are transient inference inputs: they expire after ~24 hours and are scoped to your API key's organization; the signed upload URL itself is valid ~15 minutes.

To control video token cost, set mediaResolution on the request — "low" processes video at ~4x fewer tokens (Gemini models; ignored elsewhere):

await client.send(
  new ConverseCommand({
    modelId: "gemini-2.5-flash",
    mediaResolution: "low",
    messages: [/* … */],
  })
);

Image generation

Generate images from a text prompt with client.images.generate (available on both AudacityRuntimeClient and Audacity). With responseFormat: "b64_json" the image bytes come back inline:

import { writeFile } from "node:fs/promises";

const result = await client.images.generate({
  model: "gpt-image-1",
  prompt: "A watercolor painting of a fox in a snowy forest",
  size: "1024x1024",
  responseFormat: "b64_json",
});

await writeFile("fox.png", Buffer.from(result.data[0].b64Json!, "base64"));

With responseFormat: "url" (the default) the gateway stores the image and returns a signed download URL that expires after ~24 hours:

const result = await client.images.generate({
  model: "imagen-4",
  prompt: "A watercolor painting of a fox in a snowy forest",
});

console.log(result.data[0].url); // signed download URL, valid ~24 h

Optional parameters: n (1–10 images), size ("WxH", model-dependent), quality (e.g. "standard", "hd"), and user. The response carries created, data[] (each with url or b64Json, plus revisedPrompt when the provider rewrites your prompt) and optional usage token counts. Errors map to the same exception classes as Converse (401 → AccessDeniedException, 429 → ThrottlingException, spend cap → ServiceQuotaExceededException).

Image models

| Model | Pricing | |---|---| | imagen-4 | $0.04 / image | | imagen-4-fast | $0.02 / image | | imagen-4-ultra | $0.06 / image | | gemini-2.5-flash-image | token-based (≈ $0.039 / image) | | gpt-image-1 | token-based ($5.00 / 1M text input, $40.00 / 1M image output tokens) |

Per-image models bill a flat rate per generated image; token-based models report token counts in the response usage field. Each request's cost is recorded against your key like any other API call.

Reliability note. Upstream image backends occasionally stall with a 503 for a few minutes. There is deliberately no automatic fallback to a different image model (silently swapping models would change output style and quality) — the SDK already retries 503s with backoff up to maxRetries, and callers should retry beyond that rather than switch models.


Prompt caching

Place a Bedrock-style cachePoint block after the stable prefix you want the provider to cache (system prompt, large documents). Everything up to the cache point is cached provider-side on Claude models; OpenAI/Gemini models cache automatically and ignore the marker. At most 4 cache points per request.

const response = await client.send(
  new ConverseCommand({
    modelId: "claude-sonnet-4-5",
    system: [
      { text: longSystemPrompt },
      { cachePoint: { type: "default" } },
    ],
    messages: [
      {
        role: "user",
        content: [
          { text: bigReferenceDocument },
          { cachePoint: { type: "default" } },
          { text: "Summarise the key risks." },
        ],
      },
    ],
  })
);

// Cache activity is reported in usage (Bedrock names):
console.log(response.usage?.cacheReadInputTokens);  // tokens served from cache
console.log(response.usage?.cacheWriteInputTokens); // tokens written to cache

A cachePoint with nothing before it in the same message is silently ignored.


OpenAI & Anthropic wire formats (pass-through)

Prefer the OpenAI or Anthropic request shapes over Bedrock Converse? The same client exposes both gateway-native formats directly — same key, same retry policy, same exceptions, no shape translation (requests sent verbatim, responses returned raw). Both formats work with every gateway model; the gateway bridges the wire format for you. Available on both AudacityRuntimeClient and Audacity.

// OpenAI format → POST /v1/chat/completions
const response = await client.chat.completions.create({
  model: "gpt-5.4-mini",
  messages: [{ role: "user", content: "Hello!" }],
  max_tokens: 256,
});
console.log(response.choices[0]?.message?.content);

// Streaming: raw OpenAI chunks (stream: true switches the return type)
const stream = await client.chat.completions.create({
  model: "claude-sonnet-4-6", // any gateway model
  messages: [{ role: "user", content: "Write a haiku." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
}

// Anthropic format → POST /v1/messages (Claude Code's wire format)
const message = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 256,
  messages: [{ role: "user", content: "Hello!" }],
});
console.log(message.content[0]?.["text"]);

// Streaming: raw Anthropic events (message_start … message_stop)
const events = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 256,
  messages: [{ role: "user", content: "Write a haiku." }],
  stream: true,
});
for await (const event of events) {
  if (event.type === "content_block_delta") {
    process.stdout.write((event["delta"] as { text?: string }).text ?? "");
  }
}

// Free token counting → POST /v1/messages/count_tokens
const count = await client.messages.countTokens({
  model: "claude-sonnet-4-6",
  messages: [{ role: "user", content: "How many tokens is this?" }],
});
console.log(count.input_tokens);

Fields are passed through untouched, so anything the gateway supports works — no SDK release needed for new request fields. (Using the official openai or @anthropic-ai/sdk packages instead also works: point their baseURL at the gateway — see the developer docs.)


Error handling

import {
  AudacityRuntimeClient,
  ConverseCommand,
  AccessDeniedException,
  ThrottlingException,
  MissingApiKeyError,
  AudacityError,
} from "@audacity/sdk";

const client = new AudacityRuntimeClient({ apiKey: "aireserve_api_..." });

try {
  const res = await client.send(new ConverseCommand({ ... }));
} catch (err) {
  if (err instanceof MissingApiKeyError) {
    console.error("No API key configured");
  } else if (err instanceof AccessDeniedException) {
    console.error("Auth failed:", err.message, "requestId:", err.requestId);
  } else if (err instanceof ThrottlingException) {
    console.error("Rate limited. Retry after:", err.retryAfterSeconds, "s");
  } else if (err instanceof AudacityError) {
    // All SDK errors are instances of AudacityError
    console.error(err.name, err.statusCode, err.errorCode, err.rawBody);
  }
}

All error classes support instanceof checks. Every server-derived error carries:

| Field | Type | Description | |---|---|---| | message | string | Human-readable description | | name | string | Exception class name | | statusCode | number \| undefined | HTTP status | | errorCode | string \| undefined | Raw code/type string from the server | | requestId | string \| undefined | Request ID from the server (when available) | | retryAfterSeconds | number \| undefined | From Retry-After header | | rawBody | string \| undefined | Unparsed response body |


Configuration

| Option | Env variable | Default | |---|---|---| | apiKey | AUDACITY_API_KEY | — (required) | | baseUrl | AUDACITY_BASE_URL | https://api.audacityinvestments.com | | timeoutMs | — | 120000 (2 min) | | maxRetries | — | 2 (up to 3 total attempts) |

const client = new AudacityRuntimeClient({
  apiKey: "aireserve_api_...",
  baseUrl: "https://api.audacityinvestments.com",
  timeoutMs: 30_000,
  maxRetries: 3,
});

timeoutMs bounds each attempt's connect + headers and — for Converse — the full body read. For ConverseStream it applies only until headers arrive; the SSE stream itself may run indefinitely.

Cancellation

Every operation accepts an optional abortSignal (AWS SDK v3 parity), which also cancels an in-flight stream:

const aborter = new AbortController();
const { stream } = await client.send(
  new ConverseStreamCommand({ modelId, messages }),
  { abortSignal: aborter.signal },
);

License

Copyright Audacity Investments. All rights reserved. See LICENSE.