universal-llm-client 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +414 -0
- package/dist/ai-model.d.ts +53 -0
- package/dist/ai-model.d.ts.map +1 -0
- package/dist/ai-model.js +159 -0
- package/dist/ai-model.js.map +1 -0
- package/dist/auditor.d.ts +78 -0
- package/dist/auditor.d.ts.map +1 -0
- package/dist/auditor.js +104 -0
- package/dist/auditor.js.map +1 -0
- package/dist/client.d.ts +75 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +240 -0
- package/dist/client.js.map +1 -0
- package/dist/http.d.ts +47 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +186 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +324 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +63 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/mcp.d.ts +85 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +255 -0
- package/dist/mcp.js.map +1 -0
- package/dist/providers/google.d.ts +33 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +426 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.d.ts +26 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +304 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +20 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +251 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/router.d.ts +87 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +260 -0
- package/dist/router.js.map +1 -0
- package/dist/stream-decoder.d.ts +112 -0
- package/dist/stream-decoder.d.ts.map +1 -0
- package/dist/stream-decoder.js +238 -0
- package/dist/stream-decoder.js.map +1 -0
- package/dist/tools.d.ts +78 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +207 -0
- package/dist/tools.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Igor Lins e Silva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# universal-llm-client
|
|
2
|
+
|
|
3
|
+
A universal LLM client for JavaScript/TypeScript with **transparent provider failover**, streaming tool execution, pluggable reasoning strategies, and native observability.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { AIModel } from 'universal-llm-client';
|
|
7
|
+
|
|
8
|
+
const model = new AIModel({
|
|
9
|
+
model: 'gemini-2.5-flash',
|
|
10
|
+
providers: [
|
|
11
|
+
{ type: 'google', apiKey: process.env.GOOGLE_API_KEY },
|
|
12
|
+
{ type: 'openai', url: 'https://openrouter.ai/api', apiKey: process.env.OPENROUTER_KEY },
|
|
13
|
+
{ type: 'ollama' },
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const response = await model.chat([
|
|
18
|
+
{ role: 'user', content: 'Hello!' },
|
|
19
|
+
]);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **One model, multiple backends.** If Google fails, it transparently fails over to OpenRouter, then to local Ollama. Your code never knows the difference.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- 🔄 **Transparent Failover** — Priority-ordered provider chain with retries, health tracking, and cooldowns
|
|
29
|
+
- 🛠️ **Tool Calling** — Register tools once, works across all providers. Autonomous multi-turn execution loop
|
|
30
|
+
- 🌊 **Streaming** — First-class async generator streaming with pluggable decoder strategies
|
|
31
|
+
- 🧠 **Reasoning** — Native `<think>` tag parsing, interleaved reasoning, and model thinking support
|
|
32
|
+
- 🔍 **Observability** — Built-in auditor interface for logging, cost tracking, and behavioral analysis
|
|
33
|
+
- 🌐 **Universal Runtime** — Node.js 22+, Bun, Deno, and modern browsers
|
|
34
|
+
- 🤖 **MCP Native** — Bridge MCP servers to LLM tools with zero glue code
|
|
35
|
+
- 📊 **Embeddings** — Single and batch embedding generation
|
|
36
|
+
|
|
37
|
+
## Supported Providers
|
|
38
|
+
|
|
39
|
+
| Provider | Type | Notes |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| **Ollama** | `ollama` | Local or cloud models, NDJSON streaming, model pulling |
|
|
42
|
+
| **OpenAI** | `openai` | GPT-4o, o3, etc. Also works with OpenRouter, Groq, LM Studio, vLLM |
|
|
43
|
+
| **Google AI Studio** | `google` | Gemini models, system instructions, multimodal |
|
|
44
|
+
| **Vertex AI** | `vertex` | Same as Google AI but with regional endpoints and Bearer tokens |
|
|
45
|
+
| **LlamaCpp** | `llamacpp` | Local llama.cpp / llama-server instances |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun add universal-llm-client
|
|
53
|
+
# or
|
|
54
|
+
npm install universal-llm-client
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Optional**: For MCP integration:
|
|
58
|
+
```bash
|
|
59
|
+
bun add @modelcontextprotocol/sdk
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
### Basic Chat
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { AIModel } from 'universal-llm-client';
|
|
70
|
+
|
|
71
|
+
const model = new AIModel({
|
|
72
|
+
model: 'qwen3:4b',
|
|
73
|
+
providers: [{ type: 'ollama' }],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const response = await model.chat([
|
|
77
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
78
|
+
{ role: 'user', content: 'What is the capital of France?' },
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
console.log(response.message.content);
|
|
82
|
+
// "The capital of France is Paris."
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Streaming
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
for await (const event of model.chatStream([
|
|
89
|
+
{ role: 'user', content: 'Write a haiku about code.' },
|
|
90
|
+
])) {
|
|
91
|
+
if (event.type === 'text') {
|
|
92
|
+
process.stdout.write(event.content);
|
|
93
|
+
} else if (event.type === 'thinking') {
|
|
94
|
+
// Model reasoning (when supported)
|
|
95
|
+
console.log('[thinking]', event.content);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Tool Calling
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
model.registerTool(
|
|
104
|
+
'get_weather',
|
|
105
|
+
'Get current weather for a location',
|
|
106
|
+
{
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
city: { type: 'string', description: 'City name' },
|
|
110
|
+
},
|
|
111
|
+
required: ['city'],
|
|
112
|
+
},
|
|
113
|
+
async (args) => {
|
|
114
|
+
const { city } = args as { city: string };
|
|
115
|
+
return { temperature: 22, condition: 'sunny', city };
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Autonomous tool execution — the model calls tools and loops until done
|
|
120
|
+
const response = await model.chatWithTools([
|
|
121
|
+
{ role: 'user', content: "What's the weather in Tokyo?" },
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
console.log(response.message.content);
|
|
125
|
+
// "The weather in Tokyo is 22°C and sunny."
|
|
126
|
+
console.log(response.toolTrace);
|
|
127
|
+
// [{ name: 'get_weather', args: { city: 'Tokyo' }, result: {...}, duration: 5 }]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Provider Failover
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const model = new AIModel({
|
|
134
|
+
model: 'gemini-2.5-flash',
|
|
135
|
+
retries: 2, // retries per provider before failover
|
|
136
|
+
timeout: 30000, // request timeout in ms
|
|
137
|
+
providers: [
|
|
138
|
+
{ type: 'google', apiKey: process.env.GOOGLE_KEY, priority: 0 },
|
|
139
|
+
{ type: 'openai', url: 'https://openrouter.ai/api', apiKey: process.env.OPENROUTER_KEY, priority: 1 },
|
|
140
|
+
{ type: 'ollama', url: 'http://localhost:11434', priority: 2 },
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// If Google returns 500, retries twice, then seamlessly tries OpenRouter.
|
|
145
|
+
// If OpenRouter also fails, falls back to local Ollama.
|
|
146
|
+
// Your code sees a single response.
|
|
147
|
+
const response = await model.chat([{ role: 'user', content: 'Hello' }]);
|
|
148
|
+
|
|
149
|
+
// Check provider health at any time
|
|
150
|
+
console.log(model.getProviderStatus());
|
|
151
|
+
// [{ id: 'google-0', healthy: true }, { id: 'openai-1', healthy: true }, ...]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Multimodal (Vision)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { AIModel, multimodalMessage } from 'universal-llm-client';
|
|
158
|
+
|
|
159
|
+
const model = new AIModel({
|
|
160
|
+
model: 'gemini-2.5-flash',
|
|
161
|
+
providers: [{ type: 'google', apiKey: process.env.GOOGLE_KEY }],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const response = await model.chat([
|
|
165
|
+
multimodalMessage('What do you see in this image?', [
|
|
166
|
+
'https://example.com/photo.jpg',
|
|
167
|
+
]),
|
|
168
|
+
]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Embeddings
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const embedModel = new AIModel({
|
|
175
|
+
model: 'nomic-embed-text-v2-moe:latest',
|
|
176
|
+
providers: [{ type: 'ollama' }],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const vector = await embedModel.embed('Hello world');
|
|
180
|
+
// [0.006, 0.026, -0.009, ...]
|
|
181
|
+
|
|
182
|
+
const vectors = await embedModel.embedArray(['Hello', 'World']);
|
|
183
|
+
// [[0.006, ...], [0.012, ...]]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Observability
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { AIModel, ConsoleAuditor, BufferedAuditor } from 'universal-llm-client';
|
|
190
|
+
|
|
191
|
+
// Simple console logging
|
|
192
|
+
const model = new AIModel({
|
|
193
|
+
model: 'qwen3:4b',
|
|
194
|
+
providers: [{ type: 'ollama' }],
|
|
195
|
+
auditor: new ConsoleAuditor('[LLM]'),
|
|
196
|
+
});
|
|
197
|
+
// [LLM] REQUEST [ollama] (qwen3:4b) →
|
|
198
|
+
// [LLM] RESPONSE [ollama] (qwen3:4b) 1200ms 68 tokens
|
|
199
|
+
|
|
200
|
+
// Buffered for custom sinks (OpenTelemetry, DB, etc.)
|
|
201
|
+
const auditor = new BufferedAuditor({
|
|
202
|
+
maxBufferSize: 100,
|
|
203
|
+
onFlush: async (events) => {
|
|
204
|
+
await sendToOpenTelemetry(events);
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### MCP Integration
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { AIModel, MCPToolBridge } from 'universal-llm-client';
|
|
213
|
+
|
|
214
|
+
const model = new AIModel({
|
|
215
|
+
model: 'qwen3:4b',
|
|
216
|
+
providers: [{ type: 'ollama' }],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const mcp = new MCPToolBridge({
|
|
220
|
+
servers: {
|
|
221
|
+
filesystem: {
|
|
222
|
+
command: 'npx',
|
|
223
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', './'],
|
|
224
|
+
},
|
|
225
|
+
weather: {
|
|
226
|
+
url: 'https://mcp.example.com/weather',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await mcp.connect();
|
|
232
|
+
await mcp.registerTools(model);
|
|
233
|
+
|
|
234
|
+
// MCP tools are now callable via chatWithTools
|
|
235
|
+
const response = await model.chatWithTools([
|
|
236
|
+
{ role: 'user', content: 'List files in the current directory' },
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
await mcp.disconnect();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Stream Decoders
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { AIModel, createDecoder } from 'universal-llm-client';
|
|
246
|
+
|
|
247
|
+
// Passthrough — raw text, no parsing
|
|
248
|
+
// Standard Chat — text + native reasoning + tool calls
|
|
249
|
+
// Interleaved Reasoning — parses <think> and <progress> tags from text streams
|
|
250
|
+
|
|
251
|
+
const decoder = createDecoder('interleaved-reasoning', (event) => {
|
|
252
|
+
switch (event.type) {
|
|
253
|
+
case 'text': console.log(event.content); break;
|
|
254
|
+
case 'thinking': console.log('[think]', event.content); break;
|
|
255
|
+
case 'progress': console.log('[progress]', event.content); break;
|
|
256
|
+
case 'tool_call': console.log('[tool]', event.calls); break;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
decoder.push('<think>Let me analyze this</think>The answer is 42');
|
|
261
|
+
decoder.flush();
|
|
262
|
+
|
|
263
|
+
console.log(decoder.getCleanContent()); // "The answer is 42"
|
|
264
|
+
console.log(decoder.getReasoning()); // "Let me analyze this"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## API Reference
|
|
270
|
+
|
|
271
|
+
### `AIModel`
|
|
272
|
+
|
|
273
|
+
The universal client. One class, multiple backends.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
new AIModel(config: AIModelConfig)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Config:**
|
|
280
|
+
|
|
281
|
+
| Property | Type | Default | Description |
|
|
282
|
+
|---|---|---|---|
|
|
283
|
+
| `model` | `string` | — | Model name (e.g., `'gemini-2.5-flash'`) |
|
|
284
|
+
| `providers` | `ProviderConfig[]` | — | Ordered list of provider backends |
|
|
285
|
+
| `retries` | `number` | `2` | Retries per provider before failover |
|
|
286
|
+
| `timeout` | `number` | `30000` | Request timeout in ms |
|
|
287
|
+
| `auditor` | `Auditor` | `NoopAuditor` | Observability sink |
|
|
288
|
+
| `thinking` | `boolean` | `false` | Enable model thinking/reasoning |
|
|
289
|
+
| `debug` | `boolean` | `false` | Debug logging |
|
|
290
|
+
| `defaultParameters` | `object` | — | Default parameters for all requests |
|
|
291
|
+
|
|
292
|
+
**Provider Config:**
|
|
293
|
+
|
|
294
|
+
| Property | Type | Description |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| `type` | `string` | `'ollama'`, `'openai'`, `'google'`, `'vertex'`, `'llamacpp'` |
|
|
297
|
+
| `url` | `string` | Provider URL (has sensible defaults) |
|
|
298
|
+
| `apiKey` | `string` | API key or Bearer token |
|
|
299
|
+
| `priority` | `number` | Lower = tried first (defaults to array index) |
|
|
300
|
+
| `model` | `string` | Override model name for this provider |
|
|
301
|
+
| `region` | `string` | Vertex AI region (e.g., `'us-central1'`) |
|
|
302
|
+
| `apiVersion` | `string` | API version (e.g., `'v1beta'`) |
|
|
303
|
+
|
|
304
|
+
**Methods:**
|
|
305
|
+
|
|
306
|
+
| Method | Returns | Description |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `chat(messages, options?)` | `Promise<LLMChatResponse>` | Send chat request |
|
|
309
|
+
| `chatWithTools(messages, options?)` | `Promise<LLMChatResponse>` | Chat with autonomous tool execution |
|
|
310
|
+
| `chatStream(messages, options?)` | `AsyncGenerator<DecodedEvent>` | Stream chat response |
|
|
311
|
+
| `embed(text)` | `Promise<number[]>` | Generate single embedding |
|
|
312
|
+
| `embedArray(texts)` | `Promise<number[][]>` | Generate batch embeddings |
|
|
313
|
+
| `registerTool(name, desc, params, handler)` | `void` | Register a callable tool |
|
|
314
|
+
| `registerTools(tools)` | `void` | Register multiple tools |
|
|
315
|
+
| `getModels()` | `Promise<string[]>` | List available models |
|
|
316
|
+
| `getModelInfo()` | `Promise<ModelMetadata>` | Get model metadata |
|
|
317
|
+
| `getProviderStatus()` | `ProviderStatus[]` | Check provider health |
|
|
318
|
+
| `setModel(name)` | `void` | Switch model at runtime |
|
|
319
|
+
| `dispose()` | `Promise<void>` | Clean shutdown |
|
|
320
|
+
|
|
321
|
+
### `ToolBuilder` / `ToolExecutor`
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { ToolBuilder, ToolExecutor } from 'universal-llm-client';
|
|
325
|
+
|
|
326
|
+
// Fluent builder
|
|
327
|
+
const tool = new ToolBuilder('search')
|
|
328
|
+
.description('Search the web')
|
|
329
|
+
.addParameter('query', 'string', 'Search query', true)
|
|
330
|
+
.addParameter('limit', 'number', 'Max results', false)
|
|
331
|
+
.build();
|
|
332
|
+
|
|
333
|
+
// Execution wrappers
|
|
334
|
+
const safeHandler = ToolExecutor.compose(
|
|
335
|
+
myHandler,
|
|
336
|
+
h => ToolExecutor.withTimeout(h, 5000),
|
|
337
|
+
h => ToolExecutor.safe(h),
|
|
338
|
+
h => ToolExecutor.withValidation(h, ['query']),
|
|
339
|
+
);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Auditor Interface
|
|
343
|
+
|
|
344
|
+
Implement custom observability by providing an `Auditor`:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
interface Auditor {
|
|
348
|
+
record(event: AuditEvent): void;
|
|
349
|
+
flush?(): Promise<void>;
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Built-in implementations:**
|
|
354
|
+
- `NoopAuditor` — Zero overhead (default)
|
|
355
|
+
- `ConsoleAuditor` — Structured console logging
|
|
356
|
+
- `BufferedAuditor` — Collects events for custom sinks
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Architecture
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
@akaito/universal-llm-client
|
|
364
|
+
├── AIModel ← Public API (the only class you import)
|
|
365
|
+
├── Router ← Internal failover engine
|
|
366
|
+
├── BaseLLMClient ← Abstract client with tool execution
|
|
367
|
+
├── Providers
|
|
368
|
+
│ ├── OllamaClient
|
|
369
|
+
│ ├── OpenAICompatibleClient
|
|
370
|
+
│ └── GoogleClient (AI Studio + Vertex AI)
|
|
371
|
+
├── StreamDecoder ← Pluggable reasoning strategies
|
|
372
|
+
├── Auditor ← Observability interface
|
|
373
|
+
├── MCPToolBridge ← MCP server integration
|
|
374
|
+
└── HTTP Utilities ← Universal fetch-based transport
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Design Principles
|
|
378
|
+
|
|
379
|
+
1. **Single import** — `AIModel` is the only class users need
|
|
380
|
+
2. **Provider agnostic** — Same code works with any backend
|
|
381
|
+
3. **Transparent failover** — Health tracking and cooldowns happen behind the scenes
|
|
382
|
+
4. **Zero dependencies** — Core library depends only on native `fetch`
|
|
383
|
+
5. **Agent-ready** — Stateless, composable instances designed as foundation for agent frameworks
|
|
384
|
+
6. **Observable** — Every request, response, tool call, retry, and failover is auditable
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Runtime Support
|
|
389
|
+
|
|
390
|
+
| Runtime | Version | Status |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| **Node.js** | 22+ | ✅ Full support |
|
|
393
|
+
| **Bun** | 1.0+ | ✅ Full support |
|
|
394
|
+
| **Deno** | 2.0+ | ✅ Full support |
|
|
395
|
+
| **Browsers** | Modern | ✅ No stdio MCP, HTTP transport only |
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## For Agent Framework Authors
|
|
400
|
+
|
|
401
|
+
`AIModel` is designed as the transport layer for agentic systems:
|
|
402
|
+
|
|
403
|
+
- **Stateless** — No conversation history stored. Your framework manages memory
|
|
404
|
+
- **Composable** — Create separate instances for chat, embeddings, vision
|
|
405
|
+
- **Tool tracing** — `chatWithTools()` returns full execution trace
|
|
406
|
+
- **Context budget** — `getModelInfo()` exposes `contextLength`
|
|
407
|
+
- **Auditor as system bus** — Inject custom sinks for cost tracking, behavioral scoring
|
|
408
|
+
- **StreamDecoder as UI bridge** — Select decoder strategy per-call
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal LLM Client v3 — AIModel (The Universal Client)
|
|
3
|
+
*
|
|
4
|
+
* The only public-facing class. Developers configure one model with
|
|
5
|
+
* multiple provider backends for transparent failover.
|
|
6
|
+
*
|
|
7
|
+
* Provider classes are internal — the user never imports them.
|
|
8
|
+
*/
|
|
9
|
+
import { type AIModelConfig, type LLMChatMessage, type LLMChatResponse, type ChatOptions, type ModelMetadata, type LLMFunction, type ToolHandler } from './interfaces.js';
|
|
10
|
+
import type { DecodedEvent } from './stream-decoder.js';
|
|
11
|
+
import { type ProviderStatus } from './router.js';
|
|
12
|
+
export declare class AIModel {
|
|
13
|
+
private router;
|
|
14
|
+
private auditor;
|
|
15
|
+
private config;
|
|
16
|
+
constructor(config: AIModelConfig);
|
|
17
|
+
/** Send a chat request with automatic failover across providers */
|
|
18
|
+
chat(messages: LLMChatMessage[], options?: ChatOptions): Promise<LLMChatResponse>;
|
|
19
|
+
/** Chat with automatic tool execution (multi-turn loop) */
|
|
20
|
+
chatWithTools(messages: LLMChatMessage[], options?: ChatOptions & {
|
|
21
|
+
maxIterations?: number;
|
|
22
|
+
}): Promise<LLMChatResponse>;
|
|
23
|
+
/** Stream chat response with pluggable decoder strategy */
|
|
24
|
+
chatStream(messages: LLMChatMessage[], options?: ChatOptions): AsyncGenerator<DecodedEvent, LLMChatResponse | void, unknown>;
|
|
25
|
+
/** Generate embedding for a single text */
|
|
26
|
+
embed(text: string): Promise<number[]>;
|
|
27
|
+
/** Generate embeddings for multiple texts */
|
|
28
|
+
embedArray(texts: string[]): Promise<number[][]>;
|
|
29
|
+
/** Register a tool callable by the LLM (broadcast to all providers) */
|
|
30
|
+
registerTool(name: string, description: string, parameters: LLMFunction['parameters'], handler: ToolHandler): void;
|
|
31
|
+
/** Register multiple tools at once */
|
|
32
|
+
registerTools(tools: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
parameters: LLMFunction['parameters'];
|
|
36
|
+
handler: ToolHandler;
|
|
37
|
+
}>): void;
|
|
38
|
+
/** Get available models from all configured providers */
|
|
39
|
+
getModels(): Promise<string[]>;
|
|
40
|
+
/** Get metadata about the current model (context length, capabilities) */
|
|
41
|
+
getModelInfo(): Promise<ModelMetadata>;
|
|
42
|
+
/** Switch model at runtime (updates all providers) */
|
|
43
|
+
setModel(name: string): void;
|
|
44
|
+
/** Get the current model name */
|
|
45
|
+
get model(): string;
|
|
46
|
+
/** Get health/status of all configured providers */
|
|
47
|
+
getProviderStatus(): ProviderStatus[];
|
|
48
|
+
/** Clean shutdown — flush auditor, disconnect MCP, etc. */
|
|
49
|
+
dispose(): Promise<void>;
|
|
50
|
+
private createClient;
|
|
51
|
+
private normalizeType;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=ai-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-model.d.ts","sourceRoot":"","sources":["../src/ai-model.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEH,KAAK,aAAa,EAGlB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAuB7E,qBAAa,OAAO;IAChB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IA6BjC,mEAAmE;IAC7D,IAAI,CACN,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,eAAe,CAAC;IAI3B,2DAA2D;IACrD,aAAa,CACf,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GACnD,OAAO,CAAC,eAAe,CAAC;IAI3B,2DAA2D;IACpD,UAAU,CACb,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GACtB,cAAc,CAAC,YAAY,EAAE,eAAe,GAAG,IAAI,EAAE,OAAO,CAAC;IAQhE,2CAA2C;IACrC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAI5C,6CAA6C;IACvC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAQtD,uEAAuE;IACvE,YAAY,CACR,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,WAAW,CAAC,YAAY,CAAC,EACrC,OAAO,EAAE,WAAW,GACrB,IAAI;IAIP,sCAAsC;IACtC,aAAa,CACT,KAAK,EAAE,KAAK,CAAC;QACT,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;QACtC,OAAO,EAAE,WAAW,CAAC;KACxB,CAAC,GACH,IAAI;IAQP,yDAAyD;IACnD,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,0EAA0E;IACpE,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;IAI5C,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM5B,iCAAiC;IACjC,IAAI,KAAK,IAAI,MAAM,CAElB;IAMD,oDAAoD;IACpD,iBAAiB,IAAI,cAAc,EAAE;IAQrC,2DAA2D;IACrD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B,OAAO,CAAC,YAAY;IAmCpB,OAAO,CAAC,aAAa;CAGxB"}
|
package/dist/ai-model.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal LLM Client v3 — AIModel (The Universal Client)
|
|
3
|
+
*
|
|
4
|
+
* The only public-facing class. Developers configure one model with
|
|
5
|
+
* multiple provider backends for transparent failover.
|
|
6
|
+
*
|
|
7
|
+
* Provider classes are internal — the user never imports them.
|
|
8
|
+
*/
|
|
9
|
+
import { Router } from './router.js';
|
|
10
|
+
import { NoopAuditor } from './auditor.js';
|
|
11
|
+
import { OllamaClient } from './providers/ollama.js';
|
|
12
|
+
import { OpenAICompatibleClient } from './providers/openai.js';
|
|
13
|
+
import { GoogleClient } from './providers/google.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Default Provider URLs
|
|
16
|
+
// ============================================================================
|
|
17
|
+
const DEFAULT_URLS = {
|
|
18
|
+
ollama: 'http://localhost:11434',
|
|
19
|
+
openai: 'https://api.openai.com',
|
|
20
|
+
llamacpp: 'http://localhost:8080',
|
|
21
|
+
// google and vertex build their own URLs internally
|
|
22
|
+
};
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// AIModel — The Universal Client
|
|
25
|
+
// ============================================================================
|
|
26
|
+
export class AIModel {
|
|
27
|
+
router;
|
|
28
|
+
auditor;
|
|
29
|
+
config;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.auditor = config.auditor ?? new NoopAuditor();
|
|
33
|
+
const routerConfig = {
|
|
34
|
+
retriesPerProvider: config.retries ?? 2,
|
|
35
|
+
auditor: this.auditor,
|
|
36
|
+
};
|
|
37
|
+
this.router = new Router(routerConfig);
|
|
38
|
+
// Initialize providers in order
|
|
39
|
+
for (let i = 0; i < config.providers.length; i++) {
|
|
40
|
+
const providerConfig = config.providers[i];
|
|
41
|
+
const client = this.createClient(providerConfig);
|
|
42
|
+
const id = `${this.normalizeType(providerConfig.type)}-${i}`;
|
|
43
|
+
this.router.addProvider({
|
|
44
|
+
id,
|
|
45
|
+
client,
|
|
46
|
+
priority: providerConfig.priority ?? i,
|
|
47
|
+
modelOverride: providerConfig.model,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ========================================================================
|
|
52
|
+
// Chat
|
|
53
|
+
// ========================================================================
|
|
54
|
+
/** Send a chat request with automatic failover across providers */
|
|
55
|
+
async chat(messages, options) {
|
|
56
|
+
return this.router.chat(messages, options);
|
|
57
|
+
}
|
|
58
|
+
/** Chat with automatic tool execution (multi-turn loop) */
|
|
59
|
+
async chatWithTools(messages, options) {
|
|
60
|
+
return this.router.chatWithTools(messages, options);
|
|
61
|
+
}
|
|
62
|
+
/** Stream chat response with pluggable decoder strategy */
|
|
63
|
+
async *chatStream(messages, options) {
|
|
64
|
+
return yield* this.router.chatStream(messages, options);
|
|
65
|
+
}
|
|
66
|
+
// ========================================================================
|
|
67
|
+
// Embeddings
|
|
68
|
+
// ========================================================================
|
|
69
|
+
/** Generate embedding for a single text */
|
|
70
|
+
async embed(text) {
|
|
71
|
+
return this.router.embed(text);
|
|
72
|
+
}
|
|
73
|
+
/** Generate embeddings for multiple texts */
|
|
74
|
+
async embedArray(texts) {
|
|
75
|
+
return this.router.embedArray(texts);
|
|
76
|
+
}
|
|
77
|
+
// ========================================================================
|
|
78
|
+
// Tool Registration
|
|
79
|
+
// ========================================================================
|
|
80
|
+
/** Register a tool callable by the LLM (broadcast to all providers) */
|
|
81
|
+
registerTool(name, description, parameters, handler) {
|
|
82
|
+
this.router.registerTool(name, description, parameters, handler);
|
|
83
|
+
}
|
|
84
|
+
/** Register multiple tools at once */
|
|
85
|
+
registerTools(tools) {
|
|
86
|
+
this.router.registerTools(tools);
|
|
87
|
+
}
|
|
88
|
+
// ========================================================================
|
|
89
|
+
// Model Management
|
|
90
|
+
// ========================================================================
|
|
91
|
+
/** Get available models from all configured providers */
|
|
92
|
+
async getModels() {
|
|
93
|
+
return this.router.getModels();
|
|
94
|
+
}
|
|
95
|
+
/** Get metadata about the current model (context length, capabilities) */
|
|
96
|
+
async getModelInfo() {
|
|
97
|
+
return this.router.getModelInfo();
|
|
98
|
+
}
|
|
99
|
+
/** Switch model at runtime (updates all providers) */
|
|
100
|
+
setModel(name) {
|
|
101
|
+
this.config.model = name;
|
|
102
|
+
// The model name change will be picked up by the providers
|
|
103
|
+
// through the router on next request
|
|
104
|
+
}
|
|
105
|
+
/** Get the current model name */
|
|
106
|
+
get model() {
|
|
107
|
+
return this.config.model;
|
|
108
|
+
}
|
|
109
|
+
// ========================================================================
|
|
110
|
+
// Provider Status
|
|
111
|
+
// ========================================================================
|
|
112
|
+
/** Get health/status of all configured providers */
|
|
113
|
+
getProviderStatus() {
|
|
114
|
+
return this.router.getStatus();
|
|
115
|
+
}
|
|
116
|
+
// ========================================================================
|
|
117
|
+
// Lifecycle
|
|
118
|
+
// ========================================================================
|
|
119
|
+
/** Clean shutdown — flush auditor, disconnect MCP, etc. */
|
|
120
|
+
async dispose() {
|
|
121
|
+
await this.auditor.flush?.();
|
|
122
|
+
}
|
|
123
|
+
// ========================================================================
|
|
124
|
+
// Internal: Provider Factory
|
|
125
|
+
// ========================================================================
|
|
126
|
+
createClient(providerConfig) {
|
|
127
|
+
const type = this.normalizeType(providerConfig.type);
|
|
128
|
+
const modelName = providerConfig.model ?? this.config.model;
|
|
129
|
+
const clientOptions = {
|
|
130
|
+
model: modelName,
|
|
131
|
+
url: providerConfig.url ?? DEFAULT_URLS[type] ?? '',
|
|
132
|
+
apiType: type,
|
|
133
|
+
apiKey: providerConfig.apiKey,
|
|
134
|
+
timeout: this.config.timeout ?? 30000,
|
|
135
|
+
retries: this.config.retries ?? 2,
|
|
136
|
+
debug: this.config.debug ?? false,
|
|
137
|
+
defaultParameters: this.config.defaultParameters,
|
|
138
|
+
thinking: this.config.thinking ?? false,
|
|
139
|
+
region: providerConfig.region,
|
|
140
|
+
apiVersion: providerConfig.apiVersion,
|
|
141
|
+
};
|
|
142
|
+
switch (type) {
|
|
143
|
+
case 'ollama':
|
|
144
|
+
return new OllamaClient(clientOptions, this.auditor);
|
|
145
|
+
case 'openai':
|
|
146
|
+
case 'llamacpp':
|
|
147
|
+
return new OpenAICompatibleClient(clientOptions, this.auditor);
|
|
148
|
+
case 'google':
|
|
149
|
+
case 'vertex':
|
|
150
|
+
return new GoogleClient(clientOptions, this.auditor);
|
|
151
|
+
default:
|
|
152
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
normalizeType(type) {
|
|
156
|
+
return type.toLowerCase();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=ai-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-model.js","sourceRoot":"","sources":["../src/ai-model.ts"],"names":[],"mappings":"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"}
|