A TypeScript wrapper for any OpenAI-compatible API — llm7.io, OpenAI, Azure OpenAI, Groq, Together AI, and more.
Built by Eduardo J. Barrios · edujbarrios@outlook.com
This package was originally built for private use — the goal is to have full, direct control over how LLM API calls are made, retried, streamed, and error-handled, without depending on a third-party SDK whose internals you can't audit or change.
It has been open-sourced as-is in case it is useful to someone else facing the same problem.
npm publication is not discarded. If this project gains traction or interest from the community, publishing it as an installable package on the npm registry is on the table.
| Feature | Details |
|---|---|
| Chat completions | Standard & streaming (SSE, plus NDJSON fallbacks) |
| Responses API | Create, retrieve, delete, stream, and extract text (providers exposing /responses) |
| Embeddings | /embeddings endpoint |
| Model listing & retrieval | /models and /models/{id} endpoints |
| Tool / function calling | Full type support |
| Auto-retry | Exponential back-off + jitter on 429/5xx/network errors |
| Timeout | Configurable per-client, uses AbortController |
| Typed errors | LLMAuthenticationError, LLMRateLimitError, … |
| Zero runtime deps | Uses the built-in fetch (Node ≥ 18) |
git clone https://github.com/edujbarrios/llm-ts-api-wrapper.git
cd llm-ts-api-wrapper
npm install
npm run buildThen import directly from the compiled output in your project:
import { LLMClient } from "./path/to/llm-ts-api-wrapper/dist";Since this package is designed for private/local use, you copy or submodule this repo into your project instead of installing from npm.
import { LLMClient } from "./dist"; // local build
const client = new LLMClient({
baseURL: "https://api.llm7.io/v1", // any OpenAI-compatible base URL
apiKey: process.env.LLM_API_KEY!,
defaultModel: "default", // use an ID supported by your provider
maxRetries: 3,
retryBackoffMs: 1000,
timeoutMs: 30_000,
});const response = await client.chat({
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is TypeScript?" },
],
});
console.log(response.choices[0].message.content);
// → "TypeScript is a strongly typed programming language…"const text = await client.chatText({
messages: [{ role: "user", content: "Tell me a joke." }],
});
console.log(text);for await (const chunk of client.streamChat({
messages: [{ role: "user", content: "Count to 5." }],
})) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Or collect the full stream:
const full = await client.streamChatText({
messages: [{ role: "user", content: "Count to 5." }],
});const { data: models } = await client.listModels();
models.forEach(m => console.log(m.id));const result = await client.embed({
input: "Hello, world!",
model: "text-embedding-ada-002",
});
const vector = result.data[0].embedding;The Responses API is separate from Chat Completions. Not every OpenAI-compatible service
implements /responses, so check your provider's documentation before using it.
const response = await client.createResponse({
model: "your-provider-model",
input: "Explain why the sky looks blue.",
max_output_tokens: 256,
});
const text = await client.responseText({
model: "your-provider-model",
input: "Summarize the water cycle in one sentence.",
});
const savedResponse = await client.retrieveResponse(response.id);
// SSE events, including response.output_text.delta:
for await (const event of client.streamResponse({
model: "your-provider-model",
input: "Count to three.",
})) {
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta ?? "");
}
}Also available: streamResponseText(), deleteResponse(id), and retrieveModel(id).
const res = await client.chat({
messages: [{ role: "user", content: "What's the weather in Madrid?" }],
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a city.",
parameters: {
type: "object",
properties: {
city: { type: "string" },
},
required: ["city"],
},
},
},
],
tool_choice: "auto",
});
const toolCall = res.choices[0].message.tool_calls?.[0];
if (toolCall) {
const args = JSON.parse(toolCall.function.arguments);
console.log("Calling tool:", toolCall.function.name, args);
}Set model for each request or configure defaultModel. There is no implicit
model fallback: the client raises LLMConfigError when neither is supplied.
Use a supported embedding model for embed(); a chat model usually will not work.
OpenAI-compatible providers differ: Chat Completions, embeddings, model listing,
structured outputs, newer parameters such as max_completion_tokens, and
Responses API are supported only when the selected provider and model offer them.
Fields are forwarded to the provider without SDK-specific rewriting.
import {
LLMClient,
LLMRateLimitError,
LLMAuthenticationError,
LLMTimeoutError,
LLMNetworkError,
} from "./dist";
try {
const text = await client.chatText({ messages: [...] });
} catch (err) {
if (err instanceof LLMRateLimitError) {
console.error("Rate limit hit, slow down.");
} else if (err instanceof LLMAuthenticationError) {
console.error("Bad API key.");
} else if (err instanceof LLMTimeoutError) {
console.error("Request timed out.");
} else if (err instanceof LLMNetworkError) {
console.error("Network problem:", err.cause);
} else {
throw err; // re-throw unknown errors
}
}| Option | Type | Default | Description |
|---|---|---|---|
baseURL |
string |
required | Base URL of the OpenAI-compatible API |
apiKey |
string |
required | Bearer token / API key |
defaultModel |
string |
undefined |
Fallback model when none is given per-request; otherwise model is required |
timeoutMs |
number |
30000 |
Request timeout in milliseconds |
maxRetries |
number |
2 |
Retries on 429/5xx/network errors |
retryBackoffMs |
number |
1000 |
Base delay for exponential back-off |
defaultHeaders |
Record<string,string> |
{} |
Extra headers added to every request |
| Method | Endpoint |
|---|---|
chat() / chatText() |
POST /chat/completions |
streamChat() / streamChatText() |
POST /chat/completions (SSE) |
embed() |
POST /embeddings |
listModels() |
GET /models |
retrieveModel(id) |
GET /models/{id} |
createResponse() / responseText() |
POST /responses |
streamResponse() / streamResponseText() |
POST /responses (SSE) |
retrieveResponse(id) |
GET /responses/{id} |
deleteResponse(id) |
DELETE /responses/{id} |
Ready-to-run examples live in src/examples/.
| Script | What it does |
|---|---|
npm run example:capital |
Asks llm7.io "What is the capital of Spain?" (free tier, no key needed) |
npm run build
npm run example:capital
# → Answer: The capital of Spain is Madrid.npm run build # compile to dist/
npm test # run mocked network regression tests
npm run dev # watch mode
npm run clean # remove dist/MIT © 2026 Eduardo J. Barrios — see LICENSE