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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +414 -0
  3. package/dist/ai-model.d.ts +53 -0
  4. package/dist/ai-model.d.ts.map +1 -0
  5. package/dist/ai-model.js +159 -0
  6. package/dist/ai-model.js.map +1 -0
  7. package/dist/auditor.d.ts +78 -0
  8. package/dist/auditor.d.ts.map +1 -0
  9. package/dist/auditor.js +104 -0
  10. package/dist/auditor.js.map +1 -0
  11. package/dist/client.d.ts +75 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +240 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/http.d.ts +47 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +186 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +41 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/interfaces.d.ts +324 -0
  24. package/dist/interfaces.d.ts.map +1 -0
  25. package/dist/interfaces.js +63 -0
  26. package/dist/interfaces.js.map +1 -0
  27. package/dist/mcp.d.ts +85 -0
  28. package/dist/mcp.d.ts.map +1 -0
  29. package/dist/mcp.js +255 -0
  30. package/dist/mcp.js.map +1 -0
  31. package/dist/providers/google.d.ts +33 -0
  32. package/dist/providers/google.d.ts.map +1 -0
  33. package/dist/providers/google.js +426 -0
  34. package/dist/providers/google.js.map +1 -0
  35. package/dist/providers/index.d.ts +7 -0
  36. package/dist/providers/index.d.ts.map +1 -0
  37. package/dist/providers/index.js +7 -0
  38. package/dist/providers/index.js.map +1 -0
  39. package/dist/providers/ollama.d.ts +26 -0
  40. package/dist/providers/ollama.d.ts.map +1 -0
  41. package/dist/providers/ollama.js +304 -0
  42. package/dist/providers/ollama.js.map +1 -0
  43. package/dist/providers/openai.d.ts +20 -0
  44. package/dist/providers/openai.d.ts.map +1 -0
  45. package/dist/providers/openai.js +251 -0
  46. package/dist/providers/openai.js.map +1 -0
  47. package/dist/router.d.ts +87 -0
  48. package/dist/router.d.ts.map +1 -0
  49. package/dist/router.js +260 -0
  50. package/dist/router.js.map +1 -0
  51. package/dist/stream-decoder.d.ts +112 -0
  52. package/dist/stream-decoder.d.ts.map +1 -0
  53. package/dist/stream-decoder.js +238 -0
  54. package/dist/stream-decoder.js.map +1 -0
  55. package/dist/tools.d.ts +78 -0
  56. package/dist/tools.d.ts.map +1 -0
  57. package/dist/tools.js +207 -0
  58. package/dist/tools.js.map +1 -0
  59. 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"}
@@ -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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"}