Skip to content

About

A TypeScript wrapper for any OpenAI-compatible APIs — llm7.io, OpenAI, Azure OpenAI, Groq, Together AI, and more.

Resources

Stars

34 stars

Watchers

0 watching

Forks

Repository files navigation

llm-ts-api-wrapper

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


Intent & philosophy

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.


Features

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)

Clone & build

git clone https://github.com/edujbarrios/llm-ts-api-wrapper.git
cd llm-ts-api-wrapper
npm install
npm run build

Then 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.


Quick start

1. Create a client

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,
});

2. Send a chat message

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…"

3. Convenience: get text directly

const text = await client.chatText({
  messages: [{ role: "user", content: "Tell me a joke." }],
});
console.log(text);

4. Streaming

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." }],
});

5. List models

const { data: models } = await client.listModels();
models.forEach(m => console.log(m.id));

6. Generate embeddings

const result = await client.embed({
  input: "Hello, world!",
  model: "text-embedding-ada-002",
});
const vector = result.data[0].embedding;

7. Responses API (when supported by your provider)

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).

8. Function / tool calling

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);
}

Model selection and compatibility

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.

Error handling

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
  }
}

Configuration reference

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

Supported endpoints

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}

Examples

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.

Build

npm run build    # compile to dist/
npm test         # run mocked network regression tests
npm run dev      # watch mode
npm run clean    # remove dist/

License

MIT © 2026 Eduardo J. Barrios — see LICENSE

About

A TypeScript wrapper for any OpenAI-compatible APIs — llm7.io, OpenAI, Azure OpenAI, Groq, Together AI, and more.

Resources

Stars

34 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages