@lupaflow/providers 0.1.1 → 0.1.2

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/dist/index.js ADDED
@@ -0,0 +1,617 @@
1
+ // src/registry.ts
2
+ import { ConfigError } from "@lupaflow/core";
3
+ var ProviderRegistry = class {
4
+ providers = /* @__PURE__ */ new Map();
5
+ instances = /* @__PURE__ */ new Map();
6
+ register(name, ctor) {
7
+ this.providers.set(name, ctor);
8
+ }
9
+ get(name, config) {
10
+ if (!config && this.instances.has(name)) {
11
+ return this.instances.get(name);
12
+ }
13
+ const ctor = this.providers.get(name);
14
+ if (!ctor) {
15
+ throw new ConfigError(`Unknown provider: "${name}"`, {
16
+ available: Array.from(this.providers.keys())
17
+ });
18
+ }
19
+ const instance = new ctor(config || {});
20
+ if (!config) {
21
+ this.instances.set(name, instance);
22
+ }
23
+ return instance;
24
+ }
25
+ has(name) {
26
+ return this.providers.has(name);
27
+ }
28
+ list() {
29
+ return Array.from(this.providers.keys());
30
+ }
31
+ };
32
+ var providerRegistry = new ProviderRegistry();
33
+
34
+ // src/openrouter.ts
35
+ import OpenAI from "openai";
36
+ import { ProviderError } from "@lupaflow/core";
37
+ import { getOpenRouterKey } from "@lupaflow/secrets";
38
+ import { globalEventBus } from "@lupaflow/core";
39
+ var OpenRouterProvider = class {
40
+ name = "openrouter";
41
+ defaultModel = "openai/gpt-4o-mini";
42
+ config;
43
+ client;
44
+ constructor(config = {}) {
45
+ this.config = config;
46
+ this.client = new OpenAI({
47
+ apiKey: config.apiKey || getOpenRouterKey(),
48
+ baseURL: config.baseUrl || "https://openrouter.ai/api/v1",
49
+ defaultHeaders: {
50
+ "HTTP-Referer": "https://lupaflow.dev",
51
+ "X-Title": "LupaFlow"
52
+ }
53
+ });
54
+ }
55
+ validateConfig() {
56
+ try {
57
+ getOpenRouterKey();
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ buildMessages(request) {
64
+ const messages = [];
65
+ if (request.systemPrompt) {
66
+ messages.push({ role: "system", content: request.systemPrompt });
67
+ }
68
+ for (const m of request.messages) {
69
+ if (m.role === "assistant" && m.toolCalls?.length) {
70
+ messages.push({
71
+ role: "assistant",
72
+ content: m.content,
73
+ tool_calls: m.toolCalls
74
+ });
75
+ } else if (m.role === "tool") {
76
+ messages.push({
77
+ role: "tool",
78
+ tool_call_id: m.toolCallId || "",
79
+ content: m.content || ""
80
+ });
81
+ } else {
82
+ messages.push({ role: m.role, content: m.content });
83
+ }
84
+ }
85
+ return messages;
86
+ }
87
+ buildTools(tools) {
88
+ return tools?.map((t) => ({
89
+ type: "function",
90
+ function: {
91
+ name: t.definition.name,
92
+ description: t.definition.description,
93
+ parameters: {
94
+ type: "object",
95
+ properties: t.definition.parameters,
96
+ required: t.definition.required
97
+ }
98
+ }
99
+ }));
100
+ }
101
+ async complete(request) {
102
+ const start = Date.now();
103
+ const model = request.model || this.config.model || this.defaultModel;
104
+ await globalEventBus.emit("provider:start", {
105
+ provider: this.name,
106
+ model
107
+ });
108
+ try {
109
+ const messages = this.buildMessages(request);
110
+ const tools = this.buildTools(request.tools);
111
+ const response = await this.client.chat.completions.create({
112
+ model,
113
+ messages,
114
+ tools,
115
+ temperature: request.temperature ?? this.config.temperature,
116
+ max_tokens: request.maxTokens ?? this.config.maxTokens,
117
+ top_p: request.topP ?? this.config.topP
118
+ });
119
+ const choice = response.choices[0];
120
+ const latencyMs = Date.now() - start;
121
+ const result = {
122
+ content: choice.message.content,
123
+ toolCalls: (choice.message.tool_calls || []).map((tc) => ({
124
+ id: tc.id,
125
+ name: tc.function.name,
126
+ args: JSON.parse(tc.function.arguments)
127
+ })),
128
+ usage: {
129
+ promptTokens: response.usage?.prompt_tokens || 0,
130
+ completionTokens: response.usage?.completion_tokens || 0,
131
+ totalTokens: response.usage?.total_tokens || 0
132
+ },
133
+ model: response.model,
134
+ latencyMs,
135
+ finishReason: choice.finish_reason || "stop"
136
+ };
137
+ await globalEventBus.emit("provider:complete", {
138
+ provider: this.name,
139
+ model,
140
+ tokens: result.usage.totalTokens,
141
+ latencyMs
142
+ });
143
+ return result;
144
+ } catch (err) {
145
+ const latencyMs = Date.now() - start;
146
+ await globalEventBus.emit("provider:error", {
147
+ provider: this.name,
148
+ model,
149
+ error: err.message
150
+ });
151
+ throw new ProviderError(this.name, err.message, { model, latencyMs });
152
+ }
153
+ }
154
+ async *streamComplete(request) {
155
+ const start = Date.now();
156
+ const model = request.model || this.config.model || this.defaultModel;
157
+ await globalEventBus.emit("provider:start", {
158
+ provider: this.name,
159
+ model
160
+ });
161
+ try {
162
+ const messages = this.buildMessages(request);
163
+ const tools = this.buildTools(request.tools);
164
+ const stream = await this.client.chat.completions.create({
165
+ model,
166
+ messages,
167
+ tools,
168
+ temperature: request.temperature ?? this.config.temperature,
169
+ max_tokens: request.maxTokens ?? this.config.maxTokens,
170
+ top_p: request.topP ?? this.config.topP,
171
+ stream: true
172
+ });
173
+ let fullContent = "";
174
+ const accToolCalls = /* @__PURE__ */ new Map();
175
+ for await (const chunk of stream) {
176
+ const delta = chunk.choices[0]?.delta;
177
+ if (delta?.content) {
178
+ fullContent += delta.content;
179
+ yield { type: "text", text: delta.content };
180
+ }
181
+ if (delta?.tool_calls) {
182
+ for (const tc of delta.tool_calls) {
183
+ if (!accToolCalls.has(tc.index)) {
184
+ accToolCalls.set(tc.index, { id: "", name: "", args: "" });
185
+ }
186
+ const entry = accToolCalls.get(tc.index);
187
+ if (tc.id) entry.id = tc.id;
188
+ if (tc.function?.name) entry.name = tc.function.name;
189
+ if (tc.function?.arguments) entry.args += tc.function.arguments;
190
+ }
191
+ }
192
+ if (chunk.choices[0]?.finish_reason) {
193
+ const finishReason = chunk.choices[0].finish_reason;
194
+ const toolCalls = Array.from(accToolCalls.values()).filter((tc) => tc.id && tc.name).map((tc) => ({
195
+ id: tc.id,
196
+ name: tc.name,
197
+ args: JSON.parse(tc.args || "{}")
198
+ }));
199
+ yield {
200
+ type: "finish",
201
+ content: fullContent,
202
+ toolCalls,
203
+ usage: {
204
+ promptTokens: chunk.usage?.prompt_tokens || 0,
205
+ completionTokens: chunk.usage?.completion_tokens || 0,
206
+ totalTokens: chunk.usage?.total_tokens || 0
207
+ },
208
+ model,
209
+ finishReason
210
+ };
211
+ }
212
+ }
213
+ await globalEventBus.emit("provider:complete", {
214
+ provider: this.name,
215
+ model,
216
+ tokens: 0,
217
+ latencyMs: Date.now() - start
218
+ });
219
+ } catch (err) {
220
+ await globalEventBus.emit("provider:error", {
221
+ provider: this.name,
222
+ model,
223
+ error: err.message
224
+ });
225
+ throw new ProviderError(this.name, err.message, { model, latencyMs: Date.now() - start });
226
+ }
227
+ }
228
+ async getModels() {
229
+ try {
230
+ const response = await fetch("https://openrouter.ai/api/v1/models");
231
+ const data = await response.json();
232
+ return data.data?.map((m) => m.id) || [];
233
+ } catch {
234
+ return [this.defaultModel];
235
+ }
236
+ }
237
+ };
238
+
239
+ // src/google.ts
240
+ import { GoogleGenerativeAI } from "@google/generative-ai";
241
+ import { ProviderError as ProviderError2 } from "@lupaflow/core";
242
+ import { getGoogleKey } from "@lupaflow/secrets";
243
+ import { globalEventBus as globalEventBus2 } from "@lupaflow/core";
244
+ var GoogleProvider = class {
245
+ name = "google";
246
+ defaultModel = "gemini-1.5-flash";
247
+ config;
248
+ client;
249
+ constructor(config = {}) {
250
+ this.config = config;
251
+ this.client = new GoogleGenerativeAI(config.apiKey || getGoogleKey());
252
+ }
253
+ validateConfig() {
254
+ try {
255
+ getGoogleKey();
256
+ return true;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ buildContents(request) {
262
+ const contents = [];
263
+ for (const msg of request.messages) {
264
+ if (msg.role === "system") continue;
265
+ const role = msg.role === "assistant" ? "model" : "user";
266
+ contents.push({
267
+ role,
268
+ parts: [{ text: msg.content || "" }]
269
+ });
270
+ }
271
+ return contents;
272
+ }
273
+ async complete(request) {
274
+ const start = Date.now();
275
+ const model = request.model || this.config.model || this.defaultModel;
276
+ await globalEventBus2.emit("provider:start", {
277
+ provider: this.name,
278
+ model
279
+ });
280
+ try {
281
+ const genModel = this.client.getGenerativeModel({ model });
282
+ const systemInstruction = request.systemPrompt;
283
+ const contents = this.buildContents(request);
284
+ const requestOptions = {
285
+ contents,
286
+ generationConfig: {
287
+ temperature: request.temperature ?? this.config.temperature,
288
+ maxOutputTokens: request.maxTokens ?? this.config.maxTokens,
289
+ topP: request.topP ?? this.config.topP
290
+ }
291
+ };
292
+ if (systemInstruction) {
293
+ requestOptions.systemInstruction = {
294
+ role: "user",
295
+ parts: [{ text: systemInstruction }]
296
+ };
297
+ }
298
+ const result = await genModel.generateContent(requestOptions);
299
+ const latencyMs = Date.now() - start;
300
+ const response = result.response;
301
+ const text = response.text();
302
+ const usage = response.usageMetadata || {};
303
+ const totalTokens = (usage.promptTokenCount || 0) + (usage.candidatesTokenCount || 0);
304
+ const completionResponse = {
305
+ content: text,
306
+ toolCalls: [],
307
+ usage: {
308
+ promptTokens: usage.promptTokenCount || 0,
309
+ completionTokens: usage.candidatesTokenCount || 0,
310
+ totalTokens
311
+ },
312
+ model,
313
+ latencyMs,
314
+ finishReason: "stop"
315
+ };
316
+ await globalEventBus2.emit("provider:complete", {
317
+ provider: this.name,
318
+ model,
319
+ tokens: totalTokens,
320
+ latencyMs
321
+ });
322
+ return completionResponse;
323
+ } catch (err) {
324
+ const latencyMs = Date.now() - start;
325
+ await globalEventBus2.emit("provider:error", {
326
+ provider: this.name,
327
+ model,
328
+ error: err.message
329
+ });
330
+ throw new ProviderError2(this.name, err.message, { model, latencyMs });
331
+ }
332
+ }
333
+ async *streamComplete(request) {
334
+ const start = Date.now();
335
+ const model = request.model || this.config.model || this.defaultModel;
336
+ await globalEventBus2.emit("provider:start", {
337
+ provider: this.name,
338
+ model
339
+ });
340
+ try {
341
+ const genModel = this.client.getGenerativeModel({ model });
342
+ const systemInstruction = request.systemPrompt;
343
+ const contents = this.buildContents(request);
344
+ const requestOptions = {
345
+ contents,
346
+ generationConfig: {
347
+ temperature: request.temperature ?? this.config.temperature,
348
+ maxOutputTokens: request.maxTokens ?? this.config.maxTokens,
349
+ topP: request.topP ?? this.config.topP
350
+ }
351
+ };
352
+ if (systemInstruction) {
353
+ requestOptions.systemInstruction = {
354
+ role: "user",
355
+ parts: [{ text: systemInstruction }]
356
+ };
357
+ }
358
+ const result = await genModel.generateContentStream(requestOptions);
359
+ let fullContent = "";
360
+ for await (const chunk of result.stream) {
361
+ const text = chunk.text();
362
+ if (text) {
363
+ fullContent += text;
364
+ yield { type: "text", text };
365
+ }
366
+ }
367
+ const response = await result.response;
368
+ const usage = response.usageMetadata || {};
369
+ const totalTokens = (usage.promptTokenCount || 0) + (usage.candidatesTokenCount || 0);
370
+ yield {
371
+ type: "finish",
372
+ content: fullContent,
373
+ toolCalls: [],
374
+ usage: {
375
+ promptTokens: usage.promptTokenCount || 0,
376
+ completionTokens: usage.candidatesTokenCount || 0,
377
+ totalTokens
378
+ },
379
+ model,
380
+ finishReason: "stop"
381
+ };
382
+ await globalEventBus2.emit("provider:complete", {
383
+ provider: this.name,
384
+ model,
385
+ tokens: totalTokens,
386
+ latencyMs: Date.now() - start
387
+ });
388
+ } catch (err) {
389
+ await globalEventBus2.emit("provider:error", {
390
+ provider: this.name,
391
+ model,
392
+ error: err.message
393
+ });
394
+ throw new ProviderError2(this.name, err.message, { model, latencyMs: Date.now() - start });
395
+ }
396
+ }
397
+ async getModels() {
398
+ return ["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash-exp"];
399
+ }
400
+ };
401
+
402
+ // src/groq.ts
403
+ import OpenAI2 from "openai";
404
+ import { ProviderError as ProviderError3 } from "@lupaflow/core";
405
+ import { getGroqKey } from "@lupaflow/secrets";
406
+ import { globalEventBus as globalEventBus3 } from "@lupaflow/core";
407
+ var GroqProvider = class {
408
+ name = "groq";
409
+ defaultModel = "llama-3.3-70b-versatile";
410
+ config;
411
+ client;
412
+ constructor(config = {}) {
413
+ this.config = config;
414
+ this.client = new OpenAI2({
415
+ apiKey: config.apiKey || getGroqKey(),
416
+ baseURL: config.baseUrl || "https://api.groq.com/openai/v1"
417
+ });
418
+ }
419
+ validateConfig() {
420
+ try {
421
+ getGroqKey();
422
+ return true;
423
+ } catch {
424
+ return false;
425
+ }
426
+ }
427
+ buildMessages(request) {
428
+ const messages = [];
429
+ if (request.systemPrompt) {
430
+ messages.push({ role: "system", content: request.systemPrompt });
431
+ }
432
+ for (const m of request.messages) {
433
+ if (m.role === "assistant" && m.toolCalls?.length) {
434
+ messages.push({
435
+ role: "assistant",
436
+ content: m.content,
437
+ tool_calls: m.toolCalls
438
+ });
439
+ } else if (m.role === "tool") {
440
+ messages.push({
441
+ role: "tool",
442
+ tool_call_id: m.toolCallId || "",
443
+ content: m.content || ""
444
+ });
445
+ } else {
446
+ messages.push({ role: m.role, content: m.content });
447
+ }
448
+ }
449
+ return messages;
450
+ }
451
+ buildTools(tools) {
452
+ return tools?.map((t) => ({
453
+ type: "function",
454
+ function: {
455
+ name: t.definition.name,
456
+ description: t.definition.description,
457
+ parameters: {
458
+ type: "object",
459
+ properties: t.definition.parameters,
460
+ required: t.definition.required
461
+ }
462
+ }
463
+ }));
464
+ }
465
+ async complete(request) {
466
+ const start = Date.now();
467
+ const model = request.model || this.config.model || this.defaultModel;
468
+ await globalEventBus3.emit("provider:start", {
469
+ provider: this.name,
470
+ model
471
+ });
472
+ try {
473
+ const messages = this.buildMessages(request);
474
+ const tools = this.buildTools(request.tools);
475
+ const response = await this.client.chat.completions.create({
476
+ model,
477
+ messages,
478
+ tools,
479
+ temperature: request.temperature ?? this.config.temperature,
480
+ max_tokens: request.maxTokens ?? this.config.maxTokens,
481
+ top_p: request.topP ?? this.config.topP
482
+ });
483
+ const choice = response.choices[0];
484
+ const latencyMs = Date.now() - start;
485
+ const result = {
486
+ content: choice.message.content,
487
+ toolCalls: (choice.message.tool_calls || []).map((tc) => ({
488
+ id: tc.id,
489
+ name: tc.function.name,
490
+ args: JSON.parse(tc.function.arguments)
491
+ })),
492
+ usage: {
493
+ promptTokens: response.usage?.prompt_tokens || 0,
494
+ completionTokens: response.usage?.completion_tokens || 0,
495
+ totalTokens: response.usage?.total_tokens || 0
496
+ },
497
+ model: response.model,
498
+ latencyMs,
499
+ finishReason: choice.finish_reason || "stop"
500
+ };
501
+ await globalEventBus3.emit("provider:complete", {
502
+ provider: this.name,
503
+ model,
504
+ tokens: result.usage.totalTokens,
505
+ latencyMs
506
+ });
507
+ return result;
508
+ } catch (err) {
509
+ const latencyMs = Date.now() - start;
510
+ await globalEventBus3.emit("provider:error", {
511
+ provider: this.name,
512
+ model,
513
+ error: err.message
514
+ });
515
+ throw new ProviderError3(this.name, err.message, { model, latencyMs });
516
+ }
517
+ }
518
+ async *streamComplete(request) {
519
+ const start = Date.now();
520
+ const model = request.model || this.config.model || this.defaultModel;
521
+ await globalEventBus3.emit("provider:start", {
522
+ provider: this.name,
523
+ model
524
+ });
525
+ try {
526
+ const messages = this.buildMessages(request);
527
+ const tools = this.buildTools(request.tools);
528
+ const stream = await this.client.chat.completions.create({
529
+ model,
530
+ messages,
531
+ tools,
532
+ temperature: request.temperature ?? this.config.temperature,
533
+ max_tokens: request.maxTokens ?? this.config.maxTokens,
534
+ top_p: request.topP ?? this.config.topP,
535
+ stream: true
536
+ });
537
+ let fullContent = "";
538
+ const accToolCalls = /* @__PURE__ */ new Map();
539
+ for await (const chunk of stream) {
540
+ const delta = chunk.choices[0]?.delta;
541
+ if (delta?.content) {
542
+ fullContent += delta.content;
543
+ yield { type: "text", text: delta.content };
544
+ }
545
+ if (delta?.tool_calls) {
546
+ for (const tc of delta.tool_calls) {
547
+ if (!accToolCalls.has(tc.index)) {
548
+ accToolCalls.set(tc.index, { id: "", name: "", args: "" });
549
+ }
550
+ const entry = accToolCalls.get(tc.index);
551
+ if (tc.id) entry.id = tc.id;
552
+ if (tc.function?.name) entry.name = tc.function.name;
553
+ if (tc.function?.arguments) entry.args += tc.function.arguments;
554
+ }
555
+ }
556
+ if (chunk.choices[0]?.finish_reason) {
557
+ const finishReason = chunk.choices[0].finish_reason;
558
+ const toolCalls = Array.from(accToolCalls.values()).filter((tc) => tc.id && tc.name).map((tc) => ({
559
+ id: tc.id,
560
+ name: tc.name,
561
+ args: JSON.parse(tc.args || "{}")
562
+ }));
563
+ yield {
564
+ type: "finish",
565
+ content: fullContent,
566
+ toolCalls,
567
+ usage: {
568
+ promptTokens: chunk.usage?.prompt_tokens || 0,
569
+ completionTokens: chunk.usage?.completion_tokens || 0,
570
+ totalTokens: chunk.usage?.total_tokens || 0
571
+ },
572
+ model,
573
+ finishReason
574
+ };
575
+ }
576
+ }
577
+ await globalEventBus3.emit("provider:complete", {
578
+ provider: this.name,
579
+ model,
580
+ tokens: 0,
581
+ latencyMs: Date.now() - start
582
+ });
583
+ } catch (err) {
584
+ await globalEventBus3.emit("provider:error", {
585
+ provider: this.name,
586
+ model,
587
+ error: err.message
588
+ });
589
+ throw new ProviderError3(this.name, err.message, { model, latencyMs: Date.now() - start });
590
+ }
591
+ }
592
+ async getModels() {
593
+ try {
594
+ const response = await fetch("https://api.groq.com/openai/v1/models", {
595
+ headers: {
596
+ Authorization: `Bearer ${getGroqKey()}`
597
+ }
598
+ });
599
+ const data = await response.json();
600
+ return data.data?.map((m) => m.id) || [];
601
+ } catch {
602
+ return [this.defaultModel];
603
+ }
604
+ }
605
+ };
606
+
607
+ // src/index.ts
608
+ providerRegistry.register("openrouter", OpenRouterProvider);
609
+ providerRegistry.register("google", GoogleProvider);
610
+ providerRegistry.register("groq", GroqProvider);
611
+ export {
612
+ GoogleProvider,
613
+ GroqProvider,
614
+ OpenRouterProvider,
615
+ providerRegistry
616
+ };
617
+ //# sourceMappingURL=index.js.map