sonar-stack
v1.0.0
Published
Infinitely scalable AI inference platform SDK - unified client for 50+ models
Downloads
111
Maintainers
Readme
SonarStack SDK
Infinitely Scalable AI Inference Platform — One unified TypeScript client for 50+ AI models across all major providers.
Features
✨ Provider-Agnostic: Single API for 50+ models (OpenAI, Anthropic, Google, Groq, etc.)
🚀 Tree-Shakeable: ~5KB minified, ESM + CJS with full TypeScript types
⚡ Streaming: AsyncIterable streams with helper utilities
🔄 Resilience: Automatic retries with exponential backoff, timeout support
🛡️ Type-Safe: Full TypeScript support, zero dependencies
🎯 Great DX: Simple, intuitive API with strong error handling
📦 Production-Ready:
npm publishready, proper semver
Installation
npm install sonar-stack
# or
pnpm add sonar-stack
# or
yarn add sonar-stack
Quick Start
Basic Setup
import { SonarStack } from "sonar-stack";
const client = new SonarStack({
apiKey: process.env.SONARSTACK_API_KEY,
baseUrl: "https://api.sonarsdk.xyz", // optional, default shown
timeoutMs: 30000, // optional
retries: 3, // optional
});
List Available Models
const models = await client.models.list();
console.log(models);
// Output:
// [
// {
// id: "gpt-4",
// name: "GPT-4",
// provider: "openai",
// contextWindow: 8192,
// inputPricing: 0.03,
// outputPricing: 0.06,
// supportsStreaming: true,
// tags: ["chat", "reasoning"]
// },
// ...
// ]
Examples
Chat Completions
// Non-streaming
const response = await client.chat.completions.create({
model: "gpt-4",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is TypeScript?" },
],
temperature: 0.7,
maxTokens: 500,
});
console.log(response.choices[0].message.content);
Streaming Chat
// Streaming with AsyncIterable
const stream = await client.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "Tell me a story" }],
stream: true,
});
// Option 1: Manual iteration
for await (const event of stream) {
if (event.type === "message.delta") {
process.stdout.write(event.data?.delta || "");
} else if (event.type === "error") {
console.error("Stream error:", event.error?.message);
}
}
// Option 2: Collect entire stream into text
import { streamToText } from "sonar-stack/streaming";
const stream = await client.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "Hello!" }],
stream: true,
});
const fullText = await streamToText(stream);
console.log(fullText);
Embeddings
const response = await client.embeddings.create({
model: "text-embedding-3-small",
input: "The quick brown fox jumps over the lazy dog",
});
console.log(response.data[0].embedding);
// [0.123, -0.456, 0.789, ...]
Generic Responses (OpenAI-like)
const response = await client.responses.create({
model: "gpt-4",
input: "Generate a random UUID",
});
console.log(response.output);
Abort Signals & Timeouts
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await client.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "..." }],
signal: controller.signal,
});
} catch (error) {
if (error.name === "TimeoutError") {
console.log("Request timed out");
}
} finally {
clearTimeout(timeoutId);
}
Error Handling
import {
SonarStackError,
APIError,
AuthError,
RateLimitError,
TimeoutError,
} from "sonar-stack";
try {
await client.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (error instanceof AuthError) {
console.error("Invalid API key");
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after: ${error.retryAfter}s`);
} else if (error instanceof TimeoutError) {
console.error("Request timed out");
} else if (error instanceof APIError) {
console.error(`API Error [${error.status}]:`, error.message);
console.error("Request ID:", error.requestId);
} else if (error instanceof SonarStackError) {
console.error("SonarStack error:", error.message);
}
}
Configuration
Client Options
interface ClientConfig {
// Required
apiKey: string;
// Optional
baseUrl?: string; // Default: https://api.sonarsdk.xyz
timeoutMs?: number; // Default: 30000 (30 seconds)
retries?: number; // Default: 3
headers?: Record<string, string>; // Custom headers
// Hooks for observability (never logs apiKey)
onRequest?: (request: {
method: string;
url: string;
headers: Record<string, string>;
}) => void;
onResponse?: (response: {
status: number;
headers: Record<string, string>;
requestId?: string;
}) => void;
}
Example with Logging
const client = new SonarStack({
apiKey: process.env.SONARSTACK_API_KEY,
onRequest: (req) => {
console.log(`→ ${req.method} ${req.url}`);
},
onResponse: (res) => {
console.log(`← ${res.status} (ID: ${res.requestId})`);
},
});
API Reference
Models
// List all available models
const models: Model[] = await client.models.list();
Chat Completions
// Non-streaming
const response: ChatCompletionResponse = await client.chat.completions.create({
model: string;
messages: Message[];
temperature?: number;
maxTokens?: number;
signal?: AbortSignal;
});
// Streaming
const stream: AsyncIterable<StreamEvent> = await client.chat.completions.create({
model: string;
messages: Message[];
stream: true;
signal?: AbortSignal;
});
Embeddings
const response: EmbeddingsResponse = await client.embeddings.create({
model: string;
input: string | string[];
signal?: AbortSignal;
});
Generic Responses
const response: GenericResponseResponse | AsyncIterable<StreamEvent> =
await client.responses.create({
model: string;
input: string | Record<string, unknown>;
stream?: boolean;
signal?: AbortSignal;
});
Resilience & Retry Logic
The SDK automatically retries on:
429 (Rate Limit): Respects
Retry-Afterheader, exponential backoff5xx Errors: Exponential backoff with jitter
Network Errors: Exponential backoff
Configure retry behavior:
const client = new SonarStack({
apiKey: process.env.SONARSTACK_API_KEY,
retries: 5, // Max 5 retry attempts
timeoutMs: 60000, // 60 second timeout
});
Environment Variables
SONARSTACK_API_KEY=your_api_key_here
SONARSTACK_BASE_URL=https://api.sonarsdk.xyz # optional
TypeScript Types
All types are exported and ready to use:
import type {
Model,
Message,
ChatCompletionRequest,
ChatCompletionResponse,
EmbeddingsRequest,
EmbeddingsResponse,
GenericResponseRequest,
GenericResponseResponse,
StreamEvent,
ClientConfig,
} from "sonar-stack";
Building & Publishing
# Install dependencies
pnpm install
# Build (ESM + CJS, TypeScript types)
pnpm run build
# Type checking
pnpm run type-check
# Run tests
pnpm run test
# Linting & formatting
pnpm run lint
pnpm run format
# Publish to npm (runs build + type-check + test first)
pnpm run prepublishOnly
npm publish
Package Structure
sonar-stack/
│
├── dist/ # Compiled output (generated by tsup)
│ ├── index.mjs
│ ├── index.cjs
│ ├── errors.mjs
│ ├── errors.cjs
│ ├── streaming.mjs
│ ├── streaming.cjs
│ └── *.d.ts
│
├── src/
│ ├── index.ts # Main entry point
│ ├── client.ts # SonarStack client
│ ├── http.ts # HTTP wrapper with retry
│ ├── errors.ts # Custom error classes
│ ├── types.ts # TypeScript types
│ ├── streaming.ts # SSE streaming parser
│ │
│ └── resources/
│ ├── models.ts # Models API
│ ├── chat.ts # Chat completion
│ ├── embeddings.ts # Embedding API
│ └── responses.ts # Unified response API
│
├── test/
│
├── tsconfig.json
├── tsup.config.ts
├── vitest.config.ts
├── .eslintrc.cjs
├── .prettierrc.json
├── package.json
└── README.mdBrowser Support
This SDK is designed for Node.js 18+. For browser usage, bundle it with a module bundler (e.g., Webpack, Vite, esbuild) and ensure you have a fetch polyfill if targeting older browsers.
Contributing
Contributions welcome! Please ensure:
All tests pass:
pnpm testCode is formatted:
pnpm formatLinting passes:
pnpm lintTypeScript is strict:
pnpm type-check
License
MIT © SonarStack
