mycai 1.0.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/LICENSE +23 -0
- package/README.md +119 -0
- package/bin/mycai.js +156 -0
- package/core/ghr_lattice_memory.js +401 -0
- package/core/hdc.js +312 -0
- package/core/memory.js +60 -0
- package/core/memory_engine.js +444 -0
- package/core/morphology.js +357 -0
- package/core/phase.js +87 -0
- package/core/reasoning_router.js +628 -0
- package/core/resonance.js +118 -0
- package/core/safetensors.js +62 -0
- package/core/sovereign_agent.js +164 -0
- package/core/spectral.js +259 -0
- package/core/spectral_decoder.js +868 -0
- package/dist/resonance_sdk.js +2703 -0
- package/dist/resonance_sdk.min.js +14 -0
- package/dist/spectral_core.wasm +0 -0
- package/index.d.ts +45 -0
- package/index.js +26 -0
- package/package.json +52 -0
- package/sdk/README.md +154 -0
- package/sdk/examples/banking_assistant.js +61 -0
- package/sdk/examples/browser_example.html +104 -0
- package/sdk/examples/express_integration.js +61 -0
- package/sdk/examples/node_example.js +33 -0
- package/sdk/examples/quickstart.js +55 -0
- package/sdk/index.js +22 -0
- package/sdk/ingestion.js +237 -0
- package/sdk/licensing.js +168 -0
- package/sdk/resonance_engine.js +594 -0
- package/sdk/resonance_sdk.js +792 -0
- package/sdk/storage.js +185 -0
- package/sdk/tests/sdk_verification.js +98 -0
- package/sdk/tests/test_commercial_sdk.js +136 -0
- package/sdk/types.d.ts +152 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resonance SDK - Express.js Microservice Integration Example
|
|
3
|
+
* Embeds Resonance AI Engine into a secure B2B REST microservice.
|
|
4
|
+
*
|
|
5
|
+
* Run with: node sdk/examples/express_integration.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import { ResonanceEngine, LicenseManager } from '../index.js';
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
|
|
14
|
+
// Initialize Sovereign / Business Engine
|
|
15
|
+
const licenseKey = process.env.RESONANCE_LICENSE_KEY || LicenseManager.generateLicenseKey({
|
|
16
|
+
tier: 'SOVEREIGN',
|
|
17
|
+
expiresAt: Date.now() + 365 * 24 * 3600 * 1000,
|
|
18
|
+
clientId: 'internal-enterprise'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const engine = new ResonanceEngine({ licenseKey });
|
|
22
|
+
await engine.init();
|
|
23
|
+
|
|
24
|
+
// 1. Q&A and Reasoning Endpoint (< 1ms)
|
|
25
|
+
app.post('/api/ask', async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const { prompt } = req.body;
|
|
28
|
+
const result = await engine.ask(prompt);
|
|
29
|
+
res.json(result);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
res.status(500).json({ error: err.message });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 2. Document Ingestion Endpoint
|
|
36
|
+
app.post('/api/ingest', async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const { data, type, title, metadata } = req.body;
|
|
39
|
+
const result = await engine.ingest({ data, type, title, metadata });
|
|
40
|
+
res.json(result);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
res.status(400).json({ error: err.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 3. Summarization Endpoint
|
|
47
|
+
app.post('/api/summarize', (req, res) => {
|
|
48
|
+
const { page, document, mode } = req.body;
|
|
49
|
+
const summary = engine.summarize({ page, document, mode });
|
|
50
|
+
res.json({ summary });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 4. Telemetry Endpoint
|
|
54
|
+
app.get('/api/telemetry', (req, res) => {
|
|
55
|
+
res.json(engine.getTelemetry());
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const PORT = process.env.PORT || 4100;
|
|
59
|
+
app.listen(PORT, () => {
|
|
60
|
+
console.log(`📡 Resonance B2B Microservice active on http://localhost:${PORT}`);
|
|
61
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resonance SDK v1.0 - Node.js Runtime Example
|
|
3
|
+
* Demonstrates basic text generation and morphology analysis in Node.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ResonanceSDK } from '../resonance_sdk.js';
|
|
7
|
+
|
|
8
|
+
async function run() {
|
|
9
|
+
console.log('--- Resonance SDK Node.js Example ---');
|
|
10
|
+
|
|
11
|
+
const sdk = new ResonanceSDK({ temperature: 0.5, maxLength: 8 });
|
|
12
|
+
|
|
13
|
+
console.log('Initializing SDK...');
|
|
14
|
+
await sdk.init();
|
|
15
|
+
console.log('SDK Metrics:', sdk.getMetrics());
|
|
16
|
+
|
|
17
|
+
// Generate text
|
|
18
|
+
console.log('\nGenerating text...');
|
|
19
|
+
const prompt = "yeni bir kitap";
|
|
20
|
+
const result = sdk.generate(prompt);
|
|
21
|
+
console.log(`Prompt: "${prompt}"`);
|
|
22
|
+
console.log(`Generated: "${result.generatedText}"`);
|
|
23
|
+
console.log(`Latency: ${result.totalLatencyMs.toFixed(2)} ms`);
|
|
24
|
+
|
|
25
|
+
// Analyze word
|
|
26
|
+
console.log('\nAnalyzing word morphology...');
|
|
27
|
+
const word = "evlerimizden";
|
|
28
|
+
const analysis = sdk.analyze(word);
|
|
29
|
+
console.log(`Word: "${word}"`);
|
|
30
|
+
console.log('Analysis:', analysis);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
run().catch(console.error);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resonance SDK - 60 Second B2B Quickstart
|
|
3
|
+
* Run with: node sdk/examples/quickstart.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ResonanceEngine, LicenseManager, FileStorageAdapter } from '../index.js';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('🚀 Initializing Resonance Engine (B2B Infrastructure SDK)...');
|
|
10
|
+
|
|
11
|
+
// 1. Generate a test Business Tier License
|
|
12
|
+
const licenseKey = LicenseManager.generateLicenseKey({
|
|
13
|
+
tier: 'BUSINESS',
|
|
14
|
+
expiresAt: Date.now() + 30 * 24 * 3600 * 1000, // 30 days
|
|
15
|
+
clientId: 'acme-defense-corp'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// 2. Instantiate Engine with File Persistence
|
|
19
|
+
const engine = new ResonanceEngine({
|
|
20
|
+
licenseKey,
|
|
21
|
+
storage: new FileStorageAdapter('./scratch/quickstart_memory.json')
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await engine.init();
|
|
25
|
+
console.log('✅ License Verified:', engine.getTelemetry().tier, '| Quota:', engine.getTelemetry().maxNodesQuota);
|
|
26
|
+
|
|
27
|
+
// 3. Ingest Law / Enterprise Knowledge
|
|
28
|
+
console.log('\n📥 Ingesting Document...');
|
|
29
|
+
await engine.ingest({
|
|
30
|
+
type: 'text',
|
|
31
|
+
title: '6100 Sayılı Hukuk Muhakemeleri Kanunu',
|
|
32
|
+
data: `
|
|
33
|
+
Madde 1: Göreve ilişkin kurallar, kamu düzenindendir.
|
|
34
|
+
Madde 2: Asliye hukuk mahkemelerinin görevi, dava konusunun değer ve miktarına bakılmaksızın, malvarlığı haklarına ilişkin davalarla şahıs varlığına ilişkin davaları görmektir.
|
|
35
|
+
Madde 3: İflas ve konkordato davaları asliye ticaret mahkemesinde görülür.
|
|
36
|
+
`
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 4. Ask Direct Fact Question (< 1ms reaction)
|
|
40
|
+
console.log('\n❓ Query: "Asliye hukuk mahkemelerinin görevi nedir?"');
|
|
41
|
+
const response = await engine.ask("Asliye hukuk mahkemelerinin görevi nedir?");
|
|
42
|
+
console.log('⚡ Route:', response.route, '| Latency:', response.latencyMs, 'ms');
|
|
43
|
+
console.log('💬 Answer:\n', response.answer);
|
|
44
|
+
|
|
45
|
+
// 5. Ask Math AST Gate
|
|
46
|
+
console.log('\n❓ Math Gate Query: "250 * 4 + (1500 / 3)"');
|
|
47
|
+
const mathRes = await engine.ask("250 * 4 + (1500 / 3)");
|
|
48
|
+
console.log('⚡ Math Answer:', mathRes.answer, '| Latency:', mathRes.latencyMs, 'ms (Bypassed LLM:', mathRes.llmBypassed, ')');
|
|
49
|
+
|
|
50
|
+
// 6. Inspect Telemetry
|
|
51
|
+
console.log('\n📊 Real-Time Telemetry:');
|
|
52
|
+
console.log(engine.getTelemetry());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main().catch(console.error);
|
package/sdk/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @resonance/core — Commercial B2B Infrastructure SDK
|
|
3
|
+
* Zero-Cloud, Sub-Millisecond Turkish Language Intelligence & Sovereign Memory Engine.
|
|
4
|
+
*
|
|
5
|
+
* @license Commercial
|
|
6
|
+
* @author Turkish Resonance AI Core Team
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { ResonanceEngine, ResonanceSDK } from './resonance_engine.js';
|
|
10
|
+
export { GHRLatticeMemory } from '../core/ghr_lattice_memory.js';
|
|
11
|
+
export { LicenseManager, LICENSE_TIERS } from './licensing.js';
|
|
12
|
+
export { IngestionEngine } from './ingestion.js';
|
|
13
|
+
export {
|
|
14
|
+
BaseStorageAdapter,
|
|
15
|
+
MemoryStorageAdapter,
|
|
16
|
+
FileStorageAdapter,
|
|
17
|
+
IndexedDBStorageAdapter
|
|
18
|
+
} from './storage.js';
|
|
19
|
+
|
|
20
|
+
// Default export
|
|
21
|
+
import { ResonanceEngine } from './resonance_engine.js';
|
|
22
|
+
export default ResonanceEngine;
|
package/sdk/ingestion.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resonance SDK - Modular Document Ingestion Engine
|
|
3
|
+
* Zero-Bloat Ingestion Pipeline: Text and CSV are native (0 KB external dependency).
|
|
4
|
+
* Heavy parsers (PDF, XLSX) are dynamically imported only on-demand when called.
|
|
5
|
+
*
|
|
6
|
+
* @license Commercial
|
|
7
|
+
* @author Turkish Resonance AI Core Team
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class IngestionEngine {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.maxChunkSize = options.maxChunkSize || 1000;
|
|
13
|
+
this.overlap = options.overlap || 100;
|
|
14
|
+
this._pdfModule = null;
|
|
15
|
+
this._xlsxModule = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Universal ingest method
|
|
20
|
+
* @param {Object} input
|
|
21
|
+
* @param {string|Buffer|ArrayBuffer} input.data - File content, text, or buffer
|
|
22
|
+
* @param {'text'|'pdf'|'csv'|'url'} [input.type='text']
|
|
23
|
+
* @param {string} [input.title='Untitled']
|
|
24
|
+
* @param {Object} [input.metadata={}]
|
|
25
|
+
* @returns {Promise<{ success: boolean, records: Array<{ query: string, content: string, confidence: number }>, stats: Object }>}
|
|
26
|
+
*/
|
|
27
|
+
async ingest(input) {
|
|
28
|
+
if (!input || !input.data) {
|
|
29
|
+
throw new Error('Ingest input requires a valid "data" field');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const type = (input.type || this._detectType(input)).toLowerCase();
|
|
33
|
+
const title = input.title || 'Belge';
|
|
34
|
+
const metadata = input.metadata || {};
|
|
35
|
+
|
|
36
|
+
switch (type) {
|
|
37
|
+
case 'pdf':
|
|
38
|
+
return await this.ingestPdf(input.data, title, metadata);
|
|
39
|
+
case 'csv':
|
|
40
|
+
case 'tsv':
|
|
41
|
+
return this.ingestCsv(input.data, title, metadata);
|
|
42
|
+
case 'url':
|
|
43
|
+
return await this.ingestUrl(input.data, title, metadata);
|
|
44
|
+
case 'text':
|
|
45
|
+
default:
|
|
46
|
+
return this.ingestText(input.data, title, metadata);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Native lightweight plain text / markdown ingestion
|
|
52
|
+
*/
|
|
53
|
+
ingestText(data, title, metadata = {}) {
|
|
54
|
+
const rawText = typeof data === 'string' ? data : data.toString('utf8');
|
|
55
|
+
|
|
56
|
+
// Split by double newlines OR numbered articles like "Madde 1:", "1. ", etc.
|
|
57
|
+
let paragraphs = [];
|
|
58
|
+
if (/Madde\s+\d+[:.]/i.test(rawText)) {
|
|
59
|
+
paragraphs = rawText.split(/(?=\bMadde\s+\d+[:.])/i).map(p => p.trim()).filter(p => p.length > 10);
|
|
60
|
+
} else {
|
|
61
|
+
paragraphs = rawText
|
|
62
|
+
.split(/\n{2,}|\r\n\r\n/)
|
|
63
|
+
.map(p => p.trim())
|
|
64
|
+
.filter(p => p.length > 15);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const records = [];
|
|
68
|
+
if (paragraphs.length <= 1) {
|
|
69
|
+
const content = `${title} — ${rawText.trim()}`;
|
|
70
|
+
records.push({
|
|
71
|
+
query: `${title} ${rawText.slice(0, 300)}`,
|
|
72
|
+
content,
|
|
73
|
+
confidence: 0.90,
|
|
74
|
+
metadata: { ...metadata, section: 1 }
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
paragraphs.forEach((para, idx) => {
|
|
78
|
+
const content = `${title} — Bölüm ${idx + 1} — ${para}`;
|
|
79
|
+
records.push({
|
|
80
|
+
query: `${title} ${para.slice(0, 300)}`,
|
|
81
|
+
content,
|
|
82
|
+
confidence: 0.90,
|
|
83
|
+
metadata: { ...metadata, section: idx + 1 }
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
records,
|
|
91
|
+
stats: { type: 'text', totalRecords: records.length, characters: rawText.length }
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Native lightweight CSV / TSV tabular ingestion (Zero-dependency)
|
|
97
|
+
*/
|
|
98
|
+
ingestCsv(data, title, metadata = {}) {
|
|
99
|
+
const rawStr = typeof data === 'string' ? data : data.toString('utf8');
|
|
100
|
+
const lines = rawStr.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
101
|
+
if (lines.length === 0) return { success: false, records: [], stats: { totalRecords: 0 } };
|
|
102
|
+
|
|
103
|
+
const delimiter = lines[0].includes('\t') ? '\t' : (lines[0].includes(';') ? ';' : ',');
|
|
104
|
+
const headers = lines[0].split(delimiter).map(h => h.trim().replace(/^["']|["']$/g, '').toLowerCase());
|
|
105
|
+
|
|
106
|
+
// Map common Turkish / English columns
|
|
107
|
+
const titleIdx = headers.findIndex(h => /başlık|baslik|title|name|ad|proje|konu/i.test(h));
|
|
108
|
+
const descIdx = headers.findIndex(h => /açıklama|aciklama|desc|özet|ozet|detay|summary|detail/i.test(h));
|
|
109
|
+
|
|
110
|
+
const records = [];
|
|
111
|
+
for (let i = 1; i < lines.length; i++) {
|
|
112
|
+
const parts = lines[i].split(delimiter).map(p => p.trim().replace(/^["']|["']$/g, ''));
|
|
113
|
+
if (parts.length < 2) continue;
|
|
114
|
+
|
|
115
|
+
const itemTitle = titleIdx >= 0 && parts[titleIdx] ? parts[titleIdx] : `${title} #${i}`;
|
|
116
|
+
let itemDesc = '';
|
|
117
|
+
|
|
118
|
+
if (descIdx >= 0 && parts[descIdx]) {
|
|
119
|
+
itemDesc = parts[descIdx];
|
|
120
|
+
} else {
|
|
121
|
+
itemDesc = headers.map((h, hIdx) => `${h}: ${parts[hIdx] || ''}`).join(', ');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const content = `${itemTitle} — ${itemDesc}`;
|
|
125
|
+
records.push({
|
|
126
|
+
query: `${title} ${itemTitle} ${itemDesc}`,
|
|
127
|
+
content,
|
|
128
|
+
confidence: 0.88,
|
|
129
|
+
metadata: { ...metadata, row: i }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
records,
|
|
136
|
+
stats: { type: 'csv', totalRecords: records.length, rows: lines.length - 1 }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* On-Demand Dynamic PDF Ingestion (Keeps core SDK ultra-lightweight)
|
|
142
|
+
*/
|
|
143
|
+
async ingestPdf(data, title, metadata = {}) {
|
|
144
|
+
let pdfjs = null;
|
|
145
|
+
|
|
146
|
+
// 1. Check browser global
|
|
147
|
+
if (typeof window !== 'undefined' && window.pdfjsLib) {
|
|
148
|
+
pdfjs = window.pdfjsLib;
|
|
149
|
+
} else {
|
|
150
|
+
// 2. Dynamic import in Node.js
|
|
151
|
+
try {
|
|
152
|
+
pdfjs = await import('pdfjs-dist/legacy/build/pdf.js');
|
|
153
|
+
} catch (e) {
|
|
154
|
+
throw new Error('PDF parsing requires "pdfjs-dist" package in Node.js or "pdf.min.js" in Browser. Install via: npm install pdfjs-dist');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let rawBuffer = data;
|
|
159
|
+
if (typeof data === 'string') {
|
|
160
|
+
// Handle base64 string
|
|
161
|
+
const binaryStr = atob(data);
|
|
162
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
163
|
+
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
|
164
|
+
rawBuffer = bytes;
|
|
165
|
+
} else if (Buffer.isBuffer(data)) {
|
|
166
|
+
rawBuffer = new Uint8Array(data);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const doc = await pdfjs.getDocument({ data: rawBuffer }).promise;
|
|
170
|
+
const records = [];
|
|
171
|
+
const maxPages = Math.min(doc.numPages, 100);
|
|
172
|
+
|
|
173
|
+
for (let p = 1; p <= maxPages; p++) {
|
|
174
|
+
const page = await doc.getPage(p);
|
|
175
|
+
const textContent = await page.getTextContent();
|
|
176
|
+
const pageText = textContent.items.map(it => it.str).join(' ').trim();
|
|
177
|
+
if (pageText.length > 20) {
|
|
178
|
+
const content = `${title} — Sayfa ${p} — ${pageText}`;
|
|
179
|
+
records.push({
|
|
180
|
+
query: `${title} sayfa ${p} ${pageText.slice(0, 300)}`,
|
|
181
|
+
content,
|
|
182
|
+
confidence: 0.92,
|
|
183
|
+
metadata: { ...metadata, page: p, totalPages: doc.numPages }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
success: true,
|
|
190
|
+
records,
|
|
191
|
+
stats: { type: 'pdf', totalPages: doc.numPages, processedPages: records.length }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* URL Ingestion (HTML clean text extraction)
|
|
197
|
+
*/
|
|
198
|
+
async ingestUrl(url, title, metadata = {}) {
|
|
199
|
+
if (typeof fetch === 'undefined') {
|
|
200
|
+
throw new Error('URL ingestion requires global fetch API');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resp = await fetch(url);
|
|
204
|
+
if (!resp.ok) throw new Error(`Failed to fetch URL: HTTP ${resp.status}`);
|
|
205
|
+
|
|
206
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
207
|
+
if (contentType.includes('pdf') || url.endsWith('.pdf')) {
|
|
208
|
+
const arrayBuf = await resp.arrayBuffer();
|
|
209
|
+
return await this.ingestPdf(new Uint8Array(arrayBuf), title, metadata);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const rawHtml = await resp.text();
|
|
213
|
+
const cleanText = rawHtml
|
|
214
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ' ')
|
|
215
|
+
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, ' ')
|
|
216
|
+
.replace(/<[^>]+>/g, ' ')
|
|
217
|
+
.replace(/ /g, ' ')
|
|
218
|
+
.replace(/\s{2,}/g, ' ')
|
|
219
|
+
.trim();
|
|
220
|
+
|
|
221
|
+
return this.ingestText(cleanText, title, { ...metadata, sourceUrl: url });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_detectType(input) {
|
|
225
|
+
if (typeof input.data === 'string') {
|
|
226
|
+
if (input.data.startsWith('http://') || input.data.startsWith('https://')) return 'url';
|
|
227
|
+
if (input.data.includes(',') && input.data.includes('\n')) return 'csv';
|
|
228
|
+
return 'text';
|
|
229
|
+
}
|
|
230
|
+
if (input.filename) {
|
|
231
|
+
const ext = input.filename.split('.').pop().toLowerCase();
|
|
232
|
+
if (ext === 'pdf') return 'pdf';
|
|
233
|
+
if (ext === 'csv') return 'csv';
|
|
234
|
+
}
|
|
235
|
+
return 'text';
|
|
236
|
+
}
|
|
237
|
+
}
|
package/sdk/licensing.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resonance SDK - Commercial Offline Licensing Engine (B2B Infrastructure)
|
|
3
|
+
* 100% Air-Gapped cryptographic license validation and quota enforcement.
|
|
4
|
+
*
|
|
5
|
+
* License format: RES-{TIER}-{EXPIRY_TIMESTAMP}-{MAX_NODES}-{HMAC_SIGNATURE}
|
|
6
|
+
* Example: RES-BUSINESS-1798761600000-50000-a1b2c3d4e5f6...
|
|
7
|
+
*
|
|
8
|
+
* @license Commercial
|
|
9
|
+
* @author Turkish Resonance AI Core Team
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
export const LICENSE_TIERS = {
|
|
15
|
+
STARTER: {
|
|
16
|
+
name: 'Starter',
|
|
17
|
+
maxNodes: 1000,
|
|
18
|
+
allowedRuntimes: ['node', 'browser'],
|
|
19
|
+
features: ['math', 'text', 'basic_memory', 'telemetry'],
|
|
20
|
+
wasmRequired: false
|
|
21
|
+
},
|
|
22
|
+
BUSINESS: {
|
|
23
|
+
name: 'Business',
|
|
24
|
+
maxNodes: 50000,
|
|
25
|
+
allowedRuntimes: ['node', 'browser', 'electron', 'edge'],
|
|
26
|
+
features: ['math', 'morphology', 'text', 'basic_memory', 'pdf', 'csv', 'living_memory', 'smart_summarizer', 'conflict_resolution', 'telemetry'],
|
|
27
|
+
wasmRequired: false
|
|
28
|
+
},
|
|
29
|
+
SOVEREIGN: {
|
|
30
|
+
name: 'Sovereign',
|
|
31
|
+
maxNodes: Infinity,
|
|
32
|
+
allowedRuntimes: ['node', 'browser', 'electron', 'edge', 'tactical_hardware'],
|
|
33
|
+
features: ['math', 'morphology', 'text', 'basic_memory', 'pdf', 'csv', 'living_memory', 'smart_summarizer', 'conflict_resolution', 'wasm_simd', 'custom_rules', 'multi_graph', 'telemetry'],
|
|
34
|
+
wasmRequired: true
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class LicenseManager {
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} [vendorSecret] - Master secret for signing/verifying licenses.
|
|
41
|
+
*/
|
|
42
|
+
constructor(vendorSecret = 'RESONANCE_SOVEREIGN_KEY_2026_AIRGAPPED_HMAC') {
|
|
43
|
+
this._secret = vendorSecret;
|
|
44
|
+
this.currentLicense = null;
|
|
45
|
+
this.tier = LICENSE_TIERS.STARTER;
|
|
46
|
+
this.expiryDate = null;
|
|
47
|
+
this.maxNodes = 1000;
|
|
48
|
+
this.isValid = false;
|
|
49
|
+
this.nodeCount = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate an offline commercial license key (Vendor use)
|
|
54
|
+
* @param {Object} opts
|
|
55
|
+
* @param {'STARTER'|'BUSINESS'|'SOVEREIGN'} opts.tier
|
|
56
|
+
* @param {number|Date} opts.expiresAt
|
|
57
|
+
* @param {number} [opts.maxNodes]
|
|
58
|
+
* @param {string} [opts.clientId]
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
static generateLicenseKey(opts, secret = 'RESONANCE_SOVEREIGN_KEY_2026_AIRGAPPED_HMAC') {
|
|
62
|
+
const tierKey = (opts.tier || 'STARTER').toUpperCase();
|
|
63
|
+
if (!LICENSE_TIERS[tierKey]) throw new Error(`Invalid tier: ${tierKey}`);
|
|
64
|
+
|
|
65
|
+
const tierDef = LICENSE_TIERS[tierKey];
|
|
66
|
+
const expiry = opts.expiresAt instanceof Date ? opts.expiresAt.getTime() : (opts.expiresAt || (Date.now() + 365 * 24 * 3600 * 1000));
|
|
67
|
+
const maxNodes = opts.maxNodes || (tierDef.maxNodes === Infinity ? 0 : tierDef.maxNodes);
|
|
68
|
+
const clientId = (opts.clientId || 'b2b-customer').replace(/[^a-zA-Z0-9]/g, '');
|
|
69
|
+
|
|
70
|
+
const payload = `${tierKey}:${expiry}:${maxNodes}:${clientId}`;
|
|
71
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
72
|
+
hmac.update(payload);
|
|
73
|
+
const signature = hmac.digest('hex').slice(0, 32);
|
|
74
|
+
|
|
75
|
+
return `RES-${tierKey}-${expiry}-${maxNodes}-${clientId}-${signature}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Cryptographically verify an offline license key without cloud ping
|
|
80
|
+
* @param {string} key
|
|
81
|
+
* @returns {{ valid: boolean, tier: Object, expiresAt: Date, maxNodes: number, reason?: string }}
|
|
82
|
+
*/
|
|
83
|
+
verifyLicense(key) {
|
|
84
|
+
if (!key || typeof key !== 'string') {
|
|
85
|
+
return this._fallbackCommunityLicense('No license key provided');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const parts = key.trim().split('-');
|
|
89
|
+
if (parts.length !== 6 || parts[0] !== 'RES') {
|
|
90
|
+
return this._fallbackCommunityLicense('Invalid license key format');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const [_, tierKey, expiryStr, maxNodesStr, clientId, signature] = parts;
|
|
94
|
+
const tierDef = LICENSE_TIERS[tierKey];
|
|
95
|
+
if (!tierDef) {
|
|
96
|
+
return this._fallbackCommunityLicense(`Unrecognized tier: ${tierKey}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const expiryTime = parseInt(expiryStr, 10);
|
|
100
|
+
const maxNodes = parseInt(maxNodesStr, 10);
|
|
101
|
+
|
|
102
|
+
// Verify cryptographic signature
|
|
103
|
+
const payload = `${tierKey}:${expiryTime}:${maxNodes}:${clientId}`;
|
|
104
|
+
const hmac = crypto.createHmac('sha256', this._secret);
|
|
105
|
+
hmac.update(payload);
|
|
106
|
+
const expectedSig = hmac.digest('hex').slice(0, 32);
|
|
107
|
+
|
|
108
|
+
// Constant-time comparison
|
|
109
|
+
if (!crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expectedSig, 'hex'))) {
|
|
110
|
+
return this._fallbackCommunityLicense('Cryptographic signature verification failed');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check expiration
|
|
114
|
+
if (Date.now() > expiryTime) {
|
|
115
|
+
return this._fallbackCommunityLicense(`License expired on ${new Date(expiryTime).toISOString()}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Valid license
|
|
119
|
+
this.currentLicense = key;
|
|
120
|
+
this.tier = tierDef;
|
|
121
|
+
this.expiryDate = new Date(expiryTime);
|
|
122
|
+
this.maxNodes = maxNodes === 0 ? Infinity : maxNodes;
|
|
123
|
+
this.isValid = true;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
valid: true,
|
|
127
|
+
tier: tierDef,
|
|
128
|
+
expiresAt: this.expiryDate,
|
|
129
|
+
maxNodes: this.maxNodes,
|
|
130
|
+
clientId
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a feature is authorized by the current license
|
|
136
|
+
* @param {string} featureName
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
canUseFeature(featureName) {
|
|
140
|
+
return this.tier.features.includes(featureName);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if adding N nodes exceeds quota
|
|
145
|
+
* @param {number} currentTotal
|
|
146
|
+
* @param {number} addingCount
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
149
|
+
checkNodeQuota(currentTotal, addingCount = 1) {
|
|
150
|
+
if (this.maxNodes === Infinity) return true;
|
|
151
|
+
return (currentTotal + addingCount) <= this.maxNodes;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_fallbackCommunityLicense(reason) {
|
|
155
|
+
this.currentLicense = null;
|
|
156
|
+
this.tier = LICENSE_TIERS.STARTER;
|
|
157
|
+
this.expiryDate = null;
|
|
158
|
+
this.maxNodes = LICENSE_TIERS.STARTER.maxNodes;
|
|
159
|
+
this.isValid = false;
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
tier: this.tier,
|
|
163
|
+
expiresAt: null,
|
|
164
|
+
maxNodes: this.maxNodes,
|
|
165
|
+
reason
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|