ai-models-ts
v0.1.2
Published
TypeScript/JavaScript client and model catalog for Ollama Cloud and OpenRouter APIs
Readme
ai-models-ts
Lightweight TypeScript and JavaScript client library and verified free models catalog for Ollama Cloud (
https://ollama.com) and OpenRouter (https://openrouter.ai).
Features
- 🚀 Zero Unnecessary Bloat: No heavy dependencies, no server middleware, no caching layers.
- ⚡ Ollama Cloud & OpenRouter Clients: Standalone functions and factory instances for chat completions, live model listing, and capability discovery.
- 🆓 Free Models Catalog (
free-models.ts): Built-in list of verified free models across Ollama Cloud and OpenRouter with context length and capability metadata. - 🔄 Live Model Builder: Automated upstream verification script (
npm run build:models) that probes live APIs to updatefree-models.ts. - 🌐 Universal Runtime Support: Fully compatible with Node.js (>= 18), Bun, Deno, and Cloudflare Workers (V8 isolates).
- 📦 Subpath Exports: Import only what you need (
/ollama,/openrouter,/models,/types).
Installation
npm install ai-models-tsQuick Start
1. Ollama Cloud
import { createOllamaClient, ollamaChat } from "ai-models-ts/ollama";
// Factory instance
const ollama = createOllamaClient({ apiKey: process.env.OLLAMA_API_KEY });
// Chat Completion
const response = await ollama.chat({
model: "gemma4:31b",
messages: [{ role: "user", content: "Hello from Ollama!" }],
});
const result = await response.json();
console.log(result.choices[0].message.content);
// Live Model Discovery from Endpoint
const models = await (await ollama.listModels()).json();
const tags = await (await ollama.tags()).json();
const show = await (await ollama.show("gemma4:31b")).json();Standalone Function
import { ollamaChat } from "ai-models-ts/ollama";
const response = await ollamaChat({
apiKey: process.env.OLLAMA_API_KEY!,
model: "gemma4:31b",
messages: [{ role: "user", content: "Hello!" }],
max_tokens: 100,
});2. OpenRouter
import { createOpenRouterClient, openRouterChat } from "ai-models-ts/openrouter";
// Factory instance
const openrouter = createOpenRouterClient({
apiKey: process.env.OPENROUTER_API_KEY,
});
// Chat Completion
const response = await openrouter.chat({
model: "google/gemma-4-31b-it:free",
messages: [{ role: "user", content: "Hello from OpenRouter!" }],
});
const result = await response.json();
console.log(result.choices[0].message.content);
// Live Model Discovery from Endpoint
const allModels = await (await openrouter.listModels()).json();
// Check Key Status & Limits
const keyStatus = await (await openrouter.checkKey()).json();
console.log(keyStatus.data.limit_remaining);OpenRouter currently limits free models to 20 requests per minute and 50
requests per day, or 1,000 per day after at least $10 in credits has been
purchased. openRouterChat() therefore spaces free requests sharing an API key
in the current runtime by three seconds. A specific free model also gets
openrouter/free as an OpenRouter-native model fallback in the same request,
so saturation does not create a client-side retry storm. For image requests,
text-only fallbacks are removed and the vision-capable free router is used.
Disable either safeguard when raw behavior is required, or configure it:
await openRouterChat({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "google/gemma-4-31b-it:free",
messages: [{ role: "user", content: "Hello" }],
fallbackModel: false, // or another OpenRouter model ID
rateLimitIntervalMs: false, // or a custom minimum interval
});The in-memory pacer coordinates one Node process, browser context, or Cloudflare Worker isolate. If the same key is used by multiple processes or isolates, enforce the same 20 RPM budget at the shared gateway as well.
References: OpenRouter free-model limits, native model fallbacks, and the capability-aware free router.
3. Models Catalog (free-models.ts)
import {
MODELS,
getModels,
getModel,
hasModel,
getVisionModels,
hasVision,
} from "ai-models-ts/models";
// Get free models (free=true)
const freeModels = getModels({ free: true });
console.log(freeModels.map((m) => m.id));
// Filter free models by provider
const freeOllama = getModels({ provider: "ollama", free: true });
const freeOpenRouter = getModels({ provider: "openrouter", free: true });
// Get all models with vision capability
const visionModels = getVisionModels();
console.log(visionModels.map((m) => `${m.id} (${m.provider})`));
// Check if a model supports vision
console.log(hasVision("gemma4:31b")); // true
console.log(hasVision("google/gemma-4-31b-it:free")); // true
console.log(hasVision("gpt-oss:120b")); // false
// Check if a model exists in the catalog
console.log(hasModel("gemma4:31b")); // true
console.log(hasModel("google/gemma-4-31b-it:free")); // true
// Get metadata for a model
const model = getModel("gemma4:31b");
console.log(model?.contextLength); // 262144
console.log(model?.capabilities); // ["completion", "thinking", "tools", "vision"]4. Multimodal & Vision Chat
Pass image URLs or base64 data URLs to any vision-capable model:
import { ollamaChat } from "ai-models-ts/ollama";
import { openRouterChat } from "ai-models-ts/openrouter";
const imageUrl = "data:image/png;base64,...";
// Vision completion via Ollama Cloud (gemma4:31b)
const resOllama = await ollamaChat({
apiKey: process.env.OLLAMA_API_KEY!,
model: "gemma4:31b",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe what is in this picture:" },
{ type: "image_url", image_url: { url: imageUrl } },
],
},
],
});
// Vision completion via OpenRouter (google/gemma-4-31b-it:free)
const resOpenRouter = await openRouterChat({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "google/gemma-4-31b-it:free",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What are the key objects in this image?" },
{ type: "image_url", image_url: { url: "https://example.com/image.jpg" } },
],
},
],
});Streaming Responses
All chat functions support standard Server-Sent Events (SSE) streaming with stream: true:
import { createOllamaClient } from "ai-models-ts/ollama";
const ollama = createOllamaClient({ apiKey: process.env.OLLAMA_API_KEY });
const response = await ollama.chat({
model: "gemma4:31b",
messages: [{ role: "user", content: "Tell me a story" }],
stream: true,
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}Cloudflare Worker Example
import {
ollamaChat,
openRouterChat,
ollamaGetTags,
ollamaGetVersion,
getModels,
} from "ai-models-ts";
export interface Env {
OLLAMA_API_KEY?: string;
OPENROUTER_API_KEY?: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/version") {
return ollamaGetVersion();
}
if (url.pathname === "/api/tags") {
return ollamaGetTags({ apiKey: env.OLLAMA_API_KEY });
}
if (url.pathname === "/v1/models") {
return new Response(JSON.stringify({ data: getModels({ free: true }) }), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/v1/chat/completions" && request.method === "POST") {
const body = await request.json();
if (body.model?.startsWith("openrouter/")) {
return openRouterChat({ ...body, apiKey: env.OPENROUTER_API_KEY });
}
return ollamaChat({ ...body, apiKey: env.OLLAMA_API_KEY });
}
return new Response("Not Found", { status: 404 });
},
};Rebuilding the Models Catalog
To probe the live upstream APIs of Ollama Cloud and OpenRouter and update src/models/free-models.ts:
npm run build:modelsSubpath Exports Map
| Subpath | Description |
| :--- | :--- |
| ai-models-ts | Main entrypoint re-exporting all modules |
| ai-models-ts/ollama | Ollama client, chat, listModels, tags, version, show |
| ai-models-ts/openrouter | OpenRouter client, chat, listModels, checkKey |
| ai-models-ts/models | MODELS, getModels(), getModel(), hasModel() |
| ai-models-ts/types | TypeScript interfaces (ModelInfo, OllamaChatOptions, etc.) |
License
MIT © lgnat
