diff --git a/package.json b/package.json index aae81010..cb2abf31 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,11 @@ "types": "./dist/universal/index.d.ts" } }, + "browser": { + "./dist/distributed": false, + "./dist/cli": false, + "./dist/scripts": false + }, "engines": { "node": "22.x" }, diff --git a/src/augmentations/auditLogAugmentation.ts b/src/augmentations/auditLogAugmentation.ts index 6252c997..eb84a056 100644 --- a/src/augmentations/auditLogAugmentation.ts +++ b/src/augmentations/auditLogAugmentation.ts @@ -5,7 +5,7 @@ import { BaseAugmentation } from './brainyAugmentation.js' import { AugmentationManifest } from './manifest.js' -import { createHash } from 'node:crypto' +import { createHash } from '../universal/crypto.js' export interface AuditLogConfig { enabled?: boolean diff --git a/src/augmentations/discovery/localDiscovery.ts b/src/augmentations/discovery/localDiscovery.ts index 2afbe88d..32466cfb 100644 --- a/src/augmentations/discovery/localDiscovery.ts +++ b/src/augmentations/discovery/localDiscovery.ts @@ -1,14 +1,34 @@ /** * Local Augmentation Discovery - * + * * Discovers augmentations installed locally in node_modules * and built-in augmentations that ship with Brainy + * + * NOTE: This is a Node.js-only feature that requires filesystem access */ -import { existsSync, readdirSync, readFileSync } from 'node:fs' -import { join } from 'node:path' import { AugmentationManifest } from '../manifest.js' +// Node.js modules - dynamically imported to avoid bundler issues +let fs: any = null +let path: any = null + +// Load Node.js modules if available +if (typeof window === 'undefined') { + try { + fs = await import('node:fs') + path = await import('node:path') + } catch (e) { + // Will throw error in methods if not available + } +} + +// Create compatibility layer for sync methods +const existsSync = fs?.existsSync || (() => { throw new Error('Filesystem not available') }) +const readdirSync = fs?.readdirSync || (() => { throw new Error('Filesystem not available') }) +const readFileSync = fs?.readFileSync || (() => { throw new Error('Filesystem not available') }) +const join = path?.join || ((...args: string[]) => args.join('/')) + export interface LocalAugmentation { id: string name: string diff --git a/src/critical/model-guardian.ts b/src/critical/model-guardian.ts index aacb3e16..ecf6bb4d 100644 --- a/src/critical/model-guardian.ts +++ b/src/critical/model-guardian.ts @@ -11,11 +11,8 @@ * 4. System MUST fail fast if model unavailable in production */ -import { existsSync } from 'node:fs' -import { readFile, mkdir, writeFile, stat } from 'node:fs/promises' -import { join, dirname } from 'node:path' -import { createHash } from 'node:crypto' import { env } from '@huggingface/transformers' +import { createHash } from '../universal/crypto.js' // CRITICAL: These values MUST NEVER CHANGE const CRITICAL_MODEL_CONFIG = { @@ -102,8 +99,8 @@ export class ModelGuardian { return } - // Step 2: In production, FAIL FAST - if (process.env.NODE_ENV === 'production' && !process.env.BRAINY_ALLOW_RUNTIME_DOWNLOAD) { + // Step 2: In production, FAIL FAST (Node.js only) + if (typeof window === 'undefined' && process.env.NODE_ENV === 'production' && !process.env.BRAINY_ALLOW_RUNTIME_DOWNLOAD) { throw new Error( '🚨 CRITICAL FAILURE: Transformer model not found in production!\n' + 'The model is REQUIRED for Brainy to function.\n' + @@ -147,7 +144,18 @@ export class ModelGuardian { * Verify the local model files exist and are correct */ private async verifyLocalModel(): Promise { - const modelBasePath = join(this.modelPath, ...CRITICAL_MODEL_CONFIG.modelName.split('/')) + // Browser doesn't have local file access + if (typeof window !== 'undefined') { + console.log('⚠️ Model verification skipped in browser environment') + return false + } + + // Dynamically import Node.js modules + const fs = await import('node:fs') + const fsPromises = await import('node:fs/promises') + const path = await import('node:path') + + const modelBasePath = path.join(this.modelPath, ...CRITICAL_MODEL_CONFIG.modelName.split('/')) console.log(`🔍 Debug: Checking model at path: ${modelBasePath}`) console.log(`🔍 Debug: Model path components: ${this.modelPath} + ${CRITICAL_MODEL_CONFIG.modelName.split('/')}`) @@ -159,17 +167,17 @@ export class ModelGuardian { ] for (const file of criticalFiles) { - const filePath = join(modelBasePath, file) + const filePath = path.join(modelBasePath, file) console.log(`🔍 Debug: Checking file: ${filePath}`) - - if (!existsSync(filePath)) { + + if (!fs.existsSync(filePath)) { console.log(`❌ Missing critical file: ${file} at ${filePath}`) return false } // Verify size for critical files if (CRITICAL_MODEL_CONFIG.modelSize[file]) { - const stats = await stat(filePath) + const stats = await fsPromises.stat(filePath) const expectedSize = CRITICAL_MODEL_CONFIG.modelSize[file] if (Math.abs(stats.size - expectedSize) > 1000) { // Allow 1KB variance @@ -263,24 +271,37 @@ export class ModelGuardian { * Detect where models should be stored */ private detectModelPath(): string { - const candidates = [ - process.env.BRAINY_MODELS_PATH, - './models', - join(process.cwd(), 'models'), - join(process.env.HOME || '', '.brainy', 'models'), - '/opt/models', // Lambda/container path - env.cacheDir - ] - - for (const path of candidates) { - if (path && existsSync(path)) { - const modelPath = join(path, ...CRITICAL_MODEL_CONFIG.modelName.split('/')) - if (existsSync(join(modelPath, 'onnx', 'model.onnx'))) { - return path // Return the models directory, not its parent + // Browser always uses default path + if (typeof window !== 'undefined') { + return './models' + } + + // Use require for synchronous access in Node.js + try { + const fs = require('node:fs') + const path = require('node:path') + + const candidates = [ + process.env.BRAINY_MODELS_PATH, + './models', + path.join(process.cwd(), 'models'), + path.join(process.env.HOME || '', '.brainy', 'models'), + '/opt/models', // Lambda/container path + env.cacheDir + ] + + for (const candidatePath of candidates) { + if (candidatePath && fs.existsSync(candidatePath)) { + const modelPath = path.join(candidatePath, ...CRITICAL_MODEL_CONFIG.modelName.split('/')) + if (fs.existsSync(path.join(modelPath, 'onnx', 'model.onnx'))) { + return candidatePath // Return the models directory, not its parent + } } } + } catch (e) { + // If Node.js modules not available, return default } - + // Default return './models' } diff --git a/src/utils/embedding.ts b/src/utils/embedding.ts index 1d8b812a..fa1e2848 100644 --- a/src/utils/embedding.ts +++ b/src/utils/embedding.ts @@ -197,8 +197,8 @@ export class TransformerEmbedding implements EmbeddingModel { const { createRequire } = await import('module') const require = createRequire(import.meta.url) - const path = require('path') - const fs = require('fs') + const path = require('node:path') + const fs = require('node:fs') // Try to resolve the package location try { diff --git a/src/utils/paramValidation.ts b/src/utils/paramValidation.ts index 7f1b3a95..0b732054 100644 --- a/src/utils/paramValidation.ts +++ b/src/utils/paramValidation.ts @@ -7,7 +7,33 @@ import { FindParams, AddParams, UpdateParams, RelateParams } from '../types/brainy.types.js' import { NounType, VerbType } from '../types/graphTypes.js' -import * as os from 'node:os' + +// Dynamic import for Node.js os module +let os: any = null +if (typeof window === 'undefined') { + try { + os = await import('node:os') + } catch (e) { + // OS module not available + } +} + +// Browser-safe memory detection +const getSystemMemory = (): number => { + if (os) { + return os.totalmem() + } + // Browser fallback: assume 4GB + return 4 * 1024 * 1024 * 1024 +} + +const getAvailableMemory = (): number => { + if (os) { + return os.freemem() + } + // Browser fallback: assume 2GB available + return 2 * 1024 * 1024 * 1024 +} /** * Auto-configured limits based on system resources @@ -27,8 +53,8 @@ class ValidationConfig { private constructor() { // Auto-configure based on system resources - const totalMemory = os.totalmem() - const availableMemory = os.freemem() + const totalMemory = getSystemMemory() + const availableMemory = getAvailableMemory() // Scale limits based on available memory // 1GB = 10K limit, 8GB = 80K limit, etc. @@ -224,8 +250,8 @@ export function getValidationConfig() { maxLimit: config.maxLimit, maxQueryLength: config.maxQueryLength, maxVectorDimensions: config.maxVectorDimensions, - systemMemory: os.totalmem(), - availableMemory: os.freemem() + systemMemory: getSystemMemory(), + availableMemory: getAvailableMemory() } } diff --git a/src/utils/structuredLogger.ts b/src/utils/structuredLogger.ts index ab08bda7..47240a41 100644 --- a/src/utils/structuredLogger.ts +++ b/src/utils/structuredLogger.ts @@ -5,8 +5,7 @@ */ import { performance } from 'perf_hooks' -import { hostname } from 'node:os' -import { randomUUID } from 'node:crypto' +import { randomUUID } from '../universal/crypto.js' export enum LogLevel { SILENT = -1, @@ -312,10 +311,20 @@ export class StructuredLogger { } if (this.config.includeHost) { - entry.host = hostname() + // Use dynamic import for hostname (Node.js only) + if (typeof window === 'undefined') { + try { + const os = require('node:os') + entry.host = os.hostname() + } catch { + entry.host = 'unknown' + } + } else { + entry.host = window.location?.hostname || 'browser' + } } - if (this.config.includeMemory) { + if (this.config.includeMemory && typeof process !== 'undefined' && process.memoryUsage) { const mem = process.memoryUsage() entry.performance = { memory: {