@imenam/unified-llm-client 2.1.2 → 2.3.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 (39) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +383 -359
  3. package/dist/adapters/openrouter/OpenRouterLogAdapter.d.ts +11 -0
  4. package/dist/adapters/openrouter/OpenRouterLogAdapter.d.ts.map +1 -0
  5. package/dist/adapters/openrouter/OpenRouterLogAdapter.js +51 -0
  6. package/dist/adapters/openrouter/OpenRouterOutputAdapter.d.ts +6 -0
  7. package/dist/adapters/openrouter/OpenRouterOutputAdapter.d.ts.map +1 -0
  8. package/dist/adapters/openrouter/OpenRouterOutputAdapter.js +9 -0
  9. package/dist/adapters/openrouter/OpenRouterPromptAdapter.d.ts +15 -0
  10. package/dist/adapters/openrouter/OpenRouterPromptAdapter.d.ts.map +1 -0
  11. package/dist/adapters/openrouter/OpenRouterPromptAdapter.js +374 -0
  12. package/dist/adapters/openrouter/OpenRouterToolAdapter.d.ts +6 -0
  13. package/dist/adapters/openrouter/OpenRouterToolAdapter.d.ts.map +1 -0
  14. package/dist/adapters/openrouter/OpenRouterToolAdapter.js +16 -0
  15. package/dist/adapters/qwen/QwenLogAdapter.d.ts +5 -0
  16. package/dist/adapters/qwen/QwenLogAdapter.d.ts.map +1 -1
  17. package/dist/adapters/qwen/QwenLogAdapter.js +18 -9
  18. package/dist/adapters/qwen/QwenPromptAdapter.d.ts.map +1 -1
  19. package/dist/adapters/qwen/QwenPromptAdapter.js +136 -79
  20. package/dist/common/types.d.ts +10 -1
  21. package/dist/common/types.d.ts.map +1 -1
  22. package/dist/core/LLMAdapter.d.ts.map +1 -1
  23. package/dist/core/LLMAdapter.js +16 -0
  24. package/dist/core/Models.d.ts +7 -0
  25. package/dist/core/Models.d.ts.map +1 -1
  26. package/dist/core/Models.js +21 -0
  27. package/dist/core/UnifiedLLMClient.d.ts +2 -2
  28. package/dist/core/UnifiedLLMClient.d.ts.map +1 -1
  29. package/dist/core/UnifiedLLMClient.js +1 -1
  30. package/dist/examples/audio-example.js +1 -1
  31. package/dist/examples/example.js +16 -16
  32. package/dist/examples/qwen-omni-audio-example.d.ts +2 -0
  33. package/dist/examples/qwen-omni-audio-example.d.ts.map +1 -0
  34. package/dist/examples/qwen-omni-audio-example.js +324 -0
  35. package/dist/examples/qwen-vl-example.js +7 -7
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -1
  39. package/package.json +71 -70
@@ -0,0 +1,324 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ const UnifiedLLMClient_1 = require("../core/UnifiedLLMClient");
46
+ const Models_1 = require("../core/Models");
47
+ const child_process_1 = require("child_process");
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ /**
51
+ * Test d'intégration Qwen-Omni : transcription audio horodatée.
52
+ *
53
+ * Vérifie que la sortie est un JSON exploitable, aux timecodes croissants et
54
+ * compris dans la durée du fichier.
55
+ *
56
+ * ALIBABA_CLOUD_API_KEY=... npm run example:qwen-omni -- ./chemin/audio.mp3 [--tools]
57
+ * npm run example:qwen-omni -- ./reponse.txt [duree_en_secondes]
58
+ */
59
+ const TRANSCRIPTION_PROMPT = `Transcris cet extrait audio intégralement, en français.
60
+ Réponds UNIQUEMENT avec un tableau JSON, sans texte autour et sans bloc de code.
61
+ Chaque élément représente une phrase et suit ce format exact :
62
+ {"timecode": "MM:SS", "speaker": "nom ou Inconnu", "text": "la phrase transcrite"}
63
+ Le timecode est l'instant de DÉBUT de la phrase, relatif au début de l'extrait.`;
64
+ /** Prompt du mode function calling : le format est défini par le schéma de l'outil. */
65
+ const TRANSCRIPTION_PROMPT_TOOLS = `Transcris cet extrait audio intégralement, en français,
66
+ puis transmets le résultat via l'outil submit_transcription.`;
67
+ const TRANSCRIPTION_TOOL = {
68
+ name: "submit_transcription",
69
+ description: "Enregistre la transcription horodatée d'un extrait audio.",
70
+ parameters: {
71
+ type: "object",
72
+ properties: {
73
+ segments: {
74
+ type: "array",
75
+ description: "Une entrée par phrase, dans l'ordre chronologique.",
76
+ items: {
77
+ type: "object",
78
+ properties: {
79
+ timecode: { type: "string", description: "Début de la phrase, au format MM:SS." },
80
+ speaker: { type: "string", description: "Nom du locuteur, ou 'Inconnu'." },
81
+ text: { type: "string", description: "La phrase transcrite." },
82
+ },
83
+ required: ["timecode", "speaker", "text"],
84
+ },
85
+ },
86
+ },
87
+ required: ["segments"],
88
+ },
89
+ };
90
+ /** Durée réelle du fichier, via ffprobe. Renvoie null si ffprobe est absent. */
91
+ function probeDurationSeconds(filePath) {
92
+ try {
93
+ const out = (0, child_process_1.execFileSync)("ffprobe", ["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { encoding: "utf-8" });
94
+ const seconds = parseFloat(out.trim());
95
+ return Number.isFinite(seconds) ? seconds : null;
96
+ }
97
+ catch (_a) {
98
+ return null;
99
+ }
100
+ }
101
+ /** "MM:SS" ou "HH:MM:SS" → secondes. Renvoie NaN si le format est invalide. */
102
+ function timecodeToSeconds(timecode) {
103
+ if (typeof timecode !== "string")
104
+ return NaN;
105
+ const parts = timecode.trim().split(":").map(Number);
106
+ if (parts.some(n => !Number.isFinite(n)))
107
+ return NaN;
108
+ if (parts.length === 2)
109
+ return parts[0] * 60 + parts[1];
110
+ if (parts.length === 3)
111
+ return parts[0] * 3600 + parts[1] * 60 + parts[2];
112
+ return NaN;
113
+ }
114
+ /**
115
+ * Extrait les segments horodatés d'une réponse, qu'elle soit formée d'objets
116
+ * concaténés, d'un tableau ou d'un tableau enveloppé dans un objet,
117
+ * éventuellement dans un bloc de code.
118
+ */
119
+ function extractJsonObjects(raw) {
120
+ const cleaned = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/```$/, "");
121
+ const objects = [];
122
+ let depth = 0;
123
+ let start = -1;
124
+ let inString = false;
125
+ let escaped = false;
126
+ for (let i = 0; i < cleaned.length; i++) {
127
+ const char = cleaned[i];
128
+ if (inString) {
129
+ if (escaped)
130
+ escaped = false;
131
+ else if (char === "\\")
132
+ escaped = true;
133
+ else if (char === '"')
134
+ inString = false;
135
+ continue;
136
+ }
137
+ if (char === '"')
138
+ inString = true;
139
+ else if (char === "{") {
140
+ if (depth === 0)
141
+ start = i;
142
+ depth++;
143
+ }
144
+ else if (char === "}") {
145
+ depth--;
146
+ if (depth === 0 && start !== -1) {
147
+ try {
148
+ objects.push(JSON.parse(cleaned.slice(start, i + 1)));
149
+ }
150
+ catch (_a) {
151
+ // Objet non parsable : ignoré.
152
+ }
153
+ start = -1;
154
+ }
155
+ }
156
+ }
157
+ if (!objects.length)
158
+ throw new Error("Aucun objet JSON trouvé dans la réponse.");
159
+ // Collecte tout objet portant un `timecode`, quelle que soit sa profondeur.
160
+ const segments = [];
161
+ const visit = (node) => {
162
+ if (Array.isArray(node))
163
+ return node.forEach(visit);
164
+ if (!node || typeof node !== "object")
165
+ return;
166
+ if ("timecode" in node)
167
+ return void segments.push(node);
168
+ Object.values(node).forEach(visit);
169
+ };
170
+ objects.forEach(visit);
171
+ if (!segments.length) {
172
+ throw new Error("Objets JSON trouvés, mais aucun ne porte de champ `timecode`.");
173
+ }
174
+ return segments;
175
+ }
176
+ function runTimecodeChecks(entries, durationSeconds) {
177
+ const checks = [];
178
+ checks.push({
179
+ label: "Transcription non vide",
180
+ ok: entries.length > 0,
181
+ detail: `${entries.length} segment(s)`,
182
+ });
183
+ const malformed = entries.filter(e => !e || typeof e.text !== "string" || !e.text.trim() || !Number.isFinite(timecodeToSeconds(e.timecode)));
184
+ checks.push({
185
+ label: "Tous les segments ont un timecode et un texte valides",
186
+ ok: malformed.length === 0,
187
+ detail: malformed.length ? `${malformed.length} segment(s) invalide(s)` : "aucun défaut",
188
+ });
189
+ const seconds = entries.map(e => timecodeToSeconds(e === null || e === void 0 ? void 0 : e.timecode)).filter(Number.isFinite);
190
+ const regressions = [];
191
+ for (let i = 1; i < seconds.length; i++) {
192
+ if (seconds[i] < seconds[i - 1]) {
193
+ regressions.push(`#${i}: ${entries[i].timecode} après ${entries[i - 1].timecode}`);
194
+ }
195
+ }
196
+ checks.push({
197
+ label: "Timecodes strictement croissants",
198
+ ok: regressions.length === 0,
199
+ detail: regressions.length ? regressions.slice(0, 5).join(", ") : "ordre respecté",
200
+ });
201
+ const last = seconds.length ? seconds[seconds.length - 1] : 0;
202
+ if (durationSeconds !== null) {
203
+ checks.push({
204
+ label: "Dernier timecode dans la durée du fichier",
205
+ ok: last <= durationSeconds + 1,
206
+ detail: `${last.toFixed(0)}s vs ${durationSeconds.toFixed(0)}s de piste`,
207
+ });
208
+ const coverage = durationSeconds > 0 ? last / durationSeconds : 0;
209
+ checks.push({
210
+ label: "Couverture de la piste ≥ 80 %",
211
+ ok: coverage >= 0.8,
212
+ detail: `${(coverage * 100).toFixed(0)} % couverts`,
213
+ });
214
+ }
215
+ else {
216
+ console.log("ℹ️ ffprobe introuvable : contrôles de durée ignorés.");
217
+ }
218
+ return checks;
219
+ }
220
+ function reportCost(raw) {
221
+ var _a, _b;
222
+ const usage = raw === null || raw === void 0 ? void 0 : raw.usage;
223
+ if (!usage)
224
+ return;
225
+ const { prompt, output } = Models_1.MODELS.QWEN_OMNI_FLASH.costsPer1MTokens;
226
+ const inputTokens = (_a = usage.prompt_tokens) !== null && _a !== void 0 ? _a : 0;
227
+ const outputTokens = (_b = usage.completion_tokens) !== null && _b !== void 0 ? _b : 0;
228
+ const cost = (inputTokens * prompt + outputTokens * output) / 1000000;
229
+ console.log(`\n💰 ${inputTokens} tokens entrée / ${outputTokens} sortie → ${cost.toFixed(5)} USD`);
230
+ }
231
+ /** Rejoue les contrôles sur une réponse déjà obtenue, sans rappeler l'API. */
232
+ function replaySavedResponse(rawPath, durationSeconds) {
233
+ const content = fs.readFileSync(rawPath, "utf-8");
234
+ console.log(`♻️ Rejeu hors ligne de ${path.basename(rawPath)} (${content.length} caractères)\n`);
235
+ const entries = extractJsonObjects(content);
236
+ const checks = runTimecodeChecks(entries, durationSeconds);
237
+ checks.forEach(c => console.log(` ${c.ok ? "✅" : "❌"} ${c.label} (${c.detail})`));
238
+ if (checks.some(c => !c.ok))
239
+ process.exit(1);
240
+ }
241
+ function main() {
242
+ return __awaiter(this, void 0, void 0, function* () {
243
+ var _a, _b, _c, _d, _e, _f;
244
+ const audioPath = process.argv[2];
245
+ if (!audioPath) {
246
+ console.error("Usage: npm run example:qwen-omni -- <chemin/audio.mp3>");
247
+ console.error(" npm run example:qwen-omni -- <reponse.txt> [duree_en_secondes]");
248
+ process.exit(1);
249
+ }
250
+ // Un fichier .txt est une réponse sauvegardée : les contrôles sont rejoués sans appel à l'API.
251
+ if (audioPath.toLowerCase().endsWith(".txt")) {
252
+ const duration = process.argv[3] ? Number(process.argv[3]) : null;
253
+ replaySavedResponse(audioPath, Number.isFinite(duration) ? duration : null);
254
+ return;
255
+ }
256
+ if (!fs.existsSync(audioPath)) {
257
+ console.error(`Fichier introuvable : ${audioPath}`);
258
+ process.exit(1);
259
+ }
260
+ const sizeMb = fs.statSync(audioPath).size / 1024 / 1024;
261
+ const duration = probeDurationSeconds(audioPath);
262
+ console.log(`🎧 ${path.basename(audioPath)} — ${sizeMb.toFixed(1)} Mo` +
263
+ (duration !== null ? `, ${duration.toFixed(0)}s` : ""));
264
+ console.log(`🤖 ${Models_1.MODELS.QWEN_OMNI_FLASH.modelName}\n`);
265
+ const client = new UnifiedLLMClient_1.UnifiedLLMClient(Models_1.MODELS.QWEN_OMNI_FLASH);
266
+ const startedAt = Date.now();
267
+ const useTools = process.argv.includes("--tools");
268
+ console.log(useTools ? "🔧 Mode function calling\n" : "💬 Mode prompt JSON\n");
269
+ const result = yield client.chat(Object.assign(Object.assign({ message: [
270
+ {
271
+ role: "user",
272
+ content: useTools ? TRANSCRIPTION_PROMPT_TOOLS : TRANSCRIPTION_PROMPT,
273
+ attachedFile: audioPath,
274
+ },
275
+ ], stream: true }, (useTools ? { tools: [TRANSCRIPTION_TOOL], toolChoice: TRANSCRIPTION_TOOL.name } : {})), { onChunk: chunk => {
276
+ if (chunk.contentChunk)
277
+ process.stdout.write(".");
278
+ } }));
279
+ const latencyMs = Date.now() - startedAt;
280
+ console.log(`\n\n⏱️ Réponse en ${(latencyMs / 1000).toFixed(1)}s`);
281
+ // En function calling, la transcription arrive dans les arguments de l'outil,
282
+ // pas dans le contenu du message.
283
+ const toolArguments = (_b = (_a = result.toolCalls) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.arguments;
284
+ const content = useTools && toolArguments
285
+ ? JSON.stringify(toolArguments, null, 2)
286
+ : typeof result.content === "string"
287
+ ? result.content
288
+ : JSON.stringify(result.content);
289
+ if (useTools) {
290
+ console.log(`🔧 tool_calls: ${(_d = (_c = result.toolCalls) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0}` +
291
+ (((_e = result.toolCalls) === null || _e === void 0 ? void 0 : _e[0]) ? ` → ${result.toolCalls[0].name}` : ""));
292
+ }
293
+ // La réponse brute est sauvegardée à côté du fichier audio.
294
+ const rawPath = path.join(path.dirname(audioPath), `${path.basename(audioPath)}.qwen-raw.txt`);
295
+ fs.writeFileSync(rawPath, content, "utf-8");
296
+ console.log(`📄 Réponse brute : ${rawPath}`);
297
+ console.log(`🏁 finish_reason: ${(_f = result.finishReason) !== null && _f !== void 0 ? _f : "?"} | ${content.length} caractères reçus`);
298
+ reportCost(result.raw);
299
+ let entries;
300
+ try {
301
+ entries = extractJsonObjects(content);
302
+ }
303
+ catch (error) {
304
+ console.error(`\n❌ Sortie non structurée : ${error instanceof Error ? error.message : error}`);
305
+ process.exit(1);
306
+ }
307
+ console.log("\n📋 Premiers segments :");
308
+ entries.slice(0, 5).forEach(e => { var _a; return console.log(` [${e.timecode}] ${(_a = e.speaker) !== null && _a !== void 0 ? _a : "?"} — ${e.text}`); });
309
+ const checks = runTimecodeChecks(entries, duration);
310
+ console.log("\n🔍 Contrôles :");
311
+ checks.forEach(c => console.log(` ${c.ok ? "✅" : "❌"} ${c.label} (${c.detail})`));
312
+ reportCost(result.raw);
313
+ const failed = checks.filter(c => !c.ok);
314
+ if (failed.length) {
315
+ console.error(`\n❌ ${failed.length} contrôle(s) en échec.`);
316
+ process.exit(1);
317
+ }
318
+ console.log("\n✅ Tous les contrôles passent.");
319
+ });
320
+ }
321
+ main().catch(error => {
322
+ console.error("\n💥 Échec :", error instanceof Error ? error.message : error);
323
+ process.exit(1);
324
+ });
@@ -144,13 +144,13 @@ function qwenVLBoundingBoxesExample() {
144
144
  console.log("📦 Exemple 2: Extraction de bounding boxes...");
145
145
  // Initialiser le client Qwen
146
146
  const client = new UnifiedLLMClient_1.UnifiedLLMClient(Models_1.MODELS.QWEN_VL_3);
147
- const expectedJsonFormat = `{
148
- "data": [
149
- {
150
- "bbox_2d": [x1, y1, x2, y2],
151
- "text_content": "contenu du texte"
152
- }
153
- ]
147
+ const expectedJsonFormat = `{
148
+ "data": [
149
+ {
150
+ "bbox_2d": [x1, y1, x2, y2],
151
+ "text_content": "contenu du texte"
152
+ }
153
+ ]
154
154
  }`;
155
155
  // Redimensionner l'image pour l'extraction de bounding boxes
156
156
  const resizedImageBuffer = yield resizeImageForVision("./media/Gaspard_5_with_lines.png", 1000);
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { ApiKeyResolver, ChatMessage, ToolDefinition, ToolCall, ChatResponse, AudioGenerationOptions, AudioGenerationResponse, VoiceConfig, SpeakerConfig } from "./common/types";
1
+ import { ApiKeyResolver, Model, Provider, ChatMessage, ToolDefinition, ToolCall, ChatResponse, AudioGenerationOptions, AudioGenerationResponse, VoiceConfig, SpeakerConfig } from "./common/types";
2
2
  import { PromptAdapter, PromptAdapterOptions, OutputAdapter, AudioAdapter, ChatOptionalConfig } from "./core/LLMAdapter";
3
3
  import { UnifiedLLMClient, ChatOptions } from "./core/UnifiedLLMClient";
4
- import { MODELS } from "./core/Models";
4
+ import { MODELS, openRouterModel } from "./core/Models";
5
+ import { OPENROUTER_BASE_URL } from "./adapters/openrouter/OpenRouterPromptAdapter";
5
6
  import { ProxyServer } from "./proxy/ProxyServer";
6
7
  import { ProxyChatRequest, ProxyServerOptions, ProxySSEEvent } from "./proxy/types";
7
- export { UnifiedLLMClient, MODELS, ProxyServer };
8
- export type { ApiKeyResolver, ChatMessage, ToolDefinition, ToolCall, ChatResponse, PromptAdapter, PromptAdapterOptions, OutputAdapter, AudioAdapter, AudioGenerationOptions, AudioGenerationResponse, VoiceConfig, SpeakerConfig, ChatOptionalConfig, ChatOptions, ProxyChatRequest, ProxyServerOptions, ProxySSEEvent, };
8
+ export { UnifiedLLMClient, MODELS, ProxyServer, openRouterModel, OPENROUTER_BASE_URL };
9
+ export type { ApiKeyResolver, Model, Provider, ChatMessage, ToolDefinition, ToolCall, ChatResponse, PromptAdapter, PromptAdapterOptions, OutputAdapter, AudioAdapter, AudioGenerationOptions, AudioGenerationResponse, VoiceConfig, SpeakerConfig, ChatOptionalConfig, ChatOptions, ProxyChatRequest, ProxyServerOptions, ProxySSEEvent, };
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAClL,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzH,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACjD,YAAY,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,EACvB,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GACd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnM,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzH,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,CAAC;AACvF,YAAY,EACV,cAAc,EACd,KAAK,EACL,QAAQ,EACR,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,EACvB,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GACd,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProxyServer = exports.MODELS = exports.UnifiedLLMClient = void 0;
3
+ exports.OPENROUTER_BASE_URL = exports.openRouterModel = exports.ProxyServer = exports.MODELS = exports.UnifiedLLMClient = void 0;
4
4
  const UnifiedLLMClient_1 = require("./core/UnifiedLLMClient");
5
5
  Object.defineProperty(exports, "UnifiedLLMClient", { enumerable: true, get: function () { return UnifiedLLMClient_1.UnifiedLLMClient; } });
6
6
  const Models_1 = require("./core/Models");
7
7
  Object.defineProperty(exports, "MODELS", { enumerable: true, get: function () { return Models_1.MODELS; } });
8
+ Object.defineProperty(exports, "openRouterModel", { enumerable: true, get: function () { return Models_1.openRouterModel; } });
9
+ const OpenRouterPromptAdapter_1 = require("./adapters/openrouter/OpenRouterPromptAdapter");
10
+ Object.defineProperty(exports, "OPENROUTER_BASE_URL", { enumerable: true, get: function () { return OpenRouterPromptAdapter_1.OPENROUTER_BASE_URL; } });
8
11
  const ProxyServer_1 = require("./proxy/ProxyServer");
9
12
  Object.defineProperty(exports, "ProxyServer", { enumerable: true, get: function () { return ProxyServer_1.ProxyServer; } });
package/package.json CHANGED
@@ -1,70 +1,71 @@
1
- {
2
- "name": "@imenam/unified-llm-client",
3
- "version": "2.1.2",
4
- "description": "A unified interface for multiple LLM providers (OpenAI, Gemini, Anthropic, Qwen) with tool-calling, streaming, and audio support",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist",
9
- "README.md",
10
- "LICENSE"
11
- ],
12
- "scripts": {
13
- "build": "tsc",
14
- "dev": "tsc -w",
15
- "prepublishOnly": "npm run build",
16
- "release:patch": "npm version patch && npm publish",
17
- "release:minor": "npm version minor && npm publish",
18
- "release:major": "npm version major && npm publish",
19
- "example": "node dist/examples/example.js",
20
- "example:audio": "node dist/examples/audio-example.js",
21
- "example:qwen": "node dist/examples/qwen-vl-example.js",
22
- "example:anthropic": "node dist/examples/anthropic-example.js",
23
- "example:abort": "node dist/examples/chat-abort-example.js",
24
- "example:proxy": "node dist/examples/proxy-example.js",
25
- "example:proxy-api-key-resolver": "node dist/examples/proxy-api-key-resolver-example.js",
26
- "example:api-key-resolver": "node dist/examples/api-key-resolver-example.js",
27
- "proxy:start": "node dist/examples/proxy-server-start.js",
28
- "example:error-logging": "node dist/examples/example-error-logging.js"
29
- },
30
- "keywords": [
31
- "llm",
32
- "openai",
33
- "gemini",
34
- "anthropic",
35
- "qwen",
36
- "ai",
37
- "unified",
38
- "tool-calling",
39
- "streaming",
40
- "chatgpt",
41
- "gpt",
42
- "claude",
43
- "mistral"
44
- ],
45
- "author": "imenam",
46
- "license": "MIT",
47
- "publishConfig": {
48
- "access": "public"
49
- },
50
- "repository": {
51
- "type": "git",
52
- "url": "https://github.com/imenam/globalLLMInterface.git"
53
- },
54
- "devDependencies": {
55
- "@types/mime-types": "^2.1.4",
56
- "typescript": "^5.8.3"
57
- },
58
- "dependencies": {
59
- "@anthropic-ai/sdk": "^0.52.0",
60
- "@google/genai": "^1.46.0",
61
- "@mistralai/mistralai": "^1.6.1",
62
- "@types/node": "^22.15.24",
63
- "@types/node-fetch": "^2.6.13",
64
- "dotenv": "^16.5.0",
65
- "mime-types": "^3.0.1",
66
- "node-fetch": "^3.3.2",
67
- "openai": "^4.103.0",
68
- "sharp": "^0.34.4"
69
- }
70
- }
1
+ {
2
+ "name": "@imenam/unified-llm-client",
3
+ "version": "2.3.0",
4
+ "description": "A unified interface for multiple LLM providers (OpenAI, Gemini, Anthropic, Qwen) with tool-calling, streaming, and audio support",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc -w",
15
+ "prepublishOnly": "npm run build",
16
+ "release:patch": "npm version patch && npm publish",
17
+ "release:minor": "npm version minor && npm publish",
18
+ "release:major": "npm version major && npm publish",
19
+ "example": "node dist/examples/example.js",
20
+ "example:audio": "node dist/examples/audio-example.js",
21
+ "example:qwen": "node dist/examples/qwen-vl-example.js",
22
+ "example:qwen-omni": "node dist/examples/qwen-omni-audio-example.js",
23
+ "example:anthropic": "node dist/examples/anthropic-example.js",
24
+ "example:abort": "node dist/examples/chat-abort-example.js",
25
+ "example:proxy": "node dist/examples/proxy-example.js",
26
+ "example:proxy-api-key-resolver": "node dist/examples/proxy-api-key-resolver-example.js",
27
+ "example:api-key-resolver": "node dist/examples/api-key-resolver-example.js",
28
+ "proxy:start": "node dist/examples/proxy-server-start.js",
29
+ "example:error-logging": "node dist/examples/example-error-logging.js"
30
+ },
31
+ "keywords": [
32
+ "llm",
33
+ "openai",
34
+ "gemini",
35
+ "anthropic",
36
+ "qwen",
37
+ "ai",
38
+ "unified",
39
+ "tool-calling",
40
+ "streaming",
41
+ "chatgpt",
42
+ "gpt",
43
+ "claude",
44
+ "mistral"
45
+ ],
46
+ "author": "imenam",
47
+ "license": "MIT",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/imenam/globalLLMInterface.git"
54
+ },
55
+ "devDependencies": {
56
+ "@types/mime-types": "^2.1.4",
57
+ "typescript": "^5.8.3"
58
+ },
59
+ "dependencies": {
60
+ "@anthropic-ai/sdk": "^0.52.0",
61
+ "@google/genai": "^1.46.0",
62
+ "@mistralai/mistralai": "^1.6.1",
63
+ "@types/node": "^22.15.24",
64
+ "@types/node-fetch": "^2.6.13",
65
+ "dotenv": "^16.5.0",
66
+ "mime-types": "^3.0.1",
67
+ "node-fetch": "^3.3.2",
68
+ "openai": "^4.103.0",
69
+ "sharp": "^0.34.4"
70
+ }
71
+ }