@stacksjs/ai 0.70.88 → 0.70.90

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 (45) hide show
  1. package/dist/agents/claude/index.d.ts +29 -0
  2. package/dist/agents/claude/index.js +197 -0
  3. package/dist/agents/index.d.ts +6 -0
  4. package/dist/agents/index.js +1 -0
  5. package/dist/buddy.d.ts +67 -0
  6. package/dist/buddy.js +393 -0
  7. package/dist/drivers/anthropic/index.d.ts +40 -0
  8. package/dist/drivers/anthropic/index.js +276 -0
  9. package/dist/drivers/claude-agent-sdk/index.d.ts +40 -0
  10. package/dist/drivers/claude-agent-sdk/index.js +198 -0
  11. package/dist/drivers/index.d.ts +13 -0
  12. package/dist/drivers/index.js +4 -0
  13. package/dist/drivers/ollama/index.d.ts +93 -0
  14. package/dist/drivers/ollama/index.js +332 -0
  15. package/dist/drivers/openai/index.d.ts +66 -0
  16. package/dist/drivers/openai/index.js +351 -0
  17. package/dist/image.d.ts +83 -0
  18. package/dist/image.js +375 -0
  19. package/dist/index.d.ts +39 -0
  20. package/dist/index.js +16 -0
  21. package/dist/mcp.d.ts +115 -0
  22. package/dist/mcp.js +361 -0
  23. package/dist/personalization.d.ts +118 -0
  24. package/dist/personalization.js +244 -0
  25. package/dist/search.d.ts +101 -0
  26. package/dist/search.js +316 -0
  27. package/dist/text.d.ts +10 -0
  28. package/dist/text.js +51 -0
  29. package/dist/types.d.ts +185 -0
  30. package/dist/types.js +0 -0
  31. package/dist/utils/client-bedrock-runtime.d.ts +16 -0
  32. package/dist/utils/client-bedrock-runtime.js +17 -0
  33. package/dist/utils/client-bedrock.d.ts +27 -0
  34. package/dist/utils/client-bedrock.js +20 -0
  35. package/dist/utils/model-access.d.ts +1 -0
  36. package/dist/utils/model-access.js +21 -0
  37. package/dist/utils/retry.d.ts +38 -0
  38. package/dist/utils/retry.js +39 -0
  39. package/dist/utils/tokens.d.ts +50 -0
  40. package/dist/utils/tokens.js +59 -0
  41. package/dist/utils/usage.d.ts +56 -0
  42. package/dist/utils/usage.js +27 -0
  43. package/dist/utils/vision.d.ts +22 -0
  44. package/dist/utils/vision.js +54 -0
  45. package/package.json +1 -1
@@ -0,0 +1,244 @@
1
+ export async function analyzeSentiment(text, options = {}) {
2
+ const { provider = "anthropic", aspects } = options, aspectInstruction = aspects ? `
3
+ Also analyze sentiment for these specific aspects: ${aspects.join(", ")}` : "", systemPrompt = `You are a sentiment analysis expert. Analyze the sentiment of the given text and respond with ONLY valid JSON in this exact format:
4
+ {
5
+ "sentiment": "positive" | "negative" | "neutral" | "mixed",
6
+ "score": <number from -1.0 to 1.0>,
7
+ "confidence": <number from 0.0 to 1.0>${aspects ? `,
8
+ "aspects": [{"aspect": "<name>", "sentiment": "positive" | "negative" | "neutral", "score": <number>}]` : ""}
9
+ }${aspectInstruction}`, result = await callProvider(provider, systemPrompt, text, options.model);
10
+ try {
11
+ return JSON.parse(result.content);
12
+ } catch {
13
+ const lower = result.content.toLowerCase(), isPositive = lower.includes("positive"), isNegative = lower.includes("negative");
14
+ return {
15
+ sentiment: isPositive && isNegative ? "mixed" : isPositive ? "positive" : isNegative ? "negative" : "neutral",
16
+ score: isPositive ? 0.5 : isNegative ? -0.5 : 0,
17
+ confidence: 0.5
18
+ };
19
+ }
20
+ }
21
+ export async function classifyText(text, labels, options = {}) {
22
+ const { provider = "anthropic", multiLabel = !1 } = options, systemPrompt = `You are a text classification expert. Classify the given text into ${multiLabel ? "one or more of" : "exactly one of"} these categories: ${labels.join(", ")}.
23
+
24
+ Respond with ONLY valid JSON in this exact format:
25
+ {
26
+ "label": "<primary label>",
27
+ "confidence": <number from 0.0 to 1.0>,
28
+ "allLabels": [{"label": "<label>", "confidence": <number>}]
29
+ }
30
+
31
+ Include ALL provided labels in allLabels with their confidence scores, sorted by confidence descending.`, result = await callProvider(provider, systemPrompt, text, options.model);
32
+ try {
33
+ return JSON.parse(result.content);
34
+ } catch {
35
+ return {
36
+ label: labels[0] ?? "",
37
+ confidence: 0.5,
38
+ allLabels: labels.map((l) => ({ label: l, confidence: 1 / labels.length }))
39
+ };
40
+ }
41
+ }
42
+ async function summarize(text, options = {}) {
43
+ const {
44
+ provider = "anthropic",
45
+ style = "concise",
46
+ maxLength,
47
+ language
48
+ } = options;
49
+ let styleInstruction;
50
+ switch (style) {
51
+ case "bullet-points":
52
+ styleInstruction = "Use bullet points to organize the key points.";
53
+ break;
54
+ case "detailed":
55
+ styleInstruction = "Provide a detailed summary covering all major points.";
56
+ break;
57
+ default:
58
+ styleInstruction = "Be concise and focus on the most important points.";
59
+ }
60
+ const lengthInstruction = maxLength ? ` Keep the summary under ${maxLength} words.` : "", languageInstruction = language ? ` Write the summary in ${language}.` : "", systemPrompt = `You are an expert summarizer. ${styleInstruction}${lengthInstruction}${languageInstruction}`;
61
+ return callProvider(provider, systemPrompt, `Summarize the following text:
62
+
63
+ ${text}`, options.model);
64
+ }
65
+ export async function recommend(profile, items, options = {}) {
66
+ const { provider = "anthropic", limit = 5 } = options, profileSummary = buildProfileSummary(profile), itemsList = items.map((item) => `ID: ${item.id} | Category: ${item.category || "none"} | Tags: ${item.tags?.join(", ") || "none"} | Content: ${item.content.slice(0, 200)}`).join(`
67
+ `), systemPrompt = `You are a recommendation engine. Based on the user profile, recommend the most relevant items. Respond with ONLY valid JSON:
68
+ {
69
+ "recommendations": [
70
+ {"itemId": "<id>", "score": <0.0-1.0>, "reason": "<brief reason>"}
71
+ ]
72
+ }
73
+
74
+ Return at most ${limit} recommendations, sorted by relevance score descending.`, prompt = `User Profile:
75
+ ${profileSummary}
76
+
77
+ Available Items:
78
+ ${itemsList}`, result = await callProvider(provider, systemPrompt, prompt, options.model);
79
+ try {
80
+ return {
81
+ recommendations: JSON.parse(result.content).recommendations.slice(0, limit),
82
+ model: result.model,
83
+ provider
84
+ };
85
+ } catch {
86
+ return {
87
+ recommendations: [],
88
+ model: result.model,
89
+ provider
90
+ };
91
+ }
92
+ }
93
+ function buildProfileSummary(profile) {
94
+ const topPreferences = Object.entries(profile.preferences).sort(([, a], [, b]) => b - a).slice(0, 10).map(([key, value]) => `${key}: ${value.toFixed(2)}`).join(", "), recentInteractions = profile.interactions.sort((a, b) => b.timestamp - a.timestamp).slice(0, 10).map((i) => `${i.type} on ${i.itemId}`).join(", ");
95
+ return `Segments: ${profile.segments.join(", ")}
96
+ Top Preferences: ${topPreferences || "none"}
97
+ Recent Interactions: ${recentInteractions || "none"}`;
98
+ }
99
+ export function createProfile(id, segments = []) {
100
+ return {
101
+ id,
102
+ preferences: {},
103
+ interactions: [],
104
+ segments
105
+ };
106
+ }
107
+ export function recordInteraction(profile, interaction) {
108
+ profile.interactions.push(interaction);
109
+ const weight = {
110
+ view: 0.1,
111
+ click: 0.3,
112
+ like: 0.5,
113
+ share: 0.6,
114
+ bookmark: 0.7,
115
+ purchase: 1,
116
+ dislike: -0.5,
117
+ custom: interaction.weight || 0.3
118
+ }[interaction.type] || 0.1, key = interaction.itemId;
119
+ profile.preferences[key] = (profile.preferences[key] || 0) + weight;
120
+ return profile;
121
+ }
122
+ export async function extractUserInterests(profile, items, options = {}) {
123
+ const { provider = "anthropic" } = options, interactedItemIds = new Set(profile.interactions.map((i) => i.itemId)), interactedItems = items.filter((item) => interactedItemIds.has(item.id));
124
+ if (interactedItems.length === 0)
125
+ return profile.segments;
126
+ const contentSample = interactedItems.slice(0, 20).map((item) => item.content.slice(0, 200)).join(`
127
+ ---
128
+ `), result = await callProvider(provider, `Extract the main interests/topics from the user's interaction history. Return ONLY a JSON array of strings, e.g. ["technology", "cooking", "travel"]. Maximum 10 interests.`, contentSample, options.model);
129
+ try {
130
+ return JSON.parse(result.content);
131
+ } catch {
132
+ return profile.segments;
133
+ }
134
+ }
135
+ async function callProvider(provider, systemPrompt, userMessage, model) {
136
+ if (provider === "anthropic") {
137
+ const apiKey = process.env.ANTHROPIC_API_KEY;
138
+ if (!apiKey)
139
+ throw Error("ANTHROPIC_API_KEY required.");
140
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ "x-api-key": apiKey,
145
+ "anthropic-version": "2023-06-01"
146
+ },
147
+ body: JSON.stringify({
148
+ model: model || "claude-sonnet-4-20250514",
149
+ max_tokens: 4096,
150
+ system: systemPrompt,
151
+ messages: [{ role: "user", content: userMessage }]
152
+ })
153
+ });
154
+ if (!response.ok) {
155
+ const error = await response.text();
156
+ throw Error(`Claude API error: ${error}`);
157
+ }
158
+ const data = await response.json();
159
+ return {
160
+ content: data.content[0].text,
161
+ model: data.model,
162
+ usage: {
163
+ promptTokens: data.usage?.input_tokens || 0,
164
+ completionTokens: data.usage?.output_tokens || 0,
165
+ totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
166
+ },
167
+ finishReason: data.stop_reason
168
+ };
169
+ }
170
+ if (provider === "openai") {
171
+ const apiKey = process.env.OPENAI_API_KEY;
172
+ if (!apiKey)
173
+ throw Error("OPENAI_API_KEY required.");
174
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ Authorization: `Bearer ${apiKey}`
179
+ },
180
+ body: JSON.stringify({
181
+ model: model || "gpt-4o",
182
+ max_tokens: 4096,
183
+ messages: [
184
+ { role: "system", content: systemPrompt },
185
+ { role: "user", content: userMessage }
186
+ ]
187
+ })
188
+ });
189
+ if (!response.ok) {
190
+ const error = await response.text();
191
+ throw Error(`OpenAI API error: ${error}`);
192
+ }
193
+ const data = await response.json();
194
+ return {
195
+ content: data.choices[0].message.content,
196
+ model: data.model,
197
+ usage: {
198
+ promptTokens: data.usage?.prompt_tokens || 0,
199
+ completionTokens: data.usage?.completion_tokens || 0,
200
+ totalTokens: data.usage?.total_tokens || 0
201
+ },
202
+ finishReason: data.choices[0].finish_reason
203
+ };
204
+ }
205
+ if (provider === "ollama") {
206
+ const host = process.env.OLLAMA_HOST || "http://localhost:11434", response = await fetch(`${host}/api/chat`, {
207
+ method: "POST",
208
+ headers: { "Content-Type": "application/json" },
209
+ body: JSON.stringify({
210
+ model: model || "llama3.2",
211
+ messages: [
212
+ { role: "system", content: systemPrompt },
213
+ { role: "user", content: userMessage }
214
+ ],
215
+ stream: !1
216
+ })
217
+ });
218
+ if (!response.ok) {
219
+ const error = await response.text();
220
+ throw Error(`Ollama API error: ${error}`);
221
+ }
222
+ const data = await response.json();
223
+ return {
224
+ content: data.message.content,
225
+ model: data.model,
226
+ usage: {
227
+ promptTokens: data.prompt_eval_count || 0,
228
+ completionTokens: data.eval_count || 0,
229
+ totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
230
+ },
231
+ finishReason: data.done_reason || "stop"
232
+ };
233
+ }
234
+ throw Error(`Provider not supported: ${provider}`);
235
+ }
236
+ export const personalization = {
237
+ analyzeSentiment,
238
+ classifyText,
239
+ summarize,
240
+ recommend,
241
+ createProfile,
242
+ recordInteraction,
243
+ extractUserInterests
244
+ };
@@ -0,0 +1,101 @@
1
+ import type { AIResult } from './types';
2
+ /**
3
+ * Generate embeddings for text input.
4
+ */
5
+ // eslint-disable-next-line pickier/no-unused-vars
6
+ export declare function createEmbedding(input: string, options?: EmbeddingOptions): Promise<number[]>;
7
+ // eslint-disable-next-line pickier/no-unused-vars
8
+ export declare function createEmbedding(input: string[], options?: EmbeddingOptions): Promise<number[][]>;
9
+ /**
10
+ * Calculate cosine similarity between two vectors.
11
+ */
12
+ export declare function cosineSimilarity(a: number[], b: number[]): number;
13
+ /**
14
+ * Calculate dot product similarity between two vectors.
15
+ */
16
+ export declare function dotProduct(a: number[], b: number[]): number;
17
+ /**
18
+ * Calculate Euclidean distance between two vectors.
19
+ */
20
+ export declare function euclideanDistance(a: number[], b: number[]): number;
21
+ /**
22
+ * Perform RAG: search for relevant documents and use them as context for generation.
23
+ */
24
+ export declare function rag(query: string, index: VectorIndex, options?: RAGOptions): Promise<RAGResult>;
25
+ /**
26
+ * Split text into overlapping chunks for embedding.
27
+ */
28
+ export declare function chunkText(text: string, options?: ChunkOptions): string[];
29
+ /**
30
+ * Create a searchable index from a long text by chunking and embedding.
31
+ */
32
+ export declare function indexText(text: string, options?: ChunkOptions & EmbeddingOptions & { idPrefix?: string }): Promise<VectorIndex>;
33
+ // ============================================================================
34
+ // Exports
35
+ // ============================================================================
36
+ export declare const search: {
37
+ createEmbedding: typeof createEmbedding;
38
+ cosineSimilarity: typeof cosineSimilarity;
39
+ dotProduct: typeof dotProduct;
40
+ euclideanDistance: typeof euclideanDistance;
41
+ VectorIndex: typeof VectorIndex;
42
+ rag: typeof rag;
43
+ chunkText: typeof chunkText;
44
+ indexText: typeof indexText
45
+ };
46
+ // ============================================================================
47
+ // Types
48
+ // ============================================================================
49
+ export declare interface EmbeddingOptions {
50
+ provider?: 'openai' | 'ollama'
51
+ model?: string
52
+ }
53
+ export declare interface SearchDocument {
54
+ id: string
55
+ content: string
56
+ metadata?: Record<string, unknown>
57
+ }
58
+ export declare interface IndexedDocument extends SearchDocument {
59
+ embedding: number[]
60
+ }
61
+ export declare interface SearchResult {
62
+ document: SearchDocument
63
+ score: number
64
+ rank: number
65
+ }
66
+ export declare interface RAGOptions {
67
+ provider?: 'anthropic' | 'openai' | 'ollama'
68
+ embeddingProvider?: 'openai' | 'ollama'
69
+ embeddingModel?: string
70
+ model?: string
71
+ maxTokens?: number
72
+ temperature?: number
73
+ topK?: number
74
+ systemPrompt?: string
75
+ }
76
+ export declare interface RAGResult extends AIResult {
77
+ sources: SearchResult[]
78
+ }
79
+ // ============================================================================
80
+ // Text Chunking Utilities
81
+ // ============================================================================
82
+ export declare interface ChunkOptions {
83
+ chunkSize?: number
84
+ chunkOverlap?: number
85
+ separator?: string
86
+ }
87
+ /**
88
+ * Simple in-memory vector search index.
89
+ * For production, use a dedicated vector database.
90
+ */
91
+ export declare class VectorIndex {
92
+ constructor(options?: EmbeddingOptions);
93
+ add(documents: SearchDocument[]): Promise<void>;
94
+ addWithEmbedding(document: SearchDocument, embedding: number[]): void;
95
+ search(query: string, topK?: number): Promise<SearchResult[]>;
96
+ searchByVector(queryEmbedding: number[], topK?: number): SearchResult[];
97
+ remove(id: string): boolean;
98
+ clear(): void;
99
+ get size(): number;
100
+ get ids(): string[];
101
+ }
package/dist/search.js ADDED
@@ -0,0 +1,316 @@
1
+ export async function createEmbedding(input, options = {}) {
2
+ const { provider = "openai" } = options;
3
+ if (provider === "openai")
4
+ return createEmbeddingOpenAI(input, options.model || "text-embedding-3-small");
5
+ if (provider === "ollama")
6
+ return createEmbeddingOllama(input, options.model || "nomic-embed-text");
7
+ throw Error(`Embedding provider not supported: ${provider}`);
8
+ }
9
+ async function createEmbeddingOpenAI(input, model) {
10
+ const apiKey = process.env.OPENAI_API_KEY;
11
+ if (!apiKey)
12
+ throw Error("OPENAI_API_KEY environment variable is required for embeddings.");
13
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
14
+ method: "POST",
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ Authorization: `Bearer ${apiKey}`
18
+ },
19
+ body: JSON.stringify({ model, input })
20
+ });
21
+ if (!response.ok) {
22
+ const error = await response.text();
23
+ throw Error(`OpenAI Embeddings API error: ${error}`);
24
+ }
25
+ const data = await response.json();
26
+ if (Array.isArray(input))
27
+ return data.data.map((d) => d.embedding);
28
+ const first = data.data[0];
29
+ if (!first)
30
+ throw Error("OpenAI Embeddings API returned no embedding");
31
+ return first.embedding;
32
+ }
33
+ async function createEmbeddingOllama(input, model) {
34
+ const host = process.env.OLLAMA_HOST || "http://localhost:11434", inputs = Array.isArray(input) ? input : [input], embeddings = [];
35
+ for (const text of inputs) {
36
+ const response = await fetch(`${host}/api/embeddings`, {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify({ model, prompt: text })
40
+ });
41
+ if (!response.ok) {
42
+ const error = await response.text();
43
+ throw Error(`Ollama Embeddings API error: ${error}`);
44
+ }
45
+ const data = await response.json();
46
+ embeddings.push(data.embedding);
47
+ }
48
+ if (Array.isArray(input))
49
+ return embeddings;
50
+ const first = embeddings[0];
51
+ if (!first)
52
+ throw Error("Ollama Embeddings API returned no embedding");
53
+ return first;
54
+ }
55
+ export function cosineSimilarity(a, b) {
56
+ if (a.length !== b.length)
57
+ throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);
58
+ let dotProduct = 0, normA = 0, normB = 0;
59
+ for (let i = 0;i < a.length; i++) {
60
+ const av = a[i], bv = b[i];
61
+ dotProduct += av * bv;
62
+ normA += av * av;
63
+ normB += bv * bv;
64
+ }
65
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
66
+ if (denominator === 0)
67
+ return 0;
68
+ return dotProduct / denominator;
69
+ }
70
+ export function dotProduct(a, b) {
71
+ if (a.length !== b.length)
72
+ throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);
73
+ let result = 0;
74
+ for (let i = 0;i < a.length; i++)
75
+ result += a[i] * b[i];
76
+ return result;
77
+ }
78
+ export function euclideanDistance(a, b) {
79
+ if (a.length !== b.length)
80
+ throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);
81
+ let sum = 0;
82
+ for (let i = 0;i < a.length; i++) {
83
+ const diff = a[i] - b[i];
84
+ sum += diff * diff;
85
+ }
86
+ return Math.sqrt(sum);
87
+ }
88
+
89
+ export class VectorIndex {
90
+ documents = [];
91
+ embeddingOptions;
92
+ constructor(options = {}) {
93
+ this.embeddingOptions = options;
94
+ }
95
+ async add(documents) {
96
+ if (documents.length === 0)
97
+ return;
98
+ const contents = documents.map((d) => d.content), embeddings = await createEmbedding(contents, this.embeddingOptions);
99
+ for (let i = 0;i < documents.length; i++) {
100
+ const document = documents[i], embedding = embeddings[i];
101
+ if (!document || !embedding)
102
+ continue;
103
+ this.documents.push({
104
+ ...document,
105
+ embedding
106
+ });
107
+ }
108
+ }
109
+ addWithEmbedding(document, embedding) {
110
+ this.documents.push({ ...document, embedding });
111
+ }
112
+ async search(query, topK = 5) {
113
+ if (this.documents.length === 0)
114
+ return [];
115
+ const queryEmbedding = await createEmbedding(query, this.embeddingOptions);
116
+ return this.searchByVector(queryEmbedding, topK);
117
+ }
118
+ searchByVector(queryEmbedding, topK = 5) {
119
+ if (this.documents.length === 0)
120
+ return [];
121
+ const scored = this.documents.map((doc) => ({
122
+ document: { id: doc.id, content: doc.content, metadata: doc.metadata },
123
+ score: cosineSimilarity(queryEmbedding, doc.embedding)
124
+ }));
125
+ scored.sort((a, b) => b.score - a.score);
126
+ return scored.slice(0, topK).map((result, index) => ({
127
+ ...result,
128
+ rank: index + 1
129
+ }));
130
+ }
131
+ remove(id) {
132
+ const initialLength = this.documents.length;
133
+ this.documents = this.documents.filter((d) => d.id !== id);
134
+ return this.documents.length < initialLength;
135
+ }
136
+ clear() {
137
+ this.documents = [];
138
+ }
139
+ get size() {
140
+ return this.documents.length;
141
+ }
142
+ get ids() {
143
+ return this.documents.map((d) => d.id);
144
+ }
145
+ }
146
+ export async function rag(query, index, options = {}) {
147
+ const {
148
+ provider = "anthropic",
149
+ topK = 5,
150
+ maxTokens = 4096,
151
+ temperature,
152
+ systemPrompt
153
+ } = options, searchResults = await index.search(query, topK), contextText = searchResults.map((r, i) => `[Source ${i + 1}] (score: ${r.score.toFixed(3)})
154
+ ${r.document.content}`).join(`
155
+
156
+ ---
157
+
158
+ `), ragSystemPrompt = systemPrompt || "You are a helpful assistant. Answer the user's question based on the provided context. If the context doesn't contain relevant information, say so. Always cite your sources by referencing [Source N].", fullPrompt = `Context:
159
+ ${contextText}
160
+
161
+ Question: ${query}`;
162
+ if (provider === "anthropic")
163
+ return ragWithAnthropic(fullPrompt, ragSystemPrompt, searchResults, { maxTokens, temperature, model: options.model });
164
+ if (provider === "openai")
165
+ return ragWithOpenAI(fullPrompt, ragSystemPrompt, searchResults, { maxTokens, temperature, model: options.model });
166
+ if (provider === "ollama")
167
+ return ragWithOllama(fullPrompt, ragSystemPrompt, searchResults, { maxTokens, temperature, model: options.model });
168
+ throw Error(`RAG provider not supported: ${provider}`);
169
+ }
170
+ async function ragWithAnthropic(prompt, systemPrompt, sources, options) {
171
+ const apiKey = process.env.ANTHROPIC_API_KEY;
172
+ if (!apiKey)
173
+ throw Error("ANTHROPIC_API_KEY required for RAG with Anthropic.");
174
+ const model = options.model || "claude-sonnet-4-20250514", response = await fetch("https://api.anthropic.com/v1/messages", {
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ "x-api-key": apiKey,
179
+ "anthropic-version": "2023-06-01"
180
+ },
181
+ body: JSON.stringify({
182
+ model,
183
+ max_tokens: options.maxTokens || 4096,
184
+ temperature: options.temperature,
185
+ system: systemPrompt,
186
+ messages: [{ role: "user", content: prompt }]
187
+ })
188
+ });
189
+ if (!response.ok) {
190
+ const error = await response.text();
191
+ throw Error(`Claude API error: ${error}`);
192
+ }
193
+ const data = await response.json();
194
+ return {
195
+ content: data.content[0].text,
196
+ model: data.model,
197
+ usage: {
198
+ promptTokens: data.usage?.input_tokens || 0,
199
+ completionTokens: data.usage?.output_tokens || 0,
200
+ totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
201
+ },
202
+ finishReason: data.stop_reason,
203
+ sources
204
+ };
205
+ }
206
+ async function ragWithOpenAI(prompt, systemPrompt, sources, options) {
207
+ const apiKey = process.env.OPENAI_API_KEY;
208
+ if (!apiKey)
209
+ throw Error("OPENAI_API_KEY required for RAG with OpenAI.");
210
+ const model = options.model || "gpt-4o", response = await fetch("https://api.openai.com/v1/chat/completions", {
211
+ method: "POST",
212
+ headers: {
213
+ "Content-Type": "application/json",
214
+ Authorization: `Bearer ${apiKey}`
215
+ },
216
+ body: JSON.stringify({
217
+ model,
218
+ max_tokens: options.maxTokens || 4096,
219
+ temperature: options.temperature,
220
+ messages: [
221
+ { role: "system", content: systemPrompt },
222
+ { role: "user", content: prompt }
223
+ ]
224
+ })
225
+ });
226
+ if (!response.ok) {
227
+ const error = await response.text();
228
+ throw Error(`OpenAI API error: ${error}`);
229
+ }
230
+ const data = await response.json();
231
+ return {
232
+ content: data.choices[0].message.content,
233
+ model: data.model,
234
+ usage: {
235
+ promptTokens: data.usage?.prompt_tokens || 0,
236
+ completionTokens: data.usage?.completion_tokens || 0,
237
+ totalTokens: data.usage?.total_tokens || 0
238
+ },
239
+ finishReason: data.choices[0].finish_reason,
240
+ sources
241
+ };
242
+ }
243
+ async function ragWithOllama(prompt, systemPrompt, sources, options) {
244
+ const host = process.env.OLLAMA_HOST || "http://localhost:11434", model = options.model || "llama3.2", response = await fetch(`${host}/api/chat`, {
245
+ method: "POST",
246
+ headers: { "Content-Type": "application/json" },
247
+ body: JSON.stringify({
248
+ model,
249
+ messages: [
250
+ { role: "system", content: systemPrompt },
251
+ { role: "user", content: prompt }
252
+ ],
253
+ stream: !1,
254
+ options: {
255
+ temperature: options.temperature
256
+ }
257
+ })
258
+ });
259
+ if (!response.ok) {
260
+ const error = await response.text();
261
+ throw Error(`Ollama API error: ${error}`);
262
+ }
263
+ const data = await response.json();
264
+ return {
265
+ content: data.message.content,
266
+ model: data.model,
267
+ usage: {
268
+ promptTokens: data.prompt_eval_count || 0,
269
+ completionTokens: data.eval_count || 0,
270
+ totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
271
+ },
272
+ finishReason: data.done_reason || "stop",
273
+ sources
274
+ };
275
+ }
276
+ export function chunkText(text, options = {}) {
277
+ const {
278
+ chunkSize = 1000,
279
+ chunkOverlap = 200,
280
+ separator = `
281
+ `
282
+ } = options, segments = text.split(separator), chunks = [];
283
+ let currentChunk = "";
284
+ for (const segment of segments)
285
+ if (currentChunk.length + segment.length + 1 > chunkSize && currentChunk.length > 0) {
286
+ chunks.push(currentChunk.trim());
287
+ if (chunkOverlap > 0) {
288
+ const overlapStart = Math.max(0, currentChunk.length - chunkOverlap);
289
+ currentChunk = currentChunk.slice(overlapStart) + separator + segment;
290
+ } else
291
+ currentChunk = segment;
292
+ } else
293
+ currentChunk += (currentChunk ? separator : "") + segment;
294
+ if (currentChunk.trim())
295
+ chunks.push(currentChunk.trim());
296
+ return chunks;
297
+ }
298
+ export async function indexText(text, options = {}) {
299
+ const chunks = chunkText(text, options), index = new VectorIndex(options), documents = chunks.map((chunk, i) => ({
300
+ id: `${options.idPrefix || "chunk"}-${i}`,
301
+ content: chunk,
302
+ metadata: { chunkIndex: i, totalChunks: chunks.length }
303
+ }));
304
+ await index.add(documents);
305
+ return index;
306
+ }
307
+ export const search = {
308
+ createEmbedding,
309
+ cosineSimilarity,
310
+ dotProduct,
311
+ euclideanDistance,
312
+ VectorIndex,
313
+ rag,
314
+ chunkText,
315
+ indexText
316
+ };
package/dist/text.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export declare function summarize(text: string, options?: SummarizeOptions): Promise<string>;
2
+ export declare function ask(question: string, options?: AskOptions): Promise<string>;
3
+ declare interface AiOptions {
4
+ maxTokenCount?: number
5
+ temperature?: number
6
+ topP?: number
7
+ modelId?: string
8
+ }
9
+ export declare interface SummarizeOptions extends AiOptions {}
10
+ export declare interface AskOptions extends AiOptions {}