@emilshirokikh/slyos-sdk 1.3.3 → 1.4.1

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/README.md CHANGED
@@ -37,7 +37,7 @@ const response = await sdk.generate('quantum-1.7b',
37
37
  );
38
38
 
39
39
  console.log(response);
40
- // AI runs locally - zero cost!
40
+ // AI runs locally - no per-inference charges!
41
41
  ```
42
42
 
43
43
  ---
@@ -67,7 +67,7 @@ Authenticates with SlyOS backend and registers device.
67
67
  await sdk.initialize();
68
68
  ```
69
69
 
70
- **Returns:** `Promise<void>`
70
+ **Returns:** `Promise<DeviceProfile>`
71
71
 
72
72
  ---
73
73
 
@@ -116,6 +116,70 @@ const response = await sdk.generate('quantum-1.7b',
116
116
 
117
117
  ---
118
118
 
119
+ #### `chatCompletion(modelId, request)`
120
+ OpenAI-compatible chat completions.
121
+
122
+ ---
123
+
124
+ #### `transcribe(modelId, audio, options?)`
125
+ Speech-to-text using voicecore models.
126
+
127
+ ---
128
+
129
+ #### `recommendModel(category?)`
130
+ Returns best model for the current device's hardware.
131
+
132
+ ---
133
+
134
+ #### `searchModels(query, options?)`
135
+ Search HuggingFace Hub for ONNX-compatible models.
136
+
137
+ ---
138
+
139
+ #### `getDeviceProfile()`
140
+ Returns the device's hardware profile (CPU, RAM, GPU, screen, network).
141
+
142
+ ---
143
+
144
+ #### `getModelContextWindow()`
145
+ Returns current model's context window size in tokens.
146
+
147
+ ---
148
+
149
+ #### `getDeviceId()`
150
+ Returns the persistent device identifier.
151
+
152
+ ---
153
+
154
+ #### `destroy()`
155
+ Flushes pending telemetry and cleans up timers. Call before shutting down.
156
+ ```javascript
157
+ await sdk.destroy(); // Ensures telemetry is sent
158
+ ```
159
+
160
+ #### `getSdkVersion()`
161
+ Returns the current SDK version string (e.g. `'1.4.0'`).
162
+
163
+ #### `getAvailableModels()`
164
+ Returns available models grouped by category (`llm`, `stt`).
165
+
166
+ #### `canRunModel(modelId, quant?)`
167
+ Checks if the current device can run a specific model based on hardware profile.
168
+
169
+ #### `ragQuery(modelId, knowledgeBaseId, query, options?)`
170
+ Performs a RAG query against a cloud-indexed knowledge base. Requires Hybrid RAG plan.
171
+
172
+ #### `ragQueryLocal(modelId, knowledgeBaseId, query, options?)`
173
+ Performs a RAG query using locally-cached embeddings for offline-capable retrieval.
174
+
175
+ #### `ragQueryOffline(modelId, knowledgeBaseId, query, options?)`
176
+ Fully offline RAG query using pre-synced knowledge base data.
177
+
178
+ #### `syncKnowledgeBase(knowledgeBaseId)`
179
+ Downloads and caches a knowledge base locally for offline RAG queries.
180
+
181
+ ---
182
+
119
183
  ## 🌐 Platform Support
120
184
 
121
185
  | Platform | Status | Notes |
@@ -125,7 +189,7 @@ const response = await sdk.generate('quantum-1.7b',
125
189
  | **Edge** | ✅ Supported | Chromium-based |
126
190
  | **Firefox** | ⚠️ Limited | Some models work |
127
191
  | **Node.js** | ✅ Supported | v18+ |
128
- | **React Native** | 🚧 Coming Soon | Q2 2026 |
192
+ | **React Native** | 🚧 Coming Soon | Q3 2026 |
129
193
 
130
194
  ---
131
195
 
@@ -221,25 +285,25 @@ const sdk = new SlyOS({
221
285
  ### Multiple Models
222
286
  ```javascript
223
287
  await sdk.loadModel('quantum-1.7b');
224
- await sdk.loadModel('quantum-1.7b');
288
+ await sdk.loadModel('quantum-3b');
225
289
 
226
290
  // Use different models
227
291
  const fast = await sdk.generate('quantum-1.7b', 'Quick question?');
228
- const detailed = await sdk.generate('quantum-1.7b', 'Complex question?');
292
+ const detailed = await sdk.generate('quantum-3b', 'Complex question?');
229
293
  ```
230
294
 
231
295
  ---
232
296
 
233
297
  ## 📊 Performance
234
298
 
235
- ### Benchmarks (Quantum 360M)
299
+ ### Benchmarks (Quantum 1.7B)
236
300
 
237
301
  | Metric | Browser | Node.js |
238
302
  |--------|---------|---------|
239
303
  | First load | 60-120s | 30-60s |
240
304
  | Cached load | <1s | <0.5s |
241
- | Inference | 35 tok/s | 50 tok/s |
242
- | Memory | 500MB | 300MB |
305
+ | Inference | 10-15 tok/s | 15-25 tok/s |
306
+ | Memory | 1.2GB | 900MB |
243
307
 
244
308
  ---
245
309
 
@@ -271,7 +335,7 @@ const detailed = await sdk.generate('quantum-1.7b', 'Complex question?');
271
335
 
272
336
  - API keys stored client-side (localStorage)
273
337
  - All inference happens locally (private)
274
- - Telemetry sent to SlyOS (anonymized)
338
+ - Inference telemetry batched locally (flushed every 10 inferences or 60s)
275
339
  - No user data sent to cloud
276
340
 
277
341
  ---
@@ -279,9 +343,9 @@ const detailed = await sdk.generate('quantum-1.7b', 'Complex question?');
279
343
  ## 📦 Package Info
280
344
 
281
345
  - **Package:** `@emilshirokikh/slyos-sdk`
282
- - **Version:** 1.0.0
346
+ - **Version:** 1.4.0
283
347
  - **License:** MIT
284
- - **Size:** 13.5 KB (unpacked)
348
+ - **Size:** 168 KB (unpacked)
285
349
  - **Dependencies:** axios, @huggingface/transformers
286
350
 
287
351
  ---
@@ -289,8 +353,8 @@ const detailed = await sdk.generate('quantum-1.7b', 'Complex question?');
289
353
  ## 🤝 Contributing
290
354
  ```bash
291
355
  # Clone repo
292
- git clone https://github.com/BeltoAI/sly.git
293
- cd sly/sdk
356
+ git clone https://github.com/BeltoAI/sly.os.git
357
+ cd sly.os/sdk
294
358
 
295
359
  # Install dependencies
296
360
  npm install
@@ -321,6 +385,6 @@ Built with Hugging Face Transformers.js
321
385
  ## 📞 Support
322
386
 
323
387
  - **npm:** https://www.npmjs.com/package/@emilshirokikh/slyos-sdk
324
- - **GitHub:** https://github.com/BeltoAI/sly
388
+ - **GitHub:** https://github.com/BeltoAI/sly.os
325
389
  - **Docs:** See main README.md
326
390
  - **Email:** support@slyos.world
package/dist/index.d.ts CHANGED
@@ -23,6 +23,19 @@ interface DeviceProfile {
23
23
  os: string;
24
24
  recommendedQuant: QuantizationLevel;
25
25
  maxContextWindow: number;
26
+ deviceFingerprint?: string;
27
+ gpuRenderer?: string;
28
+ gpuVramMb?: number;
29
+ screenWidth?: number;
30
+ screenHeight?: number;
31
+ pixelRatio?: number;
32
+ browserName?: string;
33
+ browserVersion?: string;
34
+ networkType?: string;
35
+ latencyToApiMs?: number;
36
+ timezone?: string;
37
+ wasmAvailable?: boolean;
38
+ webgpuAvailable?: boolean;
26
39
  }
27
40
  interface ProgressEvent {
28
41
  stage: 'initializing' | 'profiling' | 'downloading' | 'loading' | 'ready' | 'generating' | 'transcribing' | 'error';
@@ -31,7 +44,7 @@ interface ProgressEvent {
31
44
  detail?: any;
32
45
  }
33
46
  interface SlyEvent {
34
- type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
47
+ type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error' | 'telemetry_flushed';
35
48
  data?: any;
36
49
  timestamp: number;
37
50
  }
@@ -162,12 +175,21 @@ declare class SlyOS {
162
175
  private onEvent;
163
176
  private fallbackConfig;
164
177
  private modelContextWindow;
178
+ private telemetryBuffer;
179
+ private telemetryFlushTimer;
180
+ private static readonly TELEMETRY_BATCH_SIZE;
181
+ private static readonly TELEMETRY_FLUSH_INTERVAL;
165
182
  constructor(config: SlyOSConfigWithFallback);
166
183
  private emitProgress;
167
184
  private emitEvent;
185
+ private recordTelemetry;
186
+ private flushTelemetry;
168
187
  analyzeDevice(): Promise<DeviceProfile>;
169
188
  getDeviceProfile(): DeviceProfile | null;
170
189
  getModelContextWindow(): number;
190
+ getDeviceId(): string;
191
+ getSdkVersion(): string;
192
+ destroy(): Promise<void>;
171
193
  recommendModel(category?: ModelCategory): {
172
194
  modelId: string;
173
195
  quant: QuantizationLevel;
package/dist/index.js CHANGED
@@ -100,6 +100,186 @@ async function detectContextWindowFromHF(hfModelId) {
100
100
  return 2048;
101
101
  }
102
102
  }
103
+ // ─── SDK Version ────────────────────────────────────────────────────
104
+ const SDK_VERSION = '1.4.1';
105
+ // ─── Persistent Device Identity ─────────────────────────────────────
106
+ async function hashString(str) {
107
+ const isNode = typeof window === 'undefined';
108
+ if (isNode) {
109
+ const crypto = await import('crypto');
110
+ return crypto.createHash('sha256').update(str).digest('hex').substring(0, 32);
111
+ }
112
+ else {
113
+ const encoder = new TextEncoder();
114
+ const data = encoder.encode(str);
115
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
116
+ return Array.from(new Uint8Array(hashBuffer))
117
+ .map(b => b.toString(16).padStart(2, '0'))
118
+ .join('')
119
+ .substring(0, 32);
120
+ }
121
+ }
122
+ async function getOrCreateDeviceId() {
123
+ const isNode = typeof window === 'undefined';
124
+ if (isNode) {
125
+ // Node.js: persist in ~/.slyos/device-id
126
+ try {
127
+ const fs = await import('fs');
128
+ const path = await import('path');
129
+ const os = await import('os');
130
+ const slyosDir = path.join(os.homedir(), '.slyos');
131
+ const idFile = path.join(slyosDir, 'device-id');
132
+ try {
133
+ const existing = fs.readFileSync(idFile, 'utf-8').trim();
134
+ if (existing)
135
+ return existing;
136
+ }
137
+ catch { }
138
+ const deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
139
+ fs.mkdirSync(slyosDir, { recursive: true });
140
+ fs.writeFileSync(idFile, deviceId);
141
+ return deviceId;
142
+ }
143
+ catch {
144
+ return `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
145
+ }
146
+ }
147
+ else {
148
+ // Browser: persist in localStorage
149
+ const key = 'slyos_device_id';
150
+ try {
151
+ const existing = localStorage.getItem(key);
152
+ if (existing)
153
+ return existing;
154
+ }
155
+ catch { }
156
+ const deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
157
+ try {
158
+ localStorage.setItem(key, deviceId);
159
+ }
160
+ catch { }
161
+ return deviceId;
162
+ }
163
+ }
164
+ async function generateDeviceFingerprint() {
165
+ const isNode = typeof window === 'undefined';
166
+ let components = [];
167
+ if (isNode) {
168
+ try {
169
+ const os = await import('os');
170
+ const cpus = os.cpus();
171
+ components.push(cpus[0]?.model || 'unknown-cpu');
172
+ components.push(String(os.totalmem()));
173
+ components.push(os.platform());
174
+ components.push(os.arch());
175
+ components.push(String(cpus.length));
176
+ }
177
+ catch { }
178
+ }
179
+ else {
180
+ components.push(String(navigator.hardwareConcurrency || 0));
181
+ components.push(String(navigator.deviceMemory || 0));
182
+ components.push(navigator.platform || 'unknown');
183
+ // WebGL renderer for GPU fingerprint
184
+ try {
185
+ const canvas = document.createElement('canvas');
186
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
187
+ if (gl) {
188
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
189
+ if (ext) {
190
+ components.push(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || 'unknown-gpu');
191
+ }
192
+ }
193
+ }
194
+ catch { }
195
+ components.push(String(screen.width || 0));
196
+ components.push(String(screen.height || 0));
197
+ }
198
+ return await hashString(components.join('|'));
199
+ }
200
+ // ─── Enhanced Device Profiling ──────────────────────────────────────
201
+ function detectGPU() {
202
+ if (typeof window === 'undefined')
203
+ return { renderer: null, vramMb: 0 };
204
+ try {
205
+ const canvas = document.createElement('canvas');
206
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
207
+ if (!gl)
208
+ return { renderer: null, vramMb: 0 };
209
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
210
+ const renderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : null;
211
+ // Rough VRAM estimate from renderer string
212
+ let vramMb = 0;
213
+ if (renderer) {
214
+ const match = renderer.match(/(\d+)\s*MB/i);
215
+ if (match)
216
+ vramMb = parseInt(match[1]);
217
+ else if (/RTX\s*40/i.test(renderer))
218
+ vramMb = 8192;
219
+ else if (/RTX\s*30/i.test(renderer))
220
+ vramMb = 6144;
221
+ else if (/GTX/i.test(renderer))
222
+ vramMb = 4096;
223
+ else if (/Apple M[2-4]/i.test(renderer))
224
+ vramMb = 8192;
225
+ else if (/Apple M1/i.test(renderer))
226
+ vramMb = 4096;
227
+ else if (/Intel/i.test(renderer))
228
+ vramMb = 1024;
229
+ }
230
+ return { renderer, vramMb };
231
+ }
232
+ catch {
233
+ return { renderer: null, vramMb: 0 };
234
+ }
235
+ }
236
+ function detectBrowser() {
237
+ if (typeof window === 'undefined' || typeof navigator === 'undefined')
238
+ return { name: 'node', version: process.version || 'unknown' };
239
+ const ua = navigator.userAgent;
240
+ if (/Edg\//i.test(ua)) {
241
+ const m = ua.match(/Edg\/([\d.]+)/);
242
+ return { name: 'Edge', version: m?.[1] || '' };
243
+ }
244
+ if (/Chrome\//i.test(ua)) {
245
+ const m = ua.match(/Chrome\/([\d.]+)/);
246
+ return { name: 'Chrome', version: m?.[1] || '' };
247
+ }
248
+ if (/Firefox\//i.test(ua)) {
249
+ const m = ua.match(/Firefox\/([\d.]+)/);
250
+ return { name: 'Firefox', version: m?.[1] || '' };
251
+ }
252
+ if (/Safari\//i.test(ua)) {
253
+ const m = ua.match(/Version\/([\d.]+)/);
254
+ return { name: 'Safari', version: m?.[1] || '' };
255
+ }
256
+ return { name: 'unknown', version: '' };
257
+ }
258
+ function detectNetworkType() {
259
+ if (typeof navigator === 'undefined')
260
+ return 'unknown';
261
+ const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
262
+ if (!conn)
263
+ return 'unknown';
264
+ return conn.effectiveType || conn.type || 'unknown';
265
+ }
266
+ async function measureApiLatency(apiUrl) {
267
+ try {
268
+ const start = Date.now();
269
+ await axios.head(`${apiUrl}/api/health`, { timeout: 5000 });
270
+ return Date.now() - start;
271
+ }
272
+ catch {
273
+ try {
274
+ const start = Date.now();
275
+ await axios.get(`${apiUrl}/api/health`, { timeout: 5000 });
276
+ return Date.now() - start;
277
+ }
278
+ catch {
279
+ return -1;
280
+ }
281
+ }
282
+ }
103
283
  // ─── Device Profiling ───────────────────────────────────────────────
104
284
  async function profileDevice() {
105
285
  const isNode = typeof window === 'undefined';
@@ -151,6 +331,29 @@ async function profileDevice() {
151
331
  }
152
332
  const recommendedQuant = selectQuantization(memoryMB, 'quantum-1.7b'); // default baseline
153
333
  const maxContextWindow = recommendContextWindow(memoryMB, recommendedQuant);
334
+ // Enhanced profiling
335
+ const gpu = detectGPU();
336
+ const browser = detectBrowser();
337
+ const networkType = detectNetworkType();
338
+ const timezone = Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone || 'unknown';
339
+ let screenWidth = 0, screenHeight = 0, pixelRatio = 0;
340
+ let wasmAvailable = false, webgpuAvailable = false;
341
+ if (!isNode) {
342
+ screenWidth = screen?.width || 0;
343
+ screenHeight = screen?.height || 0;
344
+ pixelRatio = window?.devicePixelRatio || 1;
345
+ }
346
+ // Capability detection
347
+ try {
348
+ wasmAvailable = typeof WebAssembly !== 'undefined';
349
+ }
350
+ catch { }
351
+ if (!isNode) {
352
+ try {
353
+ webgpuAvailable = !!navigator.gpu;
354
+ }
355
+ catch { }
356
+ }
154
357
  return {
155
358
  cpuCores,
156
359
  memoryMB,
@@ -159,15 +362,28 @@ async function profileDevice() {
159
362
  os,
160
363
  recommendedQuant,
161
364
  maxContextWindow,
365
+ gpuRenderer: gpu.renderer || undefined,
366
+ gpuVramMb: gpu.vramMb || undefined,
367
+ screenWidth: screenWidth || undefined,
368
+ screenHeight: screenHeight || undefined,
369
+ pixelRatio: pixelRatio || undefined,
370
+ browserName: browser.name,
371
+ browserVersion: browser.version,
372
+ networkType,
373
+ timezone,
374
+ wasmAvailable,
375
+ webgpuAvailable,
162
376
  };
163
377
  }
164
- // ─── Main SDK Class ─────────────────────────────────────────────────
165
378
  class SlyOS {
166
379
  constructor(config) {
167
380
  this.token = null;
168
381
  this.models = new Map();
169
382
  this.deviceProfile = null;
170
383
  this.modelContextWindow = 0;
384
+ // Telemetry batching
385
+ this.telemetryBuffer = [];
386
+ this.telemetryFlushTimer = null;
171
387
  // ═══════════════════════════════════════════════════════════
172
388
  // RAG — Retrieval Augmented Generation
173
389
  // ═══════════════════════════════════════════════════════════
@@ -175,7 +391,7 @@ class SlyOS {
175
391
  this.offlineIndexes = new Map();
176
392
  this.apiKey = config.apiKey;
177
393
  this.apiUrl = config.apiUrl || 'https://api.slyos.world';
178
- this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
394
+ this.deviceId = ''; // Set asynchronously in initialize()
179
395
  this.onProgress = config.onProgress || null;
180
396
  this.onEvent = config.onEvent || null;
181
397
  this.fallbackConfig = config.fallback || null;
@@ -191,13 +407,57 @@ class SlyOS {
191
407
  this.onEvent({ type, data, timestamp: Date.now() });
192
408
  }
193
409
  }
410
+ // ── Telemetry Batching ─────────────────────────────────────────
411
+ recordTelemetry(entry) {
412
+ this.telemetryBuffer.push(entry);
413
+ if (this.telemetryBuffer.length >= SlyOS.TELEMETRY_BATCH_SIZE) {
414
+ this.flushTelemetry();
415
+ }
416
+ else if (!this.telemetryFlushTimer) {
417
+ this.telemetryFlushTimer = setTimeout(() => this.flushTelemetry(), SlyOS.TELEMETRY_FLUSH_INTERVAL);
418
+ }
419
+ }
420
+ async flushTelemetry() {
421
+ if (this.telemetryFlushTimer) {
422
+ clearTimeout(this.telemetryFlushTimer);
423
+ this.telemetryFlushTimer = null;
424
+ }
425
+ if (this.telemetryBuffer.length === 0 || !this.token)
426
+ return;
427
+ const batch = [...this.telemetryBuffer];
428
+ this.telemetryBuffer = [];
429
+ try {
430
+ await axios.post(`${this.apiUrl}/api/devices/telemetry`, {
431
+ device_id: this.deviceId,
432
+ metrics: batch,
433
+ }, {
434
+ headers: { Authorization: `Bearer ${this.token}` },
435
+ timeout: 10000,
436
+ });
437
+ this.emitEvent('telemetry_flushed', { count: batch.length });
438
+ }
439
+ catch {
440
+ // Put back on failure for next attempt
441
+ this.telemetryBuffer.unshift(...batch);
442
+ // Cap buffer to prevent memory leak
443
+ if (this.telemetryBuffer.length > 100) {
444
+ this.telemetryBuffer = this.telemetryBuffer.slice(-100);
445
+ }
446
+ }
447
+ }
194
448
  // ── Device Analysis ─────────────────────────────────────────────
195
449
  async analyzeDevice() {
196
- this.emitProgress('profiling', 10, 'Analyzing device capabilities...');
197
- this.deviceProfile = await profileDevice();
198
- this.emitProgress('profiling', 100, `Device: ${this.deviceProfile.cpuCores} cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM`);
199
- this.emitEvent('device_profiled', this.deviceProfile);
200
- return this.deviceProfile;
450
+ try {
451
+ this.emitProgress('profiling', 10, 'Analyzing device capabilities...');
452
+ this.deviceProfile = await profileDevice();
453
+ this.emitProgress('profiling', 100, `Device: ${this.deviceProfile.cpuCores} cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM`);
454
+ this.emitEvent('device_profiled', this.deviceProfile);
455
+ return this.deviceProfile;
456
+ }
457
+ catch (err) {
458
+ this.emitEvent('error', { method: 'analyzeDevice', error: err.message });
459
+ throw new Error(`Device analysis failed: ${err.message}`);
460
+ }
201
461
  }
202
462
  getDeviceProfile() {
203
463
  return this.deviceProfile;
@@ -205,6 +465,20 @@ class SlyOS {
205
465
  getModelContextWindow() {
206
466
  return this.modelContextWindow;
207
467
  }
468
+ getDeviceId() {
469
+ return this.deviceId;
470
+ }
471
+ getSdkVersion() {
472
+ return SDK_VERSION;
473
+ }
474
+ // Flush remaining telemetry and clean up timers
475
+ async destroy() {
476
+ await this.flushTelemetry();
477
+ if (this.telemetryFlushTimer) {
478
+ clearTimeout(this.telemetryFlushTimer);
479
+ this.telemetryFlushTimer = null;
480
+ }
481
+ }
208
482
  // ── Smart Model Recommendation ──────────────────────────────────
209
483
  recommendModel(category = 'llm') {
210
484
  if (!this.deviceProfile) {
@@ -240,12 +514,16 @@ class SlyOS {
240
514
  // ── Initialize ──────────────────────────────────────────────────
241
515
  async initialize() {
242
516
  this.emitProgress('initializing', 0, 'Starting SlyOS...');
243
- // Step 1: Profile device
517
+ // Step 1: Persistent device ID
518
+ this.deviceId = await getOrCreateDeviceId();
519
+ // Step 2: Profile device (enhanced)
244
520
  this.emitProgress('profiling', 5, 'Detecting device capabilities...');
245
521
  this.deviceProfile = await profileDevice();
246
- this.emitProgress('profiling', 20, `Detected: ${this.deviceProfile.cpuCores} CPU cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM, ${Math.round(this.deviceProfile.estimatedStorageMB / 1024)}GB storage`);
522
+ // Step 2b: Generate device fingerprint
523
+ this.deviceProfile.deviceFingerprint = await generateDeviceFingerprint();
524
+ this.emitProgress('profiling', 20, `Detected: ${this.deviceProfile.cpuCores} CPU cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM${this.deviceProfile.gpuRenderer ? ', GPU: ' + this.deviceProfile.gpuRenderer.substring(0, 30) : ''}`);
247
525
  this.emitEvent('device_profiled', this.deviceProfile);
248
- // Step 2: Authenticate
526
+ // Step 3: Authenticate
249
527
  this.emitProgress('initializing', 40, 'Authenticating with API key...');
250
528
  try {
251
529
  const authRes = await axios.post(`${this.apiUrl}/api/auth/sdk`, {
@@ -260,29 +538,65 @@ class SlyOS {
260
538
  this.emitEvent('error', { stage: 'auth', error: err.message });
261
539
  throw new Error(`SlyOS auth failed: ${err.response?.data?.error || err.message}`);
262
540
  }
263
- // Step 3: Register device with real specs
541
+ // Step 4: Measure API latency
542
+ const latency = await measureApiLatency(this.apiUrl);
543
+ if (latency > 0)
544
+ this.deviceProfile.latencyToApiMs = latency;
545
+ // Step 5: Register device with full intelligence profile
264
546
  this.emitProgress('initializing', 70, 'Registering device...');
265
547
  try {
548
+ // Determine supported quantizations based on memory
549
+ const mem = this.deviceProfile.memoryMB;
550
+ const supportedQuants = ['q4'];
551
+ if (mem >= 4096)
552
+ supportedQuants.push('q8');
553
+ if (mem >= 8192)
554
+ supportedQuants.push('fp16');
555
+ if (mem >= 16384)
556
+ supportedQuants.push('fp32');
557
+ // Determine recommended tier
558
+ let recommendedTier = 1;
559
+ if (mem >= 8192 && this.deviceProfile.cpuCores >= 4)
560
+ recommendedTier = 2;
561
+ if (mem >= 16384 && this.deviceProfile.cpuCores >= 8)
562
+ recommendedTier = 3;
266
563
  await axios.post(`${this.apiUrl}/api/devices/register`, {
267
564
  device_id: this.deviceId,
565
+ device_fingerprint: this.deviceProfile.deviceFingerprint,
268
566
  platform: this.deviceProfile.platform,
269
567
  os_version: this.deviceProfile.os,
270
568
  total_memory_mb: this.deviceProfile.memoryMB,
271
569
  cpu_cores: this.deviceProfile.cpuCores,
272
- has_gpu: false,
273
- recommended_quant: this.deviceProfile.recommendedQuant,
274
- max_context_window: this.deviceProfile.maxContextWindow,
570
+ // Enhanced fields
571
+ gpu_renderer: this.deviceProfile.gpuRenderer || null,
572
+ gpu_vram_mb: this.deviceProfile.gpuVramMb || null,
573
+ screen_width: this.deviceProfile.screenWidth || null,
574
+ screen_height: this.deviceProfile.screenHeight || null,
575
+ pixel_ratio: this.deviceProfile.pixelRatio || null,
576
+ browser_name: this.deviceProfile.browserName || null,
577
+ browser_version: this.deviceProfile.browserVersion || null,
578
+ sdk_version: SDK_VERSION,
579
+ network_type: this.deviceProfile.networkType || null,
580
+ latency_to_api_ms: this.deviceProfile.latencyToApiMs || null,
581
+ timezone: this.deviceProfile.timezone || null,
582
+ // Capabilities
583
+ wasm_available: this.deviceProfile.wasmAvailable || false,
584
+ webgpu_available: this.deviceProfile.webgpuAvailable || false,
585
+ supported_quants: supportedQuants,
586
+ recommended_tier: recommendedTier,
275
587
  }, {
276
588
  headers: { Authorization: `Bearer ${this.token}` },
277
589
  });
278
590
  this.emitProgress('initializing', 90, 'Device registered');
279
- this.emitEvent('device_registered', { deviceId: this.deviceId });
591
+ this.emitEvent('device_registered', { deviceId: this.deviceId, fingerprint: this.deviceProfile.deviceFingerprint });
280
592
  }
281
593
  catch (err) {
282
594
  // Non-fatal — device registration shouldn't block usage
283
595
  this.emitProgress('initializing', 90, 'Device registration skipped (non-fatal)');
284
596
  }
285
- this.emitProgress('ready', 100, `SlyOS ready recommended quantization: ${this.deviceProfile.recommendedQuant.toUpperCase()}`);
597
+ // Step 6: Start telemetry flush timer
598
+ this.telemetryFlushTimer = setTimeout(() => this.flushTelemetry(), SlyOS.TELEMETRY_FLUSH_INTERVAL);
599
+ this.emitProgress('ready', 100, `SlyOS v${SDK_VERSION} ready — ${this.deviceProfile.recommendedQuant.toUpperCase()}, ${this.deviceProfile.gpuRenderer ? 'GPU detected' : 'CPU only'}`);
286
600
  return this.deviceProfile;
287
601
  }
288
602
  // ── Model Loading ───────────────────────────────────────────────
@@ -478,7 +792,11 @@ class SlyOS {
478
792
  if (!this.models.has(modelId)) {
479
793
  await this.loadModel(modelId);
480
794
  }
481
- const { pipe, info, contextWindow } = this.models.get(modelId);
795
+ const loaded = this.models.get(modelId);
796
+ if (!loaded) {
797
+ throw new Error(`Model "${modelId}" failed to load. Check your connection and model ID.`);
798
+ }
799
+ const { pipe, info, contextWindow } = loaded;
482
800
  if (info.category !== 'llm') {
483
801
  throw new Error(`Model "${modelId}" is not an LLM. Use transcribe() for STT models.`);
484
802
  }
@@ -504,6 +822,15 @@ class SlyOS {
504
822
  const tokensPerSec = (tokensGenerated / (latency / 1000)).toFixed(1);
505
823
  this.emitProgress('ready', 100, `Generated ${tokensGenerated} tokens in ${(latency / 1000).toFixed(1)}s (${tokensPerSec} tok/s)`);
506
824
  this.emitEvent('inference_complete', { modelId, latencyMs: latency, tokensGenerated, tokensPerSec: parseFloat(tokensPerSec) });
825
+ // Batch telemetry (new device intelligence)
826
+ this.recordTelemetry({
827
+ latency_ms: latency,
828
+ tokens_generated: tokensGenerated,
829
+ success: true,
830
+ model_id: modelId,
831
+ timestamp: Date.now(),
832
+ });
833
+ // Legacy telemetry (backwards compatible)
507
834
  if (this.token) {
508
835
  await axios.post(`${this.apiUrl}/api/telemetry`, {
509
836
  device_id: this.deviceId,
@@ -521,6 +848,14 @@ class SlyOS {
521
848
  catch (error) {
522
849
  this.emitProgress('error', 0, `Generation failed: ${error.message}`);
523
850
  this.emitEvent('error', { stage: 'inference', modelId, error: error.message });
851
+ // Batch telemetry (failure)
852
+ this.recordTelemetry({
853
+ latency_ms: 0,
854
+ tokens_generated: 0,
855
+ success: false,
856
+ model_id: modelId,
857
+ timestamp: Date.now(),
858
+ });
524
859
  if (this.token) {
525
860
  await axios.post(`${this.apiUrl}/api/telemetry`, {
526
861
  device_id: this.deviceId,
@@ -540,7 +875,11 @@ class SlyOS {
540
875
  if (!this.models.has(modelId)) {
541
876
  await this.loadModel(modelId);
542
877
  }
543
- const { pipe, info } = this.models.get(modelId);
878
+ const loaded = this.models.get(modelId);
879
+ if (!loaded) {
880
+ throw new Error(`Model "${modelId}" failed to load. Check your connection and model ID.`);
881
+ }
882
+ const { pipe, info } = loaded;
544
883
  if (info.category !== 'stt') {
545
884
  throw new Error(`Model "${modelId}" is not an STT model. Use generate() for LLMs.`);
546
885
  }
@@ -1137,4 +1476,6 @@ class SlyOS {
1137
1476
  };
1138
1477
  }
1139
1478
  }
1479
+ SlyOS.TELEMETRY_BATCH_SIZE = 10;
1480
+ SlyOS.TELEMETRY_FLUSH_INTERVAL = 60000; // 60 seconds
1140
1481
  export default SlyOS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emilshirokikh/slyos-sdk",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "description": "SlyOS - On-Device AI SDK for Web and Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -45,6 +45,20 @@ interface DeviceProfile {
45
45
  os: string;
46
46
  recommendedQuant: QuantizationLevel;
47
47
  maxContextWindow: number;
48
+ // Enhanced device intelligence fields
49
+ deviceFingerprint?: string;
50
+ gpuRenderer?: string;
51
+ gpuVramMb?: number;
52
+ screenWidth?: number;
53
+ screenHeight?: number;
54
+ pixelRatio?: number;
55
+ browserName?: string;
56
+ browserVersion?: string;
57
+ networkType?: string;
58
+ latencyToApiMs?: number;
59
+ timezone?: string;
60
+ wasmAvailable?: boolean;
61
+ webgpuAvailable?: boolean;
48
62
  }
49
63
 
50
64
  interface ProgressEvent {
@@ -55,7 +69,7 @@ interface ProgressEvent {
55
69
  }
56
70
 
57
71
  interface SlyEvent {
58
- type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
72
+ type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error' | 'telemetry_flushed';
59
73
  data?: any;
60
74
  timestamp: number;
61
75
  }
@@ -305,6 +319,162 @@ async function detectContextWindowFromHF(hfModelId: string): Promise<number> {
305
319
  }
306
320
  }
307
321
 
322
+ // ─── SDK Version ────────────────────────────────────────────────────
323
+ const SDK_VERSION = '1.4.1';
324
+
325
+ // ─── Persistent Device Identity ─────────────────────────────────────
326
+
327
+ async function hashString(str: string): Promise<string> {
328
+ const isNode = typeof window === 'undefined';
329
+ if (isNode) {
330
+ const crypto = await import('crypto');
331
+ return crypto.createHash('sha256').update(str).digest('hex').substring(0, 32);
332
+ } else {
333
+ const encoder = new TextEncoder();
334
+ const data = encoder.encode(str);
335
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
336
+ return Array.from(new Uint8Array(hashBuffer))
337
+ .map(b => b.toString(16).padStart(2, '0'))
338
+ .join('')
339
+ .substring(0, 32);
340
+ }
341
+ }
342
+
343
+ async function getOrCreateDeviceId(): Promise<string> {
344
+ const isNode = typeof window === 'undefined';
345
+
346
+ if (isNode) {
347
+ // Node.js: persist in ~/.slyos/device-id
348
+ try {
349
+ const fs = await import('fs');
350
+ const path = await import('path');
351
+ const os = await import('os');
352
+ const slyosDir = path.join(os.homedir(), '.slyos');
353
+ const idFile = path.join(slyosDir, 'device-id');
354
+
355
+ try {
356
+ const existing = fs.readFileSync(idFile, 'utf-8').trim();
357
+ if (existing) return existing;
358
+ } catch {}
359
+
360
+ const deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
361
+ fs.mkdirSync(slyosDir, { recursive: true });
362
+ fs.writeFileSync(idFile, deviceId);
363
+ return deviceId;
364
+ } catch {
365
+ return `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
366
+ }
367
+ } else {
368
+ // Browser: persist in localStorage
369
+ const key = 'slyos_device_id';
370
+ try {
371
+ const existing = localStorage.getItem(key);
372
+ if (existing) return existing;
373
+ } catch {}
374
+
375
+ const deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 12)}`;
376
+ try { localStorage.setItem(key, deviceId); } catch {}
377
+ return deviceId;
378
+ }
379
+ }
380
+
381
+ async function generateDeviceFingerprint(): Promise<string> {
382
+ const isNode = typeof window === 'undefined';
383
+ let components: string[] = [];
384
+
385
+ if (isNode) {
386
+ try {
387
+ const os = await import('os');
388
+ const cpus = os.cpus();
389
+ components.push(cpus[0]?.model || 'unknown-cpu');
390
+ components.push(String(os.totalmem()));
391
+ components.push(os.platform());
392
+ components.push(os.arch());
393
+ components.push(String(cpus.length));
394
+ } catch {}
395
+ } else {
396
+ components.push(String(navigator.hardwareConcurrency || 0));
397
+ components.push(String((navigator as any).deviceMemory || 0));
398
+ components.push(navigator.platform || 'unknown');
399
+ // WebGL renderer for GPU fingerprint
400
+ try {
401
+ const canvas = document.createElement('canvas');
402
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') as WebGLRenderingContext | null;
403
+ if (gl) {
404
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
405
+ if (ext) {
406
+ components.push(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || 'unknown-gpu');
407
+ }
408
+ }
409
+ } catch {}
410
+ components.push(String(screen.width || 0));
411
+ components.push(String(screen.height || 0));
412
+ }
413
+
414
+ return await hashString(components.join('|'));
415
+ }
416
+
417
+ // ─── Enhanced Device Profiling ──────────────────────────────────────
418
+
419
+ function detectGPU(): { renderer: string | null; vramMb: number } {
420
+ if (typeof window === 'undefined') return { renderer: null, vramMb: 0 };
421
+ try {
422
+ const canvas = document.createElement('canvas');
423
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') as WebGLRenderingContext | null;
424
+ if (!gl) return { renderer: null, vramMb: 0 };
425
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
426
+ const renderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : null;
427
+ // Rough VRAM estimate from renderer string
428
+ let vramMb = 0;
429
+ if (renderer) {
430
+ const match = renderer.match(/(\d+)\s*MB/i);
431
+ if (match) vramMb = parseInt(match[1]);
432
+ else if (/RTX\s*40/i.test(renderer)) vramMb = 8192;
433
+ else if (/RTX\s*30/i.test(renderer)) vramMb = 6144;
434
+ else if (/GTX/i.test(renderer)) vramMb = 4096;
435
+ else if (/Apple M[2-4]/i.test(renderer)) vramMb = 8192;
436
+ else if (/Apple M1/i.test(renderer)) vramMb = 4096;
437
+ else if (/Intel/i.test(renderer)) vramMb = 1024;
438
+ }
439
+ return { renderer, vramMb };
440
+ } catch {
441
+ return { renderer: null, vramMb: 0 };
442
+ }
443
+ }
444
+
445
+ function detectBrowser(): { name: string; version: string } {
446
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') return { name: 'node', version: process.version || 'unknown' };
447
+ const ua = navigator.userAgent;
448
+ if (/Edg\//i.test(ua)) { const m = ua.match(/Edg\/([\d.]+)/); return { name: 'Edge', version: m?.[1] || '' }; }
449
+ if (/Chrome\//i.test(ua)) { const m = ua.match(/Chrome\/([\d.]+)/); return { name: 'Chrome', version: m?.[1] || '' }; }
450
+ if (/Firefox\//i.test(ua)) { const m = ua.match(/Firefox\/([\d.]+)/); return { name: 'Firefox', version: m?.[1] || '' }; }
451
+ if (/Safari\//i.test(ua)) { const m = ua.match(/Version\/([\d.]+)/); return { name: 'Safari', version: m?.[1] || '' }; }
452
+ return { name: 'unknown', version: '' };
453
+ }
454
+
455
+ function detectNetworkType(): string {
456
+ if (typeof navigator === 'undefined') return 'unknown';
457
+ const conn = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;
458
+ if (!conn) return 'unknown';
459
+ return conn.effectiveType || conn.type || 'unknown';
460
+ }
461
+
462
+ async function measureApiLatency(apiUrl: string): Promise<number> {
463
+ try {
464
+ const start = Date.now();
465
+ await axios.head(`${apiUrl}/api/health`, { timeout: 5000 });
466
+ return Date.now() - start;
467
+ } catch {
468
+ try {
469
+ const start = Date.now();
470
+ await axios.get(`${apiUrl}/api/health`, { timeout: 5000 });
471
+ return Date.now() - start;
472
+ } catch {
473
+ return -1;
474
+ }
475
+ }
476
+ }
477
+
308
478
  // ─── Device Profiling ───────────────────────────────────────────────
309
479
 
310
480
  async function profileDevice(): Promise<DeviceProfile> {
@@ -358,6 +528,27 @@ async function profileDevice(): Promise<DeviceProfile> {
358
528
  const recommendedQuant = selectQuantization(memoryMB, 'quantum-1.7b'); // default baseline
359
529
  const maxContextWindow = recommendContextWindow(memoryMB, recommendedQuant);
360
530
 
531
+ // Enhanced profiling
532
+ const gpu = detectGPU();
533
+ const browser = detectBrowser();
534
+ const networkType = detectNetworkType();
535
+ const timezone = Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone || 'unknown';
536
+
537
+ let screenWidth = 0, screenHeight = 0, pixelRatio = 0;
538
+ let wasmAvailable = false, webgpuAvailable = false;
539
+
540
+ if (!isNode) {
541
+ screenWidth = screen?.width || 0;
542
+ screenHeight = screen?.height || 0;
543
+ pixelRatio = window?.devicePixelRatio || 1;
544
+ }
545
+
546
+ // Capability detection
547
+ try { wasmAvailable = typeof WebAssembly !== 'undefined'; } catch {}
548
+ if (!isNode) {
549
+ try { webgpuAvailable = !!(navigator as any).gpu; } catch {}
550
+ }
551
+
361
552
  return {
362
553
  cpuCores,
363
554
  memoryMB,
@@ -366,11 +557,30 @@ async function profileDevice(): Promise<DeviceProfile> {
366
557
  os,
367
558
  recommendedQuant,
368
559
  maxContextWindow,
560
+ gpuRenderer: gpu.renderer || undefined,
561
+ gpuVramMb: gpu.vramMb || undefined,
562
+ screenWidth: screenWidth || undefined,
563
+ screenHeight: screenHeight || undefined,
564
+ pixelRatio: pixelRatio || undefined,
565
+ browserName: browser.name,
566
+ browserVersion: browser.version,
567
+ networkType,
568
+ timezone,
569
+ wasmAvailable,
570
+ webgpuAvailable,
369
571
  };
370
572
  }
371
573
 
372
574
  // ─── Main SDK Class ─────────────────────────────────────────────────
373
575
 
576
+ interface TelemetryEntry {
577
+ latency_ms: number;
578
+ tokens_generated: number;
579
+ success: boolean;
580
+ model_id: string;
581
+ timestamp: number;
582
+ }
583
+
374
584
  class SlyOS {
375
585
  private apiKey: string;
376
586
  private apiUrl: string;
@@ -382,11 +592,16 @@ class SlyOS {
382
592
  private onEvent: EventCallback | null;
383
593
  private fallbackConfig: FallbackConfig | null;
384
594
  private modelContextWindow: number = 0;
595
+ // Telemetry batching
596
+ private telemetryBuffer: TelemetryEntry[] = [];
597
+ private telemetryFlushTimer: any = null;
598
+ private static readonly TELEMETRY_BATCH_SIZE = 10;
599
+ private static readonly TELEMETRY_FLUSH_INTERVAL = 60000; // 60 seconds
385
600
 
386
601
  constructor(config: SlyOSConfigWithFallback) {
387
602
  this.apiKey = config.apiKey;
388
603
  this.apiUrl = config.apiUrl || 'https://api.slyos.world';
389
- this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
604
+ this.deviceId = ''; // Set asynchronously in initialize()
390
605
  this.onProgress = config.onProgress || null;
391
606
  this.onEvent = config.onEvent || null;
392
607
  this.fallbackConfig = config.fallback || null;
@@ -406,14 +621,59 @@ class SlyOS {
406
621
  }
407
622
  }
408
623
 
624
+ // ── Telemetry Batching ─────────────────────────────────────────
625
+
626
+ private recordTelemetry(entry: TelemetryEntry) {
627
+ this.telemetryBuffer.push(entry);
628
+ if (this.telemetryBuffer.length >= SlyOS.TELEMETRY_BATCH_SIZE) {
629
+ this.flushTelemetry();
630
+ } else if (!this.telemetryFlushTimer) {
631
+ this.telemetryFlushTimer = setTimeout(() => this.flushTelemetry(), SlyOS.TELEMETRY_FLUSH_INTERVAL);
632
+ }
633
+ }
634
+
635
+ private async flushTelemetry() {
636
+ if (this.telemetryFlushTimer) {
637
+ clearTimeout(this.telemetryFlushTimer);
638
+ this.telemetryFlushTimer = null;
639
+ }
640
+ if (this.telemetryBuffer.length === 0 || !this.token) return;
641
+
642
+ const batch = [...this.telemetryBuffer];
643
+ this.telemetryBuffer = [];
644
+
645
+ try {
646
+ await axios.post(`${this.apiUrl}/api/devices/telemetry`, {
647
+ device_id: this.deviceId,
648
+ metrics: batch,
649
+ }, {
650
+ headers: { Authorization: `Bearer ${this.token}` },
651
+ timeout: 10000,
652
+ });
653
+ this.emitEvent('telemetry_flushed', { count: batch.length });
654
+ } catch {
655
+ // Put back on failure for next attempt
656
+ this.telemetryBuffer.unshift(...batch);
657
+ // Cap buffer to prevent memory leak
658
+ if (this.telemetryBuffer.length > 100) {
659
+ this.telemetryBuffer = this.telemetryBuffer.slice(-100);
660
+ }
661
+ }
662
+ }
663
+
409
664
  // ── Device Analysis ─────────────────────────────────────────────
410
665
 
411
666
  async analyzeDevice(): Promise<DeviceProfile> {
412
- this.emitProgress('profiling', 10, 'Analyzing device capabilities...');
413
- this.deviceProfile = await profileDevice();
414
- this.emitProgress('profiling', 100, `Device: ${this.deviceProfile.cpuCores} cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM`);
415
- this.emitEvent('device_profiled', this.deviceProfile);
416
- return this.deviceProfile;
667
+ try {
668
+ this.emitProgress('profiling', 10, 'Analyzing device capabilities...');
669
+ this.deviceProfile = await profileDevice();
670
+ this.emitProgress('profiling', 100, `Device: ${this.deviceProfile.cpuCores} cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM`);
671
+ this.emitEvent('device_profiled', this.deviceProfile);
672
+ return this.deviceProfile;
673
+ } catch (err: any) {
674
+ this.emitEvent('error', { method: 'analyzeDevice', error: err.message });
675
+ throw new Error(`Device analysis failed: ${err.message}`);
676
+ }
417
677
  }
418
678
 
419
679
  getDeviceProfile(): DeviceProfile | null {
@@ -424,6 +684,23 @@ class SlyOS {
424
684
  return this.modelContextWindow;
425
685
  }
426
686
 
687
+ getDeviceId(): string {
688
+ return this.deviceId;
689
+ }
690
+
691
+ getSdkVersion(): string {
692
+ return SDK_VERSION;
693
+ }
694
+
695
+ // Flush remaining telemetry and clean up timers
696
+ async destroy(): Promise<void> {
697
+ await this.flushTelemetry();
698
+ if (this.telemetryFlushTimer) {
699
+ clearTimeout(this.telemetryFlushTimer);
700
+ this.telemetryFlushTimer = null;
701
+ }
702
+ }
703
+
427
704
  // ── Smart Model Recommendation ──────────────────────────────────
428
705
 
429
706
  recommendModel(category: ModelCategory = 'llm'): { modelId: string; quant: QuantizationLevel; contextWindow: number; reason: string } | null {
@@ -467,13 +744,20 @@ class SlyOS {
467
744
  async initialize(): Promise<DeviceProfile> {
468
745
  this.emitProgress('initializing', 0, 'Starting SlyOS...');
469
746
 
470
- // Step 1: Profile device
747
+ // Step 1: Persistent device ID
748
+ this.deviceId = await getOrCreateDeviceId();
749
+
750
+ // Step 2: Profile device (enhanced)
471
751
  this.emitProgress('profiling', 5, 'Detecting device capabilities...');
472
752
  this.deviceProfile = await profileDevice();
473
- this.emitProgress('profiling', 20, `Detected: ${this.deviceProfile.cpuCores} CPU cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM, ${Math.round(this.deviceProfile.estimatedStorageMB / 1024)}GB storage`);
753
+
754
+ // Step 2b: Generate device fingerprint
755
+ this.deviceProfile.deviceFingerprint = await generateDeviceFingerprint();
756
+
757
+ this.emitProgress('profiling', 20, `Detected: ${this.deviceProfile.cpuCores} CPU cores, ${Math.round(this.deviceProfile.memoryMB / 1024 * 10) / 10}GB RAM${this.deviceProfile.gpuRenderer ? ', GPU: ' + this.deviceProfile.gpuRenderer.substring(0, 30) : ''}`);
474
758
  this.emitEvent('device_profiled', this.deviceProfile);
475
759
 
476
- // Step 2: Authenticate
760
+ // Step 3: Authenticate
477
761
  this.emitProgress('initializing', 40, 'Authenticating with API key...');
478
762
  try {
479
763
  const authRes = await axios.post(`${this.apiUrl}/api/auth/sdk`, {
@@ -488,29 +772,63 @@ class SlyOS {
488
772
  throw new Error(`SlyOS auth failed: ${err.response?.data?.error || err.message}`);
489
773
  }
490
774
 
491
- // Step 3: Register device with real specs
775
+ // Step 4: Measure API latency
776
+ const latency = await measureApiLatency(this.apiUrl);
777
+ if (latency > 0) this.deviceProfile.latencyToApiMs = latency;
778
+
779
+ // Step 5: Register device with full intelligence profile
492
780
  this.emitProgress('initializing', 70, 'Registering device...');
493
781
  try {
782
+ // Determine supported quantizations based on memory
783
+ const mem = this.deviceProfile.memoryMB;
784
+ const supportedQuants: string[] = ['q4'];
785
+ if (mem >= 4096) supportedQuants.push('q8');
786
+ if (mem >= 8192) supportedQuants.push('fp16');
787
+ if (mem >= 16384) supportedQuants.push('fp32');
788
+
789
+ // Determine recommended tier
790
+ let recommendedTier = 1;
791
+ if (mem >= 8192 && this.deviceProfile.cpuCores >= 4) recommendedTier = 2;
792
+ if (mem >= 16384 && this.deviceProfile.cpuCores >= 8) recommendedTier = 3;
793
+
494
794
  await axios.post(`${this.apiUrl}/api/devices/register`, {
495
795
  device_id: this.deviceId,
796
+ device_fingerprint: this.deviceProfile.deviceFingerprint,
496
797
  platform: this.deviceProfile.platform,
497
798
  os_version: this.deviceProfile.os,
498
799
  total_memory_mb: this.deviceProfile.memoryMB,
499
800
  cpu_cores: this.deviceProfile.cpuCores,
500
- has_gpu: false,
501
- recommended_quant: this.deviceProfile.recommendedQuant,
502
- max_context_window: this.deviceProfile.maxContextWindow,
801
+ // Enhanced fields
802
+ gpu_renderer: this.deviceProfile.gpuRenderer || null,
803
+ gpu_vram_mb: this.deviceProfile.gpuVramMb || null,
804
+ screen_width: this.deviceProfile.screenWidth || null,
805
+ screen_height: this.deviceProfile.screenHeight || null,
806
+ pixel_ratio: this.deviceProfile.pixelRatio || null,
807
+ browser_name: this.deviceProfile.browserName || null,
808
+ browser_version: this.deviceProfile.browserVersion || null,
809
+ sdk_version: SDK_VERSION,
810
+ network_type: this.deviceProfile.networkType || null,
811
+ latency_to_api_ms: this.deviceProfile.latencyToApiMs || null,
812
+ timezone: this.deviceProfile.timezone || null,
813
+ // Capabilities
814
+ wasm_available: this.deviceProfile.wasmAvailable || false,
815
+ webgpu_available: this.deviceProfile.webgpuAvailable || false,
816
+ supported_quants: supportedQuants,
817
+ recommended_tier: recommendedTier,
503
818
  }, {
504
819
  headers: { Authorization: `Bearer ${this.token}` },
505
820
  });
506
821
  this.emitProgress('initializing', 90, 'Device registered');
507
- this.emitEvent('device_registered', { deviceId: this.deviceId });
822
+ this.emitEvent('device_registered', { deviceId: this.deviceId, fingerprint: this.deviceProfile.deviceFingerprint });
508
823
  } catch (err: any) {
509
824
  // Non-fatal — device registration shouldn't block usage
510
825
  this.emitProgress('initializing', 90, 'Device registration skipped (non-fatal)');
511
826
  }
512
827
 
513
- this.emitProgress('ready', 100, `SlyOS ready recommended quantization: ${this.deviceProfile.recommendedQuant.toUpperCase()}`);
828
+ // Step 6: Start telemetry flush timer
829
+ this.telemetryFlushTimer = setTimeout(() => this.flushTelemetry(), SlyOS.TELEMETRY_FLUSH_INTERVAL);
830
+
831
+ this.emitProgress('ready', 100, `SlyOS v${SDK_VERSION} ready — ${this.deviceProfile.recommendedQuant.toUpperCase()}, ${this.deviceProfile.gpuRenderer ? 'GPU detected' : 'CPU only'}`);
514
832
 
515
833
  return this.deviceProfile;
516
834
  }
@@ -739,7 +1057,11 @@ class SlyOS {
739
1057
  await this.loadModel(modelId);
740
1058
  }
741
1059
 
742
- const { pipe, info, contextWindow } = this.models.get(modelId);
1060
+ const loaded = this.models.get(modelId);
1061
+ if (!loaded) {
1062
+ throw new Error(`Model "${modelId}" failed to load. Check your connection and model ID.`);
1063
+ }
1064
+ const { pipe, info, contextWindow } = loaded;
743
1065
  if (info.category !== 'llm') {
744
1066
  throw new Error(`Model "${modelId}" is not an LLM. Use transcribe() for STT models.`);
745
1067
  }
@@ -771,6 +1093,16 @@ class SlyOS {
771
1093
  this.emitProgress('ready', 100, `Generated ${tokensGenerated} tokens in ${(latency / 1000).toFixed(1)}s (${tokensPerSec} tok/s)`);
772
1094
  this.emitEvent('inference_complete', { modelId, latencyMs: latency, tokensGenerated, tokensPerSec: parseFloat(tokensPerSec) });
773
1095
 
1096
+ // Batch telemetry (new device intelligence)
1097
+ this.recordTelemetry({
1098
+ latency_ms: latency,
1099
+ tokens_generated: tokensGenerated,
1100
+ success: true,
1101
+ model_id: modelId,
1102
+ timestamp: Date.now(),
1103
+ });
1104
+
1105
+ // Legacy telemetry (backwards compatible)
774
1106
  if (this.token) {
775
1107
  await axios.post(`${this.apiUrl}/api/telemetry`, {
776
1108
  device_id: this.deviceId,
@@ -789,6 +1121,15 @@ class SlyOS {
789
1121
  this.emitProgress('error', 0, `Generation failed: ${error.message}`);
790
1122
  this.emitEvent('error', { stage: 'inference', modelId, error: error.message });
791
1123
 
1124
+ // Batch telemetry (failure)
1125
+ this.recordTelemetry({
1126
+ latency_ms: 0,
1127
+ tokens_generated: 0,
1128
+ success: false,
1129
+ model_id: modelId,
1130
+ timestamp: Date.now(),
1131
+ });
1132
+
792
1133
  if (this.token) {
793
1134
  await axios.post(`${this.apiUrl}/api/telemetry`, {
794
1135
  device_id: this.deviceId,
@@ -811,7 +1152,11 @@ class SlyOS {
811
1152
  await this.loadModel(modelId);
812
1153
  }
813
1154
 
814
- const { pipe, info } = this.models.get(modelId);
1155
+ const loaded = this.models.get(modelId);
1156
+ if (!loaded) {
1157
+ throw new Error(`Model "${modelId}" failed to load. Check your connection and model ID.`);
1158
+ }
1159
+ const { pipe, info } = loaded;
815
1160
  if (info.category !== 'stt') {
816
1161
  throw new Error(`Model "${modelId}" is not an STT model. Use generate() for LLMs.`);
817
1162
  }