novixo-ai 0.1.1

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/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # novixo-ai
2
+
3
+ Unified AI client for Node.js and the browser. One API for **15 AI providers** — with automatic fallback, rate-limit detection, and response caching built in.
4
+
5
+ ```bash
6
+ npm install novixo-ai
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { NovixoAI } from "novixo-ai";
15
+
16
+ const ai = new NovixoAI({
17
+ keys: {
18
+ groq: process.env.GROQ_API_KEY,
19
+ gemini: process.env.GEMINI_API_KEY,
20
+ openai: process.env.OPENAI_API_KEY,
21
+ },
22
+ });
23
+
24
+ // Single prompt
25
+ const text = await ai.ask("Explain recursion in simple terms");
26
+
27
+ // Multi-turn chat
28
+ const response = await ai.chat([
29
+ { role: "user", content: "What is a binary tree?" },
30
+ { role: "assistant", content: "A binary tree is..." },
31
+ { role: "user", content: "Give me an example in JavaScript" },
32
+ ]);
33
+
34
+ console.log(response.text);
35
+ console.log(response.provider); // which provider answered
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Supported providers
41
+
42
+ | Provider | Key name | Default model |
43
+ |----------------|-----------------|---------------------------------------------|
44
+ | Groq | `groq` | llama3-8b-8192 |
45
+ | Google Gemini | `gemini` | gemini-1.5-flash |
46
+ | Anthropic | `anthropic` | claude-haiku-4-5-20251001 |
47
+ | OpenAI | `openai` | gpt-4o-mini |
48
+ | Cohere | `cohere` | command-r-plus |
49
+ | Mistral | `mistral` | mistral-small-latest |
50
+ | Together AI | `together` | meta-llama/Llama-3-8b-chat-hf |
51
+ | Perplexity | `perplexity` | llama-3-sonar-small-32k-chat |
52
+ | Hugging Face | `huggingface` | mistralai/Mistral-7B-Instruct-v0.2 |
53
+ | OpenRouter | `openrouter` | openai/gpt-4o-mini (access to 100+ models) |
54
+ | Fireworks AI | `fireworks` | llama-v3-8b-instruct |
55
+ | DeepSeek | `deepseek` | deepseek-chat |
56
+ | xAI (Grok) | `xai` | grok-beta |
57
+ | AI21 | `ai21` | jamba-instruct |
58
+ | NLP Cloud | `nlpcloud` | finetuned-llama-3-70b |
59
+
60
+ ---
61
+
62
+ ## How fallback works
63
+
64
+ novixo-ai tries providers **left to right** in your priority order:
65
+
66
+ 1. If a provider is **rate limited**, it's skipped and retried after cooldown.
67
+ 2. If a provider **fails**, the next one is tried automatically.
68
+ 3. If **all providers fail**, an error is thrown with details from each attempt.
69
+
70
+ Default order: `groq → gemini → openai → mistral → anthropic → ...`
71
+
72
+ ---
73
+
74
+ ## Configuration
75
+
76
+ ```ts
77
+ const ai = new NovixoAI({
78
+ // Only add keys for providers you have access to
79
+ keys: {
80
+ groq: "gsk_...",
81
+ gemini: "AIza...",
82
+ openai: "sk-...",
83
+ cohere: "...",
84
+ mistral: "...",
85
+ together: "...",
86
+ perplexity: "pplx-...",
87
+ huggingface: "hf_...",
88
+ openrouter: "sk-or-...",
89
+ fireworks: "fw-...",
90
+ deepseek: "...",
91
+ xai: "xai-...",
92
+ ai21: "...",
93
+ nlpcloud: "...",
94
+ anthropic: "sk-ant-...",
95
+ },
96
+
97
+ // Custom priority order
98
+ providers: ["groq", "gemini", "mistral", "openai"],
99
+
100
+ // Override models per provider
101
+ models: {
102
+ openai: "gpt-4o",
103
+ mistral: "mistral-large-latest",
104
+ groq: "llama3-70b-8192",
105
+ },
106
+
107
+ maxTokens: 1024, // default: 1024
108
+ temperature: 0.7, // default: 0.7
109
+ cache: true, // default: true
110
+ cacheTTL: 300_000, // default: 5 minutes
111
+ });
112
+ ```
113
+
114
+ ---
115
+
116
+ ## API
117
+
118
+ ### `ai.ask(prompt, systemPrompt?)`
119
+ Single-turn shorthand. Returns response text as a string.
120
+
121
+ ### `ai.chat(messages, options?)`
122
+ Multi-turn chat. Returns an `AIResponse`:
123
+
124
+ ```ts
125
+ {
126
+ text: string // The model's response
127
+ provider: string // Which provider answered
128
+ model: string // Which model was used
129
+ cached: boolean // Whether this came from cache
130
+ durationMs: number // Time taken in milliseconds
131
+ }
132
+ ```
133
+
134
+ ### `ai.clearCache()`
135
+ Clears the in-memory response cache.
136
+
137
+ ### `ai.cacheSize`
138
+ Number of cached entries.
139
+
140
+ ---
141
+
142
+ ## With system prompts
143
+
144
+ ```ts
145
+ const res = await ai.chat(
146
+ [{ role: "user", content: "Summarise this for me: ..." }],
147
+ { systemPrompt: "You are a concise academic writing assistant." }
148
+ );
149
+ ```
150
+
151
+ ## Force a specific provider for one call
152
+
153
+ ```ts
154
+ const res = await ai.chat(messages, {
155
+ providers: ["openai"], // only use OpenAI for this call
156
+ });
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Part of the NovixoTech ecosystem
162
+
163
+ - [novixo-engine](https://npmjs.com/package/novixo-engine) — Offline-first network SDK
164
+ - [novixo-agent-logger](https://npmjs.com/package/novixo-agent-logger) — AI agent audit trail
165
+ - **novixo-ai** — Multi-provider AI client ← you are here
166
+
167
+ ---
168
+
169
+ ## License
170
+
171
+ MIT © [NovixoTech](https://github.com/NovixoTech)
172
+
package/dist/index.cjs ADDED
@@ -0,0 +1,501 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ NovixoAI: () => NovixoAI
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/cache.ts
28
+ var ResponseCache = class {
29
+ constructor(ttlMs = 5 * 60 * 1e3) {
30
+ this.store = /* @__PURE__ */ new Map();
31
+ this.ttl = ttlMs;
32
+ }
33
+ key(messages, systemPrompt) {
34
+ return JSON.stringify({ messages, systemPrompt });
35
+ }
36
+ get(messages, systemPrompt) {
37
+ const k = this.key(messages, systemPrompt);
38
+ const entry = this.store.get(k);
39
+ if (!entry) return null;
40
+ if (Date.now() > entry.expiresAt) {
41
+ this.store.delete(k);
42
+ return null;
43
+ }
44
+ return entry.value;
45
+ }
46
+ set(messages, value, systemPrompt) {
47
+ const k = this.key(messages, systemPrompt);
48
+ this.store.set(k, { value, expiresAt: Date.now() + this.ttl });
49
+ }
50
+ clear() {
51
+ this.store.clear();
52
+ }
53
+ get size() {
54
+ return this.store.size;
55
+ }
56
+ };
57
+
58
+ // src/providers.ts
59
+ var DEFAULT_MODELS = {
60
+ groq: "llama3-8b-8192",
61
+ gemini: "gemini-1.5-flash",
62
+ anthropic: "claude-haiku-4-5-20251001",
63
+ openai: "gpt-4o-mini",
64
+ cohere: "command-r-plus",
65
+ mistral: "mistral-small-latest",
66
+ together: "meta-llama/Llama-3-8b-chat-hf",
67
+ perplexity: "llama-3-sonar-small-32k-chat",
68
+ huggingface: "mistralai/Mistral-7B-Instruct-v0.2",
69
+ openrouter: "openai/gpt-4o-mini",
70
+ fireworks: "accounts/fireworks/models/llama-v3-8b-instruct",
71
+ deepseek: "deepseek-chat",
72
+ xai: "grok-beta",
73
+ ai21: "jamba-instruct",
74
+ nlpcloud: "finetuned-llama-3-70b"
75
+ };
76
+ var rateLimitedUntil = {};
77
+ function isRateLimited(provider) {
78
+ const until = rateLimitedUntil[provider];
79
+ if (!until) return false;
80
+ if (Date.now() > until) {
81
+ delete rateLimitedUntil[provider];
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+ function markRateLimited(provider, retryAfterMs = 6e4) {
87
+ rateLimitedUntil[provider] = Date.now() + retryAfterMs;
88
+ }
89
+ function getRetryAfter(headers, fallback = 6e4) {
90
+ const val = headers.get("retry-after");
91
+ if (!val) return fallback;
92
+ const secs = parseFloat(val);
93
+ return isNaN(secs) ? fallback : secs * 1e3;
94
+ }
95
+ async function callOpenAICompat(url, authHeader, model, messages, systemPrompt, maxTokens, temperature, provider, extraBody = {}) {
96
+ const res = await fetch(url, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json", ...authHeader },
99
+ body: JSON.stringify({
100
+ model,
101
+ messages: [
102
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
103
+ ...messages
104
+ ],
105
+ max_tokens: maxTokens,
106
+ temperature,
107
+ ...extraBody
108
+ })
109
+ });
110
+ if (res.status === 429) {
111
+ markRateLimited(provider, getRetryAfter(res.headers));
112
+ throw Object.assign(new Error(`${provider} rate limited`), { rateLimited: true });
113
+ }
114
+ if (!res.ok) {
115
+ const err = await res.json().catch(() => ({}));
116
+ throw new Error(err?.error?.message || `${provider} ${res.status}`);
117
+ }
118
+ const data = await res.json();
119
+ return data.choices[0].message.content;
120
+ }
121
+ async function callGroq(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
122
+ return callOpenAICompat(
123
+ "https://api.groq.com/openai/v1/chat/completions",
124
+ { Authorization: `Bearer ${apiKey}` },
125
+ model,
126
+ messages,
127
+ systemPrompt,
128
+ maxTokens,
129
+ temperature,
130
+ "groq"
131
+ );
132
+ }
133
+ async function callOpenAI(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
134
+ return callOpenAICompat(
135
+ "https://api.openai.com/v1/chat/completions",
136
+ { Authorization: `Bearer ${apiKey}` },
137
+ model,
138
+ messages,
139
+ systemPrompt,
140
+ maxTokens,
141
+ temperature,
142
+ "openai"
143
+ );
144
+ }
145
+ async function callMistral(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
146
+ return callOpenAICompat(
147
+ "https://api.mistral.ai/v1/chat/completions",
148
+ { Authorization: `Bearer ${apiKey}` },
149
+ model,
150
+ messages,
151
+ systemPrompt,
152
+ maxTokens,
153
+ temperature,
154
+ "mistral"
155
+ );
156
+ }
157
+ async function callTogether(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
158
+ return callOpenAICompat(
159
+ "https://api.together.xyz/v1/chat/completions",
160
+ { Authorization: `Bearer ${apiKey}` },
161
+ model,
162
+ messages,
163
+ systemPrompt,
164
+ maxTokens,
165
+ temperature,
166
+ "together"
167
+ );
168
+ }
169
+ async function callPerplexity(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
170
+ return callOpenAICompat(
171
+ "https://api.perplexity.ai/chat/completions",
172
+ { Authorization: `Bearer ${apiKey}` },
173
+ model,
174
+ messages,
175
+ systemPrompt,
176
+ maxTokens,
177
+ temperature,
178
+ "perplexity"
179
+ );
180
+ }
181
+ async function callOpenRouter(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
182
+ return callOpenAICompat(
183
+ "https://openrouter.ai/api/v1/chat/completions",
184
+ { Authorization: `Bearer ${apiKey}`, "HTTP-Referer": "https://github.com/NovixoTech/novixo-ai" },
185
+ model,
186
+ messages,
187
+ systemPrompt,
188
+ maxTokens,
189
+ temperature,
190
+ "openrouter"
191
+ );
192
+ }
193
+ async function callFireworks(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
194
+ return callOpenAICompat(
195
+ "https://api.fireworks.ai/inference/v1/chat/completions",
196
+ { Authorization: `Bearer ${apiKey}` },
197
+ model,
198
+ messages,
199
+ systemPrompt,
200
+ maxTokens,
201
+ temperature,
202
+ "fireworks"
203
+ );
204
+ }
205
+ async function callDeepSeek(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
206
+ return callOpenAICompat(
207
+ "https://api.deepseek.com/v1/chat/completions",
208
+ { Authorization: `Bearer ${apiKey}` },
209
+ model,
210
+ messages,
211
+ systemPrompt,
212
+ maxTokens,
213
+ temperature,
214
+ "deepseek"
215
+ );
216
+ }
217
+ async function callXAI(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
218
+ return callOpenAICompat(
219
+ "https://api.x.ai/v1/chat/completions",
220
+ { Authorization: `Bearer ${apiKey}` },
221
+ model,
222
+ messages,
223
+ systemPrompt,
224
+ maxTokens,
225
+ temperature,
226
+ "xai"
227
+ );
228
+ }
229
+ async function callGemini(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
230
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
231
+ const contents = messages.map((m) => ({
232
+ role: m.role === "assistant" ? "model" : "user",
233
+ parts: [{ text: m.content }]
234
+ }));
235
+ const body = {
236
+ contents,
237
+ generationConfig: { maxOutputTokens: maxTokens, temperature }
238
+ };
239
+ if (systemPrompt) body.system_instruction = { parts: [{ text: systemPrompt }] };
240
+ const res = await fetch(url, {
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json" },
243
+ body: JSON.stringify(body)
244
+ });
245
+ if (res.status === 429) {
246
+ markRateLimited("gemini", getRetryAfter(res.headers));
247
+ throw Object.assign(new Error("Gemini rate limited"), { rateLimited: true });
248
+ }
249
+ if (!res.ok) {
250
+ const err = await res.json().catch(() => ({}));
251
+ throw new Error(err?.error?.message || `Gemini ${res.status}`);
252
+ }
253
+ const data = await res.json();
254
+ return data.candidates[0].content.parts[0].text;
255
+ }
256
+ async function callAnthropic(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
257
+ const body = {
258
+ model,
259
+ max_tokens: maxTokens,
260
+ temperature,
261
+ messages: messages.filter((m) => m.role !== "system")
262
+ };
263
+ if (systemPrompt) body.system = systemPrompt;
264
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
265
+ method: "POST",
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ "x-api-key": apiKey,
269
+ "anthropic-version": "2023-06-01"
270
+ },
271
+ body: JSON.stringify(body)
272
+ });
273
+ if (res.status === 429) {
274
+ markRateLimited("anthropic", getRetryAfter(res.headers));
275
+ throw Object.assign(new Error("Anthropic rate limited"), { rateLimited: true });
276
+ }
277
+ if (!res.ok) {
278
+ const err = await res.json().catch(() => ({}));
279
+ throw new Error(err?.error?.message || `Anthropic ${res.status}`);
280
+ }
281
+ const data = await res.json();
282
+ return data.content[0].text;
283
+ }
284
+ async function callCohere(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
285
+ const history = messages.slice(0, -1).map((m) => ({
286
+ role: m.role === "assistant" ? "CHATBOT" : "USER",
287
+ message: m.content
288
+ }));
289
+ const lastMessage = messages[messages.length - 1]?.content ?? "";
290
+ const res = await fetch("https://api.cohere.com/v1/chat", {
291
+ method: "POST",
292
+ headers: {
293
+ "Content-Type": "application/json",
294
+ Authorization: `Bearer ${apiKey}`
295
+ },
296
+ body: JSON.stringify({
297
+ model,
298
+ message: lastMessage,
299
+ chat_history: history,
300
+ preamble: systemPrompt,
301
+ max_tokens: maxTokens,
302
+ temperature
303
+ })
304
+ });
305
+ if (res.status === 429) {
306
+ markRateLimited("cohere", getRetryAfter(res.headers));
307
+ throw Object.assign(new Error("Cohere rate limited"), { rateLimited: true });
308
+ }
309
+ if (!res.ok) {
310
+ const err = await res.json().catch(() => ({}));
311
+ throw new Error(err?.message || `Cohere ${res.status}`);
312
+ }
313
+ const data = await res.json();
314
+ return data.text;
315
+ }
316
+ async function callHuggingFace(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
317
+ return callOpenAICompat(
318
+ `https://api-inference.huggingface.co/models/${model}/v1/chat/completions`,
319
+ { Authorization: `Bearer ${apiKey}` },
320
+ model,
321
+ messages,
322
+ systemPrompt,
323
+ maxTokens,
324
+ temperature,
325
+ "huggingface"
326
+ );
327
+ }
328
+ async function callAI21(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
329
+ return callOpenAICompat(
330
+ "https://api.ai21.com/studio/v1/chat/completions",
331
+ { Authorization: `Bearer ${apiKey}` },
332
+ model,
333
+ messages,
334
+ systemPrompt,
335
+ maxTokens,
336
+ temperature,
337
+ "ai21"
338
+ );
339
+ }
340
+ async function callNLPCloud(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
341
+ const allMessages = [
342
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
343
+ ...messages
344
+ ];
345
+ const res = await fetch(`https://api.nlpcloud.io/v1/gpu/${model}/chatbot`, {
346
+ method: "POST",
347
+ headers: {
348
+ "Content-Type": "application/json",
349
+ Authorization: `Token ${apiKey}`
350
+ },
351
+ body: JSON.stringify({
352
+ input: allMessages[allMessages.length - 1]?.content ?? "",
353
+ history: allMessages.slice(0, -1).map((m) => ({
354
+ input: m.role === "user" ? m.content : void 0,
355
+ response: m.role === "assistant" ? m.content : void 0
356
+ })),
357
+ max_length: maxTokens,
358
+ temperature
359
+ })
360
+ });
361
+ if (res.status === 429) {
362
+ markRateLimited("nlpcloud", getRetryAfter(res.headers));
363
+ throw Object.assign(new Error("NLPCloud rate limited"), { rateLimited: true });
364
+ }
365
+ if (!res.ok) {
366
+ const err = await res.json().catch(() => ({}));
367
+ throw new Error(err?.detail || `NLPCloud ${res.status}`);
368
+ }
369
+ const data = await res.json();
370
+ return data.response;
371
+ }
372
+ async function callProvider(provider, messages, systemPrompt, apiKey, modelOverride, maxTokens, temperature) {
373
+ const model = modelOverride ?? DEFAULT_MODELS[provider];
374
+ const map = {
375
+ groq: callGroq,
376
+ openai: callOpenAI,
377
+ mistral: callMistral,
378
+ together: callTogether,
379
+ perplexity: callPerplexity,
380
+ openrouter: callOpenRouter,
381
+ fireworks: callFireworks,
382
+ deepseek: callDeepSeek,
383
+ xai: callXAI,
384
+ gemini: callGemini,
385
+ anthropic: callAnthropic,
386
+ cohere: callCohere,
387
+ huggingface: callHuggingFace,
388
+ ai21: callAI21,
389
+ nlpcloud: callNLPCloud
390
+ };
391
+ const fn = map[provider];
392
+ if (!fn) throw new Error(`Unknown provider: ${provider}`);
393
+ return fn(messages, systemPrompt, apiKey, model, maxTokens, temperature);
394
+ }
395
+
396
+ // src/client.ts
397
+ var DEFAULT_PROVIDERS = ["groq", "gemini", "anthropic"];
398
+ var NovixoAI = class {
399
+ constructor(config) {
400
+ this.config = {
401
+ keys: config.keys,
402
+ providers: config.providers ?? DEFAULT_PROVIDERS,
403
+ models: config.models ?? {},
404
+ maxTokens: config.maxTokens ?? 1024,
405
+ temperature: config.temperature ?? 0.7,
406
+ cache: config.cache ?? true,
407
+ cacheTTL: config.cacheTTL ?? 5 * 60 * 1e3
408
+ };
409
+ this.cache = this.config.cache ? new ResponseCache(this.config.cacheTTL) : null;
410
+ }
411
+ /**
412
+ * Send a message and get a response.
413
+ * Tries providers in order, skipping rate-limited ones.
414
+ * Falls back automatically on failure.
415
+ *
416
+ * @example
417
+ * const res = await ai.chat([{ role: "user", content: "Explain recursion" }])
418
+ * console.log(res.text)
419
+ */
420
+ async chat(messages, options = {}) {
421
+ const systemPrompt = options.systemPrompt;
422
+ const providers = options.providers ?? this.config.providers;
423
+ const errors = [];
424
+ if (this.cache) {
425
+ const cached = this.cache.get(messages, systemPrompt);
426
+ if (cached) {
427
+ return {
428
+ text: cached,
429
+ provider: "groq",
430
+ // placeholder; cached means we don't know original
431
+ model: "cached",
432
+ cached: true,
433
+ durationMs: 0
434
+ };
435
+ }
436
+ }
437
+ for (const provider of providers) {
438
+ const apiKey = this.config.keys[provider];
439
+ if (!apiKey) continue;
440
+ if (isRateLimited(provider)) {
441
+ errors.push({ provider, message: "Rate limited, skipping", rateLimited: true });
442
+ continue;
443
+ }
444
+ const start = Date.now();
445
+ try {
446
+ const text = await callProvider(
447
+ provider,
448
+ messages,
449
+ systemPrompt,
450
+ apiKey,
451
+ this.config.models[provider],
452
+ this.config.maxTokens,
453
+ this.config.temperature
454
+ );
455
+ const response = {
456
+ text,
457
+ provider,
458
+ model: this.config.models[provider] ?? DEFAULT_MODELS[provider],
459
+ cached: false,
460
+ durationMs: Date.now() - start
461
+ };
462
+ if (this.cache) {
463
+ this.cache.set(messages, text, systemPrompt);
464
+ }
465
+ return response;
466
+ } catch (err) {
467
+ const message = err instanceof Error ? err.message : String(err);
468
+ const rateLimited = err?.rateLimited === true;
469
+ errors.push({ provider, message, rateLimited });
470
+ console.warn(`[novixo-ai] ${provider} failed: ${message}`);
471
+ }
472
+ }
473
+ const summary = errors.map((e) => `${e.provider}: ${e.message}`).join(" | ");
474
+ throw new Error(`All AI providers failed. ${summary}`);
475
+ }
476
+ /**
477
+ * Convenience: single-turn prompt → response string.
478
+ *
479
+ * @example
480
+ * const text = await ai.ask("What is photosynthesis?")
481
+ */
482
+ async ask(prompt, systemPrompt) {
483
+ const res = await this.chat(
484
+ [{ role: "user", content: prompt }],
485
+ { systemPrompt }
486
+ );
487
+ return res.text;
488
+ }
489
+ /** Clear the response cache */
490
+ clearCache() {
491
+ this.cache?.clear();
492
+ }
493
+ /** How many entries are in the cache */
494
+ get cacheSize() {
495
+ return this.cache?.size ?? 0;
496
+ }
497
+ };
498
+ // Annotate the CommonJS export names for ESM import in node:
499
+ 0 && (module.exports = {
500
+ NovixoAI
501
+ });
@@ -0,0 +1,69 @@
1
+ type Provider = "groq" | "gemini" | "anthropic" | "openai" | "cohere" | "mistral" | "together" | "perplexity" | "huggingface" | "openrouter" | "fireworks" | "deepseek" | "xai" | "ai21" | "nlpcloud";
2
+ interface Message {
3
+ role: "user" | "assistant" | "system";
4
+ content: string;
5
+ }
6
+ interface NovixoAIConfig {
7
+ /** API keys for each provider */
8
+ keys: Partial<Record<Provider, string>>;
9
+ /**
10
+ * Provider priority order. novixo-ai tries them left to right.
11
+ * Defaults to ["groq", "gemini", "openai", "mistral", "anthropic", ...]
12
+ */
13
+ providers?: Provider[];
14
+ /** Default model per provider. Override if needed. */
15
+ models?: Partial<Record<Provider, string>>;
16
+ /** Max tokens to generate (default: 1024) */
17
+ maxTokens?: number;
18
+ /** Temperature 0–1 (default: 0.7) */
19
+ temperature?: number;
20
+ /** Enable response caching (default: true) */
21
+ cache?: boolean;
22
+ /** Cache TTL in milliseconds (default: 5 minutes) */
23
+ cacheTTL?: number;
24
+ }
25
+ interface AIResponse {
26
+ text: string;
27
+ provider: Provider;
28
+ model: string;
29
+ cached: boolean;
30
+ durationMs: number;
31
+ }
32
+ interface AIError {
33
+ provider: Provider;
34
+ message: string;
35
+ rateLimited: boolean;
36
+ }
37
+
38
+ declare class NovixoAI {
39
+ private config;
40
+ private cache;
41
+ constructor(config: NovixoAIConfig);
42
+ /**
43
+ * Send a message and get a response.
44
+ * Tries providers in order, skipping rate-limited ones.
45
+ * Falls back automatically on failure.
46
+ *
47
+ * @example
48
+ * const res = await ai.chat([{ role: "user", content: "Explain recursion" }])
49
+ * console.log(res.text)
50
+ */
51
+ chat(messages: Message[], options?: {
52
+ systemPrompt?: string;
53
+ /** Override provider order for this call only */
54
+ providers?: Provider[];
55
+ }): Promise<AIResponse>;
56
+ /**
57
+ * Convenience: single-turn prompt → response string.
58
+ *
59
+ * @example
60
+ * const text = await ai.ask("What is photosynthesis?")
61
+ */
62
+ ask(prompt: string, systemPrompt?: string): Promise<string>;
63
+ /** Clear the response cache */
64
+ clearCache(): void;
65
+ /** How many entries are in the cache */
66
+ get cacheSize(): number;
67
+ }
68
+
69
+ export { type AIError, type AIResponse, type Message, NovixoAI, type NovixoAIConfig, type Provider };
@@ -0,0 +1,69 @@
1
+ type Provider = "groq" | "gemini" | "anthropic" | "openai" | "cohere" | "mistral" | "together" | "perplexity" | "huggingface" | "openrouter" | "fireworks" | "deepseek" | "xai" | "ai21" | "nlpcloud";
2
+ interface Message {
3
+ role: "user" | "assistant" | "system";
4
+ content: string;
5
+ }
6
+ interface NovixoAIConfig {
7
+ /** API keys for each provider */
8
+ keys: Partial<Record<Provider, string>>;
9
+ /**
10
+ * Provider priority order. novixo-ai tries them left to right.
11
+ * Defaults to ["groq", "gemini", "openai", "mistral", "anthropic", ...]
12
+ */
13
+ providers?: Provider[];
14
+ /** Default model per provider. Override if needed. */
15
+ models?: Partial<Record<Provider, string>>;
16
+ /** Max tokens to generate (default: 1024) */
17
+ maxTokens?: number;
18
+ /** Temperature 0–1 (default: 0.7) */
19
+ temperature?: number;
20
+ /** Enable response caching (default: true) */
21
+ cache?: boolean;
22
+ /** Cache TTL in milliseconds (default: 5 minutes) */
23
+ cacheTTL?: number;
24
+ }
25
+ interface AIResponse {
26
+ text: string;
27
+ provider: Provider;
28
+ model: string;
29
+ cached: boolean;
30
+ durationMs: number;
31
+ }
32
+ interface AIError {
33
+ provider: Provider;
34
+ message: string;
35
+ rateLimited: boolean;
36
+ }
37
+
38
+ declare class NovixoAI {
39
+ private config;
40
+ private cache;
41
+ constructor(config: NovixoAIConfig);
42
+ /**
43
+ * Send a message and get a response.
44
+ * Tries providers in order, skipping rate-limited ones.
45
+ * Falls back automatically on failure.
46
+ *
47
+ * @example
48
+ * const res = await ai.chat([{ role: "user", content: "Explain recursion" }])
49
+ * console.log(res.text)
50
+ */
51
+ chat(messages: Message[], options?: {
52
+ systemPrompt?: string;
53
+ /** Override provider order for this call only */
54
+ providers?: Provider[];
55
+ }): Promise<AIResponse>;
56
+ /**
57
+ * Convenience: single-turn prompt → response string.
58
+ *
59
+ * @example
60
+ * const text = await ai.ask("What is photosynthesis?")
61
+ */
62
+ ask(prompt: string, systemPrompt?: string): Promise<string>;
63
+ /** Clear the response cache */
64
+ clearCache(): void;
65
+ /** How many entries are in the cache */
66
+ get cacheSize(): number;
67
+ }
68
+
69
+ export { type AIError, type AIResponse, type Message, NovixoAI, type NovixoAIConfig, type Provider };
package/dist/index.js ADDED
@@ -0,0 +1,474 @@
1
+ // src/cache.ts
2
+ var ResponseCache = class {
3
+ constructor(ttlMs = 5 * 60 * 1e3) {
4
+ this.store = /* @__PURE__ */ new Map();
5
+ this.ttl = ttlMs;
6
+ }
7
+ key(messages, systemPrompt) {
8
+ return JSON.stringify({ messages, systemPrompt });
9
+ }
10
+ get(messages, systemPrompt) {
11
+ const k = this.key(messages, systemPrompt);
12
+ const entry = this.store.get(k);
13
+ if (!entry) return null;
14
+ if (Date.now() > entry.expiresAt) {
15
+ this.store.delete(k);
16
+ return null;
17
+ }
18
+ return entry.value;
19
+ }
20
+ set(messages, value, systemPrompt) {
21
+ const k = this.key(messages, systemPrompt);
22
+ this.store.set(k, { value, expiresAt: Date.now() + this.ttl });
23
+ }
24
+ clear() {
25
+ this.store.clear();
26
+ }
27
+ get size() {
28
+ return this.store.size;
29
+ }
30
+ };
31
+
32
+ // src/providers.ts
33
+ var DEFAULT_MODELS = {
34
+ groq: "llama3-8b-8192",
35
+ gemini: "gemini-1.5-flash",
36
+ anthropic: "claude-haiku-4-5-20251001",
37
+ openai: "gpt-4o-mini",
38
+ cohere: "command-r-plus",
39
+ mistral: "mistral-small-latest",
40
+ together: "meta-llama/Llama-3-8b-chat-hf",
41
+ perplexity: "llama-3-sonar-small-32k-chat",
42
+ huggingface: "mistralai/Mistral-7B-Instruct-v0.2",
43
+ openrouter: "openai/gpt-4o-mini",
44
+ fireworks: "accounts/fireworks/models/llama-v3-8b-instruct",
45
+ deepseek: "deepseek-chat",
46
+ xai: "grok-beta",
47
+ ai21: "jamba-instruct",
48
+ nlpcloud: "finetuned-llama-3-70b"
49
+ };
50
+ var rateLimitedUntil = {};
51
+ function isRateLimited(provider) {
52
+ const until = rateLimitedUntil[provider];
53
+ if (!until) return false;
54
+ if (Date.now() > until) {
55
+ delete rateLimitedUntil[provider];
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ function markRateLimited(provider, retryAfterMs = 6e4) {
61
+ rateLimitedUntil[provider] = Date.now() + retryAfterMs;
62
+ }
63
+ function getRetryAfter(headers, fallback = 6e4) {
64
+ const val = headers.get("retry-after");
65
+ if (!val) return fallback;
66
+ const secs = parseFloat(val);
67
+ return isNaN(secs) ? fallback : secs * 1e3;
68
+ }
69
+ async function callOpenAICompat(url, authHeader, model, messages, systemPrompt, maxTokens, temperature, provider, extraBody = {}) {
70
+ const res = await fetch(url, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json", ...authHeader },
73
+ body: JSON.stringify({
74
+ model,
75
+ messages: [
76
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
77
+ ...messages
78
+ ],
79
+ max_tokens: maxTokens,
80
+ temperature,
81
+ ...extraBody
82
+ })
83
+ });
84
+ if (res.status === 429) {
85
+ markRateLimited(provider, getRetryAfter(res.headers));
86
+ throw Object.assign(new Error(`${provider} rate limited`), { rateLimited: true });
87
+ }
88
+ if (!res.ok) {
89
+ const err = await res.json().catch(() => ({}));
90
+ throw new Error(err?.error?.message || `${provider} ${res.status}`);
91
+ }
92
+ const data = await res.json();
93
+ return data.choices[0].message.content;
94
+ }
95
+ async function callGroq(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
96
+ return callOpenAICompat(
97
+ "https://api.groq.com/openai/v1/chat/completions",
98
+ { Authorization: `Bearer ${apiKey}` },
99
+ model,
100
+ messages,
101
+ systemPrompt,
102
+ maxTokens,
103
+ temperature,
104
+ "groq"
105
+ );
106
+ }
107
+ async function callOpenAI(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
108
+ return callOpenAICompat(
109
+ "https://api.openai.com/v1/chat/completions",
110
+ { Authorization: `Bearer ${apiKey}` },
111
+ model,
112
+ messages,
113
+ systemPrompt,
114
+ maxTokens,
115
+ temperature,
116
+ "openai"
117
+ );
118
+ }
119
+ async function callMistral(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
120
+ return callOpenAICompat(
121
+ "https://api.mistral.ai/v1/chat/completions",
122
+ { Authorization: `Bearer ${apiKey}` },
123
+ model,
124
+ messages,
125
+ systemPrompt,
126
+ maxTokens,
127
+ temperature,
128
+ "mistral"
129
+ );
130
+ }
131
+ async function callTogether(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
132
+ return callOpenAICompat(
133
+ "https://api.together.xyz/v1/chat/completions",
134
+ { Authorization: `Bearer ${apiKey}` },
135
+ model,
136
+ messages,
137
+ systemPrompt,
138
+ maxTokens,
139
+ temperature,
140
+ "together"
141
+ );
142
+ }
143
+ async function callPerplexity(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
144
+ return callOpenAICompat(
145
+ "https://api.perplexity.ai/chat/completions",
146
+ { Authorization: `Bearer ${apiKey}` },
147
+ model,
148
+ messages,
149
+ systemPrompt,
150
+ maxTokens,
151
+ temperature,
152
+ "perplexity"
153
+ );
154
+ }
155
+ async function callOpenRouter(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
156
+ return callOpenAICompat(
157
+ "https://openrouter.ai/api/v1/chat/completions",
158
+ { Authorization: `Bearer ${apiKey}`, "HTTP-Referer": "https://github.com/NovixoTech/novixo-ai" },
159
+ model,
160
+ messages,
161
+ systemPrompt,
162
+ maxTokens,
163
+ temperature,
164
+ "openrouter"
165
+ );
166
+ }
167
+ async function callFireworks(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
168
+ return callOpenAICompat(
169
+ "https://api.fireworks.ai/inference/v1/chat/completions",
170
+ { Authorization: `Bearer ${apiKey}` },
171
+ model,
172
+ messages,
173
+ systemPrompt,
174
+ maxTokens,
175
+ temperature,
176
+ "fireworks"
177
+ );
178
+ }
179
+ async function callDeepSeek(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
180
+ return callOpenAICompat(
181
+ "https://api.deepseek.com/v1/chat/completions",
182
+ { Authorization: `Bearer ${apiKey}` },
183
+ model,
184
+ messages,
185
+ systemPrompt,
186
+ maxTokens,
187
+ temperature,
188
+ "deepseek"
189
+ );
190
+ }
191
+ async function callXAI(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
192
+ return callOpenAICompat(
193
+ "https://api.x.ai/v1/chat/completions",
194
+ { Authorization: `Bearer ${apiKey}` },
195
+ model,
196
+ messages,
197
+ systemPrompt,
198
+ maxTokens,
199
+ temperature,
200
+ "xai"
201
+ );
202
+ }
203
+ async function callGemini(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
204
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
205
+ const contents = messages.map((m) => ({
206
+ role: m.role === "assistant" ? "model" : "user",
207
+ parts: [{ text: m.content }]
208
+ }));
209
+ const body = {
210
+ contents,
211
+ generationConfig: { maxOutputTokens: maxTokens, temperature }
212
+ };
213
+ if (systemPrompt) body.system_instruction = { parts: [{ text: systemPrompt }] };
214
+ const res = await fetch(url, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify(body)
218
+ });
219
+ if (res.status === 429) {
220
+ markRateLimited("gemini", getRetryAfter(res.headers));
221
+ throw Object.assign(new Error("Gemini rate limited"), { rateLimited: true });
222
+ }
223
+ if (!res.ok) {
224
+ const err = await res.json().catch(() => ({}));
225
+ throw new Error(err?.error?.message || `Gemini ${res.status}`);
226
+ }
227
+ const data = await res.json();
228
+ return data.candidates[0].content.parts[0].text;
229
+ }
230
+ async function callAnthropic(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
231
+ const body = {
232
+ model,
233
+ max_tokens: maxTokens,
234
+ temperature,
235
+ messages: messages.filter((m) => m.role !== "system")
236
+ };
237
+ if (systemPrompt) body.system = systemPrompt;
238
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
239
+ method: "POST",
240
+ headers: {
241
+ "Content-Type": "application/json",
242
+ "x-api-key": apiKey,
243
+ "anthropic-version": "2023-06-01"
244
+ },
245
+ body: JSON.stringify(body)
246
+ });
247
+ if (res.status === 429) {
248
+ markRateLimited("anthropic", getRetryAfter(res.headers));
249
+ throw Object.assign(new Error("Anthropic rate limited"), { rateLimited: true });
250
+ }
251
+ if (!res.ok) {
252
+ const err = await res.json().catch(() => ({}));
253
+ throw new Error(err?.error?.message || `Anthropic ${res.status}`);
254
+ }
255
+ const data = await res.json();
256
+ return data.content[0].text;
257
+ }
258
+ async function callCohere(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
259
+ const history = messages.slice(0, -1).map((m) => ({
260
+ role: m.role === "assistant" ? "CHATBOT" : "USER",
261
+ message: m.content
262
+ }));
263
+ const lastMessage = messages[messages.length - 1]?.content ?? "";
264
+ const res = await fetch("https://api.cohere.com/v1/chat", {
265
+ method: "POST",
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ Authorization: `Bearer ${apiKey}`
269
+ },
270
+ body: JSON.stringify({
271
+ model,
272
+ message: lastMessage,
273
+ chat_history: history,
274
+ preamble: systemPrompt,
275
+ max_tokens: maxTokens,
276
+ temperature
277
+ })
278
+ });
279
+ if (res.status === 429) {
280
+ markRateLimited("cohere", getRetryAfter(res.headers));
281
+ throw Object.assign(new Error("Cohere rate limited"), { rateLimited: true });
282
+ }
283
+ if (!res.ok) {
284
+ const err = await res.json().catch(() => ({}));
285
+ throw new Error(err?.message || `Cohere ${res.status}`);
286
+ }
287
+ const data = await res.json();
288
+ return data.text;
289
+ }
290
+ async function callHuggingFace(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
291
+ return callOpenAICompat(
292
+ `https://api-inference.huggingface.co/models/${model}/v1/chat/completions`,
293
+ { Authorization: `Bearer ${apiKey}` },
294
+ model,
295
+ messages,
296
+ systemPrompt,
297
+ maxTokens,
298
+ temperature,
299
+ "huggingface"
300
+ );
301
+ }
302
+ async function callAI21(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
303
+ return callOpenAICompat(
304
+ "https://api.ai21.com/studio/v1/chat/completions",
305
+ { Authorization: `Bearer ${apiKey}` },
306
+ model,
307
+ messages,
308
+ systemPrompt,
309
+ maxTokens,
310
+ temperature,
311
+ "ai21"
312
+ );
313
+ }
314
+ async function callNLPCloud(messages, systemPrompt, apiKey, model, maxTokens, temperature) {
315
+ const allMessages = [
316
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
317
+ ...messages
318
+ ];
319
+ const res = await fetch(`https://api.nlpcloud.io/v1/gpu/${model}/chatbot`, {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ Authorization: `Token ${apiKey}`
324
+ },
325
+ body: JSON.stringify({
326
+ input: allMessages[allMessages.length - 1]?.content ?? "",
327
+ history: allMessages.slice(0, -1).map((m) => ({
328
+ input: m.role === "user" ? m.content : void 0,
329
+ response: m.role === "assistant" ? m.content : void 0
330
+ })),
331
+ max_length: maxTokens,
332
+ temperature
333
+ })
334
+ });
335
+ if (res.status === 429) {
336
+ markRateLimited("nlpcloud", getRetryAfter(res.headers));
337
+ throw Object.assign(new Error("NLPCloud rate limited"), { rateLimited: true });
338
+ }
339
+ if (!res.ok) {
340
+ const err = await res.json().catch(() => ({}));
341
+ throw new Error(err?.detail || `NLPCloud ${res.status}`);
342
+ }
343
+ const data = await res.json();
344
+ return data.response;
345
+ }
346
+ async function callProvider(provider, messages, systemPrompt, apiKey, modelOverride, maxTokens, temperature) {
347
+ const model = modelOverride ?? DEFAULT_MODELS[provider];
348
+ const map = {
349
+ groq: callGroq,
350
+ openai: callOpenAI,
351
+ mistral: callMistral,
352
+ together: callTogether,
353
+ perplexity: callPerplexity,
354
+ openrouter: callOpenRouter,
355
+ fireworks: callFireworks,
356
+ deepseek: callDeepSeek,
357
+ xai: callXAI,
358
+ gemini: callGemini,
359
+ anthropic: callAnthropic,
360
+ cohere: callCohere,
361
+ huggingface: callHuggingFace,
362
+ ai21: callAI21,
363
+ nlpcloud: callNLPCloud
364
+ };
365
+ const fn = map[provider];
366
+ if (!fn) throw new Error(`Unknown provider: ${provider}`);
367
+ return fn(messages, systemPrompt, apiKey, model, maxTokens, temperature);
368
+ }
369
+
370
+ // src/client.ts
371
+ var DEFAULT_PROVIDERS = ["groq", "gemini", "anthropic"];
372
+ var NovixoAI = class {
373
+ constructor(config) {
374
+ this.config = {
375
+ keys: config.keys,
376
+ providers: config.providers ?? DEFAULT_PROVIDERS,
377
+ models: config.models ?? {},
378
+ maxTokens: config.maxTokens ?? 1024,
379
+ temperature: config.temperature ?? 0.7,
380
+ cache: config.cache ?? true,
381
+ cacheTTL: config.cacheTTL ?? 5 * 60 * 1e3
382
+ };
383
+ this.cache = this.config.cache ? new ResponseCache(this.config.cacheTTL) : null;
384
+ }
385
+ /**
386
+ * Send a message and get a response.
387
+ * Tries providers in order, skipping rate-limited ones.
388
+ * Falls back automatically on failure.
389
+ *
390
+ * @example
391
+ * const res = await ai.chat([{ role: "user", content: "Explain recursion" }])
392
+ * console.log(res.text)
393
+ */
394
+ async chat(messages, options = {}) {
395
+ const systemPrompt = options.systemPrompt;
396
+ const providers = options.providers ?? this.config.providers;
397
+ const errors = [];
398
+ if (this.cache) {
399
+ const cached = this.cache.get(messages, systemPrompt);
400
+ if (cached) {
401
+ return {
402
+ text: cached,
403
+ provider: "groq",
404
+ // placeholder; cached means we don't know original
405
+ model: "cached",
406
+ cached: true,
407
+ durationMs: 0
408
+ };
409
+ }
410
+ }
411
+ for (const provider of providers) {
412
+ const apiKey = this.config.keys[provider];
413
+ if (!apiKey) continue;
414
+ if (isRateLimited(provider)) {
415
+ errors.push({ provider, message: "Rate limited, skipping", rateLimited: true });
416
+ continue;
417
+ }
418
+ const start = Date.now();
419
+ try {
420
+ const text = await callProvider(
421
+ provider,
422
+ messages,
423
+ systemPrompt,
424
+ apiKey,
425
+ this.config.models[provider],
426
+ this.config.maxTokens,
427
+ this.config.temperature
428
+ );
429
+ const response = {
430
+ text,
431
+ provider,
432
+ model: this.config.models[provider] ?? DEFAULT_MODELS[provider],
433
+ cached: false,
434
+ durationMs: Date.now() - start
435
+ };
436
+ if (this.cache) {
437
+ this.cache.set(messages, text, systemPrompt);
438
+ }
439
+ return response;
440
+ } catch (err) {
441
+ const message = err instanceof Error ? err.message : String(err);
442
+ const rateLimited = err?.rateLimited === true;
443
+ errors.push({ provider, message, rateLimited });
444
+ console.warn(`[novixo-ai] ${provider} failed: ${message}`);
445
+ }
446
+ }
447
+ const summary = errors.map((e) => `${e.provider}: ${e.message}`).join(" | ");
448
+ throw new Error(`All AI providers failed. ${summary}`);
449
+ }
450
+ /**
451
+ * Convenience: single-turn prompt → response string.
452
+ *
453
+ * @example
454
+ * const text = await ai.ask("What is photosynthesis?")
455
+ */
456
+ async ask(prompt, systemPrompt) {
457
+ const res = await this.chat(
458
+ [{ role: "user", content: prompt }],
459
+ { systemPrompt }
460
+ );
461
+ return res.text;
462
+ }
463
+ /** Clear the response cache */
464
+ clearCache() {
465
+ this.cache?.clear();
466
+ }
467
+ /** How many entries are in the cache */
468
+ get cacheSize() {
469
+ return this.cache?.size ?? 0;
470
+ }
471
+ };
472
+ export {
473
+ NovixoAI
474
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "novixo-ai",
3
+ "version": "0.1.1",
4
+ "description": "Unified AI provider client with auto-fallback, rate-limit detection, and response caching. Works with Groq, Gemini, and Anthropic.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm,cjs --dts",
20
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
21
+ "test": "node --experimental-vm-modules node_modules/.bin/jest"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "groq",
26
+ "gemini",
27
+ "anthropic",
28
+ "llm",
29
+ "fallback",
30
+ "multi-provider",
31
+ "novixo"
32
+ ],
33
+ "author": "NovixoTech",
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0"
38
+ }
39
+ }