groq-rag 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1903 @@
1
+ // src/client.ts
2
+ import Groq2 from "groq-sdk";
3
+
4
+ // src/utils/chunker.ts
5
+ var TextChunker = class {
6
+ options;
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ strategy: options.strategy || "recursive",
10
+ chunkSize: options.chunkSize || 1e3,
11
+ chunkOverlap: options.chunkOverlap || 200,
12
+ separators: options.separators || ["\n\n", "\n", ". ", " ", ""]
13
+ };
14
+ }
15
+ /**
16
+ * Chunk text into smaller pieces
17
+ */
18
+ chunk(text, documentId) {
19
+ switch (this.options.strategy) {
20
+ case "fixed":
21
+ return this.fixedChunk(text, documentId);
22
+ case "sentence":
23
+ return this.sentenceChunk(text, documentId);
24
+ case "paragraph":
25
+ return this.paragraphChunk(text, documentId);
26
+ case "recursive":
27
+ return this.recursiveChunk(text, documentId);
28
+ case "semantic":
29
+ return this.semanticChunk(text, documentId);
30
+ default:
31
+ return this.recursiveChunk(text, documentId);
32
+ }
33
+ }
34
+ /**
35
+ * Fixed-size chunking with overlap
36
+ */
37
+ fixedChunk(text, documentId) {
38
+ const chunks = [];
39
+ let start = 0;
40
+ while (start < text.length) {
41
+ const end = Math.min(start + this.options.chunkSize, text.length);
42
+ const content = text.slice(start, end).trim();
43
+ if (content) {
44
+ chunks.push({
45
+ id: `${documentId}-chunk-${chunks.length}`,
46
+ documentId,
47
+ content,
48
+ startIndex: start,
49
+ endIndex: end
50
+ });
51
+ }
52
+ start += this.options.chunkSize - this.options.chunkOverlap;
53
+ }
54
+ return chunks;
55
+ }
56
+ /**
57
+ * Sentence-based chunking
58
+ */
59
+ sentenceChunk(text, documentId) {
60
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
61
+ const chunks = [];
62
+ let currentChunk = "";
63
+ let startIndex = 0;
64
+ for (const sentence of sentences) {
65
+ if ((currentChunk + sentence).length > this.options.chunkSize && currentChunk) {
66
+ chunks.push({
67
+ id: `${documentId}-chunk-${chunks.length}`,
68
+ documentId,
69
+ content: currentChunk.trim(),
70
+ startIndex,
71
+ endIndex: startIndex + currentChunk.length
72
+ });
73
+ startIndex += currentChunk.length;
74
+ currentChunk = sentence;
75
+ } else {
76
+ currentChunk += sentence;
77
+ }
78
+ }
79
+ if (currentChunk.trim()) {
80
+ chunks.push({
81
+ id: `${documentId}-chunk-${chunks.length}`,
82
+ documentId,
83
+ content: currentChunk.trim(),
84
+ startIndex,
85
+ endIndex: startIndex + currentChunk.length
86
+ });
87
+ }
88
+ return chunks;
89
+ }
90
+ /**
91
+ * Paragraph-based chunking
92
+ */
93
+ paragraphChunk(text, documentId) {
94
+ const paragraphs = text.split(/\n\n+/);
95
+ const chunks = [];
96
+ let currentChunk = "";
97
+ let startIndex = 0;
98
+ for (const paragraph of paragraphs) {
99
+ const trimmed = paragraph.trim();
100
+ if (!trimmed) continue;
101
+ if ((currentChunk + "\n\n" + trimmed).length > this.options.chunkSize && currentChunk) {
102
+ chunks.push({
103
+ id: `${documentId}-chunk-${chunks.length}`,
104
+ documentId,
105
+ content: currentChunk.trim(),
106
+ startIndex,
107
+ endIndex: startIndex + currentChunk.length
108
+ });
109
+ startIndex += currentChunk.length;
110
+ currentChunk = trimmed;
111
+ } else {
112
+ currentChunk = currentChunk ? currentChunk + "\n\n" + trimmed : trimmed;
113
+ }
114
+ }
115
+ if (currentChunk.trim()) {
116
+ chunks.push({
117
+ id: `${documentId}-chunk-${chunks.length}`,
118
+ documentId,
119
+ content: currentChunk.trim(),
120
+ startIndex,
121
+ endIndex: startIndex + currentChunk.length
122
+ });
123
+ }
124
+ return chunks;
125
+ }
126
+ /**
127
+ * Recursive character text splitter (LangChain-style)
128
+ */
129
+ recursiveChunk(text, documentId) {
130
+ return this.recursiveSplit(text, this.options.separators, documentId);
131
+ }
132
+ recursiveSplit(text, separators, documentId, chunks = []) {
133
+ const separator = separators[0];
134
+ const remainingSeparators = separators.slice(1);
135
+ let splits;
136
+ if (separator === "") {
137
+ splits = text.split("");
138
+ } else {
139
+ splits = text.split(separator);
140
+ }
141
+ let currentChunk = "";
142
+ for (const split of splits) {
143
+ const piece = separator === "" ? split : split + separator;
144
+ if ((currentChunk + piece).length > this.options.chunkSize) {
145
+ if (currentChunk) {
146
+ if (currentChunk.length > this.options.chunkSize && remainingSeparators.length > 0) {
147
+ this.recursiveSplit(currentChunk, remainingSeparators, documentId, chunks);
148
+ } else {
149
+ chunks.push({
150
+ id: `${documentId}-chunk-${chunks.length}`,
151
+ documentId,
152
+ content: currentChunk.trim()
153
+ });
154
+ }
155
+ }
156
+ currentChunk = piece;
157
+ } else {
158
+ currentChunk += piece;
159
+ }
160
+ }
161
+ if (currentChunk.trim()) {
162
+ if (currentChunk.length > this.options.chunkSize && remainingSeparators.length > 0) {
163
+ this.recursiveSplit(currentChunk, remainingSeparators, documentId, chunks);
164
+ } else {
165
+ chunks.push({
166
+ id: `${documentId}-chunk-${chunks.length}`,
167
+ documentId,
168
+ content: currentChunk.trim()
169
+ });
170
+ }
171
+ }
172
+ return chunks;
173
+ }
174
+ /**
175
+ * Semantic chunking (placeholder - would use embeddings in production)
176
+ */
177
+ semanticChunk(text, documentId) {
178
+ return this.paragraphChunk(text, documentId);
179
+ }
180
+ };
181
+ function chunkText(text, documentId, options) {
182
+ const chunker = new TextChunker(options);
183
+ return chunker.chunk(text, documentId);
184
+ }
185
+
186
+ // src/utils/helpers.ts
187
+ function generateId() {
188
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
189
+ }
190
+ function cosineSimilarity(a, b) {
191
+ if (a.length !== b.length) {
192
+ throw new Error("Vectors must have the same length");
193
+ }
194
+ let dotProduct = 0;
195
+ let normA = 0;
196
+ let normB = 0;
197
+ for (let i = 0; i < a.length; i++) {
198
+ dotProduct += a[i] * b[i];
199
+ normA += a[i] * a[i];
200
+ normB += b[i] * b[i];
201
+ }
202
+ normA = Math.sqrt(normA);
203
+ normB = Math.sqrt(normB);
204
+ if (normA === 0 || normB === 0) {
205
+ return 0;
206
+ }
207
+ return dotProduct / (normA * normB);
208
+ }
209
+ function estimateTokens(text) {
210
+ return Math.ceil(text.length / 4);
211
+ }
212
+ function truncateToTokens(text, maxTokens) {
213
+ const estimatedChars = maxTokens * 4;
214
+ if (text.length <= estimatedChars) {
215
+ return text;
216
+ }
217
+ return text.slice(0, estimatedChars) + "...";
218
+ }
219
+ function cleanText(text) {
220
+ return text.replace(/\n{3,}/g, "\n\n").replace(/[^\S\n]+/g, " ").replace(/ ?\n ?/g, "\n").trim();
221
+ }
222
+ function extractUrls(text) {
223
+ const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/g;
224
+ return text.match(urlRegex) || [];
225
+ }
226
+ function sleep(ms) {
227
+ return new Promise((resolve) => setTimeout(resolve, ms));
228
+ }
229
+ async function retry(fn, options = {}) {
230
+ const { maxRetries = 3, baseDelay = 1e3, maxDelay = 1e4 } = options;
231
+ let lastError;
232
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
233
+ try {
234
+ return await fn();
235
+ } catch (error) {
236
+ lastError = error;
237
+ if (attempt < maxRetries) {
238
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
239
+ await sleep(delay);
240
+ }
241
+ }
242
+ }
243
+ throw lastError;
244
+ }
245
+ function formatContext(results, options = {}) {
246
+ const { includeMetadata = false, separator = "\n\n---\n\n" } = options;
247
+ return results.map((result, index) => {
248
+ let text = `[Source ${index + 1}]
249
+ ${result.content}`;
250
+ if (includeMetadata && result.metadata) {
251
+ const meta = Object.entries(result.metadata).filter(([_, v]) => v !== void 0).map(([k, v]) => `${k}: ${v}`).join(", ");
252
+ if (meta) {
253
+ text = `[Source ${index + 1} | ${meta}]
254
+ ${result.content}`;
255
+ }
256
+ }
257
+ return text;
258
+ }).join(separator);
259
+ }
260
+ function safeJsonParse(text, defaultValue) {
261
+ try {
262
+ return JSON.parse(text);
263
+ } catch {
264
+ return defaultValue;
265
+ }
266
+ }
267
+ function batch(array, size) {
268
+ const batches = [];
269
+ for (let i = 0; i < array.length; i += size) {
270
+ batches.push(array.slice(i, i + size));
271
+ }
272
+ return batches;
273
+ }
274
+
275
+ // src/rag/retriever.ts
276
+ var Retriever = class {
277
+ vectorStore;
278
+ embeddings;
279
+ chunker;
280
+ constructor(vectorStore, embeddings, chunkingOptions) {
281
+ this.vectorStore = vectorStore;
282
+ this.embeddings = embeddings;
283
+ this.chunker = new TextChunker(chunkingOptions);
284
+ }
285
+ /**
286
+ * Add a document to the retriever
287
+ */
288
+ async addDocument(content, metadata) {
289
+ const documentId = generateId();
290
+ const chunks = this.chunker.chunk(content, documentId);
291
+ const chunksWithMetadata = chunks.map((chunk) => ({
292
+ ...chunk,
293
+ metadata: { ...metadata, documentId }
294
+ }));
295
+ const texts = chunksWithMetadata.map((c) => c.content);
296
+ const embedResults = await this.embeddings.embedBatch(texts);
297
+ const chunksWithEmbeddings = chunksWithMetadata.map(
298
+ (chunk, i) => ({
299
+ ...chunk,
300
+ embedding: embedResults[i].embedding
301
+ })
302
+ );
303
+ await this.vectorStore.add(chunksWithEmbeddings);
304
+ return documentId;
305
+ }
306
+ /**
307
+ * Add multiple documents
308
+ */
309
+ async addDocuments(documents) {
310
+ const ids = [];
311
+ for (const doc of documents) {
312
+ const id = await this.addDocument(doc.content, doc.metadata);
313
+ ids.push(id);
314
+ }
315
+ return ids;
316
+ }
317
+ /**
318
+ * Add raw text directly (without chunking)
319
+ */
320
+ async addChunk(content, metadata) {
321
+ const id = generateId();
322
+ const [embedResult] = await this.embeddings.embedBatch([content]);
323
+ const chunk = {
324
+ id,
325
+ documentId: id,
326
+ content,
327
+ metadata,
328
+ embedding: embedResult.embedding
329
+ };
330
+ await this.vectorStore.add([chunk]);
331
+ return id;
332
+ }
333
+ /**
334
+ * Retrieve relevant documents for a query
335
+ */
336
+ async retrieve(query, options = {}) {
337
+ const { topK = 5, minScore = 0 } = options;
338
+ const { embedding } = await this.embeddings.embed(query);
339
+ const results = await this.vectorStore.search(embedding, { topK: topK * 2 });
340
+ return results.filter((r) => r.score >= minScore).slice(0, topK);
341
+ }
342
+ /**
343
+ * Retrieve and format context for LLM
344
+ */
345
+ async getContext(query, options = {}) {
346
+ const results = await this.retrieve(query, options);
347
+ return formatContext(
348
+ results.map((r) => ({
349
+ content: r.document.content,
350
+ metadata: r.document.metadata
351
+ })),
352
+ { includeMetadata: options.includeMetadata }
353
+ );
354
+ }
355
+ /**
356
+ * Delete documents by ID
357
+ */
358
+ async deleteDocuments(ids) {
359
+ await this.vectorStore.delete(ids);
360
+ }
361
+ /**
362
+ * Clear all documents
363
+ */
364
+ async clear() {
365
+ await this.vectorStore.clear();
366
+ }
367
+ /**
368
+ * Get document count
369
+ */
370
+ async count() {
371
+ return this.vectorStore.count();
372
+ }
373
+ };
374
+ function createRetriever(vectorStore, embeddings, options) {
375
+ return new Retriever(vectorStore, embeddings, options);
376
+ }
377
+
378
+ // src/rag/vectorStore.ts
379
+ var MemoryVectorStore = class {
380
+ documents = /* @__PURE__ */ new Map();
381
+ async add(documents) {
382
+ for (const doc of documents) {
383
+ this.documents.set(doc.id, doc);
384
+ }
385
+ }
386
+ async search(queryEmbedding, options = {}) {
387
+ const { topK = 5, filter } = options;
388
+ const results = [];
389
+ for (const doc of this.documents.values()) {
390
+ if (!doc.embedding) continue;
391
+ if (filter && doc.metadata) {
392
+ let matches = true;
393
+ for (const [key, value] of Object.entries(filter)) {
394
+ if (doc.metadata[key] !== value) {
395
+ matches = false;
396
+ break;
397
+ }
398
+ }
399
+ if (!matches) continue;
400
+ }
401
+ const score = cosineSimilarity(queryEmbedding, doc.embedding);
402
+ results.push({
403
+ document: doc,
404
+ score,
405
+ relevance: score > 0.8 ? "high" : score > 0.5 ? "medium" : "low"
406
+ });
407
+ }
408
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
409
+ }
410
+ async delete(ids) {
411
+ for (const id of ids) {
412
+ this.documents.delete(id);
413
+ }
414
+ }
415
+ async clear() {
416
+ this.documents.clear();
417
+ }
418
+ async count() {
419
+ return this.documents.size;
420
+ }
421
+ /**
422
+ * Get all documents (useful for debugging)
423
+ */
424
+ getAll() {
425
+ return Array.from(this.documents.values());
426
+ }
427
+ };
428
+ var ChromaVectorStore = class {
429
+ baseURL;
430
+ collectionName;
431
+ collectionId;
432
+ constructor(config) {
433
+ this.baseURL = config.connectionString || "http://localhost:8000";
434
+ this.collectionName = config.indexName || "groq-rag";
435
+ }
436
+ async ensureCollection() {
437
+ if (this.collectionId) return;
438
+ const response = await fetch(`${this.baseURL}/api/v1/collections`, {
439
+ method: "POST",
440
+ headers: { "Content-Type": "application/json" },
441
+ body: JSON.stringify({ name: this.collectionName })
442
+ });
443
+ if (response.ok) {
444
+ const data = await response.json();
445
+ this.collectionId = data.id;
446
+ } else {
447
+ const getResponse = await fetch(
448
+ `${this.baseURL}/api/v1/collections/${this.collectionName}`
449
+ );
450
+ if (getResponse.ok) {
451
+ const data = await getResponse.json();
452
+ this.collectionId = data.id;
453
+ }
454
+ }
455
+ }
456
+ async add(documents) {
457
+ await this.ensureCollection();
458
+ const ids = documents.map((d) => d.id);
459
+ const embeddings = documents.map((d) => d.embedding || []);
460
+ const metadatas = documents.map((d) => d.metadata || {});
461
+ const contents = documents.map((d) => d.content);
462
+ await fetch(`${this.baseURL}/api/v1/collections/${this.collectionId}/add`, {
463
+ method: "POST",
464
+ headers: { "Content-Type": "application/json" },
465
+ body: JSON.stringify({
466
+ ids,
467
+ embeddings,
468
+ metadatas,
469
+ documents: contents
470
+ })
471
+ });
472
+ }
473
+ async search(queryEmbedding, options = {}) {
474
+ await this.ensureCollection();
475
+ const { topK = 5, filter } = options;
476
+ const response = await fetch(
477
+ `${this.baseURL}/api/v1/collections/${this.collectionId}/query`,
478
+ {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify({
482
+ query_embeddings: [queryEmbedding],
483
+ n_results: topK,
484
+ where: filter
485
+ })
486
+ }
487
+ );
488
+ if (!response.ok) {
489
+ throw new Error(`Chroma query failed: ${response.statusText}`);
490
+ }
491
+ const data = await response.json();
492
+ const results = [];
493
+ const ids = data.ids[0] || [];
494
+ const distances = data.distances[0] || [];
495
+ const docs = data.documents[0] || [];
496
+ const metas = data.metadatas[0] || [];
497
+ for (let i = 0; i < ids.length; i++) {
498
+ const score = 1 - (distances[i] || 0);
499
+ results.push({
500
+ document: {
501
+ id: ids[i],
502
+ documentId: ids[i],
503
+ content: docs[i],
504
+ metadata: metas[i]
505
+ },
506
+ score,
507
+ relevance: score > 0.8 ? "high" : score > 0.5 ? "medium" : "low"
508
+ });
509
+ }
510
+ return results;
511
+ }
512
+ async delete(ids) {
513
+ await this.ensureCollection();
514
+ await fetch(`${this.baseURL}/api/v1/collections/${this.collectionId}/delete`, {
515
+ method: "POST",
516
+ headers: { "Content-Type": "application/json" },
517
+ body: JSON.stringify({ ids })
518
+ });
519
+ }
520
+ async clear() {
521
+ await fetch(`${this.baseURL}/api/v1/collections/${this.collectionName}`, {
522
+ method: "DELETE"
523
+ });
524
+ this.collectionId = void 0;
525
+ await this.ensureCollection();
526
+ }
527
+ async count() {
528
+ await this.ensureCollection();
529
+ const response = await fetch(
530
+ `${this.baseURL}/api/v1/collections/${this.collectionId}/count`
531
+ );
532
+ const data = await response.json();
533
+ return data;
534
+ }
535
+ };
536
+ function createVectorStore(config) {
537
+ switch (config.provider) {
538
+ case "chroma":
539
+ return new ChromaVectorStore(config);
540
+ case "memory":
541
+ default:
542
+ return new MemoryVectorStore();
543
+ }
544
+ }
545
+
546
+ // src/rag/embeddings.ts
547
+ import Groq from "groq-sdk";
548
+ var GroqEmbeddings = class {
549
+ client;
550
+ model;
551
+ dimensions;
552
+ constructor(config) {
553
+ this.client = config.groqClient || new Groq({ apiKey: config.apiKey });
554
+ this.model = config.model || "llama-3.3-70b-versatile";
555
+ this.dimensions = config.dimensions || 1536;
556
+ }
557
+ async embed(text) {
558
+ const embedding = await this.generatePseudoEmbedding(text);
559
+ return { embedding, tokenCount: Math.ceil(text.length / 4) };
560
+ }
561
+ async embedBatch(texts) {
562
+ const results = [];
563
+ for (const text of texts) {
564
+ results.push(await this.embed(text));
565
+ }
566
+ return results;
567
+ }
568
+ /**
569
+ * Generate a pseudo-embedding (for demo purposes)
570
+ * In production, replace with actual embedding API
571
+ */
572
+ async generatePseudoEmbedding(text) {
573
+ const embedding = new Array(this.dimensions).fill(0);
574
+ for (let i = 0; i < text.length; i++) {
575
+ const charCode = text.charCodeAt(i);
576
+ const index = charCode * (i + 1) % this.dimensions;
577
+ embedding[index] += 1 / Math.sqrt(text.length);
578
+ }
579
+ const norm = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
580
+ if (norm > 0) {
581
+ for (let i = 0; i < embedding.length; i++) {
582
+ embedding[i] /= norm;
583
+ }
584
+ }
585
+ return embedding;
586
+ }
587
+ };
588
+ var OpenAIEmbeddings = class {
589
+ apiKey;
590
+ baseURL;
591
+ model;
592
+ dimensions;
593
+ constructor(config) {
594
+ this.apiKey = config.apiKey || process.env.OPENAI_API_KEY || "";
595
+ this.baseURL = config.baseURL || "https://api.openai.com/v1";
596
+ this.model = config.model || "text-embedding-3-small";
597
+ this.dimensions = config.dimensions || 1536;
598
+ }
599
+ async embed(text) {
600
+ const response = await fetch(`${this.baseURL}/embeddings`, {
601
+ method: "POST",
602
+ headers: {
603
+ "Authorization": `Bearer ${this.apiKey}`,
604
+ "Content-Type": "application/json"
605
+ },
606
+ body: JSON.stringify({
607
+ input: text,
608
+ model: this.model,
609
+ dimensions: this.dimensions
610
+ })
611
+ });
612
+ if (!response.ok) {
613
+ throw new Error(`Embedding request failed: ${response.statusText}`);
614
+ }
615
+ const data = await response.json();
616
+ return {
617
+ embedding: data.data[0].embedding,
618
+ tokenCount: data.usage?.total_tokens
619
+ };
620
+ }
621
+ async embedBatch(texts) {
622
+ const batches = batch(texts, 100);
623
+ const results = [];
624
+ for (const batchTexts of batches) {
625
+ const response = await fetch(`${this.baseURL}/embeddings`, {
626
+ method: "POST",
627
+ headers: {
628
+ "Authorization": `Bearer ${this.apiKey}`,
629
+ "Content-Type": "application/json"
630
+ },
631
+ body: JSON.stringify({
632
+ input: batchTexts,
633
+ model: this.model,
634
+ dimensions: this.dimensions
635
+ })
636
+ });
637
+ if (!response.ok) {
638
+ throw new Error(`Embedding request failed: ${response.statusText}`);
639
+ }
640
+ const data = await response.json();
641
+ for (const item of data.data) {
642
+ results.push({
643
+ embedding: item.embedding,
644
+ tokenCount: data.usage?.total_tokens ? Math.floor(data.usage.total_tokens / batchTexts.length) : void 0
645
+ });
646
+ }
647
+ }
648
+ return results;
649
+ }
650
+ };
651
+ function createEmbeddingProvider(config, groqClient) {
652
+ switch (config.provider) {
653
+ case "openai":
654
+ return new OpenAIEmbeddings(config);
655
+ case "groq":
656
+ case "local":
657
+ default:
658
+ return new GroqEmbeddings({ ...config, groqClient });
659
+ }
660
+ }
661
+
662
+ // src/web/fetcher.ts
663
+ import * as cheerio from "cheerio";
664
+ import TurndownService from "turndown";
665
+ var WebFetcher = class {
666
+ config;
667
+ turndown;
668
+ constructor(config = {}) {
669
+ this.config = {
670
+ userAgent: config.userAgent || "Mozilla/5.0 (compatible; GroqRAG/1.0; +https://github.com/mithun50/groq-rag)",
671
+ timeout: config.timeout || 3e4,
672
+ maxContentLength: config.maxContentLength || 1e6,
673
+ // 1MB
674
+ followRedirects: config.followRedirects ?? true,
675
+ proxy: config.proxy
676
+ };
677
+ this.turndown = new TurndownService({
678
+ headingStyle: "atx",
679
+ codeBlockStyle: "fenced",
680
+ bulletListMarker: "-"
681
+ });
682
+ this.turndown.addRule("removeScripts", {
683
+ filter: ["script", "style", "noscript", "iframe"],
684
+ replacement: () => ""
685
+ });
686
+ this.turndown.addRule("preserveCode", {
687
+ filter: "pre",
688
+ replacement: (content, node) => {
689
+ const code = node.textContent || content;
690
+ return `
691
+ \`\`\`
692
+ ${code}
693
+ \`\`\`
694
+ `;
695
+ }
696
+ });
697
+ }
698
+ /**
699
+ * Fetch a URL and extract content
700
+ */
701
+ async fetch(url, options = {}) {
702
+ const {
703
+ headers = {},
704
+ timeout = this.config.timeout,
705
+ extractText = true,
706
+ includeLinks = false,
707
+ includeImages = false
708
+ } = options;
709
+ const controller = new AbortController();
710
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
711
+ try {
712
+ const response = await fetch(url, {
713
+ headers: {
714
+ "User-Agent": this.config.userAgent,
715
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
716
+ "Accept-Language": "en-US,en;q=0.5",
717
+ ...headers
718
+ },
719
+ redirect: this.config.followRedirects ? "follow" : "manual",
720
+ signal: controller.signal
721
+ });
722
+ if (!response.ok) {
723
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
724
+ }
725
+ const html = await response.text();
726
+ clearTimeout(timeoutId);
727
+ const $ = cheerio.load(html);
728
+ $("script, style, noscript, iframe, nav, footer, aside, .ads, .advertisement").remove();
729
+ const title = $("title").text().trim() || $("h1").first().text().trim() || $('meta[property="og:title"]').attr("content") || void 0;
730
+ const metadata = {
731
+ description: $('meta[name="description"]').attr("content") || $('meta[property="og:description"]').attr("content") || void 0,
732
+ author: $('meta[name="author"]').attr("content") || $('meta[property="article:author"]').attr("content") || void 0,
733
+ publishedDate: $('meta[property="article:published_time"]').attr("content") || $("time").attr("datetime") || void 0
734
+ };
735
+ let content = "";
736
+ let markdown = "";
737
+ if (extractText) {
738
+ const mainSelectors = [
739
+ "main",
740
+ "article",
741
+ '[role="main"]',
742
+ ".content",
743
+ ".post-content",
744
+ ".article-content",
745
+ ".entry-content",
746
+ "#content",
747
+ "#main"
748
+ ];
749
+ let mainHtml = $("body").html() || "";
750
+ let mainText = $("body").text();
751
+ for (const selector of mainSelectors) {
752
+ const selected = $(selector);
753
+ if (selected.length > 0) {
754
+ mainHtml = selected.first().html() || mainHtml;
755
+ mainText = selected.first().text();
756
+ break;
757
+ }
758
+ }
759
+ markdown = this.turndown.turndown(mainHtml);
760
+ content = mainText.replace(/\s+/g, " ").trim();
761
+ }
762
+ let links;
763
+ if (includeLinks) {
764
+ links = [];
765
+ $("a[href]").each((_, el) => {
766
+ const $el = $(el);
767
+ const href = $el.attr("href");
768
+ const text = $el.text().trim();
769
+ if (href && text && !href.startsWith("#") && !href.startsWith("javascript:")) {
770
+ try {
771
+ const absoluteUrl = new URL(href, url).toString();
772
+ links.push({ text, href: absoluteUrl });
773
+ } catch {
774
+ }
775
+ }
776
+ });
777
+ links = [...new Map(links.map((l) => [l.href, l])).values()];
778
+ }
779
+ let images;
780
+ if (includeImages) {
781
+ images = [];
782
+ $("img[src]").each((_, el) => {
783
+ const $el = $(el);
784
+ const src = $el.attr("src");
785
+ const alt = $el.attr("alt") || "";
786
+ if (src) {
787
+ try {
788
+ const absoluteUrl = new URL(src, url).toString();
789
+ images.push({ alt, src: absoluteUrl });
790
+ } catch {
791
+ }
792
+ }
793
+ });
794
+ }
795
+ return {
796
+ url,
797
+ title,
798
+ content,
799
+ markdown,
800
+ links,
801
+ images,
802
+ metadata,
803
+ fetchedAt: /* @__PURE__ */ new Date()
804
+ };
805
+ } catch (error) {
806
+ clearTimeout(timeoutId);
807
+ throw error;
808
+ }
809
+ }
810
+ /**
811
+ * Fetch multiple URLs in parallel
812
+ */
813
+ async fetchMany(urls, options = {}) {
814
+ const results = await Promise.allSettled(
815
+ urls.map((url) => this.fetch(url, options))
816
+ );
817
+ return results.map((result, i) => {
818
+ if (result.status === "fulfilled") {
819
+ return result.value;
820
+ } else {
821
+ return new Error(`Failed to fetch ${urls[i]}: ${result.reason}`);
822
+ }
823
+ });
824
+ }
825
+ /**
826
+ * Fetch and extract just the text content
827
+ */
828
+ async fetchText(url) {
829
+ const result = await this.fetch(url, { extractText: true });
830
+ return result.content;
831
+ }
832
+ /**
833
+ * Fetch and convert to markdown
834
+ */
835
+ async fetchMarkdown(url) {
836
+ const result = await this.fetch(url, { extractText: true });
837
+ return result.markdown || result.content;
838
+ }
839
+ };
840
+ function createFetcher(config) {
841
+ return new WebFetcher(config);
842
+ }
843
+
844
+ // src/web/search.ts
845
+ var DuckDuckGoSearch = class {
846
+ baseUrl = "https://html.duckduckgo.com/html/";
847
+ async search(query, options = {}) {
848
+ const { maxResults = 10 } = options;
849
+ try {
850
+ const response = await fetch(this.baseUrl, {
851
+ method: "POST",
852
+ headers: {
853
+ "Content-Type": "application/x-www-form-urlencoded",
854
+ "User-Agent": "Mozilla/5.0 (compatible; GroqRAG/1.0)"
855
+ },
856
+ body: `q=${encodeURIComponent(query)}`
857
+ });
858
+ if (!response.ok) {
859
+ throw new Error(`Search failed: ${response.statusText}`);
860
+ }
861
+ const html = await response.text();
862
+ const results = this.parseResults(html, maxResults);
863
+ return results;
864
+ } catch (error) {
865
+ console.error("DuckDuckGo search error:", error);
866
+ return [];
867
+ }
868
+ }
869
+ parseResults(html, maxResults) {
870
+ const results = [];
871
+ const resultRegex = /<a[^>]+class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
872
+ const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([^<]+(?:<[^>]+>[^<]+<\/[^>]+>)*[^<]*)<\/a>/gi;
873
+ let match;
874
+ let position = 1;
875
+ while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
876
+ const url = this.decodeUrl(match[1]);
877
+ const title = this.decodeHtml(match[2]);
878
+ const snippetMatch = snippetRegex.exec(html);
879
+ const snippet = snippetMatch ? this.decodeHtml(snippetMatch[1].replace(/<[^>]+>/g, "")) : "";
880
+ if (url && title && !url.includes("duckduckgo.com")) {
881
+ results.push({
882
+ title,
883
+ url,
884
+ snippet,
885
+ position: position++
886
+ });
887
+ }
888
+ }
889
+ return results;
890
+ }
891
+ decodeUrl(url) {
892
+ try {
893
+ const decoded = decodeURIComponent(url);
894
+ const match = decoded.match(/uddg=([^&]+)/);
895
+ if (match) {
896
+ return decodeURIComponent(match[1]);
897
+ }
898
+ return decoded;
899
+ } catch {
900
+ return url;
901
+ }
902
+ }
903
+ decodeHtml(html) {
904
+ return html.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").trim();
905
+ }
906
+ };
907
+ var BraveSearch = class {
908
+ apiKey;
909
+ baseUrl = "https://api.search.brave.com/res/v1/web/search";
910
+ constructor(apiKey) {
911
+ this.apiKey = apiKey;
912
+ }
913
+ async search(query, options = {}) {
914
+ const { maxResults = 10, safeSearch = true } = options;
915
+ const params = new URLSearchParams({
916
+ q: query,
917
+ count: String(maxResults),
918
+ safesearch: safeSearch ? "moderate" : "off"
919
+ });
920
+ const response = await fetch(`${this.baseUrl}?${params}`, {
921
+ headers: {
922
+ "Accept": "application/json",
923
+ "X-Subscription-Token": this.apiKey
924
+ }
925
+ });
926
+ if (!response.ok) {
927
+ throw new Error(`Brave search failed: ${response.statusText}`);
928
+ }
929
+ const data = await response.json();
930
+ return (data.web?.results || []).map((result, index) => ({
931
+ title: result.title,
932
+ url: result.url,
933
+ snippet: result.description,
934
+ position: index + 1
935
+ }));
936
+ }
937
+ };
938
+ var SerperSearch = class {
939
+ apiKey;
940
+ baseUrl = "https://google.serper.dev/search";
941
+ constructor(apiKey) {
942
+ this.apiKey = apiKey;
943
+ }
944
+ async search(query, options = {}) {
945
+ const { maxResults = 10 } = options;
946
+ const response = await fetch(this.baseUrl, {
947
+ method: "POST",
948
+ headers: {
949
+ "Content-Type": "application/json",
950
+ "X-API-KEY": this.apiKey
951
+ },
952
+ body: JSON.stringify({
953
+ q: query,
954
+ num: maxResults
955
+ })
956
+ });
957
+ if (!response.ok) {
958
+ throw new Error(`Serper search failed: ${response.statusText}`);
959
+ }
960
+ const data = await response.json();
961
+ return (data.organic || []).map((result) => ({
962
+ title: result.title,
963
+ url: result.link,
964
+ snippet: result.snippet,
965
+ position: result.position
966
+ }));
967
+ }
968
+ };
969
+ function createSearchProvider(config) {
970
+ const { provider = "duckduckgo", apiKey } = config || {};
971
+ switch (provider) {
972
+ case "brave":
973
+ if (!apiKey) throw new Error("Brave Search requires an API key");
974
+ return new BraveSearch(apiKey);
975
+ case "serper":
976
+ if (!apiKey) throw new Error("Serper Search requires an API key");
977
+ return new SerperSearch(apiKey);
978
+ case "duckduckgo":
979
+ default:
980
+ return new DuckDuckGoSearch();
981
+ }
982
+ }
983
+
984
+ // src/tools/executor.ts
985
+ var ToolExecutor = class {
986
+ tools = /* @__PURE__ */ new Map();
987
+ constructor(tools = []) {
988
+ for (const tool of tools) {
989
+ this.register(tool);
990
+ }
991
+ }
992
+ /**
993
+ * Register a tool
994
+ */
995
+ register(tool) {
996
+ this.tools.set(tool.name, tool);
997
+ }
998
+ /**
999
+ * Unregister a tool
1000
+ */
1001
+ unregister(name) {
1002
+ return this.tools.delete(name);
1003
+ }
1004
+ /**
1005
+ * Get all registered tools
1006
+ */
1007
+ getTools() {
1008
+ return Array.from(this.tools.values());
1009
+ }
1010
+ /**
1011
+ * Get tools in Groq API format
1012
+ */
1013
+ getToolsForAPI() {
1014
+ return this.getTools().map((tool) => ({
1015
+ type: "function",
1016
+ function: {
1017
+ name: tool.name,
1018
+ description: tool.description,
1019
+ parameters: tool.parameters
1020
+ }
1021
+ }));
1022
+ }
1023
+ /**
1024
+ * Check if a tool exists
1025
+ */
1026
+ has(name) {
1027
+ return this.tools.has(name);
1028
+ }
1029
+ /**
1030
+ * Execute a tool by name
1031
+ */
1032
+ async execute(name, params) {
1033
+ const startTime = Date.now();
1034
+ const tool = this.tools.get(name);
1035
+ if (!tool) {
1036
+ return {
1037
+ name,
1038
+ result: null,
1039
+ error: `Tool "${name}" not found`,
1040
+ executionTime: Date.now() - startTime
1041
+ };
1042
+ }
1043
+ try {
1044
+ const result = await tool.execute(params);
1045
+ return {
1046
+ name,
1047
+ result,
1048
+ executionTime: Date.now() - startTime
1049
+ };
1050
+ } catch (error) {
1051
+ return {
1052
+ name,
1053
+ result: null,
1054
+ error: error instanceof Error ? error.message : String(error),
1055
+ executionTime: Date.now() - startTime
1056
+ };
1057
+ }
1058
+ }
1059
+ /**
1060
+ * Execute multiple tool calls in parallel
1061
+ */
1062
+ async executeMany(calls) {
1063
+ return Promise.all(
1064
+ calls.map((call) => this.execute(call.name, call.params))
1065
+ );
1066
+ }
1067
+ /**
1068
+ * Process tool calls from Groq API response
1069
+ */
1070
+ async processToolCalls(toolCalls) {
1071
+ const results = await Promise.all(
1072
+ toolCalls.map(async (toolCall) => {
1073
+ let params = {};
1074
+ try {
1075
+ params = JSON.parse(toolCall.function.arguments);
1076
+ } catch {
1077
+ }
1078
+ const result = await this.execute(toolCall.function.name, params);
1079
+ return {
1080
+ toolCallId: toolCall.id,
1081
+ result
1082
+ };
1083
+ })
1084
+ );
1085
+ return results;
1086
+ }
1087
+ /**
1088
+ * Format tool results as messages for Groq API
1089
+ */
1090
+ formatToolResultsAsMessages(results) {
1091
+ return results.map(({ toolCallId, result }) => ({
1092
+ role: "tool",
1093
+ tool_call_id: toolCallId,
1094
+ content: result.error ? `Error: ${result.error}` : JSON.stringify(result.result)
1095
+ }));
1096
+ }
1097
+ };
1098
+ function createToolExecutor(tools) {
1099
+ return new ToolExecutor(tools);
1100
+ }
1101
+
1102
+ // src/agents/agent.ts
1103
+ function parseTextFunctionCall(text) {
1104
+ const match = text.match(/<function=(\w+)(\{[\s\S]*?\})<\/function>/);
1105
+ if (match) {
1106
+ try {
1107
+ const name = match[1];
1108
+ const args = JSON.parse(match[2]);
1109
+ return { name, args };
1110
+ } catch {
1111
+ return null;
1112
+ }
1113
+ }
1114
+ return null;
1115
+ }
1116
+ function extractFailedGeneration(error) {
1117
+ if (error instanceof Error) {
1118
+ const jsonMatch = error.message.match(/\{[\s\S]*\}/);
1119
+ if (jsonMatch) {
1120
+ try {
1121
+ const parsed = JSON.parse(jsonMatch[0]);
1122
+ return parsed.error?.failed_generation || null;
1123
+ } catch {
1124
+ return null;
1125
+ }
1126
+ }
1127
+ }
1128
+ const err = error;
1129
+ return err.error?.failed_generation || null;
1130
+ }
1131
+ var Agent = class {
1132
+ client;
1133
+ config;
1134
+ executor;
1135
+ messages;
1136
+ constructor(client, config = {}) {
1137
+ this.client = client;
1138
+ this.config = {
1139
+ name: config.name || "Agent",
1140
+ model: config.model || "llama-3.3-70b-versatile",
1141
+ systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(),
1142
+ tools: config.tools || [],
1143
+ maxIterations: config.maxIterations || 10,
1144
+ verbose: config.verbose ?? false,
1145
+ memory: config.memory || { messages: [] }
1146
+ };
1147
+ this.executor = new ToolExecutor(this.config.tools);
1148
+ this.messages = [
1149
+ { role: "system", content: this.config.systemPrompt },
1150
+ ...this.config.memory.messages
1151
+ ];
1152
+ }
1153
+ getDefaultSystemPrompt() {
1154
+ return `You are a helpful AI assistant. You have access to tools that you can use to help answer questions.
1155
+
1156
+ IMPORTANT: When you need to use a tool, the system will automatically handle the tool calling for you. Just respond naturally and the tools will be invoked through the API's function calling mechanism. Do NOT write out function calls as text like "<function=..." - that format is not supported.
1157
+
1158
+ Available capabilities:
1159
+ - Search the web for current information
1160
+ - Fetch and read content from URLs
1161
+ - Query the knowledge base for indexed documents
1162
+ - Perform mathematical calculations
1163
+ - Get current date and time
1164
+
1165
+ When you don't know something or need current information, use the appropriate tool. Provide clear, helpful responses based on the information you gather.`;
1166
+ }
1167
+ /**
1168
+ * Add a tool to the agent
1169
+ */
1170
+ addTool(tool) {
1171
+ this.config.tools.push(tool);
1172
+ this.executor.register(tool);
1173
+ }
1174
+ /**
1175
+ * Run the agent with a user message
1176
+ */
1177
+ async run(input) {
1178
+ const steps = [];
1179
+ const toolCalls = [];
1180
+ this.messages.push({ role: "user", content: input });
1181
+ let iteration = 0;
1182
+ let totalTokens = 0;
1183
+ while (iteration < this.config.maxIterations) {
1184
+ iteration++;
1185
+ if (this.config.verbose) {
1186
+ console.log(`
1187
+ [Agent] Iteration ${iteration}`);
1188
+ }
1189
+ let response;
1190
+ let textFunctionCall = null;
1191
+ try {
1192
+ response = await this.client.chat.completions.create({
1193
+ model: this.config.model,
1194
+ messages: this.messages,
1195
+ tools: this.executor.getToolsForAPI(),
1196
+ tool_choice: "auto"
1197
+ });
1198
+ } catch (error) {
1199
+ const failedGen = extractFailedGeneration(error);
1200
+ if (failedGen) {
1201
+ textFunctionCall = parseTextFunctionCall(failedGen);
1202
+ if (textFunctionCall) {
1203
+ const result = await this.executor.execute(textFunctionCall.name, textFunctionCall.args);
1204
+ toolCalls.push(result);
1205
+ steps.push({
1206
+ action: textFunctionCall.name,
1207
+ actionInput: textFunctionCall.args,
1208
+ observation: result.error || JSON.stringify(result.result)
1209
+ });
1210
+ this.messages.push({
1211
+ role: "assistant",
1212
+ content: `I'll use the ${textFunctionCall.name} tool to help with this.`
1213
+ });
1214
+ this.messages.push({
1215
+ role: "user",
1216
+ content: `Tool result: ${result.error || JSON.stringify(result.result)}`
1217
+ });
1218
+ continue;
1219
+ }
1220
+ }
1221
+ throw error;
1222
+ }
1223
+ const choice = response.choices[0];
1224
+ const message = choice.message;
1225
+ totalTokens += response.usage?.total_tokens || 0;
1226
+ this.messages.push(message);
1227
+ if (message.tool_calls && message.tool_calls.length > 0) {
1228
+ if (this.config.verbose) {
1229
+ console.log(`[Agent] Tool calls:`, message.tool_calls.map((tc) => tc.function.name));
1230
+ }
1231
+ const results = await this.executor.processToolCalls(message.tool_calls);
1232
+ for (const { toolCallId, result } of results) {
1233
+ const toolCall = message.tool_calls.find((tc) => tc.id === toolCallId);
1234
+ steps.push({
1235
+ action: toolCall?.function.name,
1236
+ actionInput: toolCall ? JSON.parse(toolCall.function.arguments) : {},
1237
+ observation: result.error || JSON.stringify(result.result)
1238
+ });
1239
+ toolCalls.push(result);
1240
+ }
1241
+ const toolMessages = this.executor.formatToolResultsAsMessages(results);
1242
+ this.messages.push(...toolMessages);
1243
+ if (this.config.verbose) {
1244
+ console.log(`[Agent] Tool results:`, results.map((r) => r.result));
1245
+ }
1246
+ } else {
1247
+ steps.push({
1248
+ thought: message.content || "",
1249
+ isFinal: true
1250
+ });
1251
+ if (this.config.verbose) {
1252
+ console.log(`[Agent] Final response:`, message.content?.substring(0, 100));
1253
+ }
1254
+ return {
1255
+ output: message.content || "",
1256
+ steps,
1257
+ toolCalls,
1258
+ totalTokens
1259
+ };
1260
+ }
1261
+ if (choice.finish_reason === "stop" && !message.tool_calls) {
1262
+ steps.push({
1263
+ thought: message.content || "",
1264
+ isFinal: true
1265
+ });
1266
+ return {
1267
+ output: message.content || "",
1268
+ steps,
1269
+ toolCalls,
1270
+ totalTokens
1271
+ };
1272
+ }
1273
+ }
1274
+ return {
1275
+ output: "Agent reached maximum iterations without completing the task.",
1276
+ steps,
1277
+ toolCalls,
1278
+ totalTokens
1279
+ };
1280
+ }
1281
+ /**
1282
+ * Run with streaming output
1283
+ */
1284
+ async *runStream(input) {
1285
+ this.messages.push({ role: "user", content: input });
1286
+ let iteration = 0;
1287
+ const steps = [];
1288
+ const toolCalls = [];
1289
+ while (iteration < this.config.maxIterations) {
1290
+ iteration++;
1291
+ let response;
1292
+ try {
1293
+ response = await this.client.chat.completions.create({
1294
+ model: this.config.model,
1295
+ messages: this.messages,
1296
+ tools: this.executor.getToolsForAPI(),
1297
+ tool_choice: "auto",
1298
+ stream: true
1299
+ });
1300
+ } catch (error) {
1301
+ const failedGen = extractFailedGeneration(error);
1302
+ if (failedGen) {
1303
+ const textFunctionCall = parseTextFunctionCall(failedGen);
1304
+ if (textFunctionCall) {
1305
+ yield { type: "tool_call", data: { name: textFunctionCall.name, arguments: JSON.stringify(textFunctionCall.args) } };
1306
+ const result = await this.executor.execute(textFunctionCall.name, textFunctionCall.args);
1307
+ yield { type: "tool_result", data: result };
1308
+ toolCalls.push(result);
1309
+ this.messages.push({
1310
+ role: "assistant",
1311
+ content: `Using ${textFunctionCall.name} tool.`
1312
+ });
1313
+ this.messages.push({
1314
+ role: "user",
1315
+ content: `Tool result: ${result.error || JSON.stringify(result.result)}`
1316
+ });
1317
+ continue;
1318
+ }
1319
+ }
1320
+ throw error;
1321
+ }
1322
+ let content = "";
1323
+ const currentToolCalls = [];
1324
+ for await (const chunk of response) {
1325
+ const delta = chunk.choices[0]?.delta;
1326
+ if (delta?.content) {
1327
+ content += delta.content;
1328
+ yield { type: "content", data: delta.content };
1329
+ }
1330
+ if (delta?.tool_calls) {
1331
+ for (const tc of delta.tool_calls) {
1332
+ if (tc.index !== void 0) {
1333
+ if (!currentToolCalls[tc.index]) {
1334
+ currentToolCalls[tc.index] = {
1335
+ id: tc.id || "",
1336
+ type: "function",
1337
+ function: { name: tc.function?.name || "", arguments: "" }
1338
+ };
1339
+ }
1340
+ if (tc.id) currentToolCalls[tc.index].id = tc.id;
1341
+ if (tc.function?.name) currentToolCalls[tc.index].function.name = tc.function.name;
1342
+ if (tc.function?.arguments) {
1343
+ currentToolCalls[tc.index].function.arguments += tc.function.arguments;
1344
+ }
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ if (currentToolCalls.length > 0) {
1350
+ this.messages.push({
1351
+ role: "assistant",
1352
+ content: content || null,
1353
+ tool_calls: currentToolCalls
1354
+ });
1355
+ for (const tc of currentToolCalls) {
1356
+ yield { type: "tool_call", data: { name: tc.function.name, arguments: tc.function.arguments } };
1357
+ const result = await this.executor.execute(
1358
+ tc.function.name,
1359
+ JSON.parse(tc.function.arguments || "{}")
1360
+ );
1361
+ yield { type: "tool_result", data: result };
1362
+ toolCalls.push(result);
1363
+ this.messages.push({
1364
+ role: "tool",
1365
+ tool_call_id: tc.id,
1366
+ content: result.error || JSON.stringify(result.result)
1367
+ });
1368
+ }
1369
+ } else {
1370
+ this.messages.push({ role: "assistant", content });
1371
+ yield { type: "done", data: { output: content, steps, toolCalls } };
1372
+ return;
1373
+ }
1374
+ }
1375
+ yield { type: "done", data: { output: "Max iterations reached", steps, toolCalls } };
1376
+ }
1377
+ /**
1378
+ * Clear conversation history
1379
+ */
1380
+ clearHistory() {
1381
+ this.messages = [{ role: "system", content: this.config.systemPrompt }];
1382
+ }
1383
+ /**
1384
+ * Get conversation history
1385
+ */
1386
+ getHistory() {
1387
+ return [...this.messages];
1388
+ }
1389
+ };
1390
+ function createAgent(client, config) {
1391
+ return new Agent(client, config);
1392
+ }
1393
+
1394
+ // src/tools/builtins.ts
1395
+ function createWebSearchTool(searchProvider = new DuckDuckGoSearch()) {
1396
+ return {
1397
+ name: "web_search",
1398
+ description: "Search the web for information. Use this to find current information, news, or research topics.",
1399
+ parameters: {
1400
+ type: "object",
1401
+ properties: {
1402
+ query: {
1403
+ type: "string",
1404
+ description: "The search query"
1405
+ },
1406
+ maxResults: {
1407
+ type: "number",
1408
+ description: "Maximum number of results to return (default: 5)"
1409
+ }
1410
+ },
1411
+ required: ["query"]
1412
+ },
1413
+ execute: async (params) => {
1414
+ const { query, maxResults = 5 } = params;
1415
+ const results = await searchProvider.search(query, { maxResults });
1416
+ return results.map((r) => ({
1417
+ title: r.title,
1418
+ url: r.url,
1419
+ snippet: r.snippet
1420
+ }));
1421
+ }
1422
+ };
1423
+ }
1424
+ function createFetchUrlTool(fetcher = new WebFetcher()) {
1425
+ return {
1426
+ name: "fetch_url",
1427
+ description: "Fetch and extract content from a URL. Use this to read web pages, articles, or documentation.",
1428
+ parameters: {
1429
+ type: "object",
1430
+ properties: {
1431
+ url: {
1432
+ type: "string",
1433
+ description: "The URL to fetch"
1434
+ },
1435
+ includeLinks: {
1436
+ type: "boolean",
1437
+ description: "Include links found on the page (default: false)"
1438
+ }
1439
+ },
1440
+ required: ["url"]
1441
+ },
1442
+ execute: async (params) => {
1443
+ const { url, includeLinks = false } = params;
1444
+ const result = await fetcher.fetch(url, { includeLinks });
1445
+ return {
1446
+ title: result.title,
1447
+ content: result.markdown || result.content,
1448
+ links: result.links?.slice(0, 10),
1449
+ metadata: result.metadata
1450
+ };
1451
+ }
1452
+ };
1453
+ }
1454
+ function createRAGQueryTool(retriever) {
1455
+ return {
1456
+ name: "rag_query",
1457
+ description: "Search the knowledge base for relevant information. Use this to find information from indexed documents.",
1458
+ parameters: {
1459
+ type: "object",
1460
+ properties: {
1461
+ query: {
1462
+ type: "string",
1463
+ description: "The search query"
1464
+ },
1465
+ topK: {
1466
+ type: "number",
1467
+ description: "Number of results to return (default: 5)"
1468
+ }
1469
+ },
1470
+ required: ["query"]
1471
+ },
1472
+ execute: async (params) => {
1473
+ const { query, topK = 5 } = params;
1474
+ const results = await retriever.retrieve(query, { topK });
1475
+ return results.map((r) => ({
1476
+ content: r.document.content,
1477
+ score: r.score,
1478
+ metadata: r.document.metadata
1479
+ }));
1480
+ }
1481
+ };
1482
+ }
1483
+ function createCalculatorTool() {
1484
+ return {
1485
+ name: "calculator",
1486
+ description: "Perform mathematical calculations. Supports basic arithmetic, powers, and common math functions.",
1487
+ parameters: {
1488
+ type: "object",
1489
+ properties: {
1490
+ expression: {
1491
+ type: "string",
1492
+ description: 'The mathematical expression to evaluate (e.g., "2 + 2", "sqrt(16)", "pow(2, 8)")'
1493
+ }
1494
+ },
1495
+ required: ["expression"]
1496
+ },
1497
+ execute: async (params) => {
1498
+ const { expression } = params;
1499
+ const sanitized = expression.replace(/[^0-9+\-*/().,%\s]/gi, "").replace(/pow/gi, "Math.pow").replace(/sqrt/gi, "Math.sqrt").replace(/abs/gi, "Math.abs").replace(/sin/gi, "Math.sin").replace(/cos/gi, "Math.cos").replace(/tan/gi, "Math.tan").replace(/log/gi, "Math.log").replace(/exp/gi, "Math.exp").replace(/pi/gi, "Math.PI").replace(/e(?![xp])/gi, "Math.E");
1500
+ try {
1501
+ if (!sanitized.trim()) {
1502
+ return { expression, error: "Invalid expression" };
1503
+ }
1504
+ const result = new Function(`return ${sanitized}`)();
1505
+ if (result === void 0 || typeof result === "number" && isNaN(result)) {
1506
+ return { expression, error: "Invalid expression" };
1507
+ }
1508
+ return { expression, result };
1509
+ } catch {
1510
+ return { expression, error: "Invalid expression" };
1511
+ }
1512
+ }
1513
+ };
1514
+ }
1515
+ function createDateTimeTool() {
1516
+ return {
1517
+ name: "get_datetime",
1518
+ description: "Get the current date and time in a specific timezone.",
1519
+ parameters: {
1520
+ type: "object",
1521
+ properties: {
1522
+ timezone: {
1523
+ type: "string",
1524
+ description: 'Timezone (e.g., "UTC", "America/New_York", "Asia/Tokyo"). Defaults to UTC.'
1525
+ }
1526
+ },
1527
+ required: []
1528
+ },
1529
+ execute: async (params) => {
1530
+ const { timezone = "UTC" } = params;
1531
+ const now = /* @__PURE__ */ new Date();
1532
+ try {
1533
+ const formatted = now.toLocaleString("en-US", { timeZone: timezone });
1534
+ return {
1535
+ datetime: formatted,
1536
+ timezone,
1537
+ timestamp: now.toISOString(),
1538
+ unix: Math.floor(now.getTime() / 1e3)
1539
+ };
1540
+ } catch {
1541
+ return {
1542
+ datetime: now.toISOString(),
1543
+ timezone: "UTC",
1544
+ timestamp: now.toISOString(),
1545
+ unix: Math.floor(now.getTime() / 1e3)
1546
+ };
1547
+ }
1548
+ }
1549
+ };
1550
+ }
1551
+ function getBuiltinTools() {
1552
+ return [
1553
+ createWebSearchTool(),
1554
+ createFetchUrlTool(),
1555
+ createCalculatorTool(),
1556
+ createDateTimeTool()
1557
+ ];
1558
+ }
1559
+
1560
+ // src/client.ts
1561
+ var GroqRAG = class {
1562
+ groq;
1563
+ config;
1564
+ retriever;
1565
+ fetcher;
1566
+ searchProvider;
1567
+ chat;
1568
+ web;
1569
+ rag;
1570
+ constructor(config = {}) {
1571
+ this.config = config;
1572
+ this.groq = new Groq2({
1573
+ apiKey: config.apiKey || process.env.GROQ_API_KEY,
1574
+ baseURL: config.baseURL,
1575
+ timeout: config.timeout,
1576
+ maxRetries: config.maxRetries
1577
+ });
1578
+ this.fetcher = createFetcher(config.web);
1579
+ this.searchProvider = createSearchProvider();
1580
+ this.chat = new ChatWithRAG(this);
1581
+ this.web = new WebModule(this);
1582
+ this.rag = new RAGModule(this);
1583
+ }
1584
+ /**
1585
+ * Get the underlying Groq client
1586
+ */
1587
+ get client() {
1588
+ return this.groq;
1589
+ }
1590
+ /**
1591
+ * Initialize RAG with vector store and embeddings
1592
+ */
1593
+ async initRAG(options) {
1594
+ const embeddingConfig = options?.embedding || {
1595
+ provider: "groq"
1596
+ };
1597
+ const vectorStoreConfig = options?.vectorStore || {
1598
+ provider: "memory"
1599
+ };
1600
+ const vectorStore = createVectorStore(vectorStoreConfig);
1601
+ const embeddings = createEmbeddingProvider(embeddingConfig, this.groq);
1602
+ this.retriever = createRetriever(vectorStore, embeddings, options?.chunking);
1603
+ return this.retriever;
1604
+ }
1605
+ /**
1606
+ * Get the retriever (initializes with defaults if not already initialized)
1607
+ */
1608
+ async getRetriever() {
1609
+ if (!this.retriever) {
1610
+ await this.initRAG();
1611
+ }
1612
+ return this.retriever;
1613
+ }
1614
+ /**
1615
+ * Get the web fetcher
1616
+ */
1617
+ getFetcher() {
1618
+ return this.fetcher;
1619
+ }
1620
+ /**
1621
+ * Get the search provider
1622
+ */
1623
+ getSearchProvider() {
1624
+ return this.searchProvider;
1625
+ }
1626
+ /**
1627
+ * Create an agent with tools
1628
+ */
1629
+ createAgent(config) {
1630
+ return createAgent(this.groq, config);
1631
+ }
1632
+ /**
1633
+ * Create an agent with built-in tools (web search, fetch, RAG)
1634
+ */
1635
+ async createAgentWithBuiltins(config) {
1636
+ const retriever = await this.getRetriever();
1637
+ const tools = [
1638
+ ...getBuiltinTools(),
1639
+ createRAGQueryTool(retriever),
1640
+ ...config?.tools || []
1641
+ ];
1642
+ return createAgent(this.groq, { ...config, tools });
1643
+ }
1644
+ /**
1645
+ * Standard chat completions (passthrough to Groq)
1646
+ */
1647
+ async complete(params) {
1648
+ return this.groq.chat.completions.create(params);
1649
+ }
1650
+ /**
1651
+ * Streaming chat completions (passthrough to Groq)
1652
+ */
1653
+ stream(params) {
1654
+ return this.groq.chat.completions.create(params);
1655
+ }
1656
+ };
1657
+ var ChatWithRAG = class {
1658
+ constructor(parent) {
1659
+ this.parent = parent;
1660
+ }
1661
+ /**
1662
+ * Chat with RAG-augmented context
1663
+ */
1664
+ async withRAG(options) {
1665
+ const {
1666
+ messages,
1667
+ model = "llama-3.3-70b-versatile",
1668
+ temperature = 0.7,
1669
+ maxTokens = 1024,
1670
+ topK = 5,
1671
+ minScore = 0,
1672
+ includeMetadata = false,
1673
+ systemPrompt
1674
+ } = options;
1675
+ const lastUserMessage = [...messages].reverse().find((m) => m.role === "user");
1676
+ if (!lastUserMessage || typeof lastUserMessage.content !== "string") {
1677
+ throw new Error("No user message found for RAG retrieval");
1678
+ }
1679
+ const retriever = await this.parent.getRetriever();
1680
+ const results = await retriever.retrieve(lastUserMessage.content, {
1681
+ topK,
1682
+ minScore
1683
+ });
1684
+ const context = formatContext(
1685
+ results.map((r) => ({
1686
+ content: r.document.content,
1687
+ metadata: r.document.metadata
1688
+ })),
1689
+ { includeMetadata }
1690
+ );
1691
+ const ragSystemPrompt = systemPrompt || `You are a helpful assistant. Use the following context to answer questions accurately.
1692
+ If the context doesn't contain relevant information, say so clearly.
1693
+
1694
+ Context:
1695
+ ${context}`;
1696
+ const augmentedMessages = [
1697
+ { role: "system", content: ragSystemPrompt },
1698
+ ...messages
1699
+ ];
1700
+ const response = await this.parent.client.chat.completions.create({
1701
+ model,
1702
+ messages: augmentedMessages,
1703
+ temperature,
1704
+ max_tokens: maxTokens
1705
+ });
1706
+ return {
1707
+ content: response.choices[0]?.message?.content || "",
1708
+ sources: results,
1709
+ usage: response.usage ? {
1710
+ promptTokens: response.usage.prompt_tokens,
1711
+ completionTokens: response.usage.completion_tokens,
1712
+ totalTokens: response.usage.total_tokens
1713
+ } : void 0
1714
+ };
1715
+ }
1716
+ /**
1717
+ * Chat with web search augmentation
1718
+ */
1719
+ async withWebSearch(options) {
1720
+ const {
1721
+ messages,
1722
+ model = "llama-3.3-70b-versatile",
1723
+ searchQuery,
1724
+ maxResults = 5
1725
+ } = options;
1726
+ const lastUserMessage = [...messages].reverse().find((m) => m.role === "user");
1727
+ const query = searchQuery || (typeof lastUserMessage?.content === "string" ? lastUserMessage.content : "");
1728
+ const searchResults = await this.parent.getSearchProvider().search(query, { maxResults });
1729
+ const context = searchResults.map((r, i) => `[${i + 1}] ${r.title}
1730
+ ${r.snippet}
1731
+ URL: ${r.url}`).join("\n\n");
1732
+ const systemPrompt = `You are a helpful assistant with access to web search results.
1733
+ Use the following search results to answer the question. Cite sources using [1], [2], etc.
1734
+
1735
+ Search Results:
1736
+ ${context}`;
1737
+ const response = await this.parent.client.chat.completions.create({
1738
+ model,
1739
+ messages: [{ role: "system", content: systemPrompt }, ...messages]
1740
+ });
1741
+ return {
1742
+ content: response.choices[0]?.message?.content || "",
1743
+ sources: searchResults
1744
+ };
1745
+ }
1746
+ /**
1747
+ * Chat with URL content
1748
+ */
1749
+ async withUrl(options) {
1750
+ const { messages, url, model = "llama-3.3-70b-versatile" } = options;
1751
+ const fetchResult = await this.parent.getFetcher().fetch(url);
1752
+ const systemPrompt = `You are a helpful assistant. Use the following web page content to answer questions.
1753
+
1754
+ Title: ${fetchResult.title || "Unknown"}
1755
+ URL: ${url}
1756
+
1757
+ Content:
1758
+ ${fetchResult.markdown || fetchResult.content}`;
1759
+ const response = await this.parent.client.chat.completions.create({
1760
+ model,
1761
+ messages: [{ role: "system", content: systemPrompt }, ...messages]
1762
+ });
1763
+ return {
1764
+ content: response.choices[0]?.message?.content || "",
1765
+ source: fetchResult
1766
+ };
1767
+ }
1768
+ };
1769
+ var WebModule = class {
1770
+ constructor(parent) {
1771
+ this.parent = parent;
1772
+ }
1773
+ /**
1774
+ * Fetch a URL
1775
+ */
1776
+ async fetch(url, options) {
1777
+ return this.parent.getFetcher().fetch(url, options);
1778
+ }
1779
+ /**
1780
+ * Fetch multiple URLs
1781
+ */
1782
+ async fetchMany(urls, options) {
1783
+ return this.parent.getFetcher().fetchMany(urls, options);
1784
+ }
1785
+ /**
1786
+ * Search the web
1787
+ */
1788
+ async search(query, options) {
1789
+ return this.parent.getSearchProvider().search(query, options);
1790
+ }
1791
+ /**
1792
+ * Fetch URL and convert to markdown
1793
+ */
1794
+ async fetchMarkdown(url) {
1795
+ return this.parent.getFetcher().fetchMarkdown(url);
1796
+ }
1797
+ };
1798
+ var RAGModule = class {
1799
+ constructor(parent) {
1800
+ this.parent = parent;
1801
+ }
1802
+ /**
1803
+ * Add a document to the knowledge base
1804
+ */
1805
+ async addDocument(content, metadata) {
1806
+ const retriever = await this.parent.getRetriever();
1807
+ return retriever.addDocument(content, metadata);
1808
+ }
1809
+ /**
1810
+ * Add multiple documents
1811
+ */
1812
+ async addDocuments(documents) {
1813
+ const retriever = await this.parent.getRetriever();
1814
+ return retriever.addDocuments(documents);
1815
+ }
1816
+ /**
1817
+ * Add a URL's content to the knowledge base
1818
+ */
1819
+ async addUrl(url, metadata) {
1820
+ const fetcher = this.parent.getFetcher();
1821
+ const result = await fetcher.fetch(url);
1822
+ const content = result.markdown || result.content;
1823
+ const retriever = await this.parent.getRetriever();
1824
+ return retriever.addDocument(content, {
1825
+ ...metadata,
1826
+ url,
1827
+ title: result.title,
1828
+ fetchedAt: result.fetchedAt.toISOString()
1829
+ });
1830
+ }
1831
+ /**
1832
+ * Query the knowledge base
1833
+ */
1834
+ async query(query, options) {
1835
+ const retriever = await this.parent.getRetriever();
1836
+ return retriever.retrieve(query, options);
1837
+ }
1838
+ /**
1839
+ * Get formatted context for a query
1840
+ */
1841
+ async getContext(query, options) {
1842
+ const retriever = await this.parent.getRetriever();
1843
+ return retriever.getContext(query, options);
1844
+ }
1845
+ /**
1846
+ * Clear the knowledge base
1847
+ */
1848
+ async clear() {
1849
+ const retriever = await this.parent.getRetriever();
1850
+ return retriever.clear();
1851
+ }
1852
+ /**
1853
+ * Get document count
1854
+ */
1855
+ async count() {
1856
+ const retriever = await this.parent.getRetriever();
1857
+ return retriever.count();
1858
+ }
1859
+ };
1860
+
1861
+ // src/index.ts
1862
+ var index_default = GroqRAG;
1863
+ export {
1864
+ Agent,
1865
+ BraveSearch,
1866
+ ChromaVectorStore,
1867
+ DuckDuckGoSearch,
1868
+ GroqEmbeddings,
1869
+ GroqRAG,
1870
+ MemoryVectorStore,
1871
+ OpenAIEmbeddings,
1872
+ Retriever,
1873
+ SerperSearch,
1874
+ TextChunker,
1875
+ ToolExecutor,
1876
+ WebFetcher,
1877
+ batch,
1878
+ chunkText,
1879
+ cleanText,
1880
+ cosineSimilarity,
1881
+ createAgent,
1882
+ createCalculatorTool,
1883
+ createDateTimeTool,
1884
+ createEmbeddingProvider,
1885
+ createFetchUrlTool,
1886
+ createFetcher,
1887
+ createRAGQueryTool,
1888
+ createRetriever,
1889
+ createSearchProvider,
1890
+ createToolExecutor,
1891
+ createVectorStore,
1892
+ createWebSearchTool,
1893
+ index_default as default,
1894
+ estimateTokens,
1895
+ extractUrls,
1896
+ formatContext,
1897
+ generateId,
1898
+ getBuiltinTools,
1899
+ retry,
1900
+ safeJsonParse,
1901
+ sleep,
1902
+ truncateToTokens
1903
+ };