[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Report a bug to help improve edgeFlow.js\ntitle: '[Bug] '\nlabels: bug\nassignees: ''\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Import '...'\n2. Call '...'\n3. See error\n\n**Expected behavior**\nA clear description of what you expected to happen.\n\n**Code sample**\n```typescript\n// Minimal reproduction\n```\n\n**Environment**\n- Browser: [e.g. Chrome 120, Firefox 118]\n- OS: [e.g. macOS 14, Windows 11]\n- edgeFlow.js version: [e.g. 0.1.0]\n- Runtime: [e.g. WebGPU, WASM]\n\n**Additional context**\nAny other context about the problem.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a feature for edgeFlow.js\ntitle: '[Feature] '\nlabels: enhancement\nassignees: ''\n---\n\n**Is your feature request related to a problem?**\nA clear description of the problem. Ex. \"I'm always frustrated when...\"\n\n**Describe the solution you'd like**\nA clear description of what you want to happen.\n\n**Describe alternatives you've considered**\nAny alternative solutions or features you've considered.\n\n**Additional context**\nAny other context, code examples, or screenshots.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Summary\n\nBrief description of the changes.\n\n## Motivation\n\nWhy is this change needed?\n\n## Changes\n\n- Change 1\n- Change 2\n\n## Testing\n\n- [ ] Unit tests pass (`npm run test:unit`)\n- [ ] TypeScript compiles (`npx tsc --noEmit`)\n- [ ] Lint passes (`npm run lint`)\n- [ ] Tested in browser (if applicable)\n\n## Breaking Changes\n\nList any breaking changes, or \"None\".\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  lint-and-typecheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: npm\n      - run: npm ci\n      - run: npm run lint\n      - run: npx tsc --noEmit\n\n  test:\n    runs-on: ubuntu-latest\n    needs: lint-and-typecheck\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: npm\n      - run: npm ci\n      - run: npm run test:unit\n      - run: npm run test:coverage\n      - uses: actions/upload-artifact@v4\n        if: always()\n        with:\n          name: coverage-report\n          path: coverage/\n\n  build:\n    runs-on: ubuntu-latest\n    needs: test\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: npm\n      - run: npm ci\n      - run: npm run build\n      - uses: actions/upload-artifact@v4\n        with:\n          name: dist\n          path: dist/\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to npm\n\non:\n  release:\n    types: [published]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: npm\n          registry-url: https://registry.npmjs.org\n      - run: npm ci\n      - run: npm run build\n      - run: npm run test:unit\n      - run: npm publish --provenance --access public\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Dependencies\nnode_modules/\n\n# Build outputs (keep dist/ for npm publishing)\n# dist/\n\n# IDE\n.idea/\n.vscode/\n*.swp\n*.swo\n.DS_Store\n\n# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Test coverage\ncoverage/\n\n# Playwright / E2E test output\ntest-results/\n\n# Environment\n.env\n.env.local\n.env.*.local\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Temporary files\ntmp/\ntemp/\n.tmp/\n.temp/\n.vercel\n.env*.local\n\n# Personal docs (not for public repo)\nINTERVIEW_PREP.md\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Commands\n\n- **Build:** `npm run build` (runs `tsc` then `scripts/build-browser.js` which produces `dist/edgeflow.browser.js` via esbuild; `onnxruntime-web` is marked external).\n- **Watch compile:** `npm run dev`\n- **Lint:** `npm run lint` (ESLint on `src/**/*.ts`)\n- **Unit/integration tests (vitest, happy-dom):** `npm test` / `npm run test:unit` / `npm run test:integration`\n  - Single test file: `npx vitest run tests/unit/tokenizer.test.ts`\n  - Single test by name: `npx vitest run -t \"test name pattern\"`\n- **E2E (Playwright, Chromium):** `npm run test:e2e`\n  - Uses `playwright.config.ts` by default; alternate configs exist for `localai`, `network`, `privatedoc` scenarios (run with `npx playwright test -c playwright.localai.config.ts`).\n  - Playwright auto-starts `npm run demo:server` on `localhost:3000`.\n- **Demo app:** `npm run demo` (builds then serves `demo/server.js` on port 3000). Load a Hugging Face ONNX URL in the browser UI to exercise pipelines.\n- **Docs (VitePress):** `npm run docs:dev` / `npm run docs:build`\n\n## Architecture\n\nedgeFlow.js is a browser-first ML inference framework. The runtime graph is: **Pipeline → BasePipeline → RuntimeManager → Runtime backend (ONNX/WebGPU/WebNN/WASM) → Scheduler → MemoryManager**. All public exports flow through `src/index.ts`.\n\n### Layered structure (`src/`)\n\n- **`core/`** — framework internals. `types.ts` is the canonical type/error surface (`EdgeFlowError`, `ErrorCodes`, all `Tensor`/`Runtime`/`Pipeline` interfaces — most other files import from here). `runtime.ts` holds the `RuntimeManager` singleton, runtime factory registry, and priority-based automatic backend selection (webgpu > webnn > wasm). `scheduler.ts` implements the global priority queue / concurrency-limited `InferenceScheduler` that every runtime dispatches through. `memory.ts` provides `MemoryManager`, `MemoryScope`, and `ModelCache` with reference-counted cleanup. `composer.ts` enables `compose()`/`parallel()` multi-stage pipelines. `plugin.ts` is the extension point for third-party pipelines/backends/middleware. `device-profiler.ts` recommends quantization/model variant based on device tier. `worker.ts` runs inference in a Web Worker.\n\n- **`backends/`** — concrete `Runtime` implementations: `onnx.ts` (onnxruntime-web, peer dep), `webgpu.ts`, `webnn.ts`, `wasm.ts`, plus `transformers-adapter.ts` for interop with transformers.js. `registerAllBackends()` wires factories into `RuntimeManager`.\n\n- **`pipelines/`** — task-specific wrappers extending `base.ts`'s `BasePipeline`. The `pipeline(task, options?)` factory in `index.ts` looks up a registered pipeline factory (built-in or plugin) and returns a ready-to-run instance. Each pipeline owns its own tokenizer/preprocessor, model loading, and result formatting.\n\n- **`utils/`** — `tokenizer.ts` (BPE, WordPiece, Unigram — loads `tokenizer.json` directly), `preprocessor.ts` (image/audio/text), `model-loader.ts` (preloading, sharding, resumable downloads), `cache.ts` (`InferenceCache`, `ModelDownloadCache` — IndexedDB-backed), `hub.ts` (HuggingFace Hub download helpers + `POPULAR_MODELS`), `offline.ts`.\n\n- **`tools/`** — developer tooling surface: `quantization.ts` (int8/uint8/float16 quant + dequant), `debugger.ts` (tensor inspection, histograms, heatmaps, trace events), `monitor.ts` (`PerformanceMonitor` + dashboard generators), `benchmark.ts`.\n\n### Cross-cutting conventions\n\n- **ESM only** (`\"type\": \"module\"`, `sideEffects: false`). All intra-repo imports use `.js` extensions even from `.ts` source — required for Node ESM resolution after `tsc` emits.\n- **`onnxruntime-web` is an optional peer dep** marked `external` in the browser bundle; consumer bundlers resolve it. Do not import it eagerly in code paths that should run without ONNX.\n- **Errors always use `EdgeFlowError` + `ErrorCodes`** from `core/types.ts` — do not throw bare `Error` from library code.\n- **Scheduling is mandatory:** runtime inference paths go through `getScheduler()`. New backends should dispatch via the scheduler rather than calling model.run directly, so priority/concurrency controls are honored.\n- **Tests:** unit/integration run under happy-dom (no real WebGPU/WebNN); those backends are exercised via Playwright E2E against the demo server. Test timeout is 30s.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to edgeFlow.js\n\nThank you for your interest in contributing to edgeFlow.js! This guide will help you get started.\n\n## Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/s-zx/edgeflow.js.git\ncd edgeflow.js\n\n# Install dependencies\nnpm install\n\n# Build the project\nnpm run build\n\n# Run tests\nnpm run test:unit\n\n# Start development mode (watch)\nnpm run dev\n```\n\n## Project Structure\n\n```\nsrc/\n├── core/           # Runtime, scheduler, memory, tensor, types\n├── backends/       # ONNX Runtime (production), WebGPU/WebNN (planned)\n├── pipelines/      # Task pipelines (text-generation, image-segmentation, etc.)\n├── utils/          # Tokenizer, preprocessor, cache, model-loader, hub\n└── tools/          # Quantization, benchmark, debugger, monitor\n```\n\n## How to Contribute\n\n### Reporting Bugs\n\nOpen an issue using the bug report template. Include:\n- A minimal code reproduction\n- Browser and OS information\n- edgeFlow.js version\n\n### Suggesting Features\n\nOpen an issue using the feature request template describing:\n- The problem you're trying to solve\n- Your proposed solution\n- Alternatives you've considered\n\n### Submitting Code\n\n1. Fork the repository\n2. Create a feature branch: `git checkout -b feature/my-feature`\n3. Make your changes\n4. Run checks: `npm run lint && npx tsc --noEmit && npm run test:unit`\n5. Commit with a descriptive message\n6. Push and open a pull request\n\n### Good First Issues\n\nLook for issues labeled `good first issue`. These are scoped tasks ideal for newcomers:\n- Adding tests for uncovered modules\n- Improving error messages\n- Adding examples\n- Documentation improvements\n\n## Code Standards\n\n- **TypeScript strict mode** — all strict options are enabled\n- **No `any`** — use proper types; `unknown` if truly dynamic\n- **ESM only** — use `.js` extensions in imports\n- **No console.log in library code** — use the event system or `console.warn` for important warnings\n- **Dispose pattern** — all resources must be disposable to prevent memory leaks\n\n## Testing\n\n```bash\nnpm run test:unit        # Run unit tests\nnpm run test:integration # Run integration tests\nnpm run test:coverage    # Generate coverage report\nnpm run test:watch       # Watch mode\n```\n\nTests use [Vitest](https://vitest.dev/). Place tests in:\n- `tests/unit/` — for isolated unit tests\n- `tests/integration/` — for pipeline/backend integration tests\n- `tests/e2e/` — for browser-based tests\n\n## Architecture Decisions\n\nedgeFlow.js is designed as an **orchestration layer**, not an inference engine. Key principles:\n\n1. **Backend agnostic** — work with any inference engine (ONNX Runtime, transformers.js, custom)\n2. **Production-first** — scheduling, memory management, error recovery matter more than model count\n3. **Honest API** — experimental features are clearly labeled, not presented as production-ready\n4. **Plugin-friendly** — custom pipelines and backends can be registered at runtime\n\n## License\n\nBy contributing, you agree that your contributions will be licensed under the MIT License.\n"
  },
  {
    "path": "README.md",
    "content": "# edgeFlow.js\n\n<div align=\"center\">\n\n**Browser ML inference framework with task scheduling and smart caching.**\n\n[![npm version](https://img.shields.io/npm/v/edgeflowjs.svg)](https://www.npmjs.com/package/edgeflowjs)\n[![install size](https://packagephobia.com/badge?p=edgeflowjs)](https://packagephobia.com/result?p=edgeflowjs)\n[![license](https://img.shields.io/npm/l/edgeflowjs)](LICENSE)\n\n[Documentation](https://edgeflow.js.org) · [Examples](examples/) · [API Reference](https://edgeflow.js.org/api) · [English](README.md) | [中文](README_CN.md)\n\n</div>\n\n---\n\n## ✨ Features\n\n- 📋 **Task Scheduler** - Priority queue, concurrency control, task cancellation\n- 🔄 **Batch Processing** - Efficient batch inference out of the box\n- 💾 **Memory Management** - Automatic memory tracking and cleanup with scopes\n- 📥 **Smart Model Loading** - Preloading, sharding, resume download support\n- 💿 **Offline Caching** - IndexedDB-based model caching for offline use\n- ⚡ **Multi-Backend** - ONNX Runtime with WebGPU/WASM execution providers, automatic fallback\n- 🤗 **HuggingFace Hub** - Direct model download with one line\n- 🔤 **Real Tokenizers** - BPE & WordPiece tokenizers, load tokenizer.json directly\n- 👷 **Web Worker Support** - Run inference in background threads\n- 📦 **Batteries Included** - ONNX Runtime bundled, zero configuration needed\n- 🎯 **TypeScript First** - Full type support with intuitive APIs\n\n## 📦 Installation\n\n```bash\nnpm install edgeflowjs\n```\n\n```bash\nyarn add edgeflowjs\n```\n\n```bash\npnpm add edgeflowjs\n```\n\n> **Note**: ONNX Runtime is included as a dependency. No additional setup required.\n\n## 🚀 Quick Start\n\n### Try the Demo\n\nRun the interactive demo locally to test all features:\n\n```bash\n# Clone and install\ngit clone https://github.com/user/edgeflow.js.git\ncd edgeflow.js\nnpm install\n\n# Build and start demo server\nnpm run demo\n```\n\nOpen **http://localhost:3000** in your browser:\n\n1. **Load Model** - Enter a Hugging Face ONNX model URL and click \"Load Model\"\n   ```\n   https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx\n   ```\n\n2. **Test Features**:\n   - 🧮 **Tensor Operations** - Test tensor creation, math ops, softmax, relu\n   - 📝 **Text Classification** - Run sentiment analysis on text\n   - 🔍 **Feature Extraction** - Extract embeddings from text\n   - ⚡ **Task Scheduling** - Test priority-based scheduling\n   - 📋 **Task Scheduler** - Test priority-based task scheduling\n   - 💾 **Memory Management** - Test allocation and cleanup\n\n### Basic Usage\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// Create a sentiment analysis pipeline\nconst sentiment = await pipeline('sentiment-analysis');\n\n// Run inference\nconst result = await sentiment.run('I love this product!');\nconsole.log(result);\n// { label: 'positive', score: 0.98, processingTime: 12.5 }\n```\n\n### Batch Processing\n\n```typescript\n// Native batch processing support\nconst results = await sentiment.run([\n  'This is amazing!',\n  'This is terrible.',\n  'It\\'s okay I guess.'\n]);\n\nconsole.log(results);\n// [\n//   { label: 'positive', score: 0.95 },\n//   { label: 'negative', score: 0.92 },\n//   { label: 'neutral', score: 0.68 }\n// ]\n```\n\n### Multiple Pipelines\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// Create multiple pipelines\nconst classifier = await pipeline('text-classification');\nconst extractor = await pipeline('feature-extraction');\n\n// Run in parallel with Promise.all\nconst [classification, features] = await Promise.all([\n  classifier.run('Sample text'),\n  extractor.run('Sample text')\n]);\n```\n\n### Image Classification\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst classifier = await pipeline('image-classification');\n\n// From URL\nconst result = await classifier.run('https://example.com/image.jpg');\n\n// From HTMLImageElement\nconst img = document.getElementById('myImage');\nconst result = await classifier.run(img);\n\n// Batch\nconst results = await classifier.run([img1, img2, img3]);\n```\n\n### Text Generation (Streaming)\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst generator = await pipeline('text-generation');\n\n// Simple generation\nconst result = await generator.run('Once upon a time', {\n  maxNewTokens: 50,\n  temperature: 0.8,\n});\nconsole.log(result.generatedText);\n\n// Streaming output\nfor await (const event of generator.stream('Hello, ')) {\n  process.stdout.write(event.token);\n  if (event.done) break;\n}\n```\n\n### Zero-shot Classification\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst classifier = await pipeline('zero-shot-classification');\n\nconst result = await classifier.classify(\n  'I love playing soccer on weekends',\n  ['sports', 'politics', 'technology', 'entertainment']\n);\n\nconsole.log(result.labels[0], result.scores[0]);\n// 'sports', 0.92\n```\n\n### Question Answering\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst qa = await pipeline('question-answering');\n\nconst result = await qa.run({\n  question: 'What is the capital of France?',\n  context: 'Paris is the capital and largest city of France.'\n});\n\nconsole.log(result.answer); // 'Paris'\n```\n\n### Load from HuggingFace Hub\n\n```typescript\nimport { fromHub, fromTask } from 'edgeflowjs';\n\n// Load by model ID (auto-downloads model, tokenizer, config)\nconst bundle = await fromHub('Xenova/distilbert-base-uncased-finetuned-sst-2-english');\nconsole.log(bundle.tokenizer); // Tokenizer instance\nconsole.log(bundle.config);    // Model config\n\n// Load by task name (uses recommended model)\nconst sentimentBundle = await fromTask('sentiment-analysis');\n```\n\n### Web Workers (Background Inference)\n\n```typescript\nimport { runInWorker, WorkerPool, isWorkerSupported } from 'edgeflowjs';\n\n// Simple: run inference in background thread\nif (isWorkerSupported()) {\n  const outputs = await runInWorker(modelUrl, inputs);\n}\n\n// Advanced: use worker pool for parallel processing\nconst pool = new WorkerPool({ numWorkers: 4 });\nawait pool.init();\n\nconst modelId = await pool.loadModel(modelUrl);\nconst results = await pool.runBatch(modelId, batchInputs);\n\npool.terminate();\n```\n\n## 🎯 Supported Tasks\n\n| Task | Pipeline | Status |\n|------|----------|--------|\n| Text Generation | `text-generation` | ✅ Production (TinyLlama, streaming, KV cache) |\n| Image Segmentation | `image-segmentation` | ✅ Production (SlimSAM, interactive prompts) |\n| Text Classification | `text-classification` | ⚠️ Experimental (heuristic, provide own model) |\n| Sentiment Analysis | `sentiment-analysis` | ⚠️ Experimental (heuristic, provide own model) |\n| Feature Extraction | `feature-extraction` | ⚠️ Experimental (mock embeddings, provide own model) |\n| Image Classification | `image-classification` | ⚠️ Experimental (heuristic, provide own model) |\n| Object Detection | `object-detection` | ⚠️ Experimental (real NMS/IoU, needs own model) |\n| Speech Recognition | `automatic-speech-recognition` | ⚠️ Experimental (preprocessing only, needs model) |\n| Zero-shot Classification | `zero-shot-classification` | ⚠️ Experimental (random scoring, needs NLI model) |\n| Question Answering | `question-answering` | ⚠️ Experimental (word overlap heuristic, needs model) |\n\n> **Note:** Experimental pipelines work for demos and testing the API surface. For production accuracy, provide a real ONNX model via `options.model` or use the **transformers.js adapter backend** to leverage HuggingFace's model ecosystem.\n\n## ⚡ Key Differentiators\n\nedgeFlow.js is not a replacement for transformers.js — it is a **production orchestration layer** that can wrap any inference engine (including transformers.js) and add the features real apps need.\n\n### What edgeFlow.js adds on top of inference engines\n\n| Feature | Inference engines alone | With edgeFlow.js |\n|---------|------------------------|------------------|\n| Task Scheduling | None — run and hope | Priority queue with concurrency limits |\n| Task Cancellation | Not possible | Cancel pending/queued tasks |\n| Batch Processing | Manual | Built-in batching with configurable size |\n| Memory Management | Manual cleanup | Automatic scopes, leak detection, GC hints |\n| Model Preloading | Manual | Background preloading with priority queue |\n| Resume Download | Start over on failure | Chunked download with automatic resume |\n| Model Caching | Basic or none | IndexedDB cache with stats and eviction |\n| Pipeline Composition | Not available | Chain multiple models (ASR → translate → TTS) |\n| Device Adaptation | Manual model selection | Auto-select model variant by device capability |\n| Performance Monitoring | External tooling needed | Built-in dashboard and alerting |\n\n## 🔌 transformers.js Adapter (Recommended)\n\nUse edgeFlow.js as an orchestration layer on top of [transformers.js](https://huggingface.co/docs/transformers.js) to get access to 1000+ HuggingFace models with scheduling, caching, and memory management:\n\n```typescript\nimport { pipeline as tfPipeline } from '@xenova/transformers';\nimport { useTransformersBackend, pipeline } from 'edgeflowjs';\n\n// Register transformers.js as the inference backend\nuseTransformersBackend({\n  pipelineFactory: tfPipeline,\n  device: 'webgpu',    // GPU acceleration\n  dtype: 'fp16',       // Half precision\n});\n\n// Use edgeFlow.js API — scheduling, caching, memory management included\nconst classifier = await pipeline('text-classification', {\n  model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n});\n\nconst result = await classifier.run('I love this product!');\n```\n\n> **Why?** transformers.js is excellent at loading and running single models. edgeFlow.js adds the production features you need when running multiple models, managing memory on constrained devices, caching for offline use, and scheduling concurrent inference.\n\n## 🔧 Configuration\n\n### Runtime Selection\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// Automatic (recommended)\nconst model = await pipeline('text-classification');\n\n// Specify runtime\nconst model = await pipeline('text-classification', {\n  runtime: 'webgpu' // or 'webnn', 'wasm', 'auto'\n});\n```\n\n### Memory Management\n\n```typescript\nimport { pipeline, getMemoryStats, gc } from 'edgeflowjs';\n\nconst model = await pipeline('text-classification');\n\n// Use the model\nawait model.run('text');\n\n// Check memory usage\nconsole.log(getMemoryStats());\n// { allocated: 50MB, used: 45MB, peak: 52MB, tensorCount: 12 }\n\n// Explicit cleanup\nmodel.dispose();\n\n// Force garbage collection\ngc();\n```\n\n### Scheduler Configuration\n\n```typescript\nimport { configureScheduler } from 'edgeflowjs';\n\nconfigureScheduler({\n  maxConcurrentTasks: 4,\n  maxConcurrentPerModel: 1,\n  defaultTimeout: 30000,\n  enableBatching: true,\n  maxBatchSize: 32,\n});\n```\n\n### Caching\n\n```typescript\nimport { pipeline, Cache } from 'edgeflowjs';\n\n// Create a cache\nconst cache = new Cache({\n  strategy: 'lru',\n  maxSize: 100 * 1024 * 1024, // 100MB\n  persistent: true, // Use IndexedDB\n});\n\nconst model = await pipeline('text-classification', {\n  cache: true\n});\n```\n\n## 🛠️ Advanced Usage\n\n### Custom Model Loading\n\n```typescript\nimport { loadModel, runInference } from 'edgeflowjs';\n\n// Load from URL with caching, sharding, and resume support\nconst model = await loadModel('https://example.com/model.bin', {\n  runtime: 'webgpu',\n  quantization: 'int8',\n  cache: true,           // Enable IndexedDB caching (default: true)\n  resumable: true,       // Enable resume download (default: true)\n  chunkSize: 5 * 1024 * 1024, // 5MB chunks for large models\n  onProgress: (progress) => console.log(`Loading: ${progress * 100}%`)\n});\n\n// Run inference\nconst outputs = await runInference(model, inputs);\n\n// Cleanup\nmodel.dispose();\n```\n\n### Preloading Models\n\n```typescript\nimport { preloadModel, preloadModels, getPreloadStatus } from 'edgeflowjs';\n\n// Preload a single model in background (with priority)\npreloadModel('https://example.com/model1.onnx', { priority: 10 });\n\n// Preload multiple models\npreloadModels([\n  { url: 'https://example.com/model1.onnx', priority: 10 },\n  { url: 'https://example.com/model2.onnx', priority: 5 },\n]);\n\n// Check preload status\nconst status = getPreloadStatus('https://example.com/model1.onnx');\n// 'pending' | 'loading' | 'complete' | 'error' | 'not_found'\n```\n\n### Model Caching\n\n```typescript\nimport { \n  isModelCached, \n  getCachedModel, \n  deleteCachedModel, \n  clearModelCache,\n  getModelCacheStats \n} from 'edgeflowjs';\n\n// Check if model is cached\nif (await isModelCached('https://example.com/model.onnx')) {\n  console.log('Model is cached!');\n}\n\n// Get cached model data directly\nconst modelData = await getCachedModel('https://example.com/model.onnx');\n\n// Delete a specific cached model\nawait deleteCachedModel('https://example.com/model.onnx');\n\n// Clear all cached models\nawait clearModelCache();\n\n// Get cache statistics\nconst stats = await getModelCacheStats();\nconsole.log(`${stats.models} models cached, ${stats.totalSize} bytes total`);\n```\n\n### Resume Downloads\n\nLarge model downloads automatically support resuming from where they left off:\n\n```typescript\nimport { loadModelData } from 'edgeflowjs';\n\n// Download with progress and resume support\nconst modelData = await loadModelData('https://example.com/large-model.onnx', {\n  resumable: true,\n  chunkSize: 10 * 1024 * 1024, // 10MB chunks\n  parallelConnections: 4,      // Download 4 chunks in parallel\n  onProgress: (progress) => {\n    console.log(`${progress.percent.toFixed(1)}% downloaded`);\n    console.log(`Speed: ${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`);\n    console.log(`ETA: ${(progress.eta / 1000).toFixed(0)}s`);\n    console.log(`Chunk ${progress.currentChunk}/${progress.totalChunks}`);\n  }\n});\n```\n\n### Model Quantization\n\n```typescript\nimport { quantize } from 'edgeflowjs/tools';\n\nconst quantized = await quantize(model, {\n  method: 'int8',\n  calibrationData: samples,\n});\n\nconsole.log(`Compression: ${quantized.compressionRatio}x`);\n// Compression: 3.8x\n```\n\n### Benchmarking\n\n```typescript\nimport { benchmark } from 'edgeflowjs/tools';\n\nconst result = await benchmark(\n  () => model.run('sample text'),\n  { warmupRuns: 5, runs: 100 }\n);\n\nconsole.log(result);\n// {\n//   avgTime: 12.5,\n//   minTime: 10.2,\n//   maxTime: 18.3,\n//   throughput: 80 // inferences/sec\n// }\n```\n\n### Memory Scope\n\n```typescript\nimport { withMemoryScope, tensor } from 'edgeflowjs';\n\nconst result = await withMemoryScope(async (scope) => {\n  // Tensors tracked in scope\n  const a = scope.track(tensor([1, 2, 3]));\n  const b = scope.track(tensor([4, 5, 6]));\n  \n  // Process...\n  const output = process(a, b);\n  \n  // Keep result, dispose others\n  return scope.keep(output);\n});\n// a and b automatically disposed\n```\n\n## 🔌 Tensor Operations\n\n```typescript\nimport { tensor, zeros, ones, matmul, softmax, relu } from 'edgeflowjs';\n\n// Create tensors\nconst a = tensor([[1, 2], [3, 4]]);\nconst b = zeros([2, 2]);\nconst c = ones([2, 2]);\n\n// Operations\nconst d = matmul(a, c);\nconst probs = softmax(d);\nconst activated = relu(d);\n\n// Cleanup\na.dispose();\nb.dispose();\nc.dispose();\n```\n\n## 🌐 Browser Support\n\n| Browser | WebGPU | WebNN | WASM |\n|---------|--------|-------|------|\n| Chrome 113+ | ✅ | ✅ | ✅ |\n| Edge 113+ | ✅ | ✅ | ✅ |\n| Firefox 118+ | ⚠️ Flag | ❌ | ✅ |\n| Safari 17+ | ⚠️ Preview | ❌ | ✅ |\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/image?repos=s-zx/edgeFlow.js&type=date&legend=top-left)](https://www.star-history.com/?repos=s-zx%2FedgeFlow.js&type=date&legend=top-left)\n\n## 📖 API Reference\n\n### Core\n\n- `pipeline(task, options?)` - Create a pipeline for a task\n- `loadModel(url, options?)` - Load a model from URL\n- `runInference(model, inputs)` - Run model inference\n- `getScheduler()` - Get the global scheduler\n- `getMemoryManager()` - Get the memory manager\n- `runInWorker(url, inputs)` - Run inference in a Web Worker\n- `WorkerPool` - Manage multiple workers for parallel inference\n\n### Pipelines\n\n- `TextClassificationPipeline` - Text/sentiment classification\n- `SentimentAnalysisPipeline` - Sentiment analysis\n- `FeatureExtractionPipeline` - Text embeddings\n- `ImageClassificationPipeline` - Image classification\n- `TextGenerationPipeline` - Text generation with streaming\n- `ObjectDetectionPipeline` - Object detection with bounding boxes\n- `AutomaticSpeechRecognitionPipeline` - Speech to text\n- `ZeroShotClassificationPipeline` - Classify without training\n- `QuestionAnsweringPipeline` - Extractive QA\n\n### HuggingFace Hub\n\n- `fromHub(modelId, options?)` - Load model bundle from HuggingFace\n- `fromTask(task, options?)` - Load recommended model for task\n- `downloadTokenizer(modelId)` - Download tokenizer only\n- `downloadConfig(modelId)` - Download config only\n- `POPULAR_MODELS` - Registry of popular models by task\n\n### Utilities\n\n- `Tokenizer` - BPE/WordPiece tokenization with HuggingFace support\n- `ImagePreprocessor` - Image preprocessing with HuggingFace config support\n- `AudioPreprocessor` - Audio preprocessing for Whisper/wav2vec\n- `Cache` - LRU caching utilities\n\n### Tools\n\n- `quantize(model, options)` - Quantize a model\n- `prune(model, options)` - Prune model weights\n- `benchmark(fn, options)` - Benchmark inference\n- `analyzeModel(model)` - Analyze model structure\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## 📄 License\n\nMIT © edgeFlow.js Contributors\n\n---\n\n<div align=\"center\">\n\n**[Get Started](https://edgeflow.js.org/getting-started) · [API Docs](https://edgeflow.js.org/api) · [Examples](examples/)**\n\nMade with ❤️ for the edge AI community\n\n</div>\n"
  },
  {
    "path": "README_CN.md",
    "content": "# edgeFlow.js\n\n<div align=\"center\">\n\n**浏览器端机器学习推理框架，内置任务调度和智能缓存**\n\n[![npm version](https://img.shields.io/npm/v/edgeflowjs.svg)](https://www.npmjs.com/package/edgeflowjs)\n[![install size](https://packagephobia.com/badge?p=edgeflowjs)](https://packagephobia.com/result?p=edgeflowjs)\n[![license](https://img.shields.io/npm/l/edgeflowjs)](LICENSE)\n\n[文档](https://edgeflow.js.org) · [示例](examples/) · [API 参考](https://edgeflow.js.org/api) · [English](README.md) | [中文](README_CN.md)\n\n</div>\n\n---\n\n## ✨ 特性\n\n- 📋 **任务调度器** - 优先级队列、并发控制、任务取消\n- 🔄 **批量处理** - 开箱即用的高效批量推理\n- 💾 **内存管理** - 自动内存追踪和作用域清理\n- 📥 **智能模型加载** - 支持预加载、分片下载、断点续传\n- 💿 **离线缓存** - 基于 IndexedDB 的模型缓存，支持离线使用\n- ⚡ **多后端支持** - WebGPU、WebNN、WASM 自动降级\n- 🤗 **HuggingFace Hub** - 一行代码从 HuggingFace 下载模型\n- 🔤 **真实分词器** - BPE 和 WordPiece 分词器，直接加载 tokenizer.json\n- 👷 **Web Worker 支持** - 在后台线程运行推理\n- 📦 **开箱即用** - 内置 ONNX Runtime，零配置直接使用\n- 🎯 **TypeScript 优先** - 完整的类型支持和直观的 API\n\n## 📦 安装\n\n```bash\nnpm install edgeflowjs\n```\n\n```bash\nyarn add edgeflowjs\n```\n\n```bash\npnpm add edgeflowjs\n```\n\n> **注意**: ONNX Runtime 已作为依赖包含，无需额外配置。\n\n## 🚀 快速开始\n\n### 体验 Demo\n\n在本地运行交互式 Demo 测试所有功能：\n\n```bash\n# 克隆并安装\ngit clone https://github.com/user/edgeflow.js.git\ncd edgeflow.js\nnpm install\n\n# 构建并启动 Demo 服务器\nnpm run demo\n```\n\n在浏览器中打开 **http://localhost:3000**：\n\n1. **加载模型** - 输入 Hugging Face ONNX 模型 URL 并点击 \"Load Model\"\n   ```\n   https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx\n   ```\n\n2. **测试功能**：\n   - 🧮 **张量运算** - 测试张量创建、数学运算、softmax、relu\n   - 📝 **文本分类** - 对文本进行情感分析\n   - 🔍 **特征提取** - 从文本中提取嵌入向量\n   - ⚡ **任务调度** - 测试优先级调度\n   - 📋 **任务调度** - 测试基于优先级的任务调度\n   - 💾 **内存管理** - 测试内存分配和清理\n\n### 基础用法\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// 创建情感分析流水线\nconst sentiment = await pipeline('sentiment-analysis');\n\n// 运行推理\nconst result = await sentiment.run('I love this product!');\nconsole.log(result);\n// { label: 'positive', score: 0.98, processingTime: 12.5 }\n```\n\n### 批量处理\n\n```typescript\n// 原生批处理支持\nconst results = await sentiment.run([\n  'This is amazing!',\n  'This is terrible.',\n  'It\\'s okay I guess.'\n]);\n\nconsole.log(results);\n// [\n//   { label: 'positive', score: 0.95 },\n//   { label: 'negative', score: 0.92 },\n//   { label: 'neutral', score: 0.68 }\n// ]\n```\n\n### 多流水线\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// 创建多个流水线\nconst classifier = await pipeline('text-classification');\nconst extractor = await pipeline('feature-extraction');\n\n// 使用 Promise.all 并行运行\nconst [classification, features] = await Promise.all([\n  classifier.run('Sample text'),\n  extractor.run('Sample text')\n]);\n```\n\n### 图像分类\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst classifier = await pipeline('image-classification');\n\n// 从 URL 加载\nconst result = await classifier.run('https://example.com/image.jpg');\n\n// 从 HTMLImageElement 加载\nconst img = document.getElementById('myImage');\nconst result = await classifier.run(img);\n\n// 批量处理\nconst results = await classifier.run([img1, img2, img3]);\n```\n\n### 文本生成（流式输出）\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst generator = await pipeline('text-generation');\n\n// 简单生成\nconst result = await generator.run('从前有座山', {\n  maxNewTokens: 50,\n  temperature: 0.8,\n});\nconsole.log(result.generatedText);\n\n// 流式输出\nfor await (const event of generator.stream('你好，')) {\n  process.stdout.write(event.token);\n  if (event.done) break;\n}\n```\n\n### 零样本分类\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst classifier = await pipeline('zero-shot-classification');\n\nconst result = await classifier.classify(\n  '周末我喜欢踢足球',\n  ['体育', '政治', '科技', '娱乐']\n);\n\nconsole.log(result.labels[0], result.scores[0]);\n// '体育', 0.92\n```\n\n### 问答系统\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst qa = await pipeline('question-answering');\n\nconst result = await qa.run({\n  question: '法国的首都是什么？',\n  context: '巴黎是法国的首都和最大城市。'\n});\n\nconsole.log(result.answer); // '巴黎'\n```\n\n### 从 HuggingFace Hub 加载\n\n```typescript\nimport { fromHub, fromTask } from 'edgeflowjs';\n\n// 通过模型 ID 加载（自动下载模型、分词器、配置）\nconst bundle = await fromHub('Xenova/distilbert-base-uncased-finetuned-sst-2-english');\nconsole.log(bundle.tokenizer); // Tokenizer 实例\nconsole.log(bundle.config);    // 模型配置\n\n// 通过任务名称加载（使用推荐模型）\nconst sentimentBundle = await fromTask('sentiment-analysis');\n```\n\n### Web Workers（后台推理）\n\n```typescript\nimport { runInWorker, WorkerPool, isWorkerSupported } from 'edgeflowjs';\n\n// 简单：在后台线程运行推理\nif (isWorkerSupported()) {\n  const outputs = await runInWorker(modelUrl, inputs);\n}\n\n// 高级：使用 Worker 池进行并行处理\nconst pool = new WorkerPool({ numWorkers: 4 });\nawait pool.init();\n\nconst modelId = await pool.loadModel(modelUrl);\nconst results = await pool.runBatch(modelId, batchInputs);\n\npool.terminate();\n```\n\n## 🎯 支持的任务\n\n| 任务 | 流水线 | 状态 |\n|------|--------|------|\n| 文本分类 | `text-classification` | ✅ |\n| 情感分析 | `sentiment-analysis` | ✅ |\n| 特征提取 | `feature-extraction` | ✅ |\n| 图像分类 | `image-classification` | ✅ |\n| 文本生成 | `text-generation` | ✅ |\n| 目标检测 | `object-detection` | ✅ |\n| 语音识别 | `automatic-speech-recognition` | ✅ |\n| 零样本分类 | `zero-shot-classification` | ✅ |\n| 问答系统 | `question-answering` | ✅ |\n\n## ⚡ 核心差异\n\n### 与 transformers.js 对比\n\n| 特性 | transformers.js | edgeFlow.js |\n|------|-----------------|-------------|\n| 任务调度器 | ❌ 无 | ✅ 优先级队列 + 并发限制 |\n| 任务取消 | ❌ 无 | ✅ 支持取消排队任务 |\n| 批量处理 | ⚠️ 手动 | ✅ 内置批处理 |\n| 内存作用域 | ❌ 无 | ✅ 作用域自动清理 |\n| 模型预加载 | ❌ 无 | ✅ 后台加载 |\n| 断点续传 | ❌ 无 | ✅ 分片 + 续传 |\n| 模型缓存 | ⚠️ 基础 | ✅ IndexedDB + 统计 |\n| TypeScript | ✅ 完整 | ✅ 完整 |\n\n## 🔧 配置\n\n### 运行时选择\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// 自动选择（推荐）\nconst model = await pipeline('text-classification');\n\n// 指定运行时\nconst model = await pipeline('text-classification', {\n  runtime: 'webgpu' // 或 'webnn', 'wasm', 'auto'\n});\n```\n\n### 内存管理\n\n```typescript\nimport { pipeline, getMemoryStats, gc } from 'edgeflowjs';\n\nconst model = await pipeline('text-classification');\n\n// 使用模型\nawait model.run('text');\n\n// 检查内存使用\nconsole.log(getMemoryStats());\n// { allocated: 50MB, used: 45MB, peak: 52MB, tensorCount: 12 }\n\n// 显式清理\nmodel.dispose();\n\n// 强制垃圾回收\ngc();\n```\n\n### 调度器配置\n\n```typescript\nimport { configureScheduler } from 'edgeflowjs';\n\nconfigureScheduler({\n  maxConcurrentTasks: 4,\n  maxConcurrentPerModel: 1,\n  defaultTimeout: 30000,\n  enableBatching: true,\n  maxBatchSize: 32,\n});\n```\n\n### 缓存\n\n```typescript\nimport { pipeline, Cache } from 'edgeflowjs';\n\n// 创建缓存\nconst cache = new Cache({\n  strategy: 'lru',\n  maxSize: 100 * 1024 * 1024, // 100MB\n  persistent: true, // 使用 IndexedDB\n});\n\nconst model = await pipeline('text-classification', {\n  cache: true\n});\n```\n\n## 🛠️ 高级用法\n\n### 自定义模型加载\n\n```typescript\nimport { loadModel, runInference } from 'edgeflowjs';\n\n// 从 URL 加载，支持缓存、分片和断点续传\nconst model = await loadModel('https://example.com/model.bin', {\n  runtime: 'webgpu',\n  quantization: 'int8',\n  cache: true,           // 启用 IndexedDB 缓存（默认: true）\n  resumable: true,       // 启用断点续传（默认: true）\n  chunkSize: 5 * 1024 * 1024, // 大模型使用 5MB 分片\n  onProgress: (progress) => console.log(`加载中: ${progress * 100}%`)\n});\n\n// 运行推理\nconst outputs = await runInference(model, inputs);\n\n// 清理\nmodel.dispose();\n```\n\n### 模型预加载\n\n```typescript\nimport { preloadModel, preloadModels, getPreloadStatus } from 'edgeflowjs';\n\n// 后台预加载单个模型（支持优先级）\npreloadModel('https://example.com/model1.onnx', { priority: 10 });\n\n// 预加载多个模型\npreloadModels([\n  { url: 'https://example.com/model1.onnx', priority: 10 },\n  { url: 'https://example.com/model2.onnx', priority: 5 },\n]);\n\n// 检查预加载状态\nconst status = getPreloadStatus('https://example.com/model1.onnx');\n// 'pending' | 'loading' | 'complete' | 'error' | 'not_found'\n```\n\n### 模型缓存\n\n```typescript\nimport { \n  isModelCached, \n  getCachedModel, \n  deleteCachedModel, \n  clearModelCache,\n  getModelCacheStats \n} from 'edgeflowjs';\n\n// 检查模型是否已缓存\nif (await isModelCached('https://example.com/model.onnx')) {\n  console.log('模型已缓存！');\n}\n\n// 直接获取缓存的模型数据\nconst modelData = await getCachedModel('https://example.com/model.onnx');\n\n// 删除特定缓存的模型\nawait deleteCachedModel('https://example.com/model.onnx');\n\n// 清空所有缓存的模型\nawait clearModelCache();\n\n// 获取缓存统计\nconst stats = await getModelCacheStats();\nconsole.log(`${stats.models} 个模型已缓存，共 ${stats.totalSize} 字节`);\n```\n\n### 断点续传下载\n\n大模型下载自动支持从断点处继续：\n\n```typescript\nimport { loadModelData } from 'edgeflowjs';\n\n// 带进度和断点续传的下载\nconst modelData = await loadModelData('https://example.com/large-model.onnx', {\n  resumable: true,\n  chunkSize: 10 * 1024 * 1024, // 10MB 分片\n  parallelConnections: 4,      // 并行下载 4 个分片\n  onProgress: (progress) => {\n    console.log(`${progress.percent.toFixed(1)}% 已下载`);\n    console.log(`速度: ${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`);\n    console.log(`预计剩余: ${(progress.eta / 1000).toFixed(0)}秒`);\n    console.log(`分片 ${progress.currentChunk}/${progress.totalChunks}`);\n  }\n});\n```\n\n### 模型量化\n\n```typescript\nimport { quantize } from 'edgeflowjs/tools';\n\nconst quantized = await quantize(model, {\n  method: 'int8',\n  calibrationData: samples,\n});\n\nconsole.log(`压缩比: ${quantized.compressionRatio}x`);\n// 压缩比: 3.8x\n```\n\n### 性能测试\n\n```typescript\nimport { benchmark } from 'edgeflowjs/tools';\n\nconst result = await benchmark(\n  () => model.run('sample text'),\n  { warmupRuns: 5, runs: 100 }\n);\n\nconsole.log(result);\n// {\n//   avgTime: 12.5,\n//   minTime: 10.2,\n//   maxTime: 18.3,\n//   throughput: 80 // 推理次数/秒\n// }\n```\n\n### 内存作用域\n\n```typescript\nimport { withMemoryScope, tensor } from 'edgeflowjs';\n\nconst result = await withMemoryScope(async (scope) => {\n  // 在作用域中追踪张量\n  const a = scope.track(tensor([1, 2, 3]));\n  const b = scope.track(tensor([4, 5, 6]));\n  \n  // 处理...\n  const output = process(a, b);\n  \n  // 保留结果，释放其他\n  return scope.keep(output);\n});\n// a 和 b 自动释放\n```\n\n## 🔌 张量操作\n\n```typescript\nimport { tensor, zeros, ones, matmul, softmax, relu } from 'edgeflowjs';\n\n// 创建张量\nconst a = tensor([[1, 2], [3, 4]]);\nconst b = zeros([2, 2]);\nconst c = ones([2, 2]);\n\n// 运算\nconst d = matmul(a, c);\nconst probs = softmax(d);\nconst activated = relu(d);\n\n// 清理\na.dispose();\nb.dispose();\nc.dispose();\n```\n\n## 🌐 浏览器支持\n\n| 浏览器 | WebGPU | WebNN | WASM |\n|--------|--------|-------|------|\n| Chrome 113+ | ✅ | ✅ | ✅ |\n| Edge 113+ | ✅ | ✅ | ✅ |\n| Firefox 118+ | ⚠️ 需开启 | ❌ | ✅ |\n| Safari 17+ | ⚠️ 预览版 | ❌ | ✅ |\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/image?repos=s-zx/edgeFlow.js&type=date&legend=top-left)](https://www.star-history.com/?repos=s-zx%2FedgeFlow.js&type=date&legend=top-left)\n\n## 📖 API 参考\n\n### 核心\n\n- `pipeline(task, options?)` - 为任务创建流水线\n- `loadModel(url, options?)` - 从 URL 加载模型\n- `runInference(model, inputs)` - 运行模型推理\n- `getScheduler()` - 获取全局调度器\n- `getMemoryManager()` - 获取内存管理器\n- `runInWorker(url, inputs)` - 在 Web Worker 中运行推理\n- `WorkerPool` - 管理多个 Worker 进行并行推理\n\n### 流水线\n\n- `TextClassificationPipeline` - 文本/情感分类\n- `SentimentAnalysisPipeline` - 情感分析\n- `FeatureExtractionPipeline` - 文本嵌入\n- `ImageClassificationPipeline` - 图像分类\n- `TextGenerationPipeline` - 文本生成（支持流式输出）\n- `ObjectDetectionPipeline` - 目标检测（带边界框）\n- `AutomaticSpeechRecognitionPipeline` - 语音转文字\n- `ZeroShotClassificationPipeline` - 零样本分类\n- `QuestionAnsweringPipeline` - 抽取式问答\n\n### HuggingFace Hub\n\n- `fromHub(modelId, options?)` - 从 HuggingFace 加载模型包\n- `fromTask(task, options?)` - 按任务加载推荐模型\n- `downloadTokenizer(modelId)` - 仅下载分词器\n- `downloadConfig(modelId)` - 仅下载配置\n- `POPULAR_MODELS` - 按任务分类的热门模型注册表\n\n### 工具类\n\n- `Tokenizer` - BPE/WordPiece 分词器，支持 HuggingFace 格式\n- `ImagePreprocessor` - 图像预处理器，支持 HuggingFace 配置\n- `AudioPreprocessor` - 音频预处理器，支持 Whisper/wav2vec\n- `Cache` - LRU 缓存工具\n\n### 工具\n\n- `quantize(model, options)` - 模型量化\n- `prune(model, options)` - 模型剪枝\n- `benchmark(fn, options)` - 性能基准测试\n- `analyzeModel(model)` - 分析模型结构\n\n## 🤝 贡献\n\n欢迎贡献！请查看我们的 [贡献指南](CONTRIBUTING.md) 了解详情。\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/amazing-feature`)\n3. 提交更改 (`git commit -m 'Add amazing feature'`)\n4. 推送到分支 (`git push origin feature/amazing-feature`)\n5. 发起 Pull Request\n\n## 📄 许可证\n\nMIT © edgeFlow.js Contributors\n\n---\n\n<div align=\"center\">\n\n**[快速开始](https://edgeflow.js.org/getting-started) · [API 文档](https://edgeflow.js.org/api) · [示例](examples/)**\n\n用 ❤️ 为边缘 AI 社区打造\n\n</div>\n"
  },
  {
    "path": "benchmarks/README.md",
    "content": "# edgeFlow.js Benchmarks\n\nThis directory contains performance benchmarks for edgeFlow.js.\n\n## Running Benchmarks\n\n```bash\nnpm install\nnpm run build\nnpm run test -- --run tests/unit/\n```\n\n> **Note:** A dedicated `npm run benchmark` script with browser-based benchmarks is planned. The unit tests include basic tensor and scheduler performance validation.\n\n## Benchmark Types\n\n### 1. Tensor Operations\n\n- Tensor creation and disposal\n- Shape transformation (reshape, transpose)\n- Math operations (add, matmul, softmax)\n\n### 2. Scheduler Throughput\n\n- Priority queue ordering under load\n- Concurrent task execution\n- Task cancellation overhead\n\n### 3. Model Loading\n\n- Cached vs uncached loads (IndexedDB)\n- Chunked download with resume\n- Preloading pipeline\n\n### 4. Inference Latency\n\n- Text generation (TinyLlama) end-to-end\n- Image segmentation (SlimSAM) encode + decode\n\n## How edgeFlow.js Adds Value\n\nedgeFlow.js is not a replacement for inference engines like ONNX Runtime or transformers.js. It is an **orchestration layer** that adds production features on top of them:\n\n| Scenario | Without edgeFlow.js | With edgeFlow.js |\n|----------|---------------------|------------------|\n| 5 concurrent model calls | Uncontrolled, may OOM | Scheduled with concurrency limits |\n| Repeated inference on same input | Recomputed every time | Cached results (LRU/TTL) |\n| Large model download interrupted | Start from scratch | Resume from last chunk |\n| Memory leak from undisposed tensors | Silent leak | Detected and warned |\n\n> All benchmark claims will be backed by reproducible scripts before the 1.0 release.\n\n## Custom Benchmarks\n\n```typescript\nimport { runBenchmark, benchmarkSuite } from 'edgeflowjs/tools';\n\nconst result = await runBenchmark(\n  async () => {\n    await model.run(input);\n  },\n  {\n    warmupRuns: 5,\n    runs: 20,\n    verbose: true,\n  }\n);\n\nconsole.log(`Average: ${result.avgTime.toFixed(2)}ms`);\nconsole.log(`Throughput: ${result.throughput.toFixed(2)} ops/sec`);\n\nconst results = await benchmarkSuite({\n  'small-model': async () => smallModel.run(input),\n  'large-model': async () => largeModel.run(input),\n});\n```\n"
  },
  {
    "path": "demo/demo.js",
    "content": "/**\n * edgeFlow.js Interactive Demo\n * \n * Organized into modules:\n * 1. State & Config\n * 2. Utilities\n * 3. UI Helpers\n * 4. Core Features\n * 5. SAM Interactive Segmentation (Real Model)\n * 6. AI Chat (Real Model)\n * 7. Demo Class (Public API)\n * 8. Initialization\n */\n\nimport * as edgeFlow from '/dist/edgeflow.browser.js';\n\n// Expose edgeFlow globally for debugging\nwindow.edgeFlow = edgeFlow;\n\n/* ==========================================================================\n   1. State & Config\n   ========================================================================== */\n\nconst state = {\n  model: null,\n  testTensors: [],\n  monitor: null,\n  // SAM state\n  samPipeline: null,\n  samModelLoaded: false,\n  samImage: null,\n  samPoints: [],\n  samCanvas: null,\n  samMaskCanvas: null,\n  samCtx: null,\n  samMaskCtx: null,\n  // Chat state\n  chatPipeline: null,\n  chatModelLoaded: false,\n  chatHistory: [],\n  chatGenerating: false,\n};\n\nconst config = {\n  defaultSeqLen: 128,\n  monitorSampleInterval: 500,\n  monitorHistorySize: 30,\n};\n\n/* ==========================================================================\n   2. Utilities\n   ========================================================================== */\n\nconst utils = {\n  /**\n   * Format bytes to human readable string\n   */\n  formatBytes(bytes) {\n    if (!bytes) return '0 B';\n    const k = 1024;\n    const sizes = ['B', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n  },\n\n  /**\n   * Sleep for given milliseconds\n   */\n  sleep(ms) {\n    return new Promise(resolve => setTimeout(resolve, ms));\n  },\n\n  /**\n   * Generate placeholder model inputs based on model metadata\n   */\n  createModelInputs(model, seqLen = config.defaultSeqLen) {\n    return model.metadata.inputs.map(spec => {\n      const data = new Array(seqLen).fill(0);\n      \n      if (spec.name.includes('input')) {\n        data[0] = 101;  // [CLS]\n        data[1] = 2054; // sample token\n        data[2] = 102;  // [SEP]\n      } else if (spec.name.includes('mask')) {\n        data[0] = 1;\n        data[1] = 1;\n        data[2] = 1;\n      }\n      \n      return edgeFlow.tensor(data, [1, seqLen], 'int64');\n    });\n  },\n\n  /**\n   * Simple tokenization and inference\n   */\n  async inferText(text) {\n    if (!state.model) throw new Error('Model not loaded');\n    \n    const tokens = text.toLowerCase().split(/\\s+/);\n    const maxLen = config.defaultSeqLen;\n    const numTokens = Math.min(tokens.length + 2, maxLen);\n    \n    const inputs = state.model.metadata.inputs.map(spec => {\n      const data = new Array(maxLen).fill(0);\n      \n      if (spec.name.includes('input')) {\n        data[0] = 101; // [CLS]\n        tokens.slice(0, maxLen - 2).forEach((t, i) => {\n          // Simple hash-based token ID (demo only)\n          data[i + 1] = Math.abs(t.split('').reduce((a, c) => a + c.charCodeAt(0), 0)) % 30000;\n        });\n        data[numTokens - 1] = 102; // [SEP]\n      } else if (spec.name.includes('mask')) {\n        for (let i = 0; i < numTokens; i++) data[i] = 1;\n      }\n      \n      return edgeFlow.tensor(data, [1, maxLen], 'int64');\n    });\n\n    const outputs = await edgeFlow.runInference(state.model, inputs);\n    const outputData = outputs[0].toArray();\n    \n    // Calculate sentiment score\n    const score = outputData.length >= 2\n      ? Math.exp(outputData[1]) / (Math.exp(outputData[0]) + Math.exp(outputData[1]))\n      : outputData[0] > 0.5 ? outputData[0] : 1 - outputData[0];\n\n    // Cleanup\n    inputs.forEach(t => t.dispose());\n    outputs.forEach(t => t.dispose());\n\n    return {\n      label: score > 0.5 ? 'positive' : 'negative',\n      score,\n    };\n  },\n};\n\n/* ==========================================================================\n   3. UI Helpers\n   ========================================================================== */\n\nconst ui = {\n  /**\n   * Get element by ID\n   */\n  $(id) {\n    return document.getElementById(id);\n  },\n\n  /**\n   * Set output content\n   */\n  setOutput(id, content, type = '') {\n    const el = this.$(id);\n    if (!el) return;\n    \n    const className = type ? `class=\"${type}\"` : '';\n    el.innerHTML = `<pre><span ${className}>${content}</span></pre>`;\n  },\n\n  /**\n   * Show loading state\n   */\n  showLoading(id, message = 'Loading...') {\n    this.setOutput(id, `<span class=\"loader\"></span>${message}`);\n  },\n\n  /**\n   * Show success message\n   */\n  showSuccess(id, message) {\n    this.setOutput(id, `✓ ${message}`, 'success');\n  },\n\n  /**\n   * Show error message\n   */\n  showError(id, error) {\n    const message = error instanceof Error ? error.message : String(error);\n    this.setOutput(id, `Error: ${message}`, 'error');\n  },\n\n  /**\n   * Render status list\n   */\n  renderStatusList(id, items) {\n    const el = this.$(id);\n    if (!el) return;\n    \n    el.innerHTML = items.map(({ label, value, status }) => `\n      <div class=\"status-item\">\n        <span>${label}</span>\n        <span class=\"${status ? 'status-badge status-' + status : ''}\">${value}</span>\n      </div>\n    `).join('');\n  },\n\n  /**\n   * Render metrics\n   */\n  renderMetrics(id, metrics) {\n    const el = this.$(id);\n    if (!el) return;\n    \n    el.innerHTML = metrics.map(({ value, label }) => `\n      <div class=\"metric\">\n        <div class=\"metric-value\">${value}</div>\n        <div class=\"metric-label\">${label}</div>\n      </div>\n    `).join('');\n    \n    el.classList.remove('hidden');\n  },\n\n  /**\n   * Update runtime status\n   */\n  async updateRuntimeStatus() {\n    try {\n      const runtimes = await edgeFlow.getAvailableRuntimes();\n      this.renderStatusList('runtime-status', [\n        { label: 'WebGPU', value: runtimes.get('webgpu') ? 'Ready' : 'N/A', status: runtimes.get('webgpu') ? 'success' : 'error' },\n        { label: 'WebNN', value: runtimes.get('webnn') ? 'Ready' : 'N/A', status: runtimes.get('webnn') ? 'success' : 'error' },\n        { label: 'WASM', value: runtimes.get('wasm') ? 'Ready' : 'N/A', status: runtimes.get('wasm') ? 'success' : 'error' },\n      ]);\n    } catch {\n      this.renderStatusList('runtime-status', [\n        { label: 'WebGPU', value: 'N/A', status: 'error' },\n        { label: 'WebNN', value: 'N/A', status: 'error' },\n        { label: 'WASM', value: 'N/A', status: 'error' },\n      ]);\n    }\n  },\n\n  /**\n   * Update memory status\n   */\n  updateMemoryStatus() {\n    try {\n      const stats = edgeFlow.getMemoryStats();\n      this.renderStatusList('memory-status', [\n        { label: 'Allocated', value: utils.formatBytes(stats.allocated || 0) },\n        { label: 'Peak', value: utils.formatBytes(stats.peak || 0) },\n        { label: 'Tensors', value: String(stats.tensorCount || 0) },\n      ]);\n    } catch {\n      this.renderStatusList('memory-status', [\n        { label: 'Allocated', value: '0 B' },\n        { label: 'Peak', value: '0 B' },\n        { label: 'Tensors', value: '0' },\n      ]);\n    }\n  },\n\n  /**\n   * Update monitor metrics\n   */\n  updateMonitorMetrics(sample) {\n    this.renderMetrics('monitor-metrics', [\n      { value: sample.inference.count, label: 'Inferences' },\n      { value: sample.inference.avgTime.toFixed(1) + 'ms', label: 'Avg Time' },\n      { value: sample.inference.throughput.toFixed(1), label: 'Ops/sec' },\n      { value: utils.formatBytes(sample.memory.usedHeap), label: 'Memory' },\n      { value: sample.system.fps || '-', label: 'FPS' },\n    ]);\n  },\n\n  /**\n   * Initialize default outputs\n   */\n  initOutputs() {\n    const defaults = {\n      'model-output': ['Click \"Load Model\" to download an ONNX model', 'info'],\n      'tensor-output': ['Click \"Run Tests\" to test tensor operations...', ''],\n      'text-output': ['Load model first, then classify text...', ''],\n      'feature-output': ['Enter text and extract features...', ''],\n      'quant-output': ['Test in-browser quantization...', ''],\n      'debugger-output': ['Inspect tensor values and statistics...', ''],\n      'benchmark-output': ['Benchmark tensor operations...', ''],\n      'scheduler-output': ['Test task scheduling with priorities...', ''],\n      'memory-output': ['Test memory allocation and cleanup...', ''],\n      'concurrency-output': ['Test concurrent inference...', ''],\n    };\n\n    for (const [id, [msg, type]] of Object.entries(defaults)) {\n      this.setOutput(id, msg, type);\n    }\n\n    // Initialize monitor metrics\n    this.renderMetrics('monitor-metrics', [\n      { value: '0', label: 'Inferences' },\n      { value: '0ms', label: 'Avg Time' },\n      { value: '0', label: 'Ops/sec' },\n      { value: '0 B', label: 'Memory' },\n      { value: '-', label: 'FPS' },\n    ]);\n  },\n};\n\n/* ==========================================================================\n   4. Core Features\n   ========================================================================== */\n\nconst features = {\n  /**\n   * Load ONNX model\n   */\n  async loadModel() {\n    const url = ui.$('model-url')?.value;\n    if (!url) {\n      ui.setOutput('model-output', 'Enter a model URL', 'warn');\n      return;\n    }\n\n    ui.showLoading('model-output', 'Loading model...');\n\n    try {\n      const start = performance.now();\n      state.model = await edgeFlow.loadModel(url, { runtime: 'wasm' });\n      const time = ((performance.now() - start) / 1000).toFixed(2);\n\n      const info = [\n        `<span class=\"success\">✓ Model loaded in ${time}s</span>`,\n        `Name: ${state.model.metadata.name}`,\n        `Size: ${utils.formatBytes(state.model.metadata.sizeBytes)}`,\n        `Inputs: ${state.model.metadata.inputs.map(i => i.name).join(', ')}`,\n      ].join('\\n');\n\n      ui.$('model-output').innerHTML = `<pre>${info}</pre>`;\n      ui.updateMemoryStatus();\n    } catch (e) {\n      ui.showError('model-output', e);\n    }\n  },\n\n  /**\n   * Test model inference\n   */\n  async testModel() {\n    if (!state.model) {\n      ui.setOutput('model-output', 'Load model first', 'warn');\n      return;\n    }\n\n    ui.showLoading('model-output', 'Running inference...');\n\n    try {\n      const inputs = utils.createModelInputs(state.model);\n      const start = performance.now();\n      const outputs = await edgeFlow.runInference(state.model, inputs);\n      const time = (performance.now() - start).toFixed(2);\n      const data = outputs[0].toArray();\n\n      const info = [\n        `<span class=\"success\">✓ Inference: ${time}ms</span>`,\n        `Output: [${data.slice(0, 5).map(x => x.toFixed(4)).join(', ')}...]`,\n      ].join('\\n');\n\n      ui.$('model-output').innerHTML = `<pre>${info}</pre>`;\n\n      inputs.forEach(t => t.dispose());\n      outputs.forEach(t => t.dispose());\n    } catch (e) {\n      ui.showError('model-output', e);\n    }\n  },\n\n  /**\n   * Run tensor operation tests\n   */\n  testTensors() {\n    try {\n      const a = edgeFlow.tensor([[1, 2], [3, 4]]);\n      const b = edgeFlow.tensor([[5, 6], [7, 8]]);\n      const sum = edgeFlow.add(a, b);\n      const rand = edgeFlow.random([10]);\n      const probs = edgeFlow.softmax(edgeFlow.tensor([1, 2, 3, 4]));\n\n      const info = [\n        `<span class=\"success\">✓ All tensor tests passed</span>`,\n        `• Created 2x2 tensor`,\n        `• Addition: [${sum.toArray()}]`,\n        `• Random: [${rand.toArray().slice(0, 5).map(x => x.toFixed(2))}...]`,\n        `• Softmax: [${probs.toArray().map(x => x.toFixed(3))}]`,\n      ].join('\\n');\n\n      ui.$('tensor-output').innerHTML = `<pre>${info}</pre>`;\n\n      [a, b, sum, rand, probs].forEach(t => t.dispose());\n      ui.updateMemoryStatus();\n    } catch (e) {\n      ui.showError('tensor-output', e);\n    }\n  },\n\n  /**\n   * Classify single text\n   */\n  async classifyText() {\n    if (!state.model) {\n      ui.setOutput('text-output', 'Load model first', 'warn');\n      return;\n    }\n\n    const text = ui.$('text-input')?.value;\n    if (!text) return;\n\n    ui.showLoading('text-output', 'Classifying...');\n\n    try {\n      const result = await utils.inferText(text);\n      const emoji = result.label === 'positive' ? '😊' : '😞';\n      const pct = (result.score * 100).toFixed(1);\n      ui.$('text-output').innerHTML = `<pre><span class=\"success\">${emoji} ${result.label.toUpperCase()}</span> (${pct}%)</pre>`;\n    } catch (e) {\n      ui.showError('text-output', e);\n    }\n  },\n\n  /**\n   * Batch classification\n   */\n  async classifyBatch() {\n    if (!state.model) {\n      ui.setOutput('text-output', 'Load model first', 'warn');\n      return;\n    }\n\n    const texts = ['I love this!', 'This is terrible.', 'Amazing!', 'Worst ever.', 'Pretty good.'];\n    ui.showLoading('text-output', 'Processing batch...');\n\n    try {\n      const start = performance.now();\n      const results = await Promise.all(texts.map(t => utils.inferText(t)));\n      const time = (performance.now() - start).toFixed(0);\n\n      const lines = results.map((r, i) => {\n        const emoji = r.label === 'positive' ? '😊' : '😞';\n        return `${emoji} \"${texts[i]}\" → ${r.label}`;\n      });\n\n      lines.push('', `<span class=\"success\">Total: ${time}ms</span>`);\n      ui.$('text-output').innerHTML = `<pre>${lines.join('\\n')}</pre>`;\n    } catch (e) {\n      ui.showError('text-output', e);\n    }\n  },\n\n  /**\n   * Extract features\n   */\n  async extractFeatures() {\n    if (!state.model) {\n      ui.setOutput('feature-output', 'Load model first', 'warn');\n      return;\n    }\n\n    const text = ui.$('feature-input')?.value;\n    if (!text) return;\n\n    ui.showLoading('feature-output', 'Extracting...');\n\n    try {\n      const inputs = utils.createModelInputs(state.model);\n      const start = performance.now();\n      const outputs = await edgeFlow.runInference(state.model, inputs);\n      const time = (performance.now() - start).toFixed(2);\n      const embeddings = outputs[0].toArray();\n      const norm = Math.sqrt(embeddings.reduce((a, b) => a + b * b, 0));\n\n      const info = [\n        `<span class=\"success\">✓ Features extracted in ${time}ms</span>`,\n        `Dimension: ${embeddings.length}`,\n        `L2 Norm: ${norm.toFixed(4)}`,\n        `Sample: [${embeddings.slice(0, 5).map(x => x.toFixed(4)).join(', ')}...]`,\n      ].join('\\n');\n\n      ui.$('feature-output').innerHTML = `<pre>${info}</pre>`;\n\n      inputs.forEach(t => t.dispose());\n      outputs.forEach(t => t.dispose());\n    } catch (e) {\n      ui.showError('feature-output', e);\n    }\n  },\n\n  /**\n   * Quantization demo\n   */\n  quantize() {\n    try {\n      const weights = edgeFlow.tensor([0.5, -0.3, 0.8, -0.1, 0.9, -0.7, 0.2, -0.4], [2, 4], 'float32');\n      const { tensor: quantized, scale, zeroPoint } = edgeFlow.quantizeTensor(weights, 'int8');\n      const dequantized = edgeFlow.dequantizeTensor(quantized, scale, zeroPoint, 'int8');\n      \n      const original = weights.toArray();\n      const recovered = dequantized.toArray();\n      const maxError = Math.max(...original.map((v, i) => Math.abs(v - recovered[i])));\n\n      const info = [\n        `<span class=\"success\">✓ Int8 Quantization</span>`,\n        `Original:    [${original.map(v => v.toFixed(3)).join(', ')}]`,\n        `Quantized:   [${quantized.toArray().join(', ')}]`,\n        `Dequantized: [${recovered.map(v => v.toFixed(3)).join(', ')}]`,\n        `Scale: ${scale.toFixed(6)}, Max Error: ${maxError.toFixed(6)}`,\n      ].join('\\n');\n\n      ui.$('quant-output').innerHTML = `<pre>${info}</pre>`;\n\n      [weights, quantized, dequantized].forEach(t => t.dispose());\n    } catch (e) {\n      ui.showError('quant-output', e);\n    }\n  },\n\n  /**\n   * Pruning demo\n   */\n  prune() {\n    try {\n      const weights = edgeFlow.tensor([0.5, -0.1, 0.8, -0.05, 0.9, -0.02, 0.2, -0.4], [2, 4], 'float32');\n      const { tensor: pruned, sparsity } = edgeFlow.pruneTensor(weights, { ratio: 0.5 });\n\n      const info = [\n        `<span class=\"success\">✓ Magnitude Pruning (50%)</span>`,\n        `Original: [${weights.toArray().map(v => v.toFixed(2)).join(', ')}]`,\n        `Pruned:   [${pruned.toArray().map(v => v.toFixed(2)).join(', ')}]`,\n        `Sparsity: ${(sparsity * 100).toFixed(1)}%`,\n      ].join('\\n');\n\n      ui.$('quant-output').innerHTML = `<pre>${info}</pre>`;\n\n      [weights, pruned].forEach(t => t.dispose());\n    } catch (e) {\n      ui.showError('quant-output', e);\n    }\n  },\n\n  /**\n   * Debugger demo\n   */\n  debug() {\n    try {\n      const data = Array.from({ length: 100 }, () => Math.random() * 2 - 1);\n      const tensor = edgeFlow.tensor(data, [10, 10], 'float32');\n      const inspection = edgeFlow.inspectTensor(tensor, 'random_weights');\n      const histogram = edgeFlow.createAsciiHistogram(inspection.histogram, 25, 4);\n\n      const info = [\n        `<span class=\"success\">Tensor: ${inspection.name}</span>`,\n        `Shape: [${inspection.shape}], Size: ${inspection.size}`,\n        `<span class=\"info\">Statistics:</span>`,\n        `  Min: ${inspection.stats.min.toFixed(4)}`,\n        `  Max: ${inspection.stats.max.toFixed(4)}`,\n        `  Mean: ${inspection.stats.mean.toFixed(4)}`,\n        `  Std: ${inspection.stats.std.toFixed(4)}`,\n        '',\n        histogram,\n      ].join('\\n');\n\n      ui.$('debugger-output').innerHTML = `<pre>${info}</pre>`;\n\n      tensor.dispose();\n    } catch (e) {\n      ui.showError('debugger-output', e);\n    }\n  },\n\n  /**\n   * Benchmark demo\n   */\n  async benchmark() {\n    ui.showLoading('benchmark-output', 'Running benchmark...');\n\n    try {\n      const result = await edgeFlow.runBenchmark(async () => {\n        const t = edgeFlow.tensor(Array.from({ length: 1000 }, () => Math.random()), [1000], 'float32');\n        const sum = t.toArray().reduce((a, b) => a + b, 0);\n        t.dispose();\n        return sum;\n      }, { warmupRuns: 2, runs: 5, name: 'Tensor Sum (1000)' });\n\n      const info = [\n        `<span class=\"success\">Benchmark: ${result.name}</span>`,\n        `Avg: ${result.avgTime.toFixed(2)}ms`,\n        `Min: ${result.minTime.toFixed(2)}ms`,\n        `Max: ${result.maxTime.toFixed(2)}ms`,\n        `Throughput: ${result.throughput.toFixed(0)} ops/sec`,\n      ].join('\\n');\n\n      ui.$('benchmark-output').innerHTML = `<pre>${info}</pre>`;\n    } catch (e) {\n      ui.showError('benchmark-output', e);\n    }\n  },\n\n  /**\n   * Scheduler test\n   */\n  async testScheduler() {\n    ui.showLoading('scheduler-output', 'Testing scheduler...');\n\n    try {\n      const scheduler = edgeFlow.getScheduler();\n      const task1 = scheduler.schedule('model-a', async () => { await utils.sleep(100); return 'Task 1'; }, 'high');\n      const task2 = scheduler.schedule('model-b', async () => { await utils.sleep(50); return 'Task 2'; }, 'normal');\n      const task3 = scheduler.schedule('model-a', async () => { await utils.sleep(75); return 'Task 3'; }, 'low');\n\n      const [r1, r2, r3] = await Promise.all([task1.wait(), task2.wait(), task3.wait()]);\n\n      const info = [\n        `<span class=\"success\">✓ Scheduler Test Passed</span>`,\n        `• ${r1} (high priority)`,\n        `• ${r2} (normal priority)`,\n        `• ${r3} (low priority)`,\n      ].join('\\n');\n\n      ui.$('scheduler-output').innerHTML = `<pre>${info}</pre>`;\n    } catch (e) {\n      ui.showError('scheduler-output', e);\n    }\n  },\n\n  /**\n   * Memory allocation test\n   */\n  allocateMemory() {\n    try {\n      const before = edgeFlow.getMemoryStats();\n      \n      for (let i = 0; i < 10; i++) {\n        state.testTensors.push(edgeFlow.random([100, 100]));\n      }\n      \n      const after = edgeFlow.getMemoryStats();\n\n      const info = [\n        `<span class=\"success\">✓ Allocated 10 tensors (100x100)</span>`,\n        `Before: ${utils.formatBytes(before.allocated || 0)}, ${before.tensorCount || 0} tensors`,\n        `After: ${utils.formatBytes(after.allocated || 0)}, ${after.tensorCount || 0} tensors`,\n      ].join('\\n');\n\n      ui.$('memory-output').innerHTML = `<pre>${info}</pre>`;\n      ui.updateMemoryStatus();\n    } catch (e) {\n      ui.showError('memory-output', e);\n    }\n  },\n\n  /**\n   * Memory cleanup\n   */\n  cleanupMemory() {\n    state.testTensors.forEach(t => {\n      if (!t.isDisposed) t.dispose();\n    });\n    state.testTensors = [];\n    edgeFlow.gc();\n\n    ui.showSuccess('memory-output', 'Memory cleaned up');\n    ui.updateMemoryStatus();\n  },\n\n  /**\n   * Concurrency test\n   */\n  async testConcurrency() {\n    if (!state.model) {\n      ui.setOutput('concurrency-output', 'Load model first', 'warn');\n      ui.$('concurrency-metrics')?.classList.add('hidden');\n      return;\n    }\n\n    ui.showLoading('concurrency-output', 'Running concurrent tasks...');\n\n    try {\n      const texts = ['Great!', 'Terrible!', 'Amazing!', 'Awful!', 'Good!', 'Bad!', 'Nice!', 'Horrible!'];\n      const start = performance.now();\n      const results = await Promise.all(texts.map(t => utils.inferText(t)));\n      const total = performance.now() - start;\n\n      const lines = [\n        `<span class=\"success\">✓ Concurrent execution complete</span>`,\n        ...results.map((r, i) => `${r.label === 'positive' ? '😊' : '😞'} \"${texts[i]}\"`),\n      ];\n\n      ui.$('concurrency-output').innerHTML = `<pre>${lines.join('\\n')}</pre>`;\n\n      ui.renderMetrics('concurrency-metrics', [\n        { value: total.toFixed(0) + 'ms', label: 'Total' },\n        { value: String(texts.length), label: 'Tasks' },\n        { value: (total / texts.length).toFixed(0) + 'ms', label: 'Avg' },\n      ]);\n    } catch (e) {\n      ui.showError('concurrency-output', e);\n    }\n  },\n\n  /**\n   * Start performance monitor\n   */\n  startMonitor() {\n    if (!state.monitor) {\n      state.monitor = new edgeFlow.PerformanceMonitor({\n        sampleInterval: config.monitorSampleInterval,\n        historySize: config.monitorHistorySize,\n      });\n      state.monitor.onSample(sample => ui.updateMonitorMetrics(sample));\n    }\n    state.monitor.start();\n  },\n\n  /**\n   * Stop monitor\n   */\n  stopMonitor() {\n    if (state.monitor) {\n      state.monitor.stop();\n    }\n  },\n\n  /**\n   * Simulate inferences for monitor\n   */\n  simulateInferences() {\n    if (!state.monitor) {\n      this.startMonitor();\n    }\n    \n    for (let i = 0; i < 5; i++) {\n      setTimeout(() => {\n        state.monitor?.recordInference(30 + Math.random() * 70);\n      }, i * 100);\n    }\n  },\n\n  /**\n   * Open dashboard modal\n   */\n  openDashboard() {\n    if (!state.monitor) {\n      this.startMonitor();\n      this.simulateInferences();\n    }\n\n    const modal = ui.$('dashboard-modal');\n    const frame = ui.$('dashboard-frame');\n    \n    if (modal && frame) {\n      frame.srcdoc = edgeFlow.generateDashboardHTML(state.monitor);\n      modal.classList.add('active');\n      document.body.style.overflow = 'hidden';\n    }\n  },\n\n  /**\n   * Close dashboard modal\n   */\n  closeDashboard() {\n    const modal = ui.$('dashboard-modal');\n    if (modal) {\n      modal.classList.remove('active');\n      document.body.style.overflow = '';\n    }\n  },\n};\n\n/* ==========================================================================\n   5. SAM Interactive Segmentation (Real Model)\n   ========================================================================== */\n\nconst sam = {\n  /**\n   * Initialize SAM UI and start model loading\n   */\n  async init() {\n    const fileInput = ui.$('sam-file-input');\n    const container = ui.$('sam-container');\n    \n    if (fileInput) {\n      fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n    }\n    \n    // Drag and drop\n    if (container) {\n      container.addEventListener('dragover', (e) => {\n        e.preventDefault();\n        container.classList.add('dragover');\n      });\n      container.addEventListener('dragleave', () => {\n        container.classList.remove('dragover');\n      });\n      container.addEventListener('drop', (e) => {\n        e.preventDefault();\n        container.classList.remove('dragover');\n        const file = e.dataTransfer?.files[0];\n        if (file && file.type.startsWith('image/')) {\n          this.loadImage(file);\n        }\n      });\n    }\n\n    // Start loading SAM models automatically\n    await this.loadModels();\n  },\n\n  /**\n   * Load SAM models with progress display\n   */\n  async loadModels() {\n    const loader = ui.$('sam-loader');\n    const loaderText = ui.$('sam-loader-text');\n    const loaderDetail = ui.$('sam-loader-detail');\n    const progress = ui.$('sam-progress');\n    const samContainer = ui.$('sam-container');\n    \n    try {\n      // Create pipeline\n      state.samPipeline = edgeFlow.createImageSegmentationPipeline();\n      \n      // Load models with progress\n      await state.samPipeline.loadModels((progressInfo) => {\n        const { model, progress: pct, loaded, total } = progressInfo;\n        \n        if (loaderText) {\n          loaderText.textContent = `Loading ${model}... (${utils.formatBytes(loaded)} / ${utils.formatBytes(total)})`;\n        }\n        if (loaderDetail) {\n          loaderDetail.textContent = `${pct}%`;\n        }\n        if (progress) {\n          progress.style.width = `${pct}%`;\n        }\n      });\n      \n      state.samModelLoaded = true;\n      \n      // Hide loader, show main UI\n      if (loader) loader.classList.add('hidden');\n      if (samContainer) samContainer.classList.remove('hidden');\n      \n      // Enable buttons\n      ui.$('sam-sample-btn')?.removeAttribute('disabled');\n      ui.$('sam-clear-btn')?.removeAttribute('disabled');\n      ui.$('sam-download-btn')?.removeAttribute('disabled');\n      \n      ui.setOutput('sam-output', '✓ SAM model loaded! Click to upload an image or use \"Sample Image\".', 'success');\n      \n    } catch (error) {\n      console.error('SAM model loading failed:', error);\n      \n      if (loaderText) {\n        loaderText.textContent = `Failed to load model: ${error.message}`;\n        loaderText.style.color = 'var(--error)';\n      }\n      if (loaderDetail) {\n        loaderDetail.textContent = 'Check console for details';\n      }\n      \n      ui.showError('sam-output', error);\n    }\n  },\n\n  /**\n   * Handle file selection\n   */\n  handleFileSelect(e) {\n    const file = e.target?.files?.[0];\n    if (file) {\n      this.loadImage(file);\n    }\n  },\n\n  /**\n   * Load image from file or URL\n   */\n  async loadImage(source) {\n    if (!state.samModelLoaded) {\n      ui.setOutput('sam-output', 'Model not loaded yet. Please wait...', 'warn');\n      return;\n    }\n    \n    ui.setOutput('sam-output', 'Loading image...', 'info');\n    \n    try {\n      const img = new Image();\n      img.crossOrigin = 'anonymous';\n      \n      await new Promise((resolve, reject) => {\n        img.onload = resolve;\n        img.onerror = reject;\n        \n        if (typeof source === 'string') {\n          img.src = source;\n        } else {\n          img.src = URL.createObjectURL(source);\n        }\n      });\n      \n      // Show workspace\n      ui.$('sam-upload')?.classList.add('hidden');\n      ui.$('sam-workspace')?.classList.remove('hidden');\n      \n      // Setup canvases\n      const canvas = ui.$('sam-canvas');\n      const maskCanvas = ui.$('sam-mask-canvas');\n      \n      if (canvas && maskCanvas) {\n        state.samCanvas = canvas;\n        state.samMaskCanvas = maskCanvas;\n        state.samCtx = canvas.getContext('2d');\n        state.samMaskCtx = maskCanvas.getContext('2d');\n        \n        // Set canvas size\n        const container = ui.$('sam-workspace');\n        const containerWidth = container?.clientWidth || 400;\n        const containerHeight = container?.clientHeight || 250;\n        \n        const scale = Math.min(\n          containerWidth / img.width,\n          containerHeight / img.height\n        );\n        \n        canvas.width = img.width * scale;\n        canvas.height = img.height * scale;\n        maskCanvas.width = canvas.width;\n        maskCanvas.height = canvas.height;\n        \n        // Draw image\n        state.samCtx.drawImage(img, 0, 0, canvas.width, canvas.height);\n        state.samImage = img;\n        state.samPoints = [];\n        \n        // Setup click handler\n        canvas.onclick = (e) => this.handleClick(e, 1); // Left click = positive\n        canvas.oncontextmenu = (e) => {\n          e.preventDefault();\n          this.handleClick(e, 0); // Right click = negative\n        };\n        \n        // Encode image with SAM encoder\n        ui.setOutput('sam-output', 'Encoding image with SAM...', 'info');\n        const encodeStart = performance.now();\n        await state.samPipeline.setImage(img);\n        const encodeTime = (performance.now() - encodeStart).toFixed(0);\n        \n        ui.setOutput('sam-output', `✓ Image encoded in ${encodeTime}ms. Click to segment objects. Left-click = include, Right-click = exclude.`, 'success');\n      }\n    } catch (error) {\n      ui.showError('sam-output', error);\n    }\n  },\n\n  /**\n   * Load sample image\n   */\n  async loadSampleImage() {\n    if (!state.samModelLoaded) {\n      ui.setOutput('sam-output', 'Model not loaded yet. Please wait...', 'warn');\n      return;\n    }\n    \n    // Using a reliable public image URL\n    const sampleUrl = 'https://images.unsplash.com/photo-1587300003388-59208cc962cb?w=640';\n    await this.loadImage(sampleUrl);\n  },\n\n  /**\n   * Handle canvas click\n   */\n  async handleClick(e, label) {\n    if (!state.samCanvas || !state.samPipeline || !state.samModelLoaded) return;\n    \n    const rect = state.samCanvas.getBoundingClientRect();\n    const x = (e.clientX - rect.left) / rect.width;\n    const y = (e.clientY - rect.top) / rect.height;\n    \n    // Add point\n    state.samPoints.push({ x, y, label });\n    \n    // Draw point indicator\n    this.drawPoints();\n    \n    // Run segmentation\n    ui.setOutput('sam-output', 'Segmenting...', 'info');\n    \n    try {\n      const startTime = performance.now();\n      const result = await state.samPipeline.segment({\n        points: state.samPoints,\n      });\n      const time = (performance.now() - startTime).toFixed(0);\n      \n      // Draw mask\n      this.drawMask(result);\n      \n      ui.setOutput('sam-output', `✓ Segmented in ${time}ms (score: ${result.score.toFixed(2)})`, 'success');\n    } catch (error) {\n      ui.showError('sam-output', error);\n    }\n  },\n\n  /**\n   * Draw points on canvas\n   */\n  drawPoints() {\n    // Remove existing point indicators\n    document.querySelectorAll('.sam-point').forEach(el => el.remove());\n    \n    const workspace = ui.$('sam-workspace');\n    if (!workspace || !state.samCanvas) return;\n    \n    for (const point of state.samPoints) {\n      const indicator = document.createElement('div');\n      indicator.className = `sam-point ${point.label === 1 ? 'positive' : 'negative'}`;\n      indicator.style.left = `${point.x * 100}%`;\n      indicator.style.top = `${point.y * 100}%`;\n      workspace.appendChild(indicator);\n    }\n  },\n\n  /**\n   * Draw segmentation mask\n   */\n  drawMask(result) {\n    if (!state.samMaskCtx || !state.samMaskCanvas) return;\n    \n    const { mask, width, height } = result;\n    const canvas = state.samMaskCanvas;\n    \n    // Create ImageData\n    const imageData = state.samMaskCtx.createImageData(canvas.width, canvas.height);\n    \n    // Scale mask to canvas size\n    const scaleX = width / canvas.width;\n    const scaleY = height / canvas.height;\n    \n    for (let y = 0; y < canvas.height; y++) {\n      for (let x = 0; x < canvas.width; x++) {\n        const srcX = Math.floor(x * scaleX);\n        const srcY = Math.floor(y * scaleY);\n        const srcIdx = srcY * width + srcX;\n        const dstIdx = (y * canvas.width + x) * 4;\n        \n        if (mask[srcIdx] > 0) {\n          // Green overlay for segmented area\n          imageData.data[dstIdx] = 127;     // R\n          imageData.data[dstIdx + 1] = 169; // G\n          imageData.data[dstIdx + 2] = 33;  // B\n          imageData.data[dstIdx + 3] = 180; // A\n        }\n      }\n    }\n    \n    state.samMaskCtx.putImageData(imageData, 0, 0);\n  },\n\n  /**\n   * Clear segmentation\n   */\n  clear() {\n    state.samPoints = [];\n    \n    // Clear mask canvas\n    if (state.samMaskCtx && state.samMaskCanvas) {\n      state.samMaskCtx.clearRect(0, 0, state.samMaskCanvas.width, state.samMaskCanvas.height);\n    }\n    \n    // Remove point indicators\n    document.querySelectorAll('.sam-point').forEach(el => el.remove());\n    \n    ui.setOutput('sam-output', 'Cleared. Click to segment objects.', 'info');\n  },\n\n  /**\n   * Download mask as PNG\n   */\n  downloadMask() {\n    if (!state.samMaskCanvas) {\n      ui.setOutput('sam-output', 'No mask to download', 'warn');\n      return;\n    }\n    \n    // Create download link\n    const link = document.createElement('a');\n    link.download = 'segmentation-mask.png';\n    link.href = state.samMaskCanvas.toDataURL('image/png');\n    link.click();\n  },\n\n  /**\n   * Reset to upload state\n   */\n  reset() {\n    state.samImage = null;\n    state.samPoints = [];\n    \n    ui.$('sam-upload')?.classList.remove('hidden');\n    ui.$('sam-workspace')?.classList.add('hidden');\n    \n    document.querySelectorAll('.sam-point').forEach(el => el.remove());\n    \n    if (state.samMaskCtx && state.samMaskCanvas) {\n      state.samMaskCtx.clearRect(0, 0, state.samMaskCanvas.width, state.samMaskCanvas.height);\n    }\n    \n    // Clear the pipeline's image embedding\n    if (state.samPipeline) {\n      state.samPipeline.clearImage();\n    }\n    \n    ui.setOutput('sam-output', 'Click on image to segment objects. Left-click = include, Right-click = exclude.', 'info');\n  },\n};\n\n/* ==========================================================================\n   6. AI Chat (Real Model)\n   ========================================================================== */\n\nconst chat = {\n  /**\n   * Initialize chat UI\n   */\n  init() {\n    const input = ui.$('chat-input');\n    if (input) {\n      input.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter' && !e.shiftKey && !state.chatGenerating) {\n          e.preventDefault();\n          this.send();\n        }\n      });\n    }\n  },\n\n  /**\n   * Load LLM model with progress display\n   */\n  async loadModel() {\n    if (state.chatModelLoaded) {\n      ui.$('chat-container')?.classList.remove('hidden');\n      ui.$('llm-loader')?.classList.add('hidden');\n      return;\n    }\n    \n    const loadBtn = ui.$('llm-load-btn');\n    const progressContainer = ui.$('llm-progress-container');\n    const progress = ui.$('llm-progress');\n    const loaderDetail = ui.$('llm-loader-detail');\n    \n    try {\n      // Disable button and show progress\n      if (loadBtn) {\n        loadBtn.disabled = true;\n        loadBtn.textContent = 'Loading...';\n      }\n      if (progressContainer) progressContainer.classList.remove('hidden');\n      if (loaderDetail) loaderDetail.classList.remove('hidden');\n      \n      this.updateStatus('loading', 'Downloading model...');\n      \n      // Create pipeline\n      state.chatPipeline = edgeFlow.createTextGenerationPipeline();\n      state.chatPipeline.setChatTemplate('chatml');\n      \n      // Load model with progress\n      await state.chatPipeline.loadModel((progressInfo) => {\n        const { stage, progress: pct } = progressInfo;\n        \n        if (loadBtn) {\n          if (stage === 'tokenizer') {\n            loadBtn.textContent = 'Loading tokenizer...';\n          } else {\n            loadBtn.textContent = `Downloading... ${pct}%`;\n          }\n        }\n        if (loaderDetail) {\n          loaderDetail.classList.add('hidden');\n        }\n        if (progress) {\n          // Tokenizer is quick, model is the main download\n          const totalProgress = stage === 'tokenizer' ? pct * 0.05 : 5 + pct * 0.95;\n          progress.style.width = `${totalProgress}%`;\n        }\n      });\n      \n      state.chatModelLoaded = true;\n      \n      // Hide loader, show chat UI\n      ui.$('llm-loader')?.classList.add('hidden');\n      ui.$('chat-container')?.classList.remove('hidden');\n      \n      this.updateStatus('ready', 'Model loaded! Ready to chat');\n      \n    } catch (error) {\n      console.error('LLM model loading failed:', error);\n      \n      if (loadBtn) {\n        loadBtn.disabled = false;\n        loadBtn.textContent = 'Retry Load';\n      }\n      if (loaderDetail) {\n        loaderDetail.textContent = `Error: ${error.message}`;\n        loaderDetail.style.color = 'var(--error)';\n      }\n      \n      this.updateStatus('error', `Failed: ${error.message}`);\n    }\n  },\n\n  /**\n   * Send message\n   */\n  async send() {\n    if (!state.chatModelLoaded) {\n      this.updateStatus('error', 'Load model first by clicking \"Load Model\"');\n      return;\n    }\n    \n    const input = ui.$('chat-input');\n    const message = input?.value?.trim();\n    \n    if (!message || state.chatGenerating) return;\n    \n    // Clear input\n    input.value = '';\n    \n    // Hide welcome message\n    const welcome = ui.$('chat-messages')?.querySelector('.chat-welcome');\n    if (welcome) welcome.remove();\n    \n    // Add user message\n    this.addMessage('user', message);\n    \n    // Set generating state\n    state.chatGenerating = true;\n    this.updateStatus('loading', 'Generating...');\n    \n    try {\n      // Add assistant message placeholder\n      const assistantMsg = this.addMessage('assistant', 'Thinking...', true);\n      \n      // Generate response using real model\n      // Note: TinyLlama in WASM is slow, limit tokens for demo\n      let response = '';\n      let tokenCount = 0;\n      \n      console.log('[Chat] Starting generation...');\n      const startTime = performance.now();\n      \n      // Use streaming for real-time feedback\n      if (state.chatPipeline.chatStream) {\n        for await (const event of state.chatPipeline.chatStream(message, {\n          maxNewTokens: 32, // Limited for browser performance\n          temperature: 0.7,\n          topP: 0.9,\n        })) {\n          response = event.generatedText;\n          tokenCount++;\n          assistantMsg.textContent = response;\n          this.updateStatus('loading', `Generating... (${tokenCount} tokens)`);\n          \n          // Scroll to bottom\n          const container = ui.$('chat-messages');\n          if (container) {\n            container.scrollTop = container.scrollHeight;\n          }\n        }\n      } else {\n        // Fallback to non-streaming\n        this.updateStatus('loading', 'Generating (this may take a while)...');\n        const result = await state.chatPipeline.chat(message, {\n          maxNewTokens: 32, // Limited for browser performance\n          temperature: 0.7,\n          topP: 0.9,\n        });\n        response = result.generatedText;\n        tokenCount = result.numTokens;\n        assistantMsg.textContent = response;\n      }\n      \n      const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);\n      console.log(`[Chat] Generated ${tokenCount} tokens in ${elapsed}s`);\n      \n      // Remove typing indicator\n      assistantMsg.classList.remove('typing');\n      \n      // Update history\n      state.chatHistory.push(\n        { role: 'user', content: message },\n        { role: 'assistant', content: response }\n      );\n      \n      this.updateStatus('ready', 'Ready to chat');\n    } catch (error) {\n      this.updateStatus('error', `Error: ${error.message}`);\n      // Remove typing indicator\n      const typingMsg = ui.$('chat-messages')?.querySelector('.typing');\n      if (typingMsg) typingMsg.remove();\n    } finally {\n      state.chatGenerating = false;\n    }\n    \n    // Scroll to bottom\n    const container = ui.$('chat-messages');\n    if (container) {\n      container.scrollTop = container.scrollHeight;\n    }\n  },\n\n  /**\n   * Add message to chat\n   */\n  addMessage(role, content, isTyping = false) {\n    const container = ui.$('chat-messages');\n    if (!container) return null;\n    \n    const msg = document.createElement('div');\n    msg.className = `chat-message ${role}${isTyping ? ' typing' : ''}`;\n    msg.textContent = content;\n    \n    container.appendChild(msg);\n    container.scrollTop = container.scrollHeight;\n    \n    return msg;\n  },\n\n  /**\n   * Update status indicator\n   */\n  updateStatus(status, text) {\n    const dot = ui.$('chat-status')?.querySelector('.chat-status-dot');\n    const textEl = ui.$('chat-status-text');\n    \n    if (dot) {\n      dot.className = `chat-status-dot ${status === 'loading' ? 'loading' : status === 'error' ? 'error' : ''}`;\n    }\n    \n    if (textEl) {\n      textEl.textContent = text;\n    }\n  },\n\n  /**\n   * Clear chat history\n   */\n  clear() {\n    state.chatHistory = [];\n    \n    // Clear conversation in pipeline\n    if (state.chatPipeline) {\n      state.chatPipeline.clearConversation();\n    }\n    \n    const container = ui.$('chat-messages');\n    if (container) {\n      container.innerHTML = `\n        <div class=\"chat-welcome\">\n          <span class=\"chat-welcome-icon\">🤖</span>\n          <p>Hi! I'm TinyLlama running entirely in your browser.</p>\n          <p class=\"chat-welcome-hint\">Ask me anything!</p>\n        </div>\n      `;\n    }\n    \n    this.updateStatus('ready', 'Ready to chat');\n  },\n};\n\n/* ==========================================================================\n   7. Demo Class (Public API)\n   ========================================================================== */\n\n/**\n * Demo public API - exposed to window for onclick handlers\n */\nwindow.Demo = {\n  // Model\n  loadModel: () => features.loadModel(),\n  testModel: () => features.testModel(),\n\n  // SAM Interactive Segmentation\n  loadSampleImage: () => sam.loadSampleImage(),\n  clearSegmentation: () => sam.clear(),\n  downloadMask: () => sam.downloadMask(),\n\n  // AI Chat\n  loadLLM: () => chat.loadModel(),\n  sendChat: () => chat.send(),\n  clearChat: () => chat.clear(),\n\n  // Core\n  testTensors: () => features.testTensors(),\n  classifyText: () => features.classifyText(),\n  classifyBatch: () => features.classifyBatch(),\n  extractFeatures: () => features.extractFeatures(),\n\n  // Tools\n  quantize: () => features.quantize(),\n  prune: () => features.prune(),\n  debug: () => features.debug(),\n  benchmark: () => features.benchmark(),\n\n  // System\n  testScheduler: () => features.testScheduler(),\n  allocateMemory: () => features.allocateMemory(),\n  cleanupMemory: () => features.cleanupMemory(),\n  testConcurrency: () => features.testConcurrency(),\n\n  // Monitor\n  startMonitor: () => features.startMonitor(),\n  stopMonitor: () => features.stopMonitor(),\n  simulateInferences: () => features.simulateInferences(),\n  openDashboard: () => features.openDashboard(),\n  closeDashboard: () => features.closeDashboard(),\n};\n\n/* ==========================================================================\n   8. Initialization\n   ========================================================================== */\n\n/**\n * Initialize demo on DOM ready\n */\nasync function init() {\n  // Initialize UI\n  ui.initOutputs();\n  await ui.updateRuntimeStatus();\n  ui.updateMemoryStatus();\n\n  // Initialize Chat UI (but don't load model yet)\n  chat.init();\n\n  // Initialize SAM and start loading models automatically\n  await sam.init();\n\n  // Setup modal close handlers\n  const modal = ui.$('dashboard-modal');\n  if (modal) {\n    modal.addEventListener('click', (e) => {\n      if (e.target === modal) {\n        features.closeDashboard();\n      }\n    });\n  }\n\n  // ESC key closes modal\n  document.addEventListener('keydown', (e) => {\n    if (e.key === 'Escape') {\n      features.closeDashboard();\n    }\n  });\n\n  console.log('✓ edgeFlow.js Demo initialized');\n}\n\n// Wait for DOM\nif (document.readyState === 'loading') {\n  document.addEventListener('DOMContentLoaded', init);\n} else {\n  init();\n}\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>edgeFlow.js - Interactive Demo</title>\n  <link rel=\"stylesheet\" href=\"/demo/styles.css\">\n  <!-- Import map for onnxruntime-web -->\n  <script type=\"importmap\">\n    {\n      \"imports\": {\n        \"onnxruntime-web\": \"https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/esm/ort.webgpu.min.js\"\n      }\n    }\n  </script>\n</head>\n<body>\n  <div class=\"container\">\n    <!-- Header -->\n    <header>\n      <h1><span>⚡ edgeFlow.js</span></h1>\n      <p class=\"subtitle\">Lightweight Browser ML Inference Framework</p>\n    </header>\n\n    <!-- Row 1: Status Cards + Model Loading -->\n    <div class=\"bento-grid\">\n      <!-- Runtime Status -->\n      <div class=\"bento-card span-3\">\n        <div class=\"card-header\">\n          <div class=\"card-icon\">🖥️</div>\n          <div class=\"card-title\">Runtime</div>\n        </div>\n        <div class=\"status-list\" id=\"runtime-status\"></div>\n      </div>\n\n      <!-- Memory Status -->\n      <div class=\"bento-card span-3\">\n        <div class=\"card-header\">\n          <div class=\"card-icon green\">📊</div>\n          <div class=\"card-title\">Memory</div>\n        </div>\n        <div class=\"status-list\" id=\"memory-status\"></div>\n      </div>\n\n      <!-- Model Loading -->\n      <div class=\"bento-card span-6\">\n        <div class=\"card-header\">\n          <div class=\"card-icon pink\">📦</div>\n          <div>\n            <div class=\"card-title\">Load ONNX Model</div>\n            <div class=\"card-desc\">Download and initialize real ONNX models from Hugging Face</div>\n          </div>\n        </div>\n        <input type=\"text\" id=\"model-url\" placeholder=\"Enter ONNX model URL...\" class=\"mb-2\"\n               value=\"https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx\">\n        <div class=\"btn-group\">\n          <button onclick=\"Demo.loadModel()\">Load Model</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.testModel()\">Test Inference</button>\n        </div>\n        <div class=\"output mt-2\" id=\"model-output\"></div>\n      </div>\n    </div>\n\n    <!-- Row 2: Advanced AI Features -->\n    <div class=\"bento-grid\">\n      <!-- SAM Interactive Segmentation -->\n      <div class=\"bento-card span-6\">\n        <div class=\"card-header\">\n          <div class=\"card-icon pink\">✂️</div>\n          <div>\n            <div class=\"card-title\">Interactive Segmentation</div>\n            <div class=\"card-desc\">Click to segment objects - powered by SAM (~14MB)</div>\n          </div>\n        </div>\n        <!-- SAM Loading State -->\n        <div class=\"model-loader\" id=\"sam-loader\">\n          <div class=\"loader-content\">\n            <div class=\"loader-spinner\"></div>\n            <div class=\"loader-text\" id=\"sam-loader-text\">Loading SAM model...</div>\n            <div class=\"progress-bar\">\n              <div class=\"progress-fill\" id=\"sam-progress\"></div>\n            </div>\n            <div class=\"loader-detail\" id=\"sam-loader-detail\">0%</div>\n          </div>\n        </div>\n        <!-- SAM Main Content -->\n        <div class=\"sam-container hidden\" id=\"sam-container\">\n          <div class=\"sam-upload\" id=\"sam-upload\">\n            <input type=\"file\" id=\"sam-file-input\" accept=\"image/*\" style=\"display: none;\">\n            <div class=\"sam-upload-content\" onclick=\"document.getElementById('sam-file-input').click()\">\n              <span class=\"sam-upload-icon\">🖼️</span>\n              <span>Drop image or click to upload</span>\n            </div>\n          </div>\n          <div class=\"sam-workspace hidden\" id=\"sam-workspace\">\n            <canvas id=\"sam-canvas\"></canvas>\n            <canvas id=\"sam-mask-canvas\"></canvas>\n          </div>\n        </div>\n        <div class=\"btn-group mt-2\">\n          <button onclick=\"Demo.loadSampleImage()\" id=\"sam-sample-btn\" disabled>Sample Image</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.clearSegmentation()\" id=\"sam-clear-btn\" disabled>Clear</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.downloadMask()\" id=\"sam-download-btn\" disabled>Download</button>\n        </div>\n        <div class=\"output mt-2\" id=\"sam-output\">Loading SAM model... Please wait.</div>\n      </div>\n\n      <!-- LLM Chat -->\n      <div class=\"bento-card span-6\">\n        <div class=\"card-header\">\n          <div class=\"card-icon green\">💬</div>\n          <div>\n            <div class=\"card-title\">AI Chat</div>\n            <div class=\"card-desc\">TinyLlama 1.1B running in your browser (~714MB)</div>\n          </div>\n        </div>\n        <!-- LLM Loading State -->\n        <div class=\"model-loader\" id=\"llm-loader\">\n          <div class=\"loader-content\">\n            <div class=\"loader-info\">\n              <p>TinyLlama is a 1.1B parameter language model.</p>\n              <p class=\"loader-warning\">Download size: ~714MB (may take several minutes)</p>\n            </div>\n            <button onclick=\"Demo.loadLLM()\" id=\"llm-load-btn\" class=\"loader-btn\">Load Model</button>\n            <div class=\"progress-bar hidden\" id=\"llm-progress-container\">\n              <div class=\"progress-fill\" id=\"llm-progress\"></div>\n            </div>\n            <div class=\"loader-detail hidden\" id=\"llm-loader-detail\">0%</div>\n          </div>\n        </div>\n        <!-- LLM Chat Content -->\n        <div class=\"chat-container hidden\" id=\"chat-container\">\n          <div class=\"chat-messages\" id=\"chat-messages\">\n            <div class=\"chat-welcome\">\n              <span class=\"chat-welcome-icon\">🤖</span>\n              <p>Hi! I'm TinyLlama running entirely in your browser.</p>\n              <p class=\"chat-welcome-hint\">Ask me anything!</p>\n            </div>\n          </div>\n          <div class=\"chat-input-container\">\n            <input type=\"text\" id=\"chat-input\" placeholder=\"Type a message...\" class=\"chat-input\">\n            <button onclick=\"Demo.sendChat()\" id=\"chat-send-btn\">Send</button>\n          </div>\n        </div>\n        <div class=\"chat-status\" id=\"chat-status\">\n          <span class=\"chat-status-dot\"></span>\n          <span id=\"chat-status-text\">Click \"Load Model\" to start</span>\n        </div>\n      </div>\n    </div>\n\n    <!-- Row 3: Core Operations -->\n    <div class=\"bento-grid\">\n      <!-- Tensor Operations -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon\">🧮</div>\n          <div>\n            <div class=\"card-title\">Tensor Operations</div>\n            <div class=\"card-desc\">Create, manipulate, and compute tensors</div>\n          </div>\n        </div>\n        <button onclick=\"Demo.testTensors()\">Run Tests</button>\n        <div class=\"output mt-2\" id=\"tensor-output\"></div>\n      </div>\n\n      <!-- Text Classification -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon pink\">📝</div>\n          <div>\n            <div class=\"card-title\">Text Classification</div>\n            <div class=\"card-desc\">Sentiment analysis with loaded model</div>\n          </div>\n        </div>\n        <input type=\"text\" id=\"text-input\" placeholder=\"Enter text...\" value=\"I love this product!\" class=\"mb-2\">\n        <div class=\"btn-group\">\n          <button onclick=\"Demo.classifyText()\">Classify</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.classifyBatch()\">Batch</button>\n        </div>\n        <div class=\"output mt-2\" id=\"text-output\"></div>\n      </div>\n\n      <!-- Feature Extraction -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon green\">🔍</div>\n          <div>\n            <div class=\"card-title\">Feature Extraction</div>\n            <div class=\"card-desc\">Extract embeddings from text</div>\n          </div>\n        </div>\n        <textarea id=\"feature-input\" class=\"mb-2\" placeholder=\"Enter text...\">Machine learning is transforming software.</textarea>\n        <button onclick=\"Demo.extractFeatures()\">Extract</button>\n        <div class=\"output mt-2\" id=\"feature-output\"></div>\n      </div>\n    </div>\n\n    <!-- Row 4: Tools -->\n    <div class=\"bento-grid\">\n      <!-- Quantization -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon orange\">📦</div>\n          <div>\n            <div class=\"card-title\">Model Quantization</div>\n            <div class=\"card-desc\">Compress tensors to int8/float16</div>\n          </div>\n        </div>\n        <div class=\"btn-group mb-2\">\n          <button onclick=\"Demo.quantize()\">Quantize</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.prune()\">Prune</button>\n        </div>\n        <div class=\"output\" id=\"quant-output\"></div>\n      </div>\n\n      <!-- Debugger -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon\">🔬</div>\n          <div>\n            <div class=\"card-title\">Tensor Debugger</div>\n            <div class=\"card-desc\">Inspect tensor statistics & distribution</div>\n          </div>\n        </div>\n        <button onclick=\"Demo.debug()\">Inspect Tensor</button>\n        <div class=\"output mt-2\" id=\"debugger-output\"></div>\n      </div>\n\n      <!-- Benchmark -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon pink\">⚡</div>\n          <div>\n            <div class=\"card-title\">Benchmark</div>\n            <div class=\"card-desc\">Measure operation performance</div>\n          </div>\n        </div>\n        <button onclick=\"Demo.benchmark()\">Run Benchmark</button>\n        <div class=\"output mt-2\" id=\"benchmark-output\"></div>\n      </div>\n    </div>\n\n    <!-- Row 5: System -->\n    <div class=\"bento-grid\">\n      <!-- Scheduler -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon green\">📋</div>\n          <div>\n            <div class=\"card-title\">Task Scheduler</div>\n            <div class=\"card-desc\">Priority-based task scheduling</div>\n          </div>\n        </div>\n        <button onclick=\"Demo.testScheduler()\">Test Scheduler</button>\n        <div class=\"output mt-2\" id=\"scheduler-output\"></div>\n      </div>\n\n      <!-- Memory Management -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon orange\">💾</div>\n          <div>\n            <div class=\"card-title\">Memory Management</div>\n            <div class=\"card-desc\">Allocate, track, and release tensors</div>\n          </div>\n        </div>\n        <div class=\"btn-group\">\n          <button onclick=\"Demo.allocateMemory()\">Allocate</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.cleanupMemory()\">Cleanup</button>\n        </div>\n        <div class=\"output mt-2\" id=\"memory-output\"></div>\n      </div>\n\n      <!-- Concurrency -->\n      <div class=\"bento-card span-4\">\n        <div class=\"card-header\">\n          <div class=\"card-icon\">⚡</div>\n          <div>\n            <div class=\"card-title\">Concurrent Execution</div>\n            <div class=\"card-desc\">Run multiple inferences in parallel</div>\n          </div>\n        </div>\n        <button onclick=\"Demo.testConcurrency()\">Run Test</button>\n        <div class=\"output mt-2\" id=\"concurrency-output\"></div>\n        <div class=\"metrics mt-2 hidden\" id=\"concurrency-metrics\"></div>\n      </div>\n    </div>\n\n    <!-- Row 6: Monitor -->\n    <div class=\"bento-grid\">\n      <div class=\"bento-card span-12\">\n        <div class=\"card-header\">\n          <div class=\"card-icon pink\">📊</div>\n          <div>\n            <div class=\"card-title\">Performance Monitor</div>\n            <div class=\"card-desc\">Real-time performance metrics and visualization</div>\n          </div>\n        </div>\n        <div class=\"btn-group mb-2\">\n          <button onclick=\"Demo.startMonitor()\">Start</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.simulateInferences()\">Simulate</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.openDashboard()\">Dashboard</button>\n          <button class=\"btn-secondary\" onclick=\"Demo.stopMonitor()\">Stop</button>\n        </div>\n        <div class=\"metrics\" id=\"monitor-metrics\"></div>\n      </div>\n    </div>\n\n    <!-- Footer -->\n    <footer>\n      <p>edgeFlow.js v0.1.0 | <a href=\"https://github.com/nicepkg/edgeflow.js\" target=\"_blank\">GitHub</a></p>\n    </footer>\n  </div>\n\n  <!-- Dashboard Modal -->\n  <div class=\"modal-overlay\" id=\"dashboard-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <span class=\"modal-title\">📊 Performance Dashboard</span>\n        <button class=\"modal-close\" onclick=\"Demo.closeDashboard()\">×</button>\n      </div>\n      <iframe id=\"dashboard-frame\" class=\"modal-frame\"></iframe>\n    </div>\n  </div>\n\n  <!-- Demo Script -->\n  <script type=\"module\" src=\"/demo/demo.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "demo/server.js",
    "content": "/**\n * Simple development server for testing edgeFlow.js\n * \n * Usage: node demo/server.js\n */\n\nimport { createServer } from 'http';\nimport { readFile } from 'fs/promises';\nimport { extname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst ROOT = join(__dirname, '..');\n\nconst MIME_TYPES = {\n  '.html': 'text/html',\n  '.js': 'application/javascript',\n  '.mjs': 'application/javascript',\n  '.css': 'text/css',\n  '.json': 'application/json',\n  '.png': 'image/png',\n  '.jpg': 'image/jpeg',\n  '.svg': 'image/svg+xml',\n  '.wasm': 'application/wasm',\n};\n\nconst PORT = process.env.PORT || 3000;\n\nconst server = createServer(async (req, res) => {\n  let url = req.url || '/';\n  \n  // Default to demo/index.html\n  if (url === '/') {\n    url = '/demo/index.html';\n  }\n\n  const filePath = join(ROOT, url);\n  const ext = extname(filePath);\n  const mimeType = MIME_TYPES[ext] || 'application/octet-stream';\n\n  try {\n    const content = await readFile(filePath);\n    \n    // Add CORS and security headers for WebGPU/WASM\n    res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');\n    res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');\n    res.setHeader('Content-Type', mimeType);\n    res.setHeader('Access-Control-Allow-Origin', '*');\n    \n    res.writeHead(200);\n    res.end(content);\n  } catch (error) {\n    if (error.code === 'ENOENT') {\n      res.writeHead(404);\n      res.end(`File not found: ${url}`);\n    } else {\n      res.writeHead(500);\n      res.end(`Server error: ${error.message}`);\n    }\n  }\n});\n\nserver.listen(PORT, () => {\n  console.log(`\n╔══════════════════════════════════════════════════════╗\n║                                                      ║\n║   ⚡ edgeFlow.js Development Server                  ║\n║                                                      ║\n║   Local:   http://localhost:${PORT}                     ║\n║                                                      ║\n║   Press Ctrl+C to stop                               ║\n║                                                      ║\n╚══════════════════════════════════════════════════════╝\n`);\n});\n"
  },
  {
    "path": "demo/styles.css",
    "content": "/**\n * edgeFlow.js Demo - Spotify-inspired Theme\n * \n * Design: Spotify color palette + liquid glass\n * - Deep blacks and grays\n * - Signature green accent\n * - Clean, bold typography\n * - Subtle glass effects\n */\n\n/* ==========================================================================\n   1. Variables & Reset\n   ========================================================================== */\n\n:root {\n  /* 牛油果绿配色 */\n  --color-accent: #7fa921;\n  --color-accent-light: #8fbc2a;\n  --color-accent-dim: rgba(127, 169, 33, 0.12);\n  --color-dark: #5a5755;\n  --color-light: #e2e1e6;\n\n  /* Background - 明亮风格 */\n  --bg-base: #d8d7dc;\n  --bg-elevated: var(--color-light);\n  --bg-highlight: #eaeaed;\n\n  /* Glass - 浅色玻璃效果 */\n  --glass-bg: rgba(255, 255, 255, 0.6);\n  --glass-bg-hover: rgba(255, 255, 255, 0.8);\n  --glass-border: rgba(255, 255, 255, 0.8);\n  --glass-border-hover: rgba(255, 255, 255, 0.95);\n  --glass-highlight: rgba(255, 255, 255, 0.5);\n\n  /* Text - 深色文字 */\n  --text-primary: var(--color-dark);\n  --text-secondary: #6e6b69;\n  --text-muted: #8a8886;\n\n  /* Accent variations */\n  --accent: var(--color-accent);\n  --accent-hover: var(--color-accent-light);\n  --accent-dim: var(--color-accent-dim);\n\n  /* Status */\n  --success: var(--color-accent);\n  --warning: #d4a520;\n  --error: #c45c4a;\n\n  /* Spacing */\n  --space-xs: 0.25rem;\n  --space-sm: 0.5rem;\n  --space-md: 0.75rem;\n  --space-lg: 1rem;\n  --space-xl: 1.5rem;\n\n  /* Border Radius */\n  --radius-sm: 4px;\n  --radius-md: 8px;\n  --radius-lg: 12px;\n  --radius-xl: 16px;\n  --radius-full: 9999px;\n\n  /* Fonts */\n  --font-sans: 'Circular', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;\n  --font-mono: 'Fira Code', 'SF Mono', monospace;\n\n  /* Glass blur */\n  --blur-glass: 40px;\n}\n\n*,\n*::before,\n*::after {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\nhtml {\n  scroll-behavior: smooth;\n}\n\nbody {\n  font-family: var(--font-sans);\n  background: linear-gradient(145deg, #d0cfd4 0%, var(--bg-base) 50%, #cccbd0 100%);\n  color: var(--text-primary);\n  min-height: 100vh;\n  line-height: 1.6;\n  overflow-x: hidden;\n}\n\n/* Subtle gradient overlay */\nbody::before {\n  content: '';\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  height: 500px;\n  background: linear-gradient(180deg, \n    rgba(127, 169, 33, 0.1) 0%, \n    transparent 100%);\n  pointer-events: none;\n  z-index: 0;\n}\n\n/* ==========================================================================\n   2. Layout\n   ========================================================================== */\n\n.container {\n  max-width: 1440px;\n  margin: 0 auto;\n  padding: var(--space-xl);\n  position: relative;\n  z-index: 1;\n}\n\n.bento-grid {\n  display: grid;\n  grid-template-columns: repeat(12, 1fr);\n  gap: var(--space-lg);\n  margin-bottom: var(--space-lg);\n}\n\n.span-3 { grid-column: span 3; }\n.span-4 { grid-column: span 4; }\n.span-5 { grid-column: span 5; }\n.span-6 { grid-column: span 6; }\n.span-7 { grid-column: span 7; }\n.span-8 { grid-column: span 8; }\n.span-12 { grid-column: span 12; }\n\n/* ==========================================================================\n   3. Header\n   ========================================================================== */\n\nheader {\n  text-align: center;\n  padding: 3rem 2rem;\n  margin-bottom: 2rem;\n  position: relative;\n  \n  /* 绿色渐变背景 */\n  background: linear-gradient(135deg, var(--color-accent) 0%, #6a9020 100%);\n  border-radius: var(--radius-xl);\n  overflow: hidden;\n  box-shadow: 0 10px 40px -10px rgba(127, 169, 33, 0.4);\n}\n\n/* 白色光晕 */\nheader::before {\n  content: '';\n  position: absolute;\n  top: -30%;\n  left: 50%;\n  transform: translateX(-50%);\n  width: 500px;\n  height: 250px;\n  background: radial-gradient(ellipse, rgba(255, 255, 255, 0.3) 0%, transparent 70%);\n  pointer-events: none;\n}\n\nh1 {\n  font-size: 3rem;\n  font-weight: 700;\n  letter-spacing: -0.04em;\n  margin-bottom: var(--space-sm);\n  position: relative;\n}\n\nh1 span {\n  color: #ffffff;\n  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n/* 白色下划线 */\nh1::after {\n  content: '';\n  display: block;\n  width: 60px;\n  height: 4px;\n  background: rgba(255, 255, 255, 0.8);\n  border-radius: 2px;\n  margin: var(--space-md) auto 0;\n}\n\n.subtitle {\n  color: rgba(255, 255, 255, 0.85);\n  font-size: 1rem;\n  font-weight: 400;\n  position: relative;\n}\n\n/* ==========================================================================\n   4. Cards - Spotify Style with Glass\n   ========================================================================== */\n\n.bento-card {\n  position: relative;\n  padding: 1.25rem;\n  \n  /* 浅色玻璃卡片 */\n  background: var(--glass-bg);\n  backdrop-filter: blur(var(--blur-glass)) saturate(180%);\n  -webkit-backdrop-filter: blur(var(--blur-glass)) saturate(180%);\n  border: 1px solid var(--glass-border);\n  border-radius: var(--radius-lg);\n  \n  box-shadow: \n    0 4px 24px -8px rgba(0, 0, 0, 0.08),\n    inset 0 1px 0 0 rgba(255, 255, 255, 0.8);\n  \n  transition: all 0.3s ease;\n}\n\n.bento-card:hover {\n  background: var(--glass-bg-hover);\n  border-color: var(--glass-border-hover);\n  transform: translateY(-2px);\n  box-shadow: \n    0 8px 32px -8px rgba(0, 0, 0, 0.12),\n    inset 0 1px 0 0 rgba(255, 255, 255, 0.9);\n}\n\n/* Card Header */\n.card-header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-md);\n  margin-bottom: var(--space-lg);\n}\n\n.card-icon {\n  width: 40px;\n  height: 40px;\n  border-radius: var(--radius-md);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 1.25rem;\n  flex-shrink: 0;\n  \n  /* 统一黄色背景 */\n  background: #f0c850;\n}\n\n.card-icon.pink,\n.card-icon.green,\n.card-icon.orange { \n  background: #f0c850;\n}\n\n.card-title {\n  font-size: 1rem;\n  font-weight: 700;\n  color: var(--text-primary);\n  letter-spacing: -0.01em;\n}\n\n.card-desc {\n  font-size: 0.8125rem;\n  color: var(--text-muted);\n  margin-top: 2px;\n}\n\n/* ==========================================================================\n   5. Components\n   ========================================================================== */\n\n/* Buttons */\nbutton {\n  position: relative;\n  padding: 0.75rem 2rem;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 700;\n  letter-spacing: 0.1em;\n  text-transform: uppercase;\n  cursor: pointer;\n  border-radius: var(--radius-full);\n  transition: all 0.2s ease;\n  \n  /* Primary accent button */\n  background: var(--accent);\n  border: none;\n  color: #ffffff;\n  box-shadow: 0 4px 12px -4px rgba(127, 169, 33, 0.4);\n}\n\nbutton:hover {\n  background: var(--accent-hover);\n  transform: scale(1.04);\n  box-shadow: 0 6px 16px -4px rgba(127, 169, 33, 0.5);\n}\n\nbutton:active {\n  transform: scale(1);\n}\n\nbutton:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n  transform: none;\n}\n\n.btn-secondary {\n  background: rgba(255, 255, 255, 0.6);\n  border: 1px solid rgba(66, 63, 61, 0.2);\n  color: var(--color-dark);\n  box-shadow: none;\n}\n\n.btn-secondary:hover {\n  background: rgba(255, 255, 255, 0.9);\n  border-color: rgba(66, 63, 61, 0.3);\n  transform: scale(1.04);\n  box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.1);\n}\n\n.btn-sm {\n  padding: 0.5rem 1rem;\n  font-size: 0.75rem;\n}\n\n.btn-group {\n  display: flex;\n  gap: var(--space-sm);\n  flex-wrap: wrap;\n}\n\n/* Inputs */\ninput,\ntextarea {\n  width: 100%;\n  padding: 0.75rem 1rem;\n  font-family: inherit;\n  font-size: 0.875rem;\n  border-radius: var(--radius-md);\n  transition: all 0.2s;\n  \n  background: rgba(255, 255, 255, 0.7);\n  border: 1px solid rgba(66, 63, 61, 0.15);\n  color: var(--text-primary);\n}\n\ninput:focus,\ntextarea:focus {\n  outline: none;\n  border-color: var(--accent);\n  background: rgba(255, 255, 255, 0.9);\n  box-shadow: 0 0 0 3px rgba(127, 169, 33, 0.1);\n}\n\ninput::placeholder,\ntextarea::placeholder {\n  color: var(--text-muted);\n}\n\ntextarea {\n  min-height: 80px;\n  resize: vertical;\n}\n\n/* Status List */\n.status-list {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-sm);\n}\n\n.status-item {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: var(--space-sm) var(--space-md);\n  font-size: 0.875rem;\n  \n  background: rgba(255, 255, 255, 0.5);\n  border-radius: var(--radius-sm);\n}\n\n.status-badge {\n  padding: var(--space-xs) var(--space-md);\n  border-radius: var(--radius-full);\n  font-size: 0.6875rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n}\n\n.status-success { \n  background: var(--accent-dim); \n  color: var(--accent);\n}\n.status-warning { \n  background: rgba(245, 158, 11, 0.15); \n  color: var(--warning); \n}\n.status-error { \n  background: rgba(233, 20, 41, 0.15); \n  color: var(--error); \n}\n.status-pending { \n  background: rgba(255, 255, 255, 0.05); \n  color: var(--text-muted); \n}\n\n/* ==========================================================================\n   6. Output & Metrics\n   ========================================================================== */\n\n.output {\n  padding: 1rem;\n  font-family: var(--font-mono);\n  font-size: 0.75rem;\n  overflow-x: auto;\n  max-height: 200px;\n  overflow-y: auto;\n  line-height: 1.7;\n  \n  background: var(--color-dark);\n  border-radius: var(--radius-md);\n  color: var(--color-light);\n}\n\n.output pre {\n  white-space: pre-wrap;\n  word-break: break-word;\n  margin: 0;\n}\n\n.output .success { color: var(--accent); }\n.output .error { color: #e8806e; }\n.output .info { color: var(--color-light); }\n.output .warn { color: #e8c860; }\n\n/* Metrics */\n.metrics {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));\n  gap: var(--space-md);\n}\n\n.metric {\n  text-align: center;\n  padding: var(--space-md);\n  \n  background: rgba(255, 255, 255, 0.5);\n  border-radius: var(--radius-md);\n  transition: background 0.2s;\n}\n\n.metric:hover {\n  background: rgba(255, 255, 255, 0.7);\n}\n\n.metric-value {\n  font-size: 1.5rem;\n  font-weight: 700;\n  color: var(--accent);\n  font-variant-numeric: tabular-nums;\n}\n\n.metric-label {\n  font-size: 0.6875rem;\n  color: var(--text-muted);\n  margin-top: var(--space-xs);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n  font-weight: 700;\n}\n\n/* ==========================================================================\n   7. Modal\n   ========================================================================== */\n\n.modal-overlay {\n  position: fixed;\n  inset: 0;\n  z-index: 1000;\n  display: none;\n  align-items: center;\n  justify-content: center;\n  padding: 2rem;\n  \n  background: rgba(66, 63, 61, 0.6);\n  backdrop-filter: blur(8px);\n}\n\n.modal-overlay.active {\n  display: flex;\n  animation: fadeIn 0.2s ease;\n}\n\n.modal {\n  position: relative;\n  width: 100%;\n  max-width: 1200px;\n  height: 90vh;\n  overflow: hidden;\n  \n  background: var(--color-light);\n  border-radius: var(--radius-xl);\n  box-shadow: 0 25px 80px -20px rgba(0, 0, 0, 0.3);\n  \n  animation: slideUp 0.3s ease;\n}\n\n.modal-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--space-lg) var(--space-xl);\n  background: rgba(255, 255, 255, 0.5);\n  border-bottom: 1px solid rgba(66, 63, 61, 0.1);\n}\n\n.modal-title {\n  font-size: 1rem;\n  font-weight: 700;\n  color: var(--text-primary);\n}\n\n.modal-close {\n  width: 32px;\n  height: 32px;\n  padding: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 1.25rem;\n  line-height: 1;\n  cursor: pointer;\n  \n  background: rgba(196, 92, 74, 0.1);\n  border: none;\n  border-radius: var(--radius-full);\n  color: var(--error);\n  transition: all 0.2s;\n}\n\n.modal-close:hover {\n  background: rgba(196, 92, 74, 0.2);\n  color: var(--error);\n  transform: scale(1.1);\n}\n\n.modal-frame {\n  width: 100%;\n  height: calc(100% - 60px);\n  border: none;\n  background: var(--color-dark);\n}\n\n/* ==========================================================================\n   8. Footer\n   ========================================================================== */\n\nfooter {\n  text-align: center;\n  padding: 2rem;\n  color: var(--text-muted);\n  font-size: 0.8125rem;\n}\n\nfooter a {\n  color: var(--text-secondary);\n  text-decoration: none;\n  transition: color 0.2s;\n}\n\nfooter a:hover {\n  color: var(--accent);\n}\n\n/* ==========================================================================\n   9. Utilities\n   ========================================================================== */\n\n.hidden { display: none !important; }\n.mt-1 { margin-top: var(--space-sm); }\n.mt-2 { margin-top: var(--space-md); }\n.mt-3 { margin-top: var(--space-lg); }\n.mb-1 { margin-bottom: var(--space-sm); }\n.mb-2 { margin-bottom: var(--space-md); }\n\n/* Loader */\n.loader {\n  display: inline-block;\n  width: 14px;\n  height: 14px;\n  border: 2px solid rgba(127, 169, 33, 0.3);\n  border-top-color: var(--accent);\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n  vertical-align: middle;\n  margin-right: var(--space-sm);\n}\n\n/* ==========================================================================\n   10. Animations\n   ========================================================================== */\n\n@keyframes spin {\n  to { transform: rotate(360deg); }\n}\n\n@keyframes fadeIn {\n  from { opacity: 0; }\n  to { opacity: 1; }\n}\n\n@keyframes slideUp {\n  from { opacity: 0; transform: translateY(20px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n/* Scrollbar */\n::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n::-webkit-scrollbar-track {\n  background: rgba(0, 0, 0, 0.05);\n  border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(66, 63, 61, 0.3);\n  border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(66, 63, 61, 0.5);\n}\n\n/* ==========================================================================\n   11. Responsive\n   ========================================================================== */\n\n@media (max-width: 1024px) {\n  .span-3,\n  .span-4,\n  .span-5 { grid-column: span 6; }\n  \n  .span-7,\n  .span-8 { grid-column: span 12; }\n}\n\n@media (max-width: 768px) {\n  .container { padding: var(--space-lg); }\n  \n  h1 { font-size: 2rem; }\n  \n  header { padding: 2rem 1.5rem; }\n  \n  .bento-grid { grid-template-columns: 1fr; }\n  \n  .span-3,\n  .span-4,\n  .span-5,\n  .span-6,\n  .span-7,\n  .span-8,\n  .span-12 { grid-column: span 1; }\n  \n  button {\n    padding: 0.625rem 1.5rem;\n  }\n  \n  .modal { height: 95vh; }\n  .modal-overlay { padding: var(--space-lg); }\n}\n\n/* ==========================================================================\n   12. SAM Interactive Segmentation\n   ========================================================================== */\n\n.sam-container {\n  position: relative;\n  width: 100%;\n  min-height: 250px;\n  border-radius: var(--radius-md);\n  overflow: hidden;\n  background: var(--color-dark);\n}\n\n.sam-upload {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  border: 2px dashed rgba(255, 255, 255, 0.3);\n  border-radius: var(--radius-md);\n  transition: all 0.3s;\n}\n\n.sam-upload:hover {\n  border-color: var(--accent);\n  background: rgba(127, 169, 33, 0.1);\n}\n\n.sam-upload-content {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-sm);\n  color: var(--color-light);\n  opacity: 0.7;\n}\n\n.sam-upload-icon {\n  font-size: 2.5rem;\n}\n\n.sam-workspace {\n  position: relative;\n  width: 100%;\n  height: 250px;\n}\n\n.sam-workspace canvas {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  object-fit: contain;\n}\n\n#sam-canvas {\n  z-index: 1;\n}\n\n#sam-mask-canvas {\n  z-index: 2;\n  pointer-events: none;\n  opacity: 0.5;\n}\n\n/* Click indicator */\n.sam-point {\n  position: absolute;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  transform: translate(-50%, -50%);\n  z-index: 3;\n  pointer-events: none;\n  animation: pointPulse 0.3s ease-out;\n}\n\n.sam-point.positive {\n  background: var(--accent);\n  box-shadow: 0 0 0 3px rgba(127, 169, 33, 0.3);\n}\n\n.sam-point.negative {\n  background: var(--error);\n  box-shadow: 0 0 0 3px rgba(196, 92, 74, 0.3);\n}\n\n@keyframes pointPulse {\n  0% { transform: translate(-50%, -50%) scale(0); opacity: 0; }\n  50% { transform: translate(-50%, -50%) scale(1.2); }\n  100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }\n}\n\n/* ==========================================================================\n   13. AI Chat\n   ========================================================================== */\n\n.chat-container {\n  display: flex;\n  flex-direction: column;\n  height: 400px;\n  min-height: 300px;\n  background: var(--color-dark);\n  border-radius: var(--radius-md);\n  overflow: hidden;\n}\n\n.chat-messages {\n  flex: 1;\n  overflow-y: auto;\n  padding: var(--space-md);\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-sm);\n}\n\n.chat-welcome {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100%;\n  text-align: center;\n  color: var(--color-light);\n  opacity: 0.7;\n}\n\n.chat-welcome-icon {\n  font-size: 2.5rem;\n  margin-bottom: var(--space-sm);\n}\n\n.chat-welcome p {\n  margin: var(--space-xs) 0;\n  font-size: 0.875rem;\n}\n\n.chat-welcome-hint {\n  opacity: 0.6;\n  font-size: 0.75rem !important;\n}\n\n.chat-message {\n  max-width: 85%;\n  padding: var(--space-sm) var(--space-md);\n  border-radius: var(--radius-md);\n  font-size: 0.875rem;\n  line-height: 1.5;\n  animation: messageSlide 0.2s ease-out;\n}\n\n@keyframes messageSlide {\n  from { opacity: 0; transform: translateY(10px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n.chat-message.user {\n  align-self: flex-end;\n  background: var(--accent);\n  color: white;\n  border-bottom-right-radius: 4px;\n}\n\n.chat-message.assistant {\n  align-self: flex-start;\n  background: rgba(255, 255, 255, 0.1);\n  color: var(--color-light);\n  border-bottom-left-radius: 4px;\n}\n\n.chat-message.assistant.typing::after {\n  content: '▋';\n  animation: blink 0.7s infinite;\n}\n\n@keyframes blink {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0; }\n}\n\n.chat-input-container {\n  display: flex;\n  gap: var(--space-sm);\n  padding: var(--space-sm);\n  background: rgba(0, 0, 0, 0.2);\n}\n\n.chat-input {\n  flex: 1;\n  padding: var(--space-sm) var(--space-md);\n  background: rgba(255, 255, 255, 0.1);\n  border: 1px solid rgba(255, 255, 255, 0.1);\n  border-radius: var(--radius-full);\n  color: var(--color-light);\n  font-size: 0.875rem;\n}\n\n.chat-input:focus {\n  outline: none;\n  border-color: var(--accent);\n  background: rgba(255, 255, 255, 0.15);\n}\n\n.chat-input::placeholder {\n  color: rgba(255, 255, 255, 0.4);\n}\n\n.chat-input-container button {\n  padding: var(--space-sm) var(--space-lg);\n  font-size: 0.75rem;\n}\n\n.chat-status {\n  display: flex;\n  align-items: center;\n  gap: var(--space-sm);\n  padding: var(--space-sm) 0;\n  font-size: 0.75rem;\n  color: var(--text-muted);\n}\n\n.chat-status-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  background: var(--accent);\n  animation: pulse 2s infinite;\n}\n\n@keyframes pulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.5; }\n}\n\n.chat-status-dot.loading {\n  background: var(--warning);\n  animation: pulse 0.5s infinite;\n}\n\n.chat-status-dot.error {\n  background: var(--error);\n  animation: none;\n}\n\n/* ==========================================================================\n   14. Model Loader\n   ========================================================================== */\n\n.model-loader {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 250px;\n  background: var(--color-dark);\n  border-radius: var(--radius-md);\n  padding: var(--space-xl);\n}\n\n.loader-content {\n  text-align: center;\n  max-width: 300px;\n}\n\n.loader-spinner {\n  width: 48px;\n  height: 48px;\n  margin: 0 auto var(--space-lg);\n  border: 3px solid rgba(127, 169, 33, 0.2);\n  border-top-color: var(--accent);\n  border-radius: 50%;\n  animation: spin 1s linear infinite;\n}\n\n.loader-text {\n  color: var(--color-light);\n  font-size: 0.875rem;\n  margin-bottom: var(--space-sm);\n}\n\n.loader-detail {\n  color: var(--text-muted);\n  font-size: 0.75rem;\n  font-family: var(--font-mono);\n}\n\n.loader-info {\n  color: var(--color-light);\n  font-size: 0.875rem;\n  margin-bottom: var(--space-lg);\n  line-height: 1.6;\n}\n\n.loader-info p {\n  margin: var(--space-xs) 0;\n}\n\n.loader-warning {\n  color: var(--warning) !important;\n  font-size: 0.75rem !important;\n  opacity: 0.9;\n}\n\n.loader-btn {\n  margin-bottom: var(--space-lg);\n}\n\n/* Progress Bar */\n.progress-bar {\n  width: 100%;\n  height: 8px;\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: var(--radius-full);\n  overflow: hidden;\n  margin: var(--space-md) 0;\n}\n\n.progress-fill {\n  height: 100%;\n  background: linear-gradient(90deg, var(--accent), var(--accent-hover));\n  border-radius: var(--radius-full);\n  width: 0%;\n  transition: width 0.3s ease;\n}\n\n.progress-fill.downloading {\n  background: linear-gradient(90deg, var(--accent), var(--accent-hover));\n  animation: progressPulse 1.5s ease-in-out infinite;\n}\n\n@keyframes progressPulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.7; }\n}\n\n/* Model loader states */\n.model-loader.loading .loader-spinner {\n  display: block;\n}\n\n.model-loader.ready {\n  display: none;\n}\n\n/* Success state for SAM */\n.sam-ready .model-loader {\n  display: none;\n}\n\n.sam-ready .sam-container {\n  display: block !important;\n}\n"
  },
  {
    "path": "dist/backends/index.d.ts",
    "content": "/**\n * edgeFlow.js - Backend Exports\n */\nexport { WebGPURuntime, createWebGPURuntime } from './webgpu.js';\nexport { WebNNRuntime, createWebNNRuntime } from './webnn.js';\nexport { WASMRuntime, createWASMRuntime } from './wasm.js';\nexport { ONNXRuntime, createONNXRuntime, isOnnxAvailable } from './onnx.js';\nexport { TransformersAdapterRuntime, useTransformersBackend, getTransformersAdapter, type TransformersAdapterOptions, type TransformersPipelineFactory, } from './transformers-adapter.js';\nexport type { Runtime, RuntimeType, RuntimeCapabilities } from '../core/types.js';\n/**\n * Register all available backends.\n *\n * Always registers the ONNX Runtime factory synchronously so there is no\n * async race between registration and the first pipeline() call.\n * `ONNXRuntime.isAvailable()` is called lazily by RuntimeManager when it\n * selects a backend, so if onnxruntime-web is not installed the runtime is\n * simply skipped at that point.\n */\nexport declare function registerAllBackends(): void;\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/backends/index.js",
    "content": "/**\n * edgeFlow.js - Backend Exports\n */\n// WebGPU Backend (planned - skeleton only)\nexport { WebGPURuntime, createWebGPURuntime } from './webgpu.js';\n// WebNN Backend (planned - skeleton only)\nexport { WebNNRuntime, createWebNNRuntime } from './webnn.js';\n// WASM Backend (basic tensor ops)\nexport { WASMRuntime, createWASMRuntime } from './wasm.js';\n// ONNX Runtime Backend (real model inference)\nexport { ONNXRuntime, createONNXRuntime, isOnnxAvailable } from './onnx.js';\n// transformers.js Adapter Backend\nexport { TransformersAdapterRuntime, useTransformersBackend, getTransformersAdapter, } from './transformers-adapter.js';\nimport { registerRuntime } from '../core/runtime.js';\nimport { createONNXRuntime } from './onnx.js';\n/**\n * Register all available backends.\n *\n * Always registers the ONNX Runtime factory synchronously so there is no\n * async race between registration and the first pipeline() call.\n * `ONNXRuntime.isAvailable()` is called lazily by RuntimeManager when it\n * selects a backend, so if onnxruntime-web is not installed the runtime is\n * simply skipped at that point.\n */\nexport function registerAllBackends() {\n    registerRuntime('wasm', createONNXRuntime);\n}\n/**\n * Auto-register backends on module load (synchronous — no race condition).\n */\nregisterAllBackends();\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/backends/onnx.d.ts",
    "content": "/**\n * edgeFlow.js - ONNX Runtime Backend\n *\n * Uses onnxruntime-web for real ONNX model inference.\n * onnxruntime-web is an optional peer dependency loaded dynamically.\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, Tensor } from '../core/types.js';\n/**\n * Check whether onnxruntime-web is importable.\n */\nexport declare function isOnnxAvailable(): Promise<boolean>;\n/**\n * ONNXRuntime - Real ONNX model inference using onnxruntime-web\n */\nexport declare class ONNXRuntime implements Runtime {\n    readonly name: RuntimeType;\n    private initialized;\n    private executionProvider;\n    get capabilities(): RuntimeCapabilities;\n    /**\n     * Check if ONNX Runtime is available (peer dependency installed)\n     */\n    isAvailable(): Promise<boolean>;\n    /**\n     * Initialize the ONNX runtime\n     */\n    initialize(): Promise<void>;\n    /**\n     * Load a model from ArrayBuffer\n     */\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /**\n     * Run inference\n     */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Run inference with named inputs\n     */\n    runNamed(model: LoadedModel, namedInputs: Map<string, Tensor>): Promise<Tensor[]>;\n    /**\n     * Unload a model\n     */\n    private unloadModel;\n    /**\n     * Dispose the runtime\n     */\n    dispose(): void;\n}\n/**\n * Create ONNX runtime factory\n */\nexport declare function createONNXRuntime(): Runtime;\n//# sourceMappingURL=onnx.d.ts.map"
  },
  {
    "path": "dist/backends/onnx.js",
    "content": "/**\n * edgeFlow.js - ONNX Runtime Backend\n *\n * Uses onnxruntime-web for real ONNX model inference.\n * onnxruntime-web is an optional peer dependency loaded dynamically.\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n// Lazy-loaded onnxruntime-web module\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet ort = null;\nasync function getOrt() {\n    if (ort)\n        return ort;\n    try {\n        // Import the WASM-only sub-path so Vite rewrites the bare specifier\n        // to ort.wasm.bundle.min.mjs. This avoids loading the JSEP/WebGPU\n        // worker module (jsep.mjs) that ort.bundle.min.mjs eagerly fetches\n        // whenever navigator.gpu exists — which causes a 404 in dev servers\n        // that restrict ES module imports from /public.\n        ort = await import('onnxruntime-web/wasm');\n        return ort;\n    }\n    catch {\n        return null;\n    }\n}\n/**\n * Check whether onnxruntime-web is importable.\n */\nexport async function isOnnxAvailable() {\n    return (await getOrt()) != null;\n}\nconst sessionStore = new Map();\n// ============================================================================\n// ONNX Runtime Implementation\n// ============================================================================\n/**\n * ONNXRuntime - Real ONNX model inference using onnxruntime-web\n */\nexport class ONNXRuntime {\n    name = 'wasm'; // Register as wasm since it's the fallback\n    initialized = false;\n    executionProvider = 'wasm';\n    get capabilities() {\n        return {\n            concurrency: true,\n            quantization: true,\n            float16: this.executionProvider === 'webgpu',\n            dynamicShapes: true,\n            maxBatchSize: 32,\n            availableMemory: 512 * 1024 * 1024, // 512MB\n        };\n    }\n    /**\n     * Check if ONNX Runtime is available (peer dependency installed)\n     */\n    async isAvailable() {\n        return isOnnxAvailable();\n    }\n    /**\n     * Initialize the ONNX runtime\n     */\n    async initialize() {\n        if (this.initialized)\n            return;\n        const ortModule = await getOrt();\n        if (!ortModule) {\n            throw new EdgeFlowError('onnxruntime-web is not installed. Install it with: npm install onnxruntime-web', ErrorCodes.RUNTIME_NOT_AVAILABLE);\n        }\n        // Configure WASM backend for browser use.\n        // numThreads=1 disables multi-threading so ort only needs the plain\n        // .wasm binary — the worker .mjs file is never requested, which avoids\n        // Vite's restriction on importing files from /public as ES modules.\n        // Consumers should copy onnxruntime-web/dist/*.wasm to public/ort/.\n        if (typeof window !== 'undefined' && ortModule.env?.wasm) {\n            ortModule.env.wasm.wasmPaths = '/ort/';\n            ortModule.env.wasm.numThreads = 1;\n        }\n        this.initialized = true;\n    }\n    /**\n     * Load a model from ArrayBuffer\n     */\n    async loadModel(modelData, options = {}) {\n        if (!this.initialized) {\n            await this.initialize();\n        }\n        try {\n            const ortModule = await getOrt();\n            if (!ortModule) {\n                throw new Error('onnxruntime-web is not installed');\n            }\n            // WASM-only execution provider — WebGPU acceleration can be added\n            // later via the dedicated WebGPURuntime backend.\n            const sessionOptions = {\n                executionProviders: ['wasm'],\n                graphOptimizationLevel: 'all',\n            };\n            const modelBytes = new Uint8Array(modelData);\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            const session = await ortModule.InferenceSession.create(modelBytes, sessionOptions);\n            // Get input/output names\n            const inputNames = session.inputNames;\n            const outputNames = session.outputNames;\n            // Generate model ID\n            const modelId = `onnx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n            // Store session\n            sessionStore.set(modelId, {\n                session,\n                inputNames: [...inputNames],\n                outputNames: [...outputNames],\n            });\n            // Create metadata\n            const metadata = {\n                name: options.metadata?.name ?? 'onnx-model',\n                version: '1.0.0',\n                inputs: inputNames.map((name) => ({\n                    name,\n                    dtype: 'float32',\n                    shape: [-1], // Dynamic shape\n                })),\n                outputs: outputNames.map((name) => ({\n                    name,\n                    dtype: 'float32',\n                    shape: [-1],\n                })),\n                sizeBytes: modelData.byteLength,\n                quantization: options.quantization ?? 'float32',\n                format: 'onnx',\n            };\n            // Create model instance\n            const model = new LoadedModelImpl(metadata, 'wasm', () => this.unloadModel(modelId));\n            // Override the ID to match our stored session\n            Object.defineProperty(model, 'id', { value: modelId, writable: false });\n            // Track in memory manager\n            getMemoryManager().trackModel(model, () => model.dispose());\n            return model;\n        }\n        catch (error) {\n            throw new EdgeFlowError(`Failed to load ONNX model: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.MODEL_LOAD_FAILED, { error });\n        }\n    }\n    /**\n     * Run inference\n     */\n    async run(model, inputs) {\n        const sessionData = sessionStore.get(model.id);\n        if (!sessionData) {\n            throw new EdgeFlowError(`ONNX session not found for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n        }\n        const { session, inputNames, outputNames } = sessionData;\n        try {\n            const ortModule = await getOrt();\n            const feeds = {};\n            for (let i = 0; i < Math.min(inputs.length, inputNames.length); i++) {\n                const inputName = inputNames[i];\n                const inputTensor = inputs[i];\n                if (inputName && inputTensor) {\n                    const dtype = inputTensor.dtype;\n                    let ortTensor;\n                    if (dtype === 'int64') {\n                        const data = inputTensor.data;\n                        ortTensor = new ortModule.Tensor('int64', data, inputTensor.shape);\n                    }\n                    else if (dtype === 'int32') {\n                        const data = inputTensor.data;\n                        ortTensor = new ortModule.Tensor('int32', data, inputTensor.shape);\n                    }\n                    else {\n                        const data = inputTensor.toFloat32Array();\n                        ortTensor = new ortModule.Tensor('float32', data, inputTensor.shape);\n                    }\n                    feeds[inputName] = ortTensor;\n                }\n            }\n            const results = await session.run(feeds);\n            // Convert outputs to EdgeFlowTensor\n            const outputs = [];\n            for (const outputName of outputNames) {\n                const ortTensor = results[outputName];\n                if (ortTensor) {\n                    const data = ortTensor.data;\n                    const shape = Array.from(ortTensor.dims).map(d => Number(d));\n                    outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, 'float32'));\n                }\n            }\n            return outputs;\n        }\n        catch (error) {\n            throw new EdgeFlowError(`ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.INFERENCE_FAILED, { modelId: model.id, error });\n        }\n    }\n    /**\n     * Run inference with named inputs\n     */\n    async runNamed(model, namedInputs) {\n        const sessionData = sessionStore.get(model.id);\n        if (!sessionData) {\n            throw new EdgeFlowError(`ONNX session not found for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n        }\n        const { session, inputNames, outputNames } = sessionData;\n        try {\n            const ortModule = await getOrt();\n            const feeds = {};\n            for (const [inputName, inputTensor] of namedInputs) {\n                const tensor = inputTensor;\n                const dtype = tensor.dtype;\n                let ortTensor;\n                if (dtype === 'int64') {\n                    const data = tensor.data;\n                    ortTensor = new ortModule.Tensor('int64', data, tensor.shape);\n                }\n                else if (dtype === 'int32') {\n                    const data = tensor.data;\n                    ortTensor = new ortModule.Tensor('int32', data, tensor.shape);\n                }\n                else {\n                    const data = tensor.toFloat32Array();\n                    ortTensor = new ortModule.Tensor('float32', data, tensor.shape);\n                }\n                feeds[inputName] = ortTensor;\n            }\n            const results = await session.run(feeds);\n            // Convert outputs to EdgeFlowTensor\n            const outputs = [];\n            for (const outputName of outputNames) {\n                const ortTensor = results[outputName];\n                if (ortTensor) {\n                    const data = ortTensor.data;\n                    const shape = Array.from(ortTensor.dims).map(d => Number(d));\n                    outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, 'float32'));\n                }\n            }\n            return outputs;\n        }\n        catch (error) {\n            throw new EdgeFlowError(`ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.INFERENCE_FAILED, { modelId: model.id, expectedInputs: inputNames, providedInputs: Array.from(namedInputs.keys()), error });\n        }\n    }\n    /**\n     * Unload a model\n     */\n    async unloadModel(modelId) {\n        const sessionData = sessionStore.get(modelId);\n        if (sessionData) {\n            // Release session will be handled by GC\n            sessionStore.delete(modelId);\n        }\n    }\n    /**\n     * Dispose the runtime\n     */\n    dispose() {\n        // Clear all sessions\n        sessionStore.clear();\n        this.initialized = false;\n    }\n}\n/**\n * Create ONNX runtime factory\n */\nexport function createONNXRuntime() {\n    return new ONNXRuntime();\n}\n//# sourceMappingURL=onnx.js.map"
  },
  {
    "path": "dist/backends/transformers-adapter.d.ts",
    "content": "/**\n * edgeFlow.js - transformers.js Adapter Backend\n *\n * Wraps transformers.js (by Hugging Face) as an inference backend, giving\n * users access to 1000+ HuggingFace models while adding edgeFlow.js's\n * orchestration layer (scheduling, caching, memory management, workers).\n *\n * @example\n * ```typescript\n * import { useTransformersBackend } from 'edgeflowjs';\n * import { pipeline as tfPipeline } from '@xenova/transformers';\n *\n * // Register the adapter\n * useTransformersBackend();\n *\n * // Now use edgeFlow.js pipeline API — inference delegates to transformers.js\n * const classifier = await pipeline('text-classification', {\n *   model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n * });\n *\n * // edgeFlow.js handles scheduling, batching, memory, caching\n * const results = await classifier.runBatch(thousandsOfTexts);\n * ```\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, Tensor } from '../core/types.js';\n/**\n * Minimal interface for a transformers.js pipeline instance.\n * We avoid importing @xenova/transformers directly so edgeFlow.js\n * does not add it as a hard dependency.\n */\ninterface TransformersPipelineInstance {\n    (input: unknown, options?: unknown): Promise<unknown>;\n    dispose?: () => Promise<void> | void;\n}\n/**\n * A factory that creates a transformers.js pipeline.\n * Users pass this so we don't hard-depend on the library.\n */\nexport type TransformersPipelineFactory = (task: string, model?: string, options?: Record<string, unknown>) => Promise<TransformersPipelineInstance>;\n/**\n * Options for configuring the transformers.js adapter.\n */\nexport interface TransformersAdapterOptions {\n    /** The pipeline factory from transformers.js (e.g. the `pipeline` function) */\n    pipelineFactory: TransformersPipelineFactory;\n    /** Default device ('webgpu' | 'wasm' | 'cpu') — passed to transformers.js */\n    device?: string;\n    /** Default dtype ('fp32' | 'fp16' | 'q8' | 'q4') */\n    dtype?: string;\n    /** Cache directory (browser IndexedDB path) */\n    cacheDir?: string;\n}\nexport declare class TransformersAdapterRuntime implements Runtime {\n    readonly name: RuntimeType;\n    get capabilities(): RuntimeCapabilities;\n    isAvailable(): Promise<boolean>;\n    initialize(): Promise<void>;\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /**\n     * Load a transformers.js pipeline by task + model name\n     * (called by the higher-level adapter pipeline, not via the\n     * standard loadModel path).\n     */\n    loadPipeline(task: string, model: string, pipelineOptions?: Record<string, unknown>): Promise<string>;\n    /**\n     * Run inference by passing the raw input to the transformers.js pipeline.\n     * The result is returned as a single EdgeFlowTensor wrapping the JSON-encoded output\n     * (since transformers.js returns task-specific objects, not raw tensors).\n     */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * High-level: run the transformers.js pipeline directly with arbitrary input.\n     * Returns the raw result object (not a tensor).\n     */\n    runDirect(modelId: string, input: unknown, options?: Record<string, unknown>): Promise<unknown>;\n    dispose(): void;\n}\n/**\n * Register the transformers.js adapter as the default inference backend.\n *\n * @example\n * ```typescript\n * import { pipeline } from '@xenova/transformers';\n * import { useTransformersBackend } from 'edgeflowjs';\n *\n * useTransformersBackend({\n *   pipelineFactory: pipeline,\n *   device: 'webgpu',\n *   dtype: 'fp16',\n * });\n * ```\n */\nexport declare function useTransformersBackend(options: TransformersAdapterOptions): void;\n/**\n * Get the adapter runtime instance (for advanced use).\n */\nexport declare function getTransformersAdapter(): TransformersAdapterRuntime | null;\nexport {};\n//# sourceMappingURL=transformers-adapter.d.ts.map"
  },
  {
    "path": "dist/backends/transformers-adapter.js",
    "content": "/**\n * edgeFlow.js - transformers.js Adapter Backend\n *\n * Wraps transformers.js (by Hugging Face) as an inference backend, giving\n * users access to 1000+ HuggingFace models while adding edgeFlow.js's\n * orchestration layer (scheduling, caching, memory management, workers).\n *\n * @example\n * ```typescript\n * import { useTransformersBackend } from 'edgeflowjs';\n * import { pipeline as tfPipeline } from '@xenova/transformers';\n *\n * // Register the adapter\n * useTransformersBackend();\n *\n * // Now use edgeFlow.js pipeline API — inference delegates to transformers.js\n * const classifier = await pipeline('text-classification', {\n *   model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n * });\n *\n * // edgeFlow.js handles scheduling, batching, memory, caching\n * const results = await classifier.runBatch(thousandsOfTexts);\n * ```\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\nimport { registerRuntime } from '../core/runtime.js';\n// ---------------------------------------------------------------------------\n// Session store: maps model IDs to transformers.js pipeline instances\n// ---------------------------------------------------------------------------\nconst sessionStore = new Map();\nlet adapterOptions = null;\n// ---------------------------------------------------------------------------\n// Runtime implementation\n// ---------------------------------------------------------------------------\nexport class TransformersAdapterRuntime {\n    name = 'wasm'; // registers under the wasm slot\n    get capabilities() {\n        return {\n            concurrency: true,\n            quantization: true,\n            float16: true,\n            dynamicShapes: true,\n            maxBatchSize: 128,\n            availableMemory: 1024 * 1024 * 1024,\n        };\n    }\n    async isAvailable() {\n        return adapterOptions?.pipelineFactory != null;\n    }\n    async initialize() {\n        if (!adapterOptions?.pipelineFactory) {\n            throw new EdgeFlowError('TransformersAdapterRuntime requires a pipelineFactory. ' +\n                'Call useTransformersBackend({ pipelineFactory }) first.', ErrorCodes.RUNTIME_INIT_FAILED);\n        }\n    }\n    async loadModel(modelData, options = {}) {\n        // modelData is unused — transformers.js downloads its own models.\n        // Instead the model identifier comes via metadata.name or the URL.\n        const modelName = options.metadata?.name ?? 'default';\n        const metadata = {\n            name: modelName,\n            version: '1.0.0',\n            inputs: [{ name: 'input', dtype: 'float32', shape: [-1] }],\n            outputs: [{ name: 'output', dtype: 'float32', shape: [-1] }],\n            sizeBytes: modelData.byteLength || 0,\n            quantization: options.quantization ?? 'float32',\n            format: 'onnx',\n        };\n        const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n        const model = new LoadedModelImpl(metadata, this.name, () => {\n            const session = sessionStore.get(modelId);\n            if (session?.instance.dispose) {\n                session.instance.dispose();\n            }\n            sessionStore.delete(modelId);\n        });\n        getMemoryManager().trackModel(model, () => model.dispose());\n        return model;\n    }\n    /**\n     * Load a transformers.js pipeline by task + model name\n     * (called by the higher-level adapter pipeline, not via the\n     * standard loadModel path).\n     */\n    async loadPipeline(task, model, pipelineOptions) {\n        if (!adapterOptions?.pipelineFactory) {\n            throw new EdgeFlowError('Adapter not initialised', ErrorCodes.RUNTIME_NOT_INITIALIZED);\n        }\n        const opts = { ...pipelineOptions };\n        if (adapterOptions.device)\n            opts['device'] = adapterOptions.device;\n        if (adapterOptions.dtype)\n            opts['dtype'] = adapterOptions.dtype;\n        const instance = await adapterOptions.pipelineFactory(task, model, opts);\n        const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n        sessionStore.set(modelId, { instance, task, model });\n        return modelId;\n    }\n    /**\n     * Run inference by passing the raw input to the transformers.js pipeline.\n     * The result is returned as a single EdgeFlowTensor wrapping the JSON-encoded output\n     * (since transformers.js returns task-specific objects, not raw tensors).\n     */\n    async run(model, inputs) {\n        const session = sessionStore.get(model.id);\n        if (!session) {\n            throw new EdgeFlowError(`No transformers.js session for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED);\n        }\n        // Reconstruct input from tensor (simple: use the float data as-is)\n        const inputData = inputs[0]?.toFloat32Array() ?? new Float32Array(0);\n        const result = await session.instance(inputData);\n        // Wrap the result in a tensor — downstream pipelines can interpret it\n        const resultArray = Array.isArray(result)\n            ? new Float32Array(result.flat(Infinity))\n            : new Float32Array([0]);\n        return [new EdgeFlowTensor(resultArray, [resultArray.length], 'float32')];\n    }\n    /**\n     * High-level: run the transformers.js pipeline directly with arbitrary input.\n     * Returns the raw result object (not a tensor).\n     */\n    async runDirect(modelId, input, options) {\n        const session = sessionStore.get(modelId);\n        if (!session) {\n            throw new EdgeFlowError(`No transformers.js session for model ${modelId}`, ErrorCodes.MODEL_NOT_LOADED);\n        }\n        return session.instance(input, options);\n    }\n    dispose() {\n        for (const [id, session] of sessionStore) {\n            if (session.instance.dispose) {\n                session.instance.dispose();\n            }\n            sessionStore.delete(id);\n        }\n    }\n}\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\nlet adapterRuntime = null;\n/**\n * Register the transformers.js adapter as the default inference backend.\n *\n * @example\n * ```typescript\n * import { pipeline } from '@xenova/transformers';\n * import { useTransformersBackend } from 'edgeflowjs';\n *\n * useTransformersBackend({\n *   pipelineFactory: pipeline,\n *   device: 'webgpu',\n *   dtype: 'fp16',\n * });\n * ```\n */\nexport function useTransformersBackend(options) {\n    adapterOptions = options;\n    adapterRuntime = new TransformersAdapterRuntime();\n    registerRuntime('wasm', () => adapterRuntime);\n}\n/**\n * Get the adapter runtime instance (for advanced use).\n */\nexport function getTransformersAdapter() {\n    return adapterRuntime;\n}\n//# sourceMappingURL=transformers-adapter.js.map"
  },
  {
    "path": "dist/backends/wasm.d.ts",
    "content": "/**\n * edgeFlow.js - WebAssembly Backend\n *\n * Pure WASM runtime for universal browser support.\n * Features:\n * - Universal compatibility\n * - SIMD acceleration when available\n * - Memory-efficient execution\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, Tensor } from '../core/types.js';\n/**\n * WASMRuntime - Pure WebAssembly inference runtime\n */\nexport declare class WASMRuntime implements Runtime {\n    readonly name: RuntimeType;\n    private module;\n    private simdSupported;\n    private models;\n    private initialized;\n    get capabilities(): RuntimeCapabilities;\n    /**\n     * Check if WASM is available\n     */\n    isAvailable(): Promise<boolean>;\n    /**\n     * Initialize the WASM runtime\n     */\n    initialize(): Promise<void>;\n    /**\n     * Check SIMD support\n     */\n    private checkSIMDSupport;\n    /**\n     * Create JavaScript fallback for WASM operations\n     */\n    private createJSFallback;\n    /**\n     * Load a model\n     */\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /**\n     * Run inference\n     */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Execute model\n     */\n    private executeModel;\n    /**\n     * Parse model configuration\n     */\n    private parseModelConfig;\n    /**\n     * Load weights into WASM memory\n     */\n    private loadWeights;\n    /**\n     * Unload a model\n     */\n    private unloadModel;\n    /**\n     * Ensure runtime is initialized\n     */\n    private ensureInitialized;\n    /**\n     * Check if SIMD is supported\n     */\n    hasSIMDSupport(): boolean;\n    /**\n     * Dispose the runtime\n     */\n    dispose(): void;\n}\n/**\n * Create WASM runtime factory\n */\nexport declare function createWASMRuntime(): Runtime;\n//# sourceMappingURL=wasm.d.ts.map"
  },
  {
    "path": "dist/backends/wasm.js",
    "content": "/**\n * edgeFlow.js - WebAssembly Backend\n *\n * Pure WASM runtime for universal browser support.\n * Features:\n * - Universal compatibility\n * - SIMD acceleration when available\n * - Memory-efficient execution\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor, softmax as tensorSoftmax, relu as tensorRelu, sigmoid as tensorSigmoid } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n// ============================================================================\n// WASM Runtime Implementation\n// ============================================================================\n/**\n * WASMRuntime - Pure WebAssembly inference runtime\n */\nexport class WASMRuntime {\n    name = 'wasm';\n    module = null;\n    simdSupported = false;\n    models = new Map();\n    initialized = false;\n    get capabilities() {\n        return {\n            concurrency: false, // WASM is single-threaded by default\n            quantization: true,\n            float16: false,\n            dynamicShapes: true,\n            maxBatchSize: 16,\n            availableMemory: 128 * 1024 * 1024, // 128MB default\n        };\n    }\n    /**\n     * Check if WASM is available\n     */\n    async isAvailable() {\n        if (typeof WebAssembly === 'undefined')\n            return false;\n        try {\n            // Check if we can instantiate a minimal WASM module\n            const bytes = new Uint8Array([\n                0x00, 0x61, 0x73, 0x6d, // Magic number\n                0x01, 0x00, 0x00, 0x00, // Version\n            ]);\n            await WebAssembly.instantiate(bytes);\n            return true;\n        }\n        catch {\n            return false;\n        }\n    }\n    /**\n     * Initialize the WASM runtime\n     */\n    async initialize() {\n        if (this.initialized)\n            return;\n        // Check SIMD support\n        this.simdSupported = await this.checkSIMDSupport();\n        // Create memory pool\n        const memory = new WebAssembly.Memory({\n            initial: 256, // 16MB initial\n            maximum: 2048, // 128MB maximum\n        });\n        // Compile and instantiate the WASM module\n        // In production, this would load an actual WASM binary\n        // For now, we use a pure JS fallback\n        this.module = {\n            memory,\n            exports: this.createJSFallback(memory),\n        };\n        this.initialized = true;\n    }\n    /**\n     * Check SIMD support\n     */\n    async checkSIMDSupport() {\n        try {\n            // SIMD detection via feature detection\n            const simdTest = new Uint8Array([\n                0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n                0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, 0x03,\n                0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00,\n                0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0b\n            ]);\n            await WebAssembly.instantiate(simdTest);\n            return true;\n        }\n        catch {\n            return false;\n        }\n    }\n    /**\n     * Create JavaScript fallback for WASM operations\n     */\n    createJSFallback(memory) {\n        let nextPtr = 0;\n        const allocations = new Map();\n        return {\n            malloc: (size) => {\n                const ptr = nextPtr;\n                nextPtr += size;\n                allocations.set(ptr, size);\n                return ptr;\n            },\n            free: (ptr) => {\n                allocations.delete(ptr);\n            },\n            matmul_f32: (aPtr, aRows, aCols, bPtr, _bRows, bCols, outPtr) => {\n                const view = new Float32Array(memory.buffer);\n                const aOffset = aPtr / 4;\n                const bOffset = bPtr / 4;\n                const outOffset = outPtr / 4;\n                for (let i = 0; i < aRows; i++) {\n                    for (let j = 0; j < bCols; j++) {\n                        let sum = 0;\n                        for (let k = 0; k < aCols; k++) {\n                            sum += (view[aOffset + i * aCols + k] ?? 0) * (view[bOffset + k * bCols + j] ?? 0);\n                        }\n                        view[outOffset + i * bCols + j] = sum;\n                    }\n                }\n            },\n            add_f32: (aPtr, bPtr, outPtr, size) => {\n                const view = new Float32Array(memory.buffer);\n                const aOffset = aPtr / 4;\n                const bOffset = bPtr / 4;\n                const outOffset = outPtr / 4;\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = (view[aOffset + i] ?? 0) + (view[bOffset + i] ?? 0);\n                }\n            },\n            mul_f32: (aPtr, bPtr, outPtr, size) => {\n                const view = new Float32Array(memory.buffer);\n                const aOffset = aPtr / 4;\n                const bOffset = bPtr / 4;\n                const outOffset = outPtr / 4;\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = (view[aOffset + i] ?? 0) * (view[bOffset + i] ?? 0);\n                }\n            },\n            relu_f32: (inputPtr, outputPtr, size) => {\n                const view = new Float32Array(memory.buffer);\n                const inOffset = inputPtr / 4;\n                const outOffset = outputPtr / 4;\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = Math.max(0, view[inOffset + i] ?? 0);\n                }\n            },\n            sigmoid_f32: (inputPtr, outputPtr, size) => {\n                const view = new Float32Array(memory.buffer);\n                const inOffset = inputPtr / 4;\n                const outOffset = outputPtr / 4;\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = 1 / (1 + Math.exp(-(view[inOffset + i] ?? 0)));\n                }\n            },\n            softmax_f32: (inputPtr, outputPtr, size) => {\n                const view = new Float32Array(memory.buffer);\n                const inOffset = inputPtr / 4;\n                const outOffset = outputPtr / 4;\n                // Find max for numerical stability\n                let max = -Infinity;\n                for (let i = 0; i < size; i++) {\n                    if ((view[inOffset + i] ?? 0) > max)\n                        max = view[inOffset + i] ?? 0;\n                }\n                // Compute exp and sum\n                let sum = 0;\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = Math.exp((view[inOffset + i] ?? 0) - max);\n                    sum += view[outOffset + i] ?? 0;\n                }\n                // Normalize\n                for (let i = 0; i < size; i++) {\n                    view[outOffset + i] = (view[outOffset + i] ?? 0) / sum;\n                }\n            },\n        };\n    }\n    /**\n     * Load a model\n     */\n    async loadModel(modelData, options = {}) {\n        this.ensureInitialized();\n        // Parse model configuration\n        const config = this.parseModelConfig(modelData);\n        // Extract and store weights\n        const wasmData = {\n            weights: new Map(),\n            config,\n            executionOrder: config.layers.map(l => l.name),\n        };\n        // Load weights into memory\n        await this.loadWeights(modelData, wasmData);\n        const modelId = `wasm_${Date.now().toString(36)}`;\n        this.models.set(modelId, wasmData);\n        // Create metadata\n        const metadata = {\n            name: config.name || options.metadata?.name || 'unknown',\n            version: config.version || '1.0.0',\n            inputs: config.inputs.map(i => ({\n                name: i.name,\n                dtype: i.dtype,\n                shape: i.shape,\n            })),\n            outputs: config.outputs.map(o => ({\n                name: o.name,\n                dtype: o.dtype,\n                shape: o.shape,\n            })),\n            sizeBytes: modelData.byteLength,\n            quantization: options.quantization ?? 'float32',\n            format: 'edgeflow',\n        };\n        // Create model instance\n        const model = new LoadedModelImpl(metadata, 'wasm', () => this.unloadModel(modelId));\n        // Track in memory manager\n        getMemoryManager().trackModel(model, () => model.dispose());\n        return model;\n    }\n    /**\n     * Run inference\n     */\n    async run(model, inputs) {\n        this.ensureInitialized();\n        // Execute model layers\n        return this.executeModel(inputs, model.metadata);\n    }\n    /**\n     * Execute model\n     */\n    async executeModel(inputs, metadata) {\n        const outputs = [];\n        for (const outputSpec of metadata.outputs) {\n            const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n            // Process based on output requirements\n            // This is a simplified implementation\n            let outputTensor;\n            if (inputs.length > 0 && inputs[0]) {\n                const inputTensor = inputs[0];\n                // Apply transformations based on layer types\n                // For demo, apply softmax to classification outputs\n                if (outputSpec.name.includes('logits') || outputSpec.name.includes('class')) {\n                    outputTensor = tensorSoftmax(inputTensor);\n                }\n                else if (outputSpec.name.includes('relu')) {\n                    outputTensor = tensorRelu(inputTensor);\n                }\n                else if (outputSpec.name.includes('sigmoid')) {\n                    outputTensor = tensorSigmoid(inputTensor);\n                }\n                else {\n                    // Identity or feature extraction\n                    const outputData = new Float32Array(outputSize);\n                    const inputData = inputTensor.toFloat32Array();\n                    for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n                        outputData[i] = inputData[i] ?? 0;\n                    }\n                    outputTensor = new EdgeFlowTensor(outputData, outputSpec.shape, 'float32');\n                }\n            }\n            else {\n                outputTensor = new EdgeFlowTensor(new Float32Array(outputSize), outputSpec.shape, 'float32');\n            }\n            outputs.push(outputTensor);\n        }\n        return outputs;\n    }\n    /**\n     * Parse model configuration\n     */\n    parseModelConfig(data) {\n        try {\n            const decoder = new TextDecoder();\n            const text = decoder.decode(new Uint8Array(data, 0, Math.min(2048, data.byteLength)));\n            if (text.trim().startsWith('{')) {\n                let jsonEnd = text.indexOf('\\n---\\n');\n                if (jsonEnd === -1) {\n                    // Try to parse as pure JSON\n                    try {\n                        return JSON.parse(text);\n                    }\n                    catch {\n                        jsonEnd = data.byteLength;\n                    }\n                }\n                const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n                return JSON.parse(jsonStr);\n            }\n        }\n        catch {\n            // Not JSON format\n        }\n        return {\n            name: 'unknown',\n            version: '1.0.0',\n            layers: [],\n            inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n            outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n        };\n    }\n    /**\n     * Load weights into WASM memory\n     */\n    async loadWeights(_modelData, _wasmData) {\n        // In a full implementation, extract and load weights\n        // This is a placeholder\n    }\n    /**\n     * Unload a model\n     */\n    unloadModel(modelId) {\n        const modelData = this.models.get(modelId);\n        if (modelData && this.module) {\n            // Free weight buffers\n            for (const weight of modelData.weights.values()) {\n                this.module.exports.free(weight.ptr);\n            }\n        }\n        this.models.delete(modelId);\n    }\n    /**\n     * Ensure runtime is initialized\n     */\n    ensureInitialized() {\n        if (!this.initialized || !this.module) {\n            throw new EdgeFlowError('WASM runtime is not initialized', ErrorCodes.RUNTIME_NOT_INITIALIZED);\n        }\n    }\n    /**\n     * Check if SIMD is supported\n     */\n    hasSIMDSupport() {\n        return this.simdSupported;\n    }\n    /**\n     * Dispose the runtime\n     */\n    dispose() {\n        // Free all model weights\n        for (const modelId of this.models.keys()) {\n            this.unloadModel(modelId);\n        }\n        this.module = null;\n        this.initialized = false;\n    }\n}\n/**\n * Create WASM runtime factory\n */\nexport function createWASMRuntime() {\n    return new WASMRuntime();\n}\n//# sourceMappingURL=wasm.js.map"
  },
  {
    "path": "dist/backends/webgpu.d.ts",
    "content": "/**\n * edgeFlow.js - WebGPU Backend\n *\n * **Status: Planned** - This is a skeleton implementation that initializes\n * WebGPU and creates compute pipelines but does not perform real model\n * inference. For GPU-accelerated inference, use the ONNX Runtime backend\n * which supports WebGPU via its execution providers.\n *\n * This backend is intended for future custom WebGPU compute shader\n * implementations that bypass ONNX Runtime for specialized ops.\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, Tensor } from '../core/types.js';\ndeclare global {\n    interface Navigator {\n        gpu?: GPU;\n    }\n    interface GPU {\n        requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null>;\n    }\n    interface GPURequestAdapterOptions {\n        powerPreference?: 'low-power' | 'high-performance';\n    }\n    interface GPUAdapter {\n        requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice>;\n    }\n    interface GPUDeviceDescriptor {\n        requiredFeatures?: string[];\n        requiredLimits?: Record<string, number>;\n    }\n    interface GPUDevice {\n        limits: GPULimits;\n        lost: Promise<GPUDeviceLostInfo>;\n        createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer;\n        createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule;\n        createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout;\n        createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout;\n        createComputePipeline(descriptor: GPUComputePipelineDescriptor): GPUComputePipeline;\n        destroy(): void;\n    }\n    interface GPULimits {\n        maxBufferSize: number;\n    }\n    interface GPUDeviceLostInfo {\n        message: string;\n        reason: string;\n    }\n    interface GPUBuffer {\n        destroy(): void;\n    }\n    interface GPUShaderModule {\n    }\n    interface GPUBindGroupLayout {\n    }\n    interface GPUPipelineLayout {\n    }\n    interface GPUComputePipeline {\n    }\n    interface GPUBufferDescriptor {\n        size: number;\n        usage: number;\n    }\n    interface GPUShaderModuleDescriptor {\n        code: string;\n    }\n    interface GPUBindGroupLayoutDescriptor {\n        entries: GPUBindGroupLayoutEntry[];\n    }\n    interface GPUBindGroupLayoutEntry {\n        binding: number;\n        visibility: number;\n        buffer?: {\n            type: string;\n        };\n    }\n    interface GPUPipelineLayoutDescriptor {\n        bindGroupLayouts: GPUBindGroupLayout[];\n    }\n    interface GPUComputePipelineDescriptor {\n        layout: GPUPipelineLayout;\n        compute: {\n            module: GPUShaderModule;\n            entryPoint: string;\n        };\n    }\n}\n/**\n * WebGPURuntime - GPU-accelerated inference runtime\n */\nexport declare class WebGPURuntime implements Runtime {\n    readonly name: RuntimeType;\n    private adapter;\n    private device;\n    private models;\n    private initialized;\n    get capabilities(): RuntimeCapabilities;\n    /**\n     * Check if WebGPU is available\n     */\n    isAvailable(): Promise<boolean>;\n    /**\n     * Initialize the WebGPU runtime\n     */\n    initialize(): Promise<void>;\n    /**\n     * Load a model\n     */\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /**\n     * Run inference\n     */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Execute model (simplified implementation)\n     */\n    private executeModel;\n    /**\n     * Parse model data\n     */\n    private parseModelData;\n    /**\n     * Upload weights to GPU\n     */\n    private uploadWeights;\n    /**\n     * Create compute pipelines\n     */\n    private createPipelines;\n    /**\n     * Unload a model\n     */\n    private unloadModel;\n    /**\n     * Ensure runtime is initialized\n     */\n    private ensureInitialized;\n    /**\n     * Dispose the runtime\n     */\n    dispose(): void;\n}\n/**\n * Create WebGPU runtime factory\n */\nexport declare function createWebGPURuntime(): Runtime;\n//# sourceMappingURL=webgpu.d.ts.map"
  },
  {
    "path": "dist/backends/webgpu.js",
    "content": "/**\n * edgeFlow.js - WebGPU Backend\n *\n * **Status: Planned** - This is a skeleton implementation that initializes\n * WebGPU and creates compute pipelines but does not perform real model\n * inference. For GPU-accelerated inference, use the ONNX Runtime backend\n * which supports WebGPU via its execution providers.\n *\n * This backend is intended for future custom WebGPU compute shader\n * implementations that bypass ONNX Runtime for specialized ops.\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n// WebGPU constants\nconst GPUBufferUsage = {\n    STORAGE: 0x0080,\n    COPY_SRC: 0x0004,\n    COPY_DST: 0x0008,\n    MAP_READ: 0x0001,\n};\nconst GPUShaderStage = {\n    COMPUTE: 0x0004,\n};\n// ============================================================================\n// WebGPU Runtime Implementation\n// ============================================================================\n/**\n * WebGPURuntime - GPU-accelerated inference runtime\n */\nexport class WebGPURuntime {\n    name = 'webgpu';\n    adapter = null;\n    device = null;\n    models = new Map();\n    initialized = false;\n    get capabilities() {\n        return {\n            concurrency: true,\n            quantization: true,\n            float16: true,\n            dynamicShapes: false,\n            maxBatchSize: 64,\n            availableMemory: this.device?.limits.maxBufferSize ?? 256 * 1024 * 1024,\n        };\n    }\n    /**\n     * Check if WebGPU is available\n     */\n    async isAvailable() {\n        if (typeof navigator === 'undefined')\n            return false;\n        if (!navigator.gpu)\n            return false;\n        try {\n            const adapter = await navigator.gpu.requestAdapter();\n            return adapter !== null;\n        }\n        catch {\n            return false;\n        }\n    }\n    /**\n     * Initialize the WebGPU runtime\n     */\n    async initialize() {\n        if (this.initialized)\n            return;\n        if (!navigator.gpu) {\n            throw new EdgeFlowError('WebGPU is not supported in this browser', ErrorCodes.RUNTIME_NOT_AVAILABLE);\n        }\n        // Request adapter\n        this.adapter = await navigator.gpu.requestAdapter({\n            powerPreference: 'high-performance',\n        });\n        if (!this.adapter) {\n            throw new EdgeFlowError('Failed to get WebGPU adapter', ErrorCodes.RUNTIME_INIT_FAILED);\n        }\n        // Request device\n        this.device = await this.adapter.requestDevice({\n            requiredFeatures: [],\n            requiredLimits: {},\n        });\n        // Handle device loss\n        this.device.lost.then((info) => {\n            console.error('WebGPU device was lost:', info.message);\n            this.initialized = false;\n            this.device = null;\n        });\n        this.initialized = true;\n    }\n    /**\n     * Load a model\n     */\n    async loadModel(modelData, options = {}) {\n        this.ensureInitialized();\n        // Parse model data\n        const config = this.parseModelData(modelData);\n        // Create shader modules and pipelines\n        const webgpuData = {\n            shaders: new Map(),\n            pipelines: new Map(),\n            weights: new Map(),\n            bindGroupLayouts: [],\n            config,\n        };\n        // Extract and upload weights\n        await this.uploadWeights(modelData, webgpuData);\n        // Create compute pipelines for each layer\n        await this.createPipelines(webgpuData);\n        // Generate model ID\n        const modelId = `webgpu_${Date.now().toString(36)}`;\n        this.models.set(modelId, webgpuData);\n        // Create metadata\n        const metadata = {\n            name: config.name || options.metadata?.name || 'unknown',\n            version: config.version,\n            inputs: config.inputs.map(i => ({\n                name: i.name,\n                dtype: i.dtype,\n                shape: i.shape,\n            })),\n            outputs: config.outputs.map(o => ({\n                name: o.name,\n                dtype: o.dtype,\n                shape: o.shape,\n            })),\n            sizeBytes: modelData.byteLength,\n            quantization: options.quantization ?? 'float32',\n            format: 'edgeflow',\n        };\n        // Create model instance\n        const model = new LoadedModelImpl(metadata, 'webgpu', () => this.unloadModel(modelId));\n        // Track in memory manager\n        getMemoryManager().trackModel(model, () => model.dispose());\n        return model;\n    }\n    /**\n     * Run inference\n     */\n    async run(model, inputs) {\n        this.ensureInitialized();\n        // For now, use a simple fallback implementation\n        // In a full implementation, this would execute the compute pipelines\n        return this.executeModel(inputs, model.metadata);\n    }\n    /**\n     * Execute model (simplified implementation)\n     */\n    async executeModel(inputs, metadata) {\n        // This is a simplified implementation\n        // A full implementation would:\n        // 1. Upload input tensors to GPU buffers\n        // 2. Execute compute pipelines in topological order\n        // 3. Read back output tensors\n        const device = this.device;\n        const outputs = [];\n        for (const outputSpec of metadata.outputs) {\n            // Create output buffer\n            const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n            const outputBuffer = device.createBuffer({\n                size: outputSize * 4, // float32\n                usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n            });\n            // Create staging buffer for readback\n            const stagingBuffer = device.createBuffer({\n                size: outputSize * 4,\n                usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n            });\n            // For now, return zeros (placeholder)\n            // In production, execute actual compute pipelines\n            const outputData = new Float32Array(outputSize);\n            // Simulate some computation based on inputs\n            if (inputs.length > 0 && inputs[0]) {\n                const inputData = inputs[0].toFloat32Array();\n                for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n                    outputData[i] = (inputData[i] ?? 0);\n                }\n            }\n            outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, 'float32'));\n            // Cleanup\n            outputBuffer.destroy();\n            stagingBuffer.destroy();\n        }\n        return outputs;\n    }\n    /**\n     * Parse model data\n     */\n    parseModelData(data) {\n        // Try to parse as JSON first (for our custom format)\n        try {\n            const decoder = new TextDecoder();\n            const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n            // Check if it starts with JSON\n            if (text.trim().startsWith('{')) {\n                // Find the JSON header end\n                let jsonEnd = text.indexOf('\\n---\\n');\n                if (jsonEnd === -1)\n                    jsonEnd = data.byteLength;\n                const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n                return JSON.parse(jsonStr);\n            }\n        }\n        catch {\n            // Not JSON format\n        }\n        // Return default config for unknown formats\n        return {\n            name: 'unknown',\n            version: '1.0.0',\n            layers: [],\n            inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n            outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n        };\n    }\n    /**\n     * Upload weights to GPU\n     */\n    async uploadWeights(_data, modelData) {\n        const device = this.device;\n        // In a full implementation, parse weight data from the model file\n        // and upload to GPU buffers\n        // Placeholder: create empty weight buffer\n        const weightsBuffer = device.createBuffer({\n            size: 1024,\n            usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n        });\n        modelData.weights.set('default', weightsBuffer);\n    }\n    /**\n     * Create compute pipelines\n     */\n    async createPipelines(modelData) {\n        const device = this.device;\n        // Create a general-purpose compute shader\n        const shaderCode = /* wgsl */ `\n      @group(0) @binding(0) var<storage, read> input: array<f32>;\n      @group(0) @binding(1) var<storage, read_write> output: array<f32>;\n      \n      @compute @workgroup_size(64)\n      fn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n        let idx = gid.x;\n        if (idx < arrayLength(&input)) {\n          output[idx] = input[idx];\n        }\n      }\n    `;\n        const shaderModule = device.createShaderModule({\n            code: shaderCode,\n        });\n        modelData.shaders.set('default', shaderModule);\n        // Create bind group layout\n        const bindGroupLayout = device.createBindGroupLayout({\n            entries: [\n                {\n                    binding: 0,\n                    visibility: GPUShaderStage.COMPUTE,\n                    buffer: { type: 'read-only-storage' },\n                },\n                {\n                    binding: 1,\n                    visibility: GPUShaderStage.COMPUTE,\n                    buffer: { type: 'storage' },\n                },\n            ],\n        });\n        modelData.bindGroupLayouts.push(bindGroupLayout);\n        // Create pipeline layout\n        const pipelineLayout = device.createPipelineLayout({\n            bindGroupLayouts: [bindGroupLayout],\n        });\n        // Create compute pipeline\n        const pipeline = device.createComputePipeline({\n            layout: pipelineLayout,\n            compute: {\n                module: shaderModule,\n                entryPoint: 'main',\n            },\n        });\n        modelData.pipelines.set('default', pipeline);\n    }\n    /**\n     * Unload a model\n     */\n    unloadModel(modelId) {\n        const modelData = this.models.get(modelId);\n        if (modelData) {\n            // Destroy GPU buffers\n            for (const buffer of modelData.weights.values()) {\n                buffer.destroy();\n            }\n            this.models.delete(modelId);\n        }\n    }\n    /**\n     * Ensure runtime is initialized\n     */\n    ensureInitialized() {\n        if (!this.initialized || !this.device) {\n            throw new EdgeFlowError('WebGPU runtime is not initialized', ErrorCodes.RUNTIME_NOT_INITIALIZED);\n        }\n    }\n    /**\n     * Dispose the runtime\n     */\n    dispose() {\n        // Unload all models\n        for (const modelId of this.models.keys()) {\n            this.unloadModel(modelId);\n        }\n        // Destroy device\n        if (this.device) {\n            this.device.destroy();\n            this.device = null;\n        }\n        this.adapter = null;\n        this.initialized = false;\n    }\n}\n/**\n * Create WebGPU runtime factory\n */\nexport function createWebGPURuntime() {\n    return new WebGPURuntime();\n}\n//# sourceMappingURL=webgpu.js.map"
  },
  {
    "path": "dist/backends/webnn.d.ts",
    "content": "/**\n * edgeFlow.js - WebNN Backend\n *\n * **Status: Planned** - This is a skeleton implementation that initializes\n * a WebNN context but does not perform real model inference or graph building.\n * For hardware-accelerated inference, use the ONNX Runtime backend which\n * supports WebNN via its execution providers when available.\n *\n * This backend is intended for future native WebNN graph building support.\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, Tensor } from '../core/types.js';\n/**\n * WebNN context type\n */\ntype MLContextType = 'default' | 'gpu' | 'cpu' | 'npu';\n/**\n * WebNN operand descriptor\n */\ninterface MLOperandDescriptor {\n    dataType: 'float32' | 'float16' | 'int32' | 'uint32' | 'int8' | 'uint8';\n    dimensions: number[];\n}\n/**\n * WebNN context options\n */\ninterface MLContextOptions {\n    deviceType?: MLContextType;\n    powerPreference?: 'default' | 'high-performance' | 'low-power';\n}\ndeclare global {\n    interface Navigator {\n        ml?: {\n            createContext(options?: MLContextOptions): Promise<MLContext>;\n        };\n    }\n    interface MLContext {\n        compute(graph: MLGraph, inputs: Record<string, ArrayBufferView>, outputs: Record<string, ArrayBufferView>): Promise<Record<string, ArrayBufferView>>;\n    }\n    interface MLGraph {\n    }\n    interface MLGraphBuilder {\n        input(name: string, desc: MLOperandDescriptor): MLOperand;\n        constant(desc: MLOperandDescriptor, data: ArrayBufferView): MLOperand;\n        build(outputs: Record<string, MLOperand>): Promise<MLGraph>;\n        add(a: MLOperand, b: MLOperand): MLOperand;\n        sub(a: MLOperand, b: MLOperand): MLOperand;\n        mul(a: MLOperand, b: MLOperand): MLOperand;\n        div(a: MLOperand, b: MLOperand): MLOperand;\n        matmul(a: MLOperand, b: MLOperand): MLOperand;\n        relu(x: MLOperand): MLOperand;\n        sigmoid(x: MLOperand): MLOperand;\n        tanh(x: MLOperand): MLOperand;\n        softmax(x: MLOperand): MLOperand;\n        reshape(x: MLOperand, newShape: number[]): MLOperand;\n        transpose(x: MLOperand, permutation?: number[]): MLOperand;\n    }\n    interface MLOperand {\n    }\n}\n/**\n * WebNNRuntime - Browser-native neural network runtime\n */\nexport declare class WebNNRuntime implements Runtime {\n    readonly name: RuntimeType;\n    private context;\n    private models;\n    private initialized;\n    private deviceType;\n    get capabilities(): RuntimeCapabilities;\n    /**\n     * Check if WebNN is available\n     */\n    isAvailable(): Promise<boolean>;\n    /**\n     * Initialize the WebNN runtime\n     */\n    initialize(): Promise<void>;\n    /**\n     * Load a model\n     */\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /**\n     * Run inference\n     */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Execute model (simplified implementation)\n     */\n    private executeModel;\n    /**\n     * Parse model configuration\n     */\n    private parseModelConfig;\n    /**\n     * Unload a model\n     */\n    private unloadModel;\n    /**\n     * Ensure runtime is initialized\n     */\n    private ensureInitialized;\n    /**\n     * Get device type\n     */\n    getDeviceType(): MLContextType;\n    /**\n     * Dispose the runtime\n     */\n    dispose(): void;\n}\n/**\n * Create WebNN runtime factory\n */\nexport declare function createWebNNRuntime(): Runtime;\nexport {};\n//# sourceMappingURL=webnn.d.ts.map"
  },
  {
    "path": "dist/backends/webnn.js",
    "content": "/**\n * edgeFlow.js - WebNN Backend\n *\n * **Status: Planned** - This is a skeleton implementation that initializes\n * a WebNN context but does not perform real model inference or graph building.\n * For hardware-accelerated inference, use the ONNX Runtime backend which\n * supports WebNN via its execution providers when available.\n *\n * This backend is intended for future native WebNN graph building support.\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n// ============================================================================\n// WebNN Runtime Implementation\n// ============================================================================\n/**\n * WebNNRuntime - Browser-native neural network runtime\n */\nexport class WebNNRuntime {\n    name = 'webnn';\n    context = null;\n    models = new Map();\n    initialized = false;\n    deviceType = 'default';\n    get capabilities() {\n        return {\n            concurrency: true,\n            quantization: true,\n            float16: true,\n            dynamicShapes: false,\n            maxBatchSize: 32,\n            availableMemory: 256 * 1024 * 1024, // Estimated\n        };\n    }\n    /**\n     * Check if WebNN is available\n     */\n    async isAvailable() {\n        if (typeof navigator === 'undefined')\n            return false;\n        if (!navigator.ml)\n            return false;\n        try {\n            const context = await navigator.ml.createContext({ deviceType: 'default' });\n            return context !== null;\n        }\n        catch {\n            return false;\n        }\n    }\n    /**\n     * Initialize the WebNN runtime\n     */\n    async initialize() {\n        if (this.initialized)\n            return;\n        if (!navigator.ml) {\n            throw new EdgeFlowError('WebNN is not supported in this browser', ErrorCodes.RUNTIME_NOT_AVAILABLE);\n        }\n        // Try to get GPU context first, fallback to CPU\n        try {\n            this.context = await navigator.ml.createContext({\n                deviceType: 'gpu',\n                powerPreference: 'high-performance',\n            });\n            this.deviceType = 'gpu';\n        }\n        catch {\n            try {\n                this.context = await navigator.ml.createContext({ deviceType: 'cpu' });\n                this.deviceType = 'cpu';\n            }\n            catch (error) {\n                throw new EdgeFlowError(`Failed to create WebNN context: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.RUNTIME_INIT_FAILED);\n            }\n        }\n        this.initialized = true;\n    }\n    /**\n     * Load a model\n     */\n    async loadModel(modelData, options = {}) {\n        this.ensureInitialized();\n        // Parse model configuration\n        const config = this.parseModelConfig(modelData);\n        // Note: Full WebNN implementation would build the graph here\n        // This is a placeholder that creates minimal metadata\n        const modelId = `webnn_${Date.now().toString(36)}`;\n        // Create metadata\n        const metadata = {\n            name: config.name || options.metadata?.name || 'unknown',\n            version: config.version || '1.0.0',\n            inputs: config.inputs.map(i => ({\n                name: i.name,\n                dtype: i.dtype,\n                shape: i.shape,\n            })),\n            outputs: config.outputs.map(o => ({\n                name: o.name,\n                dtype: o.dtype,\n                shape: o.shape,\n            })),\n            sizeBytes: modelData.byteLength,\n            quantization: options.quantization ?? 'float32',\n            format: 'edgeflow',\n        };\n        // Create model instance\n        const model = new LoadedModelImpl(metadata, 'webnn', () => this.unloadModel(modelId));\n        // Track in memory manager\n        getMemoryManager().trackModel(model, () => model.dispose());\n        return model;\n    }\n    /**\n     * Run inference\n     */\n    async run(model, inputs) {\n        this.ensureInitialized();\n        // Simplified implementation - in production, would use compiled graph\n        return this.executeModel(inputs, model.metadata);\n    }\n    /**\n     * Execute model (simplified implementation)\n     */\n    async executeModel(inputs, metadata) {\n        const outputs = [];\n        // For each expected output\n        for (const outputSpec of metadata.outputs) {\n            const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n            const outputData = new Float32Array(outputSize);\n            // Simple passthrough for demo (real impl would use WebNN compute)\n            if (inputs.length > 0 && inputs[0]) {\n                const inputData = inputs[0].toFloat32Array();\n                for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n                    outputData[i] = inputData[i] ?? 0;\n                }\n            }\n            outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, 'float32'));\n        }\n        return outputs;\n    }\n    /**\n     * Parse model configuration\n     */\n    parseModelConfig(data) {\n        try {\n            const decoder = new TextDecoder();\n            const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n            if (text.trim().startsWith('{')) {\n                let jsonEnd = text.indexOf('\\n---\\n');\n                if (jsonEnd === -1)\n                    jsonEnd = data.byteLength;\n                const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n                return JSON.parse(jsonStr);\n            }\n        }\n        catch {\n            // Not JSON format\n        }\n        return {\n            name: 'unknown',\n            version: '1.0.0',\n            inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n            outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n        };\n    }\n    /**\n     * Unload a model\n     */\n    unloadModel(modelId) {\n        this.models.delete(modelId);\n    }\n    /**\n     * Ensure runtime is initialized\n     */\n    ensureInitialized() {\n        if (!this.initialized || !this.context) {\n            throw new EdgeFlowError('WebNN runtime is not initialized', ErrorCodes.RUNTIME_NOT_INITIALIZED);\n        }\n    }\n    /**\n     * Get device type\n     */\n    getDeviceType() {\n        return this.deviceType;\n    }\n    /**\n     * Dispose the runtime\n     */\n    dispose() {\n        this.models.clear();\n        this.context = null;\n        this.initialized = false;\n    }\n}\n/**\n * Create WebNN runtime factory\n */\nexport function createWebNNRuntime() {\n    return new WebNNRuntime();\n}\n//# sourceMappingURL=webnn.js.map"
  },
  {
    "path": "dist/core/composer.d.ts",
    "content": "/**\n * edgeFlow.js - Pipeline Composer\n *\n * Chain multiple pipelines together to build complex multi-model workflows.\n * Each stage's output is transformed and fed as input to the next stage.\n *\n * @example\n * ```typescript\n * import { compose } from 'edgeflowjs';\n *\n * const speechTranslator = compose([\n *   { task: 'automatic-speech-recognition' },\n *   { task: 'translation', options: { srcLang: 'en', tgtLang: 'zh' } },\n * ]);\n *\n * const result = await speechTranslator.run(audioBlob);\n * // result.stages = [asrResult, translationResult]\n * // result.output  = final translation text\n * ```\n */\nimport { type PipelineFactoryOptions } from '../pipelines/index.js';\nimport type { PipelineTask } from './types.js';\n/**\n * A single stage in a composed pipeline.\n */\nexport interface CompositionStage {\n    /** The pipeline task to run */\n    task: PipelineTask | (string & {});\n    /** Model override for this stage */\n    model?: string;\n    /** Extra options forwarded to `pipeline()` */\n    options?: PipelineFactoryOptions;\n    /**\n     * Optional transform applied to the previous stage's output before it is\n     * passed as input to this stage. If omitted, the raw output is forwarded.\n     */\n    transform?: (previousOutput: unknown) => unknown;\n    /**\n     * Options forwarded to the pipeline's `run()` call.\n     */\n    runOptions?: Record<string, unknown>;\n}\n/**\n * Result from running a composed pipeline.\n */\nexport interface CompositionResult {\n    /** The final output from the last stage */\n    output: unknown;\n    /** Intermediate results for every stage (index-aligned with stages) */\n    stages: unknown[];\n    /** Total wall-clock time in milliseconds */\n    totalTime: number;\n    /** Per-stage timing */\n    stageTimes: number[];\n}\n/**\n * A composed (chained) pipeline.\n */\nexport interface ComposedPipeline {\n    /** Execute the full chain with the given initial input */\n    run(input: unknown): Promise<CompositionResult>;\n    /** Dispose all underlying pipeline instances */\n    dispose(): void;\n    /** Number of stages */\n    readonly length: number;\n}\n/**\n * Compose multiple pipeline stages into a single sequential chain.\n *\n * The output of each stage is fed as the input to the next stage. Use the\n * optional `transform` hook in a stage to reshape data between stages.\n *\n * All pipelines are lazily initialised on the first `run()` call and cached\n * for subsequent calls.\n *\n * @param stages - Ordered list of pipeline stages\n * @returns A composed pipeline that can be run end-to-end\n *\n * @example\n * ```typescript\n * const ocrPipeline = compose([\n *   { task: 'image-to-text' },\n *   {\n *     task: 'text-classification',\n *     transform: (ocrResult: any) => ocrResult.text,\n *   },\n * ]);\n *\n * const { output, stages, totalTime } = await ocrPipeline.run(imageElement);\n * ```\n */\nexport declare function compose(stages: CompositionStage[]): ComposedPipeline;\n/**\n * Run stages in parallel (fan-out) and collect all results.\n *\n * Unlike `compose` (which is sequential), `parallel` runs every stage\n * independently with the same input and returns an array of results.\n *\n * @example\n * ```typescript\n * const analyzer = parallel([\n *   { task: 'text-classification' },\n *   { task: 'feature-extraction' },\n *   { task: 'zero-shot-classification',\n *     transform: (text) => ({ text, candidateLabels: ['news', 'sports'] }) },\n * ]);\n *\n * const results = await analyzer.run('Breaking: team wins championship');\n * ```\n */\nexport declare function parallel(stages: CompositionStage[]): {\n    run(input: unknown): Promise<{\n        outputs: unknown[];\n        totalTime: number;\n    }>;\n    dispose(): void;\n};\n//# sourceMappingURL=composer.d.ts.map"
  },
  {
    "path": "dist/core/composer.js",
    "content": "/**\n * edgeFlow.js - Pipeline Composer\n *\n * Chain multiple pipelines together to build complex multi-model workflows.\n * Each stage's output is transformed and fed as input to the next stage.\n *\n * @example\n * ```typescript\n * import { compose } from 'edgeflowjs';\n *\n * const speechTranslator = compose([\n *   { task: 'automatic-speech-recognition' },\n *   { task: 'translation', options: { srcLang: 'en', tgtLang: 'zh' } },\n * ]);\n *\n * const result = await speechTranslator.run(audioBlob);\n * // result.stages = [asrResult, translationResult]\n * // result.output  = final translation text\n * ```\n */\nimport { pipeline } from '../pipelines/index.js';\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n/**\n * Compose multiple pipeline stages into a single sequential chain.\n *\n * The output of each stage is fed as the input to the next stage. Use the\n * optional `transform` hook in a stage to reshape data between stages.\n *\n * All pipelines are lazily initialised on the first `run()` call and cached\n * for subsequent calls.\n *\n * @param stages - Ordered list of pipeline stages\n * @returns A composed pipeline that can be run end-to-end\n *\n * @example\n * ```typescript\n * const ocrPipeline = compose([\n *   { task: 'image-to-text' },\n *   {\n *     task: 'text-classification',\n *     transform: (ocrResult: any) => ocrResult.text,\n *   },\n * ]);\n *\n * const { output, stages, totalTime } = await ocrPipeline.run(imageElement);\n * ```\n */\nexport function compose(stages) {\n    if (stages.length === 0) {\n        throw new Error('[edgeFlow.js] compose() requires at least one stage');\n    }\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    let pipelineInstances = null;\n    async function ensureInitialised() {\n        if (pipelineInstances)\n            return pipelineInstances;\n        pipelineInstances = await Promise.all(stages.map((stage) => pipeline(stage.task, {\n            model: stage.model,\n            ...stage.options,\n        })));\n        return pipelineInstances;\n    }\n    return {\n        get length() {\n            return stages.length;\n        },\n        async run(input) {\n            const instances = await ensureInitialised();\n            const stageResults = [];\n            const stageTimes = [];\n            let current = input;\n            const wallStart = performance.now();\n            for (let i = 0; i < stages.length; i++) {\n                const stage = stages[i];\n                const inst = instances[i];\n                // Apply transform from previous stage output if provided\n                if (stage.transform) {\n                    current = stage.transform(current);\n                }\n                const t0 = performance.now();\n                // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                current = await inst.run(current, stage.runOptions);\n                stageTimes.push(performance.now() - t0);\n                stageResults.push(current);\n            }\n            return {\n                output: current,\n                stages: stageResults,\n                totalTime: performance.now() - wallStart,\n                stageTimes,\n            };\n        },\n        dispose() {\n            if (pipelineInstances) {\n                for (const inst of pipelineInstances) {\n                    if (inst && typeof inst.dispose === 'function') {\n                        inst.dispose();\n                    }\n                }\n                pipelineInstances = null;\n            }\n        },\n    };\n}\n/**\n * Run stages in parallel (fan-out) and collect all results.\n *\n * Unlike `compose` (which is sequential), `parallel` runs every stage\n * independently with the same input and returns an array of results.\n *\n * @example\n * ```typescript\n * const analyzer = parallel([\n *   { task: 'text-classification' },\n *   { task: 'feature-extraction' },\n *   { task: 'zero-shot-classification',\n *     transform: (text) => ({ text, candidateLabels: ['news', 'sports'] }) },\n * ]);\n *\n * const results = await analyzer.run('Breaking: team wins championship');\n * ```\n */\nexport function parallel(stages) {\n    if (stages.length === 0) {\n        throw new Error('[edgeFlow.js] parallel() requires at least one stage');\n    }\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    let pipelineInstances = null;\n    async function ensureInitialised() {\n        if (pipelineInstances)\n            return pipelineInstances;\n        pipelineInstances = await Promise.all(stages.map((s) => pipeline(s.task, {\n            model: s.model,\n            ...s.options,\n        })));\n        return pipelineInstances;\n    }\n    return {\n        async run(input) {\n            const instances = await ensureInitialised();\n            const t0 = performance.now();\n            const outputs = await Promise.all(stages.map((stage, i) => {\n                const stageInput = stage.transform ? stage.transform(input) : input;\n                // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                return instances[i].run(stageInput, stage.runOptions);\n            }));\n            return { outputs, totalTime: performance.now() - t0 };\n        },\n        dispose() {\n            if (pipelineInstances) {\n                for (const inst of pipelineInstances) {\n                    if (inst && typeof inst.dispose === 'function') {\n                        inst.dispose();\n                    }\n                }\n                pipelineInstances = null;\n            }\n        },\n    };\n}\n//# sourceMappingURL=composer.js.map"
  },
  {
    "path": "dist/core/device-profiler.d.ts",
    "content": "/**\n * edgeFlow.js - Device Profiler\n *\n * Automatically profiles the current device and recommends optimal model\n * variants (quantization level, batch size, execution provider).\n *\n * @example\n * ```typescript\n * import { getDeviceProfile, recommendQuantization } from 'edgeflowjs';\n *\n * const profile = await getDeviceProfile();\n * console.log(profile.tier); // 'high' | 'medium' | 'low'\n *\n * const quant = recommendQuantization(profile);\n * console.log(quant); // 'fp16' | 'int8' | 'int4'\n * ```\n */\nimport type { QuantizationType } from './types.js';\n/**\n * Device capability tier.\n */\nexport type DeviceTier = 'high' | 'medium' | 'low';\n/**\n * Profiled device information.\n */\nexport interface DeviceProfile {\n    /** Capability tier */\n    tier: DeviceTier;\n    /** Number of logical CPU cores */\n    cores: number;\n    /** Device memory in GiB (navigator.deviceMemory, may be null) */\n    memoryGiB: number | null;\n    /** Whether WebGPU is available */\n    webgpu: boolean;\n    /** Whether WebNN is available */\n    webnn: boolean;\n    /** Recommended max batch size */\n    recommendedBatchSize: number;\n    /** Recommended concurrency limit */\n    recommendedConcurrency: number;\n    /** Whether the device is mobile */\n    mobile: boolean;\n    /** Raw GPU adapter info (if WebGPU available) */\n    gpuInfo?: string;\n}\n/**\n * Model variant recommendation.\n */\nexport interface ModelRecommendation {\n    /** Recommended quantization */\n    quantization: QuantizationType;\n    /** Recommended execution provider */\n    executionProvider: 'webgpu' | 'wasm';\n    /** Recommended batch size */\n    batchSize: number;\n    /** Whether to enable worker-based inference */\n    useWorker: boolean;\n}\n/**\n * Profile the current device. Results are cached after the first call.\n */\nexport declare function getDeviceProfile(): Promise<DeviceProfile>;\n/**\n * Recommend the best quantization level for the current device.\n */\nexport declare function recommendQuantization(profile: DeviceProfile): QuantizationType;\n/**\n * Get full model variant recommendations for the current device.\n */\nexport declare function recommendModelVariant(): Promise<ModelRecommendation>;\n/**\n * Reset the cached profile (useful for testing).\n */\nexport declare function resetDeviceProfile(): void;\n//# sourceMappingURL=device-profiler.d.ts.map"
  },
  {
    "path": "dist/core/device-profiler.js",
    "content": "/**\n * edgeFlow.js - Device Profiler\n *\n * Automatically profiles the current device and recommends optimal model\n * variants (quantization level, batch size, execution provider).\n *\n * @example\n * ```typescript\n * import { getDeviceProfile, recommendQuantization } from 'edgeflowjs';\n *\n * const profile = await getDeviceProfile();\n * console.log(profile.tier); // 'high' | 'medium' | 'low'\n *\n * const quant = recommendQuantization(profile);\n * console.log(quant); // 'fp16' | 'int8' | 'int4'\n * ```\n */\n// ---------------------------------------------------------------------------\n// Profiling\n// ---------------------------------------------------------------------------\nlet cachedProfile = null;\n/**\n * Profile the current device. Results are cached after the first call.\n */\nexport async function getDeviceProfile() {\n    if (cachedProfile)\n        return cachedProfile;\n    const cores = typeof navigator !== 'undefined'\n        ? navigator.hardwareConcurrency ?? 2\n        : 2;\n    const memoryGiB = typeof navigator !== 'undefined' && 'deviceMemory' in navigator\n        ? navigator.deviceMemory ?? null\n        : null;\n    const mobile = typeof navigator !== 'undefined'\n        ? /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent)\n        : false;\n    let webgpu = false;\n    let gpuInfo;\n    if (typeof navigator !== 'undefined' && 'gpu' in navigator) {\n        try {\n            const adapter = await navigator.gpu.requestAdapter();\n            webgpu = adapter != null;\n            if (adapter && typeof adapter === 'object') {\n                try {\n                    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                    const info = adapter['info'];\n                    if (info) {\n                        gpuInfo = `${info['vendor'] ?? ''} ${info['architecture'] ?? ''}`.trim() || undefined;\n                    }\n                }\n                catch {\n                    // info not available\n                }\n            }\n        }\n        catch {\n            // WebGPU not available\n        }\n    }\n    let webnn = false;\n    if (typeof navigator !== 'undefined' && 'ml' in navigator) {\n        try {\n            const ml = navigator.ml;\n            if (ml) {\n                const ctx = await ml.createContext();\n                webnn = ctx != null;\n            }\n        }\n        catch {\n            // WebNN not available\n        }\n    }\n    // Determine tier\n    let tier;\n    if (webgpu && cores >= 8 && (memoryGiB === null || memoryGiB >= 8)) {\n        tier = 'high';\n    }\n    else if (cores >= 4 && (memoryGiB === null || memoryGiB >= 4)) {\n        tier = 'medium';\n    }\n    else {\n        tier = 'low';\n    }\n    // Mobile devices get capped even if specs look good\n    if (mobile && tier === 'high') {\n        tier = 'medium';\n    }\n    const recommendedBatchSize = tier === 'high' ? 32 : tier === 'medium' ? 8 : 1;\n    const recommendedConcurrency = tier === 'high' ? 4 : tier === 'medium' ? 2 : 1;\n    cachedProfile = {\n        tier,\n        cores,\n        memoryGiB,\n        webgpu,\n        webnn,\n        recommendedBatchSize,\n        recommendedConcurrency,\n        mobile,\n        gpuInfo,\n    };\n    return cachedProfile;\n}\n/**\n * Recommend the best quantization level for the current device.\n */\nexport function recommendQuantization(profile) {\n    if (profile.tier === 'high' && profile.webgpu)\n        return 'float16';\n    if (profile.tier === 'medium')\n        return 'int8';\n    return 'int8'; // low-tier: most aggressive\n}\n/**\n * Get full model variant recommendations for the current device.\n */\nexport async function recommendModelVariant() {\n    const profile = await getDeviceProfile();\n    return {\n        quantization: recommendQuantization(profile),\n        executionProvider: profile.webgpu ? 'webgpu' : 'wasm',\n        batchSize: profile.recommendedBatchSize,\n        useWorker: profile.cores >= 4,\n    };\n}\n/**\n * Reset the cached profile (useful for testing).\n */\nexport function resetDeviceProfile() {\n    cachedProfile = null;\n}\n//# sourceMappingURL=device-profiler.js.map"
  },
  {
    "path": "dist/core/index.d.ts",
    "content": "/**\n * edgeFlow.js - Core Module Exports\n */\nexport * from './types.js';\nexport { EdgeFlowTensor, tensor, zeros, ones, full, random, randn, arange, linspace, eye, add, sub, mul, div, matmul, softmax, relu, sigmoid, tanh, sum, mean, argmax, concat, } from './tensor.js';\nexport { InferenceScheduler, getScheduler, setScheduler, configureScheduler, } from './scheduler.js';\nexport { MemoryManager, MemoryScope, ModelCache, withMemoryScope, withMemoryScopeSync, getMemoryManager, getMemoryStats, release, gc, } from './memory.js';\nexport { RuntimeManager, LoadedModelImpl, loadModel, loadModelFromBuffer, runInference, runBatchInference, getRuntimeManager, registerRuntime, getBestRuntime, getAvailableRuntimes, } from './runtime.js';\nexport { registerPlugin, getPluginPipeline, getPluginMiddleware, listPlugins, unregisterPlugin, type EdgeFlowPlugin, type PluginPipelineEntry, type PluginBackendEntry, type PluginMiddleware, } from './plugin.js';\nexport { getDeviceProfile, recommendQuantization, recommendModelVariant, resetDeviceProfile, type DeviceProfile, type DeviceTier, type ModelRecommendation, } from './device-profiler.js';\nexport { compose, parallel, type CompositionStage, type CompositionResult, type ComposedPipeline, } from './composer.js';\nexport { InferenceWorker, WorkerPool, getWorkerPool, runInWorker, isWorkerSupported, serializeTensor, deserializeTensor, type WorkerMessage, type WorkerMessageType, type LoadModelRequest, type InferenceRequest, type SerializedTensor, type WorkerPoolOptions, } from './worker.js';\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/core/index.js",
    "content": "/**\n * edgeFlow.js - Core Module Exports\n */\n// Types\nexport * from './types.js';\n// Tensor\nexport { EdgeFlowTensor, tensor, zeros, ones, full, random, randn, arange, linspace, eye, add, sub, mul, div, matmul, softmax, relu, sigmoid, tanh, sum, mean, argmax, concat, } from './tensor.js';\n// Scheduler\nexport { InferenceScheduler, getScheduler, setScheduler, configureScheduler, } from './scheduler.js';\n// Memory\nexport { MemoryManager, MemoryScope, ModelCache, withMemoryScope, withMemoryScopeSync, getMemoryManager, getMemoryStats, release, gc, } from './memory.js';\n// Runtime\nexport { RuntimeManager, LoadedModelImpl, loadModel, loadModelFromBuffer, runInference, runBatchInference, getRuntimeManager, registerRuntime, getBestRuntime, getAvailableRuntimes, } from './runtime.js';\n// Plugin System\nexport { registerPlugin, getPluginPipeline, getPluginMiddleware, listPlugins, unregisterPlugin, } from './plugin.js';\n// Device Profiler\nexport { getDeviceProfile, recommendQuantization, recommendModelVariant, resetDeviceProfile, } from './device-profiler.js';\n// Composer\nexport { compose, parallel, } from './composer.js';\n// Worker\nexport { InferenceWorker, WorkerPool, getWorkerPool, runInWorker, isWorkerSupported, serializeTensor, deserializeTensor, } from './worker.js';\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/core/memory.d.ts",
    "content": "/**\n * edgeFlow.js - Memory Management\n *\n * Efficient memory management for tensors and models.\n * Features:\n * - Memory pooling\n * - Automatic garbage collection\n * - Memory tracking and statistics\n * - Leak detection\n */\nimport { Tensor, LoadedModel, MemoryStats, MemoryPoolConfig, EventType, EventListener } from './types.js';\n/**\n * Tracked resource info\n */\ninterface TrackedResource {\n    id: string;\n    type: 'tensor' | 'model';\n    size: number;\n    createdAt: number;\n    stackTrace?: string;\n}\n/**\n * MemoryManager - Central memory management\n *\n * Provides:\n * - Resource tracking\n * - Memory statistics\n * - Garbage collection coordination\n * - Memory warning events\n */\nexport declare class MemoryManager {\n    private static instance;\n    private readonly config;\n    private readonly resources;\n    private readonly disposers;\n    private readonly listeners;\n    private allocated;\n    private peak;\n    private gcScheduled;\n    private disposed;\n    private constructor();\n    /**\n     * Get singleton instance\n     */\n    static getInstance(): MemoryManager;\n    /**\n     * Configure the memory manager\n     */\n    static configure(config: MemoryPoolConfig): void;\n    /**\n     * Track a tensor\n     */\n    track(tensor: Tensor, disposer?: () => void): void;\n    /**\n     * Track a model\n     */\n    trackModel(model: LoadedModel, disposer?: () => void): void;\n    /**\n     * Untrack a resource\n     */\n    untrack(id: string): void;\n    /**\n     * Release a resource\n     */\n    release(resourceOrId: Tensor | LoadedModel | string): void;\n    /**\n     * Estimate tensor memory size\n     */\n    private estimateTensorSize;\n    /**\n     * Get bytes per element for a data type\n     */\n    private getBytesPerElement;\n    /**\n     * Capture stack trace for debugging\n     */\n    private captureStackTrace;\n    /**\n     * Check if memory threshold is exceeded\n     */\n    private checkMemoryThreshold;\n    /**\n     * Garbage collection helper.\n     *\n     * Identifies stale resources and optionally evicts them.\n     * @param evict - If true, actually dispose stale resources (default: false)\n     * @param maxAge - Resources older than this (ms) are considered stale (default: 5 min)\n     */\n    gc(evict?: boolean, maxAge?: number): void;\n    /**\n     * Query actual browser memory usage via performance.measureUserAgentSpecificMemory()\n     * (Chrome 89+, requires cross-origin isolation). Returns null if unavailable.\n     */\n    measureBrowserMemory(): Promise<{\n        bytes: number;\n        breakdown: Array<{\n            bytes: number;\n            types: string[];\n        }>;\n    } | null>;\n    /**\n     * Get the device's total memory hint (navigator.deviceMemory).\n     * Returns null if unavailable. Value is in GiB, rounded (e.g. 4, 8).\n     */\n    getDeviceMemory(): number | null;\n    /**\n     * Get memory statistics\n     */\n    getStats(): MemoryStats;\n    /**\n     * Get detailed resource list (for debugging)\n     */\n    getResourceDetails(): TrackedResource[];\n    /**\n     * Check for potential memory leaks\n     */\n    detectLeaks(maxAge?: number): TrackedResource[];\n    /**\n     * Add event listener\n     */\n    on<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Remove event listener\n     */\n    off<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Emit event\n     */\n    private emit;\n    /**\n     * Reset statistics\n     */\n    resetStats(): void;\n    /**\n     * Dispose all resources\n     */\n    disposeAll(): void;\n    /**\n     * Dispose the manager\n     */\n    dispose(): void;\n}\n/**\n * Memory scope for automatic resource cleanup\n *\n * Usage:\n * ```typescript\n * const result = await withMemoryScope(async (scope) => {\n *   const tensor1 = scope.track(createTensor(...));\n *   const tensor2 = scope.track(createTensor(...));\n *   // Process tensors\n *   return computeResult(tensor1, tensor2);\n * });\n * // tensor1 and tensor2 are automatically disposed\n * ```\n */\nexport declare class MemoryScope {\n    private resources;\n    private children;\n    private parent;\n    constructor(parent?: MemoryScope);\n    /**\n     * Track a resource in this scope\n     */\n    track<T extends {\n        dispose: () => void;\n    }>(resource: T): T;\n    /**\n     * Create a child scope\n     */\n    createChild(): MemoryScope;\n    /**\n     * Keep a resource (don't dispose it when scope ends)\n     */\n    keep<T extends {\n        dispose: () => void;\n    }>(resource: T): T;\n    /**\n     * Dispose all resources in this scope\n     */\n    dispose(): void;\n}\n/**\n * Execute a function with automatic memory cleanup\n */\nexport declare function withMemoryScope<T>(fn: (scope: MemoryScope) => Promise<T>): Promise<T>;\n/**\n * Synchronous version of withMemoryScope\n */\nexport declare function withMemoryScopeSync<T>(fn: (scope: MemoryScope) => T): T;\n/**\n * LRU Cache for loaded models\n */\nexport declare class ModelCache {\n    private readonly maxSize;\n    private readonly maxModels;\n    private readonly cache;\n    private currentSize;\n    constructor(options?: {\n        maxSize?: number;\n        maxModels?: number;\n    });\n    /**\n     * Get a model from cache\n     */\n    get(key: string): LoadedModel | undefined;\n    /**\n     * Add a model to cache\n     */\n    set(key: string, model: LoadedModel): void;\n    /**\n     * Remove a model from cache\n     */\n    delete(key: string): boolean;\n    /**\n     * Check if model is in cache\n     */\n    has(key: string): boolean;\n    /**\n     * Evict least recently used model\n     */\n    private evictLRU;\n    /**\n     * Clear the cache\n     */\n    clear(): void;\n    /**\n     * Get cache statistics\n     */\n    getStats(): {\n        size: number;\n        count: number;\n        maxSize: number;\n        maxModels: number;\n    };\n}\n/**\n * Get memory manager instance\n */\nexport declare function getMemoryManager(): MemoryManager;\n/**\n * Get memory statistics\n */\nexport declare function getMemoryStats(): MemoryStats;\n/**\n * Release a resource\n */\nexport declare function release(resource: Tensor | LoadedModel): void;\n/**\n * Force garbage collection hint\n */\nexport declare function gc(): void;\nexport {};\n//# sourceMappingURL=memory.d.ts.map"
  },
  {
    "path": "dist/core/memory.js",
    "content": "/**\n * edgeFlow.js - Memory Management\n *\n * Efficient memory management for tensors and models.\n * Features:\n * - Memory pooling\n * - Automatic garbage collection\n * - Memory tracking and statistics\n * - Leak detection\n */\n/**\n * Default memory pool configuration\n */\nconst DEFAULT_POOL_CONFIG = {\n    initialSize: 64 * 1024 * 1024, // 64MB\n    maxSize: 512 * 1024 * 1024, // 512MB\n    growthFactor: 1.5,\n    autoGC: true,\n    gcThreshold: 0.8, // 80%\n};\n// ============================================================================\n// Memory Manager\n// ============================================================================\n/**\n * MemoryManager - Central memory management\n *\n * Provides:\n * - Resource tracking\n * - Memory statistics\n * - Garbage collection coordination\n * - Memory warning events\n */\nexport class MemoryManager {\n    static instance = null;\n    config;\n    resources = new Map();\n    disposers = new Map();\n    listeners = new Map();\n    allocated = 0;\n    peak = 0;\n    gcScheduled = false;\n    disposed = false;\n    constructor(config = {}) {\n        this.config = { ...DEFAULT_POOL_CONFIG, ...config };\n    }\n    /**\n     * Get singleton instance\n     */\n    static getInstance() {\n        if (!MemoryManager.instance) {\n            MemoryManager.instance = new MemoryManager();\n        }\n        return MemoryManager.instance;\n    }\n    /**\n     * Configure the memory manager\n     */\n    static configure(config) {\n        if (MemoryManager.instance) {\n            console.warn('MemoryManager already initialized, configuration may not apply');\n        }\n        MemoryManager.instance = new MemoryManager(config);\n    }\n    /**\n     * Track a tensor\n     */\n    track(tensor, disposer) {\n        if (this.disposed)\n            return;\n        const size = this.estimateTensorSize(tensor);\n        this.resources.set(tensor.id, {\n            id: tensor.id,\n            type: 'tensor',\n            size,\n            createdAt: Date.now(),\n            stackTrace: this.captureStackTrace(),\n        });\n        if (disposer) {\n            this.disposers.set(tensor.id, disposer);\n        }\n        this.allocated += size;\n        this.peak = Math.max(this.peak, this.allocated);\n        this.checkMemoryThreshold();\n    }\n    /**\n     * Track a model\n     */\n    trackModel(model, disposer) {\n        if (this.disposed)\n            return;\n        const size = model.metadata.sizeBytes;\n        this.resources.set(model.id, {\n            id: model.id,\n            type: 'model',\n            size,\n            createdAt: Date.now(),\n            stackTrace: this.captureStackTrace(),\n        });\n        if (disposer) {\n            this.disposers.set(model.id, disposer);\n        }\n        this.allocated += size;\n        this.peak = Math.max(this.peak, this.allocated);\n        this.checkMemoryThreshold();\n    }\n    /**\n     * Untrack a resource\n     */\n    untrack(id) {\n        const resource = this.resources.get(id);\n        if (resource) {\n            this.allocated -= resource.size;\n            this.resources.delete(id);\n            this.disposers.delete(id);\n        }\n    }\n    /**\n     * Release a resource\n     */\n    release(resourceOrId) {\n        const id = typeof resourceOrId === 'string' ? resourceOrId : resourceOrId.id;\n        const disposer = this.disposers.get(id);\n        if (disposer) {\n            try {\n                disposer();\n            }\n            catch (error) {\n                console.error('Error disposing resource:', error);\n            }\n        }\n        this.untrack(id);\n    }\n    /**\n     * Estimate tensor memory size\n     */\n    estimateTensorSize(tensor) {\n        const bytesPerElement = this.getBytesPerElement(tensor.dtype);\n        return tensor.size * bytesPerElement;\n    }\n    /**\n     * Get bytes per element for a data type\n     */\n    getBytesPerElement(dtype) {\n        switch (dtype) {\n            case 'float32':\n                return 4;\n            case 'float16':\n                return 2;\n            case 'int32':\n                return 4;\n            case 'int64':\n                return 8;\n            case 'uint8':\n            case 'int8':\n            case 'bool':\n                return 1;\n            default:\n                return 4;\n        }\n    }\n    /**\n     * Capture stack trace for debugging\n     */\n    captureStackTrace() {\n        if (typeof Error.captureStackTrace === 'function') {\n            const obj = {};\n            Error.captureStackTrace(obj, this.captureStackTrace);\n            return obj.stack;\n        }\n        return new Error().stack;\n    }\n    /**\n     * Check if memory threshold is exceeded\n     */\n    checkMemoryThreshold() {\n        if (!this.config.autoGC)\n            return;\n        const usage = this.allocated / this.config.maxSize;\n        if (usage >= this.config.gcThreshold && !this.gcScheduled) {\n            this.gcScheduled = true;\n            this.emit('memory:warning', {\n                allocated: this.allocated,\n                maxSize: this.config.maxSize,\n                usage,\n            });\n            // Schedule GC on next tick\n            setTimeout(() => {\n                this.gc();\n                this.gcScheduled = false;\n            }, 0);\n        }\n    }\n    /**\n     * Garbage collection helper.\n     *\n     * Identifies stale resources and optionally evicts them.\n     * @param evict - If true, actually dispose stale resources (default: false)\n     * @param maxAge - Resources older than this (ms) are considered stale (default: 5 min)\n     */\n    gc(evict = false, maxAge = 5 * 60 * 1000) {\n        this.emit('memory:gc', { before: this.allocated });\n        const now = Date.now();\n        const staleIds = [];\n        for (const [id, resource] of this.resources) {\n            if (now - resource.createdAt > maxAge) {\n                staleIds.push(id);\n            }\n        }\n        if (evict) {\n            for (const id of staleIds) {\n                this.release(id);\n            }\n        }\n        this.emit('memory:gc', {\n            after: this.allocated,\n            evicted: evict ? staleIds.length : 0,\n            potentialCleanup: staleIds.length,\n        });\n    }\n    /**\n     * Query actual browser memory usage via performance.measureUserAgentSpecificMemory()\n     * (Chrome 89+, requires cross-origin isolation). Returns null if unavailable.\n     */\n    async measureBrowserMemory() {\n        try {\n            if (typeof performance !== 'undefined' &&\n                'measureUserAgentSpecificMemory' in performance) {\n                // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                const result = await performance.measureUserAgentSpecificMemory();\n                return result;\n            }\n        }\n        catch {\n            // Not available or not cross-origin isolated\n        }\n        return null;\n    }\n    /**\n     * Get the device's total memory hint (navigator.deviceMemory).\n     * Returns null if unavailable. Value is in GiB, rounded (e.g. 4, 8).\n     */\n    getDeviceMemory() {\n        try {\n            if (typeof navigator !== 'undefined' && 'deviceMemory' in navigator) {\n                return navigator.deviceMemory ?? null;\n            }\n        }\n        catch {\n            // Not available\n        }\n        return null;\n    }\n    /**\n     * Get memory statistics\n     */\n    getStats() {\n        let tensorCount = 0;\n        let modelCount = 0;\n        for (const resource of this.resources.values()) {\n            if (resource.type === 'tensor') {\n                tensorCount++;\n            }\n            else {\n                modelCount++;\n            }\n        }\n        return {\n            allocated: this.allocated,\n            used: this.allocated, // In JS, allocated = used\n            peak: this.peak,\n            tensorCount,\n            modelCount,\n        };\n    }\n    /**\n     * Get detailed resource list (for debugging)\n     */\n    getResourceDetails() {\n        return Array.from(this.resources.values());\n    }\n    /**\n     * Check for potential memory leaks\n     */\n    detectLeaks(maxAge = 10 * 60 * 1000) {\n        const now = Date.now();\n        const potentialLeaks = [];\n        for (const resource of this.resources.values()) {\n            if (now - resource.createdAt > maxAge) {\n                potentialLeaks.push(resource);\n            }\n        }\n        return potentialLeaks;\n    }\n    /**\n     * Add event listener\n     */\n    on(event, listener) {\n        let listeners = this.listeners.get(event);\n        if (!listeners) {\n            listeners = new Set();\n            this.listeners.set(event, listeners);\n        }\n        listeners.add(listener);\n    }\n    /**\n     * Remove event listener\n     */\n    off(event, listener) {\n        const listeners = this.listeners.get(event);\n        if (listeners) {\n            listeners.delete(listener);\n        }\n    }\n    /**\n     * Emit event\n     */\n    emit(type, data) {\n        const event = {\n            type,\n            timestamp: Date.now(),\n            data,\n        };\n        const listeners = this.listeners.get(type);\n        if (listeners) {\n            for (const listener of listeners) {\n                try {\n                    listener(event);\n                }\n                catch (error) {\n                    console.error('Error in event listener:', error);\n                }\n            }\n        }\n    }\n    /**\n     * Reset statistics\n     */\n    resetStats() {\n        this.peak = this.allocated;\n    }\n    /**\n     * Dispose all resources\n     */\n    disposeAll() {\n        for (const id of this.resources.keys()) {\n            this.release(id);\n        }\n    }\n    /**\n     * Dispose the manager\n     */\n    dispose() {\n        this.disposeAll();\n        this.disposed = true;\n        this.listeners.clear();\n        MemoryManager.instance = null;\n    }\n}\n// ============================================================================\n// Memory Scope (RAII-like pattern)\n// ============================================================================\n/**\n * Memory scope for automatic resource cleanup\n *\n * Usage:\n * ```typescript\n * const result = await withMemoryScope(async (scope) => {\n *   const tensor1 = scope.track(createTensor(...));\n *   const tensor2 = scope.track(createTensor(...));\n *   // Process tensors\n *   return computeResult(tensor1, tensor2);\n * });\n * // tensor1 and tensor2 are automatically disposed\n * ```\n */\nexport class MemoryScope {\n    resources = [];\n    children = [];\n    parent = null;\n    constructor(parent) {\n        if (parent) {\n            this.parent = parent;\n            parent.children.push(this);\n        }\n    }\n    /**\n     * Track a resource in this scope\n     */\n    track(resource) {\n        this.resources.push(resource);\n        return resource;\n    }\n    /**\n     * Create a child scope\n     */\n    createChild() {\n        return new MemoryScope(this);\n    }\n    /**\n     * Keep a resource (don't dispose it when scope ends)\n     */\n    keep(resource) {\n        const index = this.resources.indexOf(resource);\n        if (index !== -1) {\n            this.resources.splice(index, 1);\n        }\n        return resource;\n    }\n    /**\n     * Dispose all resources in this scope\n     */\n    dispose() {\n        // Dispose children first\n        for (const child of this.children) {\n            child.dispose();\n        }\n        this.children = [];\n        // Dispose resources in reverse order\n        for (let i = this.resources.length - 1; i >= 0; i--) {\n            try {\n                this.resources[i]?.dispose();\n            }\n            catch (error) {\n                console.error('Error disposing resource in scope:', error);\n            }\n        }\n        this.resources = [];\n        // Remove from parent\n        if (this.parent) {\n            const index = this.parent.children.indexOf(this);\n            if (index !== -1) {\n                this.parent.children.splice(index, 1);\n            }\n            this.parent = null;\n        }\n    }\n}\n/**\n * Execute a function with automatic memory cleanup\n */\nexport async function withMemoryScope(fn) {\n    const scope = new MemoryScope();\n    try {\n        return await fn(scope);\n    }\n    finally {\n        scope.dispose();\n    }\n}\n/**\n * Synchronous version of withMemoryScope\n */\nexport function withMemoryScopeSync(fn) {\n    const scope = new MemoryScope();\n    try {\n        return fn(scope);\n    }\n    finally {\n        scope.dispose();\n    }\n}\n// ============================================================================\n// LRU Cache for Models\n// ============================================================================\n/**\n * LRU Cache for loaded models\n */\nexport class ModelCache {\n    maxSize;\n    maxModels;\n    cache = new Map();\n    currentSize = 0;\n    constructor(options = {}) {\n        this.maxSize = options.maxSize ?? 256 * 1024 * 1024; // 256MB default\n        this.maxModels = options.maxModels ?? 5;\n    }\n    /**\n     * Get a model from cache\n     */\n    get(key) {\n        const entry = this.cache.get(key);\n        if (entry) {\n            entry.lastAccess = Date.now();\n            return entry.model;\n        }\n        return undefined;\n    }\n    /**\n     * Add a model to cache\n     */\n    set(key, model) {\n        const size = model.metadata.sizeBytes;\n        // Check if we need to evict\n        while ((this.currentSize + size > this.maxSize || this.cache.size >= this.maxModels) &&\n            this.cache.size > 0) {\n            this.evictLRU();\n        }\n        // Add to cache\n        this.cache.set(key, {\n            model,\n            size,\n            lastAccess: Date.now(),\n        });\n        this.currentSize += size;\n    }\n    /**\n     * Remove a model from cache\n     */\n    delete(key) {\n        const entry = this.cache.get(key);\n        if (entry) {\n            entry.model.dispose();\n            this.currentSize -= entry.size;\n            this.cache.delete(key);\n            return true;\n        }\n        return false;\n    }\n    /**\n     * Check if model is in cache\n     */\n    has(key) {\n        return this.cache.has(key);\n    }\n    /**\n     * Evict least recently used model\n     */\n    evictLRU() {\n        let oldestKey = null;\n        let oldestTime = Infinity;\n        for (const [key, entry] of this.cache) {\n            if (entry.lastAccess < oldestTime) {\n                oldestTime = entry.lastAccess;\n                oldestKey = key;\n            }\n        }\n        if (oldestKey) {\n            this.delete(oldestKey);\n        }\n    }\n    /**\n     * Clear the cache\n     */\n    clear() {\n        for (const entry of this.cache.values()) {\n            entry.model.dispose();\n        }\n        this.cache.clear();\n        this.currentSize = 0;\n    }\n    /**\n     * Get cache statistics\n     */\n    getStats() {\n        return {\n            size: this.currentSize,\n            count: this.cache.size,\n            maxSize: this.maxSize,\n            maxModels: this.maxModels,\n        };\n    }\n}\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n/**\n * Get memory manager instance\n */\nexport function getMemoryManager() {\n    return MemoryManager.getInstance();\n}\n/**\n * Get memory statistics\n */\nexport function getMemoryStats() {\n    return MemoryManager.getInstance().getStats();\n}\n/**\n * Release a resource\n */\nexport function release(resource) {\n    MemoryManager.getInstance().release(resource);\n}\n/**\n * Force garbage collection hint\n */\nexport function gc() {\n    MemoryManager.getInstance().gc();\n}\n//# sourceMappingURL=memory.js.map"
  },
  {
    "path": "dist/core/plugin.d.ts",
    "content": "/**\n * edgeFlow.js - Plugin System\n *\n * Register custom pipelines, backends, and middleware via plugins.\n *\n * @example\n * ```typescript\n * import { registerPlugin } from 'edgeflowjs';\n *\n * registerPlugin({\n *   name: 'edgeflow-plugin-whisper',\n *   version: '1.0.0',\n *   pipelines: {\n *     'whisper-transcribe': {\n *       factory: (config) => new WhisperPipeline(config),\n *     },\n *   },\n * });\n *\n * // Now available via pipeline('whisper-transcribe')\n * ```\n */\nimport type { PipelineConfig, Runtime } from './types.js';\n/**\n * A pipeline factory registered by a plugin.\n */\nexport interface PluginPipelineEntry {\n    /** Factory that creates a pipeline instance */\n    factory: (config: PipelineConfig) => any;\n    /** Optional description */\n    description?: string;\n}\n/**\n * A backend registered by a plugin.\n */\nexport interface PluginBackendEntry {\n    /** Factory that creates a runtime instance */\n    factory: () => Runtime;\n    /** Optional description */\n    description?: string;\n}\n/**\n * Middleware that runs before/after inference.\n */\nexport interface PluginMiddleware {\n    /** Unique name */\n    name: string;\n    /** Called before inference with (model, inputs). Return modified inputs. */\n    before?: (ctx: {\n        modelId: string;\n        inputs: any;\n    }) => any | Promise<any>;\n    /** Called after inference with (model, outputs). Return modified outputs. */\n    after?: (ctx: {\n        modelId: string;\n        outputs: any;\n    }) => any | Promise<any>;\n}\n/**\n * Plugin definition.\n */\nexport interface EdgeFlowPlugin {\n    /** Unique plugin name (e.g. 'edgeflow-plugin-whisper') */\n    name: string;\n    /** Plugin version (semver) */\n    version: string;\n    /** Pipelines contributed by this plugin */\n    pipelines?: Record<string, PluginPipelineEntry>;\n    /** Backends contributed by this plugin */\n    backends?: Record<string, PluginBackendEntry>;\n    /** Middleware contributed by this plugin */\n    middleware?: PluginMiddleware[];\n    /** Called once when the plugin is registered */\n    setup?: () => void | Promise<void>;\n}\n/**\n * Register a plugin. Pipelines and backends are made available immediately.\n */\nexport declare function registerPlugin(plugin: EdgeFlowPlugin): Promise<void>;\n/**\n * Look up a pipeline factory registered by any plugin.\n * Returns undefined if no plugin provides this task.\n */\nexport declare function getPluginPipeline(task: string): PluginPipelineEntry | undefined;\n/**\n * Get all registered middleware.\n */\nexport declare function getPluginMiddleware(): ReadonlyArray<PluginMiddleware>;\n/**\n * List all registered plugins.\n */\nexport declare function listPlugins(): Array<{\n    name: string;\n    version: string;\n}>;\n/**\n * Unregister a plugin by name.\n */\nexport declare function unregisterPlugin(name: string): boolean;\n//# sourceMappingURL=plugin.d.ts.map"
  },
  {
    "path": "dist/core/plugin.js",
    "content": "/**\n * edgeFlow.js - Plugin System\n *\n * Register custom pipelines, backends, and middleware via plugins.\n *\n * @example\n * ```typescript\n * import { registerPlugin } from 'edgeflowjs';\n *\n * registerPlugin({\n *   name: 'edgeflow-plugin-whisper',\n *   version: '1.0.0',\n *   pipelines: {\n *     'whisper-transcribe': {\n *       factory: (config) => new WhisperPipeline(config),\n *     },\n *   },\n * });\n *\n * // Now available via pipeline('whisper-transcribe')\n * ```\n */\nimport { registerRuntime } from './runtime.js';\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\nconst registeredPlugins = new Map();\nconst pluginPipelines = new Map();\nconst pluginMiddleware = [];\n/**\n * Register a plugin. Pipelines and backends are made available immediately.\n */\nexport async function registerPlugin(plugin) {\n    if (registeredPlugins.has(plugin.name)) {\n        console.warn(`[edgeFlow.js] Plugin \"${plugin.name}\" is already registered — skipping.`);\n        return;\n    }\n    // Run setup hook\n    if (plugin.setup) {\n        await plugin.setup();\n    }\n    // Register pipelines\n    if (plugin.pipelines) {\n        for (const [task, entry] of Object.entries(plugin.pipelines)) {\n            pluginPipelines.set(task, entry);\n        }\n    }\n    // Register backends\n    if (plugin.backends) {\n        for (const [name, entry] of Object.entries(plugin.backends)) {\n            registerRuntime(name, entry.factory);\n        }\n    }\n    // Register middleware\n    if (plugin.middleware) {\n        pluginMiddleware.push(...plugin.middleware);\n    }\n    registeredPlugins.set(plugin.name, plugin);\n}\n/**\n * Look up a pipeline factory registered by any plugin.\n * Returns undefined if no plugin provides this task.\n */\nexport function getPluginPipeline(task) {\n    return pluginPipelines.get(task);\n}\n/**\n * Get all registered middleware.\n */\nexport function getPluginMiddleware() {\n    return pluginMiddleware;\n}\n/**\n * List all registered plugins.\n */\nexport function listPlugins() {\n    return Array.from(registeredPlugins.values()).map(p => ({\n        name: p.name,\n        version: p.version,\n    }));\n}\n/**\n * Unregister a plugin by name.\n */\nexport function unregisterPlugin(name) {\n    const plugin = registeredPlugins.get(name);\n    if (!plugin)\n        return false;\n    // Remove pipelines\n    if (plugin.pipelines) {\n        for (const task of Object.keys(plugin.pipelines)) {\n            pluginPipelines.delete(task);\n        }\n    }\n    // Remove middleware\n    if (plugin.middleware) {\n        for (const mw of plugin.middleware) {\n            const idx = pluginMiddleware.indexOf(mw);\n            if (idx !== -1)\n                pluginMiddleware.splice(idx, 1);\n        }\n    }\n    registeredPlugins.delete(name);\n    return true;\n}\n//# sourceMappingURL=plugin.js.map"
  },
  {
    "path": "dist/core/runtime.d.ts",
    "content": "/**\n * edgeFlow.js - Runtime Management\n *\n * Manages runtime backends and automatic selection.\n * Provides unified interface for different compute backends.\n */\nimport { Runtime, RuntimeType, RuntimeCapabilities, LoadedModel, ModelLoadOptions, ModelMetadata, Tensor, EventType, EventListener } from './types.js';\n/**\n * RuntimeManager - Manages runtime selection and lifecycle\n *\n * Features:\n * - Automatic best runtime selection\n * - Runtime registration\n * - Capability detection\n * - Fallback handling\n */\nexport declare class RuntimeManager {\n    private static instance;\n    private readonly listeners;\n    private defaultRuntime;\n    private constructor();\n    /**\n     * Get singleton instance\n     */\n    static getInstance(): RuntimeManager;\n    /**\n     * Register a runtime factory\n     */\n    register(type: RuntimeType, factory: () => Runtime): void;\n    /**\n     * Get a runtime instance\n     */\n    getRuntime(type?: RuntimeType): Promise<Runtime>;\n    /**\n     * Get the best available runtime\n     */\n    getBestRuntime(): Promise<Runtime>;\n    /**\n     * Check which runtimes are available\n     */\n    detectAvailableRuntimes(): Promise<Map<RuntimeType, boolean>>;\n    /**\n     * Get capabilities of a runtime\n     */\n    getCapabilities(type: RuntimeType): Promise<RuntimeCapabilities>;\n    /**\n     * Set default runtime\n     */\n    setDefaultRuntime(type: RuntimeType): void;\n    /**\n     * Get default runtime type\n     */\n    getDefaultRuntimeType(): RuntimeType;\n    /**\n     * Dispose a specific runtime\n     */\n    disposeRuntime(type: RuntimeType): void;\n    /**\n     * Dispose all runtimes\n     */\n    disposeAll(): void;\n    /**\n     * Add event listener\n     */\n    on<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Remove event listener\n     */\n    off<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Emit event\n     */\n    private emit;\n}\n/**\n * LoadedModelImpl - Implementation of LoadedModel interface\n */\nexport declare class LoadedModelImpl implements LoadedModel {\n    readonly id: string;\n    readonly metadata: ModelMetadata;\n    readonly runtime: RuntimeType;\n    private _isLoaded;\n    private readonly _dispose;\n    constructor(metadata: ModelMetadata, runtime: RuntimeType, dispose: () => void);\n    get isLoaded(): boolean;\n    dispose(): void;\n}\n/**\n * Load model from URL with advanced loading support\n * (caching, sharding, resume download)\n */\nexport declare function loadModel(url: string, options?: ModelLoadOptions & {\n    runtime?: RuntimeType;\n    cache?: boolean;\n    resumable?: boolean;\n    chunkSize?: number;\n    forceDownload?: boolean;\n}): Promise<LoadedModel>;\n/**\n * Load model from ArrayBuffer\n */\nexport declare function loadModelFromBuffer(data: ArrayBuffer, options?: ModelLoadOptions & {\n    runtime?: RuntimeType;\n}): Promise<LoadedModel>;\n/**\n * Run inference on a model\n */\nexport declare function runInference(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n/**\n * Run inference with named inputs\n */\nexport declare function runInferenceNamed(model: LoadedModel, namedInputs: Map<string, Tensor>): Promise<Tensor[]>;\n/**\n * Run inference with batch processing\n */\nexport declare function runBatchInference(model: LoadedModel, batches: Tensor[][]): Promise<Tensor[][]>;\n/**\n * Get runtime manager instance\n */\nexport declare function getRuntimeManager(): RuntimeManager;\n/**\n * Register a runtime\n */\nexport declare function registerRuntime(type: RuntimeType, factory: () => Runtime): void;\n/**\n * Get the best available runtime\n */\nexport declare function getBestRuntime(): Promise<Runtime>;\n/**\n * Check available runtimes\n */\nexport declare function getAvailableRuntimes(): Promise<Map<RuntimeType, boolean>>;\n//# sourceMappingURL=runtime.d.ts.map"
  },
  {
    "path": "dist/core/runtime.js",
    "content": "/**\n * edgeFlow.js - Runtime Management\n *\n * Manages runtime backends and automatic selection.\n * Provides unified interface for different compute backends.\n */\nimport { EdgeFlowError, ErrorCodes, } from './types.js';\nimport { getScheduler } from './scheduler.js';\nimport { getMemoryManager } from './memory.js';\n// ============================================================================\n// Runtime Registry\n// ============================================================================\n/**\n * Registered runtime factories\n */\nconst runtimeFactories = new Map();\n/**\n * Cached runtime instances\n */\nconst runtimeInstances = new Map();\n/**\n * Runtime priority order (higher priority first)\n */\nconst RUNTIME_PRIORITY = ['webgpu', 'webnn', 'wasm'];\n// ============================================================================\n// Runtime Manager\n// ============================================================================\n/**\n * RuntimeManager - Manages runtime selection and lifecycle\n *\n * Features:\n * - Automatic best runtime selection\n * - Runtime registration\n * - Capability detection\n * - Fallback handling\n */\nexport class RuntimeManager {\n    static instance = null;\n    listeners = new Map();\n    defaultRuntime = 'auto';\n    constructor() { }\n    /**\n     * Get singleton instance\n     */\n    static getInstance() {\n        if (!RuntimeManager.instance) {\n            RuntimeManager.instance = new RuntimeManager();\n        }\n        return RuntimeManager.instance;\n    }\n    /**\n     * Register a runtime factory\n     */\n    register(type, factory) {\n        runtimeFactories.set(type, factory);\n    }\n    /**\n     * Get a runtime instance\n     */\n    async getRuntime(type = 'auto') {\n        if (type === 'auto') {\n            return this.getBestRuntime();\n        }\n        // Check if already instantiated\n        let runtime = runtimeInstances.get(type);\n        if (runtime) {\n            return runtime;\n        }\n        // Create new instance\n        const factory = runtimeFactories.get(type);\n        if (!factory) {\n            throw new EdgeFlowError(`Runtime '${type}' is not registered`, ErrorCodes.RUNTIME_NOT_AVAILABLE, { runtime: type });\n        }\n        runtime = factory();\n        // Check availability\n        const available = await runtime.isAvailable();\n        if (!available) {\n            throw new EdgeFlowError(`Runtime '${type}' is not available in this environment`, ErrorCodes.RUNTIME_NOT_AVAILABLE, { runtime: type });\n        }\n        // Initialize\n        try {\n            await runtime.initialize();\n        }\n        catch (error) {\n            throw new EdgeFlowError(`Failed to initialize runtime '${type}': ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.RUNTIME_INIT_FAILED, { runtime: type, error });\n        }\n        runtimeInstances.set(type, runtime);\n        this.emit('runtime:ready', { runtime: type });\n        return runtime;\n    }\n    /**\n     * Get the best available runtime\n     */\n    async getBestRuntime() {\n        for (const type of RUNTIME_PRIORITY) {\n            try {\n                // Check if already available\n                const existing = runtimeInstances.get(type);\n                if (existing) {\n                    return existing;\n                }\n                // Try to create and initialize\n                const factory = runtimeFactories.get(type);\n                if (!factory)\n                    continue;\n                const runtime = factory();\n                const available = await runtime.isAvailable();\n                if (available) {\n                    await runtime.initialize();\n                    runtimeInstances.set(type, runtime);\n                    this.emit('runtime:ready', { runtime: type });\n                    return runtime;\n                }\n            }\n            catch {\n                // Try next runtime\n                continue;\n            }\n        }\n        throw new EdgeFlowError('No runtime available. Please ensure WebGPU, WebNN, or WASM is supported.', ErrorCodes.RUNTIME_NOT_AVAILABLE, { triedRuntimes: RUNTIME_PRIORITY });\n    }\n    /**\n     * Check which runtimes are available\n     */\n    async detectAvailableRuntimes() {\n        const results = new Map();\n        for (const type of RUNTIME_PRIORITY) {\n            const factory = runtimeFactories.get(type);\n            if (!factory) {\n                results.set(type, false);\n                continue;\n            }\n            try {\n                const runtime = factory();\n                results.set(type, await runtime.isAvailable());\n            }\n            catch {\n                results.set(type, false);\n            }\n        }\n        return results;\n    }\n    /**\n     * Get capabilities of a runtime\n     */\n    async getCapabilities(type) {\n        const runtime = await this.getRuntime(type);\n        return runtime.capabilities;\n    }\n    /**\n     * Set default runtime\n     */\n    setDefaultRuntime(type) {\n        this.defaultRuntime = type;\n    }\n    /**\n     * Get default runtime type\n     */\n    getDefaultRuntimeType() {\n        return this.defaultRuntime;\n    }\n    /**\n     * Dispose a specific runtime\n     */\n    disposeRuntime(type) {\n        const runtime = runtimeInstances.get(type);\n        if (runtime) {\n            runtime.dispose();\n            runtimeInstances.delete(type);\n        }\n    }\n    /**\n     * Dispose all runtimes\n     */\n    disposeAll() {\n        for (const [type, runtime] of runtimeInstances) {\n            runtime.dispose();\n            runtimeInstances.delete(type);\n        }\n    }\n    /**\n     * Add event listener\n     */\n    on(event, listener) {\n        let listeners = this.listeners.get(event);\n        if (!listeners) {\n            listeners = new Set();\n            this.listeners.set(event, listeners);\n        }\n        listeners.add(listener);\n    }\n    /**\n     * Remove event listener\n     */\n    off(event, listener) {\n        const listeners = this.listeners.get(event);\n        if (listeners) {\n            listeners.delete(listener);\n        }\n    }\n    /**\n     * Emit event\n     */\n    emit(type, data) {\n        const event = {\n            type,\n            timestamp: Date.now(),\n            data,\n        };\n        const listeners = this.listeners.get(type);\n        if (listeners) {\n            for (const listener of listeners) {\n                try {\n                    listener(event);\n                }\n                catch (error) {\n                    console.error('Error in event listener:', error);\n                }\n            }\n        }\n    }\n}\n// ============================================================================\n// Model Loader\n// ============================================================================\n/**\n * Model instance counter\n */\nlet modelIdCounter = 0;\n/**\n * Generate unique model ID\n */\nfunction generateModelId() {\n    return `model_${++modelIdCounter}_${Date.now().toString(36)}`;\n}\n/**\n * LoadedModelImpl - Implementation of LoadedModel interface\n */\nexport class LoadedModelImpl {\n    id;\n    metadata;\n    runtime;\n    _isLoaded = true;\n    _dispose;\n    constructor(metadata, runtime, dispose) {\n        this.id = generateModelId();\n        this.metadata = metadata;\n        this.runtime = runtime;\n        this._dispose = dispose;\n    }\n    get isLoaded() {\n        return this._isLoaded;\n    }\n    dispose() {\n        if (this._isLoaded) {\n            this._isLoaded = false;\n            this._dispose();\n            getMemoryManager().untrack(this.id);\n        }\n    }\n}\n// ============================================================================\n// Model Loading Functions\n// ============================================================================\n/**\n * Load model from URL with advanced loading support\n * (caching, sharding, resume download)\n */\nexport async function loadModel(url, options = {}) {\n    const manager = RuntimeManager.getInstance();\n    const runtime = await manager.getRuntime(options.runtime ?? 'auto');\n    // Import model loader dynamically to avoid circular dependencies\n    const { loadModelData } = await import('../utils/model-loader.js');\n    // Use advanced model loader with caching and resume support\n    const modelData = await loadModelData(url, {\n        cache: options.cache ?? true,\n        resumable: options.resumable ?? true,\n        chunkSize: options.chunkSize,\n        forceDownload: options.forceDownload,\n        onProgress: options.onProgress ? (progress) => {\n            options.onProgress(progress.percent / 100);\n        } : undefined,\n    });\n    // Load into runtime\n    const model = await runtime.loadModel(modelData, options);\n    return model;\n}\n/**\n * Load model from ArrayBuffer\n */\nexport async function loadModelFromBuffer(data, options = {}) {\n    const manager = RuntimeManager.getInstance();\n    const runtime = await manager.getRuntime(options.runtime ?? 'auto');\n    return runtime.loadModel(data, options);\n}\n// ============================================================================\n// Inference Functions\n// ============================================================================\n/**\n * Run inference on a model\n */\nexport async function runInference(model, inputs) {\n    if (!model.isLoaded) {\n        throw new EdgeFlowError('Model has been disposed', ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n    }\n    const manager = RuntimeManager.getInstance();\n    const runtime = await manager.getRuntime(model.runtime);\n    // Use scheduler for execution\n    const scheduler = getScheduler();\n    const task = scheduler.schedule(model.id, () => runtime.run(model, inputs));\n    return task.wait();\n}\n/**\n * Run inference with named inputs\n */\nexport async function runInferenceNamed(model, namedInputs) {\n    if (!model.isLoaded) {\n        throw new EdgeFlowError('Model has been disposed', ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n    }\n    const manager = RuntimeManager.getInstance();\n    const runtime = await manager.getRuntime(model.runtime);\n    // Check if runtime supports named inputs\n    if (!('runNamed' in runtime)) {\n        throw new EdgeFlowError('Runtime does not support named inputs', ErrorCodes.INFERENCE_FAILED, { modelId: model.id });\n    }\n    // Use scheduler for execution\n    const scheduler = getScheduler();\n    const task = scheduler.schedule(model.id, () => runtime.runNamed(model, namedInputs));\n    return task.wait();\n}\n/**\n * Run inference with batch processing\n */\nexport async function runBatchInference(model, batches) {\n    const scheduler = getScheduler();\n    const manager = RuntimeManager.getInstance();\n    const runtime = await manager.getRuntime(model.runtime);\n    // Schedule all batches\n    const tasks = batches.map(inputs => scheduler.schedule(model.id, () => runtime.run(model, inputs)));\n    // Wait for all to complete\n    return Promise.all(tasks.map(task => task.wait()));\n}\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n/**\n * Get runtime manager instance\n */\nexport function getRuntimeManager() {\n    return RuntimeManager.getInstance();\n}\n/**\n * Register a runtime\n */\nexport function registerRuntime(type, factory) {\n    RuntimeManager.getInstance().register(type, factory);\n}\n/**\n * Get the best available runtime\n */\nexport async function getBestRuntime() {\n    return RuntimeManager.getInstance().getBestRuntime();\n}\n/**\n * Check available runtimes\n */\nexport async function getAvailableRuntimes() {\n    return RuntimeManager.getInstance().detectAvailableRuntimes();\n}\n//# sourceMappingURL=runtime.js.map"
  },
  {
    "path": "dist/core/scheduler.d.ts",
    "content": "/**\n * edgeFlow.js - Inference Scheduler\n *\n * Task scheduler for managing concurrent inference execution.\n * Supports priority queues, model-level isolation, and batch processing.\n */\nimport { InferenceTask, TaskPriority, SchedulerOptions, EventType, EventListener } from './types.js';\n/**\n * InferenceScheduler - Manages concurrent task execution\n *\n * Features:\n * - Priority-based task scheduling\n * - Model-level concurrency control\n * - Optional batch processing\n * - Task cancellation\n * - Event emission\n */\nexport declare class InferenceScheduler {\n    private readonly options;\n    private readonly queues;\n    private readonly runningTasks;\n    private readonly allTasks;\n    private readonly batchers;\n    private readonly listeners;\n    private readonly circuits;\n    private globalRunningCount;\n    private isProcessing;\n    private disposed;\n    constructor(options?: SchedulerOptions);\n    /**\n     * Get circuit breaker state for a model, creating default if absent\n     */\n    private getCircuit;\n    /**\n     * Check if the circuit for a model allows new tasks\n     */\n    private isCircuitOpen;\n    /**\n     * Record a success for circuit breaker\n     */\n    private circuitSuccess;\n    /**\n     * Record a failure for circuit breaker\n     */\n    private circuitFailure;\n    /**\n     * Get or create queue for a model\n     */\n    private getQueue;\n    /**\n     * Get or create running set for a model\n     */\n    private getRunningSet;\n    /**\n     * Check if we can start a new task for a model\n     */\n    private canStartTask;\n    /**\n     * Process pending tasks\n     */\n    private processQueue;\n    /**\n     * Schedule a task for execution\n     */\n    schedule<T>(modelId: string, executor: () => Promise<T>, priority?: TaskPriority): InferenceTask<T>;\n    /**\n     * Schedule with timeout\n     */\n    scheduleWithTimeout<T>(modelId: string, executor: () => Promise<T>, timeout?: number, priority?: TaskPriority): InferenceTask<T>;\n    /**\n     * Schedule multiple tasks and wait for all\n     */\n    scheduleAll<T>(tasks: Array<{\n        modelId: string;\n        executor: () => Promise<T>;\n        priority?: TaskPriority;\n    }>): Promise<T[]>;\n    /**\n     * Get task by ID\n     */\n    getTask(taskId: string): InferenceTask | undefined;\n    /**\n     * Cancel a task\n     */\n    cancelTask(taskId: string): boolean;\n    /**\n     * Cancel all tasks for a model\n     */\n    cancelAllForModel(modelId: string): number;\n    /**\n     * Get statistics\n     */\n    getStats(): {\n        totalTasks: number;\n        pendingTasks: number;\n        runningTasks: number;\n        completedTasks: number;\n        failedTasks: number;\n        cancelledTasks: number;\n        queuedByModel: Record<string, number>;\n    };\n    /**\n     * Add event listener\n     */\n    on<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Remove event listener\n     */\n    off<T = unknown>(event: EventType, listener: EventListener<T>): void;\n    /**\n     * Emit event\n     */\n    private emit;\n    /**\n     * Clear completed/failed/cancelled tasks from history\n     */\n    clearHistory(): void;\n    /**\n     * Dispose the scheduler\n     */\n    dispose(): void;\n}\n/**\n * Get the global scheduler instance\n */\nexport declare function getScheduler(): InferenceScheduler;\n/**\n * Set the global scheduler instance\n */\nexport declare function setScheduler(scheduler: InferenceScheduler): void;\n/**\n * Configure the global scheduler\n */\nexport declare function configureScheduler(options: SchedulerOptions): void;\n//# sourceMappingURL=scheduler.d.ts.map"
  },
  {
    "path": "dist/core/scheduler.js",
    "content": "/**\n * edgeFlow.js - Inference Scheduler\n *\n * Task scheduler for managing concurrent inference execution.\n * Supports priority queues, model-level isolation, and batch processing.\n */\nimport { EdgeFlowError, ErrorCodes, } from './types.js';\n// ============================================================================\n// Task Implementation\n// ============================================================================\n/**\n * Internal task implementation\n */\nclass Task {\n    id;\n    modelId;\n    priority;\n    createdAt;\n    _status = 'pending';\n    _startedAt;\n    _completedAt;\n    _result;\n    _error;\n    _executor;\n    _resolvers = [];\n    _cancelled = false;\n    constructor(id, modelId, priority, executor) {\n        this.id = id;\n        this.modelId = modelId;\n        this.priority = priority;\n        this.createdAt = Date.now();\n        this._executor = executor;\n    }\n    get status() {\n        return this._status;\n    }\n    get startedAt() {\n        return this._startedAt;\n    }\n    get completedAt() {\n        return this._completedAt;\n    }\n    get result() {\n        return this._result;\n    }\n    get error() {\n        return this._error;\n    }\n    /**\n     * Cancel the task\n     */\n    cancel() {\n        if (this._status === 'pending') {\n            this._cancelled = true;\n            this._status = 'cancelled';\n            this._completedAt = Date.now();\n            const cancelError = new EdgeFlowError('Task was cancelled', ErrorCodes.INFERENCE_CANCELLED, { taskId: this.id });\n            for (const { reject } of this._resolvers) {\n                reject(cancelError);\n            }\n            this._resolvers = [];\n        }\n    }\n    /**\n     * Wait for task completion\n     */\n    wait() {\n        if (this._status === 'completed') {\n            return Promise.resolve(this._result);\n        }\n        if (this._status === 'failed') {\n            return Promise.reject(this._error);\n        }\n        if (this._status === 'cancelled') {\n            return Promise.reject(new EdgeFlowError('Task was cancelled', ErrorCodes.INFERENCE_CANCELLED, { taskId: this.id }));\n        }\n        return new Promise((resolve, reject) => {\n            this._resolvers.push({ resolve, reject });\n        });\n    }\n    /**\n     * Execute the task\n     */\n    async execute() {\n        if (this._cancelled) {\n            return;\n        }\n        this._status = 'running';\n        this._startedAt = Date.now();\n        try {\n            this._result = await this._executor();\n            this._status = 'completed';\n            this._completedAt = Date.now();\n            for (const { resolve } of this._resolvers) {\n                resolve(this._result);\n            }\n        }\n        catch (err) {\n            this._error = err instanceof Error ? err : new Error(String(err));\n            this._status = 'failed';\n            this._completedAt = Date.now();\n            for (const { reject } of this._resolvers) {\n                reject(this._error);\n            }\n        }\n        this._resolvers = [];\n    }\n}\n// ============================================================================\n// Priority Queue Implementation\n// ============================================================================\n/**\n * Priority mapping for comparison\n */\nconst PRIORITY_ORDER = {\n    critical: 0,\n    high: 1,\n    normal: 2,\n    low: 3,\n};\n/**\n * Priority queue for tasks\n */\nclass PriorityQueue {\n    items = [];\n    get length() {\n        return this.items.length;\n    }\n    isEmpty() {\n        return this.items.length === 0;\n    }\n    /**\n     * Add item to queue with priority ordering\n     */\n    enqueue(item) {\n        let inserted = false;\n        for (let i = 0; i < this.items.length; i++) {\n            const currentItem = this.items[i];\n            if (currentItem && PRIORITY_ORDER[item.priority] < PRIORITY_ORDER[currentItem.priority]) {\n                this.items.splice(i, 0, item);\n                inserted = true;\n                break;\n            }\n        }\n        if (!inserted) {\n            this.items.push(item);\n        }\n    }\n    /**\n     * Remove and return highest priority item\n     */\n    dequeue() {\n        return this.items.shift();\n    }\n    /**\n     * Peek at highest priority item without removing\n     */\n    peek() {\n        return this.items[0];\n    }\n    /**\n     * Remove a specific item by ID\n     */\n    remove(id) {\n        const index = this.items.findIndex(item => item.id === id);\n        if (index !== -1) {\n            const [removed] = this.items.splice(index, 1);\n            return removed;\n        }\n        return undefined;\n    }\n    /**\n     * Get all items\n     */\n    getAll() {\n        return [...this.items];\n    }\n    /**\n     * Clear the queue\n     */\n    clear() {\n        this.items = [];\n    }\n}\n// ============================================================================\n// Batch Collector\n// ============================================================================\n/**\n * Collects tasks for batch processing\n */\nclass BatchCollector {\n    tasks = [];\n    timer = null;\n    maxSize;\n    timeout;\n    onBatch;\n    constructor(maxSize, timeout, onBatch) {\n        this.maxSize = maxSize;\n        this.timeout = timeout;\n        this.onBatch = onBatch;\n    }\n    add(task) {\n        this.tasks.push(task);\n        if (this.tasks.length >= this.maxSize) {\n            this.flush();\n        }\n        else if (!this.timer) {\n            this.timer = setTimeout(() => this.flush(), this.timeout);\n        }\n    }\n    flush() {\n        if (this.timer) {\n            clearTimeout(this.timer);\n            this.timer = null;\n        }\n        if (this.tasks.length > 0) {\n            const batch = this.tasks;\n            this.tasks = [];\n            this.onBatch(batch);\n        }\n    }\n    clear() {\n        if (this.timer) {\n            clearTimeout(this.timer);\n            this.timer = null;\n        }\n        this.tasks = [];\n    }\n}\n// ============================================================================\n// Inference Scheduler\n// ============================================================================\n// Counter for task IDs\nlet taskIdCounter = 0;\n/**\n * Generate unique task ID\n */\nfunction generateTaskId() {\n    return `task_${++taskIdCounter}_${Date.now().toString(36)}`;\n}\n/**\n * Default scheduler options\n */\nconst DEFAULT_OPTIONS = {\n    maxConcurrentTasks: 4,\n    maxConcurrentPerModel: 1,\n    defaultTimeout: 30000,\n    enableBatching: false,\n    maxBatchSize: 32,\n    batchTimeout: 50,\n    maxRetries: 0,\n    retryBaseDelay: 1000,\n    circuitBreaker: false,\n    circuitBreakerThreshold: 5,\n    circuitBreakerResetTimeout: 30000,\n};\n/**\n * InferenceScheduler - Manages concurrent task execution\n *\n * Features:\n * - Priority-based task scheduling\n * - Model-level concurrency control\n * - Optional batch processing\n * - Task cancellation\n * - Event emission\n */\nexport class InferenceScheduler {\n    options;\n    queues = new Map();\n    runningTasks = new Map();\n    allTasks = new Map();\n    batchers = new Map();\n    listeners = new Map();\n    circuits = new Map();\n    globalRunningCount = 0;\n    isProcessing = false;\n    disposed = false;\n    constructor(options = {}) {\n        this.options = { ...DEFAULT_OPTIONS, ...options };\n    }\n    /**\n     * Get circuit breaker state for a model, creating default if absent\n     */\n    getCircuit(modelId) {\n        let c = this.circuits.get(modelId);\n        if (!c) {\n            c = { failures: 0, state: 'closed', lastFailure: 0 };\n            this.circuits.set(modelId, c);\n        }\n        return c;\n    }\n    /**\n     * Check if the circuit for a model allows new tasks\n     */\n    isCircuitOpen(modelId) {\n        if (!this.options.circuitBreaker)\n            return false;\n        const c = this.getCircuit(modelId);\n        if (c.state === 'closed')\n            return false;\n        if (c.state === 'open') {\n            if (Date.now() - c.lastFailure > this.options.circuitBreakerResetTimeout) {\n                c.state = 'half-open';\n                return false; // allow one probe\n            }\n            return true;\n        }\n        return false; // half-open allows one\n    }\n    /**\n     * Record a success for circuit breaker\n     */\n    circuitSuccess(modelId) {\n        if (!this.options.circuitBreaker)\n            return;\n        const c = this.getCircuit(modelId);\n        c.failures = 0;\n        c.state = 'closed';\n    }\n    /**\n     * Record a failure for circuit breaker\n     */\n    circuitFailure(modelId) {\n        if (!this.options.circuitBreaker)\n            return;\n        const c = this.getCircuit(modelId);\n        c.failures++;\n        c.lastFailure = Date.now();\n        if (c.failures >= this.options.circuitBreakerThreshold) {\n            c.state = 'open';\n            this.emit('inference:error', {\n                modelId,\n                error: new Error(`Circuit breaker opened after ${c.failures} consecutive failures`),\n            });\n        }\n    }\n    /**\n     * Get or create queue for a model\n     */\n    getQueue(modelId) {\n        let queue = this.queues.get(modelId);\n        if (!queue) {\n            queue = new PriorityQueue();\n            this.queues.set(modelId, queue);\n        }\n        return queue;\n    }\n    /**\n     * Get or create running set for a model\n     */\n    getRunningSet(modelId) {\n        let running = this.runningTasks.get(modelId);\n        if (!running) {\n            running = new Set();\n            this.runningTasks.set(modelId, running);\n        }\n        return running;\n    }\n    /**\n     * Check if we can start a new task for a model\n     */\n    canStartTask(modelId) {\n        if (this.globalRunningCount >= this.options.maxConcurrentTasks) {\n            return false;\n        }\n        const running = this.runningTasks.get(modelId);\n        if (running && running.size >= this.options.maxConcurrentPerModel) {\n            return false;\n        }\n        return true;\n    }\n    /**\n     * Process pending tasks\n     */\n    async processQueue() {\n        if (this.isProcessing || this.disposed) {\n            return;\n        }\n        this.isProcessing = true;\n        try {\n            // Find tasks that can be started\n            const tasksToStart = [];\n            for (const [modelId, queue] of this.queues) {\n                while (!queue.isEmpty() && this.canStartTask(modelId)) {\n                    const task = queue.dequeue();\n                    if (task && task.status === 'pending') {\n                        tasksToStart.push(task);\n                        const running = this.getRunningSet(modelId);\n                        running.add(task.id);\n                        this.globalRunningCount++;\n                    }\n                }\n            }\n            // Execute tasks concurrently\n            await Promise.all(tasksToStart.map(async (task) => {\n                this.emit('inference:start', { taskId: task.id, modelId: task.modelId });\n                try {\n                    await task.execute();\n                    this.emit('inference:complete', {\n                        taskId: task.id,\n                        modelId: task.modelId,\n                        duration: (task.completedAt ?? 0) - (task.startedAt ?? 0),\n                    });\n                }\n                catch (error) {\n                    this.emit('inference:error', {\n                        taskId: task.id,\n                        modelId: task.modelId,\n                        error,\n                    });\n                }\n                finally {\n                    // Clean up\n                    const running = this.runningTasks.get(task.modelId);\n                    if (running) {\n                        running.delete(task.id);\n                    }\n                    this.globalRunningCount--;\n                }\n            }));\n        }\n        finally {\n            this.isProcessing = false;\n        }\n        // Check if there are more tasks to process\n        let hasPending = false;\n        for (const queue of this.queues.values()) {\n            if (!queue.isEmpty()) {\n                hasPending = true;\n                break;\n            }\n        }\n        if (hasPending) {\n            // Use setImmediate-like behavior for next tick processing\n            setTimeout(() => this.processQueue(), 0);\n        }\n    }\n    /**\n     * Schedule a task for execution\n     */\n    schedule(modelId, executor, priority = 'normal') {\n        if (this.disposed) {\n            throw new EdgeFlowError('Scheduler has been disposed', ErrorCodes.RUNTIME_NOT_INITIALIZED);\n        }\n        if (this.isCircuitOpen(modelId)) {\n            throw new EdgeFlowError(`Circuit breaker is open for model ${modelId} — too many consecutive failures. ` +\n                `Retry after ${this.options.circuitBreakerResetTimeout}ms.`, ErrorCodes.INFERENCE_FAILED, { modelId });\n        }\n        // Wrap executor with retry logic\n        const maxRetries = this.options.maxRetries;\n        const baseDelay = this.options.retryBaseDelay;\n        const wrappedExecutor = maxRetries > 0\n            ? async () => {\n                let lastError;\n                for (let attempt = 0; attempt <= maxRetries; attempt++) {\n                    try {\n                        const result = await executor();\n                        this.circuitSuccess(modelId);\n                        return result;\n                    }\n                    catch (err) {\n                        lastError = err instanceof Error ? err : new Error(String(err));\n                        this.circuitFailure(modelId);\n                        if (attempt < maxRetries) {\n                            const delay = baseDelay * Math.pow(2, attempt);\n                            await new Promise(r => setTimeout(r, delay));\n                        }\n                    }\n                }\n                throw lastError;\n            }\n            : async () => {\n                try {\n                    const result = await executor();\n                    this.circuitSuccess(modelId);\n                    return result;\n                }\n                catch (err) {\n                    this.circuitFailure(modelId);\n                    throw err;\n                }\n            };\n        const task = new Task(generateTaskId(), modelId, priority, wrappedExecutor);\n        this.allTasks.set(task.id, task);\n        const queue = this.getQueue(modelId);\n        queue.enqueue(task);\n        this.processQueue();\n        return task;\n    }\n    /**\n     * Schedule with timeout\n     */\n    scheduleWithTimeout(modelId, executor, timeout = this.options.defaultTimeout, priority = 'normal') {\n        const timeoutExecutor = () => {\n            return new Promise((resolve, reject) => {\n                const timer = setTimeout(() => {\n                    reject(new EdgeFlowError(`Task timed out after ${timeout}ms`, ErrorCodes.INFERENCE_TIMEOUT, { timeout }));\n                }, timeout);\n                executor()\n                    .then(result => {\n                    clearTimeout(timer);\n                    resolve(result);\n                })\n                    .catch(error => {\n                    clearTimeout(timer);\n                    reject(error);\n                });\n            });\n        };\n        return this.schedule(modelId, timeoutExecutor, priority);\n    }\n    /**\n     * Schedule multiple tasks and wait for all\n     */\n    async scheduleAll(tasks) {\n        const scheduledTasks = tasks.map(({ modelId, executor, priority }) => this.schedule(modelId, executor, priority));\n        return Promise.all(scheduledTasks.map(task => task.wait()));\n    }\n    /**\n     * Get task by ID\n     */\n    getTask(taskId) {\n        return this.allTasks.get(taskId);\n    }\n    /**\n     * Cancel a task\n     */\n    cancelTask(taskId) {\n        const task = this.allTasks.get(taskId);\n        if (task && task.status === 'pending') {\n            task.cancel();\n            // Remove from queue\n            for (const queue of this.queues.values()) {\n                queue.remove(taskId);\n            }\n            return true;\n        }\n        return false;\n    }\n    /**\n     * Cancel all tasks for a model\n     */\n    cancelAllForModel(modelId) {\n        const queue = this.queues.get(modelId);\n        if (!queue)\n            return 0;\n        let cancelled = 0;\n        for (const task of queue.getAll()) {\n            if (task.status === 'pending') {\n                task.cancel();\n                cancelled++;\n            }\n        }\n        queue.clear();\n        return cancelled;\n    }\n    /**\n     * Get statistics\n     */\n    getStats() {\n        const stats = {\n            totalTasks: this.allTasks.size,\n            pendingTasks: 0,\n            runningTasks: 0,\n            completedTasks: 0,\n            failedTasks: 0,\n            cancelledTasks: 0,\n            queuedByModel: {},\n        };\n        for (const task of this.allTasks.values()) {\n            switch (task.status) {\n                case 'pending':\n                    stats.pendingTasks++;\n                    break;\n                case 'running':\n                    stats.runningTasks++;\n                    break;\n                case 'completed':\n                    stats.completedTasks++;\n                    break;\n                case 'failed':\n                    stats.failedTasks++;\n                    break;\n                case 'cancelled':\n                    stats.cancelledTasks++;\n                    break;\n            }\n        }\n        for (const [modelId, queue] of this.queues) {\n            stats.queuedByModel[modelId] = queue.length;\n        }\n        return stats;\n    }\n    /**\n     * Add event listener\n     */\n    on(event, listener) {\n        let listeners = this.listeners.get(event);\n        if (!listeners) {\n            listeners = new Set();\n            this.listeners.set(event, listeners);\n        }\n        listeners.add(listener);\n    }\n    /**\n     * Remove event listener\n     */\n    off(event, listener) {\n        const listeners = this.listeners.get(event);\n        if (listeners) {\n            listeners.delete(listener);\n        }\n    }\n    /**\n     * Emit event\n     */\n    emit(type, data) {\n        const event = {\n            type,\n            timestamp: Date.now(),\n            data,\n        };\n        const listeners = this.listeners.get(type);\n        if (listeners) {\n            for (const listener of listeners) {\n                try {\n                    listener(event);\n                }\n                catch (error) {\n                    console.error('Error in event listener:', error);\n                }\n            }\n        }\n    }\n    /**\n     * Clear completed/failed/cancelled tasks from history\n     */\n    clearHistory() {\n        for (const [taskId, task] of this.allTasks) {\n            if (task.status === 'completed' ||\n                task.status === 'failed' ||\n                task.status === 'cancelled') {\n                this.allTasks.delete(taskId);\n            }\n        }\n    }\n    /**\n     * Dispose the scheduler\n     */\n    dispose() {\n        this.disposed = true;\n        // Cancel all pending tasks\n        for (const queue of this.queues.values()) {\n            for (const task of queue.getAll()) {\n                task.cancel();\n            }\n            queue.clear();\n        }\n        // Clear batchers\n        for (const batcher of this.batchers.values()) {\n            batcher.clear();\n        }\n        this.queues.clear();\n        this.runningTasks.clear();\n        this.allTasks.clear();\n        this.batchers.clear();\n        this.listeners.clear();\n    }\n}\n// ============================================================================\n// Global Scheduler Instance\n// ============================================================================\nlet globalScheduler = null;\n/**\n * Get the global scheduler instance\n */\nexport function getScheduler() {\n    if (!globalScheduler) {\n        globalScheduler = new InferenceScheduler();\n    }\n    return globalScheduler;\n}\n/**\n * Set the global scheduler instance\n */\nexport function setScheduler(scheduler) {\n    if (globalScheduler) {\n        globalScheduler.dispose();\n    }\n    globalScheduler = scheduler;\n}\n/**\n * Configure the global scheduler\n */\nexport function configureScheduler(options) {\n    setScheduler(new InferenceScheduler(options));\n}\n//# sourceMappingURL=scheduler.js.map"
  },
  {
    "path": "dist/core/tensor.d.ts",
    "content": "/**\n * edgeFlow.js - Tensor Implementation\n *\n * Lightweight tensor implementation with efficient memory management.\n */\nimport { Tensor, DataType, Shape, TypedArray } from './types.js';\n/**\n * EdgeFlowTensor - Core tensor implementation\n */\nexport declare class EdgeFlowTensor implements Tensor {\n    readonly id: string;\n    readonly dtype: DataType;\n    readonly shape: Shape;\n    readonly size: number;\n    private _data;\n    private _isDisposed;\n    constructor(data: TypedArray | number[], shape: Shape, dtype?: DataType);\n    get data(): TypedArray;\n    get isDisposed(): boolean;\n    /**\n     * Check if tensor has been disposed\n     */\n    private checkDisposed;\n    /**\n     * Convert to Float32Array\n     */\n    toFloat32Array(): Float32Array;\n    /**\n     * Convert to regular array\n     */\n    toArray(): number[];\n    /**\n     * Clone the tensor\n     */\n    clone(): EdgeFlowTensor;\n    /**\n     * Dispose the tensor and free memory\n     */\n    dispose(): void;\n    /**\n     * Get value at specific indices\n     */\n    get(...indices: number[]): number;\n    /**\n     * Set value at specific indices\n     */\n    set(value: number, ...indices: number[]): void;\n    /**\n     * Reshape the tensor (returns new tensor)\n     */\n    reshape(newShape: Shape): EdgeFlowTensor;\n    /**\n     * Transpose the tensor (2D only for now)\n     */\n    transpose(): EdgeFlowTensor;\n    /**\n     * Create string representation\n     */\n    toString(): string;\n}\n/**\n * Create a tensor from data\n */\nexport declare function tensor(data: TypedArray | number[] | number[][], shape?: Shape, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a tensor filled with zeros\n */\nexport declare function zeros(shape: Shape, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a tensor filled with ones\n */\nexport declare function ones(shape: Shape, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a tensor filled with a specific value\n */\nexport declare function full(shape: Shape, value: number, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a tensor with random values between 0 and 1\n */\nexport declare function random(shape: Shape, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a tensor with random values from normal distribution\n */\nexport declare function randn(shape: Shape, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a 1D tensor with evenly spaced values\n */\nexport declare function arange(start: number, stop?: number, step?: number, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create a 1D tensor with evenly spaced values (specify number of points)\n */\nexport declare function linspace(start: number, stop: number, num?: number, dtype?: DataType): EdgeFlowTensor;\n/**\n * Create an identity matrix\n */\nexport declare function eye(n: number, dtype?: DataType): EdgeFlowTensor;\n/**\n * Element-wise addition\n */\nexport declare function add(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor;\n/**\n * Element-wise subtraction\n */\nexport declare function sub(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor;\n/**\n * Element-wise multiplication\n */\nexport declare function mul(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor;\n/**\n * Element-wise division\n */\nexport declare function div(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor;\n/**\n * Matrix multiplication (2D tensors)\n */\nexport declare function matmul(a: EdgeFlowTensor, b: EdgeFlowTensor): EdgeFlowTensor;\n/**\n * Softmax activation\n */\nexport declare function softmax(t: EdgeFlowTensor, axis?: number): EdgeFlowTensor;\n/**\n * ReLU activation\n */\nexport declare function relu(t: EdgeFlowTensor): EdgeFlowTensor;\n/**\n * Sigmoid activation\n */\nexport declare function sigmoid(t: EdgeFlowTensor): EdgeFlowTensor;\n/**\n * Tanh activation\n */\nexport declare function tanh(t: EdgeFlowTensor): EdgeFlowTensor;\n/**\n * Sum all elements or along an axis\n */\nexport declare function sum(t: EdgeFlowTensor, axis?: number): EdgeFlowTensor | number;\n/**\n * Mean of all elements or along an axis\n */\nexport declare function mean(t: EdgeFlowTensor, axis?: number): EdgeFlowTensor | number;\n/**\n * Argmax - return index of maximum value\n */\nexport declare function argmax(t: EdgeFlowTensor, axis?: number): number | EdgeFlowTensor;\n/**\n * Concatenate tensors along an axis\n */\nexport declare function concat(tensors: EdgeFlowTensor[], axis?: number): EdgeFlowTensor;\n//# sourceMappingURL=tensor.d.ts.map"
  },
  {
    "path": "dist/core/tensor.js",
    "content": "/**\n * edgeFlow.js - Tensor Implementation\n *\n * Lightweight tensor implementation with efficient memory management.\n */\nimport { EdgeFlowError, ErrorCodes } from './types.js';\n// Counter for generating unique tensor IDs\nlet tensorIdCounter = 0;\n/**\n * Generate a unique tensor ID\n */\nfunction generateTensorId() {\n    return `tensor_${++tensorIdCounter}_${Date.now().toString(36)}`;\n}\n/**\n * Get the typed array constructor for a data type\n */\nfunction getTypedArrayConstructor(dtype) {\n    switch (dtype) {\n        case 'float32':\n            return Float32Array;\n        case 'float16':\n            // Float16 not natively supported, use Float32Array\n            return Float32Array;\n        case 'int32':\n            return Int32Array;\n        case 'int64':\n            return BigInt64Array;\n        case 'uint8':\n        case 'bool':\n            return Uint8Array;\n        case 'int8':\n            return Int8Array;\n        default:\n            throw new EdgeFlowError(`Unsupported data type: ${dtype}`, ErrorCodes.INVALID_ARGUMENT, { dtype });\n    }\n}\n/**\n * Calculate the total number of elements from shape\n */\nfunction calculateSize(shape) {\n    if (shape.length === 0)\n        return 1; // Scalar\n    return shape.reduce((acc, dim) => acc * dim, 1);\n}\n/**\n * Validate tensor shape\n */\nfunction validateShape(shape) {\n    for (let i = 0; i < shape.length; i++) {\n        const dim = shape[i];\n        if (dim === undefined || !Number.isInteger(dim) || dim < 0) {\n            throw new EdgeFlowError(`Invalid shape dimension at index ${i}: ${dim}`, ErrorCodes.INVALID_ARGUMENT, { shape, index: i, dimension: dim });\n        }\n    }\n}\n/**\n * EdgeFlowTensor - Core tensor implementation\n */\nexport class EdgeFlowTensor {\n    id;\n    dtype;\n    shape;\n    size;\n    _data;\n    _isDisposed = false;\n    constructor(data, shape, dtype = 'float32') {\n        validateShape(shape);\n        this.id = generateTensorId();\n        this.dtype = dtype;\n        this.shape = Object.freeze([...shape]);\n        this.size = calculateSize(this.shape);\n        // Validate data size matches shape\n        const expectedSize = this.size;\n        if (data.length !== expectedSize) {\n            throw new EdgeFlowError(`Data length (${data.length}) does not match shape ${JSON.stringify(shape)} (expected ${expectedSize})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { dataLength: data.length, expectedSize, shape });\n        }\n        // Convert to appropriate typed array\n        if (data instanceof Array) {\n            const TypedArrayCtor = getTypedArrayConstructor(dtype);\n            this._data = new TypedArrayCtor(data.length);\n            if (dtype === 'int64') {\n                // BigInt64Array requires BigInt values\n                const bigIntData = this._data;\n                for (let i = 0; i < data.length; i++) {\n                    bigIntData[i] = BigInt(Math.round(data[i] ?? 0));\n                }\n            }\n            else {\n                for (let i = 0; i < data.length; i++) {\n                    this._data[i] = data[i] ?? 0;\n                }\n            }\n        }\n        else {\n            this._data = data;\n        }\n    }\n    get data() {\n        this.checkDisposed();\n        return this._data;\n    }\n    get isDisposed() {\n        return this._isDisposed;\n    }\n    /**\n     * Check if tensor has been disposed\n     */\n    checkDisposed() {\n        if (this._isDisposed) {\n            throw new EdgeFlowError('Cannot access disposed tensor', ErrorCodes.TENSOR_DISPOSED, { tensorId: this.id });\n        }\n    }\n    /**\n     * Convert to Float32Array\n     */\n    toFloat32Array() {\n        this.checkDisposed();\n        if (this._data instanceof Float32Array) {\n            return this._data;\n        }\n        const result = new Float32Array(this.size);\n        for (let i = 0; i < this.size; i++) {\n            result[i] = Number(this._data[i] ?? 0);\n        }\n        return result;\n    }\n    /**\n     * Convert to regular array\n     */\n    toArray() {\n        this.checkDisposed();\n        if (this.dtype === 'int64') {\n            // BigInt64Array needs special handling\n            const bigIntData = this._data;\n            const result = [];\n            for (let i = 0; i < bigIntData.length; i++) {\n                result.push(Number(bigIntData[i]));\n            }\n            return result;\n        }\n        return Array.from(this._data);\n    }\n    /**\n     * Clone the tensor\n     */\n    clone() {\n        this.checkDisposed();\n        const TypedArrayCtor = this._data.constructor;\n        const clonedData = new TypedArrayCtor(this._data);\n        return new EdgeFlowTensor(clonedData, this.shape, this.dtype);\n    }\n    /**\n     * Dispose the tensor and free memory\n     */\n    dispose() {\n        if (!this._isDisposed) {\n            this._isDisposed = true;\n            // Help garbage collection - use Object.assign to avoid type issues\n            Object.assign(this, { _data: null });\n        }\n    }\n    /**\n     * Get value at specific indices\n     */\n    get(...indices) {\n        this.checkDisposed();\n        if (indices.length !== this.shape.length) {\n            throw new EdgeFlowError(`Expected ${this.shape.length} indices, got ${indices.length}`, ErrorCodes.INVALID_ARGUMENT, { expectedIndices: this.shape.length, gotIndices: indices.length });\n        }\n        let flatIndex = 0;\n        let stride = 1;\n        for (let i = this.shape.length - 1; i >= 0; i--) {\n            const idx = indices[i] ?? 0;\n            const dim = this.shape[i] ?? 1;\n            if (idx < 0 || idx >= dim) {\n                throw new EdgeFlowError(`Index ${idx} out of bounds for dimension ${i} with size ${dim}`, ErrorCodes.INVALID_ARGUMENT, { index: idx, dimension: i, size: dim });\n            }\n            flatIndex += idx * stride;\n            stride *= dim;\n        }\n        return Number(this._data[flatIndex] ?? 0);\n    }\n    /**\n     * Set value at specific indices\n     */\n    set(value, ...indices) {\n        this.checkDisposed();\n        if (indices.length !== this.shape.length) {\n            throw new EdgeFlowError(`Expected ${this.shape.length} indices, got ${indices.length}`, ErrorCodes.INVALID_ARGUMENT, { expectedIndices: this.shape.length, gotIndices: indices.length });\n        }\n        let flatIndex = 0;\n        let stride = 1;\n        for (let i = this.shape.length - 1; i >= 0; i--) {\n            const idx = indices[i] ?? 0;\n            const dim = this.shape[i] ?? 1;\n            if (idx < 0 || idx >= dim) {\n                throw new EdgeFlowError(`Index ${idx} out of bounds for dimension ${i} with size ${dim}`, ErrorCodes.INVALID_ARGUMENT, { index: idx, dimension: i, size: dim });\n            }\n            flatIndex += idx * stride;\n            stride *= dim;\n        }\n        this._data[flatIndex] = value;\n    }\n    /**\n     * Reshape the tensor (returns new tensor)\n     */\n    reshape(newShape) {\n        this.checkDisposed();\n        const newSize = calculateSize(newShape);\n        if (newSize !== this.size) {\n            throw new EdgeFlowError(`Cannot reshape tensor of size ${this.size} to shape ${JSON.stringify(newShape)} (size ${newSize})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { currentSize: this.size, newSize, newShape });\n        }\n        const TypedArrayCtor = this._data.constructor;\n        const clonedData = new TypedArrayCtor(this._data);\n        return new EdgeFlowTensor(clonedData, newShape, this.dtype);\n    }\n    /**\n     * Transpose the tensor (2D only for now)\n     */\n    transpose() {\n        this.checkDisposed();\n        if (this.shape.length !== 2) {\n            throw new EdgeFlowError('Transpose is currently only supported for 2D tensors', ErrorCodes.NOT_IMPLEMENTED, { shape: this.shape });\n        }\n        const [rows, cols] = this.shape;\n        const result = new Float32Array(this.size);\n        for (let i = 0; i < rows; i++) {\n            for (let j = 0; j < cols; j++) {\n                result[j * rows + i] = Number(this._data[i * cols + j] ?? 0);\n            }\n        }\n        return new EdgeFlowTensor(result, [cols, rows], this.dtype);\n    }\n    /**\n     * Create string representation\n     */\n    toString() {\n        return `Tensor(shape=[${this.shape.join(', ')}], dtype=${this.dtype})`;\n    }\n}\n// ============================================================================\n// Tensor Factory Functions\n// ============================================================================\n/**\n * Create a tensor from data\n */\nexport function tensor(data, shape, dtype = 'float32') {\n    // Handle nested arrays\n    if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {\n        const rows = data.length;\n        const cols = data[0].length;\n        const flatData = [];\n        for (const row of data) {\n            if (row.length !== cols) {\n                throw new EdgeFlowError('Nested arrays must have consistent dimensions', ErrorCodes.INVALID_ARGUMENT);\n            }\n            flatData.push(...row);\n        }\n        return new EdgeFlowTensor(flatData, shape ?? [rows, cols], dtype);\n    }\n    // Infer shape if not provided\n    const inferredShape = shape ?? [data.length];\n    return new EdgeFlowTensor(data, inferredShape, dtype);\n}\n/**\n * Create a tensor filled with zeros\n */\nexport function zeros(shape, dtype = 'float32') {\n    const size = calculateSize(shape);\n    const TypedArrayCtor = getTypedArrayConstructor(dtype);\n    const data = new TypedArrayCtor(size);\n    return new EdgeFlowTensor(data, shape, dtype);\n}\n/**\n * Create a tensor filled with ones\n */\nexport function ones(shape, dtype = 'float32') {\n    const size = calculateSize(shape);\n    const TypedArrayCtor = getTypedArrayConstructor(dtype);\n    const data = new TypedArrayCtor(size);\n    data.fill(1);\n    return new EdgeFlowTensor(data, shape, dtype);\n}\n/**\n * Create a tensor filled with a specific value\n */\nexport function full(shape, value, dtype = 'float32') {\n    const size = calculateSize(shape);\n    const TypedArrayCtor = getTypedArrayConstructor(dtype);\n    const data = new TypedArrayCtor(size);\n    data.fill(value);\n    return new EdgeFlowTensor(data, shape, dtype);\n}\n/**\n * Create a tensor with random values between 0 and 1\n */\nexport function random(shape, dtype = 'float32') {\n    const size = calculateSize(shape);\n    const data = new Float32Array(size);\n    for (let i = 0; i < size; i++) {\n        data[i] = Math.random();\n    }\n    return new EdgeFlowTensor(data, shape, dtype);\n}\n/**\n * Create a tensor with random values from normal distribution\n */\nexport function randn(shape, dtype = 'float32') {\n    const size = calculateSize(shape);\n    const data = new Float32Array(size);\n    // Box-Muller transform for normal distribution\n    for (let i = 0; i < size; i += 2) {\n        const u1 = Math.random();\n        const u2 = Math.random();\n        const r = Math.sqrt(-2 * Math.log(u1));\n        const theta = 2 * Math.PI * u2;\n        data[i] = r * Math.cos(theta);\n        if (i + 1 < size) {\n            data[i + 1] = r * Math.sin(theta);\n        }\n    }\n    return new EdgeFlowTensor(data, shape, dtype);\n}\n/**\n * Create a 1D tensor with evenly spaced values\n */\nexport function arange(start, stop, step = 1, dtype = 'float32') {\n    if (stop === undefined) {\n        stop = start;\n        start = 0;\n    }\n    const size = Math.ceil((stop - start) / step);\n    const data = new Float32Array(size);\n    for (let i = 0; i < size; i++) {\n        data[i] = start + i * step;\n    }\n    return new EdgeFlowTensor(data, [size], dtype);\n}\n/**\n * Create a 1D tensor with evenly spaced values (specify number of points)\n */\nexport function linspace(start, stop, num = 50, dtype = 'float32') {\n    const data = new Float32Array(num);\n    const step = (stop - start) / (num - 1);\n    for (let i = 0; i < num; i++) {\n        data[i] = start + i * step;\n    }\n    return new EdgeFlowTensor(data, [num], dtype);\n}\n/**\n * Create an identity matrix\n */\nexport function eye(n, dtype = 'float32') {\n    const data = new Float32Array(n * n);\n    for (let i = 0; i < n; i++) {\n        data[i * n + i] = 1;\n    }\n    return new EdgeFlowTensor(data, [n, n], dtype);\n}\n// ============================================================================\n// Tensor Operations\n// ============================================================================\n/**\n * Element-wise addition\n */\nexport function add(a, b) {\n    if (typeof b === 'number') {\n        const result = new Float32Array(a.size);\n        const aData = a.toFloat32Array();\n        for (let i = 0; i < a.size; i++) {\n            result[i] = (aData[i] ?? 0) + b;\n        }\n        return new EdgeFlowTensor(result, a.shape, a.dtype);\n    }\n    if (a.size !== b.size) {\n        throw new EdgeFlowError('Tensor sizes must match for element-wise operations', ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n    }\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    const bData = b.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n        result[i] = (aData[i] ?? 0) + (bData[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n/**\n * Element-wise subtraction\n */\nexport function sub(a, b) {\n    if (typeof b === 'number') {\n        const result = new Float32Array(a.size);\n        const aData = a.toFloat32Array();\n        for (let i = 0; i < a.size; i++) {\n            result[i] = (aData[i] ?? 0) - b;\n        }\n        return new EdgeFlowTensor(result, a.shape, a.dtype);\n    }\n    if (a.size !== b.size) {\n        throw new EdgeFlowError('Tensor sizes must match for element-wise operations', ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n    }\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    const bData = b.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n        result[i] = (aData[i] ?? 0) - (bData[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n/**\n * Element-wise multiplication\n */\nexport function mul(a, b) {\n    if (typeof b === 'number') {\n        const result = new Float32Array(a.size);\n        const aData = a.toFloat32Array();\n        for (let i = 0; i < a.size; i++) {\n            result[i] = (aData[i] ?? 0) * b;\n        }\n        return new EdgeFlowTensor(result, a.shape, a.dtype);\n    }\n    if (a.size !== b.size) {\n        throw new EdgeFlowError('Tensor sizes must match for element-wise operations', ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n    }\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    const bData = b.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n        result[i] = (aData[i] ?? 0) * (bData[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n/**\n * Element-wise division\n */\nexport function div(a, b) {\n    if (typeof b === 'number') {\n        const result = new Float32Array(a.size);\n        const aData = a.toFloat32Array();\n        for (let i = 0; i < a.size; i++) {\n            result[i] = (aData[i] ?? 0) / b;\n        }\n        return new EdgeFlowTensor(result, a.shape, a.dtype);\n    }\n    if (a.size !== b.size) {\n        throw new EdgeFlowError('Tensor sizes must match for element-wise operations', ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n    }\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    const bData = b.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n        result[i] = (aData[i] ?? 0) / (bData[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n/**\n * Matrix multiplication (2D tensors)\n */\nexport function matmul(a, b) {\n    if (a.shape.length !== 2 || b.shape.length !== 2) {\n        throw new EdgeFlowError('matmul requires 2D tensors', ErrorCodes.INVALID_ARGUMENT, { aShape: a.shape, bShape: b.shape });\n    }\n    const [m, k1] = a.shape;\n    const [k2, n] = b.shape;\n    if (k1 !== k2) {\n        throw new EdgeFlowError(`Matrix dimensions incompatible for multiplication: (${m}x${k1}) @ (${k2}x${n})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n    }\n    const result = new Float32Array(m * n);\n    const aData = a.toFloat32Array();\n    const bData = b.toFloat32Array();\n    for (let i = 0; i < m; i++) {\n        for (let j = 0; j < n; j++) {\n            let sum = 0;\n            for (let k = 0; k < k1; k++) {\n                sum += (aData[i * k1 + k] ?? 0) * (bData[k * n + j] ?? 0);\n            }\n            result[i * n + j] = sum;\n        }\n    }\n    return new EdgeFlowTensor(result, [m, n], a.dtype);\n}\n/**\n * Softmax activation\n */\nexport function softmax(t, axis = -1) {\n    const data = t.toFloat32Array();\n    const result = new Float32Array(t.size);\n    // Handle negative axis\n    const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n    if (actualAxis < 0 || actualAxis >= t.shape.length) {\n        throw new EdgeFlowError(`Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`, ErrorCodes.INVALID_ARGUMENT, { axis, shape: t.shape });\n    }\n    // For 1D tensors\n    if (t.shape.length === 1) {\n        let max = -Infinity;\n        for (let i = 0; i < t.size; i++) {\n            if ((data[i] ?? 0) > max)\n                max = data[i] ?? 0;\n        }\n        let sum = 0;\n        for (let i = 0; i < t.size; i++) {\n            result[i] = Math.exp((data[i] ?? 0) - max);\n            sum += result[i] ?? 0;\n        }\n        for (let i = 0; i < t.size; i++) {\n            result[i] = (result[i] ?? 0) / sum;\n        }\n        return new EdgeFlowTensor(result, t.shape, t.dtype);\n    }\n    // For 2D tensors along last axis\n    if (t.shape.length === 2 && actualAxis === 1) {\n        const [rows, cols] = t.shape;\n        for (let i = 0; i < rows; i++) {\n            let max = -Infinity;\n            for (let j = 0; j < cols; j++) {\n                if ((data[i * cols + j] ?? 0) > max)\n                    max = data[i * cols + j] ?? 0;\n            }\n            let sum = 0;\n            for (let j = 0; j < cols; j++) {\n                result[i * cols + j] = Math.exp((data[i * cols + j] ?? 0) - max);\n                sum += result[i * cols + j] ?? 0;\n            }\n            for (let j = 0; j < cols; j++) {\n                result[i * cols + j] = (result[i * cols + j] ?? 0) / sum;\n            }\n        }\n        return new EdgeFlowTensor(result, t.shape, t.dtype);\n    }\n    throw new EdgeFlowError('Softmax currently only supports 1D tensors or 2D tensors along the last axis', ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\n/**\n * ReLU activation\n */\nexport function relu(t) {\n    const data = t.toFloat32Array();\n    const result = new Float32Array(t.size);\n    for (let i = 0; i < t.size; i++) {\n        result[i] = Math.max(0, data[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n/**\n * Sigmoid activation\n */\nexport function sigmoid(t) {\n    const data = t.toFloat32Array();\n    const result = new Float32Array(t.size);\n    for (let i = 0; i < t.size; i++) {\n        result[i] = 1 / (1 + Math.exp(-(data[i] ?? 0)));\n    }\n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n/**\n * Tanh activation\n */\nexport function tanh(t) {\n    const data = t.toFloat32Array();\n    const result = new Float32Array(t.size);\n    for (let i = 0; i < t.size; i++) {\n        result[i] = Math.tanh(data[i] ?? 0);\n    }\n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n/**\n * Sum all elements or along an axis\n */\nexport function sum(t, axis) {\n    const data = t.toFloat32Array();\n    if (axis === undefined) {\n        let total = 0;\n        for (let i = 0; i < t.size; i++) {\n            total += data[i] ?? 0;\n        }\n        return total;\n    }\n    // Handle negative axis\n    const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n    if (actualAxis < 0 || actualAxis >= t.shape.length) {\n        throw new EdgeFlowError(`Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`, ErrorCodes.INVALID_ARGUMENT, { axis, shape: t.shape });\n    }\n    // Calculate new shape\n    const newShape = [...t.shape];\n    newShape.splice(actualAxis, 1);\n    if (newShape.length === 0) {\n        let total = 0;\n        for (let i = 0; i < t.size; i++) {\n            total += data[i] ?? 0;\n        }\n        return total;\n    }\n    // For 2D sum along axis\n    if (t.shape.length === 2) {\n        const [rows, cols] = t.shape;\n        if (actualAxis === 0) {\n            const result = new Float32Array(cols);\n            for (let j = 0; j < cols; j++) {\n                for (let i = 0; i < rows; i++) {\n                    result[j] = (result[j] ?? 0) + (data[i * cols + j] ?? 0);\n                }\n            }\n            return new EdgeFlowTensor(result, [cols], t.dtype);\n        }\n        else {\n            const result = new Float32Array(rows);\n            for (let i = 0; i < rows; i++) {\n                for (let j = 0; j < cols; j++) {\n                    result[i] = (result[i] ?? 0) + (data[i * cols + j] ?? 0);\n                }\n            }\n            return new EdgeFlowTensor(result, [rows], t.dtype);\n        }\n    }\n    throw new EdgeFlowError('Sum along axis currently only supports up to 2D tensors', ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\n/**\n * Mean of all elements or along an axis\n */\nexport function mean(t, axis) {\n    if (axis === undefined) {\n        return sum(t) / t.size;\n    }\n    const result = sum(t, axis);\n    if (typeof result === 'number') {\n        return result / (t.shape[axis] ?? 1);\n    }\n    const axisSize = t.shape[axis] ?? 1;\n    return div(result, axisSize);\n}\n/**\n * Argmax - return index of maximum value\n */\nexport function argmax(t, axis) {\n    const data = t.toFloat32Array();\n    if (axis === undefined) {\n        let maxIdx = 0;\n        let maxVal = data[0] ?? -Infinity;\n        for (let i = 1; i < t.size; i++) {\n            if ((data[i] ?? -Infinity) > maxVal) {\n                maxVal = data[i] ?? -Infinity;\n                maxIdx = i;\n            }\n        }\n        return maxIdx;\n    }\n    // Handle negative axis\n    const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n    // For 2D along last axis\n    if (t.shape.length === 2 && actualAxis === 1) {\n        const [rows, cols] = t.shape;\n        const result = new Float32Array(rows);\n        for (let i = 0; i < rows; i++) {\n            let maxIdx = 0;\n            let maxVal = data[i * cols] ?? -Infinity;\n            for (let j = 1; j < cols; j++) {\n                if ((data[i * cols + j] ?? -Infinity) > maxVal) {\n                    maxVal = data[i * cols + j] ?? -Infinity;\n                    maxIdx = j;\n                }\n            }\n            result[i] = maxIdx;\n        }\n        return new EdgeFlowTensor(result, [rows], 'int32');\n    }\n    throw new EdgeFlowError('Argmax along axis currently only supports 2D tensors along the last axis', ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\n/**\n * Concatenate tensors along an axis\n */\nexport function concat(tensors, axis = 0) {\n    if (tensors.length === 0) {\n        throw new EdgeFlowError('Cannot concatenate empty array of tensors', ErrorCodes.INVALID_ARGUMENT);\n    }\n    if (tensors.length === 1) {\n        return tensors[0]?.clone() ?? zeros([0]);\n    }\n    const first = tensors[0];\n    if (!first) {\n        throw new EdgeFlowError('First tensor is undefined', ErrorCodes.INVALID_ARGUMENT);\n    }\n    // Handle negative axis\n    const actualAxis = axis < 0 ? first.shape.length + axis : axis;\n    // Validate shapes\n    for (let i = 1; i < tensors.length; i++) {\n        const t = tensors[i];\n        if (!t)\n            continue;\n        if (t.shape.length !== first.shape.length) {\n            throw new EdgeFlowError('All tensors must have the same number of dimensions', ErrorCodes.TENSOR_SHAPE_MISMATCH);\n        }\n        for (let j = 0; j < first.shape.length; j++) {\n            if (j !== actualAxis && first.shape[j] !== t.shape[j]) {\n                throw new EdgeFlowError(`Shape mismatch at dimension ${j}`, ErrorCodes.TENSOR_SHAPE_MISMATCH);\n            }\n        }\n    }\n    // Calculate new shape\n    const newShape = [...first.shape];\n    let totalAxisSize = 0;\n    for (const t of tensors) {\n        if (t)\n            totalAxisSize += t.shape[actualAxis] ?? 0;\n    }\n    newShape[actualAxis] = totalAxisSize;\n    // For 1D concatenation\n    if (first.shape.length === 1) {\n        const result = new Float32Array(totalAxisSize);\n        let offset = 0;\n        for (const t of tensors) {\n            if (!t)\n                continue;\n            result.set(t.toFloat32Array(), offset);\n            offset += t.size;\n        }\n        return new EdgeFlowTensor(result, newShape, first.dtype);\n    }\n    throw new EdgeFlowError('Concatenation currently only supports 1D tensors', ErrorCodes.NOT_IMPLEMENTED);\n}\n//# sourceMappingURL=tensor.js.map"
  },
  {
    "path": "dist/core/types.d.ts",
    "content": "/**\n * edgeFlow.js - Core Type Definitions\n *\n * This file contains all the core types used throughout the framework.\n */\n/**\n * Supported data types for tensors\n */\nexport type DataType = 'float32' | 'float16' | 'int32' | 'int64' | 'uint8' | 'int8' | 'bool';\n/**\n * TypedArray types used for tensor data\n */\nexport type TypedArray = Float32Array | Float64Array | Int32Array | BigInt64Array | Uint8Array | Int8Array;\n/**\n * Tensor shape definition\n */\nexport type Shape = readonly number[];\n/**\n * Tensor interface\n */\nexport interface Tensor {\n    /** Unique identifier for the tensor */\n    readonly id: string;\n    /** Data type of the tensor */\n    readonly dtype: DataType;\n    /** Shape of the tensor */\n    readonly shape: Shape;\n    /** Total number of elements */\n    readonly size: number;\n    /** Underlying data */\n    readonly data: TypedArray;\n    /** Get data as Float32Array */\n    toFloat32Array(): Float32Array;\n    /** Get data as array */\n    toArray(): number[];\n    /** Clone the tensor */\n    clone(): Tensor;\n    /** Dispose the tensor and free memory */\n    dispose(): void;\n    /** Check if tensor has been disposed */\n    readonly isDisposed: boolean;\n}\n/**\n * Supported runtime backends\n */\nexport type RuntimeType = 'webgpu' | 'webnn' | 'wasm' | 'auto';\n/**\n * Runtime capability flags\n */\nexport interface RuntimeCapabilities {\n    /** Supports concurrent execution */\n    concurrency: boolean;\n    /** Supports quantized models */\n    quantization: boolean;\n    /** Supports float16 */\n    float16: boolean;\n    /** Supports dynamic shapes */\n    dynamicShapes: boolean;\n    /** Maximum batch size */\n    maxBatchSize: number;\n    /** Available memory in bytes */\n    availableMemory: number;\n}\n/**\n * Runtime interface that all backends must implement\n */\nexport interface Runtime {\n    /** Runtime name */\n    readonly name: RuntimeType;\n    /** Runtime capabilities */\n    readonly capabilities: RuntimeCapabilities;\n    /** Initialize the runtime */\n    initialize(): Promise<void>;\n    /** Check if runtime is available in current environment */\n    isAvailable(): Promise<boolean>;\n    /** Load a model from ArrayBuffer */\n    loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n    /** Run inference */\n    run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n    /** Run inference with named inputs (optional) */\n    runNamed?(model: LoadedModel, namedInputs: Map<string, Tensor>): Promise<Tensor[]>;\n    /** Dispose the runtime and free resources */\n    dispose(): void;\n}\n/**\n * Model format types\n */\nexport type ModelFormat = 'onnx' | 'edgeflow' | 'safetensors';\n/**\n * Model quantization types\n */\nexport type QuantizationType = 'float32' | 'float16' | 'int8' | 'uint8' | 'int4';\n/**\n * Model metadata\n */\nexport interface ModelMetadata {\n    /** Model name/identifier */\n    name: string;\n    /** Model version */\n    version?: string;\n    /** Model description */\n    description?: string;\n    /** Model author */\n    author?: string;\n    /** Model license */\n    license?: string;\n    /** Model tags */\n    tags?: string[];\n    /** Input specifications */\n    inputs: ModelIOSpec[];\n    /** Output specifications */\n    outputs: ModelIOSpec[];\n    /** Model size in bytes */\n    sizeBytes: number;\n    /** Quantization type */\n    quantization: QuantizationType;\n    /** Model format */\n    format: ModelFormat;\n}\n/**\n * Model input/output specification\n */\nexport interface ModelIOSpec {\n    /** Name of the input/output */\n    name: string;\n    /** Data type */\n    dtype: DataType;\n    /** Shape (use -1 for dynamic dimensions) */\n    shape: number[];\n    /** Optional description */\n    description?: string;\n}\n/**\n * Options for loading a model\n */\nexport interface ModelLoadOptions {\n    /** Target quantization (convert during load) */\n    quantization?: QuantizationType;\n    /** Custom metadata */\n    metadata?: Partial<ModelMetadata>;\n    /** Enable caching */\n    cache?: boolean;\n    /** Progress callback */\n    onProgress?: (progress: number) => void;\n}\n/**\n * Loaded model instance\n */\nexport interface LoadedModel {\n    /** Unique model instance ID */\n    readonly id: string;\n    /** Model metadata */\n    readonly metadata: ModelMetadata;\n    /** Check if model is loaded */\n    readonly isLoaded: boolean;\n    /** Runtime this model is loaded on */\n    readonly runtime: RuntimeType;\n    /** Dispose the model and free resources */\n    dispose(): void;\n}\n/**\n * Task priority levels\n */\nexport type TaskPriority = 'low' | 'normal' | 'high' | 'critical';\n/**\n * Task status\n */\nexport type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n/**\n * Inference task definition\n */\nexport interface InferenceTask<T = unknown> {\n    /** Unique task ID */\n    readonly id: string;\n    /** Model ID this task is for */\n    readonly modelId: string;\n    /** Task priority */\n    readonly priority: TaskPriority;\n    /** Task status */\n    readonly status: TaskStatus;\n    /** Creation timestamp */\n    readonly createdAt: number;\n    /** Start timestamp (when running) */\n    readonly startedAt?: number;\n    /** Completion timestamp */\n    readonly completedAt?: number;\n    /** Task result (when completed) */\n    readonly result?: T;\n    /** Task error (when failed) */\n    readonly error?: Error;\n    /** Cancel the task */\n    cancel(): void;\n    /** Wait for task completion */\n    wait(): Promise<T>;\n}\n/**\n * Scheduler options\n */\nexport interface SchedulerOptions {\n    /** Maximum concurrent tasks across all models */\n    maxConcurrentTasks?: number;\n    /** Maximum concurrent tasks per model */\n    maxConcurrentPerModel?: number;\n    /** Default task timeout in milliseconds */\n    defaultTimeout?: number;\n    /** Enable task batching */\n    enableBatching?: boolean;\n    /** Maximum batch size */\n    maxBatchSize?: number;\n    /** Batch timeout in milliseconds */\n    batchTimeout?: number;\n    /** Maximum retry attempts for failed tasks (default: 0 = no retry) */\n    maxRetries?: number;\n    /** Base delay between retries in ms (exponential backoff) */\n    retryBaseDelay?: number;\n    /** Enable circuit breaker per model (default: false) */\n    circuitBreaker?: boolean;\n    /** Consecutive failures before the circuit opens (default: 5) */\n    circuitBreakerThreshold?: number;\n    /** Time in ms before the circuit half-opens to test (default: 30000) */\n    circuitBreakerResetTimeout?: number;\n}\n/**\n * Memory statistics\n */\nexport interface MemoryStats {\n    /** Total allocated memory in bytes */\n    allocated: number;\n    /** Currently used memory in bytes */\n    used: number;\n    /** Peak memory usage in bytes */\n    peak: number;\n    /** Number of active tensors */\n    tensorCount: number;\n    /** Number of loaded models */\n    modelCount: number;\n}\n/**\n * Memory pool configuration\n */\nexport interface MemoryPoolConfig {\n    /** Initial pool size in bytes */\n    initialSize?: number;\n    /** Maximum pool size in bytes */\n    maxSize?: number;\n    /** Growth factor when expanding */\n    growthFactor?: number;\n    /** Enable automatic garbage collection */\n    autoGC?: boolean;\n    /** GC threshold (percentage of max size) */\n    gcThreshold?: number;\n}\n/**\n * Supported pipeline tasks\n */\nexport type PipelineTask = 'text-classification' | 'token-classification' | 'question-answering' | 'fill-mask' | 'text-generation' | 'text2text-generation' | 'summarization' | 'translation' | 'feature-extraction' | 'sentiment-analysis' | 'zero-shot-classification' | 'image-classification' | 'object-detection' | 'image-segmentation' | 'depth-estimation' | 'image-to-text' | 'audio-classification' | 'automatic-speech-recognition' | 'text-to-speech';\n/**\n * Pipeline configuration\n */\nexport interface PipelineConfig {\n    /** Task type */\n    task: PipelineTask;\n    /** Model ID or path */\n    model: string;\n    /** Runtime to use */\n    runtime?: RuntimeType;\n    /** Enable caching */\n    cache?: boolean;\n    /** Quantization type */\n    quantization?: QuantizationType;\n    /** Device to use */\n    device?: 'cpu' | 'gpu';\n    /** Custom tokenizer config */\n    tokenizer?: TokenizerConfig;\n}\n/**\n * Pipeline options passed during inference\n */\nexport interface PipelineOptions {\n    /** Batch size */\n    batchSize?: number;\n    /** Top K results */\n    topK?: number;\n    /** Temperature for generation */\n    temperature?: number;\n    /** Maximum length for generation */\n    maxLength?: number;\n    /** Task timeout in milliseconds */\n    timeout?: number;\n}\n/**\n * Tokenizer configuration\n */\nexport interface TokenizerConfig {\n    /** Vocabulary size */\n    vocabSize: number;\n    /** Maximum sequence length */\n    maxLength: number;\n    /** Padding token ID */\n    padTokenId: number;\n    /** Unknown token ID */\n    unkTokenId: number;\n    /** Start of sequence token ID */\n    bosTokenId?: number;\n    /** End of sequence token ID */\n    eosTokenId?: number;\n    /** Separator token ID */\n    sepTokenId?: number;\n    /** CLS token ID */\n    clsTokenId?: number;\n    /** Mask token ID */\n    maskTokenId?: number;\n}\n/**\n * Tokenized output\n */\nexport interface TokenizedOutput {\n    /** Input IDs */\n    inputIds: number[];\n    /** Attention mask */\n    attentionMask: number[];\n    /** Token type IDs (for segment embeddings) */\n    tokenTypeIds?: number[];\n    /** Special tokens mask */\n    specialTokensMask?: number[];\n    /** Offset mapping (for token-level tasks) */\n    offsetMapping?: [number, number][];\n}\n/**\n * Base error class for edgeFlow errors\n */\nexport declare class EdgeFlowError extends Error {\n    readonly code: string;\n    readonly details?: Record<string, unknown> | undefined;\n    constructor(message: string, code: string, details?: Record<string, unknown> | undefined);\n}\n/**\n * Error codes\n */\nexport declare const ErrorCodes: {\n    readonly RUNTIME_NOT_AVAILABLE: \"RUNTIME_NOT_AVAILABLE\";\n    readonly RUNTIME_INIT_FAILED: \"RUNTIME_INIT_FAILED\";\n    readonly RUNTIME_NOT_INITIALIZED: \"RUNTIME_NOT_INITIALIZED\";\n    readonly MODEL_NOT_FOUND: \"MODEL_NOT_FOUND\";\n    readonly MODEL_LOAD_FAILED: \"MODEL_LOAD_FAILED\";\n    readonly MODEL_INVALID_FORMAT: \"MODEL_INVALID_FORMAT\";\n    readonly MODEL_NOT_LOADED: \"MODEL_NOT_LOADED\";\n    readonly INFERENCE_FAILED: \"INFERENCE_FAILED\";\n    readonly INFERENCE_TIMEOUT: \"INFERENCE_TIMEOUT\";\n    readonly INFERENCE_CANCELLED: \"INFERENCE_CANCELLED\";\n    readonly OUT_OF_MEMORY: \"OUT_OF_MEMORY\";\n    readonly MEMORY_LEAK_DETECTED: \"MEMORY_LEAK_DETECTED\";\n    readonly TENSOR_SHAPE_MISMATCH: \"TENSOR_SHAPE_MISMATCH\";\n    readonly TENSOR_DTYPE_MISMATCH: \"TENSOR_DTYPE_MISMATCH\";\n    readonly TENSOR_DISPOSED: \"TENSOR_DISPOSED\";\n    readonly PIPELINE_NOT_SUPPORTED: \"PIPELINE_NOT_SUPPORTED\";\n    readonly PIPELINE_INPUT_INVALID: \"PIPELINE_INPUT_INVALID\";\n    readonly INVALID_ARGUMENT: \"INVALID_ARGUMENT\";\n    readonly NOT_IMPLEMENTED: \"NOT_IMPLEMENTED\";\n    readonly UNKNOWN_ERROR: \"UNKNOWN_ERROR\";\n};\nexport type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];\n/**\n * Event types emitted by edgeFlow\n */\nexport type EventType = 'model:loading' | 'model:loaded' | 'model:unloaded' | 'inference:start' | 'inference:complete' | 'inference:error' | 'memory:warning' | 'memory:gc' | 'runtime:ready' | 'runtime:error';\n/**\n * Event payload interface\n */\nexport interface EdgeFlowEvent<T = unknown> {\n    type: EventType;\n    timestamp: number;\n    data: T;\n}\n/**\n * Event listener function type\n */\nexport type EventListener<T = unknown> = (event: EdgeFlowEvent<T>) => void;\n//# sourceMappingURL=types.d.ts.map"
  },
  {
    "path": "dist/core/types.js",
    "content": "/**\n * edgeFlow.js - Core Type Definitions\n *\n * This file contains all the core types used throughout the framework.\n */\n// ============================================================================\n// Error Types\n// ============================================================================\n/**\n * Base error class for edgeFlow errors\n */\nexport class EdgeFlowError extends Error {\n    code;\n    details;\n    constructor(message, code, details) {\n        super(message);\n        this.code = code;\n        this.details = details;\n        this.name = 'EdgeFlowError';\n    }\n}\n/**\n * Error codes\n */\nexport const ErrorCodes = {\n    // Runtime errors\n    RUNTIME_NOT_AVAILABLE: 'RUNTIME_NOT_AVAILABLE',\n    RUNTIME_INIT_FAILED: 'RUNTIME_INIT_FAILED',\n    RUNTIME_NOT_INITIALIZED: 'RUNTIME_NOT_INITIALIZED',\n    // Model errors\n    MODEL_NOT_FOUND: 'MODEL_NOT_FOUND',\n    MODEL_LOAD_FAILED: 'MODEL_LOAD_FAILED',\n    MODEL_INVALID_FORMAT: 'MODEL_INVALID_FORMAT',\n    MODEL_NOT_LOADED: 'MODEL_NOT_LOADED',\n    // Inference errors\n    INFERENCE_FAILED: 'INFERENCE_FAILED',\n    INFERENCE_TIMEOUT: 'INFERENCE_TIMEOUT',\n    INFERENCE_CANCELLED: 'INFERENCE_CANCELLED',\n    // Memory errors\n    OUT_OF_MEMORY: 'OUT_OF_MEMORY',\n    MEMORY_LEAK_DETECTED: 'MEMORY_LEAK_DETECTED',\n    // Tensor errors\n    TENSOR_SHAPE_MISMATCH: 'TENSOR_SHAPE_MISMATCH',\n    TENSOR_DTYPE_MISMATCH: 'TENSOR_DTYPE_MISMATCH',\n    TENSOR_DISPOSED: 'TENSOR_DISPOSED',\n    // Pipeline errors\n    PIPELINE_NOT_SUPPORTED: 'PIPELINE_NOT_SUPPORTED',\n    PIPELINE_INPUT_INVALID: 'PIPELINE_INPUT_INVALID',\n    // General errors\n    INVALID_ARGUMENT: 'INVALID_ARGUMENT',\n    NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',\n    UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n};\n//# sourceMappingURL=types.js.map"
  },
  {
    "path": "dist/core/worker.d.ts",
    "content": "/**\n * edgeFlow.js - Web Worker Support\n *\n * Run inference in a Web Worker to avoid blocking the main thread.\n */\nimport type { Tensor, RuntimeType } from './types.js';\n/**\n * Worker message types\n */\nexport type WorkerMessageType = 'init' | 'load_model' | 'run_inference' | 'dispose' | 'ready' | 'result' | 'error' | 'progress';\n/**\n * Worker message structure\n */\nexport interface WorkerMessage {\n    id: string;\n    type: WorkerMessageType;\n    payload?: unknown;\n}\n/**\n * Worker request for loading a model\n */\nexport interface LoadModelRequest {\n    url: string;\n    options?: {\n        runtime?: RuntimeType;\n        cache?: boolean;\n    };\n}\n/**\n * Worker request for running inference\n */\nexport interface InferenceRequest {\n    modelId: string;\n    inputs: SerializedTensor[];\n}\n/**\n * Serialized tensor for transfer\n */\nexport interface SerializedTensor {\n    data: ArrayBuffer;\n    shape: number[];\n    dtype: string;\n}\n/**\n * Worker pool options\n */\nexport interface WorkerPoolOptions {\n    /** Number of workers (default: navigator.hardwareConcurrency or 4) */\n    numWorkers?: number;\n    /** Worker script URL (default: auto-detect) */\n    workerUrl?: string;\n}\n/**\n * Serialize a tensor for transfer to worker\n */\nexport declare function serializeTensor(tensor: Tensor): SerializedTensor;\n/**\n * Deserialize a tensor from worker.\n * Uses a lazy import to avoid circular dependency issues.\n */\nexport declare function deserializeTensor(serialized: SerializedTensor): Promise<Tensor>;\n/**\n * Synchronous deserialisation used internally where async is not feasible.\n * Requires EdgeFlowTensor to be passed in to avoid require().\n */\nexport declare function deserializeTensorSync(serialized: SerializedTensor, TensorClass: new (data: Float32Array, shape: number[], dtype: string) => Tensor): Tensor;\nexport type WorkerHealthState = 'alive' | 'dead' | 'restarting';\n/**\n * InferenceWorker - Wrapper for a single Web Worker with auto-restart\n */\nexport declare class InferenceWorker {\n    private worker;\n    private pendingRequests;\n    private isReady;\n    private readyPromise;\n    private readyResolve;\n    private workerUrl;\n    private _health;\n    private restartAttempts;\n    constructor(workerUrl?: string);\n    get health(): WorkerHealthState;\n    /**\n     * Initialize the worker\n     */\n    private initWorker;\n    /**\n     * Handle worker crash: reject pending, mark dead, attempt restart\n     */\n    private handleCrash;\n    /**\n     * Restart the worker with exponential backoff\n     */\n    private attemptRestart;\n    /**\n     * Restart: terminate old, create new\n     */\n    restart(): void;\n    /**\n     * Create worker code as blob URL\n     */\n    private createWorkerBlob;\n    /**\n     * Handle worker message\n     */\n    private handleMessage;\n    /**\n     * Send a request to the worker\n     */\n    private sendRequest;\n    /**\n     * Initialize the worker\n     */\n    init(): Promise<void>;\n    /**\n     * Load a model\n     */\n    loadModel(url: string, options?: {\n        runtime?: RuntimeType;\n        cache?: boolean;\n    }): Promise<string>;\n    /**\n     * Run inference\n     */\n    runInference(modelId: string, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Dispose a model\n     */\n    dispose(modelId: string): Promise<void>;\n    /**\n     * Terminate the worker\n     */\n    terminate(): void;\n}\n/**\n * WorkerPool - Manage multiple workers for parallel inference.\n * Automatically falls back to healthy workers when one is dead.\n */\nexport declare class WorkerPool {\n    private workers;\n    private currentIndex;\n    private modelAssignments;\n    private poolOptions;\n    constructor(options?: WorkerPoolOptions);\n    /**\n     * Get next healthy worker (round-robin, skipping dead ones)\n     */\n    private getNextHealthyWorker;\n    /**\n     * Get worker for a specific model, falling back to any healthy worker\n     */\n    private getWorkerForModel;\n    /**\n     * Replace a worker at a given index with a fresh one\n     */\n    replaceWorker(index: number): void;\n    /**\n     * Initialize all workers\n     */\n    init(): Promise<void>;\n    /**\n     * Load a model on a worker\n     */\n    loadModel(url: string, options?: {\n        runtime?: RuntimeType;\n        cache?: boolean;\n    }): Promise<string>;\n    /**\n     * Run inference (auto-retries on a healthy worker if assigned one is dead)\n     */\n    runInference(modelId: string, inputs: Tensor[]): Promise<Tensor[]>;\n    /**\n     * Run inference on multiple inputs in parallel\n     */\n    runBatch(modelId: string, batchInputs: Tensor[][]): Promise<Tensor[][]>;\n    /**\n     * Dispose a model\n     */\n    dispose(modelId: string): Promise<void>;\n    /**\n     * Terminate all workers\n     */\n    terminate(): void;\n    /**\n     * Get number of workers\n     */\n    get size(): number;\n}\n/**\n * Get or create global worker pool\n */\nexport declare function getWorkerPool(options?: WorkerPoolOptions): WorkerPool;\n/**\n * Run inference in a worker\n */\nexport declare function runInWorker(modelUrl: string, inputs: Tensor[], options?: {\n    cache?: boolean;\n}): Promise<Tensor[]>;\n/**\n * Check if Web Workers are supported\n */\nexport declare function isWorkerSupported(): boolean;\n//# sourceMappingURL=worker.d.ts.map"
  },
  {
    "path": "dist/core/worker.js",
    "content": "/**\n * edgeFlow.js - Web Worker Support\n *\n * Run inference in a Web Worker to avoid blocking the main thread.\n */\n// ============================================================================\n// Tensor Serialization\n// ============================================================================\n/**\n * Serialize a tensor for transfer to worker\n */\nexport function serializeTensor(tensor) {\n    const data = tensor.toFloat32Array();\n    // Create a copy of the ArrayBuffer\n    const buffer = new ArrayBuffer(data.byteLength);\n    new Float32Array(buffer).set(data);\n    return {\n        data: buffer,\n        shape: [...tensor.shape],\n        dtype: tensor.dtype,\n    };\n}\n/**\n * Deserialize a tensor from worker.\n * Uses a lazy import to avoid circular dependency issues.\n */\nexport async function deserializeTensor(serialized) {\n    const { EdgeFlowTensor } = await import('./tensor.js');\n    const data = new Float32Array(serialized.data);\n    return new EdgeFlowTensor(data, serialized.shape, serialized.dtype);\n}\n/**\n * Synchronous deserialisation used internally where async is not feasible.\n * Requires EdgeFlowTensor to be passed in to avoid require().\n */\nexport function deserializeTensorSync(serialized, TensorClass) {\n    const data = new Float32Array(serialized.data);\n    return new TensorClass(data, serialized.shape, serialized.dtype);\n}\nconst MAX_RESTART_ATTEMPTS = 3;\nconst RESTART_BASE_DELAY_MS = 1000;\n/**\n * InferenceWorker - Wrapper for a single Web Worker with auto-restart\n */\nexport class InferenceWorker {\n    worker = null;\n    pendingRequests = new Map();\n    isReady = false;\n    readyPromise;\n    readyResolve;\n    workerUrl;\n    _health = 'alive';\n    restartAttempts = 0;\n    constructor(workerUrl) {\n        this.workerUrl = workerUrl;\n        this.readyPromise = new Promise(resolve => {\n            this.readyResolve = resolve;\n        });\n        this.initWorker(workerUrl);\n    }\n    get health() {\n        return this._health;\n    }\n    /**\n     * Initialize the worker\n     */\n    initWorker(workerUrl) {\n        const url = workerUrl ?? this.createWorkerBlob();\n        this.worker = new Worker(url, { type: 'module' });\n        this.worker.onmessage = (event) => {\n            this.handleMessage(event.data);\n        };\n        this.worker.onerror = (error) => {\n            console.error('Worker error:', error);\n            this.handleCrash();\n        };\n        this.worker.onmessageerror = () => {\n            this.handleCrash();\n        };\n    }\n    /**\n     * Handle worker crash: reject pending, mark dead, attempt restart\n     */\n    handleCrash() {\n        this._health = 'dead';\n        this.isReady = false;\n        const crashError = new Error('Worker crashed');\n        for (const [, { reject }] of this.pendingRequests) {\n            reject(crashError);\n        }\n        this.pendingRequests.clear();\n        this.attemptRestart();\n    }\n    /**\n     * Restart the worker with exponential backoff\n     */\n    attemptRestart() {\n        if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {\n            console.error(`Worker failed to restart after ${MAX_RESTART_ATTEMPTS} attempts`);\n            return;\n        }\n        this._health = 'restarting';\n        const delay = RESTART_BASE_DELAY_MS * Math.pow(2, this.restartAttempts);\n        this.restartAttempts++;\n        setTimeout(() => {\n            this.restart();\n        }, delay);\n    }\n    /**\n     * Restart: terminate old, create new\n     */\n    restart() {\n        if (this.worker) {\n            try {\n                this.worker.terminate();\n            }\n            catch { /* already dead */ }\n            this.worker = null;\n        }\n        this.readyPromise = new Promise(resolve => {\n            this.readyResolve = resolve;\n        });\n        this.isReady = false;\n        try {\n            this.initWorker(this.workerUrl);\n            this._health = 'alive';\n            this.restartAttempts = 0;\n        }\n        catch {\n            this._health = 'dead';\n            this.attemptRestart();\n        }\n    }\n    /**\n     * Create worker code as blob URL\n     */\n    createWorkerBlob() {\n        const workerCode = `\n      // edgeFlow.js Worker\n      let models = new Map();\n      let ort = null;\n      \n      // Load ONNX Runtime\n      async function loadOrt() {\n        if (ort) return ort;\n        ort = await import('https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/esm/ort.min.js');\n        return ort;\n      }\n      \n      // Handle messages\n      self.onmessage = async (event) => {\n        const { id, type, payload } = event.data;\n        \n        try {\n          switch (type) {\n            case 'init': {\n              await loadOrt();\n              self.postMessage({ id, type: 'ready' });\n              break;\n            }\n            \n            case 'load_model': {\n              await loadOrt();\n              const { url, options } = payload;\n              const response = await fetch(url);\n              const arrayBuffer = await response.arrayBuffer();\n              const session = await ort.InferenceSession.create(\n                new Uint8Array(arrayBuffer),\n                { executionProviders: ['wasm'] }\n              );\n              const modelId = 'model_' + Date.now();\n              models.set(modelId, session);\n              self.postMessage({\n                id,\n                type: 'result',\n                payload: { modelId }\n              });\n              break;\n            }\n            \n            case 'run_inference': {\n              const { modelId, inputs } = payload;\n              const session = models.get(modelId);\n              if (!session) {\n                throw new Error('Model not found: ' + modelId);\n              }\n              \n              // Prepare inputs\n              const feeds = {};\n              const inputNames = session.inputNames;\n              for (let i = 0; i < inputs.length && i < inputNames.length; i++) {\n                const input = inputs[i];\n                const data = new Float32Array(input.data);\n                feeds[inputNames[i]] = new ort.Tensor(input.dtype, data, input.shape);\n              }\n              \n              // Run inference\n              const results = await session.run(feeds);\n              \n              // Serialize outputs\n              const outputs = [];\n              for (const name of session.outputNames) {\n                const tensor = results[name];\n                outputs.push({\n                  data: tensor.data.buffer.slice(0),\n                  shape: tensor.dims,\n                  dtype: tensor.type\n                });\n              }\n              \n              self.postMessage(\n                { id, type: 'result', payload: { outputs } },\n                outputs.map(o => o.data)\n              );\n              break;\n            }\n            \n            case 'dispose': {\n              const { modelId } = payload;\n              const session = models.get(modelId);\n              if (session) {\n                // session.release(); // Not available in all versions\n                models.delete(modelId);\n              }\n              self.postMessage({ id, type: 'result', payload: { success: true } });\n              break;\n            }\n          }\n        } catch (error) {\n          self.postMessage({\n            id,\n            type: 'error',\n            payload: { message: error.message }\n          });\n        }\n      };\n    `;\n        const blob = new Blob([workerCode], { type: 'application/javascript' });\n        return URL.createObjectURL(blob);\n    }\n    /**\n     * Handle worker message\n     */\n    handleMessage(message) {\n        if (message.type === 'ready') {\n            this.isReady = true;\n            this.readyResolve();\n            return;\n        }\n        const request = this.pendingRequests.get(message.id);\n        if (!request)\n            return;\n        this.pendingRequests.delete(message.id);\n        if (message.type === 'error') {\n            const payload = message.payload;\n            request.reject(new Error(payload.message));\n        }\n        else {\n            request.resolve(message.payload);\n        }\n    }\n    /**\n     * Send a request to the worker\n     */\n    async sendRequest(type, payload) {\n        if (!this.worker) {\n            throw new Error('Worker not initialized');\n        }\n        const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n        return new Promise((resolve, reject) => {\n            this.pendingRequests.set(id, { resolve: resolve, reject });\n            const message = { id, type, payload };\n            // Transfer ArrayBuffers for efficiency\n            const transfers = [];\n            if (payload && typeof payload === 'object' && 'inputs' in payload) {\n                const inputs = payload.inputs;\n                for (const input of inputs) {\n                    if (input.data instanceof ArrayBuffer) {\n                        transfers.push(input.data);\n                    }\n                }\n            }\n            this.worker.postMessage(message, transfers);\n        });\n    }\n    /**\n     * Initialize the worker\n     */\n    async init() {\n        if (this.isReady)\n            return;\n        await this.sendRequest('init');\n        await this.readyPromise;\n    }\n    /**\n     * Load a model\n     */\n    async loadModel(url, options) {\n        await this.init();\n        const result = await this.sendRequest('load_model', { url, options });\n        return result.modelId;\n    }\n    /**\n     * Run inference\n     */\n    async runInference(modelId, inputs) {\n        const serializedInputs = inputs.map(serializeTensor);\n        const result = await this.sendRequest('run_inference', { modelId, inputs: serializedInputs });\n        return Promise.all(result.outputs.map(deserializeTensor));\n    }\n    /**\n     * Dispose a model\n     */\n    async dispose(modelId) {\n        await this.sendRequest('dispose', { modelId });\n    }\n    /**\n     * Terminate the worker\n     */\n    terminate() {\n        if (this.worker) {\n            this.worker.terminate();\n            this.worker = null;\n        }\n        this.pendingRequests.clear();\n    }\n}\n// ============================================================================\n// Worker Pool\n// ============================================================================\n/**\n * WorkerPool - Manage multiple workers for parallel inference.\n * Automatically falls back to healthy workers when one is dead.\n */\nexport class WorkerPool {\n    workers = [];\n    currentIndex = 0;\n    modelAssignments = new Map();\n    poolOptions;\n    constructor(options = {}) {\n        this.poolOptions = options;\n        const numWorkers = options.numWorkers ??\n            (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 4) ?? 4;\n        for (let i = 0; i < numWorkers; i++) {\n            this.workers.push(new InferenceWorker(options.workerUrl));\n        }\n    }\n    /**\n     * Get next healthy worker (round-robin, skipping dead ones)\n     */\n    getNextHealthyWorker() {\n        const len = this.workers.length;\n        for (let attempt = 0; attempt < len; attempt++) {\n            const worker = this.workers[this.currentIndex];\n            this.currentIndex = (this.currentIndex + 1) % len;\n            if (worker.health === 'alive')\n                return worker;\n        }\n        // All dead — try restarting first one and return it\n        const worker = this.workers[0];\n        if (worker.health === 'dead')\n            worker.restart();\n        return worker;\n    }\n    /**\n     * Get worker for a specific model, falling back to any healthy worker\n     */\n    getWorkerForModel(modelId) {\n        const index = this.modelAssignments.get(modelId);\n        if (index !== undefined) {\n            const worker = this.workers[index];\n            if (worker.health === 'alive')\n                return worker;\n            // Assigned worker is dead — pick a healthy one and reassign\n            const replacement = this.getNextHealthyWorker();\n            this.modelAssignments.set(modelId, this.workers.indexOf(replacement));\n            return replacement;\n        }\n        return this.getNextHealthyWorker();\n    }\n    /**\n     * Replace a worker at a given index with a fresh one\n     */\n    replaceWorker(index) {\n        if (index < 0 || index >= this.workers.length)\n            return;\n        const old = this.workers[index];\n        old.terminate();\n        this.workers[index] = new InferenceWorker(this.poolOptions.workerUrl);\n    }\n    /**\n     * Initialize all workers\n     */\n    async init() {\n        await Promise.all(this.workers.map(w => w.init()));\n    }\n    /**\n     * Load a model on a worker\n     */\n    async loadModel(url, options) {\n        const worker = this.getNextHealthyWorker();\n        const modelId = await worker.loadModel(url, options);\n        this.modelAssignments.set(modelId, this.workers.indexOf(worker));\n        return modelId;\n    }\n    /**\n     * Run inference (auto-retries on a healthy worker if assigned one is dead)\n     */\n    async runInference(modelId, inputs) {\n        const worker = this.getWorkerForModel(modelId);\n        return worker.runInference(modelId, inputs);\n    }\n    /**\n     * Run inference on multiple inputs in parallel\n     */\n    async runBatch(modelId, batchInputs) {\n        const results = await Promise.all(batchInputs.map((inputs, i) => {\n            const worker = this.workers[i % this.workers.length];\n            return worker.runInference(modelId, inputs);\n        }));\n        return results;\n    }\n    /**\n     * Dispose a model\n     */\n    async dispose(modelId) {\n        const worker = this.getWorkerForModel(modelId);\n        await worker.dispose(modelId);\n        this.modelAssignments.delete(modelId);\n    }\n    /**\n     * Terminate all workers\n     */\n    terminate() {\n        for (const worker of this.workers) {\n            worker.terminate();\n        }\n        this.workers = [];\n        this.modelAssignments.clear();\n    }\n    /**\n     * Get number of workers\n     */\n    get size() {\n        return this.workers.length;\n    }\n}\n// ============================================================================\n// Global Instance\n// ============================================================================\nlet globalWorkerPool = null;\n/**\n * Get or create global worker pool\n */\nexport function getWorkerPool(options) {\n    if (!globalWorkerPool) {\n        globalWorkerPool = new WorkerPool(options);\n    }\n    return globalWorkerPool;\n}\n/**\n * Run inference in a worker\n */\nexport async function runInWorker(modelUrl, inputs, options) {\n    const pool = getWorkerPool();\n    await pool.init();\n    const modelId = await pool.loadModel(modelUrl, options);\n    const outputs = await pool.runInference(modelId, inputs);\n    return outputs;\n}\n/**\n * Check if Web Workers are supported\n */\nexport function isWorkerSupported() {\n    return typeof Worker !== 'undefined';\n}\n//# sourceMappingURL=worker.js.map"
  },
  {
    "path": "dist/edgeflow.browser.js",
    "content": "/* edgeFlow.js - Browser Bundle */\n\nvar __defProp = Object.defineProperty;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __esm = (fn, res) => function __init() {\n  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;\n};\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __publicField = (obj, key, value) => {\n  __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n  return value;\n};\n\n// dist/core/types.js\nvar EdgeFlowError, ErrorCodes;\nvar init_types = __esm({\n  \"dist/core/types.js\"() {\n    \"use strict\";\n    EdgeFlowError = class extends Error {\n      constructor(message, code, details) {\n        super(message);\n        __publicField(this, \"code\");\n        __publicField(this, \"details\");\n        this.code = code;\n        this.details = details;\n        this.name = \"EdgeFlowError\";\n      }\n    };\n    ErrorCodes = {\n      // Runtime errors\n      RUNTIME_NOT_AVAILABLE: \"RUNTIME_NOT_AVAILABLE\",\n      RUNTIME_INIT_FAILED: \"RUNTIME_INIT_FAILED\",\n      RUNTIME_NOT_INITIALIZED: \"RUNTIME_NOT_INITIALIZED\",\n      // Model errors\n      MODEL_NOT_FOUND: \"MODEL_NOT_FOUND\",\n      MODEL_LOAD_FAILED: \"MODEL_LOAD_FAILED\",\n      MODEL_INVALID_FORMAT: \"MODEL_INVALID_FORMAT\",\n      MODEL_NOT_LOADED: \"MODEL_NOT_LOADED\",\n      // Inference errors\n      INFERENCE_FAILED: \"INFERENCE_FAILED\",\n      INFERENCE_TIMEOUT: \"INFERENCE_TIMEOUT\",\n      INFERENCE_CANCELLED: \"INFERENCE_CANCELLED\",\n      // Memory errors\n      OUT_OF_MEMORY: \"OUT_OF_MEMORY\",\n      MEMORY_LEAK_DETECTED: \"MEMORY_LEAK_DETECTED\",\n      // Tensor errors\n      TENSOR_SHAPE_MISMATCH: \"TENSOR_SHAPE_MISMATCH\",\n      TENSOR_DTYPE_MISMATCH: \"TENSOR_DTYPE_MISMATCH\",\n      TENSOR_DISPOSED: \"TENSOR_DISPOSED\",\n      // Pipeline errors\n      PIPELINE_NOT_SUPPORTED: \"PIPELINE_NOT_SUPPORTED\",\n      PIPELINE_INPUT_INVALID: \"PIPELINE_INPUT_INVALID\",\n      // General errors\n      INVALID_ARGUMENT: \"INVALID_ARGUMENT\",\n      NOT_IMPLEMENTED: \"NOT_IMPLEMENTED\",\n      UNKNOWN_ERROR: \"UNKNOWN_ERROR\"\n    };\n  }\n});\n\n// dist/core/tensor.js\nfunction generateTensorId() {\n  return `tensor_${++tensorIdCounter}_${Date.now().toString(36)}`;\n}\nfunction getTypedArrayConstructor(dtype) {\n  switch (dtype) {\n    case \"float32\":\n      return Float32Array;\n    case \"float16\":\n      return Float32Array;\n    case \"int32\":\n      return Int32Array;\n    case \"int64\":\n      return BigInt64Array;\n    case \"uint8\":\n    case \"bool\":\n      return Uint8Array;\n    case \"int8\":\n      return Int8Array;\n    default:\n      throw new EdgeFlowError(`Unsupported data type: ${dtype}`, ErrorCodes.INVALID_ARGUMENT, { dtype });\n  }\n}\nfunction calculateSize(shape) {\n  if (shape.length === 0)\n    return 1;\n  return shape.reduce((acc, dim) => acc * dim, 1);\n}\nfunction validateShape(shape) {\n  for (let i = 0; i < shape.length; i++) {\n    const dim = shape[i];\n    if (dim === void 0 || !Number.isInteger(dim) || dim < 0) {\n      throw new EdgeFlowError(`Invalid shape dimension at index ${i}: ${dim}`, ErrorCodes.INVALID_ARGUMENT, { shape, index: i, dimension: dim });\n    }\n  }\n}\nfunction tensor(data, shape, dtype = \"float32\") {\n  if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {\n    const rows = data.length;\n    const cols = data[0].length;\n    const flatData = [];\n    for (const row of data) {\n      if (row.length !== cols) {\n        throw new EdgeFlowError(\"Nested arrays must have consistent dimensions\", ErrorCodes.INVALID_ARGUMENT);\n      }\n      flatData.push(...row);\n    }\n    return new EdgeFlowTensor(flatData, shape ?? [rows, cols], dtype);\n  }\n  const inferredShape = shape ?? [data.length];\n  return new EdgeFlowTensor(data, inferredShape, dtype);\n}\nfunction zeros(shape, dtype = \"float32\") {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\nfunction ones(shape, dtype = \"float32\") {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  data.fill(1);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\nfunction full(shape, value, dtype = \"float32\") {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  data.fill(value);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\nfunction random(shape, dtype = \"float32\") {\n  const size = calculateSize(shape);\n  const data = new Float32Array(size);\n  for (let i = 0; i < size; i++) {\n    data[i] = Math.random();\n  }\n  return new EdgeFlowTensor(data, shape, dtype);\n}\nfunction randn(shape, dtype = \"float32\") {\n  const size = calculateSize(shape);\n  const data = new Float32Array(size);\n  for (let i = 0; i < size; i += 2) {\n    const u1 = Math.random();\n    const u2 = Math.random();\n    const r = Math.sqrt(-2 * Math.log(u1));\n    const theta = 2 * Math.PI * u2;\n    data[i] = r * Math.cos(theta);\n    if (i + 1 < size) {\n      data[i + 1] = r * Math.sin(theta);\n    }\n  }\n  return new EdgeFlowTensor(data, shape, dtype);\n}\nfunction arange(start, stop, step = 1, dtype = \"float32\") {\n  if (stop === void 0) {\n    stop = start;\n    start = 0;\n  }\n  const size = Math.ceil((stop - start) / step);\n  const data = new Float32Array(size);\n  for (let i = 0; i < size; i++) {\n    data[i] = start + i * step;\n  }\n  return new EdgeFlowTensor(data, [size], dtype);\n}\nfunction linspace(start, stop, num = 50, dtype = \"float32\") {\n  const data = new Float32Array(num);\n  const step = (stop - start) / (num - 1);\n  for (let i = 0; i < num; i++) {\n    data[i] = start + i * step;\n  }\n  return new EdgeFlowTensor(data, [num], dtype);\n}\nfunction eye(n, dtype = \"float32\") {\n  const data = new Float32Array(n * n);\n  for (let i = 0; i < n; i++) {\n    data[i * n + i] = 1;\n  }\n  return new EdgeFlowTensor(data, [n, n], dtype);\n}\nfunction add(a, b) {\n  if (typeof b === \"number\") {\n    const result2 = new Float32Array(a.size);\n    const aData2 = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result2[i] = (aData2[i] ?? 0) + b;\n    }\n    return new EdgeFlowTensor(result2, a.shape, a.dtype);\n  }\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\"Tensor sizes must match for element-wise operations\", ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n  }\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) + (bData[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\nfunction sub(a, b) {\n  if (typeof b === \"number\") {\n    const result2 = new Float32Array(a.size);\n    const aData2 = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result2[i] = (aData2[i] ?? 0) - b;\n    }\n    return new EdgeFlowTensor(result2, a.shape, a.dtype);\n  }\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\"Tensor sizes must match for element-wise operations\", ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n  }\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) - (bData[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\nfunction mul(a, b) {\n  if (typeof b === \"number\") {\n    const result2 = new Float32Array(a.size);\n    const aData2 = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result2[i] = (aData2[i] ?? 0) * b;\n    }\n    return new EdgeFlowTensor(result2, a.shape, a.dtype);\n  }\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\"Tensor sizes must match for element-wise operations\", ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n  }\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) * (bData[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\nfunction div(a, b) {\n  if (typeof b === \"number\") {\n    const result2 = new Float32Array(a.size);\n    const aData2 = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result2[i] = (aData2[i] ?? 0) / b;\n    }\n    return new EdgeFlowTensor(result2, a.shape, a.dtype);\n  }\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\"Tensor sizes must match for element-wise operations\", ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n  }\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) / (bData[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\nfunction matmul(a, b) {\n  if (a.shape.length !== 2 || b.shape.length !== 2) {\n    throw new EdgeFlowError(\"matmul requires 2D tensors\", ErrorCodes.INVALID_ARGUMENT, { aShape: a.shape, bShape: b.shape });\n  }\n  const [m, k1] = a.shape;\n  const [k2, n] = b.shape;\n  if (k1 !== k2) {\n    throw new EdgeFlowError(`Matrix dimensions incompatible for multiplication: (${m}x${k1}) @ (${k2}x${n})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { aShape: a.shape, bShape: b.shape });\n  }\n  const result = new Float32Array(m * n);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  for (let i = 0; i < m; i++) {\n    for (let j = 0; j < n; j++) {\n      let sum2 = 0;\n      for (let k = 0; k < k1; k++) {\n        sum2 += (aData[i * k1 + k] ?? 0) * (bData[k * n + j] ?? 0);\n      }\n      result[i * n + j] = sum2;\n    }\n  }\n  return new EdgeFlowTensor(result, [m, n], a.dtype);\n}\nfunction softmax(t, axis = -1) {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  if (actualAxis < 0 || actualAxis >= t.shape.length) {\n    throw new EdgeFlowError(`Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`, ErrorCodes.INVALID_ARGUMENT, { axis, shape: t.shape });\n  }\n  if (t.shape.length === 1) {\n    let max = -Infinity;\n    for (let i = 0; i < t.size; i++) {\n      if ((data[i] ?? 0) > max)\n        max = data[i] ?? 0;\n    }\n    let sum2 = 0;\n    for (let i = 0; i < t.size; i++) {\n      result[i] = Math.exp((data[i] ?? 0) - max);\n      sum2 += result[i] ?? 0;\n    }\n    for (let i = 0; i < t.size; i++) {\n      result[i] = (result[i] ?? 0) / sum2;\n    }\n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n  }\n  if (t.shape.length === 2 && actualAxis === 1) {\n    const [rows, cols] = t.shape;\n    for (let i = 0; i < rows; i++) {\n      let max = -Infinity;\n      for (let j = 0; j < cols; j++) {\n        if ((data[i * cols + j] ?? 0) > max)\n          max = data[i * cols + j] ?? 0;\n      }\n      let sum2 = 0;\n      for (let j = 0; j < cols; j++) {\n        result[i * cols + j] = Math.exp((data[i * cols + j] ?? 0) - max);\n        sum2 += result[i * cols + j] ?? 0;\n      }\n      for (let j = 0; j < cols; j++) {\n        result[i * cols + j] = (result[i * cols + j] ?? 0) / sum2;\n      }\n    }\n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n  }\n  throw new EdgeFlowError(\"Softmax currently only supports 1D tensors or 2D tensors along the last axis\", ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\nfunction relu(t) {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  for (let i = 0; i < t.size; i++) {\n    result[i] = Math.max(0, data[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\nfunction sigmoid(t) {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  for (let i = 0; i < t.size; i++) {\n    result[i] = 1 / (1 + Math.exp(-(data[i] ?? 0)));\n  }\n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\nfunction tanh(t) {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  for (let i = 0; i < t.size; i++) {\n    result[i] = Math.tanh(data[i] ?? 0);\n  }\n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\nfunction sum(t, axis) {\n  const data = t.toFloat32Array();\n  if (axis === void 0) {\n    let total = 0;\n    for (let i = 0; i < t.size; i++) {\n      total += data[i] ?? 0;\n    }\n    return total;\n  }\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  if (actualAxis < 0 || actualAxis >= t.shape.length) {\n    throw new EdgeFlowError(`Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`, ErrorCodes.INVALID_ARGUMENT, { axis, shape: t.shape });\n  }\n  const newShape = [...t.shape];\n  newShape.splice(actualAxis, 1);\n  if (newShape.length === 0) {\n    let total = 0;\n    for (let i = 0; i < t.size; i++) {\n      total += data[i] ?? 0;\n    }\n    return total;\n  }\n  if (t.shape.length === 2) {\n    const [rows, cols] = t.shape;\n    if (actualAxis === 0) {\n      const result = new Float32Array(cols);\n      for (let j = 0; j < cols; j++) {\n        for (let i = 0; i < rows; i++) {\n          result[j] = (result[j] ?? 0) + (data[i * cols + j] ?? 0);\n        }\n      }\n      return new EdgeFlowTensor(result, [cols], t.dtype);\n    } else {\n      const result = new Float32Array(rows);\n      for (let i = 0; i < rows; i++) {\n        for (let j = 0; j < cols; j++) {\n          result[i] = (result[i] ?? 0) + (data[i * cols + j] ?? 0);\n        }\n      }\n      return new EdgeFlowTensor(result, [rows], t.dtype);\n    }\n  }\n  throw new EdgeFlowError(\"Sum along axis currently only supports up to 2D tensors\", ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\nfunction mean(t, axis) {\n  if (axis === void 0) {\n    return sum(t) / t.size;\n  }\n  const result = sum(t, axis);\n  if (typeof result === \"number\") {\n    return result / (t.shape[axis] ?? 1);\n  }\n  const axisSize = t.shape[axis] ?? 1;\n  return div(result, axisSize);\n}\nfunction argmax(t, axis) {\n  const data = t.toFloat32Array();\n  if (axis === void 0) {\n    let maxIdx = 0;\n    let maxVal = data[0] ?? -Infinity;\n    for (let i = 1; i < t.size; i++) {\n      if ((data[i] ?? -Infinity) > maxVal) {\n        maxVal = data[i] ?? -Infinity;\n        maxIdx = i;\n      }\n    }\n    return maxIdx;\n  }\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  if (t.shape.length === 2 && actualAxis === 1) {\n    const [rows, cols] = t.shape;\n    const result = new Float32Array(rows);\n    for (let i = 0; i < rows; i++) {\n      let maxIdx = 0;\n      let maxVal = data[i * cols] ?? -Infinity;\n      for (let j = 1; j < cols; j++) {\n        if ((data[i * cols + j] ?? -Infinity) > maxVal) {\n          maxVal = data[i * cols + j] ?? -Infinity;\n          maxIdx = j;\n        }\n      }\n      result[i] = maxIdx;\n    }\n    return new EdgeFlowTensor(result, [rows], \"int32\");\n  }\n  throw new EdgeFlowError(\"Argmax along axis currently only supports 2D tensors along the last axis\", ErrorCodes.NOT_IMPLEMENTED, { shape: t.shape, axis });\n}\nfunction concat(tensors, axis = 0) {\n  if (tensors.length === 0) {\n    throw new EdgeFlowError(\"Cannot concatenate empty array of tensors\", ErrorCodes.INVALID_ARGUMENT);\n  }\n  if (tensors.length === 1) {\n    return tensors[0]?.clone() ?? zeros([0]);\n  }\n  const first = tensors[0];\n  if (!first) {\n    throw new EdgeFlowError(\"First tensor is undefined\", ErrorCodes.INVALID_ARGUMENT);\n  }\n  const actualAxis = axis < 0 ? first.shape.length + axis : axis;\n  for (let i = 1; i < tensors.length; i++) {\n    const t = tensors[i];\n    if (!t)\n      continue;\n    if (t.shape.length !== first.shape.length) {\n      throw new EdgeFlowError(\"All tensors must have the same number of dimensions\", ErrorCodes.TENSOR_SHAPE_MISMATCH);\n    }\n    for (let j = 0; j < first.shape.length; j++) {\n      if (j !== actualAxis && first.shape[j] !== t.shape[j]) {\n        throw new EdgeFlowError(`Shape mismatch at dimension ${j}`, ErrorCodes.TENSOR_SHAPE_MISMATCH);\n      }\n    }\n  }\n  const newShape = [...first.shape];\n  let totalAxisSize = 0;\n  for (const t of tensors) {\n    if (t)\n      totalAxisSize += t.shape[actualAxis] ?? 0;\n  }\n  newShape[actualAxis] = totalAxisSize;\n  if (first.shape.length === 1) {\n    const result = new Float32Array(totalAxisSize);\n    let offset = 0;\n    for (const t of tensors) {\n      if (!t)\n        continue;\n      result.set(t.toFloat32Array(), offset);\n      offset += t.size;\n    }\n    return new EdgeFlowTensor(result, newShape, first.dtype);\n  }\n  throw new EdgeFlowError(\"Concatenation currently only supports 1D tensors\", ErrorCodes.NOT_IMPLEMENTED);\n}\nvar tensorIdCounter, EdgeFlowTensor;\nvar init_tensor = __esm({\n  \"dist/core/tensor.js\"() {\n    \"use strict\";\n    init_types();\n    tensorIdCounter = 0;\n    EdgeFlowTensor = class _EdgeFlowTensor {\n      constructor(data, shape, dtype = \"float32\") {\n        __publicField(this, \"id\");\n        __publicField(this, \"dtype\");\n        __publicField(this, \"shape\");\n        __publicField(this, \"size\");\n        __publicField(this, \"_data\");\n        __publicField(this, \"_isDisposed\", false);\n        validateShape(shape);\n        this.id = generateTensorId();\n        this.dtype = dtype;\n        this.shape = Object.freeze([...shape]);\n        this.size = calculateSize(this.shape);\n        const expectedSize = this.size;\n        if (data.length !== expectedSize) {\n          throw new EdgeFlowError(`Data length (${data.length}) does not match shape ${JSON.stringify(shape)} (expected ${expectedSize})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { dataLength: data.length, expectedSize, shape });\n        }\n        if (data instanceof Array) {\n          const TypedArrayCtor = getTypedArrayConstructor(dtype);\n          this._data = new TypedArrayCtor(data.length);\n          if (dtype === \"int64\") {\n            const bigIntData = this._data;\n            for (let i = 0; i < data.length; i++) {\n              bigIntData[i] = BigInt(Math.round(data[i] ?? 0));\n            }\n          } else {\n            for (let i = 0; i < data.length; i++) {\n              this._data[i] = data[i] ?? 0;\n            }\n          }\n        } else {\n          this._data = data;\n        }\n      }\n      get data() {\n        this.checkDisposed();\n        return this._data;\n      }\n      get isDisposed() {\n        return this._isDisposed;\n      }\n      /**\n       * Check if tensor has been disposed\n       */\n      checkDisposed() {\n        if (this._isDisposed) {\n          throw new EdgeFlowError(\"Cannot access disposed tensor\", ErrorCodes.TENSOR_DISPOSED, { tensorId: this.id });\n        }\n      }\n      /**\n       * Convert to Float32Array\n       */\n      toFloat32Array() {\n        this.checkDisposed();\n        if (this._data instanceof Float32Array) {\n          return this._data;\n        }\n        const result = new Float32Array(this.size);\n        for (let i = 0; i < this.size; i++) {\n          result[i] = Number(this._data[i] ?? 0);\n        }\n        return result;\n      }\n      /**\n       * Convert to regular array\n       */\n      toArray() {\n        this.checkDisposed();\n        if (this.dtype === \"int64\") {\n          const bigIntData = this._data;\n          const result = [];\n          for (let i = 0; i < bigIntData.length; i++) {\n            result.push(Number(bigIntData[i]));\n          }\n          return result;\n        }\n        return Array.from(this._data);\n      }\n      /**\n       * Clone the tensor\n       */\n      clone() {\n        this.checkDisposed();\n        const TypedArrayCtor = this._data.constructor;\n        const clonedData = new TypedArrayCtor(this._data);\n        return new _EdgeFlowTensor(clonedData, this.shape, this.dtype);\n      }\n      /**\n       * Dispose the tensor and free memory\n       */\n      dispose() {\n        if (!this._isDisposed) {\n          this._isDisposed = true;\n          Object.assign(this, { _data: null });\n        }\n      }\n      /**\n       * Get value at specific indices\n       */\n      get(...indices) {\n        this.checkDisposed();\n        if (indices.length !== this.shape.length) {\n          throw new EdgeFlowError(`Expected ${this.shape.length} indices, got ${indices.length}`, ErrorCodes.INVALID_ARGUMENT, { expectedIndices: this.shape.length, gotIndices: indices.length });\n        }\n        let flatIndex = 0;\n        let stride = 1;\n        for (let i = this.shape.length - 1; i >= 0; i--) {\n          const idx = indices[i] ?? 0;\n          const dim = this.shape[i] ?? 1;\n          if (idx < 0 || idx >= dim) {\n            throw new EdgeFlowError(`Index ${idx} out of bounds for dimension ${i} with size ${dim}`, ErrorCodes.INVALID_ARGUMENT, { index: idx, dimension: i, size: dim });\n          }\n          flatIndex += idx * stride;\n          stride *= dim;\n        }\n        return Number(this._data[flatIndex] ?? 0);\n      }\n      /**\n       * Set value at specific indices\n       */\n      set(value, ...indices) {\n        this.checkDisposed();\n        if (indices.length !== this.shape.length) {\n          throw new EdgeFlowError(`Expected ${this.shape.length} indices, got ${indices.length}`, ErrorCodes.INVALID_ARGUMENT, { expectedIndices: this.shape.length, gotIndices: indices.length });\n        }\n        let flatIndex = 0;\n        let stride = 1;\n        for (let i = this.shape.length - 1; i >= 0; i--) {\n          const idx = indices[i] ?? 0;\n          const dim = this.shape[i] ?? 1;\n          if (idx < 0 || idx >= dim) {\n            throw new EdgeFlowError(`Index ${idx} out of bounds for dimension ${i} with size ${dim}`, ErrorCodes.INVALID_ARGUMENT, { index: idx, dimension: i, size: dim });\n          }\n          flatIndex += idx * stride;\n          stride *= dim;\n        }\n        this._data[flatIndex] = value;\n      }\n      /**\n       * Reshape the tensor (returns new tensor)\n       */\n      reshape(newShape) {\n        this.checkDisposed();\n        const newSize = calculateSize(newShape);\n        if (newSize !== this.size) {\n          throw new EdgeFlowError(`Cannot reshape tensor of size ${this.size} to shape ${JSON.stringify(newShape)} (size ${newSize})`, ErrorCodes.TENSOR_SHAPE_MISMATCH, { currentSize: this.size, newSize, newShape });\n        }\n        const TypedArrayCtor = this._data.constructor;\n        const clonedData = new TypedArrayCtor(this._data);\n        return new _EdgeFlowTensor(clonedData, newShape, this.dtype);\n      }\n      /**\n       * Transpose the tensor (2D only for now)\n       */\n      transpose() {\n        this.checkDisposed();\n        if (this.shape.length !== 2) {\n          throw new EdgeFlowError(\"Transpose is currently only supported for 2D tensors\", ErrorCodes.NOT_IMPLEMENTED, { shape: this.shape });\n        }\n        const [rows, cols] = this.shape;\n        const result = new Float32Array(this.size);\n        for (let i = 0; i < rows; i++) {\n          for (let j = 0; j < cols; j++) {\n            result[j * rows + i] = Number(this._data[i * cols + j] ?? 0);\n          }\n        }\n        return new _EdgeFlowTensor(result, [cols, rows], this.dtype);\n      }\n      /**\n       * Create string representation\n       */\n      toString() {\n        return `Tensor(shape=[${this.shape.join(\", \")}], dtype=${this.dtype})`;\n      }\n    };\n  }\n});\n\n// dist/utils/model-loader.js\nvar model_loader_exports = {};\n__export(model_loader_exports, {\n  cancelPreload: () => cancelPreload,\n  clearModelCache: () => clearModelCache,\n  deleteCachedModel: () => deleteCachedModel,\n  getCachedModel: () => getCachedModel,\n  getModelCacheStats: () => getModelCacheStats,\n  getPreloadStatus: () => getPreloadStatus,\n  getPreloadedModel: () => getPreloadedModel,\n  isModelCached: () => isModelCached,\n  loadModelData: () => loadModelData,\n  preloadModel: () => preloadModel,\n  preloadModels: () => preloadModels\n});\nasync function supportsRangeRequests(url) {\n  try {\n    const response = await fetch(url, { method: \"HEAD\" });\n    const acceptRanges = response.headers.get(\"Accept-Ranges\");\n    const contentLength = response.headers.get(\"Content-Length\");\n    const etag = response.headers.get(\"ETag\") ?? void 0;\n    return {\n      supports: acceptRanges === \"bytes\",\n      size: contentLength ? parseInt(contentLength, 10) : 0,\n      etag\n    };\n  } catch {\n    return { supports: false, size: 0 };\n  }\n}\nasync function downloadChunk(url, start, end, timeout) {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n  try {\n    const response = await fetch(url, {\n      headers: { Range: `bytes=${start}-${end}` },\n      signal: controller.signal\n    });\n    if (response.status !== 206 && response.status !== 200) {\n      throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n    }\n    return await response.arrayBuffer();\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\nasync function downloadWithResume(url, options) {\n  const {\n    chunkSize = 5 * 1024 * 1024,\n    // 5MB\n    parallelConnections = 4,\n    timeout = 3e4,\n    onProgress\n  } = options;\n  const { supports: supportsRange, size: totalSize, etag } = await supportsRangeRequests(url);\n  if (!supportsRange || totalSize < chunkSize * 2) {\n    return downloadSimple(url, timeout, onProgress);\n  }\n  let state = await modelCache.getDownloadState(url);\n  if (!state || etag && state.totalSize !== totalSize) {\n    const numChunks = Math.ceil(totalSize / chunkSize);\n    const chunks2 = [];\n    for (let i = 0; i < numChunks; i++) {\n      const start = i * chunkSize;\n      const end = Math.min(start + chunkSize - 1, totalSize - 1);\n      chunks2.push({ index: i, start, end, downloaded: false });\n    }\n    state = {\n      url,\n      totalSize,\n      downloadedSize: 0,\n      chunks: chunks2,\n      startedAt: Date.now()\n    };\n    await modelCache.deleteModel(url);\n  }\n  const pendingChunks = state.chunks.filter((c) => !c.downloaded);\n  let downloadedSize = state.downloadedSize;\n  const startTime = Date.now();\n  let lastProgressTime = startTime;\n  let lastDownloadedSize = downloadedSize;\n  const reportProgress = () => {\n    if (!onProgress)\n      return;\n    const now = Date.now();\n    const elapsed = (now - lastProgressTime) / 1e3;\n    const bytesDownloaded = downloadedSize - lastDownloadedSize;\n    const speed = elapsed > 0 ? bytesDownloaded / elapsed : 0;\n    const remaining = totalSize - downloadedSize;\n    const eta = speed > 0 ? remaining / speed * 1e3 : 0;\n    onProgress({\n      loaded: downloadedSize,\n      total: totalSize,\n      percent: downloadedSize / totalSize * 100,\n      speed,\n      eta,\n      currentChunk: state.chunks.filter((c) => c.downloaded).length,\n      totalChunks: state.chunks.length\n    });\n    lastProgressTime = now;\n    lastDownloadedSize = downloadedSize;\n  };\n  const downloadQueue = [...pendingChunks];\n  const inProgress = /* @__PURE__ */ new Map();\n  while (downloadQueue.length > 0 || inProgress.size > 0) {\n    while (downloadQueue.length > 0 && inProgress.size < parallelConnections) {\n      const chunk = downloadQueue.shift();\n      const downloadPromise = (async () => {\n        try {\n          const data = await downloadChunk(url, chunk.start, chunk.end, timeout);\n          await modelCache.saveChunk(url, chunk.index, data);\n          chunk.downloaded = true;\n          downloadedSize += data.byteLength;\n          state.downloadedSize = downloadedSize;\n          await modelCache.saveDownloadState(state);\n          reportProgress();\n        } finally {\n          inProgress.delete(chunk.index);\n        }\n      })();\n      inProgress.set(chunk.index, downloadPromise);\n    }\n    if (inProgress.size > 0) {\n      await Promise.race(inProgress.values());\n    }\n  }\n  const chunks = await modelCache.getChunks(url);\n  const result = new Uint8Array(totalSize);\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(new Uint8Array(chunk), offset);\n    offset += chunk.byteLength;\n  }\n  await modelCache.saveMeta({\n    url,\n    size: totalSize,\n    etag,\n    cachedAt: Date.now(),\n    chunks: chunks.length,\n    complete: true\n  });\n  await modelCache.deleteDownloadState(url);\n  return result.buffer;\n}\nasync function downloadSimple(url, timeout, onProgress) {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n  try {\n    const response = await fetch(url, { signal: controller.signal });\n    if (!response.ok) {\n      throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n    }\n    const contentLength = response.headers.get(\"Content-Length\");\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n    if (!response.body || !onProgress || total === 0) {\n      return await response.arrayBuffer();\n    }\n    const reader = response.body.getReader();\n    const chunks = [];\n    let loaded = 0;\n    const startTime = Date.now();\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done)\n        break;\n      chunks.push(value);\n      loaded += value.length;\n      const elapsed = (Date.now() - startTime) / 1e3;\n      const speed = elapsed > 0 ? loaded / elapsed : 0;\n      const remaining = total - loaded;\n      const eta = speed > 0 ? remaining / speed * 1e3 : 0;\n      onProgress({\n        loaded,\n        total,\n        percent: loaded / total * 100,\n        speed,\n        eta\n      });\n    }\n    const result = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      result.set(chunk, offset);\n      offset += chunk.length;\n    }\n    return result.buffer;\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\nasync function loadModelData(url, options = {}) {\n  const { cache = true, forceDownload = false, resumable = true } = options;\n  if (cache && !forceDownload) {\n    const cached = await modelCache.getModel(url);\n    if (cached) {\n      const firstByte = new Uint8Array(cached)[0];\n      const isHtmlOrText = firstByte === 60 || firstByte === 123;\n      if (isHtmlOrText || cached.byteLength < 1024) {\n        console.warn(`[edgeFlow.js] Cached model for ${url} appears corrupt (${cached.byteLength} bytes, first byte 0x${firstByte?.toString(16)}). Evicting and re-downloading.`);\n        await modelCache.deleteModel(url);\n      } else {\n        console.log(`\\u2713 Model loaded from cache: ${url}`);\n        options.onProgress?.({\n          loaded: cached.byteLength,\n          total: cached.byteLength,\n          percent: 100,\n          speed: 0,\n          eta: 0\n        });\n        return cached;\n      }\n    }\n  }\n  let data;\n  if (resumable) {\n    data = await downloadWithResume(url, options);\n  } else {\n    data = await downloadSimple(url, options.timeout ?? 3e4, options.onProgress);\n  }\n  if (cache) {\n    if (!resumable) {\n      await modelCache.saveChunk(url, 0, data);\n      await modelCache.saveMeta({\n        url,\n        size: data.byteLength,\n        cachedAt: Date.now(),\n        chunks: 1,\n        complete: true\n      });\n    }\n  }\n  return data;\n}\nfunction preloadModel(url, options = {}) {\n  return preloadManager.preload(url, options);\n}\nfunction preloadModels(urls, options = {}) {\n  return Promise.all(urls.map(({ url, priority }) => preloadManager.preload(url, { ...options, priority })));\n}\nasync function isModelCached(url) {\n  const meta = await modelCache.getMeta(url);\n  return meta?.complete ?? false;\n}\nasync function getCachedModel(url) {\n  return modelCache.getModel(url);\n}\nasync function deleteCachedModel(url) {\n  return modelCache.deleteModel(url);\n}\nasync function clearModelCache() {\n  return modelCache.clear();\n}\nasync function getModelCacheStats() {\n  return modelCache.getStats();\n}\nfunction getPreloadStatus(url) {\n  return preloadManager.getStatus(url);\n}\nfunction cancelPreload(url) {\n  preloadManager.cancel(url);\n}\nasync function getPreloadedModel(url) {\n  return preloadManager.get(url);\n}\nvar DB_NAME, DB_VERSION, STORE_META, STORE_CHUNKS, STORE_STATE, ModelCache2, modelCache, PreloadManager, preloadManager;\nvar init_model_loader = __esm({\n  \"dist/utils/model-loader.js\"() {\n    \"use strict\";\n    DB_NAME = \"edgeflow-model-cache\";\n    DB_VERSION = 1;\n    STORE_META = \"meta\";\n    STORE_CHUNKS = \"chunks\";\n    STORE_STATE = \"download-state\";\n    ModelCache2 = class {\n      constructor() {\n        __publicField(this, \"db\", null);\n        __publicField(this, \"dbPromise\", null);\n      }\n      /**\n       * Open the database\n       */\n      async openDB() {\n        if (this.db)\n          return this.db;\n        if (this.dbPromise)\n          return this.dbPromise;\n        this.dbPromise = new Promise((resolve, reject) => {\n          const request = indexedDB.open(DB_NAME, DB_VERSION);\n          request.onupgradeneeded = (event) => {\n            const db = event.target.result;\n            if (!db.objectStoreNames.contains(STORE_META)) {\n              db.createObjectStore(STORE_META, { keyPath: \"url\" });\n            }\n            if (!db.objectStoreNames.contains(STORE_CHUNKS)) {\n              const chunkStore = db.createObjectStore(STORE_CHUNKS, { keyPath: [\"url\", \"index\"] });\n              chunkStore.createIndex(\"url\", \"url\", { unique: false });\n            }\n            if (!db.objectStoreNames.contains(STORE_STATE)) {\n              db.createObjectStore(STORE_STATE, { keyPath: \"url\" });\n            }\n          };\n          request.onsuccess = () => {\n            this.db = request.result;\n            resolve(this.db);\n          };\n          request.onerror = () => reject(request.error);\n        });\n        return this.dbPromise;\n      }\n      /**\n       * Get cached model metadata\n       */\n      async getMeta(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_META, \"readonly\");\n          const store = tx.objectStore(STORE_META);\n          const request = store.get(url);\n          request.onsuccess = () => resolve(request.result ?? null);\n          request.onerror = () => reject(request.error);\n        });\n      }\n      /**\n       * Save model metadata (with quota error handling)\n       */\n      async saveMeta(meta) {\n        try {\n          await this.putInStore(STORE_META, meta);\n        } catch (err) {\n          if (this.isQuotaError(err)) {\n            await this.evictOldest(meta.size);\n            try {\n              await this.putInStore(STORE_META, meta);\n            } catch {\n              console.warn(\"[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache.\");\n            }\n          } else {\n            throw err;\n          }\n        }\n      }\n      /**\n       * Save a chunk (with quota error handling)\n       */\n      async saveChunk(url, index, data) {\n        try {\n          await this.putInStore(STORE_CHUNKS, { url, index, data });\n        } catch (err) {\n          if (this.isQuotaError(err)) {\n            await this.evictOldest(data.byteLength);\n            try {\n              await this.putInStore(STORE_CHUNKS, { url, index, data });\n            } catch {\n              console.warn(\"[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache for chunk.\");\n            }\n          } else {\n            throw err;\n          }\n        }\n      }\n      /**\n       * Generic put helper\n       */\n      async putInStore(storeName, value) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(storeName, \"readwrite\");\n          const store = tx.objectStore(storeName);\n          store.put(value);\n          tx.oncomplete = () => resolve();\n          tx.onerror = () => reject(tx.error);\n        });\n      }\n      /**\n       * Detect IndexedDB quota exceeded errors\n       */\n      isQuotaError(err) {\n        if (err instanceof DOMException) {\n          return err.name === \"QuotaExceededError\" || err.code === 22;\n        }\n        return false;\n      }\n      /**\n       * Evict oldest cached models to free space.\n       * Deletes models by ascending `cachedAt` until at least `bytesNeeded` is freed.\n       */\n      async evictOldest(bytesNeeded) {\n        const db = await this.openDB();\n        const allMeta = await new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_META, \"readonly\");\n          const store = tx.objectStore(STORE_META);\n          const request = store.getAll();\n          request.onsuccess = () => resolve(request.result ?? []);\n          request.onerror = () => reject(request.error);\n        });\n        allMeta.sort((a, b) => a.cachedAt - b.cachedAt);\n        let freed = 0;\n        for (const meta of allMeta) {\n          if (freed >= bytesNeeded)\n            break;\n          await this.deleteModel(meta.url);\n          freed += meta.size;\n        }\n      }\n      /**\n       * Get all chunks for a URL\n       */\n      async getChunks(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_CHUNKS, \"readonly\");\n          const store = tx.objectStore(STORE_CHUNKS);\n          const index = store.index(\"url\");\n          const request = index.getAll(url);\n          request.onsuccess = () => {\n            const results = request.result;\n            results.sort((a, b) => a.index - b.index);\n            resolve(results.map((r) => r.data));\n          };\n          request.onerror = () => reject(request.error);\n        });\n      }\n      /**\n       * Get complete model data (merged chunks)\n       */\n      async getModel(url) {\n        const meta = await this.getMeta(url);\n        if (!meta || !meta.complete)\n          return null;\n        const chunks = await this.getChunks(url);\n        if (chunks.length === 0)\n          return null;\n        const totalSize = chunks.reduce((sum2, chunk) => sum2 + chunk.byteLength, 0);\n        const result = new Uint8Array(totalSize);\n        let offset = 0;\n        for (const chunk of chunks) {\n          result.set(new Uint8Array(chunk), offset);\n          offset += chunk.byteLength;\n        }\n        return result.buffer;\n      }\n      /**\n       * Save download state (for resume, with quota handling)\n       */\n      async saveDownloadState(state) {\n        try {\n          await this.putInStore(STORE_STATE, state);\n        } catch (err) {\n          if (this.isQuotaError(err)) {\n            console.warn(\"[edgeFlow.js] IndexedDB quota exceeded saving download state; resume may not work.\");\n          } else {\n            throw err;\n          }\n        }\n      }\n      /**\n       * Get download state\n       */\n      async getDownloadState(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_STATE, \"readonly\");\n          const store = tx.objectStore(STORE_STATE);\n          const request = store.get(url);\n          request.onsuccess = () => resolve(request.result ?? null);\n          request.onerror = () => reject(request.error);\n        });\n      }\n      /**\n       * Delete download state\n       */\n      async deleteDownloadState(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_STATE, \"readwrite\");\n          const store = tx.objectStore(STORE_STATE);\n          store.delete(url);\n          tx.oncomplete = () => resolve();\n          tx.onerror = () => reject(tx.error);\n        });\n      }\n      /**\n       * Delete cached model\n       */\n      async deleteModel(url) {\n        const db = await this.openDB();\n        await new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_META, \"readwrite\");\n          const store = tx.objectStore(STORE_META);\n          store.delete(url);\n          tx.oncomplete = () => resolve();\n          tx.onerror = () => reject(tx.error);\n        });\n        const chunks = await this.getChunks(url);\n        if (chunks.length > 0) {\n          await new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_CHUNKS, \"readwrite\");\n            const store = tx.objectStore(STORE_CHUNKS);\n            const index = store.index(\"url\");\n            const request = index.openCursor(IDBKeyRange.only(url));\n            request.onsuccess = (event) => {\n              const cursor = event.target.result;\n              if (cursor) {\n                cursor.delete();\n                cursor.continue();\n              }\n            };\n            tx.oncomplete = () => resolve();\n            tx.onerror = () => reject(tx.error);\n          });\n        }\n        await this.deleteDownloadState(url);\n      }\n      /**\n       * Clear all cached models\n       */\n      async clear() {\n        const db = await this.openDB();\n        const stores = [STORE_META, STORE_CHUNKS, STORE_STATE];\n        for (const storeName of stores) {\n          await new Promise((resolve, reject) => {\n            const tx = db.transaction(storeName, \"readwrite\");\n            const store = tx.objectStore(storeName);\n            store.clear();\n            tx.oncomplete = () => resolve();\n            tx.onerror = () => reject(tx.error);\n          });\n        }\n      }\n      /**\n       * Get cache statistics\n       */\n      async getStats() {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n          const tx = db.transaction(STORE_META, \"readonly\");\n          const store = tx.objectStore(STORE_META);\n          const request = store.getAll();\n          request.onsuccess = () => {\n            const metas = request.result;\n            resolve({\n              models: metas.filter((m) => m.complete).length,\n              totalSize: metas.reduce((sum2, m) => sum2 + (m.complete ? m.size : 0), 0)\n            });\n          };\n          request.onerror = () => reject(request.error);\n        });\n      }\n    };\n    modelCache = new ModelCache2();\n    PreloadManager = class {\n      constructor() {\n        __publicField(this, \"tasks\", /* @__PURE__ */ new Map());\n        __publicField(this, \"queue\", []);\n        __publicField(this, \"maxConcurrent\", 2);\n        __publicField(this, \"activeCount\", 0);\n      }\n      /**\n       * Preload a model in the background\n       */\n      preload(url, options = {}) {\n        const existing = this.tasks.get(url);\n        if (existing) {\n          return existing.promise;\n        }\n        let resolve;\n        let reject;\n        const promise = new Promise((res, rej) => {\n          resolve = res;\n          reject = rej;\n        });\n        const task = {\n          url,\n          priority: options.priority ?? 0,\n          options,\n          promise,\n          resolve,\n          reject,\n          status: \"pending\"\n        };\n        this.tasks.set(url, task);\n        const insertIndex = this.queue.findIndex((u) => {\n          const t = this.tasks.get(u);\n          return t && t.priority < task.priority;\n        });\n        if (insertIndex === -1) {\n          this.queue.push(url);\n        } else {\n          this.queue.splice(insertIndex, 0, url);\n        }\n        this.processQueue();\n        return promise;\n      }\n      /**\n       * Process the preload queue\n       */\n      async processQueue() {\n        while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {\n          const url = this.queue.shift();\n          if (!url)\n            break;\n          const task = this.tasks.get(url);\n          if (!task || task.status !== \"pending\")\n            continue;\n          this.activeCount++;\n          task.status = \"loading\";\n          this.downloadTask(task).finally(() => {\n            this.activeCount--;\n            this.processQueue();\n          });\n        }\n      }\n      /**\n       * Download a preload task\n       */\n      async downloadTask(task) {\n        try {\n          const data = await loadModelData(task.url, task.options);\n          task.status = \"complete\";\n          task.resolve(data);\n        } catch (error) {\n          task.status = \"error\";\n          task.reject(error instanceof Error ? error : new Error(String(error)));\n        }\n      }\n      /**\n       * Check if a model is preloaded\n       */\n      isPreloaded(url) {\n        const task = this.tasks.get(url);\n        return task?.status === \"complete\";\n      }\n      /**\n       * Get preload status\n       */\n      getStatus(url) {\n        const task = this.tasks.get(url);\n        return task?.status ?? \"not_found\";\n      }\n      /**\n       * Get preloaded model data\n       */\n      async get(url) {\n        const task = this.tasks.get(url);\n        if (!task)\n          return null;\n        if (task.status === \"complete\" || task.status === \"loading\") {\n          return task.promise;\n        }\n        return null;\n      }\n      /**\n       * Cancel preload\n       */\n      cancel(url) {\n        const task = this.tasks.get(url);\n        if (task && task.status === \"pending\") {\n          this.tasks.delete(url);\n          this.queue = this.queue.filter((u) => u !== url);\n          task.reject(new Error(\"Preload cancelled\"));\n        }\n      }\n      /**\n       * Clear all preloads\n       */\n      clear() {\n        for (const [, task] of this.tasks) {\n          if (task.status === \"pending\") {\n            task.reject(new Error(\"Preload cleared\"));\n          }\n        }\n        this.tasks.clear();\n        this.queue = [];\n      }\n    };\n    preloadManager = new PreloadManager();\n  }\n});\n\n// dist/index.js\ninit_types();\ninit_tensor();\n\n// dist/core/scheduler.js\ninit_types();\nvar Task = class {\n  constructor(id, modelId, priority, executor) {\n    __publicField(this, \"id\");\n    __publicField(this, \"modelId\");\n    __publicField(this, \"priority\");\n    __publicField(this, \"createdAt\");\n    __publicField(this, \"_status\", \"pending\");\n    __publicField(this, \"_startedAt\");\n    __publicField(this, \"_completedAt\");\n    __publicField(this, \"_result\");\n    __publicField(this, \"_error\");\n    __publicField(this, \"_executor\");\n    __publicField(this, \"_resolvers\", []);\n    __publicField(this, \"_cancelled\", false);\n    this.id = id;\n    this.modelId = modelId;\n    this.priority = priority;\n    this.createdAt = Date.now();\n    this._executor = executor;\n  }\n  get status() {\n    return this._status;\n  }\n  get startedAt() {\n    return this._startedAt;\n  }\n  get completedAt() {\n    return this._completedAt;\n  }\n  get result() {\n    return this._result;\n  }\n  get error() {\n    return this._error;\n  }\n  /**\n   * Cancel the task\n   */\n  cancel() {\n    if (this._status === \"pending\") {\n      this._cancelled = true;\n      this._status = \"cancelled\";\n      this._completedAt = Date.now();\n      const cancelError = new EdgeFlowError(\"Task was cancelled\", ErrorCodes.INFERENCE_CANCELLED, { taskId: this.id });\n      for (const { reject } of this._resolvers) {\n        reject(cancelError);\n      }\n      this._resolvers = [];\n    }\n  }\n  /**\n   * Wait for task completion\n   */\n  wait() {\n    if (this._status === \"completed\") {\n      return Promise.resolve(this._result);\n    }\n    if (this._status === \"failed\") {\n      return Promise.reject(this._error);\n    }\n    if (this._status === \"cancelled\") {\n      return Promise.reject(new EdgeFlowError(\"Task was cancelled\", ErrorCodes.INFERENCE_CANCELLED, { taskId: this.id }));\n    }\n    return new Promise((resolve, reject) => {\n      this._resolvers.push({ resolve, reject });\n    });\n  }\n  /**\n   * Execute the task\n   */\n  async execute() {\n    if (this._cancelled) {\n      return;\n    }\n    this._status = \"running\";\n    this._startedAt = Date.now();\n    try {\n      this._result = await this._executor();\n      this._status = \"completed\";\n      this._completedAt = Date.now();\n      for (const { resolve } of this._resolvers) {\n        resolve(this._result);\n      }\n    } catch (err) {\n      this._error = err instanceof Error ? err : new Error(String(err));\n      this._status = \"failed\";\n      this._completedAt = Date.now();\n      for (const { reject } of this._resolvers) {\n        reject(this._error);\n      }\n    }\n    this._resolvers = [];\n  }\n};\nvar PRIORITY_ORDER = {\n  critical: 0,\n  high: 1,\n  normal: 2,\n  low: 3\n};\nvar PriorityQueue = class {\n  constructor() {\n    __publicField(this, \"items\", []);\n  }\n  get length() {\n    return this.items.length;\n  }\n  isEmpty() {\n    return this.items.length === 0;\n  }\n  /**\n   * Add item to queue with priority ordering\n   */\n  enqueue(item) {\n    let inserted = false;\n    for (let i = 0; i < this.items.length; i++) {\n      const currentItem = this.items[i];\n      if (currentItem && PRIORITY_ORDER[item.priority] < PRIORITY_ORDER[currentItem.priority]) {\n        this.items.splice(i, 0, item);\n        inserted = true;\n        break;\n      }\n    }\n    if (!inserted) {\n      this.items.push(item);\n    }\n  }\n  /**\n   * Remove and return highest priority item\n   */\n  dequeue() {\n    return this.items.shift();\n  }\n  /**\n   * Peek at highest priority item without removing\n   */\n  peek() {\n    return this.items[0];\n  }\n  /**\n   * Remove a specific item by ID\n   */\n  remove(id) {\n    const index = this.items.findIndex((item) => item.id === id);\n    if (index !== -1) {\n      const [removed] = this.items.splice(index, 1);\n      return removed;\n    }\n    return void 0;\n  }\n  /**\n   * Get all items\n   */\n  getAll() {\n    return [...this.items];\n  }\n  /**\n   * Clear the queue\n   */\n  clear() {\n    this.items = [];\n  }\n};\nvar taskIdCounter = 0;\nfunction generateTaskId() {\n  return `task_${++taskIdCounter}_${Date.now().toString(36)}`;\n}\nvar DEFAULT_OPTIONS = {\n  maxConcurrentTasks: 4,\n  maxConcurrentPerModel: 1,\n  defaultTimeout: 3e4,\n  enableBatching: false,\n  maxBatchSize: 32,\n  batchTimeout: 50,\n  maxRetries: 0,\n  retryBaseDelay: 1e3,\n  circuitBreaker: false,\n  circuitBreakerThreshold: 5,\n  circuitBreakerResetTimeout: 3e4\n};\nvar InferenceScheduler = class {\n  constructor(options = {}) {\n    __publicField(this, \"options\");\n    __publicField(this, \"queues\", /* @__PURE__ */ new Map());\n    __publicField(this, \"runningTasks\", /* @__PURE__ */ new Map());\n    __publicField(this, \"allTasks\", /* @__PURE__ */ new Map());\n    __publicField(this, \"batchers\", /* @__PURE__ */ new Map());\n    __publicField(this, \"listeners\", /* @__PURE__ */ new Map());\n    __publicField(this, \"circuits\", /* @__PURE__ */ new Map());\n    __publicField(this, \"globalRunningCount\", 0);\n    __publicField(this, \"isProcessing\", false);\n    __publicField(this, \"disposed\", false);\n    this.options = { ...DEFAULT_OPTIONS, ...options };\n  }\n  /**\n   * Get circuit breaker state for a model, creating default if absent\n   */\n  getCircuit(modelId) {\n    let c = this.circuits.get(modelId);\n    if (!c) {\n      c = { failures: 0, state: \"closed\", lastFailure: 0 };\n      this.circuits.set(modelId, c);\n    }\n    return c;\n  }\n  /**\n   * Check if the circuit for a model allows new tasks\n   */\n  isCircuitOpen(modelId) {\n    if (!this.options.circuitBreaker)\n      return false;\n    const c = this.getCircuit(modelId);\n    if (c.state === \"closed\")\n      return false;\n    if (c.state === \"open\") {\n      if (Date.now() - c.lastFailure > this.options.circuitBreakerResetTimeout) {\n        c.state = \"half-open\";\n        return false;\n      }\n      return true;\n    }\n    return false;\n  }\n  /**\n   * Record a success for circuit breaker\n   */\n  circuitSuccess(modelId) {\n    if (!this.options.circuitBreaker)\n      return;\n    const c = this.getCircuit(modelId);\n    c.failures = 0;\n    c.state = \"closed\";\n  }\n  /**\n   * Record a failure for circuit breaker\n   */\n  circuitFailure(modelId) {\n    if (!this.options.circuitBreaker)\n      return;\n    const c = this.getCircuit(modelId);\n    c.failures++;\n    c.lastFailure = Date.now();\n    if (c.failures >= this.options.circuitBreakerThreshold) {\n      c.state = \"open\";\n      this.emit(\"inference:error\", {\n        modelId,\n        error: new Error(`Circuit breaker opened after ${c.failures} consecutive failures`)\n      });\n    }\n  }\n  /**\n   * Get or create queue for a model\n   */\n  getQueue(modelId) {\n    let queue = this.queues.get(modelId);\n    if (!queue) {\n      queue = new PriorityQueue();\n      this.queues.set(modelId, queue);\n    }\n    return queue;\n  }\n  /**\n   * Get or create running set for a model\n   */\n  getRunningSet(modelId) {\n    let running = this.runningTasks.get(modelId);\n    if (!running) {\n      running = /* @__PURE__ */ new Set();\n      this.runningTasks.set(modelId, running);\n    }\n    return running;\n  }\n  /**\n   * Check if we can start a new task for a model\n   */\n  canStartTask(modelId) {\n    if (this.globalRunningCount >= this.options.maxConcurrentTasks) {\n      return false;\n    }\n    const running = this.runningTasks.get(modelId);\n    if (running && running.size >= this.options.maxConcurrentPerModel) {\n      return false;\n    }\n    return true;\n  }\n  /**\n   * Process pending tasks\n   */\n  async processQueue() {\n    if (this.isProcessing || this.disposed) {\n      return;\n    }\n    this.isProcessing = true;\n    try {\n      const tasksToStart = [];\n      for (const [modelId, queue] of this.queues) {\n        while (!queue.isEmpty() && this.canStartTask(modelId)) {\n          const task = queue.dequeue();\n          if (task && task.status === \"pending\") {\n            tasksToStart.push(task);\n            const running = this.getRunningSet(modelId);\n            running.add(task.id);\n            this.globalRunningCount++;\n          }\n        }\n      }\n      await Promise.all(tasksToStart.map(async (task) => {\n        this.emit(\"inference:start\", { taskId: task.id, modelId: task.modelId });\n        try {\n          await task.execute();\n          this.emit(\"inference:complete\", {\n            taskId: task.id,\n            modelId: task.modelId,\n            duration: (task.completedAt ?? 0) - (task.startedAt ?? 0)\n          });\n        } catch (error) {\n          this.emit(\"inference:error\", {\n            taskId: task.id,\n            modelId: task.modelId,\n            error\n          });\n        } finally {\n          const running = this.runningTasks.get(task.modelId);\n          if (running) {\n            running.delete(task.id);\n          }\n          this.globalRunningCount--;\n        }\n      }));\n    } finally {\n      this.isProcessing = false;\n    }\n    let hasPending = false;\n    for (const queue of this.queues.values()) {\n      if (!queue.isEmpty()) {\n        hasPending = true;\n        break;\n      }\n    }\n    if (hasPending) {\n      setTimeout(() => this.processQueue(), 0);\n    }\n  }\n  /**\n   * Schedule a task for execution\n   */\n  schedule(modelId, executor, priority = \"normal\") {\n    if (this.disposed) {\n      throw new EdgeFlowError(\"Scheduler has been disposed\", ErrorCodes.RUNTIME_NOT_INITIALIZED);\n    }\n    if (this.isCircuitOpen(modelId)) {\n      throw new EdgeFlowError(`Circuit breaker is open for model ${modelId} \\u2014 too many consecutive failures. Retry after ${this.options.circuitBreakerResetTimeout}ms.`, ErrorCodes.INFERENCE_FAILED, { modelId });\n    }\n    const maxRetries = this.options.maxRetries;\n    const baseDelay = this.options.retryBaseDelay;\n    const wrappedExecutor = maxRetries > 0 ? async () => {\n      let lastError;\n      for (let attempt = 0; attempt <= maxRetries; attempt++) {\n        try {\n          const result = await executor();\n          this.circuitSuccess(modelId);\n          return result;\n        } catch (err) {\n          lastError = err instanceof Error ? err : new Error(String(err));\n          this.circuitFailure(modelId);\n          if (attempt < maxRetries) {\n            const delay = baseDelay * Math.pow(2, attempt);\n            await new Promise((r) => setTimeout(r, delay));\n          }\n        }\n      }\n      throw lastError;\n    } : async () => {\n      try {\n        const result = await executor();\n        this.circuitSuccess(modelId);\n        return result;\n      } catch (err) {\n        this.circuitFailure(modelId);\n        throw err;\n      }\n    };\n    const task = new Task(generateTaskId(), modelId, priority, wrappedExecutor);\n    this.allTasks.set(task.id, task);\n    const queue = this.getQueue(modelId);\n    queue.enqueue(task);\n    this.processQueue();\n    return task;\n  }\n  /**\n   * Schedule with timeout\n   */\n  scheduleWithTimeout(modelId, executor, timeout = this.options.defaultTimeout, priority = \"normal\") {\n    const timeoutExecutor = () => {\n      return new Promise((resolve, reject) => {\n        const timer = setTimeout(() => {\n          reject(new EdgeFlowError(`Task timed out after ${timeout}ms`, ErrorCodes.INFERENCE_TIMEOUT, { timeout }));\n        }, timeout);\n        executor().then((result) => {\n          clearTimeout(timer);\n          resolve(result);\n        }).catch((error) => {\n          clearTimeout(timer);\n          reject(error);\n        });\n      });\n    };\n    return this.schedule(modelId, timeoutExecutor, priority);\n  }\n  /**\n   * Schedule multiple tasks and wait for all\n   */\n  async scheduleAll(tasks) {\n    const scheduledTasks = tasks.map(({ modelId, executor, priority }) => this.schedule(modelId, executor, priority));\n    return Promise.all(scheduledTasks.map((task) => task.wait()));\n  }\n  /**\n   * Get task by ID\n   */\n  getTask(taskId) {\n    return this.allTasks.get(taskId);\n  }\n  /**\n   * Cancel a task\n   */\n  cancelTask(taskId) {\n    const task = this.allTasks.get(taskId);\n    if (task && task.status === \"pending\") {\n      task.cancel();\n      for (const queue of this.queues.values()) {\n        queue.remove(taskId);\n      }\n      return true;\n    }\n    return false;\n  }\n  /**\n   * Cancel all tasks for a model\n   */\n  cancelAllForModel(modelId) {\n    const queue = this.queues.get(modelId);\n    if (!queue)\n      return 0;\n    let cancelled = 0;\n    for (const task of queue.getAll()) {\n      if (task.status === \"pending\") {\n        task.cancel();\n        cancelled++;\n      }\n    }\n    queue.clear();\n    return cancelled;\n  }\n  /**\n   * Get statistics\n   */\n  getStats() {\n    const stats = {\n      totalTasks: this.allTasks.size,\n      pendingTasks: 0,\n      runningTasks: 0,\n      completedTasks: 0,\n      failedTasks: 0,\n      cancelledTasks: 0,\n      queuedByModel: {}\n    };\n    for (const task of this.allTasks.values()) {\n      switch (task.status) {\n        case \"pending\":\n          stats.pendingTasks++;\n          break;\n        case \"running\":\n          stats.runningTasks++;\n          break;\n        case \"completed\":\n          stats.completedTasks++;\n          break;\n        case \"failed\":\n          stats.failedTasks++;\n          break;\n        case \"cancelled\":\n          stats.cancelledTasks++;\n          break;\n      }\n    }\n    for (const [modelId, queue] of this.queues) {\n      stats.queuedByModel[modelId] = queue.length;\n    }\n    return stats;\n  }\n  /**\n   * Add event listener\n   */\n  on(event, listener) {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = /* @__PURE__ */ new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener);\n  }\n  /**\n   * Remove event listener\n   */\n  off(event, listener) {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener);\n    }\n  }\n  /**\n   * Emit event\n   */\n  emit(type, data) {\n    const event = {\n      type,\n      timestamp: Date.now(),\n      data\n    };\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error(\"Error in event listener:\", error);\n        }\n      }\n    }\n  }\n  /**\n   * Clear completed/failed/cancelled tasks from history\n   */\n  clearHistory() {\n    for (const [taskId, task] of this.allTasks) {\n      if (task.status === \"completed\" || task.status === \"failed\" || task.status === \"cancelled\") {\n        this.allTasks.delete(taskId);\n      }\n    }\n  }\n  /**\n   * Dispose the scheduler\n   */\n  dispose() {\n    this.disposed = true;\n    for (const queue of this.queues.values()) {\n      for (const task of queue.getAll()) {\n        task.cancel();\n      }\n      queue.clear();\n    }\n    for (const batcher of this.batchers.values()) {\n      batcher.clear();\n    }\n    this.queues.clear();\n    this.runningTasks.clear();\n    this.allTasks.clear();\n    this.batchers.clear();\n    this.listeners.clear();\n  }\n};\nvar globalScheduler = null;\nfunction getScheduler() {\n  if (!globalScheduler) {\n    globalScheduler = new InferenceScheduler();\n  }\n  return globalScheduler;\n}\nfunction setScheduler(scheduler) {\n  if (globalScheduler) {\n    globalScheduler.dispose();\n  }\n  globalScheduler = scheduler;\n}\nfunction configureScheduler(options) {\n  setScheduler(new InferenceScheduler(options));\n}\n\n// dist/core/memory.js\nvar DEFAULT_POOL_CONFIG = {\n  initialSize: 64 * 1024 * 1024,\n  // 64MB\n  maxSize: 512 * 1024 * 1024,\n  // 512MB\n  growthFactor: 1.5,\n  autoGC: true,\n  gcThreshold: 0.8\n  // 80%\n};\nvar _MemoryManager = class _MemoryManager {\n  constructor(config = {}) {\n    __publicField(this, \"config\");\n    __publicField(this, \"resources\", /* @__PURE__ */ new Map());\n    __publicField(this, \"disposers\", /* @__PURE__ */ new Map());\n    __publicField(this, \"listeners\", /* @__PURE__ */ new Map());\n    __publicField(this, \"allocated\", 0);\n    __publicField(this, \"peak\", 0);\n    __publicField(this, \"gcScheduled\", false);\n    __publicField(this, \"disposed\", false);\n    this.config = { ...DEFAULT_POOL_CONFIG, ...config };\n  }\n  /**\n   * Get singleton instance\n   */\n  static getInstance() {\n    if (!_MemoryManager.instance) {\n      _MemoryManager.instance = new _MemoryManager();\n    }\n    return _MemoryManager.instance;\n  }\n  /**\n   * Configure the memory manager\n   */\n  static configure(config) {\n    if (_MemoryManager.instance) {\n      console.warn(\"MemoryManager already initialized, configuration may not apply\");\n    }\n    _MemoryManager.instance = new _MemoryManager(config);\n  }\n  /**\n   * Track a tensor\n   */\n  track(tensor2, disposer) {\n    if (this.disposed)\n      return;\n    const size = this.estimateTensorSize(tensor2);\n    this.resources.set(tensor2.id, {\n      id: tensor2.id,\n      type: \"tensor\",\n      size,\n      createdAt: Date.now(),\n      stackTrace: this.captureStackTrace()\n    });\n    if (disposer) {\n      this.disposers.set(tensor2.id, disposer);\n    }\n    this.allocated += size;\n    this.peak = Math.max(this.peak, this.allocated);\n    this.checkMemoryThreshold();\n  }\n  /**\n   * Track a model\n   */\n  trackModel(model, disposer) {\n    if (this.disposed)\n      return;\n    const size = model.metadata.sizeBytes;\n    this.resources.set(model.id, {\n      id: model.id,\n      type: \"model\",\n      size,\n      createdAt: Date.now(),\n      stackTrace: this.captureStackTrace()\n    });\n    if (disposer) {\n      this.disposers.set(model.id, disposer);\n    }\n    this.allocated += size;\n    this.peak = Math.max(this.peak, this.allocated);\n    this.checkMemoryThreshold();\n  }\n  /**\n   * Untrack a resource\n   */\n  untrack(id) {\n    const resource = this.resources.get(id);\n    if (resource) {\n      this.allocated -= resource.size;\n      this.resources.delete(id);\n      this.disposers.delete(id);\n    }\n  }\n  /**\n   * Release a resource\n   */\n  release(resourceOrId) {\n    const id = typeof resourceOrId === \"string\" ? resourceOrId : resourceOrId.id;\n    const disposer = this.disposers.get(id);\n    if (disposer) {\n      try {\n        disposer();\n      } catch (error) {\n        console.error(\"Error disposing resource:\", error);\n      }\n    }\n    this.untrack(id);\n  }\n  /**\n   * Estimate tensor memory size\n   */\n  estimateTensorSize(tensor2) {\n    const bytesPerElement = this.getBytesPerElement(tensor2.dtype);\n    return tensor2.size * bytesPerElement;\n  }\n  /**\n   * Get bytes per element for a data type\n   */\n  getBytesPerElement(dtype) {\n    switch (dtype) {\n      case \"float32\":\n        return 4;\n      case \"float16\":\n        return 2;\n      case \"int32\":\n        return 4;\n      case \"int64\":\n        return 8;\n      case \"uint8\":\n      case \"int8\":\n      case \"bool\":\n        return 1;\n      default:\n        return 4;\n    }\n  }\n  /**\n   * Capture stack trace for debugging\n   */\n  captureStackTrace() {\n    if (typeof Error.captureStackTrace === \"function\") {\n      const obj = {};\n      Error.captureStackTrace(obj, this.captureStackTrace);\n      return obj.stack;\n    }\n    return new Error().stack;\n  }\n  /**\n   * Check if memory threshold is exceeded\n   */\n  checkMemoryThreshold() {\n    if (!this.config.autoGC)\n      return;\n    const usage = this.allocated / this.config.maxSize;\n    if (usage >= this.config.gcThreshold && !this.gcScheduled) {\n      this.gcScheduled = true;\n      this.emit(\"memory:warning\", {\n        allocated: this.allocated,\n        maxSize: this.config.maxSize,\n        usage\n      });\n      setTimeout(() => {\n        this.gc();\n        this.gcScheduled = false;\n      }, 0);\n    }\n  }\n  /**\n   * Garbage collection helper.\n   *\n   * Identifies stale resources and optionally evicts them.\n   * @param evict - If true, actually dispose stale resources (default: false)\n   * @param maxAge - Resources older than this (ms) are considered stale (default: 5 min)\n   */\n  gc(evict = false, maxAge = 5 * 60 * 1e3) {\n    this.emit(\"memory:gc\", { before: this.allocated });\n    const now = Date.now();\n    const staleIds = [];\n    for (const [id, resource] of this.resources) {\n      if (now - resource.createdAt > maxAge) {\n        staleIds.push(id);\n      }\n    }\n    if (evict) {\n      for (const id of staleIds) {\n        this.release(id);\n      }\n    }\n    this.emit(\"memory:gc\", {\n      after: this.allocated,\n      evicted: evict ? staleIds.length : 0,\n      potentialCleanup: staleIds.length\n    });\n  }\n  /**\n   * Query actual browser memory usage via performance.measureUserAgentSpecificMemory()\n   * (Chrome 89+, requires cross-origin isolation). Returns null if unavailable.\n   */\n  async measureBrowserMemory() {\n    try {\n      if (typeof performance !== \"undefined\" && \"measureUserAgentSpecificMemory\" in performance) {\n        const result = await performance.measureUserAgentSpecificMemory();\n        return result;\n      }\n    } catch {\n    }\n    return null;\n  }\n  /**\n   * Get the device's total memory hint (navigator.deviceMemory).\n   * Returns null if unavailable. Value is in GiB, rounded (e.g. 4, 8).\n   */\n  getDeviceMemory() {\n    try {\n      if (typeof navigator !== \"undefined\" && \"deviceMemory\" in navigator) {\n        return navigator.deviceMemory ?? null;\n      }\n    } catch {\n    }\n    return null;\n  }\n  /**\n   * Get memory statistics\n   */\n  getStats() {\n    let tensorCount = 0;\n    let modelCount = 0;\n    for (const resource of this.resources.values()) {\n      if (resource.type === \"tensor\") {\n        tensorCount++;\n      } else {\n        modelCount++;\n      }\n    }\n    return {\n      allocated: this.allocated,\n      used: this.allocated,\n      // In JS, allocated = used\n      peak: this.peak,\n      tensorCount,\n      modelCount\n    };\n  }\n  /**\n   * Get detailed resource list (for debugging)\n   */\n  getResourceDetails() {\n    return Array.from(this.resources.values());\n  }\n  /**\n   * Check for potential memory leaks\n   */\n  detectLeaks(maxAge = 10 * 60 * 1e3) {\n    const now = Date.now();\n    const potentialLeaks = [];\n    for (const resource of this.resources.values()) {\n      if (now - resource.createdAt > maxAge) {\n        potentialLeaks.push(resource);\n      }\n    }\n    return potentialLeaks;\n  }\n  /**\n   * Add event listener\n   */\n  on(event, listener) {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = /* @__PURE__ */ new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener);\n  }\n  /**\n   * Remove event listener\n   */\n  off(event, listener) {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener);\n    }\n  }\n  /**\n   * Emit event\n   */\n  emit(type, data) {\n    const event = {\n      type,\n      timestamp: Date.now(),\n      data\n    };\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error(\"Error in event listener:\", error);\n        }\n      }\n    }\n  }\n  /**\n   * Reset statistics\n   */\n  resetStats() {\n    this.peak = this.allocated;\n  }\n  /**\n   * Dispose all resources\n   */\n  disposeAll() {\n    for (const id of this.resources.keys()) {\n      this.release(id);\n    }\n  }\n  /**\n   * Dispose the manager\n   */\n  dispose() {\n    this.disposeAll();\n    this.disposed = true;\n    this.listeners.clear();\n    _MemoryManager.instance = null;\n  }\n};\n__publicField(_MemoryManager, \"instance\", null);\nvar MemoryManager = _MemoryManager;\nvar MemoryScope = class _MemoryScope {\n  constructor(parent) {\n    __publicField(this, \"resources\", []);\n    __publicField(this, \"children\", []);\n    __publicField(this, \"parent\", null);\n    if (parent) {\n      this.parent = parent;\n      parent.children.push(this);\n    }\n  }\n  /**\n   * Track a resource in this scope\n   */\n  track(resource) {\n    this.resources.push(resource);\n    return resource;\n  }\n  /**\n   * Create a child scope\n   */\n  createChild() {\n    return new _MemoryScope(this);\n  }\n  /**\n   * Keep a resource (don't dispose it when scope ends)\n   */\n  keep(resource) {\n    const index = this.resources.indexOf(resource);\n    if (index !== -1) {\n      this.resources.splice(index, 1);\n    }\n    return resource;\n  }\n  /**\n   * Dispose all resources in this scope\n   */\n  dispose() {\n    for (const child of this.children) {\n      child.dispose();\n    }\n    this.children = [];\n    for (let i = this.resources.length - 1; i >= 0; i--) {\n      try {\n        this.resources[i]?.dispose();\n      } catch (error) {\n        console.error(\"Error disposing resource in scope:\", error);\n      }\n    }\n    this.resources = [];\n    if (this.parent) {\n      const index = this.parent.children.indexOf(this);\n      if (index !== -1) {\n        this.parent.children.splice(index, 1);\n      }\n      this.parent = null;\n    }\n  }\n};\nasync function withMemoryScope(fn) {\n  const scope = new MemoryScope();\n  try {\n    return await fn(scope);\n  } finally {\n    scope.dispose();\n  }\n}\nfunction withMemoryScopeSync(fn) {\n  const scope = new MemoryScope();\n  try {\n    return fn(scope);\n  } finally {\n    scope.dispose();\n  }\n}\nvar ModelCache = class {\n  constructor(options = {}) {\n    __publicField(this, \"maxSize\");\n    __publicField(this, \"maxModels\");\n    __publicField(this, \"cache\", /* @__PURE__ */ new Map());\n    __publicField(this, \"currentSize\", 0);\n    this.maxSize = options.maxSize ?? 256 * 1024 * 1024;\n    this.maxModels = options.maxModels ?? 5;\n  }\n  /**\n   * Get a model from cache\n   */\n  get(key) {\n    const entry = this.cache.get(key);\n    if (entry) {\n      entry.lastAccess = Date.now();\n      return entry.model;\n    }\n    return void 0;\n  }\n  /**\n   * Add a model to cache\n   */\n  set(key, model) {\n    const size = model.metadata.sizeBytes;\n    while ((this.currentSize + size > this.maxSize || this.cache.size >= this.maxModels) && this.cache.size > 0) {\n      this.evictLRU();\n    }\n    this.cache.set(key, {\n      model,\n      size,\n      lastAccess: Date.now()\n    });\n    this.currentSize += size;\n  }\n  /**\n   * Remove a model from cache\n   */\n  delete(key) {\n    const entry = this.cache.get(key);\n    if (entry) {\n      entry.model.dispose();\n      this.currentSize -= entry.size;\n      this.cache.delete(key);\n      return true;\n    }\n    return false;\n  }\n  /**\n   * Check if model is in cache\n   */\n  has(key) {\n    return this.cache.has(key);\n  }\n  /**\n   * Evict least recently used model\n   */\n  evictLRU() {\n    let oldestKey = null;\n    let oldestTime = Infinity;\n    for (const [key, entry] of this.cache) {\n      if (entry.lastAccess < oldestTime) {\n        oldestTime = entry.lastAccess;\n        oldestKey = key;\n      }\n    }\n    if (oldestKey) {\n      this.delete(oldestKey);\n    }\n  }\n  /**\n   * Clear the cache\n   */\n  clear() {\n    for (const entry of this.cache.values()) {\n      entry.model.dispose();\n    }\n    this.cache.clear();\n    this.currentSize = 0;\n  }\n  /**\n   * Get cache statistics\n   */\n  getStats() {\n    return {\n      size: this.currentSize,\n      count: this.cache.size,\n      maxSize: this.maxSize,\n      maxModels: this.maxModels\n    };\n  }\n};\nfunction getMemoryManager() {\n  return MemoryManager.getInstance();\n}\nfunction getMemoryStats() {\n  return MemoryManager.getInstance().getStats();\n}\nfunction release(resource) {\n  MemoryManager.getInstance().release(resource);\n}\nfunction gc() {\n  MemoryManager.getInstance().gc();\n}\n\n// dist/core/runtime.js\ninit_types();\nvar runtimeFactories = /* @__PURE__ */ new Map();\nvar runtimeInstances = /* @__PURE__ */ new Map();\nvar RUNTIME_PRIORITY = [\"webgpu\", \"webnn\", \"wasm\"];\nvar _RuntimeManager = class _RuntimeManager {\n  constructor() {\n    __publicField(this, \"listeners\", /* @__PURE__ */ new Map());\n    __publicField(this, \"defaultRuntime\", \"auto\");\n  }\n  /**\n   * Get singleton instance\n   */\n  static getInstance() {\n    if (!_RuntimeManager.instance) {\n      _RuntimeManager.instance = new _RuntimeManager();\n    }\n    return _RuntimeManager.instance;\n  }\n  /**\n   * Register a runtime factory\n   */\n  register(type, factory) {\n    runtimeFactories.set(type, factory);\n  }\n  /**\n   * Get a runtime instance\n   */\n  async getRuntime(type = \"auto\") {\n    if (type === \"auto\") {\n      return this.getBestRuntime();\n    }\n    let runtime = runtimeInstances.get(type);\n    if (runtime) {\n      return runtime;\n    }\n    const factory = runtimeFactories.get(type);\n    if (!factory) {\n      throw new EdgeFlowError(`Runtime '${type}' is not registered`, ErrorCodes.RUNTIME_NOT_AVAILABLE, { runtime: type });\n    }\n    runtime = factory();\n    const available = await runtime.isAvailable();\n    if (!available) {\n      throw new EdgeFlowError(`Runtime '${type}' is not available in this environment`, ErrorCodes.RUNTIME_NOT_AVAILABLE, { runtime: type });\n    }\n    try {\n      await runtime.initialize();\n    } catch (error) {\n      throw new EdgeFlowError(`Failed to initialize runtime '${type}': ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.RUNTIME_INIT_FAILED, { runtime: type, error });\n    }\n    runtimeInstances.set(type, runtime);\n    this.emit(\"runtime:ready\", { runtime: type });\n    return runtime;\n  }\n  /**\n   * Get the best available runtime\n   */\n  async getBestRuntime() {\n    for (const type of RUNTIME_PRIORITY) {\n      try {\n        const existing = runtimeInstances.get(type);\n        if (existing) {\n          return existing;\n        }\n        const factory = runtimeFactories.get(type);\n        if (!factory)\n          continue;\n        const runtime = factory();\n        const available = await runtime.isAvailable();\n        if (available) {\n          await runtime.initialize();\n          runtimeInstances.set(type, runtime);\n          this.emit(\"runtime:ready\", { runtime: type });\n          return runtime;\n        }\n      } catch {\n        continue;\n      }\n    }\n    throw new EdgeFlowError(\"No runtime available. Please ensure WebGPU, WebNN, or WASM is supported.\", ErrorCodes.RUNTIME_NOT_AVAILABLE, { triedRuntimes: RUNTIME_PRIORITY });\n  }\n  /**\n   * Check which runtimes are available\n   */\n  async detectAvailableRuntimes() {\n    const results = /* @__PURE__ */ new Map();\n    for (const type of RUNTIME_PRIORITY) {\n      const factory = runtimeFactories.get(type);\n      if (!factory) {\n        results.set(type, false);\n        continue;\n      }\n      try {\n        const runtime = factory();\n        results.set(type, await runtime.isAvailable());\n      } catch {\n        results.set(type, false);\n      }\n    }\n    return results;\n  }\n  /**\n   * Get capabilities of a runtime\n   */\n  async getCapabilities(type) {\n    const runtime = await this.getRuntime(type);\n    return runtime.capabilities;\n  }\n  /**\n   * Set default runtime\n   */\n  setDefaultRuntime(type) {\n    this.defaultRuntime = type;\n  }\n  /**\n   * Get default runtime type\n   */\n  getDefaultRuntimeType() {\n    return this.defaultRuntime;\n  }\n  /**\n   * Dispose a specific runtime\n   */\n  disposeRuntime(type) {\n    const runtime = runtimeInstances.get(type);\n    if (runtime) {\n      runtime.dispose();\n      runtimeInstances.delete(type);\n    }\n  }\n  /**\n   * Dispose all runtimes\n   */\n  disposeAll() {\n    for (const [type, runtime] of runtimeInstances) {\n      runtime.dispose();\n      runtimeInstances.delete(type);\n    }\n  }\n  /**\n   * Add event listener\n   */\n  on(event, listener) {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = /* @__PURE__ */ new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener);\n  }\n  /**\n   * Remove event listener\n   */\n  off(event, listener) {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener);\n    }\n  }\n  /**\n   * Emit event\n   */\n  emit(type, data) {\n    const event = {\n      type,\n      timestamp: Date.now(),\n      data\n    };\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error(\"Error in event listener:\", error);\n        }\n      }\n    }\n  }\n};\n__publicField(_RuntimeManager, \"instance\", null);\nvar RuntimeManager = _RuntimeManager;\nvar modelIdCounter = 0;\nfunction generateModelId() {\n  return `model_${++modelIdCounter}_${Date.now().toString(36)}`;\n}\nvar LoadedModelImpl = class {\n  constructor(metadata, runtime, dispose) {\n    __publicField(this, \"id\");\n    __publicField(this, \"metadata\");\n    __publicField(this, \"runtime\");\n    __publicField(this, \"_isLoaded\", true);\n    __publicField(this, \"_dispose\");\n    this.id = generateModelId();\n    this.metadata = metadata;\n    this.runtime = runtime;\n    this._dispose = dispose;\n  }\n  get isLoaded() {\n    return this._isLoaded;\n  }\n  dispose() {\n    if (this._isLoaded) {\n      this._isLoaded = false;\n      this._dispose();\n      getMemoryManager().untrack(this.id);\n    }\n  }\n};\nasync function loadModel(url, options = {}) {\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(options.runtime ?? \"auto\");\n  const { loadModelData: loadModelData2 } = await Promise.resolve().then(() => (init_model_loader(), model_loader_exports));\n  const modelData = await loadModelData2(url, {\n    cache: options.cache ?? true,\n    resumable: options.resumable ?? true,\n    chunkSize: options.chunkSize,\n    forceDownload: options.forceDownload,\n    onProgress: options.onProgress ? (progress) => {\n      options.onProgress(progress.percent / 100);\n    } : void 0\n  });\n  const model = await runtime.loadModel(modelData, options);\n  return model;\n}\nasync function loadModelFromBuffer(data, options = {}) {\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(options.runtime ?? \"auto\");\n  return runtime.loadModel(data, options);\n}\nasync function runInference(model, inputs) {\n  if (!model.isLoaded) {\n    throw new EdgeFlowError(\"Model has been disposed\", ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n  }\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n  const scheduler = getScheduler();\n  const task = scheduler.schedule(model.id, () => runtime.run(model, inputs));\n  return task.wait();\n}\nasync function runInferenceNamed(model, namedInputs) {\n  if (!model.isLoaded) {\n    throw new EdgeFlowError(\"Model has been disposed\", ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n  }\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n  if (!(\"runNamed\" in runtime)) {\n    throw new EdgeFlowError(\"Runtime does not support named inputs\", ErrorCodes.INFERENCE_FAILED, { modelId: model.id });\n  }\n  const scheduler = getScheduler();\n  const task = scheduler.schedule(model.id, () => runtime.runNamed(model, namedInputs));\n  return task.wait();\n}\nasync function runBatchInference(model, batches) {\n  const scheduler = getScheduler();\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n  const tasks = batches.map((inputs) => scheduler.schedule(model.id, () => runtime.run(model, inputs)));\n  return Promise.all(tasks.map((task) => task.wait()));\n}\nfunction getRuntimeManager() {\n  return RuntimeManager.getInstance();\n}\nfunction registerRuntime(type, factory) {\n  RuntimeManager.getInstance().register(type, factory);\n}\nasync function getBestRuntime() {\n  return RuntimeManager.getInstance().getBestRuntime();\n}\nasync function getAvailableRuntimes() {\n  return RuntimeManager.getInstance().detectAvailableRuntimes();\n}\n\n// dist/core/plugin.js\nvar registeredPlugins = /* @__PURE__ */ new Map();\nvar pluginPipelines = /* @__PURE__ */ new Map();\nvar pluginMiddleware = [];\nasync function registerPlugin(plugin) {\n  if (registeredPlugins.has(plugin.name)) {\n    console.warn(`[edgeFlow.js] Plugin \"${plugin.name}\" is already registered \\u2014 skipping.`);\n    return;\n  }\n  if (plugin.setup) {\n    await plugin.setup();\n  }\n  if (plugin.pipelines) {\n    for (const [task, entry] of Object.entries(plugin.pipelines)) {\n      pluginPipelines.set(task, entry);\n    }\n  }\n  if (plugin.backends) {\n    for (const [name, entry] of Object.entries(plugin.backends)) {\n      registerRuntime(name, entry.factory);\n    }\n  }\n  if (plugin.middleware) {\n    pluginMiddleware.push(...plugin.middleware);\n  }\n  registeredPlugins.set(plugin.name, plugin);\n}\nfunction getPluginPipeline(task) {\n  return pluginPipelines.get(task);\n}\nfunction getPluginMiddleware() {\n  return pluginMiddleware;\n}\nfunction listPlugins() {\n  return Array.from(registeredPlugins.values()).map((p) => ({\n    name: p.name,\n    version: p.version\n  }));\n}\nfunction unregisterPlugin(name) {\n  const plugin = registeredPlugins.get(name);\n  if (!plugin)\n    return false;\n  if (plugin.pipelines) {\n    for (const task of Object.keys(plugin.pipelines)) {\n      pluginPipelines.delete(task);\n    }\n  }\n  if (plugin.middleware) {\n    for (const mw of plugin.middleware) {\n      const idx = pluginMiddleware.indexOf(mw);\n      if (idx !== -1)\n        pluginMiddleware.splice(idx, 1);\n    }\n  }\n  registeredPlugins.delete(name);\n  return true;\n}\n\n// dist/core/device-profiler.js\nvar cachedProfile = null;\nasync function getDeviceProfile() {\n  if (cachedProfile)\n    return cachedProfile;\n  const cores = typeof navigator !== \"undefined\" ? navigator.hardwareConcurrency ?? 2 : 2;\n  const memoryGiB = typeof navigator !== \"undefined\" && \"deviceMemory\" in navigator ? navigator.deviceMemory ?? null : null;\n  const mobile = typeof navigator !== \"undefined\" ? /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent) : false;\n  let webgpu = false;\n  let gpuInfo;\n  if (typeof navigator !== \"undefined\" && \"gpu\" in navigator) {\n    try {\n      const adapter = await navigator.gpu.requestAdapter();\n      webgpu = adapter != null;\n      if (adapter && typeof adapter === \"object\") {\n        try {\n          const info = adapter[\"info\"];\n          if (info) {\n            gpuInfo = `${info[\"vendor\"] ?? \"\"} ${info[\"architecture\"] ?? \"\"}`.trim() || void 0;\n          }\n        } catch {\n        }\n      }\n    } catch {\n    }\n  }\n  let webnn = false;\n  if (typeof navigator !== \"undefined\" && \"ml\" in navigator) {\n    try {\n      const ml = navigator.ml;\n      if (ml) {\n        const ctx = await ml.createContext();\n        webnn = ctx != null;\n      }\n    } catch {\n    }\n  }\n  let tier;\n  if (webgpu && cores >= 8 && (memoryGiB === null || memoryGiB >= 8)) {\n    tier = \"high\";\n  } else if (cores >= 4 && (memoryGiB === null || memoryGiB >= 4)) {\n    tier = \"medium\";\n  } else {\n    tier = \"low\";\n  }\n  if (mobile && tier === \"high\") {\n    tier = \"medium\";\n  }\n  const recommendedBatchSize = tier === \"high\" ? 32 : tier === \"medium\" ? 8 : 1;\n  const recommendedConcurrency = tier === \"high\" ? 4 : tier === \"medium\" ? 2 : 1;\n  cachedProfile = {\n    tier,\n    cores,\n    memoryGiB,\n    webgpu,\n    webnn,\n    recommendedBatchSize,\n    recommendedConcurrency,\n    mobile,\n    gpuInfo\n  };\n  return cachedProfile;\n}\nfunction recommendQuantization(profile) {\n  if (profile.tier === \"high\" && profile.webgpu)\n    return \"float16\";\n  if (profile.tier === \"medium\")\n    return \"int8\";\n  return \"int8\";\n}\nasync function recommendModelVariant() {\n  const profile = await getDeviceProfile();\n  return {\n    quantization: recommendQuantization(profile),\n    executionProvider: profile.webgpu ? \"webgpu\" : \"wasm\",\n    batchSize: profile.recommendedBatchSize,\n    useWorker: profile.cores >= 4\n  };\n}\nfunction resetDeviceProfile() {\n  cachedProfile = null;\n}\n\n// dist/backends/webgpu.js\ninit_types();\ninit_tensor();\nvar GPUBufferUsage = {\n  STORAGE: 128,\n  COPY_SRC: 4,\n  COPY_DST: 8,\n  MAP_READ: 1\n};\nvar GPUShaderStage = {\n  COMPUTE: 4\n};\nvar WebGPURuntime = class {\n  constructor() {\n    __publicField(this, \"name\", \"webgpu\");\n    __publicField(this, \"adapter\", null);\n    __publicField(this, \"device\", null);\n    __publicField(this, \"models\", /* @__PURE__ */ new Map());\n    __publicField(this, \"initialized\", false);\n  }\n  get capabilities() {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: false,\n      maxBatchSize: 64,\n      availableMemory: this.device?.limits.maxBufferSize ?? 256 * 1024 * 1024\n    };\n  }\n  /**\n   * Check if WebGPU is available\n   */\n  async isAvailable() {\n    if (typeof navigator === \"undefined\")\n      return false;\n    if (!navigator.gpu)\n      return false;\n    try {\n      const adapter = await navigator.gpu.requestAdapter();\n      return adapter !== null;\n    } catch {\n      return false;\n    }\n  }\n  /**\n   * Initialize the WebGPU runtime\n   */\n  async initialize() {\n    if (this.initialized)\n      return;\n    if (!navigator.gpu) {\n      throw new EdgeFlowError(\"WebGPU is not supported in this browser\", ErrorCodes.RUNTIME_NOT_AVAILABLE);\n    }\n    this.adapter = await navigator.gpu.requestAdapter({\n      powerPreference: \"high-performance\"\n    });\n    if (!this.adapter) {\n      throw new EdgeFlowError(\"Failed to get WebGPU adapter\", ErrorCodes.RUNTIME_INIT_FAILED);\n    }\n    this.device = await this.adapter.requestDevice({\n      requiredFeatures: [],\n      requiredLimits: {}\n    });\n    this.device.lost.then((info) => {\n      console.error(\"WebGPU device was lost:\", info.message);\n      this.initialized = false;\n      this.device = null;\n    });\n    this.initialized = true;\n  }\n  /**\n   * Load a model\n   */\n  async loadModel(modelData, options = {}) {\n    this.ensureInitialized();\n    const config = this.parseModelData(modelData);\n    const webgpuData = {\n      shaders: /* @__PURE__ */ new Map(),\n      pipelines: /* @__PURE__ */ new Map(),\n      weights: /* @__PURE__ */ new Map(),\n      bindGroupLayouts: [],\n      config\n    };\n    await this.uploadWeights(modelData, webgpuData);\n    await this.createPipelines(webgpuData);\n    const modelId = `webgpu_${Date.now().toString(36)}`;\n    this.models.set(modelId, webgpuData);\n    const metadata = {\n      name: config.name || options.metadata?.name || \"unknown\",\n      version: config.version,\n      inputs: config.inputs.map((i) => ({\n        name: i.name,\n        dtype: i.dtype,\n        shape: i.shape\n      })),\n      outputs: config.outputs.map((o) => ({\n        name: o.name,\n        dtype: o.dtype,\n        shape: o.shape\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? \"float32\",\n      format: \"edgeflow\"\n    };\n    const model = new LoadedModelImpl(metadata, \"webgpu\", () => this.unloadModel(modelId));\n    getMemoryManager().trackModel(model, () => model.dispose());\n    return model;\n  }\n  /**\n   * Run inference\n   */\n  async run(model, inputs) {\n    this.ensureInitialized();\n    return this.executeModel(inputs, model.metadata);\n  }\n  /**\n   * Execute model (simplified implementation)\n   */\n  async executeModel(inputs, metadata) {\n    const device = this.device;\n    const outputs = [];\n    for (const outputSpec of metadata.outputs) {\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      const outputBuffer = device.createBuffer({\n        size: outputSize * 4,\n        // float32\n        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC\n      });\n      const stagingBuffer = device.createBuffer({\n        size: outputSize * 4,\n        usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST\n      });\n      const outputData = new Float32Array(outputSize);\n      if (inputs.length > 0 && inputs[0]) {\n        const inputData = inputs[0].toFloat32Array();\n        for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n          outputData[i] = inputData[i] ?? 0;\n        }\n      }\n      outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, \"float32\"));\n      outputBuffer.destroy();\n      stagingBuffer.destroy();\n    }\n    return outputs;\n  }\n  /**\n   * Parse model data\n   */\n  parseModelData(data) {\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n      if (text.trim().startsWith(\"{\")) {\n        let jsonEnd = text.indexOf(\"\\n---\\n\");\n        if (jsonEnd === -1)\n          jsonEnd = data.byteLength;\n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr);\n      }\n    } catch {\n    }\n    return {\n      name: \"unknown\",\n      version: \"1.0.0\",\n      layers: [],\n      inputs: [{ name: \"input\", shape: [-1, 768], dtype: \"float32\" }],\n      outputs: [{ name: \"output\", shape: [-1, 768], dtype: \"float32\" }]\n    };\n  }\n  /**\n   * Upload weights to GPU\n   */\n  async uploadWeights(_data, modelData) {\n    const device = this.device;\n    const weightsBuffer = device.createBuffer({\n      size: 1024,\n      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST\n    });\n    modelData.weights.set(\"default\", weightsBuffer);\n  }\n  /**\n   * Create compute pipelines\n   */\n  async createPipelines(modelData) {\n    const device = this.device;\n    const shaderCode = (\n      /* wgsl */\n      `\n      @group(0) @binding(0) var<storage, read> input: array<f32>;\n      @group(0) @binding(1) var<storage, read_write> output: array<f32>;\n      \n      @compute @workgroup_size(64)\n      fn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n        let idx = gid.x;\n        if (idx < arrayLength(&input)) {\n          output[idx] = input[idx];\n        }\n      }\n    `\n    );\n    const shaderModule = device.createShaderModule({\n      code: shaderCode\n    });\n    modelData.shaders.set(\"default\", shaderModule);\n    const bindGroupLayout = device.createBindGroupLayout({\n      entries: [\n        {\n          binding: 0,\n          visibility: GPUShaderStage.COMPUTE,\n          buffer: { type: \"read-only-storage\" }\n        },\n        {\n          binding: 1,\n          visibility: GPUShaderStage.COMPUTE,\n          buffer: { type: \"storage\" }\n        }\n      ]\n    });\n    modelData.bindGroupLayouts.push(bindGroupLayout);\n    const pipelineLayout = device.createPipelineLayout({\n      bindGroupLayouts: [bindGroupLayout]\n    });\n    const pipeline2 = device.createComputePipeline({\n      layout: pipelineLayout,\n      compute: {\n        module: shaderModule,\n        entryPoint: \"main\"\n      }\n    });\n    modelData.pipelines.set(\"default\", pipeline2);\n  }\n  /**\n   * Unload a model\n   */\n  unloadModel(modelId) {\n    const modelData = this.models.get(modelId);\n    if (modelData) {\n      for (const buffer of modelData.weights.values()) {\n        buffer.destroy();\n      }\n      this.models.delete(modelId);\n    }\n  }\n  /**\n   * Ensure runtime is initialized\n   */\n  ensureInitialized() {\n    if (!this.initialized || !this.device) {\n      throw new EdgeFlowError(\"WebGPU runtime is not initialized\", ErrorCodes.RUNTIME_NOT_INITIALIZED);\n    }\n  }\n  /**\n   * Dispose the runtime\n   */\n  dispose() {\n    for (const modelId of this.models.keys()) {\n      this.unloadModel(modelId);\n    }\n    if (this.device) {\n      this.device.destroy();\n      this.device = null;\n    }\n    this.adapter = null;\n    this.initialized = false;\n  }\n};\nfunction createWebGPURuntime() {\n  return new WebGPURuntime();\n}\n\n// dist/backends/webnn.js\ninit_types();\ninit_tensor();\nvar WebNNRuntime = class {\n  constructor() {\n    __publicField(this, \"name\", \"webnn\");\n    __publicField(this, \"context\", null);\n    __publicField(this, \"models\", /* @__PURE__ */ new Map());\n    __publicField(this, \"initialized\", false);\n    __publicField(this, \"deviceType\", \"default\");\n  }\n  get capabilities() {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: false,\n      maxBatchSize: 32,\n      availableMemory: 256 * 1024 * 1024\n      // Estimated\n    };\n  }\n  /**\n   * Check if WebNN is available\n   */\n  async isAvailable() {\n    if (typeof navigator === \"undefined\")\n      return false;\n    if (!navigator.ml)\n      return false;\n    try {\n      const context = await navigator.ml.createContext({ deviceType: \"default\" });\n      return context !== null;\n    } catch {\n      return false;\n    }\n  }\n  /**\n   * Initialize the WebNN runtime\n   */\n  async initialize() {\n    if (this.initialized)\n      return;\n    if (!navigator.ml) {\n      throw new EdgeFlowError(\"WebNN is not supported in this browser\", ErrorCodes.RUNTIME_NOT_AVAILABLE);\n    }\n    try {\n      this.context = await navigator.ml.createContext({\n        deviceType: \"gpu\",\n        powerPreference: \"high-performance\"\n      });\n      this.deviceType = \"gpu\";\n    } catch {\n      try {\n        this.context = await navigator.ml.createContext({ deviceType: \"cpu\" });\n        this.deviceType = \"cpu\";\n      } catch (error) {\n        throw new EdgeFlowError(`Failed to create WebNN context: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.RUNTIME_INIT_FAILED);\n      }\n    }\n    this.initialized = true;\n  }\n  /**\n   * Load a model\n   */\n  async loadModel(modelData, options = {}) {\n    this.ensureInitialized();\n    const config = this.parseModelConfig(modelData);\n    const modelId = `webnn_${Date.now().toString(36)}`;\n    const metadata = {\n      name: config.name || options.metadata?.name || \"unknown\",\n      version: config.version || \"1.0.0\",\n      inputs: config.inputs.map((i) => ({\n        name: i.name,\n        dtype: i.dtype,\n        shape: i.shape\n      })),\n      outputs: config.outputs.map((o) => ({\n        name: o.name,\n        dtype: o.dtype,\n        shape: o.shape\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? \"float32\",\n      format: \"edgeflow\"\n    };\n    const model = new LoadedModelImpl(metadata, \"webnn\", () => this.unloadModel(modelId));\n    getMemoryManager().trackModel(model, () => model.dispose());\n    return model;\n  }\n  /**\n   * Run inference\n   */\n  async run(model, inputs) {\n    this.ensureInitialized();\n    return this.executeModel(inputs, model.metadata);\n  }\n  /**\n   * Execute model (simplified implementation)\n   */\n  async executeModel(inputs, metadata) {\n    const outputs = [];\n    for (const outputSpec of metadata.outputs) {\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      const outputData = new Float32Array(outputSize);\n      if (inputs.length > 0 && inputs[0]) {\n        const inputData = inputs[0].toFloat32Array();\n        for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n          outputData[i] = inputData[i] ?? 0;\n        }\n      }\n      outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, \"float32\"));\n    }\n    return outputs;\n  }\n  /**\n   * Parse model configuration\n   */\n  parseModelConfig(data) {\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n      if (text.trim().startsWith(\"{\")) {\n        let jsonEnd = text.indexOf(\"\\n---\\n\");\n        if (jsonEnd === -1)\n          jsonEnd = data.byteLength;\n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr);\n      }\n    } catch {\n    }\n    return {\n      name: \"unknown\",\n      version: \"1.0.0\",\n      inputs: [{ name: \"input\", shape: [-1, 768], dtype: \"float32\" }],\n      outputs: [{ name: \"output\", shape: [-1, 768], dtype: \"float32\" }]\n    };\n  }\n  /**\n   * Unload a model\n   */\n  unloadModel(modelId) {\n    this.models.delete(modelId);\n  }\n  /**\n   * Ensure runtime is initialized\n   */\n  ensureInitialized() {\n    if (!this.initialized || !this.context) {\n      throw new EdgeFlowError(\"WebNN runtime is not initialized\", ErrorCodes.RUNTIME_NOT_INITIALIZED);\n    }\n  }\n  /**\n   * Get device type\n   */\n  getDeviceType() {\n    return this.deviceType;\n  }\n  /**\n   * Dispose the runtime\n   */\n  dispose() {\n    this.models.clear();\n    this.context = null;\n    this.initialized = false;\n  }\n};\nfunction createWebNNRuntime() {\n  return new WebNNRuntime();\n}\n\n// dist/backends/wasm.js\ninit_types();\ninit_tensor();\nvar WASMRuntime = class {\n  constructor() {\n    __publicField(this, \"name\", \"wasm\");\n    __publicField(this, \"module\", null);\n    __publicField(this, \"simdSupported\", false);\n    __publicField(this, \"models\", /* @__PURE__ */ new Map());\n    __publicField(this, \"initialized\", false);\n  }\n  get capabilities() {\n    return {\n      concurrency: false,\n      // WASM is single-threaded by default\n      quantization: true,\n      float16: false,\n      dynamicShapes: true,\n      maxBatchSize: 16,\n      availableMemory: 128 * 1024 * 1024\n      // 128MB default\n    };\n  }\n  /**\n   * Check if WASM is available\n   */\n  async isAvailable() {\n    if (typeof WebAssembly === \"undefined\")\n      return false;\n    try {\n      const bytes = new Uint8Array([\n        0,\n        97,\n        115,\n        109,\n        // Magic number\n        1,\n        0,\n        0,\n        0\n        // Version\n      ]);\n      await WebAssembly.instantiate(bytes);\n      return true;\n    } catch {\n      return false;\n    }\n  }\n  /**\n   * Initialize the WASM runtime\n   */\n  async initialize() {\n    if (this.initialized)\n      return;\n    this.simdSupported = await this.checkSIMDSupport();\n    const memory = new WebAssembly.Memory({\n      initial: 256,\n      // 16MB initial\n      maximum: 2048\n      // 128MB maximum\n    });\n    this.module = {\n      memory,\n      exports: this.createJSFallback(memory)\n    };\n    this.initialized = true;\n  }\n  /**\n   * Check SIMD support\n   */\n  async checkSIMDSupport() {\n    try {\n      const simdTest = new Uint8Array([\n        0,\n        97,\n        115,\n        109,\n        1,\n        0,\n        0,\n        0,\n        1,\n        5,\n        1,\n        96,\n        0,\n        1,\n        123,\n        3,\n        2,\n        1,\n        0,\n        10,\n        10,\n        1,\n        8,\n        0,\n        253,\n        12,\n        0,\n        0,\n        0,\n        0,\n        11\n      ]);\n      await WebAssembly.instantiate(simdTest);\n      return true;\n    } catch {\n      return false;\n    }\n  }\n  /**\n   * Create JavaScript fallback for WASM operations\n   */\n  createJSFallback(memory) {\n    let nextPtr = 0;\n    const allocations = /* @__PURE__ */ new Map();\n    return {\n      malloc: (size) => {\n        const ptr = nextPtr;\n        nextPtr += size;\n        allocations.set(ptr, size);\n        return ptr;\n      },\n      free: (ptr) => {\n        allocations.delete(ptr);\n      },\n      matmul_f32: (aPtr, aRows, aCols, bPtr, _bRows, bCols, outPtr) => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n        for (let i = 0; i < aRows; i++) {\n          for (let j = 0; j < bCols; j++) {\n            let sum2 = 0;\n            for (let k = 0; k < aCols; k++) {\n              sum2 += (view[aOffset + i * aCols + k] ?? 0) * (view[bOffset + k * bCols + j] ?? 0);\n            }\n            view[outOffset + i * bCols + j] = sum2;\n          }\n        }\n      },\n      add_f32: (aPtr, bPtr, outPtr, size) => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[aOffset + i] ?? 0) + (view[bOffset + i] ?? 0);\n        }\n      },\n      mul_f32: (aPtr, bPtr, outPtr, size) => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[aOffset + i] ?? 0) * (view[bOffset + i] ?? 0);\n        }\n      },\n      relu_f32: (inputPtr, outputPtr, size) => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = Math.max(0, view[inOffset + i] ?? 0);\n        }\n      },\n      sigmoid_f32: (inputPtr, outputPtr, size) => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = 1 / (1 + Math.exp(-(view[inOffset + i] ?? 0)));\n        }\n      },\n      softmax_f32: (inputPtr, outputPtr, size) => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n        let max = -Infinity;\n        for (let i = 0; i < size; i++) {\n          if ((view[inOffset + i] ?? 0) > max)\n            max = view[inOffset + i] ?? 0;\n        }\n        let sum2 = 0;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = Math.exp((view[inOffset + i] ?? 0) - max);\n          sum2 += view[outOffset + i] ?? 0;\n        }\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[outOffset + i] ?? 0) / sum2;\n        }\n      }\n    };\n  }\n  /**\n   * Load a model\n   */\n  async loadModel(modelData, options = {}) {\n    this.ensureInitialized();\n    const config = this.parseModelConfig(modelData);\n    const wasmData = {\n      weights: /* @__PURE__ */ new Map(),\n      config,\n      executionOrder: config.layers.map((l) => l.name)\n    };\n    await this.loadWeights(modelData, wasmData);\n    const modelId = `wasm_${Date.now().toString(36)}`;\n    this.models.set(modelId, wasmData);\n    const metadata = {\n      name: config.name || options.metadata?.name || \"unknown\",\n      version: config.version || \"1.0.0\",\n      inputs: config.inputs.map((i) => ({\n        name: i.name,\n        dtype: i.dtype,\n        shape: i.shape\n      })),\n      outputs: config.outputs.map((o) => ({\n        name: o.name,\n        dtype: o.dtype,\n        shape: o.shape\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? \"float32\",\n      format: \"edgeflow\"\n    };\n    const model = new LoadedModelImpl(metadata, \"wasm\", () => this.unloadModel(modelId));\n    getMemoryManager().trackModel(model, () => model.dispose());\n    return model;\n  }\n  /**\n   * Run inference\n   */\n  async run(model, inputs) {\n    this.ensureInitialized();\n    return this.executeModel(inputs, model.metadata);\n  }\n  /**\n   * Execute model\n   */\n  async executeModel(inputs, metadata) {\n    const outputs = [];\n    for (const outputSpec of metadata.outputs) {\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      let outputTensor;\n      if (inputs.length > 0 && inputs[0]) {\n        const inputTensor = inputs[0];\n        if (outputSpec.name.includes(\"logits\") || outputSpec.name.includes(\"class\")) {\n          outputTensor = softmax(inputTensor);\n        } else if (outputSpec.name.includes(\"relu\")) {\n          outputTensor = relu(inputTensor);\n        } else if (outputSpec.name.includes(\"sigmoid\")) {\n          outputTensor = sigmoid(inputTensor);\n        } else {\n          const outputData = new Float32Array(outputSize);\n          const inputData = inputTensor.toFloat32Array();\n          for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n            outputData[i] = inputData[i] ?? 0;\n          }\n          outputTensor = new EdgeFlowTensor(outputData, outputSpec.shape, \"float32\");\n        }\n      } else {\n        outputTensor = new EdgeFlowTensor(new Float32Array(outputSize), outputSpec.shape, \"float32\");\n      }\n      outputs.push(outputTensor);\n    }\n    return outputs;\n  }\n  /**\n   * Parse model configuration\n   */\n  parseModelConfig(data) {\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(2048, data.byteLength)));\n      if (text.trim().startsWith(\"{\")) {\n        let jsonEnd = text.indexOf(\"\\n---\\n\");\n        if (jsonEnd === -1) {\n          try {\n            return JSON.parse(text);\n          } catch {\n            jsonEnd = data.byteLength;\n          }\n        }\n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr);\n      }\n    } catch {\n    }\n    return {\n      name: \"unknown\",\n      version: \"1.0.0\",\n      layers: [],\n      inputs: [{ name: \"input\", shape: [-1, 768], dtype: \"float32\" }],\n      outputs: [{ name: \"output\", shape: [-1, 768], dtype: \"float32\" }]\n    };\n  }\n  /**\n   * Load weights into WASM memory\n   */\n  async loadWeights(_modelData, _wasmData) {\n  }\n  /**\n   * Unload a model\n   */\n  unloadModel(modelId) {\n    const modelData = this.models.get(modelId);\n    if (modelData && this.module) {\n      for (const weight of modelData.weights.values()) {\n        this.module.exports.free(weight.ptr);\n      }\n    }\n    this.models.delete(modelId);\n  }\n  /**\n   * Ensure runtime is initialized\n   */\n  ensureInitialized() {\n    if (!this.initialized || !this.module) {\n      throw new EdgeFlowError(\"WASM runtime is not initialized\", ErrorCodes.RUNTIME_NOT_INITIALIZED);\n    }\n  }\n  /**\n   * Check if SIMD is supported\n   */\n  hasSIMDSupport() {\n    return this.simdSupported;\n  }\n  /**\n   * Dispose the runtime\n   */\n  dispose() {\n    for (const modelId of this.models.keys()) {\n      this.unloadModel(modelId);\n    }\n    this.module = null;\n    this.initialized = false;\n  }\n};\nfunction createWASMRuntime() {\n  return new WASMRuntime();\n}\n\n// dist/backends/onnx.js\ninit_types();\ninit_tensor();\nvar ort = null;\nasync function getOrt() {\n  if (ort)\n    return ort;\n  try {\n    ort = await import(\"onnxruntime-web/wasm\");\n    return ort;\n  } catch {\n    return null;\n  }\n}\nasync function isOnnxAvailable() {\n  return await getOrt() != null;\n}\nvar sessionStore = /* @__PURE__ */ new Map();\nvar ONNXRuntime = class {\n  constructor() {\n    __publicField(this, \"name\", \"wasm\");\n    // Register as wasm since it's the fallback\n    __publicField(this, \"initialized\", false);\n    __publicField(this, \"executionProvider\", \"wasm\");\n  }\n  get capabilities() {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: this.executionProvider === \"webgpu\",\n      dynamicShapes: true,\n      maxBatchSize: 32,\n      availableMemory: 512 * 1024 * 1024\n      // 512MB\n    };\n  }\n  /**\n   * Check if ONNX Runtime is available (peer dependency installed)\n   */\n  async isAvailable() {\n    return isOnnxAvailable();\n  }\n  /**\n   * Initialize the ONNX runtime\n   */\n  async initialize() {\n    if (this.initialized)\n      return;\n    const ortModule = await getOrt();\n    if (!ortModule) {\n      throw new EdgeFlowError(\"onnxruntime-web is not installed. Install it with: npm install onnxruntime-web\", ErrorCodes.RUNTIME_NOT_AVAILABLE);\n    }\n    if (typeof window !== \"undefined\" && ortModule.env?.wasm) {\n      ortModule.env.wasm.wasmPaths = \"/ort/\";\n      ortModule.env.wasm.numThreads = 1;\n    }\n    this.initialized = true;\n  }\n  /**\n   * Load a model from ArrayBuffer\n   */\n  async loadModel(modelData, options = {}) {\n    if (!this.initialized) {\n      await this.initialize();\n    }\n    try {\n      const ortModule = await getOrt();\n      if (!ortModule) {\n        throw new Error(\"onnxruntime-web is not installed\");\n      }\n      const sessionOptions = {\n        executionProviders: [\"wasm\"],\n        graphOptimizationLevel: \"all\"\n      };\n      const modelBytes = new Uint8Array(modelData);\n      const session = await ortModule.InferenceSession.create(modelBytes, sessionOptions);\n      const inputNames = session.inputNames;\n      const outputNames = session.outputNames;\n      const modelId = `onnx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n      sessionStore.set(modelId, {\n        session,\n        inputNames: [...inputNames],\n        outputNames: [...outputNames]\n      });\n      const metadata = {\n        name: options.metadata?.name ?? \"onnx-model\",\n        version: \"1.0.0\",\n        inputs: inputNames.map((name) => ({\n          name,\n          dtype: \"float32\",\n          shape: [-1]\n          // Dynamic shape\n        })),\n        outputs: outputNames.map((name) => ({\n          name,\n          dtype: \"float32\",\n          shape: [-1]\n        })),\n        sizeBytes: modelData.byteLength,\n        quantization: options.quantization ?? \"float32\",\n        format: \"onnx\"\n      };\n      const model = new LoadedModelImpl(metadata, \"wasm\", () => this.unloadModel(modelId));\n      Object.defineProperty(model, \"id\", { value: modelId, writable: false });\n      getMemoryManager().trackModel(model, () => model.dispose());\n      return model;\n    } catch (error) {\n      throw new EdgeFlowError(`Failed to load ONNX model: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.MODEL_LOAD_FAILED, { error });\n    }\n  }\n  /**\n   * Run inference\n   */\n  async run(model, inputs) {\n    const sessionData = sessionStore.get(model.id);\n    if (!sessionData) {\n      throw new EdgeFlowError(`ONNX session not found for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n    }\n    const { session, inputNames, outputNames } = sessionData;\n    try {\n      const ortModule = await getOrt();\n      const feeds = {};\n      for (let i = 0; i < Math.min(inputs.length, inputNames.length); i++) {\n        const inputName = inputNames[i];\n        const inputTensor = inputs[i];\n        if (inputName && inputTensor) {\n          const dtype = inputTensor.dtype;\n          let ortTensor;\n          if (dtype === \"int64\") {\n            const data = inputTensor.data;\n            ortTensor = new ortModule.Tensor(\"int64\", data, inputTensor.shape);\n          } else if (dtype === \"int32\") {\n            const data = inputTensor.data;\n            ortTensor = new ortModule.Tensor(\"int32\", data, inputTensor.shape);\n          } else {\n            const data = inputTensor.toFloat32Array();\n            ortTensor = new ortModule.Tensor(\"float32\", data, inputTensor.shape);\n          }\n          feeds[inputName] = ortTensor;\n        }\n      }\n      const results = await session.run(feeds);\n      const outputs = [];\n      for (const outputName of outputNames) {\n        const ortTensor = results[outputName];\n        if (ortTensor) {\n          const data = ortTensor.data;\n          const shape = Array.from(ortTensor.dims).map((d) => Number(d));\n          outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, \"float32\"));\n        }\n      }\n      return outputs;\n    } catch (error) {\n      throw new EdgeFlowError(`ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.INFERENCE_FAILED, { modelId: model.id, error });\n    }\n  }\n  /**\n   * Run inference with named inputs\n   */\n  async runNamed(model, namedInputs) {\n    const sessionData = sessionStore.get(model.id);\n    if (!sessionData) {\n      throw new EdgeFlowError(`ONNX session not found for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED, { modelId: model.id });\n    }\n    const { session, inputNames, outputNames } = sessionData;\n    try {\n      const ortModule = await getOrt();\n      const feeds = {};\n      for (const [inputName, inputTensor] of namedInputs) {\n        const tensor2 = inputTensor;\n        const dtype = tensor2.dtype;\n        let ortTensor;\n        if (dtype === \"int64\") {\n          const data = tensor2.data;\n          ortTensor = new ortModule.Tensor(\"int64\", data, tensor2.shape);\n        } else if (dtype === \"int32\") {\n          const data = tensor2.data;\n          ortTensor = new ortModule.Tensor(\"int32\", data, tensor2.shape);\n        } else {\n          const data = tensor2.toFloat32Array();\n          ortTensor = new ortModule.Tensor(\"float32\", data, tensor2.shape);\n        }\n        feeds[inputName] = ortTensor;\n      }\n      const results = await session.run(feeds);\n      const outputs = [];\n      for (const outputName of outputNames) {\n        const ortTensor = results[outputName];\n        if (ortTensor) {\n          const data = ortTensor.data;\n          const shape = Array.from(ortTensor.dims).map((d) => Number(d));\n          outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, \"float32\"));\n        }\n      }\n      return outputs;\n    } catch (error) {\n      throw new EdgeFlowError(`ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`, ErrorCodes.INFERENCE_FAILED, { modelId: model.id, expectedInputs: inputNames, providedInputs: Array.from(namedInputs.keys()), error });\n    }\n  }\n  /**\n   * Unload a model\n   */\n  async unloadModel(modelId) {\n    const sessionData = sessionStore.get(modelId);\n    if (sessionData) {\n      sessionStore.delete(modelId);\n    }\n  }\n  /**\n   * Dispose the runtime\n   */\n  dispose() {\n    sessionStore.clear();\n    this.initialized = false;\n  }\n};\nfunction createONNXRuntime() {\n  return new ONNXRuntime();\n}\n\n// dist/backends/transformers-adapter.js\ninit_types();\ninit_tensor();\nvar sessionStore2 = /* @__PURE__ */ new Map();\nvar adapterOptions = null;\nvar TransformersAdapterRuntime = class {\n  constructor() {\n    __publicField(this, \"name\", \"wasm\");\n  }\n  // registers under the wasm slot\n  get capabilities() {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: true,\n      maxBatchSize: 128,\n      availableMemory: 1024 * 1024 * 1024\n    };\n  }\n  async isAvailable() {\n    return adapterOptions?.pipelineFactory != null;\n  }\n  async initialize() {\n    if (!adapterOptions?.pipelineFactory) {\n      throw new EdgeFlowError(\"TransformersAdapterRuntime requires a pipelineFactory. Call useTransformersBackend({ pipelineFactory }) first.\", ErrorCodes.RUNTIME_INIT_FAILED);\n    }\n  }\n  async loadModel(modelData, options = {}) {\n    const modelName = options.metadata?.name ?? \"default\";\n    const metadata = {\n      name: modelName,\n      version: \"1.0.0\",\n      inputs: [{ name: \"input\", dtype: \"float32\", shape: [-1] }],\n      outputs: [{ name: \"output\", dtype: \"float32\", shape: [-1] }],\n      sizeBytes: modelData.byteLength || 0,\n      quantization: options.quantization ?? \"float32\",\n      format: \"onnx\"\n    };\n    const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n    const model = new LoadedModelImpl(metadata, this.name, () => {\n      const session = sessionStore2.get(modelId);\n      if (session?.instance.dispose) {\n        session.instance.dispose();\n      }\n      sessionStore2.delete(modelId);\n    });\n    getMemoryManager().trackModel(model, () => model.dispose());\n    return model;\n  }\n  /**\n   * Load a transformers.js pipeline by task + model name\n   * (called by the higher-level adapter pipeline, not via the\n   * standard loadModel path).\n   */\n  async loadPipeline(task, model, pipelineOptions) {\n    if (!adapterOptions?.pipelineFactory) {\n      throw new EdgeFlowError(\"Adapter not initialised\", ErrorCodes.RUNTIME_NOT_INITIALIZED);\n    }\n    const opts = { ...pipelineOptions };\n    if (adapterOptions.device)\n      opts[\"device\"] = adapterOptions.device;\n    if (adapterOptions.dtype)\n      opts[\"dtype\"] = adapterOptions.dtype;\n    const instance = await adapterOptions.pipelineFactory(task, model, opts);\n    const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n    sessionStore2.set(modelId, { instance, task, model });\n    return modelId;\n  }\n  /**\n   * Run inference by passing the raw input to the transformers.js pipeline.\n   * The result is returned as a single EdgeFlowTensor wrapping the JSON-encoded output\n   * (since transformers.js returns task-specific objects, not raw tensors).\n   */\n  async run(model, inputs) {\n    const session = sessionStore2.get(model.id);\n    if (!session) {\n      throw new EdgeFlowError(`No transformers.js session for model ${model.id}`, ErrorCodes.MODEL_NOT_LOADED);\n    }\n    const inputData = inputs[0]?.toFloat32Array() ?? new Float32Array(0);\n    const result = await session.instance(inputData);\n    const resultArray = Array.isArray(result) ? new Float32Array(result.flat(Infinity)) : new Float32Array([0]);\n    return [new EdgeFlowTensor(resultArray, [resultArray.length], \"float32\")];\n  }\n  /**\n   * High-level: run the transformers.js pipeline directly with arbitrary input.\n   * Returns the raw result object (not a tensor).\n   */\n  async runDirect(modelId, input, options) {\n    const session = sessionStore2.get(modelId);\n    if (!session) {\n      throw new EdgeFlowError(`No transformers.js session for model ${modelId}`, ErrorCodes.MODEL_NOT_LOADED);\n    }\n    return session.instance(input, options);\n  }\n  dispose() {\n    for (const [id, session] of sessionStore2) {\n      if (session.instance.dispose) {\n        session.instance.dispose();\n      }\n      sessionStore2.delete(id);\n    }\n  }\n};\nvar adapterRuntime = null;\nfunction useTransformersBackend(options) {\n  adapterOptions = options;\n  adapterRuntime = new TransformersAdapterRuntime();\n  registerRuntime(\"wasm\", () => adapterRuntime);\n}\nfunction getTransformersAdapter() {\n  return adapterRuntime;\n}\n\n// dist/backends/index.js\nfunction registerAllBackends() {\n  registerRuntime(\"wasm\", createONNXRuntime);\n}\nregisterAllBackends();\n\n// dist/utils/cache.js\nvar Cache = class {\n  constructor(options = {}) {\n    __publicField(this, \"options\");\n    __publicField(this, \"cache\", /* @__PURE__ */ new Map());\n    __publicField(this, \"currentSize\", 0);\n    __publicField(this, \"hits\", 0);\n    __publicField(this, \"misses\", 0);\n    this.options = {\n      strategy: options.strategy ?? \"lru\",\n      maxSize: options.maxSize ?? 100 * 1024 * 1024,\n      // 100MB\n      maxEntries: options.maxEntries ?? 1e3,\n      ttl: options.ttl ?? 0,\n      // 0 = no TTL\n      persistent: options.persistent ?? false,\n      name: options.name ?? \"edgeflow-cache\"\n    };\n    if (this.options.persistent) {\n      this.loadFromStorage();\n    }\n  }\n  /**\n   * Get value from cache\n   */\n  get(key) {\n    const entry = this.cache.get(key);\n    if (!entry) {\n      this.misses++;\n      return void 0;\n    }\n    if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n      this.delete(key);\n      this.misses++;\n      return void 0;\n    }\n    entry.accessedAt = Date.now();\n    entry.accessCount++;\n    this.hits++;\n    return entry.value;\n  }\n  /**\n   * Set value in cache\n   */\n  set(key, value, size, ttl) {\n    if (this.cache.has(key)) {\n      this.delete(key);\n    }\n    while ((this.currentSize + size > this.options.maxSize || this.cache.size >= this.options.maxEntries) && this.cache.size > 0) {\n      this.evict();\n    }\n    const entryTtl = ttl !== void 0 ? ttl : this.options.ttl > 0 ? this.options.ttl : void 0;\n    const entry = {\n      value,\n      size,\n      createdAt: Date.now(),\n      accessedAt: Date.now(),\n      accessCount: 1,\n      ttl: entryTtl\n    };\n    this.cache.set(key, entry);\n    this.currentSize += size;\n    if (this.options.persistent) {\n      this.saveToStorage();\n    }\n  }\n  /**\n   * Check if key exists\n   */\n  has(key) {\n    const entry = this.cache.get(key);\n    if (!entry)\n      return false;\n    if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n      this.delete(key);\n      return false;\n    }\n    return true;\n  }\n  /**\n   * Delete entry\n   */\n  delete(key) {\n    const entry = this.cache.get(key);\n    if (entry) {\n      this.currentSize -= entry.size;\n      this.cache.delete(key);\n      if (this.options.persistent) {\n        this.saveToStorage();\n      }\n      return true;\n    }\n    return false;\n  }\n  /**\n   * Clear the cache\n   */\n  clear() {\n    this.cache.clear();\n    this.currentSize = 0;\n    this.hits = 0;\n    this.misses = 0;\n    if (this.options.persistent) {\n      this.clearStorage();\n    }\n  }\n  /**\n   * Get cache statistics\n   */\n  getStats() {\n    const total = this.hits + this.misses;\n    return {\n      entries: this.cache.size,\n      size: this.currentSize,\n      hits: this.hits,\n      misses: this.misses,\n      hitRate: total > 0 ? this.hits / total : 0\n    };\n  }\n  /**\n   * Evict an entry based on strategy\n   */\n  evict() {\n    let keyToEvict = null;\n    switch (this.options.strategy) {\n      case \"lru\":\n        keyToEvict = this.findLRU();\n        break;\n      case \"lfu\":\n        keyToEvict = this.findLFU();\n        break;\n      case \"fifo\":\n        keyToEvict = this.findOldest();\n        break;\n      case \"ttl\":\n        keyToEvict = this.findExpired() ?? this.findOldest();\n        break;\n    }\n    if (keyToEvict) {\n      this.delete(keyToEvict);\n    }\n  }\n  /**\n   * Find least recently used entry\n   */\n  findLRU() {\n    let oldest = null;\n    let oldestTime = Infinity;\n    for (const [key, entry] of this.cache) {\n      if (entry.accessedAt < oldestTime) {\n        oldestTime = entry.accessedAt;\n        oldest = key;\n      }\n    }\n    return oldest;\n  }\n  /**\n   * Find least frequently used entry\n   */\n  findLFU() {\n    let lfu = null;\n    let minCount = Infinity;\n    for (const [key, entry] of this.cache) {\n      if (entry.accessCount < minCount) {\n        minCount = entry.accessCount;\n        lfu = key;\n      }\n    }\n    return lfu;\n  }\n  /**\n   * Find oldest entry (FIFO)\n   */\n  findOldest() {\n    let oldest = null;\n    let oldestTime = Infinity;\n    for (const [key, entry] of this.cache) {\n      if (entry.createdAt < oldestTime) {\n        oldestTime = entry.createdAt;\n        oldest = key;\n      }\n    }\n    return oldest;\n  }\n  /**\n   * Find expired entry\n   */\n  findExpired() {\n    const now = Date.now();\n    for (const [key, entry] of this.cache) {\n      if (entry.ttl && now - entry.createdAt > entry.ttl) {\n        return key;\n      }\n    }\n    return null;\n  }\n  /**\n   * Load cache from IndexedDB\n   */\n  async loadFromStorage() {\n    if (typeof indexedDB === \"undefined\")\n      return;\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction(\"cache\", \"readonly\");\n      const store = tx.objectStore(\"cache\");\n      const request = store.getAll();\n      return new Promise((resolve, reject) => {\n        request.onsuccess = () => {\n          const entries = request.result;\n          for (const { key, entry } of entries) {\n            this.cache.set(key, entry);\n            this.currentSize += entry.size;\n          }\n          resolve();\n        };\n        request.onerror = () => reject(request.error);\n      });\n    } catch {\n    }\n  }\n  /**\n   * Save cache to IndexedDB\n   */\n  async saveToStorage() {\n    if (typeof indexedDB === \"undefined\")\n      return;\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction(\"cache\", \"readwrite\");\n      const store = tx.objectStore(\"cache\");\n      store.clear();\n      for (const [key, entry] of this.cache) {\n        store.put({ key, entry });\n      }\n      return new Promise((resolve, reject) => {\n        tx.oncomplete = () => resolve();\n        tx.onerror = () => reject(tx.error);\n      });\n    } catch {\n    }\n  }\n  /**\n   * Clear IndexedDB storage\n   */\n  async clearStorage() {\n    if (typeof indexedDB === \"undefined\")\n      return;\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction(\"cache\", \"readwrite\");\n      const store = tx.objectStore(\"cache\");\n      store.clear();\n    } catch {\n    }\n  }\n  /**\n   * Open IndexedDB database\n   */\n  openDB() {\n    return new Promise((resolve, reject) => {\n      const request = indexedDB.open(this.options.name, 1);\n      request.onupgradeneeded = () => {\n        const db = request.result;\n        if (!db.objectStoreNames.contains(\"cache\")) {\n          db.createObjectStore(\"cache\", { keyPath: \"key\" });\n        }\n      };\n      request.onsuccess = () => resolve(request.result);\n      request.onerror = () => reject(request.error);\n    });\n  }\n};\nvar InferenceCache = class extends Cache {\n  /**\n   * Generate cache key from input\n   */\n  generateKey(modelId, input) {\n    const inputArray = Array.isArray(input) ? input : Array.from(input);\n    const hash = this.hashArray(inputArray);\n    return `${modelId}:${hash}`;\n  }\n  /**\n   * Simple hash function for arrays\n   */\n  hashArray(arr) {\n    let hash = 0;\n    const sample = arr.length > 100 ? arr.filter((_, i) => i % Math.floor(arr.length / 100) === 0) : arr;\n    for (let i = 0; i < sample.length; i++) {\n      const value = sample[i] ?? 0;\n      hash = (hash << 5) - hash + (value * 1e3 | 0);\n      hash |= 0;\n    }\n    return hash.toString(36);\n  }\n};\nvar ModelDownloadCache = class {\n  constructor(cacheName = \"edgeflow-models\") {\n    __publicField(this, \"cacheName\");\n    __publicField(this, \"cache\", null);\n    this.cacheName = cacheName;\n  }\n  /**\n   * Initialize cache\n   */\n  async ensureCache() {\n    if (!this.cache) {\n      if (typeof caches === \"undefined\") {\n        throw new Error(\"Cache API is not available\");\n      }\n      this.cache = await caches.open(this.cacheName);\n    }\n    return this.cache;\n  }\n  /**\n   * Get cached response\n   */\n  async get(url) {\n    try {\n      const cache = await this.ensureCache();\n      return await cache.match(url) ?? void 0;\n    } catch {\n      return void 0;\n    }\n  }\n  /**\n   * Store response in cache\n   */\n  async put(url, response) {\n    try {\n      const cache = await this.ensureCache();\n      await cache.put(url, response.clone());\n    } catch {\n    }\n  }\n  /**\n   * Delete cached response\n   */\n  async delete(url) {\n    try {\n      const cache = await this.ensureCache();\n      return await cache.delete(url);\n    } catch {\n      return false;\n    }\n  }\n  /**\n   * Clear all cached models\n   */\n  async clear() {\n    try {\n      await caches.delete(this.cacheName);\n      this.cache = null;\n    } catch {\n    }\n  }\n  /**\n   * Get all cached URLs\n   */\n  async keys() {\n    try {\n      const cache = await this.ensureCache();\n      const requests = await cache.keys();\n      return requests.map((r) => r.url);\n    } catch {\n      return [];\n    }\n  }\n};\nfunction createCache(preset = \"medium\", options = {}) {\n  const presets = {\n    small: {\n      maxSize: 10 * 1024 * 1024,\n      // 10MB\n      maxEntries: 100\n    },\n    medium: {\n      maxSize: 100 * 1024 * 1024,\n      // 100MB\n      maxEntries: 500\n    },\n    large: {\n      maxSize: 500 * 1024 * 1024,\n      // 500MB\n      maxEntries: 2e3\n    },\n    custom: {}\n  };\n  return new Cache({ ...presets[preset], ...options });\n}\n\n// dist/pipelines/base.js\nvar BasePipeline = class {\n  constructor(config) {\n    __publicField(this, \"model\", null);\n    __publicField(this, \"config\");\n    __publicField(this, \"modelCache\");\n    __publicField(this, \"downloadCache\");\n    __publicField(this, \"isReady\", false);\n    this.config = config;\n    this.modelCache = new ModelCache();\n    this.downloadCache = new ModelDownloadCache();\n  }\n  /**\n   * Initialize the pipeline (load model).\n   *\n   * Skips model loading when `config.model === 'default'` — concrete\n   * subclasses that define their own DEFAULT_MODELS handle all model\n   * loading in their overridden `initialize()` methods, so the base\n   * should not attempt to fetch a URL called \"default\".\n   */\n  async initialize() {\n    if (this.isReady && this.model)\n      return;\n    if (this.config.model === \"default\") {\n      this.isReady = true;\n      return;\n    }\n    const cachedModel = this.modelCache.get(this.config.model);\n    if (cachedModel) {\n      this.model = cachedModel;\n      this.isReady = true;\n      return;\n    }\n    this.model = await this.loadModelWithCache(this.config.model);\n    this.isReady = true;\n  }\n  /**\n   * Load model with caching\n   */\n  async loadModelWithCache(modelPath) {\n    const cachedResponse = await this.downloadCache.get(modelPath);\n    if (cachedResponse) {\n    }\n    try {\n      const response = await fetch(modelPath);\n      if (response.ok) {\n        await this.downloadCache.put(modelPath, response.clone());\n      }\n    } catch {\n    }\n    return loadModel(modelPath, {\n      runtime: this.config.runtime,\n      quantization: this.config.quantization,\n      cache: this.config.cache\n    });\n  }\n  /**\n   * Run inference (single input)\n   */\n  async run(input, options) {\n    await this.initialize();\n    const startTime = performance.now();\n    const preprocessed = await this.preprocess(input);\n    const outputs = await runInference(this.model, preprocessed);\n    const result = await this.postprocess(outputs, options);\n    if (result && typeof result === \"object\" && \"processingTime\" in result) {\n      result.processingTime = performance.now() - startTime;\n    }\n    return result;\n  }\n  /**\n   * Run batch inference\n   */\n  async runBatch(inputs, options) {\n    await this.initialize();\n    const results = await Promise.all(inputs.map((input) => this.run(input, options)));\n    return results;\n  }\n  /**\n   * Get the task type\n   */\n  get task() {\n    return this.config.task;\n  }\n  /**\n   * Check if pipeline is ready\n   */\n  get ready() {\n    return this.isReady;\n  }\n  /**\n   * Dispose the pipeline\n   */\n  dispose() {\n    if (this.model) {\n      this.model.dispose();\n      this.model = null;\n    }\n    this.isReady = false;\n  }\n};\nvar pipelineFactories = /* @__PURE__ */ new Map();\nfunction registerPipeline(task, factory) {\n  pipelineFactories.set(task, factory);\n}\nfunction getPipelineFactory(task) {\n  return pipelineFactories.get(task);\n}\nvar SENTIMENT_LABELS = [\"negative\", \"positive\"];\nvar EMOTION_LABELS = [\n  \"anger\",\n  \"disgust\",\n  \"fear\",\n  \"joy\",\n  \"sadness\",\n  \"surprise\",\n  \"neutral\"\n];\nvar IMAGENET_LABELS = [\n  \"tench\",\n  \"goldfish\",\n  \"great white shark\",\n  \"tiger shark\",\n  \"hammerhead\",\n  \"electric ray\",\n  \"stingray\",\n  \"cock\",\n  \"hen\",\n  \"ostrich\"\n];\n\n// dist/pipelines/text-classification.js\ninit_tensor();\n\n// dist/utils/tokenizer.js\ninit_types();\nvar Tokenizer = class _Tokenizer {\n  constructor() {\n    __publicField(this, \"vocab\", /* @__PURE__ */ new Map());\n    __publicField(this, \"reverseVocab\", /* @__PURE__ */ new Map());\n    __publicField(this, \"merges\", /* @__PURE__ */ new Map());\n    __publicField(this, \"addedTokens\", /* @__PURE__ */ new Map());\n    __publicField(this, \"specialTokens\", /* @__PURE__ */ new Set());\n    __publicField(this, \"modelType\", \"BPE\");\n    __publicField(this, \"unkToken\", \"[UNK]\");\n    __publicField(this, \"continuingSubwordPrefix\", \"##\");\n    // Special token IDs\n    __publicField(this, \"padTokenId\", 0);\n    __publicField(this, \"unkTokenId\", 0);\n    __publicField(this, \"clsTokenId\");\n    __publicField(this, \"sepTokenId\");\n    __publicField(this, \"maskTokenId\");\n    __publicField(this, \"bosTokenId\");\n    __publicField(this, \"eosTokenId\");\n    // Config\n    __publicField(this, \"maxLength\", 512);\n    __publicField(this, \"doLowerCase\", false);\n    __publicField(this, \"stripAccents\", false);\n    // Post-processor config\n    __publicField(this, \"postProcessor\");\n    // Byte encoder for BPE\n    __publicField(this, \"byteEncoder\", /* @__PURE__ */ new Map());\n    __publicField(this, \"byteDecoder\", /* @__PURE__ */ new Map());\n    this.initByteEncoder();\n  }\n  /**\n   * Initialize byte encoder/decoder for BPE\n   */\n  initByteEncoder() {\n    const bytes = [];\n    for (let i = 33; i <= 126; i++)\n      bytes.push(i);\n    for (let i = 161; i <= 172; i++)\n      bytes.push(i);\n    for (let i = 174; i <= 255; i++)\n      bytes.push(i);\n    const chars = [...bytes];\n    let n = 0;\n    for (let i = 0; i < 256; i++) {\n      if (!bytes.includes(i)) {\n        bytes.push(i);\n        chars.push(256 + n);\n        n++;\n      }\n    }\n    for (let i = 0; i < bytes.length; i++) {\n      const byte = bytes[i];\n      const char = String.fromCharCode(chars[i]);\n      this.byteEncoder.set(byte, char);\n      this.byteDecoder.set(char, byte);\n    }\n  }\n  /**\n   * Load from HuggingFace tokenizer.json\n   */\n  static async fromJSON(json) {\n    const tokenizer = new _Tokenizer();\n    const data = typeof json === \"string\" ? JSON.parse(json) : json;\n    if (data.model) {\n      tokenizer.modelType = data.model.type;\n      if (data.model.vocab) {\n        if (Array.isArray(data.model.vocab)) {\n          const unigramVocab = data.model.vocab;\n          for (let i = 0; i < unigramVocab.length; i++) {\n            const entry = unigramVocab[i];\n            const token = Array.isArray(entry) ? entry[0] : entry;\n            tokenizer.vocab.set(token, i);\n            tokenizer.reverseVocab.set(i, token);\n          }\n        } else {\n          for (const [token, id] of Object.entries(data.model.vocab)) {\n            tokenizer.vocab.set(token, id);\n            tokenizer.reverseVocab.set(id, token);\n          }\n        }\n      }\n      if (data.model.merges) {\n        for (let i = 0; i < data.model.merges.length; i++) {\n          tokenizer.merges.set(data.model.merges[i], i);\n        }\n      }\n      tokenizer.unkToken = data.model.unk_token ?? \"[UNK]\";\n      tokenizer.continuingSubwordPrefix = data.model.continuing_subword_prefix ?? \"##\";\n    }\n    if (data.added_tokens) {\n      for (const token of data.added_tokens) {\n        tokenizer.addedTokens.set(token.content, token.id);\n        tokenizer.reverseVocab.set(token.id, token.content);\n        if (token.special) {\n          tokenizer.specialTokens.add(token.content);\n        }\n        const content = token.content.toLowerCase();\n        if (content.includes(\"pad\"))\n          tokenizer.padTokenId = token.id;\n        if (content.includes(\"unk\"))\n          tokenizer.unkTokenId = token.id;\n        if (content.includes(\"cls\") || content === \"[cls]\")\n          tokenizer.clsTokenId = token.id;\n        if (content.includes(\"sep\") || content === \"[sep]\")\n          tokenizer.sepTokenId = token.id;\n        if (content.includes(\"mask\"))\n          tokenizer.maskTokenId = token.id;\n        if (content.includes(\"bos\") || content === \"<s>\")\n          tokenizer.bosTokenId = token.id;\n        if (content.includes(\"eos\") || content === \"</s>\")\n          tokenizer.eosTokenId = token.id;\n      }\n    }\n    if (data.normalizer) {\n      tokenizer.doLowerCase = data.normalizer.lowercase ?? false;\n      tokenizer.stripAccents = data.normalizer.strip_accents ?? false;\n    }\n    if (data.truncation) {\n      tokenizer.maxLength = data.truncation.max_length;\n    }\n    if (data.post_processor) {\n      tokenizer.postProcessor = data.post_processor;\n    }\n    return tokenizer;\n  }\n  /**\n   * Load from URL (tokenizer.json)\n   */\n  static async fromUrl(url) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new EdgeFlowError(`Failed to load tokenizer from ${url}: ${response.status}`, ErrorCodes.MODEL_NOT_FOUND);\n    }\n    const json = await response.json();\n    return _Tokenizer.fromJSON(json);\n  }\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromHuggingFace(modelId, options) {\n    const revision = options?.revision ?? \"main\";\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/tokenizer.json`;\n    return _Tokenizer.fromUrl(url);\n  }\n  /**\n   * Normalize text\n   */\n  normalize(text) {\n    let result = text;\n    if (this.doLowerCase) {\n      result = result.toLowerCase();\n    }\n    if (this.stripAccents) {\n      result = result.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\n    }\n    result = result.replace(/\\s+/g, \" \").trim();\n    return result;\n  }\n  /**\n   * Pre-tokenize text (split into words)\n   */\n  preTokenize(text) {\n    const pattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+/gu;\n    const matches = text.match(pattern);\n    return matches ?? [text];\n  }\n  /**\n   * Encode text to bytes (for BPE)\n   */\n  textToBytes(text) {\n    const encoder = new TextEncoder();\n    const bytes = encoder.encode(text);\n    return Array.from(bytes).map((b) => this.byteEncoder.get(b) ?? \"\").join(\"\");\n  }\n  /**\n   * Decode bytes to text (for BPE)\n   */\n  bytesToText(text) {\n    const bytes = new Uint8Array(text.split(\"\").map((c) => this.byteDecoder.get(c) ?? 0));\n    const decoder = new TextDecoder(\"utf-8\", { fatal: false });\n    return decoder.decode(bytes);\n  }\n  /**\n   * Get BPE pairs from word\n   */\n  getPairs(word) {\n    const pairs = /* @__PURE__ */ new Set();\n    for (let i = 0; i < word.length - 1; i++) {\n      pairs.add(`${word[i]} ${word[i + 1]}`);\n    }\n    return pairs;\n  }\n  /**\n   * Apply BPE to a word\n   */\n  bpe(token) {\n    if (this.vocab.has(token)) {\n      return [token];\n    }\n    let word = token.split(\"\");\n    let pairs = this.getPairs(word);\n    if (pairs.size === 0) {\n      return [token];\n    }\n    while (true) {\n      let minPair = null;\n      let minRank = Infinity;\n      for (const pair of pairs) {\n        const rank = this.merges.get(pair);\n        if (rank !== void 0 && rank < minRank) {\n          minRank = rank;\n          minPair = pair;\n        }\n      }\n      if (minPair === null)\n        break;\n      const parts = minPair.split(\" \");\n      const first = parts[0];\n      const second = parts[1];\n      if (!first || !second)\n        break;\n      const newWord = [];\n      let i = 0;\n      while (i < word.length) {\n        const j = word.indexOf(first, i);\n        if (j === -1) {\n          newWord.push(...word.slice(i));\n          break;\n        }\n        newWord.push(...word.slice(i, j));\n        if (word[j] === first && j < word.length - 1 && word[j + 1] === second) {\n          newWord.push(first + second);\n          i = j + 2;\n        } else {\n          newWord.push(word[j]);\n          i = j + 1;\n        }\n      }\n      word = newWord;\n      if (word.length === 1)\n        break;\n      pairs = this.getPairs(word);\n    }\n    return word;\n  }\n  /**\n   * WordPiece tokenization\n   */\n  wordPiece(word) {\n    if (this.vocab.has(word)) {\n      return [word];\n    }\n    const tokens = [];\n    let start = 0;\n    while (start < word.length) {\n      let end = word.length;\n      let curSubstr = null;\n      while (start < end) {\n        let substr = word.slice(start, end);\n        if (start > 0) {\n          substr = this.continuingSubwordPrefix + substr;\n        }\n        if (this.vocab.has(substr)) {\n          curSubstr = substr;\n          break;\n        }\n        end--;\n      }\n      if (curSubstr === null) {\n        tokens.push(this.unkToken);\n        start++;\n      } else {\n        tokens.push(curSubstr);\n        start = end;\n      }\n    }\n    return tokens;\n  }\n  /**\n   * Tokenize a single word\n   */\n  tokenizeWord(word) {\n    if (this.addedTokens.has(word)) {\n      return [word];\n    }\n    switch (this.modelType) {\n      case \"BPE\": {\n        const byteStr = this.textToBytes(word);\n        return this.bpe(byteStr);\n      }\n      case \"WordPiece\":\n        return this.wordPiece(word);\n      case \"Unigram\":\n        return this.unigramTokenize(word);\n      default:\n        return this.vocab.has(word) ? [word] : [this.unkToken];\n    }\n  }\n  /**\n   * Greedy longest-match tokenizer for SentencePiece Unigram models.\n   * Adds the U+2581 (▁) word-start prefix expected by SPM-based models.\n   */\n  unigramTokenize(word) {\n    const prefixedWord = \"\\u2581\" + word;\n    const tokens = [];\n    let start = 0;\n    const text = prefixedWord;\n    while (start < text.length) {\n      let end = text.length;\n      let found = false;\n      while (end > start) {\n        const sub2 = text.slice(start, end);\n        if (this.vocab.has(sub2)) {\n          tokens.push(sub2);\n          start = end;\n          found = true;\n          break;\n        }\n        end--;\n      }\n      if (!found) {\n        const ch = text[start];\n        tokens.push(this.vocab.has(ch) ? ch : this.unkToken);\n        start++;\n      }\n    }\n    return tokens.length > 0 ? tokens : [this.unkToken];\n  }\n  /**\n   * Main tokenization\n   */\n  tokenize(text) {\n    const normalized = this.normalize(text);\n    const tokens = [];\n    let remaining = normalized;\n    const sortedAddedTokens = Array.from(this.addedTokens.keys()).sort((a, b) => b.length - a.length);\n    for (const addedToken of sortedAddedTokens) {\n      if (remaining.includes(addedToken)) {\n        const parts = remaining.split(addedToken);\n        const newRemaining = [];\n        for (let i = 0; i < parts.length; i++) {\n          if (parts[i]) {\n            newRemaining.push(parts[i]);\n          }\n          if (i < parts.length - 1) {\n            tokens.push(addedToken);\n          }\n        }\n        remaining = newRemaining.join(\" \");\n      }\n    }\n    if (remaining.trim()) {\n      const words = this.preTokenize(remaining);\n      for (const word of words) {\n        if (!word)\n          continue;\n        const wordTokens = this.tokenizeWord(word);\n        tokens.push(...wordTokens);\n      }\n    }\n    return tokens;\n  }\n  /**\n   * Convert tokens to IDs\n   */\n  convertTokensToIds(tokens) {\n    return tokens.map((token) => {\n      const addedId = this.addedTokens.get(token);\n      if (addedId !== void 0)\n        return addedId;\n      const vocabId = this.vocab.get(token);\n      if (vocabId !== void 0)\n        return vocabId;\n      return this.unkTokenId;\n    });\n  }\n  /**\n   * Convert IDs to tokens\n   */\n  convertIdsToTokens(ids) {\n    return ids.map((id) => this.reverseVocab.get(id) ?? this.unkToken);\n  }\n  /**\n   * Apply post-processing (add special tokens)\n   */\n  postProcess(ids, pairIds) {\n    if (!this.postProcessor) {\n      const result2 = [];\n      const typeIds2 = [];\n      if (this.clsTokenId !== void 0) {\n        result2.push(this.clsTokenId);\n        typeIds2.push(0);\n      }\n      result2.push(...ids);\n      typeIds2.push(...ids.map(() => 0));\n      if (this.sepTokenId !== void 0) {\n        result2.push(this.sepTokenId);\n        typeIds2.push(0);\n      }\n      if (pairIds) {\n        result2.push(...pairIds);\n        typeIds2.push(...pairIds.map(() => 1));\n        if (this.sepTokenId !== void 0) {\n          result2.push(this.sepTokenId);\n          typeIds2.push(1);\n        }\n      }\n      return { ids: result2, typeIds: typeIds2 };\n    }\n    const template = pairIds ? this.postProcessor.pair : this.postProcessor.single;\n    if (!template) {\n      return { ids, typeIds: ids.map(() => 0) };\n    }\n    const result = [];\n    const typeIds = [];\n    for (const item of template) {\n      if (\"SpecialToken\" in item) {\n        const specialToken = this.postProcessor.special_tokens?.[item.SpecialToken.id];\n        if (specialToken) {\n          result.push(...specialToken.ids);\n          typeIds.push(...specialToken.ids.map(() => item.SpecialToken.type_id));\n        }\n      } else if (\"Sequence\" in item) {\n        const seqIds = item.Sequence.id === \"A\" ? ids : pairIds ?? [];\n        result.push(...seqIds);\n        typeIds.push(...seqIds.map(() => item.Sequence.type_id));\n      }\n    }\n    return { ids: result, typeIds };\n  }\n  /**\n   * Encode text\n   */\n  encode(text, options = {}) {\n    const { addSpecialTokens = true, maxLength = this.maxLength, padding = \"max_length\", truncation = true, returnAttentionMask = true, returnTokenTypeIds = false, textPair } = options;\n    const tokens = this.tokenize(text);\n    let inputIds = this.convertTokensToIds(tokens);\n    let pairIds;\n    if (textPair) {\n      const pairTokens = this.tokenize(textPair);\n      pairIds = this.convertTokensToIds(pairTokens);\n    }\n    let tokenTypeIds;\n    if (addSpecialTokens) {\n      const processed = this.postProcess(inputIds, pairIds);\n      inputIds = processed.ids;\n      if (returnTokenTypeIds) {\n        tokenTypeIds = processed.typeIds;\n      }\n    } else if (pairIds) {\n      inputIds = [...inputIds, ...pairIds];\n      if (returnTokenTypeIds) {\n        tokenTypeIds = [...inputIds.map(() => 0), ...pairIds.map(() => 1)];\n      }\n    }\n    if (truncation && inputIds.length > maxLength) {\n      inputIds = inputIds.slice(0, maxLength);\n      if (tokenTypeIds) {\n        tokenTypeIds = tokenTypeIds.slice(0, maxLength);\n      }\n    }\n    let attentionMask = [];\n    if (returnAttentionMask) {\n      attentionMask = inputIds.map(() => 1);\n    }\n    if (padding === \"max_length\" && inputIds.length < maxLength) {\n      const padLength = maxLength - inputIds.length;\n      inputIds = [...inputIds, ...new Array(padLength).fill(this.padTokenId)];\n      if (returnAttentionMask) {\n        attentionMask = [...attentionMask, ...new Array(padLength).fill(0)];\n      }\n      if (tokenTypeIds) {\n        tokenTypeIds = [...tokenTypeIds, ...new Array(padLength).fill(0)];\n      }\n    }\n    const result = {\n      inputIds,\n      attentionMask\n    };\n    if (returnTokenTypeIds && tokenTypeIds) {\n      result.tokenTypeIds = tokenTypeIds;\n    }\n    return result;\n  }\n  /**\n   * Batch encode\n   */\n  encodeBatch(texts, options = {}) {\n    if (options.padding === \"longest\") {\n      const encodings = texts.map((t) => this.encode(t, { ...options, padding: \"do_not_pad\" }));\n      const maxLen = Math.max(...encodings.map((e) => e.inputIds.length));\n      return texts.map((t) => this.encode(t, { ...options, maxLength: maxLen, padding: \"max_length\" }));\n    }\n    return texts.map((t) => this.encode(t, options));\n  }\n  /**\n   * Decode IDs to text\n   */\n  decode(ids, skipSpecialTokens = true) {\n    let tokens = this.convertIdsToTokens(ids);\n    if (skipSpecialTokens) {\n      tokens = tokens.filter((t) => !this.specialTokens.has(t));\n    }\n    let text = tokens.join(\"\");\n    if (this.modelType === \"BPE\") {\n      text = this.bytesToText(text);\n    }\n    if (this.modelType === \"WordPiece\") {\n      text = text.replace(new RegExp(this.continuingSubwordPrefix, \"g\"), \"\");\n    }\n    text = text.replace(/\\s+/g, \" \").trim();\n    return text;\n  }\n  /**\n   * Decode batch\n   */\n  decodeBatch(batchIds, skipSpecialTokens = true) {\n    return batchIds.map((ids) => this.decode(ids, skipSpecialTokens));\n  }\n  /**\n   * Get vocabulary size\n   */\n  get vocabSize() {\n    return this.vocab.size + this.addedTokens.size;\n  }\n  /**\n   * Get special token IDs\n   */\n  getSpecialTokenIds() {\n    return {\n      padTokenId: this.padTokenId,\n      unkTokenId: this.unkTokenId,\n      clsTokenId: this.clsTokenId,\n      sepTokenId: this.sepTokenId,\n      maskTokenId: this.maskTokenId,\n      bosTokenId: this.bosTokenId,\n      eosTokenId: this.eosTokenId\n    };\n  }\n  /**\n   * Get config\n   */\n  getConfig() {\n    return {\n      vocabSize: this.vocabSize,\n      maxLength: this.maxLength,\n      padTokenId: this.padTokenId,\n      unkTokenId: this.unkTokenId,\n      clsTokenId: this.clsTokenId,\n      sepTokenId: this.sepTokenId,\n      maskTokenId: this.maskTokenId,\n      bosTokenId: this.bosTokenId,\n      eosTokenId: this.eosTokenId\n    };\n  }\n  /**\n   * Check if token is special\n   */\n  isSpecialToken(token) {\n    return this.specialTokens.has(token);\n  }\n  /**\n   * Get token ID\n   */\n  getTokenId(token) {\n    return this.addedTokens.get(token) ?? this.vocab.get(token);\n  }\n  /**\n   * Get token from ID\n   */\n  getToken(id) {\n    return this.reverseVocab.get(id);\n  }\n};\nfunction createBasicTokenizer() {\n  const tokenizer = new Tokenizer();\n  return tokenizer;\n}\nasync function loadTokenizer(url) {\n  return Tokenizer.fromUrl(url);\n}\nasync function loadTokenizerFromHub(modelId, options) {\n  return Tokenizer.fromHuggingFace(modelId, options);\n}\n\n// dist/pipelines/text-classification.js\ninit_model_loader();\nvar DEFAULT_MODELS = {\n  model: \"https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/tokenizer.json\"\n};\nvar DEFAULT_SST2_LABELS = [\"NEGATIVE\", \"POSITIVE\"];\nvar TextClassificationPipeline = class extends BasePipeline {\n  constructor(config, labels) {\n    super(config);\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"labels\");\n    __publicField(this, \"modelUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    this.labels = labels ?? DEFAULT_SST2_LABELS;\n    this.modelUrl = config.model !== \"default\" ? config.model : DEFAULT_MODELS.model;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  setLabels(labels) {\n    this.labels = labels;\n  }\n  async run(input, options) {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    await this.initialize();\n    const startTime = performance.now();\n    const results = [];\n    for (const text of inputs) {\n      const tensorInputs = await this.preprocess(text);\n      const outputs = await this.runInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n    return isBatch ? results : results[0];\n  }\n  async preprocess(input) {\n    const text = Array.isArray(input) ? input[0] : input;\n    const encoded = this.tokenizer.encode(text, {\n      maxLength: 128,\n      padding: \"max_length\",\n      truncation: true\n    });\n    const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\");\n    const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map((m) => BigInt(m))), [1, encoded.attentionMask.length], \"int64\");\n    return [inputIds, attentionMask];\n  }\n  async runInference(inputs) {\n    const namedInputs = /* @__PURE__ */ new Map();\n    namedInputs.set(\"input_ids\", inputs[0]);\n    namedInputs.set(\"attention_mask\", inputs[1]);\n    const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n    return outputs;\n  }\n  async postprocess(outputs, options) {\n    const logits = outputs[0];\n    if (!logits) {\n      return { label: \"unknown\", score: 0 };\n    }\n    const probs = softmax(logits, -1);\n    const probsArray = probs.toFloat32Array();\n    let maxIdx = 0;\n    let maxScore = probsArray[0] ?? 0;\n    for (let i = 1; i < probsArray.length; i++) {\n      if ((probsArray[i] ?? 0) > maxScore) {\n        maxScore = probsArray[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n    const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n    return {\n      label,\n      score: maxScore\n    };\n  }\n};\nvar SentimentAnalysisPipeline = class extends TextClassificationPipeline {\n  constructor(config) {\n    super(config, SENTIMENT_LABELS);\n  }\n  async analyze(text, options) {\n    return this.run(text, options);\n  }\n};\nfunction createTextClassificationPipeline(config = {}) {\n  return new TextClassificationPipeline({\n    task: \"text-classification\",\n    model: config.model ?? \"default\",\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization\n  });\n}\nfunction createSentimentAnalysisPipeline(config = {}) {\n  return new SentimentAnalysisPipeline({\n    task: \"sentiment-analysis\",\n    model: config.model ?? \"default\",\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization\n  });\n}\nregisterPipeline(\"text-classification\", (config) => new TextClassificationPipeline(config));\nregisterPipeline(\"sentiment-analysis\", (config) => new SentimentAnalysisPipeline(config));\n\n// dist/pipelines/feature-extraction.js\ninit_tensor();\ninit_model_loader();\nvar DEFAULT_MODELS2 = {\n  model: \"https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model_quantized.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/tokenizer.json\"\n};\nvar DEFAULT_EMBEDDING_DIM = 384;\nvar FeatureExtractionPipeline = class extends BasePipeline {\n  constructor(config, embeddingDim = DEFAULT_EMBEDDING_DIM) {\n    super(config);\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"embeddingDim\");\n    __publicField(this, \"modelUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    this.embeddingDim = embeddingDim;\n    this.modelUrl = config.model !== \"default\" ? config.model : DEFAULT_MODELS2.model;\n    this.tokenizerUrl = DEFAULT_MODELS2.tokenizer;\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  async run(input, options) {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    await this.initialize();\n    const startTime = performance.now();\n    const results = [];\n    for (const text of inputs) {\n      const tensorInputs = await this.preprocess(text);\n      const outputs = await this.runInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n    return isBatch ? results : results[0];\n  }\n  async preprocess(input) {\n    const text = Array.isArray(input) ? input[0] : input;\n    const encoded = this.tokenizer.encode(text, {\n      maxLength: 128,\n      padding: \"max_length\",\n      truncation: true\n    });\n    const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\");\n    const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map((m) => BigInt(m))), [1, encoded.attentionMask.length], \"int64\");\n    const tokenTypeIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(() => BigInt(0))), [1, encoded.inputIds.length], \"int64\");\n    return [inputIds, attentionMask, tokenTypeIds];\n  }\n  async runInference(inputs) {\n    const namedInputs = /* @__PURE__ */ new Map();\n    namedInputs.set(\"input_ids\", inputs[0]);\n    namedInputs.set(\"attention_mask\", inputs[1]);\n    namedInputs.set(\"token_type_ids\", inputs[2]);\n    const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n    return outputs;\n  }\n  async postprocess(outputs, options) {\n    const hiddenStates = outputs[0];\n    if (!hiddenStates) {\n      return { embeddings: [] };\n    }\n    const pooling = options?.pooling ?? \"mean\";\n    const normalize = options?.normalize ?? true;\n    let embeddings;\n    switch (pooling) {\n      case \"cls\":\n        embeddings = this.extractCLSEmbedding(hiddenStates);\n        break;\n      case \"max\":\n        embeddings = this.maxPooling(hiddenStates);\n        break;\n      case \"none\":\n        embeddings = hiddenStates.toArray();\n        break;\n      case \"mean\":\n      default:\n        embeddings = this.meanPooling(hiddenStates);\n        break;\n    }\n    if (normalize) {\n      embeddings = this.normalizeVector(embeddings);\n    }\n    if (options?.outputDim && options.outputDim < embeddings.length) {\n      embeddings = embeddings.slice(0, options.outputDim);\n    }\n    return { embeddings };\n  }\n  extractCLSEmbedding(hiddenStates) {\n    const data = hiddenStates.toFloat32Array();\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    return Array.from(data.slice(0, embeddingDim));\n  }\n  meanPooling(hiddenStates) {\n    const data = hiddenStates.toFloat32Array();\n    const seqLen = hiddenStates.shape[1] ?? 1;\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    const result = new Float32Array(embeddingDim);\n    for (let i = 0; i < seqLen; i++) {\n      for (let j = 0; j < embeddingDim; j++) {\n        result[j] = (result[j] ?? 0) + (data[i * embeddingDim + j] ?? 0) / seqLen;\n      }\n    }\n    return Array.from(result);\n  }\n  maxPooling(hiddenStates) {\n    const data = hiddenStates.toFloat32Array();\n    const seqLen = hiddenStates.shape[1] ?? 1;\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    const result = new Array(embeddingDim).fill(-Infinity);\n    for (let i = 0; i < seqLen; i++) {\n      for (let j = 0; j < embeddingDim; j++) {\n        const val = data[i * embeddingDim + j] ?? 0;\n        if (val > (result[j] ?? -Infinity)) {\n          result[j] = val;\n        }\n      }\n    }\n    return result;\n  }\n  normalizeVector(vec) {\n    let norm = 0;\n    for (const v of vec) {\n      norm += v * v;\n    }\n    norm = Math.sqrt(norm);\n    if (norm === 0)\n      return vec;\n    return vec.map((v) => v / norm);\n  }\n};\nfunction createFeatureExtractionPipeline(config = {}) {\n  return new FeatureExtractionPipeline({\n    task: \"feature-extraction\",\n    model: config.model ?? \"default\",\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization\n  });\n}\nregisterPipeline(\"feature-extraction\", (config) => new FeatureExtractionPipeline(config));\n\n// dist/pipelines/image-classification.js\ninit_tensor();\n\n// dist/utils/preprocessor.js\ninit_tensor();\nvar DEFAULT_IMAGE_OPTIONS = {\n  width: 224,\n  height: 224,\n  resizeMode: \"cover\",\n  mean: [0.485, 0.456, 0.406],\n  std: [0.229, 0.224, 0.225],\n  rescaleFactor: 1 / 255,\n  grayscale: false,\n  channelFormat: \"CHW\",\n  dtype: \"float32\",\n  doResize: true,\n  doRescale: true,\n  doNormalize: true,\n  doCenterCrop: false,\n  paddingColor: [0, 0, 0]\n};\nvar ImagePreprocessor = class _ImagePreprocessor {\n  constructor(options = {}) {\n    __publicField(this, \"options\");\n    __publicField(this, \"canvas\", null);\n    __publicField(this, \"ctx\", null);\n    const size = options.size;\n    const width = options.width ?? size ?? DEFAULT_IMAGE_OPTIONS.width;\n    const height = options.height ?? size ?? DEFAULT_IMAGE_OPTIONS.height;\n    this.options = {\n      ...DEFAULT_IMAGE_OPTIONS,\n      ...options,\n      width,\n      height,\n      size: size ?? width,\n      cropSize: options.cropSize ?? options.size ?? width\n    };\n  }\n  /**\n   * Load from HuggingFace preprocessor_config.json\n   */\n  static fromConfig(config) {\n    const options = {};\n    const size = config[\"size\"];\n    if (size !== void 0) {\n      if (typeof size === \"number\") {\n        options.size = size;\n      } else if (typeof size === \"object\" && size !== null) {\n        const sizeObj = size;\n        options.width = sizeObj.width ?? sizeObj.shortest_edge;\n        options.height = sizeObj.height ?? sizeObj.shortest_edge;\n      }\n    }\n    const cropSize = config[\"crop_size\"];\n    if (cropSize !== void 0) {\n      if (typeof cropSize === \"number\") {\n        options.cropSize = cropSize;\n      } else if (typeof cropSize === \"object\" && cropSize !== null) {\n        const cropObj = cropSize;\n        options.cropSize = { width: cropObj.width ?? 224, height: cropObj.height ?? 224 };\n      }\n    }\n    const imageMean = config[\"image_mean\"];\n    if (Array.isArray(imageMean)) {\n      options.mean = imageMean;\n    }\n    const imageStd = config[\"image_std\"];\n    if (Array.isArray(imageStd)) {\n      options.std = imageStd;\n    }\n    const rescaleFactor = config[\"rescale_factor\"];\n    if (typeof rescaleFactor === \"number\") {\n      options.rescaleFactor = rescaleFactor;\n    }\n    const doResize = config[\"do_resize\"];\n    if (typeof doResize === \"boolean\") {\n      options.doResize = doResize;\n    }\n    const doRescale = config[\"do_rescale\"];\n    if (typeof doRescale === \"boolean\") {\n      options.doRescale = doRescale;\n    }\n    const doNormalize = config[\"do_normalize\"];\n    if (typeof doNormalize === \"boolean\") {\n      options.doNormalize = doNormalize;\n    }\n    const doCenterCrop = config[\"do_center_crop\"];\n    if (typeof doCenterCrop === \"boolean\") {\n      options.doCenterCrop = doCenterCrop;\n    }\n    if (config[\"resample\"] !== void 0) {\n      options.resizeMode = \"cover\";\n    }\n    return new _ImagePreprocessor(options);\n  }\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromUrl(url) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load preprocessor config from ${url}`);\n    }\n    const config = await response.json();\n    return _ImagePreprocessor.fromConfig(config);\n  }\n  /**\n   * Load from HuggingFace Hub by model ID\n   */\n  static async fromHuggingFace(modelId, options) {\n    const revision = options?.revision ?? \"main\";\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n    return _ImagePreprocessor.fromUrl(url);\n  }\n  /**\n   * Initialize canvas (lazy)\n   */\n  ensureCanvas() {\n    if (!this.canvas) {\n      if (typeof document !== \"undefined\") {\n        this.canvas = document.createElement(\"canvas\");\n        this.ctx = this.canvas.getContext(\"2d\");\n      } else {\n        throw new Error(\"ImagePreprocessor requires a browser environment\");\n      }\n    }\n  }\n  /**\n   * Process an image\n   */\n  async process(input) {\n    let imageData;\n    if (typeof input === \"string\") {\n      imageData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      imageData = await this.loadFromBlob(input);\n    } else if (input instanceof ImageData) {\n      imageData = input;\n    } else {\n      imageData = this.toImageData(input);\n    }\n    let processed = imageData;\n    if (this.options.doResize) {\n      processed = this.resize(processed);\n    }\n    if (this.options.doCenterCrop) {\n      processed = this.centerCrop(processed);\n    }\n    return this.toTensor(processed);\n  }\n  /**\n   * Process multiple images (batch)\n   */\n  async processBatch(inputs) {\n    const tensors = await Promise.all(inputs.map((input) => this.process(input)));\n    const batchSize = tensors.length;\n    const firstTensor = tensors[0];\n    if (!firstTensor) {\n      return new EdgeFlowTensor(new Float32Array(0), [0], \"float32\");\n    }\n    const channels = firstTensor.shape[0] ?? 3;\n    const height = firstTensor.shape[1] ?? this.options.height;\n    const width = firstTensor.shape[2] ?? this.options.width;\n    const batchData = new Float32Array(batchSize * channels * height * width);\n    for (let i = 0; i < tensors.length; i++) {\n      const t = tensors[i];\n      if (t) {\n        batchData.set(t.toFloat32Array(), i * channels * height * width);\n      }\n    }\n    return new EdgeFlowTensor(batchData, [batchSize, channels, height, width], \"float32\");\n  }\n  /**\n   * Load image from URL or base64\n   */\n  async loadFromUrl(url) {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n      img.onload = () => {\n        resolve(this.toImageData(img));\n      };\n      img.onerror = () => {\n        reject(new Error(`Failed to load image from ${url}`));\n      };\n      img.src = url;\n    });\n  }\n  /**\n   * Load image from Blob/File\n   */\n  async loadFromBlob(blob) {\n    const url = URL.createObjectURL(blob);\n    try {\n      return await this.loadFromUrl(url);\n    } finally {\n      URL.revokeObjectURL(url);\n    }\n  }\n  /**\n   * Center crop image\n   */\n  centerCrop(imageData) {\n    const cropSize = this.options.cropSize;\n    let cropWidth;\n    let cropHeight;\n    if (typeof cropSize === \"number\") {\n      cropWidth = cropSize;\n      cropHeight = cropSize;\n    } else {\n      cropWidth = cropSize.width;\n      cropHeight = cropSize.height;\n    }\n    const srcX = Math.max(0, Math.floor((imageData.width - cropWidth) / 2));\n    const srcY = Math.max(0, Math.floor((imageData.height - cropHeight) / 2));\n    this.ensureCanvas();\n    const srcCanvas = document.createElement(\"canvas\");\n    srcCanvas.width = imageData.width;\n    srcCanvas.height = imageData.height;\n    const srcCtx = srcCanvas.getContext(\"2d\");\n    srcCtx.putImageData(imageData, 0, 0);\n    this.canvas.width = cropWidth;\n    this.canvas.height = cropHeight;\n    this.ctx.drawImage(srcCanvas, srcX, srcY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);\n    return this.ctx.getImageData(0, 0, cropWidth, cropHeight);\n  }\n  /**\n   * Convert image element to ImageData\n   */\n  toImageData(source) {\n    this.ensureCanvas();\n    const { width, height } = source;\n    this.canvas.width = width;\n    this.canvas.height = height;\n    this.ctx.drawImage(source, 0, 0);\n    return this.ctx.getImageData(0, 0, width, height);\n  }\n  /**\n   * Resize image data\n   */\n  resize(imageData) {\n    const { width, height, resizeMode } = this.options;\n    this.ensureCanvas();\n    let srcX = 0, srcY = 0, srcW = imageData.width, srcH = imageData.height;\n    let dstX = 0, dstY = 0, dstW = width, dstH = height;\n    if (resizeMode === \"contain\") {\n      const scale = Math.min(width / imageData.width, height / imageData.height);\n      dstW = Math.round(imageData.width * scale);\n      dstH = Math.round(imageData.height * scale);\n      dstX = Math.round((width - dstW) / 2);\n      dstY = Math.round((height - dstH) / 2);\n    } else if (resizeMode === \"cover\") {\n      const scale = Math.max(width / imageData.width, height / imageData.height);\n      srcW = Math.round(width / scale);\n      srcH = Math.round(height / scale);\n      srcX = Math.round((imageData.width - srcW) / 2);\n      srcY = Math.round((imageData.height - srcH) / 2);\n    }\n    const srcCanvas = document.createElement(\"canvas\");\n    srcCanvas.width = imageData.width;\n    srcCanvas.height = imageData.height;\n    const srcCtx = srcCanvas.getContext(\"2d\");\n    srcCtx.putImageData(imageData, 0, 0);\n    this.canvas.width = width;\n    this.canvas.height = height;\n    if (resizeMode === \"contain\" || resizeMode === \"pad\") {\n      this.ctx.fillStyle = \"black\";\n      this.ctx.fillRect(0, 0, width, height);\n    }\n    this.ctx.drawImage(srcCanvas, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH);\n    return this.ctx.getImageData(0, 0, width, height);\n  }\n  /**\n   * Convert ImageData to tensor\n   */\n  toTensor(imageData) {\n    const { mean: mean2, std, grayscale, channelFormat, dtype, doRescale, rescaleFactor, doNormalize } = this.options;\n    const height = imageData.height;\n    const width = imageData.width;\n    const channels = grayscale ? 1 : 3;\n    const data = new Float32Array(channels * height * width);\n    const pixels = imageData.data;\n    for (let y = 0; y < height; y++) {\n      for (let x = 0; x < width; x++) {\n        const pixelIdx = (y * width + x) * 4;\n        if (grayscale) {\n          let gray = 0.299 * (pixels[pixelIdx] ?? 0) + 0.587 * (pixels[pixelIdx + 1] ?? 0) + 0.114 * (pixels[pixelIdx + 2] ?? 0);\n          if (doRescale) {\n            gray *= rescaleFactor;\n          }\n          if (doNormalize) {\n            gray = (gray - (mean2[0] ?? 0)) / (std[0] ?? 1);\n          }\n          const idx = y * width + x;\n          data[idx] = gray;\n        } else if (channelFormat === \"CHW\") {\n          for (let c = 0; c < 3; c++) {\n            let value = pixels[pixelIdx + c] ?? 0;\n            if (doRescale) {\n              value *= rescaleFactor;\n            }\n            if (doNormalize) {\n              value = (value - (mean2[c] ?? 0)) / (std[c] ?? 1);\n            }\n            const idx = c * height * width + y * width + x;\n            data[idx] = value;\n          }\n        } else {\n          for (let c = 0; c < 3; c++) {\n            let value = pixels[pixelIdx + c] ?? 0;\n            if (doRescale) {\n              value *= rescaleFactor;\n            }\n            if (doNormalize) {\n              value = (value - (mean2[c] ?? 0)) / (std[c] ?? 1);\n            }\n            const idx = y * width * 3 + x * 3 + c;\n            data[idx] = value;\n          }\n        }\n      }\n    }\n    const shape = channelFormat === \"CHW\" ? [channels, height, width] : [height, width, channels];\n    return new EdgeFlowTensor(data, shape, dtype);\n  }\n  /**\n   * Get current options\n   */\n  getOptions() {\n    return { ...this.options };\n  }\n};\nvar DEFAULT_AUDIO_OPTIONS = {\n  sampleRate: 16e3,\n  nMels: 80,\n  nFft: 400,\n  hopLength: 160,\n  normalize: true,\n  maxDuration: 30\n};\nvar AudioPreprocessor = class _AudioPreprocessor {\n  constructor(options = {}) {\n    __publicField(this, \"options\");\n    __publicField(this, \"audioContext\", null);\n    this.options = { ...DEFAULT_AUDIO_OPTIONS, ...options };\n  }\n  /**\n   * Load from HuggingFace feature_extractor config\n   */\n  static fromConfig(config) {\n    const options = {};\n    const samplingRate = config[\"sampling_rate\"];\n    if (typeof samplingRate === \"number\") {\n      options.sampleRate = samplingRate;\n    }\n    const featureSize = config[\"feature_size\"];\n    if (typeof featureSize === \"number\") {\n      options.nMels = featureSize;\n    }\n    const nFft = config[\"n_fft\"];\n    if (typeof nFft === \"number\") {\n      options.nFft = nFft;\n    }\n    const hopLength = config[\"hop_length\"];\n    if (typeof hopLength === \"number\") {\n      options.hopLength = hopLength;\n    }\n    return new _AudioPreprocessor(options);\n  }\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromHuggingFace(modelId, options) {\n    const revision = options?.revision ?? \"main\";\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load audio config from ${url}`);\n    }\n    const config = await response.json();\n    return _AudioPreprocessor.fromConfig(config);\n  }\n  /**\n   * Initialize audio context (lazy)\n   */\n  ensureAudioContext() {\n    if (!this.audioContext) {\n      if (typeof AudioContext !== \"undefined\") {\n        this.audioContext = new AudioContext({ sampleRate: this.options.sampleRate });\n      } else {\n        throw new Error(\"AudioPreprocessor requires Web Audio API support\");\n      }\n    }\n  }\n  /**\n   * Process audio data\n   */\n  async process(input) {\n    let audioData;\n    if (typeof input === \"string\") {\n      audioData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      audioData = await this.loadFromBlob(input);\n    } else if (input instanceof AudioBuffer) {\n      audioData = this.audioBufferToFloat32(input);\n    } else if (input instanceof Float32Array) {\n      audioData = input;\n    } else {\n      audioData = await this.decodeAudioData(input);\n    }\n    if (this.options.normalize) {\n      audioData = this.normalizeAudio(audioData);\n    }\n    const maxSamples = this.options.maxDuration * this.options.sampleRate;\n    if (audioData.length > maxSamples) {\n      audioData = audioData.slice(0, maxSamples);\n    }\n    const melSpec = this.computeMelSpectrogram(audioData);\n    return melSpec;\n  }\n  /**\n   * Process raw waveform (for models that don't need mel spectrogram)\n   */\n  async processRaw(input) {\n    let audioData;\n    if (typeof input === \"string\") {\n      audioData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      audioData = await this.loadFromBlob(input);\n    } else if (input instanceof AudioBuffer) {\n      audioData = this.audioBufferToFloat32(input);\n    } else if (input instanceof Float32Array) {\n      audioData = input;\n    } else {\n      audioData = await this.decodeAudioData(input);\n    }\n    if (this.options.normalize) {\n      audioData = this.normalizeAudio(audioData);\n    }\n    const maxSamples = this.options.maxDuration * this.options.sampleRate;\n    if (audioData.length > maxSamples) {\n      audioData = audioData.slice(0, maxSamples);\n    }\n    return new EdgeFlowTensor(audioData, [1, audioData.length], \"float32\");\n  }\n  /**\n   * Load audio from URL\n   */\n  async loadFromUrl(url) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load audio from ${url}`);\n    }\n    const arrayBuffer = await response.arrayBuffer();\n    return this.decodeAudioData(arrayBuffer);\n  }\n  /**\n   * Load audio from Blob/File\n   */\n  async loadFromBlob(blob) {\n    const arrayBuffer = await blob.arrayBuffer();\n    return this.decodeAudioData(arrayBuffer);\n  }\n  /**\n   * Decode audio data\n   */\n  async decodeAudioData(data) {\n    this.ensureAudioContext();\n    const audioBuffer = await this.audioContext.decodeAudioData(data.slice(0));\n    return this.audioBufferToFloat32(audioBuffer);\n  }\n  /**\n   * Convert AudioBuffer to Float32Array\n   */\n  audioBufferToFloat32(buffer) {\n    const channelData = buffer.getChannelData(0);\n    return new Float32Array(channelData);\n  }\n  /**\n   * Normalize audio\n   */\n  normalizeAudio(data) {\n    let max = 0;\n    for (let i = 0; i < data.length; i++) {\n      const abs = Math.abs(data[i] ?? 0);\n      if (abs > max)\n        max = abs;\n    }\n    if (max > 0) {\n      const result = new Float32Array(data.length);\n      for (let i = 0; i < data.length; i++) {\n        result[i] = (data[i] ?? 0) / max;\n      }\n      return result;\n    }\n    return data;\n  }\n  /**\n   * Compute mel spectrogram (simplified implementation)\n   */\n  computeMelSpectrogram(audio) {\n    const { nMels, nFft, hopLength } = this.options;\n    const numFrames = Math.floor((audio.length - nFft) / hopLength) + 1;\n    if (numFrames <= 0) {\n      return new EdgeFlowTensor(new Float32Array(nMels), [1, nMels], \"float32\");\n    }\n    const melSpec = new Float32Array(numFrames * nMels);\n    for (let frame = 0; frame < numFrames; frame++) {\n      const start = frame * hopLength;\n      for (let mel = 0; mel < nMels; mel++) {\n        let energy = 0;\n        const freqStart = Math.floor(mel / nMels * (nFft / 2));\n        const freqEnd = Math.floor((mel + 1) / nMels * (nFft / 2));\n        for (let i = freqStart; i < Math.min(freqEnd, nFft); i++) {\n          const sample = audio[start + i] ?? 0;\n          energy += sample * sample;\n        }\n        melSpec[frame * nMels + mel] = Math.log(energy + 1e-10);\n      }\n    }\n    return new EdgeFlowTensor(melSpec, [numFrames, nMels], \"float32\");\n  }\n  /**\n   * Dispose resources\n   */\n  dispose() {\n    if (this.audioContext) {\n      this.audioContext.close();\n      this.audioContext = null;\n    }\n  }\n};\nfunction preprocessText(text, options = {}) {\n  const { lowercase = true, removePunctuation = false, normalizeWhitespace = true, maxLength } = options;\n  let result = text;\n  if (lowercase) {\n    result = result.toLowerCase();\n  }\n  if (removePunctuation) {\n    result = result.replace(/[^\\w\\s]/g, \"\");\n  }\n  if (normalizeWhitespace) {\n    result = result.replace(/\\s+/g, \" \").trim();\n  }\n  if (maxLength && result.length > maxLength) {\n    result = result.slice(0, maxLength);\n  }\n  return result;\n}\nfunction createImagePreprocessor(preset = \"imagenet\", options = {}) {\n  const presets = {\n    imagenet: {\n      width: 224,\n      height: 224,\n      mean: [0.485, 0.456, 0.406],\n      std: [0.229, 0.224, 0.225]\n    },\n    clip: {\n      width: 224,\n      height: 224,\n      mean: [0.48145466, 0.4578275, 0.40821073],\n      std: [0.26862954, 0.26130258, 0.27577711]\n    },\n    vit: {\n      width: 224,\n      height: 224,\n      mean: [0.5, 0.5, 0.5],\n      std: [0.5, 0.5, 0.5]\n    },\n    custom: {}\n  };\n  return new ImagePreprocessor({ ...presets[preset], ...options });\n}\nfunction createAudioPreprocessor(preset = \"whisper\", options = {}) {\n  const presets = {\n    whisper: {\n      sampleRate: 16e3,\n      nMels: 80,\n      nFft: 400,\n      hopLength: 160\n    },\n    wav2vec: {\n      sampleRate: 16e3,\n      normalize: true\n    },\n    custom: {}\n  };\n  return new AudioPreprocessor({ ...presets[preset], ...options });\n}\n\n// dist/pipelines/image-classification.js\ninit_model_loader();\nvar DEFAULT_MODELS3 = {\n  model: \"https://huggingface.co/Xenova/mobilevit-small/resolve/main/onnx/model_quantized.onnx\"\n};\nvar ImageClassificationPipeline = class extends BasePipeline {\n  constructor(config, labels, _numClasses = 1e3) {\n    super(config);\n    __publicField(this, \"preprocessor\", null);\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"labels\");\n    __publicField(this, \"modelUrl\");\n    this.labels = labels ?? IMAGENET_LABELS;\n    this.modelUrl = config.model !== \"default\" ? config.model : DEFAULT_MODELS3.model;\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.preprocessor) {\n      this.preprocessor = createImagePreprocessor(\"imagenet\");\n    }\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  setLabels(labels) {\n    this.labels = labels;\n  }\n  async run(input, options) {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    await this.initialize();\n    const startTime = performance.now();\n    const results = [];\n    for (const image of inputs) {\n      const tensorInputs = await this.preprocess(image);\n      const outputs = await this.runModelInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n    return isBatch ? results : results[0];\n  }\n  async preprocess(input) {\n    const image = Array.isArray(input) ? input[0] : input;\n    const tensor2 = await this.preprocessor.process(image);\n    if (tensor2.shape.length === 3) {\n      return [tensor2.reshape([1, ...tensor2.shape])];\n    }\n    return [tensor2];\n  }\n  async runModelInference(inputs) {\n    const outputs = await runInference(this.onnxModel, inputs);\n    return outputs;\n  }\n  async postprocess(outputs, options) {\n    const logits = outputs[0];\n    if (!logits) {\n      return { label: \"unknown\", score: 0 };\n    }\n    const probs = softmax(logits, -1);\n    const probsArray = probs.toFloat32Array();\n    let maxIdx = 0;\n    let maxScore = probsArray[0] ?? 0;\n    for (let i = 1; i < probsArray.length; i++) {\n      if ((probsArray[i] ?? 0) > maxScore) {\n        maxScore = probsArray[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n    const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n    return { label, score: maxScore };\n  }\n};\nfunction createImageClassificationPipeline(config = {}, labels) {\n  return new ImageClassificationPipeline({\n    task: \"image-classification\",\n    model: config.model ?? \"default\",\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization\n  }, labels);\n}\nregisterPipeline(\"image-classification\", (config) => new ImageClassificationPipeline(config));\n\n// dist/pipelines/text-generation.js\ninit_tensor();\nvar DEFAULT_LLM_MODELS = {\n  model: \"https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/onnx/model_q4f16.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/tokenizer.json\"\n};\nvar TextGenerationPipeline = class extends BasePipeline {\n  constructor(config) {\n    super(config ?? {\n      task: \"text-generation\",\n      model: \"default\"\n    });\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"eosTokenId\", 50256);\n    // GPT-2 default\n    __publicField(this, \"llmModel\", null);\n    __publicField(this, \"modelsLoaded\", false);\n    // Custom model URLs\n    __publicField(this, \"modelUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    // ==========================================================================\n    // Chat / Conversation Support\n    // ==========================================================================\n    __publicField(this, \"conversationHistory\", []);\n    __publicField(this, \"chatTemplateType\", \"chatml\");\n    this.modelUrl = DEFAULT_LLM_MODELS.model;\n    this.tokenizerUrl = DEFAULT_LLM_MODELS.tokenizer;\n  }\n  /**\n   * Check if model is loaded\n   */\n  get isModelLoaded() {\n    return this.modelsLoaded;\n  }\n  /**\n   * Set custom model URLs\n   */\n  setModelUrls(model, tokenizer) {\n    this.modelUrl = model;\n    this.tokenizerUrl = tokenizer;\n  }\n  /**\n   * Load model and tokenizer with progress callback\n   */\n  async loadModel(onProgress) {\n    if (this.modelsLoaded)\n      return;\n    onProgress?.({ stage: \"tokenizer\", loaded: 0, total: 100, progress: 0 });\n    try {\n      const tokenizerResponse = await fetch(this.tokenizerUrl);\n      if (!tokenizerResponse.ok) {\n        throw new Error(`Failed to fetch tokenizer: ${tokenizerResponse.status}`);\n      }\n      const tokenizerJson = await tokenizerResponse.json();\n      this.tokenizer = await Tokenizer.fromJSON(tokenizerJson);\n      const specialIds = this.tokenizer.getSpecialTokenIds();\n      this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 2;\n      onProgress?.({ stage: \"tokenizer\", loaded: 100, total: 100, progress: 100 });\n    } catch (error) {\n      throw new Error(`Failed to load tokenizer: ${error}`);\n    }\n    onProgress?.({ stage: \"model\", loaded: 0, total: 100, progress: 0 });\n    const modelData = await this.fetchModelWithProgress(this.modelUrl, (loaded, total) => {\n      onProgress?.({\n        stage: \"model\",\n        loaded,\n        total,\n        progress: Math.round(loaded / total * 100)\n      });\n    });\n    this.llmModel = await loadModelFromBuffer(modelData, {\n      runtime: \"wasm\"\n      // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n    this.model = this.llmModel;\n    this.modelsLoaded = true;\n  }\n  /**\n   * Fetch model with progress tracking\n   */\n  async fetchModelWithProgress(url, onProgress) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n    }\n    const contentLength = response.headers.get(\"content-length\");\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n    if (!response.body) {\n      const buffer2 = await response.arrayBuffer();\n      onProgress(buffer2.byteLength, buffer2.byteLength);\n      return buffer2;\n    }\n    const reader = response.body.getReader();\n    const chunks = [];\n    let loaded = 0;\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done)\n        break;\n      chunks.push(value);\n      loaded += value.length;\n      onProgress(loaded, total || loaded);\n    }\n    const buffer = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      buffer.set(chunk, offset);\n      offset += chunk.length;\n    }\n    return buffer.buffer;\n  }\n  /**\n   * Initialize pipeline (override to skip default model loading)\n   */\n  async initialize() {\n    if (this.isReady)\n      return;\n    this.isReady = true;\n  }\n  /**\n   * Set tokenizer\n   */\n  setTokenizer(tokenizer) {\n    this.tokenizer = tokenizer;\n    const specialIds = tokenizer.getSpecialTokenIds();\n    this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 50256;\n  }\n  /**\n   * Preprocess - not used for text generation (handled in generateSingle)\n   */\n  async preprocess(input) {\n    const text = Array.isArray(input) ? input[0] ?? \"\" : input;\n    if (!this.tokenizer) {\n      return [new EdgeFlowTensor(new Float32Array([0]), [1], \"float32\")];\n    }\n    const encoded = this.tokenizer.encode(text, {\n      addSpecialTokens: false,\n      padding: \"do_not_pad\"\n    });\n    return [new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\")];\n  }\n  /**\n   * Postprocess - not used for text generation (handled in generateSingle)\n   */\n  async postprocess(_outputs, _options) {\n    return {\n      generatedText: \"\",\n      tokenIds: [],\n      numTokens: 0,\n      processingTime: 0\n    };\n  }\n  /**\n   * Generate text (non-streaming)\n   */\n  async run(prompt, options) {\n    await this.initialize();\n    const prompts = Array.isArray(prompt) ? prompt : [prompt];\n    const results = await Promise.all(prompts.map((p) => this.generateSingle(p, options ?? {})));\n    return Array.isArray(prompt) ? results : results[0];\n  }\n  /**\n   * Generate text with streaming (async generator)\n   */\n  async *stream(prompt, options = {}) {\n    const startTime = performance.now();\n    if (!this.tokenizer) {\n      throw new Error(\"Tokenizer not set. Call setTokenizer() first.\");\n    }\n    const { maxNewTokens = 50, maxLength = 512, temperature = 1, topK = 0, topP = 1, repetitionPenalty = 1, stopSequences = [], doSample = true } = options;\n    const encoded = this.tokenizer.encode(prompt, {\n      addSpecialTokens: false,\n      padding: \"do_not_pad\",\n      truncation: false\n    });\n    let inputIds = [...encoded.inputIds];\n    const generatedIds = [];\n    let generatedText = \"\";\n    for (let i = 0; i < maxNewTokens; i++) {\n      if (inputIds.length >= maxLength)\n        break;\n      const nextTokenId = await this.generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample);\n      if (nextTokenId === this.eosTokenId) {\n        yield {\n          token: \"\",\n          tokenId: nextTokenId,\n          generatedText,\n          done: true\n        };\n        break;\n      }\n      const token = this.tokenizer.decode([nextTokenId], true);\n      generatedIds.push(nextTokenId);\n      inputIds.push(nextTokenId);\n      generatedText += token;\n      if (options.onToken) {\n        options.onToken(token, nextTokenId);\n      }\n      let shouldStop = false;\n      for (const stopSeq of stopSequences) {\n        if (generatedText.endsWith(stopSeq)) {\n          generatedText = generatedText.slice(0, -stopSeq.length);\n          shouldStop = true;\n          break;\n        }\n      }\n      yield {\n        token,\n        tokenId: nextTokenId,\n        generatedText,\n        done: shouldStop\n      };\n      if (shouldStop)\n        break;\n    }\n    const endTime = performance.now();\n    console.log(`Generation completed in ${(endTime - startTime).toFixed(2)}ms`);\n  }\n  /**\n   * Generate a single sequence (non-streaming)\n   */\n  async generateSingle(prompt, options) {\n    const startTime = performance.now();\n    if (!this.tokenizer) {\n      throw new Error(\"Tokenizer not set. Call setTokenizer() first.\");\n    }\n    const { maxNewTokens = 50, maxLength = 512, temperature = 1, topK = 0, topP = 1, repetitionPenalty = 1, stopSequences = [], doSample = true, returnFullText = false } = options;\n    const encoded = this.tokenizer.encode(prompt, {\n      addSpecialTokens: false,\n      padding: \"do_not_pad\",\n      truncation: false\n    });\n    let inputIds = [...encoded.inputIds];\n    const generatedIds = [];\n    for (let i = 0; i < maxNewTokens; i++) {\n      if (inputIds.length >= maxLength)\n        break;\n      const nextTokenId = await this.generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample);\n      if (nextTokenId === this.eosTokenId)\n        break;\n      generatedIds.push(nextTokenId);\n      inputIds.push(nextTokenId);\n      if (options.onToken) {\n        const token = this.tokenizer.decode([nextTokenId], true);\n        options.onToken(token, nextTokenId);\n      }\n      const currentText = this.tokenizer.decode(generatedIds, true);\n      let shouldStop = false;\n      for (const stopSeq of stopSequences) {\n        if (currentText.endsWith(stopSeq)) {\n          shouldStop = true;\n          break;\n        }\n      }\n      if (shouldStop)\n        break;\n    }\n    const generatedText = this.tokenizer.decode(generatedIds, true);\n    const endTime = performance.now();\n    return {\n      generatedText,\n      fullText: returnFullText ? prompt + generatedText : void 0,\n      tokenIds: generatedIds,\n      numTokens: generatedIds.length,\n      processingTime: endTime - startTime\n    };\n  }\n  /**\n   * Generate next token using the model\n   */\n  async generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample) {\n    if (!this.model) {\n      throw new Error(\"Model not loaded\");\n    }\n    const seqLen = inputIds.length;\n    const inputs = /* @__PURE__ */ new Map();\n    inputs.set(\"input_ids\", new EdgeFlowTensor(BigInt64Array.from(inputIds.map((id) => BigInt(id))), [1, seqLen], \"int64\"));\n    inputs.set(\"attention_mask\", new EdgeFlowTensor(BigInt64Array.from(inputIds.map(() => BigInt(1))), [1, seqLen], \"int64\"));\n    inputs.set(\"position_ids\", new EdgeFlowTensor(BigInt64Array.from(Array.from({ length: seqLen }, (_, i) => BigInt(i))), [1, seqLen], \"int64\"));\n    const numLayers = 22;\n    const numKVHeads = 4;\n    const headDim = 64;\n    for (let i = 0; i < numLayers; i++) {\n      inputs.set(`past_key_values.${i}.key`, new EdgeFlowTensor(new Float32Array(0), [1, numKVHeads, 0, headDim], \"float32\"));\n      inputs.set(`past_key_values.${i}.value`, new EdgeFlowTensor(new Float32Array(0), [1, numKVHeads, 0, headDim], \"float32\"));\n    }\n    const outputs = await runInferenceNamed(this.model, inputs);\n    if (!outputs || outputs.length === 0) {\n      throw new Error(\"Model returned no outputs\");\n    }\n    const logits = outputs[0];\n    const logitsData = logits.toFloat32Array();\n    const vocabSize = logits.shape[logits.shape.length - 1] ?? 50257;\n    const lastPositionLogits = new Float32Array(vocabSize);\n    const offset = (inputIds.length - 1) * vocabSize;\n    for (let i = 0; i < vocabSize; i++) {\n      lastPositionLogits[i] = logitsData[offset + i] ?? 0;\n    }\n    if (repetitionPenalty !== 1) {\n      for (const prevId of inputIds) {\n        if (prevId < vocabSize) {\n          const score = lastPositionLogits[prevId] ?? 0;\n          lastPositionLogits[prevId] = score > 0 ? score / repetitionPenalty : score * repetitionPenalty;\n        }\n      }\n    }\n    if (temperature !== 1) {\n      for (let i = 0; i < vocabSize; i++) {\n        lastPositionLogits[i] = (lastPositionLogits[i] ?? 0) / temperature;\n      }\n    }\n    const logitsTensor = new EdgeFlowTensor(lastPositionLogits, [vocabSize], \"float32\");\n    const probs = softmax(logitsTensor).toFloat32Array();\n    if (doSample) {\n      return this.sample(probs, topK, topP);\n    } else {\n      return this.greedy(probs);\n    }\n  }\n  /**\n   * Greedy decoding (argmax)\n   */\n  greedy(probs) {\n    let maxIdx = 0;\n    let maxProb = probs[0] ?? 0;\n    for (let i = 1; i < probs.length; i++) {\n      if ((probs[i] ?? 0) > maxProb) {\n        maxProb = probs[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n    return maxIdx;\n  }\n  /**\n   * Sample from probability distribution with top-k/top-p filtering\n   */\n  sample(probs, topK, topP) {\n    const indices = Array.from({ length: probs.length }, (_, i) => i);\n    indices.sort((a, b) => (probs[b] ?? 0) - (probs[a] ?? 0));\n    let candidateIndices = indices;\n    if (topK > 0 && topK < probs.length) {\n      candidateIndices = indices.slice(0, topK);\n    }\n    if (topP < 1) {\n      let cumulativeProb = 0;\n      const filtered = [];\n      for (const idx of candidateIndices) {\n        filtered.push(idx);\n        cumulativeProb += probs[idx] ?? 0;\n        if (cumulativeProb >= topP)\n          break;\n      }\n      candidateIndices = filtered;\n    }\n    let totalProb = 0;\n    for (const idx of candidateIndices) {\n      totalProb += probs[idx] ?? 0;\n    }\n    const r = Math.random() * totalProb;\n    let cumulative = 0;\n    for (const idx of candidateIndices) {\n      cumulative += probs[idx] ?? 0;\n      if (cumulative >= r) {\n        return idx;\n      }\n    }\n    return candidateIndices[0] ?? 0;\n  }\n  /**\n   * Set the chat template type\n   */\n  setChatTemplate(templateType) {\n    this.chatTemplateType = templateType;\n  }\n  /**\n   * Apply chat template to messages\n   */\n  applyChatTemplate(messages, options) {\n    const templateType = options?.templateType ?? this.chatTemplateType;\n    switch (templateType) {\n      case \"chatml\":\n        return this.applyChatMLTemplate(messages);\n      case \"llama2\":\n        return this.applyLlama2Template(messages);\n      case \"llama3\":\n        return this.applyLlama3Template(messages);\n      case \"mistral\":\n        return this.applyMistralTemplate(messages);\n      case \"phi3\":\n        return this.applyPhi3Template(messages);\n      case \"alpaca\":\n        return this.applyAlpacaTemplate(messages);\n      case \"vicuna\":\n        return this.applyVicunaTemplate(messages);\n      case \"custom\":\n        return this.applyCustomTemplate(messages, options?.customTemplate ?? {});\n      default:\n        return this.applyChatMLTemplate(messages);\n    }\n  }\n  /**\n   * ChatML template (used by many models including Qwen, Yi)\n   */\n  applyChatMLTemplate(messages) {\n    let prompt = \"\";\n    for (const msg of messages) {\n      prompt += `<|im_start|>${msg.role}\n${msg.content}<|im_end|>\n`;\n    }\n    prompt += \"<|im_start|>assistant\\n\";\n    return prompt;\n  }\n  /**\n   * Llama 2 template\n   */\n  applyLlama2Template(messages) {\n    let prompt = \"\";\n    let systemMsg = \"\";\n    for (const msg of messages) {\n      if (msg.role === \"system\") {\n        systemMsg = msg.content;\n      } else if (msg.role === \"user\") {\n        if (systemMsg) {\n          prompt += `<s>[INST] <<SYS>>\n${systemMsg}\n<</SYS>>\n\n${msg.content} [/INST]`;\n          systemMsg = \"\";\n        } else {\n          prompt += `<s>[INST] ${msg.content} [/INST]`;\n        }\n      } else if (msg.role === \"assistant\") {\n        prompt += ` ${msg.content} </s>`;\n      }\n    }\n    return prompt;\n  }\n  /**\n   * Llama 3 template\n   */\n  applyLlama3Template(messages) {\n    let prompt = \"<|begin_of_text|>\";\n    for (const msg of messages) {\n      prompt += `<|start_header_id|>${msg.role}<|end_header_id|>\n\n${msg.content}<|eot_id|>`;\n    }\n    prompt += \"<|start_header_id|>assistant<|end_header_id|>\\n\\n\";\n    return prompt;\n  }\n  /**\n   * Mistral template\n   */\n  applyMistralTemplate(messages) {\n    let prompt = \"<s>\";\n    for (const msg of messages) {\n      if (msg.role === \"user\") {\n        prompt += `[INST] ${msg.content} [/INST]`;\n      } else if (msg.role === \"assistant\") {\n        prompt += ` ${msg.content}</s>`;\n      } else if (msg.role === \"system\") {\n        prompt += `[INST] ${msg.content}\n`;\n      }\n    }\n    return prompt;\n  }\n  /**\n   * Phi-3 template\n   */\n  applyPhi3Template(messages) {\n    let prompt = \"\";\n    for (const msg of messages) {\n      prompt += `<|${msg.role}|>\n${msg.content}<|end|>\n`;\n    }\n    prompt += \"<|assistant|>\\n\";\n    return prompt;\n  }\n  /**\n   * Alpaca template\n   */\n  applyAlpacaTemplate(messages) {\n    let prompt = \"\";\n    let instruction = \"\";\n    let input = \"\";\n    for (const msg of messages) {\n      if (msg.role === \"system\") {\n        instruction = msg.content;\n      } else if (msg.role === \"user\") {\n        input = msg.content;\n      }\n    }\n    if (instruction) {\n      prompt = `### Instruction:\n${instruction}\n\n`;\n    }\n    if (input) {\n      prompt += `### Input:\n${input}\n\n`;\n    }\n    prompt += \"### Response:\\n\";\n    return prompt;\n  }\n  /**\n   * Vicuna template\n   */\n  applyVicunaTemplate(messages) {\n    let prompt = \"\";\n    for (const msg of messages) {\n      if (msg.role === \"system\") {\n        prompt += `${msg.content}\n\n`;\n      } else if (msg.role === \"user\") {\n        prompt += `USER: ${msg.content}\n`;\n      } else if (msg.role === \"assistant\") {\n        prompt += `ASSISTANT: ${msg.content}\n`;\n      }\n    }\n    prompt += \"ASSISTANT:\";\n    return prompt;\n  }\n  /**\n   * Custom template\n   */\n  applyCustomTemplate(messages, template) {\n    const { systemPrefix = \"\", systemSuffix = \"\\n\", userPrefix = \"User: \", userSuffix = \"\\n\", assistantPrefix = \"Assistant: \", assistantSuffix = \"\\n\", separator = \"\" } = template;\n    let prompt = \"\";\n    for (let i = 0; i < messages.length; i++) {\n      const msg = messages[i];\n      if (i > 0)\n        prompt += separator;\n      switch (msg.role) {\n        case \"system\":\n          prompt += `${systemPrefix}${msg.content}${systemSuffix}`;\n          break;\n        case \"user\":\n          prompt += `${userPrefix}${msg.content}${userSuffix}`;\n          break;\n        case \"assistant\":\n          prompt += `${assistantPrefix}${msg.content}${assistantSuffix}`;\n          break;\n      }\n    }\n    prompt += assistantPrefix;\n    return prompt;\n  }\n  /**\n   * Chat with the model\n   *\n   * @example\n   * ```typescript\n   * const generator = await pipeline('text-generation', 'model');\n   *\n   * // Single turn\n   * const response = await generator.chat('Hello, how are you?');\n   *\n   * // Multi-turn with history\n   * const response1 = await generator.chat('What is AI?');\n   * const response2 = await generator.chat('Can you give an example?');\n   *\n   * // With system prompt\n   * const response = await generator.chat('Hello', {\n   *   systemPrompt: 'You are a helpful assistant.',\n   * });\n   * ```\n   */\n  async chat(userMessage, options) {\n    if (options?.systemPrompt && (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== \"system\")) {\n      this.conversationHistory.unshift({\n        role: \"system\",\n        content: options.systemPrompt\n      });\n    }\n    this.conversationHistory.push({\n      role: \"user\",\n      content: userMessage\n    });\n    const prompt = this.applyChatTemplate(this.conversationHistory, options);\n    const result = await this.run(prompt, {\n      ...options,\n      stopSequences: [\n        ...options?.stopSequences ?? [],\n        \"<|im_end|>\",\n        \"<|end|>\",\n        \"<|eot_id|>\",\n        \"</s>\",\n        \"\\n\\nUser:\",\n        \"\\n\\nHuman:\"\n      ]\n    });\n    const response = Array.isArray(result) ? result[0] : result;\n    this.conversationHistory.push({\n      role: \"assistant\",\n      content: response.generatedText.trim()\n    });\n    return response;\n  }\n  /**\n   * Stream chat response\n   */\n  async *chatStream(userMessage, options) {\n    if (options?.systemPrompt && (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== \"system\")) {\n      this.conversationHistory.unshift({\n        role: \"system\",\n        content: options.systemPrompt\n      });\n    }\n    this.conversationHistory.push({\n      role: \"user\",\n      content: userMessage\n    });\n    const prompt = this.applyChatTemplate(this.conversationHistory, options);\n    let fullResponse = \"\";\n    for await (const event of this.stream(prompt, {\n      ...options,\n      stopSequences: [\n        ...options?.stopSequences ?? [],\n        \"<|im_end|>\",\n        \"<|end|>\",\n        \"<|eot_id|>\",\n        \"</s>\"\n      ]\n    })) {\n      fullResponse = event.generatedText;\n      yield event;\n    }\n    this.conversationHistory.push({\n      role: \"assistant\",\n      content: fullResponse.trim()\n    });\n  }\n  /**\n   * Get conversation history\n   */\n  getConversationHistory() {\n    return [...this.conversationHistory];\n  }\n  /**\n   * Set conversation history\n   */\n  setConversationHistory(messages) {\n    this.conversationHistory = [...messages];\n  }\n  /**\n   * Clear conversation history\n   */\n  clearConversation() {\n    this.conversationHistory = [];\n  }\n  /**\n   * Remove last exchange (user message + assistant response)\n   */\n  undoLastExchange() {\n    if (this.conversationHistory.length > 0 && this.conversationHistory[this.conversationHistory.length - 1]?.role === \"assistant\") {\n      this.conversationHistory.pop();\n    }\n    if (this.conversationHistory.length > 0 && this.conversationHistory[this.conversationHistory.length - 1]?.role === \"user\") {\n      this.conversationHistory.pop();\n    }\n  }\n};\nfunction createTextGenerationPipeline(config) {\n  return new TextGenerationPipeline(config);\n}\n\n// dist/pipelines/object-detection.js\ninit_tensor();\ninit_model_loader();\nvar DEFAULT_MODELS4 = {\n  model: \"https://huggingface.co/Xenova/yolos-tiny/resolve/main/onnx/model_quantized.onnx\"\n};\nvar COCO_LABELS = [\n  \"person\",\n  \"bicycle\",\n  \"car\",\n  \"motorcycle\",\n  \"airplane\",\n  \"bus\",\n  \"train\",\n  \"truck\",\n  \"boat\",\n  \"traffic light\",\n  \"fire hydrant\",\n  \"stop sign\",\n  \"parking meter\",\n  \"bench\",\n  \"bird\",\n  \"cat\",\n  \"dog\",\n  \"horse\",\n  \"sheep\",\n  \"cow\",\n  \"elephant\",\n  \"bear\",\n  \"zebra\",\n  \"giraffe\",\n  \"backpack\",\n  \"umbrella\",\n  \"handbag\",\n  \"tie\",\n  \"suitcase\",\n  \"frisbee\",\n  \"skis\",\n  \"snowboard\",\n  \"sports ball\",\n  \"kite\",\n  \"baseball bat\",\n  \"baseball glove\",\n  \"skateboard\",\n  \"surfboard\",\n  \"tennis racket\",\n  \"bottle\",\n  \"wine glass\",\n  \"cup\",\n  \"fork\",\n  \"knife\",\n  \"spoon\",\n  \"bowl\",\n  \"banana\",\n  \"apple\",\n  \"sandwich\",\n  \"orange\",\n  \"broccoli\",\n  \"carrot\",\n  \"hot dog\",\n  \"pizza\",\n  \"donut\",\n  \"cake\",\n  \"chair\",\n  \"couch\",\n  \"potted plant\",\n  \"bed\",\n  \"dining table\",\n  \"toilet\",\n  \"tv\",\n  \"laptop\",\n  \"mouse\",\n  \"remote\",\n  \"keyboard\",\n  \"cell phone\",\n  \"microwave\",\n  \"oven\",\n  \"toaster\",\n  \"sink\",\n  \"refrigerator\",\n  \"book\",\n  \"clock\",\n  \"vase\",\n  \"scissors\",\n  \"teddy bear\",\n  \"hair drier\",\n  \"toothbrush\"\n];\nvar ObjectDetectionPipeline = class extends BasePipeline {\n  constructor(config, labels) {\n    super(config ?? {\n      task: \"object-detection\",\n      model: \"default\"\n    });\n    __publicField(this, \"preprocessor\");\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"labels\");\n    __publicField(this, \"modelUrl\");\n    this.labels = labels ?? COCO_LABELS;\n    this.modelUrl = config?.model && config.model !== \"default\" ? config.model : DEFAULT_MODELS4.model;\n    this.preprocessor = new ImagePreprocessor({\n      width: 640,\n      height: 640,\n      mean: [0.485, 0.456, 0.406],\n      std: [0.229, 0.224, 0.225],\n      channelFormat: \"CHW\"\n    });\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  setLabels(labels) {\n    this.labels = labels;\n  }\n  async run(input, options) {\n    await this.initialize();\n    const tensorInputs = await this.preprocess(input);\n    const outputs = await this.runModelInference(tensorInputs);\n    return this.postprocess(outputs, options);\n  }\n  async preprocess(input) {\n    const inputs = Array.isArray(input) ? input : [input];\n    if (inputs.length === 1) {\n      const tensor2 = await this.preprocessor.process(inputs[0]);\n      return [new EdgeFlowTensor(tensor2.toFloat32Array(), [1, ...tensor2.shape], \"float32\")];\n    }\n    return [await this.preprocessor.processBatch(inputs)];\n  }\n  async runModelInference(inputs) {\n    const outputs = await runInference(this.onnxModel, inputs);\n    return outputs;\n  }\n  async postprocess(outputs, options) {\n    const opts = options ?? {};\n    const threshold = opts.threshold ?? 0.5;\n    const topK = opts.topK ?? 100;\n    const nms = opts.nms ?? true;\n    const iouThreshold = opts.iouThreshold ?? 0.5;\n    if (!outputs[0]) {\n      return [];\n    }\n    const outputData = outputs[0].toFloat32Array();\n    const shape = [...outputs[0].shape];\n    const detections = this.parseDetections(outputData, shape, threshold);\n    let filtered = nms ? this.nonMaxSuppression(detections, iouThreshold) : detections;\n    filtered.sort((a, b) => b.score - a.score);\n    filtered = filtered.slice(0, topK);\n    return filtered;\n  }\n  parseDetections(data, shape, threshold) {\n    const detections = [];\n    const numBoxes = shape[1] ?? 0;\n    const boxSize = shape[2] ?? 0;\n    if (boxSize >= 5) {\n      const numClasses = boxSize - 5;\n      for (let i = 0; i < numBoxes; i++) {\n        const offset = i * boxSize;\n        const objectness = data[offset + 4] ?? 0;\n        if (objectness < threshold)\n          continue;\n        let maxClassScore = 0;\n        let maxClassIdx = 0;\n        for (let c = 0; c < numClasses; c++) {\n          const score = data[offset + 5 + c] ?? 0;\n          if (score > maxClassScore) {\n            maxClassScore = score;\n            maxClassIdx = c;\n          }\n        }\n        const confidence = objectness * maxClassScore;\n        if (confidence < threshold)\n          continue;\n        const x = data[offset] ?? 0;\n        const y = data[offset + 1] ?? 0;\n        const w = data[offset + 2] ?? 0;\n        const h = data[offset + 3] ?? 0;\n        detections.push({\n          label: this.labels[maxClassIdx] ?? `class_${maxClassIdx}`,\n          score: confidence,\n          classId: maxClassIdx,\n          box: {\n            x: Math.max(0, x - w / 2),\n            y: Math.max(0, y - h / 2),\n            width: w,\n            height: h\n          },\n          boxNormalized: {\n            x: Math.max(0, x - w / 2),\n            y: Math.max(0, y - h / 2),\n            width: w,\n            height: h\n          }\n        });\n      }\n    } else if (boxSize === 4) {\n      for (let i = 0; i < numBoxes; i++) {\n        const offset = i * boxSize;\n        const x1 = data[offset] ?? 0;\n        const y1 = data[offset + 1] ?? 0;\n        const x2 = data[offset + 2] ?? 0;\n        const y2 = data[offset + 3] ?? 0;\n        detections.push({\n          label: this.labels[0] ?? \"object\",\n          score: 1,\n          classId: 0,\n          box: {\n            x: x1,\n            y: y1,\n            width: x2 - x1,\n            height: y2 - y1\n          },\n          boxNormalized: {\n            x: x1,\n            y: y1,\n            width: x2 - x1,\n            height: y2 - y1\n          }\n        });\n      }\n    }\n    return detections;\n  }\n  nonMaxSuppression(detections, iouThreshold) {\n    if (detections.length === 0)\n      return [];\n    const sorted = [...detections].sort((a, b) => b.score - a.score);\n    const selected = [];\n    const active = new Array(sorted.length).fill(true);\n    for (let i = 0; i < sorted.length; i++) {\n      if (!active[i])\n        continue;\n      const current = sorted[i];\n      selected.push(current);\n      for (let j = i + 1; j < sorted.length; j++) {\n        if (!active[j])\n          continue;\n        const other = sorted[j];\n        if (current.classId !== other.classId)\n          continue;\n        const iou = this.computeIoU(current.box, other.box);\n        if (iou > iouThreshold) {\n          active[j] = false;\n        }\n      }\n    }\n    return selected;\n  }\n  computeIoU(a, b) {\n    const xOverlap = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));\n    const yOverlap = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));\n    const intersection = xOverlap * yOverlap;\n    const aArea = a.width * a.height;\n    const bArea = b.width * b.height;\n    const union = aArea + bArea - intersection;\n    return union > 0 ? intersection / union : 0;\n  }\n};\nregisterPipeline(\"object-detection\", (config) => new ObjectDetectionPipeline(config));\n\n// dist/pipelines/automatic-speech-recognition.js\ninit_tensor();\ninit_model_loader();\nvar DEFAULT_MODELS5 = {\n  encoder: \"https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/encoder_model_quantized.onnx\",\n  decoder: \"https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/decoder_model_merged_quantized.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/whisper-tiny/resolve/main/tokenizer.json\"\n};\nvar SOT_TOKEN = 50258;\nvar TRANSLATE_TOKEN = 50358;\nvar TRANSCRIBE_TOKEN = 50359;\nvar EOT_TOKEN = 50257;\nvar NO_TIMESTAMPS_TOKEN = 50363;\nvar EN_TOKEN = 50259;\nvar MAX_DECODER_TOKENS = 448;\nvar AutomaticSpeechRecognitionPipeline = class extends BasePipeline {\n  constructor(config) {\n    super(config ?? {\n      task: \"automatic-speech-recognition\",\n      model: \"default\"\n    });\n    __publicField(this, \"audioPreprocessor\");\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"encoderModel\", null);\n    __publicField(this, \"decoderModel\", null);\n    __publicField(this, \"encoderUrl\");\n    __publicField(this, \"decoderUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    this.encoderUrl = DEFAULT_MODELS5.encoder;\n    this.decoderUrl = DEFAULT_MODELS5.decoder;\n    this.tokenizerUrl = DEFAULT_MODELS5.tokenizer;\n    this.audioPreprocessor = new AudioPreprocessor({\n      sampleRate: 16e3,\n      nMels: 80,\n      nFft: 400,\n      hopLength: 160,\n      maxDuration: 30\n    });\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n    if (!this.encoderModel) {\n      const data = await loadModelData(this.encoderUrl, { cache: this.config.cache ?? true });\n      this.encoderModel = await loadModelFromBuffer(data);\n    }\n    if (!this.decoderModel) {\n      const data = await loadModelData(this.decoderUrl, { cache: this.config.cache ?? true });\n      this.decoderModel = await loadModelFromBuffer(data);\n    }\n  }\n  setTokenizer(tokenizer) {\n    this.tokenizer = tokenizer;\n  }\n  async run(input, options) {\n    await this.initialize();\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    const opts = options ?? {};\n    const results = [];\n    for (const audio of inputs) {\n      const result = await this.transcribeSingle(audio, opts);\n      results.push(result);\n    }\n    return isBatch ? results : results[0];\n  }\n  async transcribeSingle(audio, options) {\n    const startTime = performance.now();\n    const melTensor = await this.audioPreprocessor.process(audio);\n    const melInput = new EdgeFlowTensor(melTensor.toFloat32Array(), [1, ...melTensor.shape], \"float32\");\n    const encoderOutputs = await runInference(this.encoderModel, [melInput]);\n    const encoderHidden = encoderOutputs[0];\n    const task = options.task ?? \"transcribe\";\n    const initialTokens = this.buildInitialTokens(task, options.language);\n    const generatedTokens = await this.autoregressiveDecode(encoderHidden, initialTokens);\n    const text = this.tokenizer.decode(generatedTokens, true);\n    const result = {\n      text: text.trim(),\n      processingTime: performance.now() - startTime\n    };\n    if (options.returnTimestamps) {\n      result.chunks = this.extractTimestamps(generatedTokens, text);\n    }\n    return result;\n  }\n  buildInitialTokens(task, language) {\n    const tokens = [SOT_TOKEN];\n    tokens.push(language ? this.getLanguageToken(language) : EN_TOKEN);\n    tokens.push(task === \"translate\" ? TRANSLATE_TOKEN : TRANSCRIBE_TOKEN);\n    tokens.push(NO_TIMESTAMPS_TOKEN);\n    return tokens;\n  }\n  getLanguageToken(language) {\n    const langMap = {\n      en: 50259,\n      zh: 50260,\n      de: 50261,\n      es: 50262,\n      ru: 50263,\n      ko: 50264,\n      fr: 50265,\n      ja: 50266,\n      pt: 50267,\n      tr: 50268,\n      pl: 50269,\n      ca: 50270,\n      nl: 50271,\n      ar: 50272,\n      sv: 50273,\n      it: 50274,\n      id: 50275,\n      hi: 50276,\n      fi: 50277,\n      vi: 50278\n    };\n    return langMap[language.toLowerCase()] ?? EN_TOKEN;\n  }\n  /**\n   * Autoregressive decoder loop similar to text-generation.\n   * Feeds encoder hidden states + growing token sequence to decoder.\n   */\n  async autoregressiveDecode(encoderHidden, initialTokens) {\n    const tokens = [...initialTokens];\n    for (let step = 0; step < MAX_DECODER_TOKENS; step++) {\n      const decoderInputIds = new EdgeFlowTensor(BigInt64Array.from(tokens.map((t) => BigInt(t))), [1, tokens.length], \"int64\");\n      const namedInputs = /* @__PURE__ */ new Map();\n      namedInputs.set(\"input_ids\", decoderInputIds);\n      namedInputs.set(\"encoder_hidden_states\", encoderHidden);\n      const decoderOutputs = await runInferenceNamed(this.decoderModel, namedInputs);\n      const logits = decoderOutputs[0].toFloat32Array();\n      const vocabSize = logits.length / tokens.length;\n      const lastTokenLogits = logits.slice((tokens.length - 1) * vocabSize);\n      let bestId = 0;\n      let bestVal = lastTokenLogits[0] ?? -Infinity;\n      for (let i = 1; i < lastTokenLogits.length; i++) {\n        if ((lastTokenLogits[i] ?? -Infinity) > bestVal) {\n          bestVal = lastTokenLogits[i] ?? -Infinity;\n          bestId = i;\n        }\n      }\n      if (bestId === EOT_TOKEN)\n        break;\n      tokens.push(bestId);\n    }\n    return tokens.slice(initialTokens.length);\n  }\n  extractTimestamps(_tokenIds, text) {\n    const words = text.split(/\\s+/).filter((w) => w.length > 0);\n    const chunks = [];\n    const wordsPerSecond = 2.5;\n    let chunkText = \"\";\n    let chunkStart = 0;\n    for (let i = 0; i < words.length; i++) {\n      chunkText += (chunkText ? \" \" : \"\") + words[i];\n      if ((i + 1) % 5 === 0 || i === words.length - 1) {\n        const duration = chunkText.split(/\\s+/).length / wordsPerSecond;\n        chunks.push({\n          text: chunkText,\n          start: chunkStart,\n          end: chunkStart + duration\n        });\n        chunkStart = chunkStart + duration;\n        chunkText = \"\";\n      }\n    }\n    return chunks;\n  }\n  async processLongAudio(audio, options = {}) {\n    const chunkDuration = options.chunkDuration ?? 30;\n    const chunkOverlap = options.chunkOverlap ?? 5;\n    const rawTensor = await this.audioPreprocessor.processRaw(audio);\n    const audioData = rawTensor.toFloat32Array();\n    const sampleRate = 16e3;\n    const chunkSamples = chunkDuration * sampleRate;\n    const overlapSamples = chunkOverlap * sampleRate;\n    const stepSamples = chunkSamples - overlapSamples;\n    const chunks = [];\n    for (let start = 0; start < audioData.length; start += stepSamples) {\n      const end = Math.min(start + chunkSamples, audioData.length);\n      const chunkAudio = audioData.slice(start, end);\n      const chunkResult = await this.run(new Float32Array(chunkAudio), options);\n      if (chunkResult.chunks) {\n        const timeOffset = start / sampleRate;\n        chunkResult.chunks = chunkResult.chunks.map((c) => ({\n          ...c,\n          start: c.start + timeOffset,\n          end: c.end + timeOffset\n        }));\n      }\n      chunks.push(chunkResult);\n    }\n    const mergedText = chunks.map((c) => c.text).join(\" \");\n    const mergedChunks = chunks.flatMap((c) => c.chunks ?? []);\n    return {\n      text: mergedText,\n      chunks: mergedChunks\n    };\n  }\n  async preprocess(input) {\n    const inputs = Array.isArray(input) ? input : [input];\n    const tensors = await Promise.all(inputs.map((audio) => this.audioPreprocessor.process(audio)));\n    if (tensors.length === 1) {\n      const t = tensors[0];\n      return [new EdgeFlowTensor(t.toFloat32Array(), [1, ...t.shape], \"float32\")];\n    }\n    return tensors;\n  }\n  async postprocess(outputs, options) {\n    const opts = options ?? {};\n    const returnTimestamps = opts.returnTimestamps ?? false;\n    if (!outputs[0]) {\n      return { text: \"\" };\n    }\n    const outputData = outputs[0].toFloat32Array();\n    const shape = outputs[0].shape;\n    const text = this.decodeOutput(outputData, shape);\n    const result = { text };\n    if (returnTimestamps) {\n      result.chunks = this.extractTimestamps([], text);\n    }\n    return result;\n  }\n  decodeOutput(data, shape) {\n    const seqLen = shape[1] ?? data.length;\n    const vocabSize = shape[2] ?? 1;\n    const tokenIds = [];\n    if (vocabSize > 1) {\n      for (let i = 0; i < seqLen; i++) {\n        const offset = i * vocabSize;\n        let maxIdx = 0;\n        let maxVal = data[offset] ?? -Infinity;\n        for (let j = 1; j < vocabSize; j++) {\n          if ((data[offset + j] ?? -Infinity) > maxVal) {\n            maxVal = data[offset + j] ?? -Infinity;\n            maxIdx = j;\n          }\n        }\n        tokenIds.push(maxIdx);\n      }\n    } else {\n      for (let i = 0; i < data.length; i++) {\n        tokenIds.push(Math.round(data[i] ?? 0));\n      }\n    }\n    if (this.tokenizer) {\n      return this.tokenizer.decode(tokenIds, true);\n    }\n    return tokenIds.join(\" \");\n  }\n};\nregisterPipeline(\"automatic-speech-recognition\", (config) => new AutomaticSpeechRecognitionPipeline(config));\n\n// dist/pipelines/zero-shot-classification.js\ninit_tensor();\ninit_model_loader();\nvar DEFAULT_MODELS6 = {\n  model: \"https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/onnx/model_quantized.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/tokenizer.json\"\n};\nvar ENTAILMENT_IDX = 2;\nvar ZeroShotClassificationPipeline = class extends BasePipeline {\n  constructor(config) {\n    super(config ?? {\n      task: \"zero-shot-classification\",\n      model: \"default\"\n    });\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"hypothesisTemplate\", \"This text is about {label}.\");\n    __publicField(this, \"modelUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    this.modelUrl = config?.model && config.model !== \"default\" ? config.model : DEFAULT_MODELS6.model;\n    this.tokenizerUrl = DEFAULT_MODELS6.tokenizer;\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  setTokenizer(tokenizer) {\n    this.tokenizer = tokenizer;\n  }\n  async classify(text, candidateLabels, options) {\n    return this.run({ text, candidateLabels }, options);\n  }\n  async run(input, options) {\n    await this.initialize();\n    const { text, candidateLabels } = input;\n    const opts = options ?? {};\n    const texts = Array.isArray(text) ? text : [text];\n    const template = opts.hypothesisTemplate ?? this.hypothesisTemplate;\n    const multiLabel = opts.multiLabel ?? false;\n    const results = await Promise.all(texts.map((t) => this.classifySingle(t, candidateLabels, template, multiLabel)));\n    return Array.isArray(text) ? results : results[0];\n  }\n  async classifySingle(text, candidateLabels, template, multiLabel) {\n    const startTime = performance.now();\n    const hypotheses = candidateLabels.map((label) => template.replace(\"{label}\", label));\n    const scores = [];\n    for (const hypothesis of hypotheses) {\n      const score = await this.scoreHypothesis(text, hypothesis);\n      scores.push(score);\n    }\n    let normalizedScores;\n    if (multiLabel) {\n      normalizedScores = scores.map((s) => 1 / (1 + Math.exp(-s)));\n    } else {\n      const tensor2 = new EdgeFlowTensor(new Float32Array(scores), [scores.length], \"float32\");\n      normalizedScores = Array.from(softmax(tensor2).toFloat32Array());\n    }\n    const indexed = candidateLabels.map((label, i) => ({\n      label,\n      score: normalizedScores[i] ?? 0\n    }));\n    indexed.sort((a, b) => b.score - a.score);\n    return {\n      sequence: text,\n      labels: indexed.map((i) => i.label),\n      scores: indexed.map((i) => i.score),\n      processingTime: performance.now() - startTime\n    };\n  }\n  /**\n   * Score a single hypothesis using the real NLI ONNX model.\n   * Returns the entailment logit.\n   */\n  async scoreHypothesis(premise, hypothesis) {\n    const encoded = this.tokenizer.encode(premise, {\n      textPair: hypothesis,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      returnAttentionMask: true\n    });\n    const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\");\n    const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map((m) => BigInt(m))), [1, encoded.attentionMask.length], \"int64\");\n    const namedInputs = /* @__PURE__ */ new Map();\n    namedInputs.set(\"input_ids\", inputIds);\n    namedInputs.set(\"attention_mask\", attentionMask);\n    const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n    const logits = outputs[0].toFloat32Array();\n    return logits[ENTAILMENT_IDX] ?? 0;\n  }\n  async preprocess(input) {\n    const { text, candidateLabels } = input;\n    const firstText = Array.isArray(text) ? text[0] ?? \"\" : text;\n    const firstLabel = candidateLabels[0] ?? \"\";\n    const encoded = this.tokenizer.encode(firstText, {\n      textPair: this.hypothesisTemplate.replace(\"{label}\", firstLabel),\n      addSpecialTokens: true,\n      maxLength: 512\n    });\n    return [new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\")];\n  }\n  async postprocess(_outputs, _options) {\n    return {\n      sequence: \"\",\n      labels: [],\n      scores: []\n    };\n  }\n};\nregisterPipeline(\"zero-shot-classification\", (config) => new ZeroShotClassificationPipeline(config));\n\n// dist/pipelines/question-answering.js\ninit_tensor();\ninit_model_loader();\nvar DEFAULT_MODELS7 = {\n  model: \"https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/onnx/model_quantized.onnx\",\n  tokenizer: \"https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/tokenizer.json\"\n};\nvar QuestionAnsweringPipeline = class extends BasePipeline {\n  constructor(config) {\n    super(config ?? {\n      task: \"question-answering\",\n      model: \"default\"\n    });\n    __publicField(this, \"tokenizer\", null);\n    __publicField(this, \"onnxModel\", null);\n    __publicField(this, \"modelUrl\");\n    __publicField(this, \"tokenizerUrl\");\n    this.modelUrl = config?.model && config.model !== \"default\" ? config.model : DEFAULT_MODELS7.model;\n    this.tokenizerUrl = DEFAULT_MODELS7.tokenizer;\n  }\n  async initialize() {\n    await super.initialize();\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n  setTokenizer(tokenizer) {\n    this.tokenizer = tokenizer;\n  }\n  async run(input, options) {\n    await this.initialize();\n    const inputs = Array.isArray(input) ? input : [input];\n    const results = await Promise.all(inputs.map((i) => this.answerQuestion(i, options ?? {})));\n    return Array.isArray(input) ? results : results[0];\n  }\n  async answerQuestion(input, options) {\n    const startTime = performance.now();\n    const { question, context } = input;\n    const maxAnswerLength = options.maxAnswerLength ?? 30;\n    const encoded = this.tokenizer.encode(question, {\n      textPair: context,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      returnAttentionMask: true,\n      returnTokenTypeIds: true\n    });\n    const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\");\n    const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map((m) => BigInt(m))), [1, encoded.attentionMask.length], \"int64\");\n    const namedInputs = /* @__PURE__ */ new Map();\n    namedInputs.set(\"input_ids\", inputIds);\n    namedInputs.set(\"attention_mask\", attentionMask);\n    const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n    if (outputs.length < 2) {\n      return { answer: \"\", score: 0, start: 0, end: 0, processingTime: performance.now() - startTime };\n    }\n    const startLogits = outputs[0].toFloat32Array();\n    const endLogits = outputs[1].toFloat32Array();\n    const seqLen = startLogits.length;\n    const startProbs = softmax(new EdgeFlowTensor(new Float32Array(startLogits), [seqLen], \"float32\")).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(new Float32Array(endLogits), [seqLen], \"float32\")).toFloat32Array();\n    let bestStartIdx = 0;\n    let bestEndIdx = 0;\n    let bestScore = 0;\n    for (let s = 0; s < seqLen; s++) {\n      for (let e = s; e < Math.min(s + maxAnswerLength, seqLen); e++) {\n        const score = (startProbs[s] ?? 0) * (endProbs[e] ?? 0);\n        if (score > bestScore) {\n          bestScore = score;\n          bestStartIdx = s;\n          bestEndIdx = e;\n        }\n      }\n    }\n    const answerTokenIds = encoded.inputIds.slice(bestStartIdx, bestEndIdx + 1);\n    const answer = this.tokenizer.decode(answerTokenIds, true);\n    const charStart = this.tokenOffsetToCharOffset(context, question, encoded.inputIds, bestStartIdx);\n    const charEnd = this.tokenOffsetToCharOffset(context, question, encoded.inputIds, bestEndIdx) + 1;\n    return {\n      answer: answer || \"\",\n      score: bestScore,\n      start: charStart,\n      end: charEnd,\n      processingTime: performance.now() - startTime\n    };\n  }\n  tokenOffsetToCharOffset(context, _question, inputIds, tokenIdx) {\n    const decoded = this.tokenizer.decode(inputIds.slice(0, tokenIdx + 1), true);\n    const contextStart = context.indexOf(decoded.trim().split(\" \").pop() ?? \"\");\n    return contextStart >= 0 ? contextStart : 0;\n  }\n  async preprocess(input) {\n    const qaInput = Array.isArray(input) ? input[0] : input;\n    const encoded = this.tokenizer.encode(qaInput.question, {\n      textPair: qaInput.context,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      returnAttentionMask: true,\n      returnTokenTypeIds: true\n    });\n    return [\n      new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map((id) => BigInt(id))), [1, encoded.inputIds.length], \"int64\"),\n      new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map((m) => BigInt(m))), [1, encoded.attentionMask.length], \"int64\")\n    ];\n  }\n  async postprocess(outputs, _options) {\n    if (outputs.length < 2) {\n      return { answer: \"\", score: 0, start: 0, end: 0 };\n    }\n    const startLogits = outputs[0].toFloat32Array();\n    const endLogits = outputs[1].toFloat32Array();\n    const seqLen = startLogits.length;\n    const startProbs = softmax(new EdgeFlowTensor(startLogits, [seqLen], \"float32\")).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(endLogits, [seqLen], \"float32\")).toFloat32Array();\n    let bestStart = 0;\n    let bestEnd = 0;\n    let bestScore = 0;\n    for (let start = 0; start < seqLen; start++) {\n      for (let end = start; end < Math.min(start + 30, seqLen); end++) {\n        const score = (startProbs[start] ?? 0) * (endProbs[end] ?? 0);\n        if (score > bestScore) {\n          bestScore = score;\n          bestStart = start;\n          bestEnd = end;\n        }\n      }\n    }\n    return {\n      answer: \"\",\n      score: bestScore,\n      start: bestStart,\n      end: bestEnd\n    };\n  }\n};\nregisterPipeline(\"question-answering\", (config) => new QuestionAnsweringPipeline(config));\n\n// dist/pipelines/image-segmentation.js\ninit_tensor();\nvar DEFAULT_SAM_MODELS = {\n  encoder: \"https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/vision_encoder_quantized.onnx\",\n  decoder: \"https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/prompt_encoder_mask_decoder_quantized.onnx\"\n};\nvar ImageSegmentationPipeline = class extends BasePipeline {\n  constructor(config) {\n    super(config);\n    __publicField(this, \"encoderModel\", null);\n    __publicField(this, \"decoderModel\", null);\n    __publicField(this, \"imageEmbedding\", null);\n    __publicField(this, \"imagePositionalEmbedding\", null);\n    __publicField(this, \"currentImageSize\", null);\n    __publicField(this, \"resizedImageSize\", null);\n    __publicField(this, \"inputSize\", 1024);\n    // SAM default input size\n    __publicField(this, \"modelsLoaded\", false);\n    // Custom model URLs\n    __publicField(this, \"encoderUrl\");\n    __publicField(this, \"decoderUrl\");\n    this.encoderUrl = DEFAULT_SAM_MODELS.encoder;\n    this.decoderUrl = DEFAULT_SAM_MODELS.decoder;\n  }\n  /**\n   * Check if models are loaded\n   */\n  get isModelsLoaded() {\n    return this.modelsLoaded;\n  }\n  /**\n   * Set custom model URLs\n   */\n  setModelUrls(encoder, decoder) {\n    this.encoderUrl = encoder;\n    this.decoderUrl = decoder;\n  }\n  /**\n   * Load both encoder and decoder models with progress callback\n   */\n  async loadModels(onProgress) {\n    if (this.modelsLoaded)\n      return;\n    onProgress?.({ model: \"encoder\", loaded: 0, total: 100, progress: 0 });\n    const encoderData = await this.fetchModelWithProgress(this.encoderUrl, (loaded, total) => {\n      onProgress?.({\n        model: \"encoder\",\n        loaded,\n        total,\n        progress: Math.round(loaded / total * 100)\n      });\n    });\n    this.encoderModel = await loadModelFromBuffer(encoderData, {\n      runtime: \"wasm\"\n      // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n    onProgress?.({ model: \"decoder\", loaded: 0, total: 100, progress: 0 });\n    const decoderData = await this.fetchModelWithProgress(this.decoderUrl, (loaded, total) => {\n      onProgress?.({\n        model: \"decoder\",\n        loaded,\n        total,\n        progress: Math.round(loaded / total * 100)\n      });\n    });\n    this.decoderModel = await loadModelFromBuffer(decoderData, {\n      runtime: \"wasm\"\n      // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n    this.modelsLoaded = true;\n  }\n  /**\n   * Fetch model with progress tracking\n   */\n  async fetchModelWithProgress(url, onProgress) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n    }\n    const contentLength = response.headers.get(\"content-length\");\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n    if (!response.body) {\n      const buffer2 = await response.arrayBuffer();\n      onProgress(buffer2.byteLength, buffer2.byteLength);\n      return buffer2;\n    }\n    const reader = response.body.getReader();\n    const chunks = [];\n    let loaded = 0;\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done)\n        break;\n      chunks.push(value);\n      loaded += value.length;\n      onProgress(loaded, total || loaded);\n    }\n    const buffer = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      buffer.set(chunk, offset);\n      offset += chunk.length;\n    }\n    return buffer.buffer;\n  }\n  /**\n   * Initialize pipeline (override to skip default model loading)\n   */\n  async initialize() {\n    if (this.isReady)\n      return;\n    this.isReady = true;\n  }\n  /**\n   * Load encoder model (processes the image once)\n   */\n  async loadEncoder(modelUrl) {\n    this.encoderModel = await loadModel(modelUrl, {\n      runtime: \"wasm\"\n    });\n  }\n  /**\n   * Load decoder model (processes prompts to generate masks)\n   */\n  async loadDecoder(modelUrl) {\n    this.decoderModel = await loadModel(modelUrl, {\n      runtime: \"wasm\"\n    });\n  }\n  /**\n   * Set and encode the image (call once per image)\n   */\n  async setImage(image) {\n    if (!this.modelsLoaded) {\n      throw new Error(\"Models not loaded. Call loadModels() first.\");\n    }\n    const imageData = await this.loadImage(image);\n    this.currentImageSize = {\n      width: imageData.width,\n      height: imageData.height\n    };\n    const { tensor: inputTensor, resizedSize } = this.preprocessImage(imageData);\n    this.resizedImageSize = resizedSize;\n    if (this.encoderModel) {\n      const outputs = await runInference(this.encoderModel, [inputTensor]);\n      this.imageEmbedding = outputs[0];\n      this.imagePositionalEmbedding = outputs[1];\n      console.log(\"[SAM] Encoder outputs:\", outputs.length);\n      console.log(\"[SAM] image_embeddings shape:\", this.imageEmbedding.shape);\n      if (this.imagePositionalEmbedding) {\n        console.log(\"[SAM] image_positional_embeddings shape:\", this.imagePositionalEmbedding.shape);\n      }\n    } else {\n      throw new Error(\"Encoder model not loaded\");\n    }\n  }\n  /**\n   * Segment the image with given prompts\n   */\n  async segment(options = {}) {\n    if (!this.imageEmbedding || !this.currentImageSize || !this.resizedImageSize) {\n      throw new Error(\"No image set. Call setImage() first.\");\n    }\n    if (!this.decoderModel) {\n      throw new Error(\"Decoder model not loaded\");\n    }\n    const startTime = performance.now();\n    const { points = [], boxes = [], maskThreshold = 0, returnAllMasks = false } = options;\n    const decoderInputs = this.prepareDecoderInputs(points, boxes);\n    decoderInputs.set(\"image_embeddings\", this.imageEmbedding);\n    if (this.imagePositionalEmbedding) {\n      decoderInputs.set(\"image_positional_embeddings\", this.imagePositionalEmbedding);\n    } else {\n      throw new Error(\"image_positional_embeddings not available from encoder\");\n    }\n    const outputs = await runInferenceNamed(this.decoderModel, decoderInputs);\n    const masks = outputs[0];\n    const scores = outputs[1];\n    const result = this.postprocessMasks(masks, scores, maskThreshold, returnAllMasks);\n    result.processingTime = performance.now() - startTime;\n    return result;\n  }\n  /**\n   * Run segmentation (implements BasePipeline interface)\n   */\n  async run(input, options) {\n    await this.setImage(input);\n    return this.segment(options);\n  }\n  /**\n   * Load image from various sources\n   */\n  async loadImage(input) {\n    if (typeof input === \"string\") {\n      return this.loadImageFromUrl(input);\n    } else if (input instanceof HTMLImageElement) {\n      return this.imageElementToImageData(input);\n    } else if (input instanceof HTMLCanvasElement) {\n      return this.canvasToImageData(input);\n    } else if (input instanceof ImageData) {\n      return input;\n    } else if (typeof ImageBitmap !== \"undefined\" && input instanceof ImageBitmap) {\n      return this.imageBitmapToImageData(input);\n    }\n    throw new Error(\"Unsupported image input type\");\n  }\n  /**\n   * Load image from URL\n   */\n  async loadImageFromUrl(url) {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n      img.onload = () => {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = img.width;\n        canvas.height = img.height;\n        const ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, 0, 0);\n        resolve(ctx.getImageData(0, 0, img.width, img.height));\n      };\n      img.onerror = reject;\n      img.src = url;\n    });\n  }\n  /**\n   * Convert HTMLImageElement to ImageData\n   */\n  imageElementToImageData(img) {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = img.naturalWidth || img.width;\n    canvas.height = img.naturalHeight || img.height;\n    const ctx = canvas.getContext(\"2d\");\n    ctx.drawImage(img, 0, 0);\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n  /**\n   * Convert canvas to ImageData\n   */\n  canvasToImageData(canvas) {\n    const ctx = canvas.getContext(\"2d\");\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n  /**\n   * Convert ImageBitmap to ImageData\n   */\n  imageBitmapToImageData(bitmap) {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = bitmap.width;\n    canvas.height = bitmap.height;\n    const ctx = canvas.getContext(\"2d\");\n    ctx.drawImage(bitmap, 0, 0);\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n  /**\n   * Preprocess image for SAM\n   */\n  preprocessImage(imageData) {\n    const { width, height } = imageData;\n    const scale = this.inputSize / Math.max(width, height);\n    const newWidth = Math.round(width * scale);\n    const newHeight = Math.round(height * scale);\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = this.inputSize;\n    canvas.height = this.inputSize;\n    const ctx = canvas.getContext(\"2d\");\n    ctx.fillStyle = `rgb(123.675, 116.28, 103.53)`;\n    ctx.fillRect(0, 0, this.inputSize, this.inputSize);\n    const tempCanvas = document.createElement(\"canvas\");\n    tempCanvas.width = width;\n    tempCanvas.height = height;\n    const tempCtx = tempCanvas.getContext(\"2d\");\n    tempCtx.putImageData(imageData, 0, 0);\n    ctx.drawImage(tempCanvas, 0, 0, newWidth, newHeight);\n    const resizedData = ctx.getImageData(0, 0, this.inputSize, this.inputSize);\n    const tensorData = new Float32Array(3 * this.inputSize * this.inputSize);\n    const mean2 = [123.675, 116.28, 103.53];\n    const std = [58.395, 57.12, 57.375];\n    for (let i = 0; i < this.inputSize * this.inputSize; i++) {\n      const pixelIdx = i * 4;\n      tensorData[i] = (resizedData.data[pixelIdx] - mean2[0]) / std[0];\n      tensorData[this.inputSize * this.inputSize + i] = (resizedData.data[pixelIdx + 1] - mean2[1]) / std[1];\n      tensorData[2 * this.inputSize * this.inputSize + i] = (resizedData.data[pixelIdx + 2] - mean2[2]) / std[2];\n    }\n    return {\n      tensor: new EdgeFlowTensor(tensorData, [1, 3, this.inputSize, this.inputSize], \"float32\"),\n      resizedSize: { width: newWidth, height: newHeight }\n    };\n  }\n  /**\n   * Prepare decoder inputs (prompts) for SlimSAM\n   *\n   * SlimSAM prompt_encoder_mask_decoder expects these named inputs:\n   * - image_embeddings: [1, 256, 64, 64]\n   * - point_coords: [batch, num_points, 2]\n   * - point_labels: [batch, num_points]\n   * - mask_input: [batch, 1, 256, 256]\n   * - has_mask_input: [batch, 1]\n   * - orig_im_size: [2]\n   * - position_ids: [batch, num_points]\n   */\n  prepareDecoderInputs(points, boxes) {\n    const { width: resizedW, height: resizedH } = this.resizedImageSize;\n    const scaleX = resizedW;\n    const scaleY = resizedH;\n    const allPoints = [];\n    const allLabels = [];\n    for (const point of points) {\n      allPoints.push(point.x * scaleX, point.y * scaleY);\n      allLabels.push(point.label);\n    }\n    for (const box of boxes) {\n      allPoints.push(box.x1 * scaleX, box.y1 * scaleY);\n      allLabels.push(2);\n      allPoints.push(box.x2 * scaleX, box.y2 * scaleY);\n      allLabels.push(3);\n    }\n    if (allPoints.length === 0) {\n      allPoints.push(resizedW / 2, resizedH / 2);\n      allLabels.push(1);\n    }\n    const numPoints = allLabels.length;\n    const inputs = /* @__PURE__ */ new Map();\n    inputs.set(\"input_points\", new EdgeFlowTensor(new Float32Array(allPoints), [1, 1, numPoints, 2], \"float32\"));\n    inputs.set(\"input_labels\", new EdgeFlowTensor(BigInt64Array.from(allLabels.map((l) => BigInt(l))), [1, 1, numPoints], \"int64\"));\n    return inputs;\n  }\n  /**\n   * Post-process masks from decoder output\n   */\n  postprocessMasks(masks, scores, threshold, returnAllMasks) {\n    const { width, height } = this.currentImageSize;\n    const scoresData = scores.toFloat32Array();\n    const masksData = masks.toFloat32Array();\n    const numMasks = scoresData.length;\n    const maskShape = masks.shape;\n    const maskH = maskShape[2] ?? height;\n    const maskW = maskShape[3] ?? width;\n    let bestIdx = 0;\n    let bestScore = scoresData[0] ?? 0;\n    for (let i = 1; i < numMasks; i++) {\n      if ((scoresData[i] ?? 0) > bestScore) {\n        bestScore = scoresData[i] ?? 0;\n        bestIdx = i;\n      }\n    }\n    const outputMask = this.resizeMask(masksData, bestIdx, maskW, maskH, width, height, threshold);\n    const result = {\n      mask: outputMask,\n      width,\n      height,\n      score: bestScore\n    };\n    if (returnAllMasks && numMasks > 1) {\n      result.allMasks = [];\n      for (let m = 0; m < numMasks; m++) {\n        const mask = this.resizeMask(masksData, m, maskW, maskH, width, height, threshold);\n        result.allMasks.push({\n          mask,\n          score: scoresData[m] ?? 0\n        });\n      }\n    }\n    return result;\n  }\n  /**\n   * Resize mask from model output size to original image size\n   */\n  resizeMask(masksData, maskIdx, srcW, srcH, dstW, dstH, threshold) {\n    const outputMask = new Uint8Array(dstW * dstH);\n    const maskOffset = maskIdx * srcW * srcH;\n    for (let y = 0; y < dstH; y++) {\n      for (let x = 0; x < dstW; x++) {\n        const srcX = x / dstW * srcW;\n        const srcY = y / dstH * srcH;\n        const x0 = Math.floor(srcX);\n        const x1 = Math.min(x0 + 1, srcW - 1);\n        const y0 = Math.floor(srcY);\n        const y1 = Math.min(y0 + 1, srcH - 1);\n        const xFrac = srcX - x0;\n        const yFrac = srcY - y0;\n        const v00 = masksData[maskOffset + y0 * srcW + x0] ?? 0;\n        const v01 = masksData[maskOffset + y0 * srcW + x1] ?? 0;\n        const v10 = masksData[maskOffset + y1 * srcW + x0] ?? 0;\n        const v11 = masksData[maskOffset + y1 * srcW + x1] ?? 0;\n        const value = v00 * (1 - xFrac) * (1 - yFrac) + v01 * xFrac * (1 - yFrac) + v10 * (1 - xFrac) * yFrac + v11 * xFrac * yFrac;\n        const sigmoid2 = 1 / (1 + Math.exp(-value));\n        outputMask[y * dstW + x] = sigmoid2 > threshold ? 255 : 0;\n      }\n    }\n    return outputMask;\n  }\n  /**\n   * Clear the current image embedding\n   */\n  clearImage() {\n    this.imageEmbedding = null;\n    this.imagePositionalEmbedding = null;\n    this.currentImageSize = null;\n    this.resizedImageSize = null;\n  }\n  /**\n   * Preprocess (required by BasePipeline)\n   */\n  async preprocess(input) {\n    const imageData = await this.loadImage(input);\n    const { tensor: tensor2 } = this.preprocessImage(imageData);\n    return [tensor2];\n  }\n  /**\n   * Postprocess (required by BasePipeline)\n   */\n  async postprocess(_outputs, _options) {\n    return {\n      mask: new Uint8Array(0),\n      width: 0,\n      height: 0,\n      score: 0\n    };\n  }\n  /**\n   * Dispose resources\n   */\n  dispose() {\n    super.dispose();\n    this.encoderModel?.dispose();\n    this.decoderModel?.dispose();\n    this.imageEmbedding = null;\n    this.imagePositionalEmbedding = null;\n    this.currentImageSize = null;\n    this.resizedImageSize = null;\n    this.modelsLoaded = false;\n  }\n};\nfunction createImageSegmentationPipeline(config = {}) {\n  return new ImageSegmentationPipeline({\n    task: \"image-segmentation\",\n    model: config.model ?? \"slimsam\",\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization\n  });\n}\nregisterPipeline(\"image-segmentation\", (config) => new ImageSegmentationPipeline(config));\n\n// dist/pipelines/index.js\nasync function pipeline(task, options) {\n  registerAllBackends();\n  const config = {\n    task,\n    model: options?.model ?? \"default\",\n    runtime: options?.runtime,\n    cache: options?.cache ?? true,\n    quantization: options?.quantization\n  };\n  let pipelineInstance;\n  switch (task) {\n    case \"text-classification\":\n      pipelineInstance = new TextClassificationPipeline(config, options?.labels);\n      break;\n    case \"sentiment-analysis\":\n      pipelineInstance = new SentimentAnalysisPipeline(config);\n      break;\n    case \"feature-extraction\":\n      pipelineInstance = new FeatureExtractionPipeline(config);\n      break;\n    case \"image-classification\":\n      pipelineInstance = new ImageClassificationPipeline(config, options?.labels);\n      break;\n    case \"text-generation\":\n      pipelineInstance = new TextGenerationPipeline(config);\n      break;\n    case \"object-detection\":\n      pipelineInstance = new ObjectDetectionPipeline(config, options?.labels);\n      break;\n    case \"automatic-speech-recognition\":\n      pipelineInstance = new AutomaticSpeechRecognitionPipeline(config);\n      break;\n    case \"zero-shot-classification\":\n      pipelineInstance = new ZeroShotClassificationPipeline(config);\n      break;\n    case \"question-answering\":\n      pipelineInstance = new QuestionAnsweringPipeline(config);\n      break;\n    case \"image-segmentation\":\n      pipelineInstance = new ImageSegmentationPipeline(config);\n      break;\n    default: {\n      const pluginEntry = getPluginPipeline(task);\n      if (pluginEntry) {\n        pipelineInstance = pluginEntry.factory(config);\n        break;\n      }\n      throw new Error(`Unknown pipeline task: \"${task}\". Register a plugin with registerPlugin() to add custom pipeline tasks.`);\n    }\n  }\n  await pipelineInstance.initialize();\n  return pipelineInstance;\n}\nasync function createPipelines(tasks, options) {\n  const pipelines = await Promise.all(tasks.map((task) => pipeline(task, options)));\n  const result = {};\n  for (let i = 0; i < tasks.length; i++) {\n    const task = tasks[i];\n    result[task] = pipelines[i];\n  }\n  return result;\n}\n\n// dist/core/composer.js\nfunction compose(stages) {\n  if (stages.length === 0) {\n    throw new Error(\"[edgeFlow.js] compose() requires at least one stage\");\n  }\n  let pipelineInstances = null;\n  async function ensureInitialised() {\n    if (pipelineInstances)\n      return pipelineInstances;\n    pipelineInstances = await Promise.all(stages.map((stage) => pipeline(stage.task, {\n      model: stage.model,\n      ...stage.options\n    })));\n    return pipelineInstances;\n  }\n  return {\n    get length() {\n      return stages.length;\n    },\n    async run(input) {\n      const instances = await ensureInitialised();\n      const stageResults = [];\n      const stageTimes = [];\n      let current = input;\n      const wallStart = performance.now();\n      for (let i = 0; i < stages.length; i++) {\n        const stage = stages[i];\n        const inst = instances[i];\n        if (stage.transform) {\n          current = stage.transform(current);\n        }\n        const t0 = performance.now();\n        current = await inst.run(current, stage.runOptions);\n        stageTimes.push(performance.now() - t0);\n        stageResults.push(current);\n      }\n      return {\n        output: current,\n        stages: stageResults,\n        totalTime: performance.now() - wallStart,\n        stageTimes\n      };\n    },\n    dispose() {\n      if (pipelineInstances) {\n        for (const inst of pipelineInstances) {\n          if (inst && typeof inst.dispose === \"function\") {\n            inst.dispose();\n          }\n        }\n        pipelineInstances = null;\n      }\n    }\n  };\n}\nfunction parallel(stages) {\n  if (stages.length === 0) {\n    throw new Error(\"[edgeFlow.js] parallel() requires at least one stage\");\n  }\n  let pipelineInstances = null;\n  async function ensureInitialised() {\n    if (pipelineInstances)\n      return pipelineInstances;\n    pipelineInstances = await Promise.all(stages.map((s) => pipeline(s.task, {\n      model: s.model,\n      ...s.options\n    })));\n    return pipelineInstances;\n  }\n  return {\n    async run(input) {\n      const instances = await ensureInitialised();\n      const t0 = performance.now();\n      const outputs = await Promise.all(stages.map((stage, i) => {\n        const stageInput = stage.transform ? stage.transform(input) : input;\n        return instances[i].run(stageInput, stage.runOptions);\n      }));\n      return { outputs, totalTime: performance.now() - t0 };\n    },\n    dispose() {\n      if (pipelineInstances) {\n        for (const inst of pipelineInstances) {\n          if (inst && typeof inst.dispose === \"function\") {\n            inst.dispose();\n          }\n        }\n        pipelineInstances = null;\n      }\n    }\n  };\n}\n\n// dist/utils/index.js\ninit_model_loader();\n\n// dist/utils/hub.js\ninit_model_loader();\ninit_types();\nvar DEFAULT_ENDPOINT = \"https://huggingface.co\";\nvar DEFAULT_REVISION = \"main\";\nvar ONNX_MODEL_FILES = [\n  \"model.onnx\",\n  \"model_quantized.onnx\",\n  \"model_int8.onnx\",\n  \"model_uint8.onnx\",\n  \"model_fp16.onnx\",\n  \"onnx/model.onnx\",\n  \"onnx/model_quantized.onnx\"\n];\nfunction buildFileUrl(modelId, filename, options = {}) {\n  const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;\n  const revision = options.revision ?? DEFAULT_REVISION;\n  const subfolder = options.subfolder ? `${options.subfolder}/` : \"\";\n  return `${endpoint}/${modelId}/resolve/${revision}/${subfolder}${filename}`;\n}\nasync function fetchWithAuth(url, token) {\n  const headers = {};\n  if (token) {\n    headers[\"Authorization\"] = `Bearer ${token}`;\n  }\n  const response = await fetch(url, { headers });\n  return response;\n}\nasync function fileExists(modelId, filename, options = {}) {\n  const url = buildFileUrl(modelId, filename, options);\n  try {\n    const response = await fetchWithAuth(url, options.token);\n    return response.ok || response.status === 302;\n  } catch {\n    return false;\n  }\n}\nasync function findOnnxModel(modelId, options = {}) {\n  for (const filename of ONNX_MODEL_FILES) {\n    if (await fileExists(modelId, filename, options)) {\n      return filename;\n    }\n  }\n  return null;\n}\nasync function downloadFile(modelId, filename, options = {}) {\n  const url = buildFileUrl(modelId, filename, options);\n  return loadModelData(url, {\n    cache: options.cache ?? true,\n    forceDownload: options.forceDownload ?? false,\n    onProgress: options.onProgress ? (progress) => {\n      options.onProgress({\n        file: filename,\n        fileIndex: 1,\n        totalFiles: 1,\n        fileProgress: progress,\n        overallProgress: progress.percent\n      });\n    } : void 0\n  });\n}\nasync function downloadJson(modelId, filename, options = {}) {\n  const url = buildFileUrl(modelId, filename, options);\n  if (options.cache !== false && !options.forceDownload) {\n    const cached = await isModelCached(url);\n    if (cached) {\n      const data = await loadModelData(url, { cache: true });\n      const text = new TextDecoder().decode(data);\n      return JSON.parse(text);\n    }\n  }\n  const response = await fetchWithAuth(url, options.token);\n  if (!response.ok) {\n    throw new EdgeFlowError(`Failed to download ${filename} from ${modelId}: ${response.status}`, ErrorCodes.MODEL_NOT_FOUND);\n  }\n  return response.json();\n}\nasync function downloadTokenizer(modelId, options = {}) {\n  const url = buildFileUrl(modelId, \"tokenizer.json\", options);\n  return Tokenizer.fromUrl(url);\n}\nasync function downloadConfig(modelId, options = {}) {\n  return downloadJson(modelId, \"config.json\", options);\n}\nasync function downloadModel(modelId, options = {}) {\n  const files = {};\n  const totalSteps = 3;\n  let currentStep = 0;\n  const reportProgress = (file, progress) => {\n    if (options.onProgress) {\n      const baseProgress = currentStep / totalSteps * 100;\n      const stepProgress = progress.percent / totalSteps;\n      options.onProgress({\n        file,\n        fileIndex: currentStep + 1,\n        totalFiles: totalSteps,\n        fileProgress: progress,\n        overallProgress: baseProgress + stepProgress\n      });\n    }\n  };\n  console.log(`\\u{1F50D} Finding ONNX model in ${modelId}...`);\n  const modelFile = await findOnnxModel(modelId, options);\n  if (!modelFile) {\n    throw new EdgeFlowError(`No ONNX model found in ${modelId}. Please ensure the model has an ONNX file.`, ErrorCodes.MODEL_NOT_FOUND, { modelId, triedFiles: ONNX_MODEL_FILES });\n  }\n  files.model = modelFile;\n  console.log(`\\u{1F4E6} Downloading model: ${modelFile}`);\n  const modelData = await downloadFile(modelId, modelFile, {\n    ...options,\n    onProgress: (p) => reportProgress(modelFile, p.fileProgress)\n  });\n  currentStep = 1;\n  let tokenizer;\n  try {\n    console.log(`\\u{1F4DD} Downloading tokenizer...`);\n    files.tokenizer = \"tokenizer.json\";\n    tokenizer = await downloadTokenizer(modelId, options);\n    console.log(`\\u2713 Tokenizer loaded`);\n  } catch (error) {\n    console.warn(`\\u26A0\\uFE0F No tokenizer found for ${modelId}`);\n  }\n  currentStep = 2;\n  let config;\n  try {\n    console.log(`\\u2699\\uFE0F Downloading config...`);\n    files.config = \"config.json\";\n    config = await downloadConfig(modelId, options);\n    console.log(`\\u2713 Config loaded`);\n  } catch (error) {\n    console.warn(`\\u26A0\\uFE0F No config found for ${modelId}`);\n  }\n  currentStep = 3;\n  if (options.onProgress) {\n    options.onProgress({\n      file: \"complete\",\n      fileIndex: totalSteps,\n      totalFiles: totalSteps,\n      fileProgress: { loaded: 1, total: 1, percent: 100, speed: 0, eta: 0 },\n      overallProgress: 100\n    });\n  }\n  console.log(`\\u2705 Model bundle downloaded: ${modelId}`);\n  return {\n    modelId,\n    modelData,\n    tokenizer,\n    config,\n    files\n  };\n}\nasync function fromHub(modelId, options = {}) {\n  return downloadModel(modelId, options);\n}\nasync function modelExists(modelId, options = {}) {\n  try {\n    const modelFile = await findOnnxModel(modelId, options);\n    return modelFile !== null;\n  } catch {\n    return false;\n  }\n}\nasync function getModelInfo(modelId, options = {}) {\n  const [onnxFile, hasTokenizer, config] = await Promise.all([\n    findOnnxModel(modelId, options),\n    fileExists(modelId, \"tokenizer.json\", options),\n    downloadConfig(modelId, options).catch(() => void 0)\n  ]);\n  return {\n    hasOnnx: onnxFile !== null,\n    onnxFile: onnxFile ?? void 0,\n    hasTokenizer,\n    hasConfig: config !== void 0,\n    config\n  };\n}\nvar POPULAR_MODELS = {\n  // Text Classification / Sentiment\n  \"sentiment-analysis\": \"Xenova/distilbert-base-uncased-finetuned-sst-2-english\",\n  \"text-classification\": \"Xenova/distilbert-base-uncased-finetuned-sst-2-english\",\n  // Feature Extraction\n  \"feature-extraction\": \"Xenova/all-MiniLM-L6-v2\",\n  \"sentence-similarity\": \"Xenova/all-MiniLM-L6-v2\",\n  // Question Answering\n  \"question-answering\": \"Xenova/distilbert-base-cased-distilled-squad\",\n  // Token Classification\n  \"ner\": \"Xenova/bert-base-NER\",\n  \"token-classification\": \"Xenova/bert-base-NER\",\n  // Text Generation\n  \"text-generation\": \"Xenova/gpt2\",\n  // Translation\n  \"translation-en-fr\": \"Xenova/t5-small\",\n  \"translation-en-de\": \"Xenova/t5-small\",\n  // Summarization\n  \"summarization\": \"Xenova/distilbart-cnn-6-6\",\n  // Fill Mask\n  \"fill-mask\": \"Xenova/bert-base-uncased\",\n  // Image Classification\n  \"image-classification\": \"Xenova/vit-base-patch16-224\",\n  // Object Detection\n  \"object-detection\": \"Xenova/detr-resnet-50\",\n  // Image Segmentation\n  \"image-segmentation\": \"Xenova/segformer-b0-finetuned-ade-512-512\",\n  // Zero-shot Classification\n  \"zero-shot-classification\": \"Xenova/mobilebert-uncased-mnli\",\n  // Speech Recognition\n  \"automatic-speech-recognition\": \"Xenova/whisper-tiny.en\",\n  // Text-to-Speech\n  \"text-to-speech\": \"Xenova/speecht5_tts\"\n};\nfunction getDefaultModel(task) {\n  return POPULAR_MODELS[task];\n}\nasync function fromTask(task, options = {}) {\n  const modelId = getDefaultModel(task);\n  return downloadModel(modelId, options);\n}\n\n// dist/tools/benchmark.js\nasync function benchmark(fn, options = {}) {\n  const { warmupRuns = 3, runs = 10, verbose = false, timeout = 3e4, name = \"benchmark\" } = options;\n  const times = [];\n  let failedRuns = 0;\n  if (verbose)\n    console.log(`[${name}] Running ${warmupRuns} warmup iterations...`);\n  for (let i = 0; i < warmupRuns; i++) {\n    try {\n      await Promise.race([\n        Promise.resolve(fn()),\n        new Promise((_, reject) => setTimeout(() => reject(new Error(\"Timeout\")), timeout))\n      ]);\n    } catch {\n    }\n  }\n  if (verbose)\n    console.log(`[${name}] Running ${runs} measured iterations...`);\n  for (let i = 0; i < runs; i++) {\n    try {\n      const start = performance.now();\n      await Promise.race([\n        Promise.resolve(fn()),\n        new Promise((_, reject) => setTimeout(() => reject(new Error(\"Timeout\")), timeout))\n      ]);\n      const end = performance.now();\n      times.push(end - start);\n      if (verbose)\n        console.log(`  Run ${i + 1}: ${(end - start).toFixed(2)}ms`);\n    } catch (error) {\n      failedRuns++;\n      if (verbose)\n        console.log(`  Run ${i + 1}: FAILED - ${error}`);\n    }\n  }\n  if (times.length === 0) {\n    throw new Error(`All ${runs} runs failed`);\n  }\n  const sorted = [...times].sort((a, b) => a - b);\n  const sum2 = times.reduce((a, b) => a + b, 0);\n  const avg = sum2 / times.length;\n  const variance = times.reduce((sum3, t) => sum3 + Math.pow(t - avg, 2), 0) / times.length;\n  const stdDev = Math.sqrt(variance);\n  const result = {\n    name,\n    avgTime: avg,\n    medianTime: sorted[Math.floor(sorted.length / 2)] ?? 0,\n    minTime: sorted[0] ?? 0,\n    maxTime: sorted[sorted.length - 1] ?? 0,\n    stdDev,\n    p95: sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1] ?? 0,\n    p99: sorted[Math.floor(sorted.length * 0.99)] ?? sorted[sorted.length - 1] ?? 0,\n    throughput: 1e3 / avg,\n    times,\n    totalRuns: runs,\n    failedRuns\n  };\n  if (verbose) {\n    console.log(`\n[${name}] Results:`);\n    console.log(`  Avg: ${result.avgTime.toFixed(2)}ms`);\n    console.log(`  Median: ${result.medianTime.toFixed(2)}ms`);\n    console.log(`  Min: ${result.minTime.toFixed(2)}ms`);\n    console.log(`  Max: ${result.maxTime.toFixed(2)}ms`);\n    console.log(`  Std Dev: ${result.stdDev.toFixed(2)}ms`);\n    console.log(`  P95: ${result.p95.toFixed(2)}ms`);\n    console.log(`  Throughput: ${result.throughput.toFixed(2)} ops/sec`);\n  }\n  return result;\n}\nasync function compareBenchmarks(baseline, comparison, options = {}) {\n  const baselineResult = await benchmark(baseline, {\n    ...options,\n    name: options.name ? `${options.name} (baseline)` : \"baseline\"\n  });\n  const comparisonResult = await benchmark(comparison, {\n    ...options,\n    name: options.name ? `${options.name} (comparison)` : \"comparison\"\n  });\n  const speedup = baselineResult.avgTime / comparisonResult.avgTime;\n  const percentFaster = (baselineResult.avgTime - comparisonResult.avgTime) / baselineResult.avgTime * 100;\n  let winner;\n  if (Math.abs(percentFaster) < 5) {\n    winner = \"tie\";\n  } else if (percentFaster > 0) {\n    winner = \"comparison\";\n  } else {\n    winner = \"baseline\";\n  }\n  return {\n    baseline: baselineResult,\n    comparison: comparisonResult,\n    speedup,\n    percentFaster,\n    winner\n  };\n}\nasync function benchmarkSuite(suite, options = {}) {\n  const results = {};\n  for (const [name, fn] of Object.entries(suite)) {\n    console.log(`\n=== ${name} ===`);\n    results[name] = await benchmark(fn, { ...options, name, verbose: true });\n  }\n  return results;\n}\nfunction formatBenchmarkResult(result) {\n  return `\n\\u250C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n\\u2502 ${result.name.padEnd(39)} \\u2502\n\\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n\\u2502 Avg Time:    ${result.avgTime.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 Median:      ${result.medianTime.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 Min Time:    ${result.minTime.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 Max Time:    ${result.maxTime.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 Std Dev:     ${result.stdDev.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 P95:         ${result.p95.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 P99:         ${result.p99.toFixed(2).padStart(10)}ms             \\u2502\n\\u2502 Throughput:  ${result.throughput.toFixed(2).padStart(10)} ops/sec     \\u2502\n\\u2502 Runs:        ${result.totalRuns.toString().padStart(10)} (${result.failedRuns} failed)  \\u2502\n\\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n  `.trim();\n}\nfunction formatComparisonResult(result) {\n  const arrow = result.percentFaster > 0 ? \"\\u2191\" : result.percentFaster < 0 ? \"\\u2193\" : \"=\";\n  const winnerText = result.winner === \"comparison\" ? \"Comparison is faster!\" : result.winner === \"baseline\" ? \"Baseline is faster!\" : \"Results are similar\";\n  return `\n\\u250C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n\\u2502                  BENCHMARK COMPARISON               \\u2502\n\\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n\\u2502 Baseline:    ${result.baseline.avgTime.toFixed(2).padStart(10)}ms                       \\u2502\n\\u2502 Comparison:  ${result.comparison.avgTime.toFixed(2).padStart(10)}ms                       \\u2502\n\\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n\\u2502 Speedup:     ${result.speedup.toFixed(2).padStart(10)}x                        \\u2502\n\\u2502 Difference:  ${arrow} ${Math.abs(result.percentFaster).toFixed(1).padStart(8)}%                      \\u2502\n\\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n\\u2502 Winner: ${winnerText.padEnd(42)} \\u2502\n\\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n  `.trim();\n}\nasync function benchmarkMemory(fn, options = {}) {\n  const { name = \"memory-benchmark\", runs = 5 } = options;\n  const getMemory = () => {\n    if (typeof performance !== \"undefined\" && \"memory\" in performance) {\n      return performance.memory.usedJSHeapSize;\n    }\n    return 0;\n  };\n  const memoryReadings = [];\n  const initialMemory = getMemory();\n  for (let i = 0; i < runs; i++) {\n    await fn();\n    memoryReadings.push(getMemory());\n  }\n  const peakMemory = Math.max(...memoryReadings);\n  const avgMemory = memoryReadings.reduce((a, b) => a + b, 0) / memoryReadings.length;\n  const memoryDelta = avgMemory - initialMemory;\n  return {\n    name,\n    peakMemory,\n    avgMemory,\n    memoryDelta\n  };\n}\n\n// dist/core/index.js\ninit_types();\ninit_tensor();\n\n// dist/tools/quantization.js\nfunction calculateQuantParams(data, bits, symmetric, perChannel, channelAxis = 0, shape = []) {\n  const qmin = symmetric ? -(1 << bits - 1) : 0;\n  const qmax = symmetric ? (1 << bits - 1) - 1 : (1 << bits) - 1;\n  if (perChannel && shape.length > 1) {\n    const numChannels = shape[channelAxis] ?? 1;\n    const scales = new Float32Array(numChannels);\n    const zeroPoints = new Int32Array(numChannels);\n    const channelSize = data.length / numChannels;\n    let globalMin = Infinity;\n    let globalMax = -Infinity;\n    for (let c = 0; c < numChannels; c++) {\n      let min = Infinity;\n      let max = -Infinity;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        min = Math.min(min, val);\n        max = Math.max(max, val);\n      }\n      globalMin = Math.min(globalMin, min);\n      globalMax = Math.max(globalMax, max);\n      if (symmetric) {\n        const absMax = Math.max(Math.abs(min), Math.abs(max));\n        scales[c] = absMax / qmax;\n        zeroPoints[c] = 0;\n      } else {\n        scales[c] = (max - min) / (qmax - qmin);\n        zeroPoints[c] = Math.round(qmin - min / (scales[c] || 1));\n      }\n      if (scales[c] === 0)\n        scales[c] = 1;\n    }\n    return { scale: scales, zeroPoint: zeroPoints, min: globalMin, max: globalMax };\n  } else {\n    let min = Infinity;\n    let max = -Infinity;\n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n    let scale;\n    let zeroPoint;\n    if (symmetric) {\n      const absMax = Math.max(Math.abs(min), Math.abs(max));\n      scale = absMax / qmax;\n      zeroPoint = 0;\n    } else {\n      scale = (max - min) / (qmax - qmin);\n      zeroPoint = Math.round(qmin - min / (scale || 1));\n    }\n    if (scale === 0)\n      scale = 1;\n    return { scale, zeroPoint, min, max };\n  }\n}\nfunction quantizeToInt8(data, scale, zeroPoint, perChannel, channelSize = data.length) {\n  const result = new Int8Array(data.length);\n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = zeroPoint[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        result[idx] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n      }\n    }\n  } else {\n    const s = scale;\n    const zp = zeroPoint;\n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      result[i] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n    }\n  }\n  return result;\n}\nfunction quantizeToUint8(data, scale, zeroPoint, perChannel, channelSize = data.length) {\n  const result = new Uint8Array(data.length);\n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = zeroPoint[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        result[idx] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n      }\n    }\n  } else {\n    const s = scale;\n    const zp = zeroPoint;\n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      result[i] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n    }\n  }\n  return result;\n}\nfunction quantizeToInt4(data, scale, zeroPoint) {\n  const packedLength = Math.ceil(data.length / 2);\n  const result = new Uint8Array(packedLength);\n  for (let i = 0; i < data.length; i += 2) {\n    const val1 = data[i] ?? 0;\n    const val2 = data[i + 1] ?? 0;\n    const q1 = Math.max(0, Math.min(15, Math.round(val1 / scale + zeroPoint + 8)));\n    const q2 = Math.max(0, Math.min(15, Math.round(val2 / scale + zeroPoint + 8)));\n    result[i >> 1] = q1 << 4 | q2;\n  }\n  return result;\n}\nfunction quantizeToFloat16(data) {\n  const result = new Uint16Array(data.length);\n  for (let i = 0; i < data.length; i++) {\n    result[i] = float32ToFloat16(data[i] ?? 0);\n  }\n  return result;\n}\nfunction float32ToFloat16(value) {\n  const float32View = new Float32Array(1);\n  const int32View = new Int32Array(float32View.buffer);\n  float32View[0] = value;\n  const f = int32View[0];\n  const sign = f >> 16 & 32768;\n  const exponent = (f >> 23 & 255) - 127 + 15;\n  const mantissa = f & 8388607;\n  if (exponent <= 0) {\n    if (exponent < -10) {\n      return sign;\n    }\n    const m = (mantissa | 8388608) >> 1 - exponent;\n    return sign | m >> 13;\n  } else if (exponent >= 31) {\n    return sign | 31744;\n  }\n  return sign | exponent << 10 | mantissa >> 13;\n}\nfunction dequantizeInt8(data, scale, zeroPoint, perChannel = false, channelSize = data.length) {\n  const result = new Float32Array(data.length);\n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = zeroPoint[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        result[idx] = ((data[idx] ?? 0) - zp) * s;\n      }\n    }\n  } else {\n    const s = scale;\n    const zp = zeroPoint;\n    for (let i = 0; i < data.length; i++) {\n      result[i] = ((data[i] ?? 0) - zp) * s;\n    }\n  }\n  return result;\n}\nfunction dequantizeUint8(data, scale, zeroPoint, perChannel = false, channelSize = data.length) {\n  const result = new Float32Array(data.length);\n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = zeroPoint[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        result[idx] = ((data[idx] ?? 0) - zp) * s;\n      }\n    }\n  } else {\n    const s = scale;\n    const zp = zeroPoint;\n    for (let i = 0; i < data.length; i++) {\n      result[i] = ((data[i] ?? 0) - zp) * s;\n    }\n  }\n  return result;\n}\nfunction float16ToFloat32(value) {\n  const sign = (value & 32768) >> 15;\n  const exponent = (value & 31744) >> 10;\n  const mantissa = value & 1023;\n  if (exponent === 0) {\n    if (mantissa === 0) {\n      return sign === 0 ? 0 : -0;\n    }\n    return (sign === 0 ? 1 : -1) * Math.pow(2, -14) * (mantissa / 1024);\n  } else if (exponent === 31) {\n    if (mantissa === 0) {\n      return sign === 0 ? Infinity : -Infinity;\n    }\n    return NaN;\n  }\n  return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + mantissa / 1024);\n}\nfunction dequantizeFloat16(data) {\n  const result = new Float32Array(data.length);\n  for (let i = 0; i < data.length; i++) {\n    result[i] = float16ToFloat32(data[i] ?? 0);\n  }\n  return result;\n}\nfunction parseModelWeights(modelData) {\n  const weights = [];\n  const float32Array = new Float32Array(modelData);\n  weights.push({\n    name: \"model_weights\",\n    data: float32Array,\n    shape: [float32Array.length],\n    dtype: \"float32\"\n  });\n  return weights;\n}\nfunction serializeQuantizedModel(model) {\n  const encoder = new TextEncoder();\n  let totalSize = 20;\n  for (const weight of model.weights) {\n    const nameBytes = encoder.encode(weight.name);\n    const dtypeBytes = encoder.encode(weight.dtype);\n    const origDtypeBytes = encoder.encode(weight.originalDtype);\n    totalSize += 4 + nameBytes.length;\n    totalSize += 4 + weight.shape.length * 4;\n    totalSize += 4 + dtypeBytes.length;\n    totalSize += 4 + origDtypeBytes.length;\n    totalSize += 1;\n    if (weight.scale !== void 0) {\n      totalSize += Array.isArray(weight.scale) ? 4 + weight.scale.length * 4 : 4;\n    }\n    totalSize += 1;\n    if (weight.zeroPoint !== void 0) {\n      totalSize += Array.isArray(weight.zeroPoint) ? 4 + weight.zeroPoint.length * 4 : 4;\n    }\n    totalSize += 8 + weight.data.byteLength;\n  }\n  const buffer = new ArrayBuffer(totalSize);\n  const view = new DataView(buffer);\n  const uint8 = new Uint8Array(buffer);\n  let offset = 0;\n  view.setUint32(offset, model.version, true);\n  offset += 4;\n  view.setUint32(offset, [\"int8\", \"uint8\", \"int4\", \"float16\", \"dynamic\"].indexOf(model.quantizationType), true);\n  offset += 4;\n  view.setUint32(offset, model.originalSize & 4294967295, true);\n  offset += 4;\n  view.setUint32(offset, model.originalSize / 4294967296 >>> 0, true);\n  offset += 4;\n  view.setUint32(offset, model.weights.length, true);\n  offset += 4;\n  for (const weight of model.weights) {\n    const nameBytes = encoder.encode(weight.name);\n    const dtypeBytes = encoder.encode(weight.dtype);\n    const origDtypeBytes = encoder.encode(weight.originalDtype);\n    view.setUint32(offset, nameBytes.length, true);\n    offset += 4;\n    uint8.set(nameBytes, offset);\n    offset += nameBytes.length;\n    view.setUint32(offset, weight.shape.length, true);\n    offset += 4;\n    for (const dim of weight.shape) {\n      view.setInt32(offset, dim, true);\n      offset += 4;\n    }\n    view.setUint32(offset, dtypeBytes.length, true);\n    offset += 4;\n    uint8.set(dtypeBytes, offset);\n    offset += dtypeBytes.length;\n    view.setUint32(offset, origDtypeBytes.length, true);\n    offset += 4;\n    uint8.set(origDtypeBytes, offset);\n    offset += origDtypeBytes.length;\n    if (weight.scale !== void 0) {\n      view.setUint8(offset, 1);\n      offset += 1;\n      if (Array.isArray(weight.scale)) {\n        view.setUint32(offset, weight.scale.length, true);\n        offset += 4;\n        for (const s of weight.scale) {\n          view.setFloat32(offset, s, true);\n          offset += 4;\n        }\n      } else {\n        view.setUint32(offset, 1, true);\n        offset += 4;\n        view.setFloat32(offset, weight.scale, true);\n        offset += 4;\n      }\n    } else {\n      view.setUint8(offset, 0);\n      offset += 1;\n    }\n    if (weight.zeroPoint !== void 0) {\n      view.setUint8(offset, 1);\n      offset += 1;\n      if (Array.isArray(weight.zeroPoint)) {\n        view.setUint32(offset, weight.zeroPoint.length, true);\n        offset += 4;\n        for (const zp of weight.zeroPoint) {\n          view.setInt32(offset, zp, true);\n          offset += 4;\n        }\n      } else {\n        view.setUint32(offset, 1, true);\n        offset += 4;\n        view.setInt32(offset, weight.zeroPoint, true);\n        offset += 4;\n      }\n    } else {\n      view.setUint8(offset, 0);\n      offset += 1;\n    }\n    const dataLow = weight.data.byteLength & 4294967295;\n    const dataHigh = weight.data.byteLength / 4294967296 >>> 0;\n    view.setUint32(offset, dataLow, true);\n    offset += 4;\n    view.setUint32(offset, dataHigh, true);\n    offset += 4;\n    uint8.set(new Uint8Array(weight.data), offset);\n    offset += weight.data.byteLength;\n  }\n  return buffer;\n}\nasync function quantizeModel(modelData, options) {\n  const { type, skipPatterns = [], perChannel = false, symmetric = true, onProgress, minTensorSize = 100 } = options;\n  const originalSize = modelData.byteLength;\n  const layerStats = [];\n  let tensorsQuantized = 0;\n  let tensorsSkipped = 0;\n  onProgress?.({ stage: \"analyzing\", current: 0, total: 1, percent: 0 });\n  const weights = parseModelWeights(modelData);\n  const quantizedWeights = [];\n  let totalParams = 0;\n  let quantizedParams = 0;\n  const scales = [];\n  for (let i = 0; i < weights.length; i++) {\n    const weight = weights[i];\n    const percent = (i + 1) / weights.length * 100;\n    onProgress?.({\n      stage: \"quantizing\",\n      current: i + 1,\n      total: weights.length,\n      percent,\n      layerName: weight.name\n    });\n    totalParams += weight.data.length;\n    const shouldSkip = weight.data.length < minTensorSize || skipPatterns.some((pattern) => {\n      if (typeof pattern === \"string\") {\n        return weight.name.includes(pattern);\n      }\n      return pattern.test(weight.name);\n    });\n    if (shouldSkip) {\n      tensorsSkipped++;\n      layerStats.push({\n        name: weight.name,\n        originalDtype: weight.dtype,\n        quantizedDtype: weight.dtype,\n        originalSize: weight.data.byteLength,\n        quantizedSize: weight.data.byteLength,\n        scale: 1,\n        zeroPoint: 0,\n        minValue: Math.min(...weight.data),\n        maxValue: Math.max(...weight.data),\n        skipped: true,\n        skipReason: weight.data.length < minTensorSize ? \"Tensor too small\" : \"Matched skip pattern\"\n      });\n      quantizedWeights.push({\n        name: weight.name,\n        data: weight.data.buffer.slice(0),\n        shape: weight.shape,\n        dtype: weight.dtype,\n        originalDtype: weight.dtype\n      });\n      continue;\n    }\n    const bits = type === \"int4\" ? 4 : 8;\n    const params = calculateQuantParams(weight.data, bits, symmetric, perChannel, 0, weight.shape);\n    let quantizedData2;\n    let quantizedDtype;\n    switch (type) {\n      case \"int8\":\n        const int8Data = quantizeToInt8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n        quantizedData2 = int8Data.buffer.slice(0);\n        quantizedDtype = \"int8\";\n        break;\n      case \"uint8\":\n        const uint8Data = quantizeToUint8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n        quantizedData2 = uint8Data.buffer.slice(0);\n        quantizedDtype = \"uint8\";\n        break;\n      case \"int4\":\n        const int4Data = quantizeToInt4(weight.data, params.scale, params.zeroPoint);\n        quantizedData2 = int4Data.buffer.slice(0);\n        quantizedDtype = \"int4\";\n        break;\n      case \"float16\":\n        const fp16Data = quantizeToFloat16(weight.data);\n        quantizedData2 = fp16Data.buffer.slice(0);\n        quantizedDtype = \"float16\";\n        break;\n      case \"dynamic\":\n      default:\n        const dynData = quantizeToInt8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n        quantizedData2 = dynData.buffer.slice(0);\n        quantizedDtype = \"int8\";\n        break;\n    }\n    tensorsQuantized++;\n    quantizedParams += weight.data.length;\n    const scaleValue = params.scale instanceof Float32Array ? Array.from(params.scale) : params.scale;\n    const zpValue = params.zeroPoint instanceof Int32Array ? Array.from(params.zeroPoint) : params.zeroPoint;\n    if (typeof scaleValue === \"number\") {\n      scales.push(scaleValue);\n    } else {\n      scales.push(...scaleValue);\n    }\n    layerStats.push({\n      name: weight.name,\n      originalDtype: weight.dtype,\n      quantizedDtype,\n      originalSize: weight.data.byteLength,\n      quantizedSize: quantizedData2.byteLength,\n      scale: scaleValue,\n      zeroPoint: zpValue,\n      minValue: params.min,\n      maxValue: params.max,\n      skipped: false\n    });\n    quantizedWeights.push({\n      name: weight.name,\n      data: quantizedData2,\n      shape: weight.shape,\n      dtype: quantizedDtype,\n      originalDtype: weight.dtype,\n      scale: scaleValue,\n      zeroPoint: zpValue\n    });\n  }\n  onProgress?.({ stage: \"packing\", current: 0, total: 1, percent: 0 });\n  const quantizedModel = {\n    version: 1,\n    quantizationType: type,\n    originalSize,\n    weights: quantizedWeights\n  };\n  const quantizedData = serializeQuantizedModel(quantizedModel);\n  onProgress?.({ stage: \"complete\", current: 1, total: 1, percent: 100 });\n  const avgScale = scales.length > 0 ? scales.reduce((a, b) => a + b, 0) / scales.length : 1;\n  const minScale = scales.length > 0 ? Math.min(...scales) : 1;\n  const maxScale = scales.length > 0 ? Math.max(...scales) : 1;\n  const bitsReduction = type === \"int4\" ? 8 : type === \"float16\" ? 2 : 4;\n  const errorEstimate = avgScale / bitsReduction;\n  return {\n    data: quantizedData,\n    originalSize,\n    quantizedSize: quantizedData.byteLength,\n    compressionRatio: originalSize / quantizedData.byteLength,\n    tensorsQuantized,\n    tensorsSkipped,\n    layerStats,\n    stats: {\n      totalParameters: totalParams,\n      quantizedParameters: quantizedParams,\n      averageScale: avgScale,\n      minScale,\n      maxScale,\n      errorEstimate\n    }\n  };\n}\nfunction quantizeTensor(tensor2, type, options = {}) {\n  const { symmetric = true, perChannel = false } = options;\n  const data = tensor2.toFloat32Array();\n  const shape = tensor2.shape;\n  const bits = type === \"int4\" ? 4 : 8;\n  const params = calculateQuantParams(data, bits, symmetric, perChannel, 0, shape);\n  let quantizedData;\n  let dtype;\n  switch (type) {\n    case \"int8\":\n      quantizedData = quantizeToInt8(data, params.scale, params.zeroPoint, perChannel);\n      dtype = \"int32\";\n      break;\n    case \"uint8\":\n      quantizedData = quantizeToUint8(data, params.scale, params.zeroPoint, perChannel);\n      dtype = \"int32\";\n      break;\n    case \"float16\":\n      quantizedData = quantizeToFloat16(data);\n      dtype = \"float32\";\n      break;\n    default:\n      quantizedData = quantizeToInt8(data, params.scale, params.zeroPoint, perChannel);\n      dtype = \"int32\";\n  }\n  const scaleValue = params.scale instanceof Float32Array ? Array.from(params.scale) : params.scale;\n  const zpValue = params.zeroPoint instanceof Int32Array ? Array.from(params.zeroPoint) : params.zeroPoint;\n  return {\n    tensor: new EdgeFlowTensor(Array.from(quantizedData), shape, dtype),\n    scale: scaleValue,\n    zeroPoint: zpValue\n  };\n}\nfunction dequantizeTensor(tensor2, scale, zeroPoint, type) {\n  const data = tensor2.toArray();\n  const shape = tensor2.shape;\n  let dequantizedData;\n  const scaleArr = Array.isArray(scale) ? new Float32Array(scale) : scale;\n  const zpArr = Array.isArray(zeroPoint) ? new Int32Array(zeroPoint) : zeroPoint;\n  const perChannel = Array.isArray(scale);\n  switch (type) {\n    case \"int8\":\n      dequantizedData = dequantizeInt8(new Int8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n      break;\n    case \"uint8\":\n      dequantizedData = dequantizeUint8(new Uint8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n      break;\n    case \"float16\":\n      dequantizedData = dequantizeFloat16(new Uint16Array(data.map(Number)));\n      break;\n    default:\n      dequantizedData = dequantizeInt8(new Int8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n  }\n  return new EdgeFlowTensor(Array.from(dequantizedData), shape, \"float32\");\n}\nfunction pruneTensor(tensor2, options = {}) {\n  const { ratio = 0.5, method = \"magnitude\", threshold } = options;\n  const data = tensor2.toFloat32Array();\n  const shape = tensor2.shape;\n  const mask = new Float32Array(data.length);\n  const prunedData = new Float32Array(data.length);\n  let prunedCount = 0;\n  if (method === \"magnitude\") {\n    const absValues = Array.from(data).map(Math.abs).sort((a, b) => a - b);\n    const thresholdIndex = Math.floor(absValues.length * ratio);\n    const computedThreshold = threshold ?? (absValues[thresholdIndex] ?? 0);\n    for (let i = 0; i < data.length; i++) {\n      if (Math.abs(data[i] ?? 0) > computedThreshold) {\n        mask[i] = 1;\n        prunedData[i] = data[i] ?? 0;\n      } else {\n        mask[i] = 0;\n        prunedData[i] = 0;\n        prunedCount++;\n      }\n    }\n  } else if (method === \"random\") {\n    for (let i = 0; i < data.length; i++) {\n      if (Math.random() > ratio) {\n        mask[i] = 1;\n        prunedData[i] = data[i] ?? 0;\n      } else {\n        mask[i] = 0;\n        prunedData[i] = 0;\n        prunedCount++;\n      }\n    }\n  }\n  return {\n    tensor: new EdgeFlowTensor(Array.from(prunedData), shape, \"float32\"),\n    mask: new EdgeFlowTensor(Array.from(mask), shape, \"float32\"),\n    sparsity: prunedCount / data.length\n  };\n}\nasync function pruneModel(modelData, options = {}) {\n  const { onProgress } = options;\n  onProgress?.({ current: 0, total: 1, percent: 0 });\n  const weights = parseModelWeights(modelData);\n  let totalParams = 0;\n  let prunedParams = 0;\n  for (const weight of weights) {\n    totalParams += weight.data.length;\n    const tensor2 = new EdgeFlowTensor(Array.from(weight.data), weight.shape, \"float32\");\n    const { sparsity } = pruneTensor(tensor2, options);\n    prunedParams += Math.floor(weight.data.length * sparsity);\n  }\n  onProgress?.({ current: 1, total: 1, percent: 100 });\n  return {\n    data: modelData,\n    // In a real implementation, we'd create a sparse format\n    originalSize: modelData.byteLength,\n    prunedSize: modelData.byteLength,\n    // Would be smaller with sparse format\n    sparsity: prunedParams / totalParams,\n    parametersPruned: prunedParams,\n    totalParameters: totalParams\n  };\n}\nasync function analyzeModel(modelData) {\n  const weights = parseModelWeights(modelData);\n  const totalSize = modelData.byteLength;\n  const dtypeBreakdown = {};\n  let totalParams = 0;\n  const tensorInfos = [];\n  for (const weight of weights) {\n    totalParams += weight.data.length;\n    const bytesPerElement = weight.dtype === \"float32\" ? 4 : weight.dtype === \"float16\" ? 2 : weight.dtype === \"int8\" ? 1 : 4;\n    const size = weight.data.length * bytesPerElement;\n    if (!dtypeBreakdown[weight.dtype]) {\n      dtypeBreakdown[weight.dtype] = { count: 0, size: 0 };\n    }\n    dtypeBreakdown[weight.dtype].count++;\n    dtypeBreakdown[weight.dtype].size += size;\n    tensorInfos.push({\n      name: weight.name,\n      size,\n      shape: weight.shape\n    });\n  }\n  tensorInfos.sort((a, b) => b.size - a.size);\n  const largestTensors = tensorInfos.slice(0, 10);\n  const estimatedQuantizedSizes = {\n    int8: Math.ceil(totalSize / 4),\n    uint8: Math.ceil(totalSize / 4),\n    int4: Math.ceil(totalSize / 8),\n    float16: Math.ceil(totalSize / 2),\n    dynamic: Math.ceil(totalSize / 4)\n  };\n  let recommendedQuantization = \"dynamic\";\n  if (totalSize > 500 * 1024 * 1024) {\n    recommendedQuantization = \"int4\";\n  } else if (totalSize > 100 * 1024 * 1024) {\n    recommendedQuantization = \"int8\";\n  } else if (totalSize > 50 * 1024 * 1024) {\n    recommendedQuantization = \"float16\";\n  }\n  return {\n    totalSize,\n    tensorCount: weights.length,\n    totalParameters: totalParams,\n    dtypeBreakdown,\n    largestTensors,\n    estimatedMemory: totalParams * 4,\n    // Assuming float32 at runtime\n    recommendedQuantization,\n    estimatedQuantizedSizes\n  };\n}\nasync function exportModel(modelData, options) {\n  const { format, quantize: quantize2 } = options;\n  let data = modelData;\n  if (quantize2) {\n    const result = await quantizeModel(modelData, { type: quantize2 });\n    data = result.data;\n  }\n  switch (format) {\n    case \"edgeflow\":\n      return data;\n    case \"onnx\":\n      return data;\n    case \"tflite\":\n      return data;\n    default:\n      return data;\n  }\n}\n\n// dist/tools/debugger.js\nfunction calculateTensorStats(data) {\n  const arr = data instanceof Float32Array ? data : new Float32Array(data);\n  let min = Infinity;\n  let max = -Infinity;\n  let sum2 = 0;\n  let zeros2 = 0;\n  let nans = 0;\n  let infinities = 0;\n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (isNaN(val)) {\n      nans++;\n      continue;\n    }\n    if (!isFinite(val)) {\n      infinities++;\n      continue;\n    }\n    min = Math.min(min, val);\n    max = Math.max(max, val);\n    sum2 += val;\n    if (val === 0)\n      zeros2++;\n  }\n  const validCount = arr.length - nans - infinities;\n  const mean2 = validCount > 0 ? sum2 / validCount : 0;\n  let varianceSum = 0;\n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      varianceSum += Math.pow(val - mean2, 2);\n    }\n  }\n  const std = validCount > 0 ? Math.sqrt(varianceSum / validCount) : 0;\n  return {\n    min: min === Infinity ? 0 : min,\n    max: max === -Infinity ? 0 : max,\n    mean: mean2,\n    std,\n    zeros: zeros2,\n    nans,\n    infinities,\n    sparsity: zeros2 / arr.length\n  };\n}\nfunction createHistogram(data, bins = 50) {\n  const arr = data instanceof Float32Array ? data : new Float32Array(data);\n  let min = Infinity;\n  let max = -Infinity;\n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n  }\n  if (min === Infinity || max === -Infinity || min === max) {\n    return { bins: [min || 0], counts: [arr.length], binEdges: [min || 0, max || 0] };\n  }\n  const binWidth = (max - min) / bins;\n  const counts = new Array(bins).fill(0);\n  const binEdges = new Array(bins + 1);\n  for (let i = 0; i <= bins; i++) {\n    binEdges[i] = min + i * binWidth;\n  }\n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      const binIndex = Math.min(Math.floor((val - min) / binWidth), bins - 1);\n      counts[binIndex]++;\n    }\n  }\n  return {\n    bins: binEdges.slice(0, -1).map((e, i) => (e + binEdges[i + 1]) / 2),\n    counts,\n    binEdges\n  };\n}\nfunction inspectTensor(tensor2, name = \"tensor\", options = {}) {\n  const { histogram = true, maxSample = 10 } = options;\n  const data = tensor2.toFloat32Array();\n  const shape = tensor2.shape;\n  const size = tensor2.size;\n  const sampleIndices = [];\n  const step = Math.max(1, Math.floor(size / maxSample));\n  for (let i = 0; i < size && sampleIndices.length < maxSample; i += step) {\n    sampleIndices.push(i);\n  }\n  const sample = sampleIndices.map((i) => data[i] ?? 0);\n  const bytesPerElement = tensor2.dtype === \"float32\" ? 4 : tensor2.dtype === \"int32\" ? 4 : tensor2.dtype === \"int64\" ? 8 : 4;\n  const memoryBytes = size * bytesPerElement;\n  return {\n    name,\n    shape,\n    dtype: tensor2.dtype,\n    size,\n    memoryBytes,\n    stats: calculateTensorStats(data),\n    sample,\n    histogram: histogram ? createHistogram(data) : void 0\n  };\n}\nfunction formatTensorInspection(inspection) {\n  const { name, shape, dtype, size, memoryBytes, stats, sample } = inspection;\n  const lines = [\n    `\\u250C\\u2500 Tensor: ${name} \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500`,\n    `\\u2502 Shape: [${shape.join(\", \")}]`,\n    `\\u2502 Dtype: ${dtype}`,\n    `\\u2502 Size: ${size.toLocaleString()} elements`,\n    `\\u2502 Memory: ${formatBytes(memoryBytes)}`,\n    `\\u251C\\u2500 Statistics \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500`,\n    `\\u2502 Min: ${stats.min.toFixed(6)}`,\n    `\\u2502 Max: ${stats.max.toFixed(6)}`,\n    `\\u2502 Mean: ${stats.mean.toFixed(6)}`,\n    `\\u2502 Std: ${stats.std.toFixed(6)}`,\n    `\\u2502 Sparsity: ${(stats.sparsity * 100).toFixed(2)}%`\n  ];\n  if (stats.nans > 0) {\n    lines.push(`\\u2502 \\u26A0\\uFE0F NaN values: ${stats.nans}`);\n  }\n  if (stats.infinities > 0) {\n    lines.push(`\\u2502 \\u26A0\\uFE0F Infinity values: ${stats.infinities}`);\n  }\n  lines.push(`\\u251C\\u2500 Sample Values \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500`);\n  lines.push(`\\u2502 [${sample.map((v) => v.toFixed(4)).join(\", \")}]`);\n  lines.push(`\\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500`);\n  return lines.join(\"\\n\");\n}\nfunction formatBytes(bytes) {\n  if (bytes < 1024)\n    return `${bytes} B`;\n  if (bytes < 1024 * 1024)\n    return `${(bytes / 1024).toFixed(2)} KB`;\n  if (bytes < 1024 * 1024 * 1024)\n    return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n  return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\nvar EdgeFlowDebugger = class {\n  constructor(config = {}) {\n    __publicField(this, \"config\");\n    __publicField(this, \"events\", []);\n    __publicField(this, \"traces\", []);\n    __publicField(this, \"performanceMetrics\");\n    __publicField(this, \"listeners\", /* @__PURE__ */ new Map());\n    __publicField(this, \"isEnabled\", true);\n    this.config = {\n      logging: config.logging ?? true,\n      logLevel: config.logLevel ?? \"info\",\n      inspectTensors: config.inspectTensors ?? true,\n      maxDisplayValues: config.maxDisplayValues ?? 10,\n      trackPerformance: config.trackPerformance ?? true,\n      logger: config.logger ?? this.defaultLogger.bind(this)\n    };\n    this.performanceMetrics = {\n      inferenceCount: 0,\n      totalInferenceTime: 0,\n      averageInferenceTime: 0,\n      minInferenceTime: Infinity,\n      maxInferenceTime: 0,\n      peakMemoryUsage: 0,\n      currentMemoryUsage: 0,\n      tensorAllocations: 0,\n      tensorDeallocations: 0\n    };\n  }\n  /**\n   * Default logger\n   */\n  defaultLogger(level, message, data) {\n    const timestamp = (/* @__PURE__ */ new Date()).toISOString();\n    const prefix = `[edgeFlow.js ${timestamp}] [${level.toUpperCase()}]`;\n    switch (level) {\n      case \"debug\":\n        console.debug(prefix, message, data ?? \"\");\n        break;\n      case \"info\":\n        console.info(prefix, message, data ?? \"\");\n        break;\n      case \"warn\":\n        console.warn(prefix, message, data ?? \"\");\n        break;\n      case \"error\":\n        console.error(prefix, message, data ?? \"\");\n        break;\n      default:\n        console.log(prefix, message, data ?? \"\");\n    }\n  }\n  /**\n   * Log a message\n   */\n  log(level, message, data) {\n    if (!this.isEnabled || !this.config.logging)\n      return;\n    const levels = [\"debug\", \"info\", \"warn\", \"error\"];\n    const configLevel = levels.indexOf(this.config.logLevel);\n    const msgLevel = levels.indexOf(level);\n    if (msgLevel >= configLevel) {\n      this.config.logger(level, message, data);\n    }\n  }\n  /**\n   * Add debug event\n   */\n  addEvent(event) {\n    this.events.push(event);\n    const listeners = this.listeners.get(event.type) ?? [];\n    for (const listener of listeners) {\n      listener(event);\n    }\n    if (this.events.length > 1e3) {\n      this.events = this.events.slice(-1e3);\n    }\n  }\n  /**\n   * Enable debugger\n   */\n  enable() {\n    this.isEnabled = true;\n    this.log(\"info\", \"Debugger enabled\");\n  }\n  /**\n   * Disable debugger\n   */\n  disable() {\n    this.isEnabled = false;\n  }\n  /**\n   * Subscribe to events\n   */\n  on(type, callback) {\n    const listeners = this.listeners.get(type) ?? [];\n    listeners.push(callback);\n    this.listeners.set(type, listeners);\n    return () => {\n      const idx = listeners.indexOf(callback);\n      if (idx !== -1)\n        listeners.splice(idx, 1);\n    };\n  }\n  /**\n   * Inspect and log a tensor\n   */\n  inspectTensor(tensor2, name = \"tensor\") {\n    const inspection = inspectTensor(tensor2, name, {\n      histogram: true,\n      maxSample: this.config.maxDisplayValues\n    });\n    if (this.config.inspectTensors) {\n      this.log(\"debug\", `Tensor: ${name}`, inspection);\n      this.addEvent({\n        type: \"tensor\",\n        timestamp: Date.now(),\n        message: `Inspected tensor: ${name}`,\n        data: inspection\n      });\n      if (inspection.stats.nans > 0) {\n        this.log(\"warn\", `Tensor \"${name}\" contains ${inspection.stats.nans} NaN values`);\n      }\n      if (inspection.stats.infinities > 0) {\n        this.log(\"warn\", `Tensor \"${name}\" contains ${inspection.stats.infinities} Infinity values`);\n      }\n    }\n    return inspection;\n  }\n  /**\n   * Start tracing an inference\n   */\n  startTrace(modelId) {\n    const id = `trace_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n    const trace = {\n      id,\n      modelId,\n      timestamp: Date.now(),\n      inputs: [],\n      outputs: [],\n      duration: 0,\n      memoryUsed: 0,\n      operations: []\n    };\n    this.traces.push(trace);\n    this.log(\"debug\", `Started trace: ${id} for model: ${modelId}`);\n    return id;\n  }\n  /**\n   * Add input to trace\n   */\n  traceInput(traceId, tensor2, name) {\n    const trace = this.traces.find((t) => t.id === traceId);\n    if (!trace)\n      return;\n    trace.inputs.push(inspectTensor(tensor2, name));\n  }\n  /**\n   * Add output to trace\n   */\n  traceOutput(traceId, tensor2, name) {\n    const trace = this.traces.find((t) => t.id === traceId);\n    if (!trace)\n      return;\n    trace.outputs.push(inspectTensor(tensor2, name));\n  }\n  /**\n   * Add operation to trace\n   */\n  traceOperation(traceId, operation) {\n    const trace = this.traces.find((t) => t.id === traceId);\n    if (!trace)\n      return;\n    trace.operations.push(operation);\n  }\n  /**\n   * End trace\n   */\n  endTrace(traceId) {\n    const trace = this.traces.find((t) => t.id === traceId);\n    if (!trace)\n      return;\n    trace.duration = Date.now() - trace.timestamp;\n    this.performanceMetrics.inferenceCount++;\n    this.performanceMetrics.totalInferenceTime += trace.duration;\n    this.performanceMetrics.averageInferenceTime = this.performanceMetrics.totalInferenceTime / this.performanceMetrics.inferenceCount;\n    this.performanceMetrics.minInferenceTime = Math.min(this.performanceMetrics.minInferenceTime, trace.duration);\n    this.performanceMetrics.maxInferenceTime = Math.max(this.performanceMetrics.maxInferenceTime, trace.duration);\n    this.log(\"info\", `Trace completed: ${traceId}`, {\n      duration: `${trace.duration}ms`,\n      inputs: trace.inputs.length,\n      outputs: trace.outputs.length,\n      operations: trace.operations.length\n    });\n    this.addEvent({\n      type: \"inference\",\n      timestamp: Date.now(),\n      message: `Inference completed in ${trace.duration}ms`,\n      data: trace\n    });\n    return trace;\n  }\n  /**\n   * Record tensor allocation\n   */\n  recordAllocation(tensor2) {\n    if (!this.config.trackPerformance)\n      return;\n    this.performanceMetrics.tensorAllocations++;\n    const memory = tensor2.size * 4;\n    this.performanceMetrics.currentMemoryUsage += memory;\n    this.performanceMetrics.peakMemoryUsage = Math.max(this.performanceMetrics.peakMemoryUsage, this.performanceMetrics.currentMemoryUsage);\n  }\n  /**\n   * Record tensor deallocation\n   */\n  recordDeallocation(tensor2) {\n    if (!this.config.trackPerformance)\n      return;\n    this.performanceMetrics.tensorDeallocations++;\n    const memory = tensor2.size * 4;\n    this.performanceMetrics.currentMemoryUsage -= memory;\n  }\n  /**\n   * Get performance metrics\n   */\n  getPerformanceMetrics() {\n    return { ...this.performanceMetrics };\n  }\n  /**\n   * Get all events\n   */\n  getEvents() {\n    return [...this.events];\n  }\n  /**\n   * Get all traces\n   */\n  getTraces() {\n    return [...this.traces];\n  }\n  /**\n   * Get trace by ID\n   */\n  getTrace(traceId) {\n    return this.traces.find((t) => t.id === traceId);\n  }\n  /**\n   * Clear all data\n   */\n  clear() {\n    this.events = [];\n    this.traces = [];\n    this.performanceMetrics = {\n      inferenceCount: 0,\n      totalInferenceTime: 0,\n      averageInferenceTime: 0,\n      minInferenceTime: Infinity,\n      maxInferenceTime: 0,\n      peakMemoryUsage: 0,\n      currentMemoryUsage: 0,\n      tensorAllocations: 0,\n      tensorDeallocations: 0\n    };\n  }\n  /**\n   * Export debug data\n   */\n  export() {\n    return {\n      events: this.getEvents(),\n      traces: this.getTraces(),\n      metrics: this.getPerformanceMetrics(),\n      timestamp: Date.now()\n    };\n  }\n  /**\n   * Generate summary report\n   */\n  generateReport() {\n    const metrics = this.getPerformanceMetrics();\n    const traces = this.getTraces();\n    const lines = [\n      \"\\u2554\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2557\",\n      \"\\u2551               edgeFlow.js Debug Report                          \\u2551\",\n      \"\\u2560\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2563\",\n      \"\\u2551 Performance Metrics                                             \\u2551\",\n      \"\\u255F\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2562\",\n      `\\u2551 Total Inferences:     ${metrics.inferenceCount.toString().padStart(10)}                          \\u2551`,\n      `\\u2551 Average Time:         ${metrics.averageInferenceTime.toFixed(2).padStart(10)}ms                       \\u2551`,\n      `\\u2551 Min Time:             ${(metrics.minInferenceTime === Infinity ? 0 : metrics.minInferenceTime).toFixed(2).padStart(10)}ms                       \\u2551`,\n      `\\u2551 Max Time:             ${metrics.maxInferenceTime.toFixed(2).padStart(10)}ms                       \\u2551`,\n      `\\u2551 Peak Memory:          ${formatBytes(metrics.peakMemoryUsage).padStart(10)}                          \\u2551`,\n      `\\u2551 Current Memory:       ${formatBytes(metrics.currentMemoryUsage).padStart(10)}                          \\u2551`,\n      `\\u2551 Tensor Allocations:   ${metrics.tensorAllocations.toString().padStart(10)}                          \\u2551`,\n      `\\u2551 Tensor Deallocations: ${metrics.tensorDeallocations.toString().padStart(10)}                          \\u2551`,\n      \"\\u255F\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2562\",\n      \"\\u2551 Recent Traces                                                   \\u2551\",\n      \"\\u255F\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2562\"\n    ];\n    const recentTraces = traces.slice(-5);\n    for (const trace of recentTraces) {\n      lines.push(`\\u2551 ${trace.id.slice(0, 20).padEnd(20)} | ${trace.duration.toFixed(2).padStart(8)}ms | ${trace.modelId.slice(0, 20).padEnd(20)} \\u2551`);\n    }\n    if (recentTraces.length === 0) {\n      lines.push(\"\\u2551 No traces recorded                                              \\u2551\");\n    }\n    lines.push(\"\\u255A\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255D\");\n    return lines.join(\"\\n\");\n  }\n};\nvar globalDebugger = null;\nfunction getDebugger(config) {\n  if (!globalDebugger || config) {\n    globalDebugger = new EdgeFlowDebugger(config);\n  }\n  return globalDebugger;\n}\nfunction enableDebugging(config) {\n  const debugger_ = getDebugger(config);\n  debugger_.enable();\n  return debugger_;\n}\nfunction disableDebugging() {\n  globalDebugger?.disable();\n}\nfunction createAsciiHistogram(histogram, width = 50, height = 10) {\n  const { counts, binEdges } = histogram;\n  const maxCount = Math.max(...counts);\n  if (maxCount === 0)\n    return \"No data to display\";\n  const lines = [];\n  const scaled = counts.map((c) => Math.round(c / maxCount * height));\n  for (let row = height; row > 0; row--) {\n    let line = row === height ? `${maxCount.toString().padStart(6)} \\u2502` : \"       \\u2502\";\n    for (let col = 0; col < width && col < scaled.length; col++) {\n      line += (scaled[col] ?? 0) >= row ? \"\\u2588\" : \" \";\n    }\n    lines.push(line);\n  }\n  lines.push(\"       \\u2514\" + \"\\u2500\".repeat(Math.min(width, scaled.length)));\n  const minLabel = (binEdges[0] ?? 0).toFixed(2);\n  const maxLabel = (binEdges[binEdges.length - 1] ?? 0).toFixed(2);\n  lines.push(`        ${minLabel}${\" \".repeat(Math.max(0, Math.min(width, scaled.length) - minLabel.length - maxLabel.length))}${maxLabel}`);\n  return lines.join(\"\\n\");\n}\nfunction createTensorHeatmap(tensor2, width = 40) {\n  const shape = tensor2.shape;\n  if (shape.length !== 2) {\n    return \"Heatmap only supports 2D tensors\";\n  }\n  const [rows, cols] = shape;\n  if (rows === void 0 || cols === void 0) {\n    return \"Invalid tensor shape\";\n  }\n  const data = tensor2.toFloat32Array();\n  let min = Infinity;\n  let max = -Infinity;\n  for (let i = 0; i < data.length; i++) {\n    const val = data[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n  }\n  const range = max - min;\n  const chars = [\" \", \"\\u2591\", \"\\u2592\", \"\\u2593\", \"\\u2588\"];\n  const lines = [];\n  const scaleX = Math.max(1, Math.ceil(cols / width));\n  const displayCols = Math.min(cols, width);\n  for (let r = 0; r < rows; r++) {\n    let line = \"\";\n    for (let c = 0; c < displayCols; c++) {\n      const idx = r * cols + c * scaleX;\n      const val = data[idx] ?? 0;\n      const normalized = range > 0 ? (val - min) / range : 0;\n      const charIdx = Math.floor(normalized * (chars.length - 1));\n      line += chars[charIdx];\n    }\n    lines.push(line);\n  }\n  return lines.join(\"\\n\");\n}\nfunction visualizeModelArchitecture(layers) {\n  const lines = [];\n  lines.push(\"\\u250C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\");\n  lines.push(\"\\u2502                        Model Architecture                          \\u2502\");\n  lines.push(\"\\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\");\n  for (let i = 0; i < layers.length; i++) {\n    const layer = layers[i];\n    const inputStr = `[${layer.inputShape.join(\"\\xD7\")}]`;\n    const outputStr = `[${layer.outputShape.join(\"\\xD7\")}]`;\n    lines.push(`\\u2502 ${(i + 1).toString().padStart(2)}. ${layer.name.padEnd(20)} \\u2502 ${layer.type.padEnd(15)} \\u2502`);\n    lines.push(`\\u2502     ${inputStr.padEnd(15)} \\u2192 ${outputStr.padEnd(15)}                   \\u2502`);\n    if (i < layers.length - 1) {\n      lines.push(\"\\u2502                           \\u2193                                        \\u2502\");\n    }\n  }\n  lines.push(\"\\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\");\n  return lines.join(\"\\n\");\n}\n\n// dist/tools/monitor.js\nvar PerformanceMonitor = class {\n  constructor(config = {}) {\n    __publicField(this, \"config\");\n    __publicField(this, \"samples\", []);\n    __publicField(this, \"isRunning\", false);\n    __publicField(this, \"intervalId\", null);\n    __publicField(this, \"alerts\", []);\n    __publicField(this, \"alertListeners\", []);\n    __publicField(this, \"sampleListeners\", []);\n    // Inference tracking\n    __publicField(this, \"inferenceCount\", 0);\n    __publicField(this, \"inferenceTimes\", []);\n    __publicField(this, \"queueLength\", 0);\n    __publicField(this, \"activeCount\", 0);\n    // FPS tracking\n    __publicField(this, \"frameCount\", 0);\n    __publicField(this, \"lastFrameTime\", 0);\n    __publicField(this, \"fps\", 0);\n    __publicField(this, \"rafId\", null);\n    // Memory tracking\n    __publicField(this, \"tensorMemory\", 0);\n    __publicField(this, \"cacheMemory\", 0);\n    this.config = {\n      enabled: config.enabled ?? true,\n      sampleInterval: config.sampleInterval ?? 1e3,\n      historySize: config.historySize ?? 60,\n      monitorMemory: config.monitorMemory ?? true,\n      monitorFPS: config.monitorFPS ?? true,\n      collectors: config.collectors ?? []\n    };\n  }\n  /**\n   * Start monitoring\n   */\n  start() {\n    if (this.isRunning)\n      return;\n    this.isRunning = true;\n    this.intervalId = setInterval(() => {\n      this.collectSample();\n    }, this.config.sampleInterval);\n    if (this.config.monitorFPS && typeof requestAnimationFrame !== \"undefined\") {\n      this.lastFrameTime = performance.now();\n      this.frameCount = 0;\n      this.monitorFPS();\n    }\n  }\n  /**\n   * Stop monitoring\n   */\n  stop() {\n    this.isRunning = false;\n    if (this.intervalId) {\n      clearInterval(this.intervalId);\n      this.intervalId = null;\n    }\n    if (this.rafId) {\n      cancelAnimationFrame(this.rafId);\n      this.rafId = null;\n    }\n  }\n  /**\n   * Monitor FPS\n   */\n  monitorFPS() {\n    if (!this.isRunning)\n      return;\n    this.frameCount++;\n    const now = performance.now();\n    const elapsed = now - this.lastFrameTime;\n    if (elapsed >= 1e3) {\n      this.fps = Math.round(this.frameCount * 1e3 / elapsed);\n      this.frameCount = 0;\n      this.lastFrameTime = now;\n    }\n    this.rafId = requestAnimationFrame(() => this.monitorFPS());\n  }\n  /**\n   * Collect a performance sample\n   */\n  collectSample() {\n    const now = Date.now();\n    const avgTime = this.inferenceTimes.length > 0 ? this.inferenceTimes.reduce((a, b) => a + b, 0) / this.inferenceTimes.length : 0;\n    const minTime = this.inferenceTimes.length > 0 ? Math.min(...this.inferenceTimes) : 0;\n    const maxTime = this.inferenceTimes.length > 0 ? Math.max(...this.inferenceTimes) : 0;\n    const throughput = this.inferenceCount / (this.config.sampleInterval / 1e3);\n    const inference = {\n      count: this.inferenceCount,\n      avgTime,\n      minTime,\n      maxTime,\n      throughput,\n      queueLength: this.queueLength,\n      activeCount: this.activeCount\n    };\n    const memory = this.collectMemoryMetrics();\n    const system = this.collectSystemMetrics();\n    const custom = {};\n    for (const collector of this.config.collectors) {\n      try {\n        Object.assign(custom, collector());\n      } catch {\n      }\n    }\n    const sample = {\n      timestamp: now,\n      inference,\n      memory,\n      system,\n      custom\n    };\n    this.samples.push(sample);\n    if (this.samples.length > this.config.historySize) {\n      this.samples.shift();\n    }\n    this.checkAlerts(sample);\n    for (const listener of this.sampleListeners) {\n      listener(sample);\n    }\n    this.inferenceCount = 0;\n    this.inferenceTimes = [];\n  }\n  /**\n   * Collect memory metrics\n   */\n  collectMemoryMetrics() {\n    let usedHeap = 0;\n    let totalHeap = 0;\n    let heapLimit = 0;\n    if (typeof performance !== \"undefined\" && \"memory\" in performance) {\n      const memory = performance.memory;\n      usedHeap = memory.usedJSHeapSize;\n      totalHeap = memory.totalJSHeapSize;\n      heapLimit = memory.jsHeapSizeLimit;\n    }\n    return {\n      usedHeap,\n      totalHeap,\n      heapLimit,\n      heapUsage: heapLimit > 0 ? usedHeap / heapLimit : 0,\n      tensorMemory: this.tensorMemory,\n      cacheMemory: this.cacheMemory\n    };\n  }\n  /**\n   * Collect system metrics\n   */\n  collectSystemMetrics() {\n    const lastSample = this.samples[this.samples.length - 1];\n    const deltaTime = lastSample ? Date.now() - lastSample.timestamp : this.config.sampleInterval;\n    let webgpuAvailable = false;\n    if (typeof navigator !== \"undefined\" && \"gpu\" in navigator) {\n      webgpuAvailable = true;\n    }\n    let webnnAvailable = false;\n    if (typeof navigator !== \"undefined\" && \"ml\" in navigator) {\n      webnnAvailable = true;\n    }\n    return {\n      fps: this.fps,\n      cpuUsage: this.estimateCPUUsage(),\n      deltaTime,\n      userAgent: typeof navigator !== \"undefined\" ? navigator.userAgent : \"unknown\",\n      webgpuAvailable,\n      webnnAvailable\n    };\n  }\n  /**\n   * Estimate CPU usage based on inference times\n   */\n  estimateCPUUsage() {\n    if (this.inferenceTimes.length === 0)\n      return 0;\n    const totalTime = this.inferenceTimes.reduce((a, b) => a + b, 0);\n    return Math.min(1, totalTime / this.config.sampleInterval);\n  }\n  /**\n   * Check alerts\n   */\n  checkAlerts(sample) {\n    for (const alert of this.alerts) {\n      const value = this.getMetricValue(sample, alert.metric);\n      if (value === void 0)\n        continue;\n      let triggered = false;\n      switch (alert.operator) {\n        case \">\":\n          triggered = value > alert.threshold;\n          break;\n        case \"<\":\n          triggered = value < alert.threshold;\n          break;\n        case \">=\":\n          triggered = value >= alert.threshold;\n          break;\n        case \"<=\":\n          triggered = value <= alert.threshold;\n          break;\n        case \"==\":\n          triggered = value === alert.threshold;\n          break;\n        case \"!=\":\n          triggered = value !== alert.threshold;\n          break;\n      }\n      if (triggered) {\n        const event = {\n          config: alert,\n          value,\n          timestamp: sample.timestamp\n        };\n        for (const listener of this.alertListeners) {\n          listener(event);\n        }\n      }\n    }\n  }\n  /**\n   * Get metric value from sample\n   */\n  getMetricValue(sample, metric) {\n    const parts = metric.split(\".\");\n    let value = sample;\n    for (const part of parts) {\n      if (value && typeof value === \"object\" && part in value) {\n        value = value[part];\n      } else {\n        return void 0;\n      }\n    }\n    return typeof value === \"number\" ? value : void 0;\n  }\n  /**\n   * Record an inference\n   */\n  recordInference(duration) {\n    this.inferenceCount++;\n    this.inferenceTimes.push(duration);\n  }\n  /**\n   * Update queue length\n   */\n  updateQueueLength(length) {\n    this.queueLength = length;\n  }\n  /**\n   * Update active count\n   */\n  updateActiveCount(count) {\n    this.activeCount = count;\n  }\n  /**\n   * Update tensor memory\n   */\n  updateTensorMemory(bytes) {\n    this.tensorMemory = bytes;\n  }\n  /**\n   * Update cache memory\n   */\n  updateCacheMemory(bytes) {\n    this.cacheMemory = bytes;\n  }\n  /**\n   * Add an alert\n   */\n  addAlert(config) {\n    this.alerts.push(config);\n  }\n  /**\n   * Remove an alert\n   */\n  removeAlert(metric) {\n    this.alerts = this.alerts.filter((a) => a.metric !== metric);\n  }\n  /**\n   * Subscribe to alerts\n   */\n  onAlert(callback) {\n    this.alertListeners.push(callback);\n    return () => {\n      const idx = this.alertListeners.indexOf(callback);\n      if (idx !== -1)\n        this.alertListeners.splice(idx, 1);\n    };\n  }\n  /**\n   * Subscribe to samples\n   */\n  onSample(callback) {\n    this.sampleListeners.push(callback);\n    return () => {\n      const idx = this.sampleListeners.indexOf(callback);\n      if (idx !== -1)\n        this.sampleListeners.splice(idx, 1);\n    };\n  }\n  /**\n   * Get current sample\n   */\n  getCurrentSample() {\n    return this.samples[this.samples.length - 1];\n  }\n  /**\n   * Get all samples\n   */\n  getSamples() {\n    return [...this.samples];\n  }\n  /**\n   * Get samples in time range\n   */\n  getSamplesInRange(startTime, endTime) {\n    return this.samples.filter((s) => s.timestamp >= startTime && s.timestamp <= endTime);\n  }\n  /**\n   * Get summary statistics\n   */\n  getSummary() {\n    if (this.samples.length === 0) {\n      return {\n        avgInferenceTime: 0,\n        avgThroughput: 0,\n        avgMemoryUsage: 0,\n        avgFPS: 0,\n        totalInferences: 0,\n        uptime: 0\n      };\n    }\n    const avgInferenceTime = this.samples.reduce((sum2, s) => sum2 + s.inference.avgTime, 0) / this.samples.length;\n    const avgThroughput = this.samples.reduce((sum2, s) => sum2 + s.inference.throughput, 0) / this.samples.length;\n    const avgMemoryUsage = this.samples.reduce((sum2, s) => sum2 + s.memory.heapUsage, 0) / this.samples.length;\n    const avgFPS = this.samples.reduce((sum2, s) => sum2 + s.system.fps, 0) / this.samples.length;\n    const totalInferences = this.samples.reduce((sum2, s) => sum2 + s.inference.count, 0);\n    const firstSample = this.samples[0];\n    const lastSample = this.samples[this.samples.length - 1];\n    const uptime = lastSample.timestamp - firstSample.timestamp;\n    return {\n      avgInferenceTime,\n      avgThroughput,\n      avgMemoryUsage,\n      avgFPS,\n      totalInferences,\n      uptime\n    };\n  }\n  /**\n   * Clear all data\n   */\n  clear() {\n    this.samples = [];\n    this.inferenceCount = 0;\n    this.inferenceTimes = [];\n    this.queueLength = 0;\n    this.activeCount = 0;\n    this.tensorMemory = 0;\n    this.cacheMemory = 0;\n  }\n  /**\n   * Export data\n   */\n  export() {\n    return {\n      samples: this.getSamples(),\n      summary: this.getSummary(),\n      config: this.config,\n      timestamp: Date.now()\n    };\n  }\n};\nfunction generateDashboardHTML(monitor) {\n  const summary = monitor.getSummary();\n  const samples = monitor.getSamples();\n  const lastSample = samples[samples.length - 1];\n  const formatBytes2 = (bytes) => {\n    if (bytes < 1024)\n      return `${bytes} B`;\n    if (bytes < 1024 * 1024)\n      return `${(bytes / 1024).toFixed(1)} KB`;\n    if (bytes < 1024 * 1024 * 1024)\n      return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n    return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n  };\n  const formatDuration = (ms) => {\n    if (ms < 1e3)\n      return `${ms.toFixed(0)}ms`;\n    if (ms < 6e4)\n      return `${(ms / 1e3).toFixed(1)}s`;\n    return `${(ms / 6e4).toFixed(1)}m`;\n  };\n  return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>edgeFlow.js Performance Dashboard</title>\n  <style>\n    :root {\n      --bg-primary: #0d1117;\n      --bg-secondary: #161b22;\n      --bg-tertiary: #21262d;\n      --text-primary: #f0f6fc;\n      --text-secondary: #8b949e;\n      --accent: #58a6ff;\n      --success: #3fb950;\n      --warning: #d29922;\n      --error: #f85149;\n    }\n    \n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n    \n    body {\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n      background: var(--bg-primary);\n      color: var(--text-primary);\n      line-height: 1.6;\n    }\n    \n    .dashboard {\n      max-width: 1400px;\n      margin: 0 auto;\n      padding: 24px;\n    }\n    \n    header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 32px;\n      padding-bottom: 16px;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    h1 {\n      font-size: 24px;\n      font-weight: 600;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n    \n    .status {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      font-size: 14px;\n      color: var(--text-secondary);\n    }\n    \n    .status-dot {\n      width: 8px;\n      height: 8px;\n      border-radius: 50%;\n      background: var(--success);\n    }\n    \n    .grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      gap: 20px;\n      margin-bottom: 32px;\n    }\n    \n    .card {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n    }\n    \n    .card-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .card-title {\n      font-size: 14px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .card-value {\n      font-size: 36px;\n      font-weight: 700;\n      font-variant-numeric: tabular-nums;\n    }\n    \n    .card-value.small {\n      font-size: 24px;\n    }\n    \n    .card-unit {\n      font-size: 14px;\n      color: var(--text-secondary);\n      margin-left: 4px;\n    }\n    \n    .card-change {\n      font-size: 12px;\n      padding: 4px 8px;\n      border-radius: 4px;\n    }\n    \n    .card-change.up {\n      background: rgba(63, 185, 80, 0.2);\n      color: var(--success);\n    }\n    \n    .card-change.down {\n      background: rgba(248, 81, 73, 0.2);\n      color: var(--error);\n    }\n    \n    .progress-bar {\n      height: 8px;\n      background: var(--bg-tertiary);\n      border-radius: 4px;\n      overflow: hidden;\n      margin-top: 12px;\n    }\n    \n    .progress-fill {\n      height: 100%;\n      border-radius: 4px;\n      transition: width 0.3s ease;\n    }\n    \n    .progress-fill.blue { background: var(--accent); }\n    .progress-fill.green { background: var(--success); }\n    .progress-fill.yellow { background: var(--warning); }\n    .progress-fill.red { background: var(--error); }\n    \n    .chart-container {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n      margin-bottom: 20px;\n    }\n    \n    .chart-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .chart-title {\n      font-size: 16px;\n      font-weight: 600;\n    }\n    \n    .chart {\n      height: 200px;\n      position: relative;\n    }\n    \n    .chart-line {\n      stroke: var(--accent);\n      stroke-width: 2;\n      fill: none;\n    }\n    \n    .chart-area {\n      fill: url(#chartGradient);\n      opacity: 0.3;\n    }\n    \n    .chart-grid {\n      stroke: var(--bg-tertiary);\n      stroke-width: 1;\n    }\n    \n    .table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n    \n    .table th,\n    .table td {\n      padding: 12px 16px;\n      text-align: left;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    .table th {\n      font-size: 12px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .table td {\n      font-variant-numeric: tabular-nums;\n    }\n    \n    footer {\n      text-align: center;\n      padding: 24px;\n      color: var(--text-secondary);\n      font-size: 14px;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"dashboard\">\n    <header>\n      <h1>\n        <svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\">\n          <rect width=\"32\" height=\"32\" rx=\"8\" fill=\"var(--accent)\"/>\n          <path d=\"M8 16L14 10L20 16L26 10\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n          <path d=\"M8 22L14 16L20 22L26 16\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\"/>\n        </svg>\n        edgeFlow.js Performance Dashboard\n      </h1>\n      <div class=\"status\">\n        <div class=\"status-dot\"></div>\n        Running for ${formatDuration(summary.uptime)}\n      </div>\n    </header>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Total Inferences</span>\n        </div>\n        <div class=\"card-value\">${summary.totalInferences.toLocaleString()}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg Inference Time</span>\n        </div>\n        <div class=\"card-value\">${summary.avgInferenceTime.toFixed(1)}<span class=\"card-unit\">ms</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Throughput</span>\n        </div>\n        <div class=\"card-value\">${summary.avgThroughput.toFixed(1)}<span class=\"card-unit\">ops/s</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg FPS</span>\n        </div>\n        <div class=\"card-value\">${Math.round(summary.avgFPS)}</div>\n      </div>\n    </div>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Memory Usage</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes2(lastSample?.memory.usedHeap ?? 0)}</div>\n        <div class=\"progress-bar\">\n          <div class=\"progress-fill ${summary.avgMemoryUsage > 0.8 ? \"red\" : summary.avgMemoryUsage > 0.6 ? \"yellow\" : \"green\"}\" \n               style=\"width: ${(summary.avgMemoryUsage * 100).toFixed(0)}%\"></div>\n        </div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Tensor Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes2(lastSample?.memory.tensorMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Cache Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes2(lastSample?.memory.cacheMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Queue Length</span>\n        </div>\n        <div class=\"card-value small\">${lastSample?.inference.queueLength ?? 0}</div>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Inference Time History</span>\n      </div>\n      <div class=\"chart\">\n        <svg width=\"100%\" height=\"100%\" viewBox=\"0 0 600 200\" preserveAspectRatio=\"none\">\n          <defs>\n            <linearGradient id=\"chartGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stop-color=\"var(--accent)\" stop-opacity=\"0.5\"/>\n              <stop offset=\"100%\" stop-color=\"var(--accent)\" stop-opacity=\"0\"/>\n            </linearGradient>\n          </defs>\n          ${generateChartPath(samples)}\n        </svg>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Recent Samples</span>\n      </div>\n      <table class=\"table\">\n        <thead>\n          <tr>\n            <th>Time</th>\n            <th>Inferences</th>\n            <th>Avg Time</th>\n            <th>Throughput</th>\n            <th>Memory</th>\n            <th>FPS</th>\n          </tr>\n        </thead>\n        <tbody>\n          ${samples.slice(-10).reverse().map((s) => `\n            <tr>\n              <td>${new Date(s.timestamp).toLocaleTimeString()}</td>\n              <td>${s.inference.count}</td>\n              <td>${s.inference.avgTime.toFixed(2)}ms</td>\n              <td>${s.inference.throughput.toFixed(1)}/s</td>\n              <td>${formatBytes2(s.memory.usedHeap)}</td>\n              <td>${s.system.fps}</td>\n            </tr>\n          `).join(\"\")}\n        </tbody>\n      </table>\n    </div>\n    \n    <footer>\n      Generated at ${(/* @__PURE__ */ new Date()).toLocaleString()} | edgeFlow.js Performance Monitor\n    </footer>\n  </div>\n</body>\n</html>\n  `.trim();\n}\nfunction generateChartPath(samples) {\n  if (samples.length < 2)\n    return \"\";\n  const width = 600;\n  const height = 180;\n  const padding = 10;\n  const times = samples.map((s) => s.inference.avgTime);\n  const maxTime = Math.max(...times, 1);\n  const points = samples.map((s, i) => {\n    const x = padding + i / (samples.length - 1) * (width - 2 * padding);\n    const y = height - padding - s.inference.avgTime / maxTime * (height - 2 * padding);\n    return `${x},${y}`;\n  });\n  const linePath = `M ${points.join(\" L \")}`;\n  const areaPath = `M ${padding},${height - padding} L ${points.join(\" L \")} L ${width - padding},${height - padding} Z`;\n  const gridLines = [];\n  for (let i = 0; i <= 4; i++) {\n    const y = padding + i / 4 * (height - 2 * padding);\n    gridLines.push(`<line class=\"chart-grid\" x1=\"${padding}\" y1=\"${y}\" x2=\"${width - padding}\" y2=\"${y}\"/>`);\n  }\n  return `\n    ${gridLines.join(\"\\n\")}\n    <path class=\"chart-area\" d=\"${areaPath}\"/>\n    <path class=\"chart-line\" d=\"${linePath}\"/>\n  `;\n}\nfunction generateAsciiDashboard(monitor) {\n  const summary = monitor.getSummary();\n  const samples = monitor.getSamples();\n  const lastSample = samples[samples.length - 1];\n  const formatBytes2 = (bytes) => {\n    if (bytes < 1024)\n      return `${bytes} B`;\n    if (bytes < 1024 * 1024)\n      return `${(bytes / 1024).toFixed(1)} KB`;\n    if (bytes < 1024 * 1024 * 1024)\n      return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n    return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n  };\n  const bar = (value, max, width = 20) => {\n    const filled = Math.round(value / max * width);\n    return \"\\u2588\".repeat(filled) + \"\\u2591\".repeat(width - filled);\n  };\n  const lines = [\n    \"\\u2554\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2557\",\n    \"\\u2551             edgeFlow.js Performance Monitor Dashboard                   \\u2551\",\n    \"\\u2560\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2563\",\n    \"\\u2551                                                                          \\u2551\",\n    `\\u2551  Total Inferences:  ${summary.totalInferences.toString().padStart(10)}                                      \\u2551`,\n    `\\u2551  Avg Inference:     ${summary.avgInferenceTime.toFixed(2).padStart(10)}ms                                     \\u2551`,\n    `\\u2551  Throughput:        ${summary.avgThroughput.toFixed(2).padStart(10)} ops/s                                 \\u2551`,\n    `\\u2551  Avg FPS:           ${Math.round(summary.avgFPS).toString().padStart(10)}                                      \\u2551`,\n    \"\\u2551                                                                          \\u2551\",\n    \"\\u255F\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2562\",\n    \"\\u2551 Memory Usage                                                             \\u2551\",\n    `\\u2551  Heap:    ${bar(summary.avgMemoryUsage, 1)} ${(summary.avgMemoryUsage * 100).toFixed(0).padStart(3)}%            \\u2551`,\n    `\\u2551  Used:    ${formatBytes2(lastSample?.memory.usedHeap ?? 0).padStart(10)}                                          \\u2551`,\n    `\\u2551  Tensor:  ${formatBytes2(lastSample?.memory.tensorMemory ?? 0).padStart(10)}                                          \\u2551`,\n    `\\u2551  Cache:   ${formatBytes2(lastSample?.memory.cacheMemory ?? 0).padStart(10)}                                          \\u2551`,\n    \"\\u2551                                                                          \\u2551\",\n    \"\\u255F\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2562\",\n    \"\\u2551 Inference Time History (last 30 samples)                                 \\u2551\",\n    \"\\u2551                                                                          \\u2551\"\n  ];\n  const recentSamples = samples.slice(-30);\n  if (recentSamples.length > 0) {\n    const times = recentSamples.map((s) => s.inference.avgTime);\n    const maxTime = Math.max(...times, 1);\n    const chartHeight = 5;\n    for (let row = chartHeight; row > 0; row--) {\n      let line = \"\\u2551  \";\n      for (const time of times) {\n        const height = Math.ceil(time / maxTime * chartHeight);\n        line += height >= row ? \"\\u2593\" : \" \";\n      }\n      lines.push(line.padEnd(76) + \"\\u2551\");\n    }\n    lines.push(\"\\u2551  \" + \"\\u2500\".repeat(30) + \"                                            \\u2551\");\n  }\n  lines.push(\"\\u2551                                                                          \\u2551\");\n  lines.push(`\\u2551  Last updated: ${(/* @__PURE__ */ new Date()).toLocaleString().padEnd(40)}             \\u2551`);\n  lines.push(\"\\u255A\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255D\");\n  return lines.join(\"\\n\");\n}\nvar globalMonitor = null;\nfunction getMonitor(config) {\n  if (!globalMonitor || config) {\n    globalMonitor = new PerformanceMonitor(config);\n  }\n  return globalMonitor;\n}\nfunction startMonitoring(config) {\n  const monitor = getMonitor(config);\n  monitor.start();\n  return monitor;\n}\nfunction stopMonitoring() {\n  globalMonitor?.stop();\n}\n\n// dist/tools/index.js\nasync function quantize(model, options) {\n  const modelData = model instanceof ArrayBuffer ? model : await getModelData(model);\n  const originalSize = modelData.byteLength;\n  let quantizedData;\n  let layersQuantized = 0;\n  let layersSkipped = 0;\n  switch (options.method) {\n    case \"int8\":\n      ({ data: quantizedData, layersQuantized, layersSkipped } = quantizeInt8(modelData, options));\n      break;\n    case \"uint8\":\n      ({ data: quantizedData, layersQuantized, layersSkipped } = quantizeUint8(modelData, options));\n      break;\n    case \"float16\":\n      ({ data: quantizedData, layersQuantized, layersSkipped } = quantizeFloat16(modelData, options));\n      break;\n    case \"int4\":\n      ({ data: quantizedData, layersQuantized, layersSkipped } = quantizeInt4(modelData, options));\n      break;\n    default:\n      quantizedData = modelData;\n  }\n  return {\n    modelData: quantizedData,\n    originalSize,\n    quantizedSize: quantizedData.byteLength,\n    compressionRatio: originalSize / quantizedData.byteLength,\n    stats: {\n      layersQuantized,\n      layersSkipped\n    }\n  };\n}\nasync function getModelData(_model) {\n  return new ArrayBuffer(0);\n}\nfunction quantizeInt8(data, _options) {\n  const input = new Float32Array(data);\n  const output = new Int8Array(input.length);\n  let max = 0;\n  for (let i = 0; i < input.length; i++) {\n    const abs = Math.abs(input[i] ?? 0);\n    if (abs > max)\n      max = abs;\n  }\n  const scale = max / 127;\n  for (let i = 0; i < input.length; i++) {\n    output[i] = Math.round((input[i] ?? 0) / scale);\n  }\n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0\n  };\n}\nfunction quantizeUint8(data, _options) {\n  const input = new Float32Array(data);\n  const output = new Uint8Array(input.length);\n  let min = Infinity, max = -Infinity;\n  for (let i = 0; i < input.length; i++) {\n    const val = input[i] ?? 0;\n    if (val < min)\n      min = val;\n    if (val > max)\n      max = val;\n  }\n  const scale = (max - min) / 255;\n  for (let i = 0; i < input.length; i++) {\n    output[i] = Math.round(((input[i] ?? 0) - min) / scale);\n  }\n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0\n  };\n}\nfunction quantizeFloat16(data, _options) {\n  const input = new Float32Array(data);\n  const output = new Uint16Array(input.length);\n  for (let i = 0; i < input.length; i++) {\n    output[i] = float32ToFloat162(input[i] ?? 0);\n  }\n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0\n  };\n}\nfunction quantizeInt4(data, _options) {\n  const input = new Float32Array(data);\n  const output = new Uint8Array(Math.ceil(input.length / 2));\n  let max = 0;\n  for (let i = 0; i < input.length; i++) {\n    const abs = Math.abs(input[i] ?? 0);\n    if (abs > max)\n      max = abs;\n  }\n  const scale = max / 7;\n  for (let i = 0; i < input.length; i += 2) {\n    const val1 = Math.round((input[i] ?? 0) / scale) + 8;\n    const val2 = Math.round((input[i + 1] ?? 0) / scale) + 8;\n    output[i / 2] = (val1 & 15) << 4 | val2 & 15;\n  }\n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0\n  };\n}\nfunction float32ToFloat162(value) {\n  const floatView = new Float32Array(1);\n  const int32View = new Int32Array(floatView.buffer);\n  floatView[0] = value;\n  const x = int32View[0] ?? 0;\n  let bits = x >> 16 & 32768;\n  let m = x >> 12 & 2047;\n  const e = x >> 23 & 255;\n  if (e < 103) {\n    return bits;\n  }\n  if (e > 142) {\n    bits |= 31744;\n    bits |= (e === 255 ? 0 : 1) && x & 8388607;\n    return bits;\n  }\n  if (e < 113) {\n    m |= 2048;\n    bits |= (m >> 114 - e) + (m >> 113 - e & 1);\n    return bits;\n  }\n  bits |= e - 112 << 10 | m >> 1;\n  bits += m & 1;\n  return bits;\n}\nasync function prune(model, options) {\n  const modelData = model instanceof ArrayBuffer ? model : await getModelData(model);\n  const weights = new Float32Array(modelData);\n  const total = weights.length;\n  const magnitudes = weights.map(Math.abs);\n  const sorted = [...magnitudes].sort((a, b) => a - b);\n  const thresholdIdx = Math.floor(options.sparsity * sorted.length);\n  const threshold = sorted[thresholdIdx] ?? 0;\n  let pruned = 0;\n  for (let i = 0; i < weights.length; i++) {\n    if (Math.abs(weights[i] ?? 0) < threshold) {\n      weights[i] = 0;\n      pruned++;\n    }\n  }\n  return {\n    modelData: weights.buffer,\n    actualSparsity: pruned / total,\n    parametersPruned: pruned,\n    totalParameters: total\n  };\n}\nasync function analyzeModel2(model) {\n  const size = model instanceof ArrayBuffer ? model.byteLength : model.metadata.sizeBytes;\n  const estimatedParams = Math.floor(size / 4);\n  return {\n    totalParameters: estimatedParams,\n    sizeBytes: size,\n    layers: [],\n    estimatedFlops: estimatedParams * 2,\n    // Rough estimate\n    memoryRequirements: {\n      weights: size,\n      activations: size * 0.1,\n      // Rough estimate\n      total: size * 1.1\n    }\n  };\n}\nasync function benchmark2(runFn, options = {}) {\n  const { warmupRuns = 3, runs = 10 } = options;\n  for (let i = 0; i < warmupRuns; i++) {\n    await runFn();\n  }\n  const times = [];\n  for (let i = 0; i < runs; i++) {\n    const start = performance.now();\n    await runFn();\n    times.push(performance.now() - start);\n  }\n  const sum2 = times.reduce((a, b) => a + b, 0);\n  const avgTime = sum2 / times.length;\n  const minTime = Math.min(...times);\n  const maxTime = Math.max(...times);\n  const squaredDiffs = times.map((t) => Math.pow(t - avgTime, 2));\n  const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / times.length;\n  const stdDev = Math.sqrt(avgSquaredDiff);\n  return {\n    avgTime,\n    minTime,\n    maxTime,\n    stdDev,\n    throughput: 1e3 / avgTime,\n    times\n  };\n}\nasync function exportModel2(model, format) {\n  const modelData = model instanceof ArrayBuffer ? model : await getModelData(model);\n  switch (format) {\n    case \"json\":\n      const array = new Float32Array(modelData);\n      return JSON.stringify(Array.from(array));\n    case \"binary\":\n    case \"onnx\":\n    default:\n      return modelData;\n  }\n}\n\n// dist/index.js\nasync function isSupported() {\n  const runtimes = await getAvailableRuntimes();\n  return Array.from(runtimes.values()).some((v) => v);\n}\nasync function getBestRuntimeType() {\n  const runtimes = await getAvailableRuntimes();\n  if (runtimes.get(\"webgpu\"))\n    return \"webgpu\";\n  if (runtimes.get(\"webnn\"))\n    return \"webnn\";\n  if (runtimes.get(\"wasm\"))\n    return \"wasm\";\n  return null;\n}\nasync function preload(models) {\n  const cache = new ModelDownloadCache();\n  await Promise.all(models.map(async (url) => {\n    if (!await cache.get(url)) {\n      const response = await fetch(url);\n      if (response.ok) {\n        await cache.put(url, response);\n      }\n    }\n  }));\n}\nvar VERSION = \"0.1.0\";\nasync function getInfo() {\n  const runtimes = await getAvailableRuntimes();\n  return {\n    version: VERSION,\n    runtimes: {\n      webgpu: runtimes.get(\"webgpu\") ?? false,\n      webnn: runtimes.get(\"webnn\") ?? false,\n      wasm: runtimes.get(\"wasm\") ?? false,\n      auto: true\n    },\n    features: [\n      \"concurrent-execution\",\n      \"batch-processing\",\n      \"memory-management\",\n      \"model-caching\",\n      \"quantization\"\n    ]\n  };\n}\nexport {\n  AudioPreprocessor,\n  BasePipeline,\n  Cache,\n  EMOTION_LABELS,\n  EdgeFlowDebugger,\n  EdgeFlowError,\n  EdgeFlowTensor,\n  ErrorCodes,\n  FeatureExtractionPipeline,\n  IMAGENET_LABELS,\n  ImageClassificationPipeline,\n  ImagePreprocessor,\n  ImageSegmentationPipeline,\n  InferenceCache,\n  InferenceScheduler,\n  LoadedModelImpl,\n  MemoryManager,\n  MemoryScope,\n  ModelCache,\n  ModelDownloadCache,\n  POPULAR_MODELS,\n  PerformanceMonitor,\n  RuntimeManager,\n  SENTIMENT_LABELS,\n  SentimentAnalysisPipeline,\n  TextClassificationPipeline,\n  TextGenerationPipeline,\n  Tokenizer,\n  TransformersAdapterRuntime,\n  VERSION,\n  WASMRuntime,\n  WebGPURuntime,\n  WebNNRuntime,\n  add,\n  analyzeModel2 as analyzeModel,\n  analyzeModel as analyzeModelDetailed,\n  arange,\n  argmax,\n  benchmark2 as benchmark,\n  benchmarkMemory,\n  benchmarkSuite,\n  cancelPreload,\n  clearModelCache,\n  compareBenchmarks,\n  compose,\n  concat,\n  configureScheduler,\n  createAsciiHistogram,\n  createAudioPreprocessor,\n  createBasicTokenizer,\n  createCache,\n  createFeatureExtractionPipeline,\n  createImageClassificationPipeline,\n  createImagePreprocessor,\n  createImageSegmentationPipeline,\n  createPipelines,\n  createSentimentAnalysisPipeline,\n  createTensorHeatmap,\n  createTextClassificationPipeline,\n  createTextGenerationPipeline,\n  createWASMRuntime,\n  createWebGPURuntime,\n  createWebNNRuntime,\n  deleteCachedModel,\n  dequantizeFloat16,\n  dequantizeInt8,\n  dequantizeTensor,\n  dequantizeUint8,\n  disableDebugging,\n  div,\n  downloadConfig,\n  downloadModel,\n  downloadTokenizer,\n  enableDebugging,\n  exportModel2 as exportModel,\n  exportModel as exportModelAdvanced,\n  eye,\n  float16ToFloat32,\n  formatBenchmarkResult,\n  formatComparisonResult,\n  formatTensorInspection,\n  fromHub,\n  fromTask,\n  full,\n  gc,\n  generateAsciiDashboard,\n  generateDashboardHTML,\n  getAvailableRuntimes,\n  getBestRuntime,\n  getBestRuntimeType,\n  getCachedModel,\n  getDebugger,\n  getDefaultModel,\n  getDeviceProfile,\n  getInfo,\n  getMemoryManager,\n  getMemoryStats,\n  getModelCacheStats,\n  getModelInfo,\n  getMonitor,\n  getPipelineFactory,\n  getPluginMiddleware,\n  getPluginPipeline,\n  getPreloadStatus,\n  getPreloadedModel,\n  getRuntimeManager,\n  getScheduler,\n  getTransformersAdapter,\n  inspectTensor,\n  isModelCached,\n  isSupported,\n  linspace,\n  listPlugins,\n  loadModel,\n  loadModelData,\n  loadModelFromBuffer,\n  loadTokenizer,\n  loadTokenizerFromHub,\n  matmul,\n  mean,\n  modelExists,\n  mul,\n  ones,\n  parallel,\n  pipeline,\n  preload,\n  preloadModel,\n  preloadModels,\n  preprocessText,\n  prune,\n  pruneModel,\n  pruneTensor,\n  quantize,\n  quantizeModel,\n  quantizeTensor,\n  randn,\n  random,\n  recommendModelVariant,\n  recommendQuantization,\n  registerAllBackends,\n  registerPipeline,\n  registerPlugin,\n  registerRuntime,\n  release,\n  relu,\n  resetDeviceProfile,\n  runBatchInference,\n  benchmark as runBenchmark,\n  runInference,\n  setScheduler,\n  sigmoid,\n  softmax,\n  startMonitoring,\n  stopMonitoring,\n  sub,\n  sum,\n  tanh,\n  tensor,\n  unregisterPlugin,\n  useTransformersBackend,\n  visualizeModelArchitecture,\n  withMemoryScope,\n  withMemoryScopeSync,\n  zeros\n};\n//# sourceMappingURL=edgeflow.browser.js.map\n"
  },
  {
    "path": "dist/index.d.ts",
    "content": "/**\n * edgeFlow.js\n *\n * Lightweight, high-performance browser ML inference framework\n * with native concurrency support.\n *\n * @example\n * ```typescript\n * import { pipeline } from 'edgeflow';\n *\n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n *\n * // Run inference\n * const result = await sentiment.run('I love this product!');\n * console.log(result); // { label: 'positive', score: 0.98 }\n *\n * // Batch processing\n * const results = await sentiment.run([\n *   'This is amazing!',\n *   'This is terrible.'\n * ]);\n *\n * // Concurrent execution with different models\n * const classifier = await pipeline('text-classification');\n * const extractor = await pipeline('feature-extraction');\n *\n * const [classification, features] = await Promise.all([\n *   classifier.run('Sample text'),\n *   extractor.run('Sample text')\n * ]);\n * ```\n *\n * @packageDocumentation\n */\nexport type { DataType, TypedArray, Shape, Tensor, RuntimeType, RuntimeCapabilities, Runtime, ModelFormat, QuantizationType, ModelMetadata, ModelIOSpec, ModelLoadOptions, LoadedModel, TaskPriority, TaskStatus, InferenceTask, SchedulerOptions, MemoryStats, MemoryPoolConfig, PipelineTask, PipelineConfig, PipelineOptions, TokenizerConfig, TokenizedOutput, EventType, EdgeFlowEvent, EventListener, ErrorCode, } from './core/types.js';\nexport { EdgeFlowError, ErrorCodes } from './core/types.js';\nexport { EdgeFlowTensor, tensor, zeros, ones, full, random, randn, arange, linspace, eye, add, sub, mul, div, matmul, softmax, relu, sigmoid, tanh, sum, mean, argmax, concat, } from './core/tensor.js';\nexport { InferenceScheduler, getScheduler, setScheduler, configureScheduler, } from './core/scheduler.js';\nexport { MemoryManager, MemoryScope, ModelCache, withMemoryScope, withMemoryScopeSync, getMemoryManager, getMemoryStats, release, gc, } from './core/memory.js';\nexport { registerPlugin, getPluginPipeline, getPluginMiddleware, listPlugins, unregisterPlugin, type EdgeFlowPlugin, type PluginPipelineEntry, type PluginBackendEntry, type PluginMiddleware, } from './core/plugin.js';\nexport { getDeviceProfile, recommendQuantization, recommendModelVariant, resetDeviceProfile, type DeviceProfile, type DeviceTier, type ModelRecommendation, } from './core/device-profiler.js';\nexport { compose, parallel, type CompositionStage, type CompositionResult, type ComposedPipeline, } from './core/composer.js';\nexport { RuntimeManager, LoadedModelImpl, loadModel, loadModelFromBuffer, runInference, runBatchInference, getRuntimeManager, registerRuntime, getBestRuntime, getAvailableRuntimes, } from './core/runtime.js';\nexport { WebGPURuntime, createWebGPURuntime, WebNNRuntime, createWebNNRuntime, WASMRuntime, createWASMRuntime, registerAllBackends, TransformersAdapterRuntime, useTransformersBackend, getTransformersAdapter, type TransformersAdapterOptions, type TransformersPipelineFactory, } from './backends/index.js';\nexport { pipeline, createPipelines, BasePipeline, registerPipeline, getPipelineFactory, SENTIMENT_LABELS, EMOTION_LABELS, IMAGENET_LABELS, type PipelineResult, type TextClassificationResult, type FeatureExtractionResult, type ImageClassificationResult, type ObjectDetectionResult, TextClassificationPipeline, SentimentAnalysisPipeline, FeatureExtractionPipeline, ImageClassificationPipeline, TextGenerationPipeline, ImageSegmentationPipeline, createTextClassificationPipeline, createSentimentAnalysisPipeline, createFeatureExtractionPipeline, createImageClassificationPipeline, createTextGenerationPipeline, createImageSegmentationPipeline, type PipelineFactoryOptions, type TextClassificationOptions, type FeatureExtractionOptions, type ImageClassificationOptions, type ImageInput, type TextGenerationOptions, type TextGenerationResult, type GenerationStreamEvent, type ChatMessage, type ChatOptions, type ChatTemplateType, type LLMLoadProgress, type ImageSegmentationOptions, type ImageSegmentationResult, type PointPrompt, type BoxPrompt, type ModelLoadProgress, } from './pipelines/index.js';\nexport { Tokenizer, createBasicTokenizer, loadTokenizer, loadTokenizerFromHub, type TokenizerModel, type TokenizerOptions, ImagePreprocessor, AudioPreprocessor, preprocessText, createImagePreprocessor, createAudioPreprocessor, type ImagePreprocessorOptions, type AudioPreprocessorOptions, type TextPreprocessorOptions, Cache, InferenceCache, ModelDownloadCache, createCache, type CacheStrategy, type CacheOptions, type CacheStats, loadModelData, preloadModel, preloadModels, isModelCached, getCachedModel, deleteCachedModel, clearModelCache, getModelCacheStats, getPreloadStatus, cancelPreload, getPreloadedModel, type DownloadProgress, type ModelLoaderOptions, type PreloadOptions, fromHub, fromTask, downloadModel, downloadTokenizer, downloadConfig, modelExists, getModelInfo, getDefaultModel, POPULAR_MODELS, type HubOptions, type HubDownloadProgress, type ModelConfig, type ModelBundle, type PopularModelTask, } from './utils/index.js';\nexport { quantize, type QuantizationOptions, type QuantizationResult, prune, type PruningOptions, type PruningResult, analyzeModel, type ModelAnalysis, benchmark, type BenchmarkOptions, type BenchmarkResult, exportModel, quantizeModel, quantizeTensor, dequantizeTensor, pruneModel, pruneTensor, analyzeModelDetailed, exportModelAdvanced, dequantizeInt8, dequantizeUint8, dequantizeFloat16, float16ToFloat32, type QuantizationMethod, type AdvancedQuantizationOptions, type QuantizationProgress, type AdvancedQuantizationResult, type LayerQuantizationStats, type QuantizationStats, type AdvancedPruningOptions, type AdvancedPruningResult, type DetailedModelAnalysis, type ExportFormat, type ExportOptions, EdgeFlowDebugger, getDebugger, enableDebugging, disableDebugging, inspectTensor, formatTensorInspection, createAsciiHistogram, createTensorHeatmap, visualizeModelArchitecture, type DebuggerConfig, type TensorInspection, type TensorStats, type HistogramData, type InferenceTrace, type OperationTrace, type DebugEvent, type DebugPerformanceMetrics, PerformanceMonitor, getMonitor, startMonitoring, stopMonitoring, generateDashboardHTML, generateAsciiDashboard, type MonitorConfig, type PerformanceSample, type InferenceMetrics, type MemoryMetrics, type SystemMetrics, type AlertConfig, type AlertEvent, type WidgetData, runBenchmark, compareBenchmarks, benchmarkSuite, benchmarkMemory, formatBenchmarkResult, formatComparisonResult, type DetailedBenchmarkOptions, type DetailedBenchmarkResult, type CompareBenchmarkResult, type MemoryBenchmarkResult, } from './tools/index.js';\n/**\n * Check if edgeFlow is supported in the current environment\n */\nexport declare function isSupported(): Promise<boolean>;\n/**\n * Get the best available runtime type\n */\nexport declare function getBestRuntimeType(): Promise<RuntimeType | null>;\n/**\n * Preload models for faster subsequent loading\n */\nexport declare function preload(models: string[]): Promise<void>;\n/**\n * edgeFlow.js version\n */\nexport declare const VERSION = \"0.1.0\";\n/**\n * Get framework info\n */\nexport declare function getInfo(): Promise<{\n    version: string;\n    runtimes: Record<RuntimeType, boolean>;\n    features: string[];\n}>;\nimport { RuntimeType } from './core/types.js';\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/index.js",
    "content": "/**\n * edgeFlow.js\n *\n * Lightweight, high-performance browser ML inference framework\n * with native concurrency support.\n *\n * @example\n * ```typescript\n * import { pipeline } from 'edgeflow';\n *\n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n *\n * // Run inference\n * const result = await sentiment.run('I love this product!');\n * console.log(result); // { label: 'positive', score: 0.98 }\n *\n * // Batch processing\n * const results = await sentiment.run([\n *   'This is amazing!',\n *   'This is terrible.'\n * ]);\n *\n * // Concurrent execution with different models\n * const classifier = await pipeline('text-classification');\n * const extractor = await pipeline('feature-extraction');\n *\n * const [classification, features] = await Promise.all([\n *   classifier.run('Sample text'),\n *   extractor.run('Sample text')\n * ]);\n * ```\n *\n * @packageDocumentation\n */\n// Error class\nexport { EdgeFlowError, ErrorCodes } from './core/types.js';\n// Tensor operations\nexport { EdgeFlowTensor, tensor, zeros, ones, full, random, randn, arange, linspace, eye, add, sub, mul, div, matmul, softmax, relu, sigmoid, tanh, sum, mean, argmax, concat, } from './core/tensor.js';\n// Scheduler\nexport { InferenceScheduler, getScheduler, setScheduler, configureScheduler, } from './core/scheduler.js';\n// Memory management\nexport { MemoryManager, MemoryScope, ModelCache, withMemoryScope, withMemoryScopeSync, getMemoryManager, getMemoryStats, release, gc, } from './core/memory.js';\n// Plugin system\nexport { registerPlugin, getPluginPipeline, getPluginMiddleware, listPlugins, unregisterPlugin, } from './core/plugin.js';\n// Device profiling\nexport { getDeviceProfile, recommendQuantization, recommendModelVariant, resetDeviceProfile, } from './core/device-profiler.js';\n// Pipeline composition\nexport { compose, parallel, } from './core/composer.js';\n// Runtime management\nexport { RuntimeManager, LoadedModelImpl, loadModel, loadModelFromBuffer, runInference, runBatchInference, getRuntimeManager, registerRuntime, getBestRuntime, getAvailableRuntimes, } from './core/runtime.js';\n// ============================================================================\n// Backend Exports\n// ============================================================================\nexport { WebGPURuntime, createWebGPURuntime, WebNNRuntime, createWebNNRuntime, WASMRuntime, createWASMRuntime, registerAllBackends, \n// transformers.js adapter\nTransformersAdapterRuntime, useTransformersBackend, getTransformersAdapter, } from './backends/index.js';\n// ============================================================================\n// Pipeline Exports\n// ============================================================================\nexport { \n// Factory function\npipeline, createPipelines, \n// Base classes\nBasePipeline, registerPipeline, getPipelineFactory, \n// Labels\nSENTIMENT_LABELS, EMOTION_LABELS, IMAGENET_LABELS, \n// Pipelines\nTextClassificationPipeline, SentimentAnalysisPipeline, FeatureExtractionPipeline, ImageClassificationPipeline, TextGenerationPipeline, ImageSegmentationPipeline, \n// Factory functions\ncreateTextClassificationPipeline, createSentimentAnalysisPipeline, createFeatureExtractionPipeline, createImageClassificationPipeline, createTextGenerationPipeline, createImageSegmentationPipeline, } from './pipelines/index.js';\n// ============================================================================\n// Utility Exports\n// ============================================================================\nexport { \n// Tokenizer\nTokenizer, createBasicTokenizer, loadTokenizer, loadTokenizerFromHub, \n// Preprocessor\nImagePreprocessor, AudioPreprocessor, preprocessText, createImagePreprocessor, createAudioPreprocessor, \n// Cache\nCache, InferenceCache, ModelDownloadCache, createCache, \n// Model Loader (Preloading, Sharding, Resume, Caching)\nloadModelData, preloadModel, preloadModels, isModelCached, getCachedModel, deleteCachedModel, clearModelCache, getModelCacheStats, getPreloadStatus, cancelPreload, getPreloadedModel, \n// HuggingFace Hub Integration\nfromHub, fromTask, downloadModel, downloadTokenizer, downloadConfig, modelExists, getModelInfo, getDefaultModel, POPULAR_MODELS, } from './utils/index.js';\n// ============================================================================\n// Tools Exports\n// ============================================================================\nexport { \n// Quantization (basic)\nquantize, \n// Pruning (basic)\nprune, \n// Analysis (basic)\nanalyzeModel, \n// Benchmarking (basic)\nbenchmark, \n// Export\nexportModel, \n// Advanced Quantization\nquantizeModel, quantizeTensor, dequantizeTensor, pruneModel, pruneTensor, analyzeModelDetailed, exportModelAdvanced, dequantizeInt8, dequantizeUint8, dequantizeFloat16, float16ToFloat32, \n// Debugging Tools\nEdgeFlowDebugger, getDebugger, enableDebugging, disableDebugging, inspectTensor, formatTensorInspection, createAsciiHistogram, createTensorHeatmap, visualizeModelArchitecture, \n// Performance Monitor\nPerformanceMonitor, getMonitor, startMonitoring, stopMonitoring, generateDashboardHTML, generateAsciiDashboard, \n// Benchmark utilities\nrunBenchmark, compareBenchmarks, benchmarkSuite, benchmarkMemory, formatBenchmarkResult, formatComparisonResult, } from './tools/index.js';\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n/**\n * Check if edgeFlow is supported in the current environment\n */\nexport async function isSupported() {\n    const runtimes = await getAvailableRuntimes();\n    return Array.from(runtimes.values()).some(v => v);\n}\n/**\n * Get the best available runtime type\n */\nexport async function getBestRuntimeType() {\n    const runtimes = await getAvailableRuntimes();\n    if (runtimes.get('webgpu'))\n        return 'webgpu';\n    if (runtimes.get('webnn'))\n        return 'webnn';\n    if (runtimes.get('wasm'))\n        return 'wasm';\n    return null;\n}\n/**\n * Preload models for faster subsequent loading\n */\nexport async function preload(models) {\n    const cache = new ModelDownloadCache();\n    await Promise.all(models.map(async (url) => {\n        if (!(await cache.get(url))) {\n            const response = await fetch(url);\n            if (response.ok) {\n                await cache.put(url, response);\n            }\n        }\n    }));\n}\n// ============================================================================\n// Version Info\n// ============================================================================\n/**\n * edgeFlow.js version\n */\nexport const VERSION = '0.1.0';\n/**\n * Get framework info\n */\nexport async function getInfo() {\n    const runtimes = await getAvailableRuntimes();\n    return {\n        version: VERSION,\n        runtimes: {\n            webgpu: runtimes.get('webgpu') ?? false,\n            webnn: runtimes.get('webnn') ?? false,\n            wasm: runtimes.get('wasm') ?? false,\n            auto: true,\n        },\n        features: [\n            'concurrent-execution',\n            'batch-processing',\n            'memory-management',\n            'model-caching',\n            'quantization',\n        ],\n    };\n}\nimport { getAvailableRuntimes } from './core/runtime.js';\nimport { ModelDownloadCache } from './utils/cache.js';\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/pipelines/automatic-speech-recognition.d.ts",
    "content": "/**\n * edgeFlow.js - Automatic Speech Recognition Pipeline\n *\n * Transcribe audio to text using Whisper ONNX models (encoder + decoder).\n */\nimport { BasePipeline, PipelineResult } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { type AudioInput } from '../utils/preprocessor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nexport interface ASROptions extends PipelineOptions {\n    language?: string;\n    task?: 'transcribe' | 'translate';\n    returnTimestamps?: boolean | 'word' | 'chunk';\n    maxDuration?: number;\n    chunkDuration?: number;\n    chunkOverlap?: number;\n}\nexport interface WordTimestamp {\n    word: string;\n    start: number;\n    end: number;\n    confidence?: number;\n}\nexport interface ChunkTimestamp {\n    text: string;\n    start: number;\n    end: number;\n}\nexport interface ASRResult extends PipelineResult {\n    text: string;\n    language?: string;\n    words?: WordTimestamp[];\n    chunks?: ChunkTimestamp[];\n}\nexport declare class AutomaticSpeechRecognitionPipeline extends BasePipeline<AudioInput | AudioInput[], ASRResult | ASRResult[]> {\n    private audioPreprocessor;\n    private tokenizer;\n    private encoderModel;\n    private decoderModel;\n    private encoderUrl;\n    private decoderUrl;\n    private tokenizerUrl;\n    constructor(config?: PipelineConfig);\n    initialize(): Promise<void>;\n    setTokenizer(tokenizer: Tokenizer): void;\n    run(input: AudioInput | AudioInput[], options?: PipelineOptions): Promise<ASRResult | ASRResult[]>;\n    private transcribeSingle;\n    private buildInitialTokens;\n    private getLanguageToken;\n    /**\n     * Autoregressive decoder loop similar to text-generation.\n     * Feeds encoder hidden states + growing token sequence to decoder.\n     */\n    private autoregressiveDecode;\n    private extractTimestamps;\n    processLongAudio(audio: AudioInput, options?: ASROptions): Promise<ASRResult>;\n    protected preprocess(input: AudioInput | AudioInput[]): Promise<EdgeFlowTensor[]>;\n    protected postprocess(outputs: EdgeFlowTensor[], options?: PipelineOptions): Promise<ASRResult | ASRResult[]>;\n    private decodeOutput;\n}\nexport declare function createASRPipeline(config?: PipelineConfig): AutomaticSpeechRecognitionPipeline;\n//# sourceMappingURL=automatic-speech-recognition.d.ts.map"
  },
  {
    "path": "dist/pipelines/automatic-speech-recognition.js",
    "content": "/**\n * edgeFlow.js - Automatic Speech Recognition Pipeline\n *\n * Transcribe audio to text using Whisper ONNX models (encoder + decoder).\n */\nimport { BasePipeline, registerPipeline } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { AudioPreprocessor } from '../utils/preprocessor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference, runInferenceNamed } from '../core/runtime.js';\n// ============================================================================\n// Default Model (Whisper-tiny, quantized encoder + decoder)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    encoder: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/encoder_model_quantized.onnx',\n    decoder: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/decoder_model_merged_quantized.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/tokenizer.json',\n};\n// Whisper special tokens\nconst SOT_TOKEN = 50258; // <|startoftranscript|>\nconst TRANSLATE_TOKEN = 50358; // <|translate|>\nconst TRANSCRIBE_TOKEN = 50359; // <|transcribe|>\nconst EOT_TOKEN = 50257; // <|endoftext|>\nconst NO_TIMESTAMPS_TOKEN = 50363; // <|notimestamps|>\nconst EN_TOKEN = 50259; // <|en|>\nconst MAX_DECODER_TOKENS = 448;\n// ============================================================================\n// ASR Pipeline\n// ============================================================================\nexport class AutomaticSpeechRecognitionPipeline extends BasePipeline {\n    audioPreprocessor;\n    tokenizer = null;\n    encoderModel = null;\n    decoderModel = null;\n    encoderUrl;\n    decoderUrl;\n    tokenizerUrl;\n    constructor(config) {\n        super(config ?? {\n            task: 'automatic-speech-recognition',\n            model: 'default',\n        });\n        this.encoderUrl = DEFAULT_MODELS.encoder;\n        this.decoderUrl = DEFAULT_MODELS.decoder;\n        this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n        this.audioPreprocessor = new AudioPreprocessor({\n            sampleRate: 16000,\n            nMels: 80,\n            nFft: 400,\n            hopLength: 160,\n            maxDuration: 30,\n        });\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.tokenizer) {\n            this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n        }\n        if (!this.encoderModel) {\n            const data = await loadModelData(this.encoderUrl, { cache: this.config.cache ?? true });\n            this.encoderModel = await loadModelFromBuffer(data);\n        }\n        if (!this.decoderModel) {\n            const data = await loadModelData(this.decoderUrl, { cache: this.config.cache ?? true });\n            this.decoderModel = await loadModelFromBuffer(data);\n        }\n    }\n    setTokenizer(tokenizer) {\n        this.tokenizer = tokenizer;\n    }\n    async run(input, options) {\n        await this.initialize();\n        const isBatch = Array.isArray(input);\n        const inputs = isBatch ? input : [input];\n        const opts = options ?? {};\n        const results = [];\n        for (const audio of inputs) {\n            const result = await this.transcribeSingle(audio, opts);\n            results.push(result);\n        }\n        return isBatch ? results : results[0];\n    }\n    async transcribeSingle(audio, options) {\n        const startTime = performance.now();\n        // 1. Preprocess audio → mel spectrogram\n        const melTensor = await this.audioPreprocessor.process(audio);\n        const melInput = new EdgeFlowTensor(melTensor.toFloat32Array(), [1, ...melTensor.shape], 'float32');\n        // 2. Run encoder\n        const encoderOutputs = await runInference(this.encoderModel, [melInput]);\n        const encoderHidden = encoderOutputs[0];\n        // 3. Autoregressive decoder loop\n        const task = options.task ?? 'transcribe';\n        const initialTokens = this.buildInitialTokens(task, options.language);\n        const generatedTokens = await this.autoregressiveDecode(encoderHidden, initialTokens);\n        // 4. Decode tokens to text\n        const text = this.tokenizer.decode(generatedTokens, true);\n        const result = {\n            text: text.trim(),\n            processingTime: performance.now() - startTime,\n        };\n        if (options.returnTimestamps) {\n            result.chunks = this.extractTimestamps(generatedTokens, text);\n        }\n        return result;\n    }\n    buildInitialTokens(task, language) {\n        const tokens = [SOT_TOKEN];\n        tokens.push(language ? this.getLanguageToken(language) : EN_TOKEN);\n        tokens.push(task === 'translate' ? TRANSLATE_TOKEN : TRANSCRIBE_TOKEN);\n        tokens.push(NO_TIMESTAMPS_TOKEN);\n        return tokens;\n    }\n    getLanguageToken(language) {\n        // Whisper language tokens start at 50259 for English\n        const langMap = {\n            en: 50259, zh: 50260, de: 50261, es: 50262, ru: 50263,\n            ko: 50264, fr: 50265, ja: 50266, pt: 50267, tr: 50268,\n            pl: 50269, ca: 50270, nl: 50271, ar: 50272, sv: 50273,\n            it: 50274, id: 50275, hi: 50276, fi: 50277, vi: 50278,\n        };\n        return langMap[language.toLowerCase()] ?? EN_TOKEN;\n    }\n    /**\n     * Autoregressive decoder loop similar to text-generation.\n     * Feeds encoder hidden states + growing token sequence to decoder.\n     */\n    async autoregressiveDecode(encoderHidden, initialTokens) {\n        const tokens = [...initialTokens];\n        for (let step = 0; step < MAX_DECODER_TOKENS; step++) {\n            const decoderInputIds = new EdgeFlowTensor(BigInt64Array.from(tokens.map(t => BigInt(t))), [1, tokens.length], 'int64');\n            const namedInputs = new Map();\n            namedInputs.set('input_ids', decoderInputIds);\n            namedInputs.set('encoder_hidden_states', encoderHidden);\n            const decoderOutputs = await runInferenceNamed(this.decoderModel, namedInputs);\n            const logits = decoderOutputs[0].toFloat32Array();\n            // Get logits for the last token position\n            const vocabSize = logits.length / tokens.length;\n            const lastTokenLogits = logits.slice((tokens.length - 1) * vocabSize);\n            // Greedy: argmax\n            let bestId = 0;\n            let bestVal = lastTokenLogits[0] ?? -Infinity;\n            for (let i = 1; i < lastTokenLogits.length; i++) {\n                if ((lastTokenLogits[i] ?? -Infinity) > bestVal) {\n                    bestVal = lastTokenLogits[i] ?? -Infinity;\n                    bestId = i;\n                }\n            }\n            if (bestId === EOT_TOKEN)\n                break;\n            tokens.push(bestId);\n        }\n        // Strip initial tokens to return only generated tokens\n        return tokens.slice(initialTokens.length);\n    }\n    extractTimestamps(_tokenIds, text) {\n        // Simplified timestamp extraction: split by punctuation\n        const words = text.split(/\\s+/).filter(w => w.length > 0);\n        const chunks = [];\n        const wordsPerSecond = 2.5;\n        let chunkText = '';\n        let chunkStart = 0;\n        for (let i = 0; i < words.length; i++) {\n            chunkText += (chunkText ? ' ' : '') + words[i];\n            if ((i + 1) % 5 === 0 || i === words.length - 1) {\n                const duration = chunkText.split(/\\s+/).length / wordsPerSecond;\n                chunks.push({\n                    text: chunkText,\n                    start: chunkStart,\n                    end: chunkStart + duration,\n                });\n                chunkStart = chunkStart + duration;\n                chunkText = '';\n            }\n        }\n        return chunks;\n    }\n    async processLongAudio(audio, options = {}) {\n        const chunkDuration = options.chunkDuration ?? 30;\n        const chunkOverlap = options.chunkOverlap ?? 5;\n        const rawTensor = await this.audioPreprocessor.processRaw(audio);\n        const audioData = rawTensor.toFloat32Array();\n        const sampleRate = 16000;\n        const chunkSamples = chunkDuration * sampleRate;\n        const overlapSamples = chunkOverlap * sampleRate;\n        const stepSamples = chunkSamples - overlapSamples;\n        const chunks = [];\n        for (let start = 0; start < audioData.length; start += stepSamples) {\n            const end = Math.min(start + chunkSamples, audioData.length);\n            const chunkAudio = audioData.slice(start, end);\n            const chunkResult = await this.run(new Float32Array(chunkAudio), options);\n            if (chunkResult.chunks) {\n                const timeOffset = start / sampleRate;\n                chunkResult.chunks = chunkResult.chunks.map(c => ({\n                    ...c,\n                    start: c.start + timeOffset,\n                    end: c.end + timeOffset,\n                }));\n            }\n            chunks.push(chunkResult);\n        }\n        const mergedText = chunks.map(c => c.text).join(' ');\n        const mergedChunks = chunks.flatMap(c => c.chunks ?? []);\n        return {\n            text: mergedText,\n            chunks: mergedChunks,\n        };\n    }\n    async preprocess(input) {\n        const inputs = Array.isArray(input) ? input : [input];\n        const tensors = await Promise.all(inputs.map(audio => this.audioPreprocessor.process(audio)));\n        if (tensors.length === 1) {\n            const t = tensors[0];\n            return [new EdgeFlowTensor(t.toFloat32Array(), [1, ...t.shape], 'float32')];\n        }\n        return tensors;\n    }\n    async postprocess(outputs, options) {\n        const opts = options ?? {};\n        const returnTimestamps = opts.returnTimestamps ?? false;\n        if (!outputs[0]) {\n            return { text: '' };\n        }\n        const outputData = outputs[0].toFloat32Array();\n        const shape = outputs[0].shape;\n        const text = this.decodeOutput(outputData, shape);\n        const result = { text };\n        if (returnTimestamps) {\n            result.chunks = this.extractTimestamps([], text);\n        }\n        return result;\n    }\n    decodeOutput(data, shape) {\n        const seqLen = shape[1] ?? data.length;\n        const vocabSize = shape[2] ?? 1;\n        const tokenIds = [];\n        if (vocabSize > 1) {\n            for (let i = 0; i < seqLen; i++) {\n                const offset = i * vocabSize;\n                let maxIdx = 0;\n                let maxVal = data[offset] ?? -Infinity;\n                for (let j = 1; j < vocabSize; j++) {\n                    if ((data[offset + j] ?? -Infinity) > maxVal) {\n                        maxVal = data[offset + j] ?? -Infinity;\n                        maxIdx = j;\n                    }\n                }\n                tokenIds.push(maxIdx);\n            }\n        }\n        else {\n            for (let i = 0; i < data.length; i++) {\n                tokenIds.push(Math.round(data[i] ?? 0));\n            }\n        }\n        if (this.tokenizer) {\n            return this.tokenizer.decode(tokenIds, true);\n        }\n        return tokenIds.join(' ');\n    }\n}\n// ============================================================================\n// Factory\n// ============================================================================\nexport function createASRPipeline(config) {\n    return new AutomaticSpeechRecognitionPipeline(config);\n}\nregisterPipeline('automatic-speech-recognition', (config) => new AutomaticSpeechRecognitionPipeline(config));\n//# sourceMappingURL=automatic-speech-recognition.js.map"
  },
  {
    "path": "dist/pipelines/base.d.ts",
    "content": "/**\n * edgeFlow.js - Base Pipeline\n *\n * Base class and utilities for all pipeline implementations.\n */\nimport { LoadedModel, PipelineConfig, PipelineOptions, PipelineTask } from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { ModelCache } from '../core/memory.js';\nimport { ModelDownloadCache } from '../utils/cache.js';\n/**\n * Pipeline result base interface\n */\nexport interface PipelineResult {\n    /** Processing time in milliseconds */\n    processingTime?: number;\n}\n/**\n * Text classification result\n */\nexport interface TextClassificationResult extends PipelineResult {\n    label: string;\n    score: number;\n}\n/**\n * Feature extraction result\n */\nexport interface FeatureExtractionResult extends PipelineResult {\n    embeddings: number[];\n}\n/**\n * Image classification result\n */\nexport interface ImageClassificationResult extends PipelineResult {\n    label: string;\n    score: number;\n}\n/**\n * Object detection result\n */\nexport interface ObjectDetectionResult extends PipelineResult {\n    label: string;\n    score: number;\n    box: {\n        x: number;\n        y: number;\n        width: number;\n        height: number;\n    };\n}\n/**\n * BasePipeline - Abstract base class for all pipelines\n */\nexport declare abstract class BasePipeline<TInput, TOutput extends PipelineResult | PipelineResult[]> {\n    protected model: LoadedModel | null;\n    protected readonly config: PipelineConfig;\n    protected readonly modelCache: ModelCache;\n    protected readonly downloadCache: ModelDownloadCache;\n    protected isReady: boolean;\n    constructor(config: PipelineConfig);\n    /**\n     * Initialize the pipeline (load model).\n     *\n     * Skips model loading when `config.model === 'default'` — concrete\n     * subclasses that define their own DEFAULT_MODELS handle all model\n     * loading in their overridden `initialize()` methods, so the base\n     * should not attempt to fetch a URL called \"default\".\n     */\n    initialize(): Promise<void>;\n    /**\n     * Load model with caching\n     */\n    protected loadModelWithCache(modelPath: string): Promise<LoadedModel>;\n    /**\n     * Run inference (single input)\n     */\n    run(input: TInput, options?: PipelineOptions): Promise<TOutput>;\n    /**\n     * Run batch inference\n     */\n    runBatch(inputs: TInput[], options?: PipelineOptions): Promise<TOutput[]>;\n    /**\n     * Preprocess input - must be implemented by subclasses\n     */\n    protected abstract preprocess(input: TInput): Promise<EdgeFlowTensor[]>;\n    /**\n     * Postprocess output - must be implemented by subclasses\n     */\n    protected abstract postprocess(outputs: EdgeFlowTensor[], options?: PipelineOptions): Promise<TOutput>;\n    /**\n     * Get the task type\n     */\n    get task(): PipelineTask;\n    /**\n     * Check if pipeline is ready\n     */\n    get ready(): boolean;\n    /**\n     * Dispose the pipeline\n     */\n    dispose(): void;\n}\n/**\n * Pipeline factory function type\n */\ntype PipelineFactory = (config: PipelineConfig) => BasePipeline<any, any>;\n/**\n * Register a pipeline factory\n */\nexport declare function registerPipeline(task: PipelineTask, factory: PipelineFactory): void;\n/**\n * Get a pipeline factory\n */\nexport declare function getPipelineFactory(task: PipelineTask): PipelineFactory | undefined;\n/**\n * Common sentiment labels\n */\nexport declare const SENTIMENT_LABELS: string[];\n/**\n * Common emotion labels\n */\nexport declare const EMOTION_LABELS: string[];\n/**\n * ImageNet top-10 labels (for demo)\n */\nexport declare const IMAGENET_LABELS: string[];\nexport {};\n//# sourceMappingURL=base.d.ts.map"
  },
  {
    "path": "dist/pipelines/base.js",
    "content": "/**\n * edgeFlow.js - Base Pipeline\n *\n * Base class and utilities for all pipeline implementations.\n */\nimport { loadModel, runInference } from '../core/runtime.js';\nimport { ModelCache } from '../core/memory.js';\nimport { ModelDownloadCache } from '../utils/cache.js';\n// ============================================================================\n// Base Pipeline Class\n// ============================================================================\n/**\n * BasePipeline - Abstract base class for all pipelines\n */\nexport class BasePipeline {\n    model = null;\n    config;\n    modelCache;\n    downloadCache;\n    isReady = false;\n    constructor(config) {\n        this.config = config;\n        this.modelCache = new ModelCache();\n        this.downloadCache = new ModelDownloadCache();\n    }\n    /**\n     * Initialize the pipeline (load model).\n     *\n     * Skips model loading when `config.model === 'default'` — concrete\n     * subclasses that define their own DEFAULT_MODELS handle all model\n     * loading in their overridden `initialize()` methods, so the base\n     * should not attempt to fetch a URL called \"default\".\n     */\n    async initialize() {\n        if (this.isReady && this.model)\n            return;\n        // Skip generic model loading for subclasses that manage their own models.\n        if (this.config.model === 'default') {\n            this.isReady = true;\n            return;\n        }\n        // Check model cache first\n        const cachedModel = this.modelCache.get(this.config.model);\n        if (cachedModel) {\n            this.model = cachedModel;\n            this.isReady = true;\n            return;\n        }\n        // Load model using the explicit URL from config\n        this.model = await this.loadModelWithCache(this.config.model);\n        this.isReady = true;\n    }\n    /**\n     * Load model with caching\n     */\n    async loadModelWithCache(modelPath) {\n        // Try download cache first\n        const cachedResponse = await this.downloadCache.get(modelPath);\n        if (cachedResponse) {\n            // Use cached data\n        }\n        // Download and cache (or use mock for now)\n        try {\n            const response = await fetch(modelPath);\n            if (response.ok) {\n                // Cache the response\n                await this.downloadCache.put(modelPath, response.clone());\n            }\n        }\n        catch {\n            // Ignore fetch errors for demo\n        }\n        // Load into runtime\n        return loadModel(modelPath, {\n            runtime: this.config.runtime,\n            quantization: this.config.quantization,\n            cache: this.config.cache,\n        });\n    }\n    /**\n     * Run inference (single input)\n     */\n    async run(input, options) {\n        await this.initialize();\n        const startTime = performance.now();\n        // Preprocess\n        const preprocessed = await this.preprocess(input);\n        // Run inference\n        const outputs = await runInference(this.model, preprocessed);\n        // Postprocess\n        const result = await this.postprocess(outputs, options);\n        if (result && typeof result === 'object' && 'processingTime' in result) {\n            result.processingTime = performance.now() - startTime;\n        }\n        return result;\n    }\n    /**\n     * Run batch inference\n     */\n    async runBatch(inputs, options) {\n        await this.initialize();\n        // Process all inputs\n        const results = await Promise.all(inputs.map(input => this.run(input, options)));\n        return results;\n    }\n    /**\n     * Get the task type\n     */\n    get task() {\n        return this.config.task;\n    }\n    /**\n     * Check if pipeline is ready\n     */\n    get ready() {\n        return this.isReady;\n    }\n    /**\n     * Dispose the pipeline\n     */\n    dispose() {\n        if (this.model) {\n            this.model.dispose();\n            this.model = null;\n        }\n        this.isReady = false;\n    }\n}\n/**\n * Registered pipeline factories\n */\nconst pipelineFactories = new Map();\n/**\n * Register a pipeline factory\n */\nexport function registerPipeline(task, factory) {\n    pipelineFactories.set(task, factory);\n}\n/**\n * Get a pipeline factory\n */\nexport function getPipelineFactory(task) {\n    return pipelineFactories.get(task);\n}\n// ============================================================================\n// Default Label Maps\n// ============================================================================\n/**\n * Common sentiment labels\n */\nexport const SENTIMENT_LABELS = ['negative', 'positive'];\n/**\n * Common emotion labels\n */\nexport const EMOTION_LABELS = [\n    'anger', 'disgust', 'fear', 'joy', 'sadness', 'surprise', 'neutral'\n];\n/**\n * ImageNet top-10 labels (for demo)\n */\nexport const IMAGENET_LABELS = [\n    'tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead',\n    'electric ray', 'stingray', 'cock', 'hen', 'ostrich'\n];\n//# sourceMappingURL=base.js.map"
  },
  {
    "path": "dist/pipelines/feature-extraction.d.ts",
    "content": "/**\n * edgeFlow.js - Feature Extraction Pipeline\n *\n * Extract embeddings/features from text using sentence-transformer models.\n */\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, FeatureExtractionResult } from './base.js';\nexport interface FeatureExtractionOptions extends PipelineOptions {\n    pooling?: 'mean' | 'max' | 'cls' | 'none';\n    normalize?: boolean;\n    outputDim?: number;\n}\nexport declare class FeatureExtractionPipeline extends BasePipeline<string | string[], FeatureExtractionResult | FeatureExtractionResult[]> {\n    private tokenizer;\n    private onnxModel;\n    private embeddingDim;\n    private modelUrl;\n    private tokenizerUrl;\n    constructor(config: PipelineConfig, embeddingDim?: number);\n    initialize(): Promise<void>;\n    run(input: string | string[], options?: FeatureExtractionOptions): Promise<FeatureExtractionResult | FeatureExtractionResult[]>;\n    protected preprocess(input: string | string[]): Promise<EdgeFlowTensor[]>;\n    private runInference;\n    protected postprocess(outputs: EdgeFlowTensor[], options?: FeatureExtractionOptions): Promise<FeatureExtractionResult>;\n    private extractCLSEmbedding;\n    private meanPooling;\n    private maxPooling;\n    private normalizeVector;\n}\nexport declare function createFeatureExtractionPipeline(config?: Partial<PipelineConfig>): FeatureExtractionPipeline;\n//# sourceMappingURL=feature-extraction.d.ts.map"
  },
  {
    "path": "dist/pipelines/feature-extraction.js",
    "content": "/**\n * edgeFlow.js - Feature Extraction Pipeline\n *\n * Extract embeddings/features from text using sentence-transformer models.\n */\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\nimport { BasePipeline, registerPipeline, } from './base.js';\n// ============================================================================\n// Default Model (all-MiniLM-L6-v2, 384-dim sentence embeddings)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model_quantized.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/tokenizer.json',\n};\nconst DEFAULT_EMBEDDING_DIM = 384;\nexport class FeatureExtractionPipeline extends BasePipeline {\n    tokenizer = null;\n    onnxModel = null;\n    embeddingDim;\n    modelUrl;\n    tokenizerUrl;\n    constructor(config, embeddingDim = DEFAULT_EMBEDDING_DIM) {\n        super(config);\n        this.embeddingDim = embeddingDim;\n        this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n        this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.tokenizer) {\n            this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n        }\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    async run(input, options) {\n        const isBatch = Array.isArray(input);\n        const inputs = isBatch ? input : [input];\n        await this.initialize();\n        const startTime = performance.now();\n        const results = [];\n        for (const text of inputs) {\n            const tensorInputs = await this.preprocess(text);\n            const outputs = await this.runInference(tensorInputs);\n            const result = await this.postprocess(outputs, options);\n            results.push(result);\n        }\n        const processingTime = performance.now() - startTime;\n        for (const result of results) {\n            result.processingTime = processingTime / results.length;\n        }\n        return isBatch ? results : results[0];\n    }\n    async preprocess(input) {\n        const text = Array.isArray(input) ? input[0] : input;\n        const encoded = this.tokenizer.encode(text, {\n            maxLength: 128,\n            padding: 'max_length',\n            truncation: true,\n        });\n        const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64');\n        const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))), [1, encoded.attentionMask.length], 'int64');\n        const tokenTypeIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(() => BigInt(0))), [1, encoded.inputIds.length], 'int64');\n        return [inputIds, attentionMask, tokenTypeIds];\n    }\n    async runInference(inputs) {\n        const namedInputs = new Map();\n        namedInputs.set('input_ids', inputs[0]);\n        namedInputs.set('attention_mask', inputs[1]);\n        namedInputs.set('token_type_ids', inputs[2]);\n        const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n        return outputs;\n    }\n    async postprocess(outputs, options) {\n        const hiddenStates = outputs[0];\n        if (!hiddenStates) {\n            return { embeddings: [] };\n        }\n        const pooling = options?.pooling ?? 'mean';\n        const normalize = options?.normalize ?? true;\n        let embeddings;\n        switch (pooling) {\n            case 'cls':\n                embeddings = this.extractCLSEmbedding(hiddenStates);\n                break;\n            case 'max':\n                embeddings = this.maxPooling(hiddenStates);\n                break;\n            case 'none':\n                embeddings = hiddenStates.toArray();\n                break;\n            case 'mean':\n            default:\n                embeddings = this.meanPooling(hiddenStates);\n                break;\n        }\n        if (normalize) {\n            embeddings = this.normalizeVector(embeddings);\n        }\n        if (options?.outputDim && options.outputDim < embeddings.length) {\n            embeddings = embeddings.slice(0, options.outputDim);\n        }\n        return { embeddings };\n    }\n    extractCLSEmbedding(hiddenStates) {\n        const data = hiddenStates.toFloat32Array();\n        const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n        return Array.from(data.slice(0, embeddingDim));\n    }\n    meanPooling(hiddenStates) {\n        const data = hiddenStates.toFloat32Array();\n        const seqLen = hiddenStates.shape[1] ?? 1;\n        const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n        const result = new Float32Array(embeddingDim);\n        for (let i = 0; i < seqLen; i++) {\n            for (let j = 0; j < embeddingDim; j++) {\n                result[j] = (result[j] ?? 0) + (data[i * embeddingDim + j] ?? 0) / seqLen;\n            }\n        }\n        return Array.from(result);\n    }\n    maxPooling(hiddenStates) {\n        const data = hiddenStates.toFloat32Array();\n        const seqLen = hiddenStates.shape[1] ?? 1;\n        const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n        const result = new Array(embeddingDim).fill(-Infinity);\n        for (let i = 0; i < seqLen; i++) {\n            for (let j = 0; j < embeddingDim; j++) {\n                const val = data[i * embeddingDim + j] ?? 0;\n                if (val > (result[j] ?? -Infinity)) {\n                    result[j] = val;\n                }\n            }\n        }\n        return result;\n    }\n    normalizeVector(vec) {\n        let norm = 0;\n        for (const v of vec) {\n            norm += v * v;\n        }\n        norm = Math.sqrt(norm);\n        if (norm === 0)\n            return vec;\n        return vec.map(v => v / norm);\n    }\n}\n// ============================================================================\n// Factory Function\n// ============================================================================\nexport function createFeatureExtractionPipeline(config = {}) {\n    return new FeatureExtractionPipeline({\n        task: 'feature-extraction',\n        model: config.model ?? 'default',\n        runtime: config.runtime,\n        cache: config.cache ?? true,\n        quantization: config.quantization,\n    });\n}\nregisterPipeline('feature-extraction', (config) => new FeatureExtractionPipeline(config));\n//# sourceMappingURL=feature-extraction.js.map"
  },
  {
    "path": "dist/pipelines/image-classification.d.ts",
    "content": "/**\n * edgeFlow.js - Image Classification Pipeline\n *\n * Classify images into categories using vision models.\n */\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, ImageClassificationResult } from './base.js';\nexport interface ImageClassificationOptions extends PipelineOptions {\n    returnAllScores?: boolean;\n    labels?: string[];\n    topK?: number;\n}\nexport type ImageInput = HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData | string;\nexport declare class ImageClassificationPipeline extends BasePipeline<ImageInput | ImageInput[], ImageClassificationResult | ImageClassificationResult[]> {\n    private preprocessor;\n    private onnxModel;\n    private labels;\n    private modelUrl;\n    constructor(config: PipelineConfig, labels?: string[], _numClasses?: number);\n    initialize(): Promise<void>;\n    setLabels(labels: string[]): void;\n    run(input: ImageInput | ImageInput[], options?: ImageClassificationOptions): Promise<ImageClassificationResult | ImageClassificationResult[]>;\n    protected preprocess(input: ImageInput | ImageInput[]): Promise<EdgeFlowTensor[]>;\n    private runModelInference;\n    protected postprocess(outputs: EdgeFlowTensor[], options?: ImageClassificationOptions): Promise<ImageClassificationResult>;\n}\nexport declare function createImageClassificationPipeline(config?: Partial<PipelineConfig>, labels?: string[]): ImageClassificationPipeline;\n//# sourceMappingURL=image-classification.d.ts.map"
  },
  {
    "path": "dist/pipelines/image-classification.js",
    "content": "/**\n * edgeFlow.js - Image Classification Pipeline\n *\n * Classify images into categories using vision models.\n */\nimport { softmax } from '../core/tensor.js';\nimport { createImagePreprocessor } from '../utils/preprocessor.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference } from '../core/runtime.js';\nimport { BasePipeline, registerPipeline, IMAGENET_LABELS, } from './base.js';\n// ============================================================================\n// Default Model (MobileViT-small, quantized)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/mobilevit-small/resolve/main/onnx/model_quantized.onnx',\n};\nexport class ImageClassificationPipeline extends BasePipeline {\n    preprocessor = null;\n    onnxModel = null;\n    labels;\n    modelUrl;\n    constructor(config, labels, _numClasses = 1000) {\n        super(config);\n        this.labels = labels ?? IMAGENET_LABELS;\n        this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.preprocessor) {\n            this.preprocessor = createImagePreprocessor('imagenet');\n        }\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    setLabels(labels) {\n        this.labels = labels;\n    }\n    async run(input, options) {\n        const isBatch = Array.isArray(input);\n        const inputs = isBatch ? input : [input];\n        await this.initialize();\n        const startTime = performance.now();\n        const results = [];\n        for (const image of inputs) {\n            const tensorInputs = await this.preprocess(image);\n            const outputs = await this.runModelInference(tensorInputs);\n            const result = await this.postprocess(outputs, options);\n            results.push(result);\n        }\n        const processingTime = performance.now() - startTime;\n        for (const result of results) {\n            result.processingTime = processingTime / results.length;\n        }\n        return isBatch ? results : results[0];\n    }\n    async preprocess(input) {\n        const image = Array.isArray(input) ? input[0] : input;\n        const tensor = await this.preprocessor.process(image);\n        if (tensor.shape.length === 3) {\n            return [tensor.reshape([1, ...tensor.shape])];\n        }\n        return [tensor];\n    }\n    async runModelInference(inputs) {\n        const outputs = await runInference(this.onnxModel, inputs);\n        return outputs;\n    }\n    async postprocess(outputs, options) {\n        const logits = outputs[0];\n        if (!logits) {\n            return { label: 'unknown', score: 0 };\n        }\n        const probs = softmax(logits, -1);\n        const probsArray = probs.toFloat32Array();\n        let maxIdx = 0;\n        let maxScore = probsArray[0] ?? 0;\n        for (let i = 1; i < probsArray.length; i++) {\n            if ((probsArray[i] ?? 0) > maxScore) {\n                maxScore = probsArray[i] ?? 0;\n                maxIdx = i;\n            }\n        }\n        const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n        return { label, score: maxScore };\n    }\n}\n// ============================================================================\n// Factory Function\n// ============================================================================\nexport function createImageClassificationPipeline(config = {}, labels) {\n    return new ImageClassificationPipeline({\n        task: 'image-classification',\n        model: config.model ?? 'default',\n        runtime: config.runtime,\n        cache: config.cache ?? true,\n        quantization: config.quantization,\n    }, labels);\n}\nregisterPipeline('image-classification', (config) => new ImageClassificationPipeline(config));\n//# sourceMappingURL=image-classification.js.map"
  },
  {
    "path": "dist/pipelines/image-segmentation.d.ts",
    "content": "/**\n * edgeFlow.js - Image Segmentation Pipeline\n *\n * Interactive image segmentation using SAM (Segment Anything Model).\n * Supports point prompts and bounding box prompts.\n */\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, PipelineResult } from './base.js';\n/**\n * Point prompt for segmentation\n */\nexport interface PointPrompt {\n    /** X coordinate (0-1 normalized) */\n    x: number;\n    /** Y coordinate (0-1 normalized) */\n    y: number;\n    /** 1 for foreground (include), 0 for background (exclude) */\n    label: 0 | 1;\n}\n/**\n * Box prompt for segmentation\n */\nexport interface BoxPrompt {\n    /** Top-left X (0-1 normalized) */\n    x1: number;\n    /** Top-left Y (0-1 normalized) */\n    y1: number;\n    /** Bottom-right X (0-1 normalized) */\n    x2: number;\n    /** Bottom-right Y (0-1 normalized) */\n    y2: number;\n}\n/**\n * Model loading progress callback\n */\nexport interface ModelLoadProgress {\n    /** Model name (encoder or decoder) */\n    model: 'encoder' | 'decoder';\n    /** Bytes loaded */\n    loaded: number;\n    /** Total bytes */\n    total: number;\n    /** Progress percentage (0-100) */\n    progress: number;\n}\n/**\n * Segmentation options\n */\nexport interface ImageSegmentationOptions extends PipelineOptions {\n    /** Point prompts */\n    points?: PointPrompt[];\n    /** Box prompts */\n    boxes?: BoxPrompt[];\n    /** Return all masks or just the best one */\n    returnAllMasks?: boolean;\n    /** Mask threshold (0-1) */\n    maskThreshold?: number;\n}\n/**\n * Segmentation result\n */\nexport interface ImageSegmentationResult extends PipelineResult {\n    /** Segmentation mask (Uint8Array, 0 or 255) */\n    mask: Uint8Array;\n    /** Mask width */\n    width: number;\n    /** Mask height */\n    height: number;\n    /** Confidence score */\n    score: number;\n    /** All masks if returnAllMasks is true */\n    allMasks?: Array<{\n        mask: Uint8Array;\n        score: number;\n    }>;\n}\n/**\n * Image input types\n */\nexport type ImageInput = HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData | string;\n/**\n * ImageSegmentationPipeline - Interactive image segmentation\n *\n * Uses SAM-style models for point/box prompted segmentation.\n *\n * @example\n * ```typescript\n * const segmenter = createImageSegmentationPipeline();\n *\n * // Load models with progress callback\n * await segmenter.loadModels((progress) => {\n *   console.log(`Loading ${progress.model}: ${progress.progress}%`);\n * });\n *\n * // Set image and segment\n * await segmenter.setImage(imageElement);\n * const result = await segmenter.segment({\n *   points: [{ x: 0.5, y: 0.5, label: 1 }]\n * });\n * ```\n */\nexport declare class ImageSegmentationPipeline extends BasePipeline<ImageInput, ImageSegmentationResult> {\n    private encoderModel;\n    private decoderModel;\n    private imageEmbedding;\n    private imagePositionalEmbedding;\n    private currentImageSize;\n    private resizedImageSize;\n    private inputSize;\n    private modelsLoaded;\n    private encoderUrl;\n    private decoderUrl;\n    constructor(config: PipelineConfig);\n    /**\n     * Check if models are loaded\n     */\n    get isModelsLoaded(): boolean;\n    /**\n     * Set custom model URLs\n     */\n    setModelUrls(encoder: string, decoder: string): void;\n    /**\n     * Load both encoder and decoder models with progress callback\n     */\n    loadModels(onProgress?: (progress: ModelLoadProgress) => void): Promise<void>;\n    /**\n     * Fetch model with progress tracking\n     */\n    private fetchModelWithProgress;\n    /**\n     * Initialize pipeline (override to skip default model loading)\n     */\n    initialize(): Promise<void>;\n    /**\n     * Load encoder model (processes the image once)\n     */\n    loadEncoder(modelUrl: string): Promise<void>;\n    /**\n     * Load decoder model (processes prompts to generate masks)\n     */\n    loadDecoder(modelUrl: string): Promise<void>;\n    /**\n     * Set and encode the image (call once per image)\n     */\n    setImage(image: ImageInput): Promise<void>;\n    /**\n     * Segment the image with given prompts\n     */\n    segment(options?: ImageSegmentationOptions): Promise<ImageSegmentationResult>;\n    /**\n     * Run segmentation (implements BasePipeline interface)\n     */\n    run(input: ImageInput, options?: ImageSegmentationOptions): Promise<ImageSegmentationResult>;\n    /**\n     * Load image from various sources\n     */\n    private loadImage;\n    /**\n     * Load image from URL\n     */\n    private loadImageFromUrl;\n    /**\n     * Convert HTMLImageElement to ImageData\n     */\n    private imageElementToImageData;\n    /**\n     * Convert canvas to ImageData\n     */\n    private canvasToImageData;\n    /**\n     * Convert ImageBitmap to ImageData\n     */\n    private imageBitmapToImageData;\n    /**\n     * Preprocess image for SAM\n     */\n    private preprocessImage;\n    /**\n     * Prepare decoder inputs (prompts) for SlimSAM\n     *\n     * SlimSAM prompt_encoder_mask_decoder expects these named inputs:\n     * - image_embeddings: [1, 256, 64, 64]\n     * - point_coords: [batch, num_points, 2]\n     * - point_labels: [batch, num_points]\n     * - mask_input: [batch, 1, 256, 256]\n     * - has_mask_input: [batch, 1]\n     * - orig_im_size: [2]\n     * - position_ids: [batch, num_points]\n     */\n    private prepareDecoderInputs;\n    /**\n     * Post-process masks from decoder output\n     */\n    private postprocessMasks;\n    /**\n     * Resize mask from model output size to original image size\n     */\n    private resizeMask;\n    /**\n     * Clear the current image embedding\n     */\n    clearImage(): void;\n    /**\n     * Preprocess (required by BasePipeline)\n     */\n    protected preprocess(input: ImageInput): Promise<EdgeFlowTensor[]>;\n    /**\n     * Postprocess (required by BasePipeline)\n     */\n    protected postprocess(_outputs: EdgeFlowTensor[], _options?: PipelineOptions): Promise<ImageSegmentationResult>;\n    /**\n     * Dispose resources\n     */\n    dispose(): void;\n}\n/**\n * Create image segmentation pipeline\n */\nexport declare function createImageSegmentationPipeline(config?: Partial<PipelineConfig>): ImageSegmentationPipeline;\n//# sourceMappingURL=image-segmentation.d.ts.map"
  },
  {
    "path": "dist/pipelines/image-segmentation.js",
    "content": "/**\n * edgeFlow.js - Image Segmentation Pipeline\n *\n * Interactive image segmentation using SAM (Segment Anything Model).\n * Supports point prompts and bounding box prompts.\n */\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, registerPipeline } from './base.js';\nimport { loadModel, loadModelFromBuffer, runInference, runInferenceNamed } from '../core/runtime.js';\n// ============================================================================\n// Default Model URLs (SlimSAM - quantized for browser)\n// ============================================================================\nconst DEFAULT_SAM_MODELS = {\n    encoder: 'https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/vision_encoder_quantized.onnx',\n    decoder: 'https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/prompt_encoder_mask_decoder_quantized.onnx',\n};\n// ============================================================================\n// Image Segmentation Pipeline\n// ============================================================================\n/**\n * ImageSegmentationPipeline - Interactive image segmentation\n *\n * Uses SAM-style models for point/box prompted segmentation.\n *\n * @example\n * ```typescript\n * const segmenter = createImageSegmentationPipeline();\n *\n * // Load models with progress callback\n * await segmenter.loadModels((progress) => {\n *   console.log(`Loading ${progress.model}: ${progress.progress}%`);\n * });\n *\n * // Set image and segment\n * await segmenter.setImage(imageElement);\n * const result = await segmenter.segment({\n *   points: [{ x: 0.5, y: 0.5, label: 1 }]\n * });\n * ```\n */\nexport class ImageSegmentationPipeline extends BasePipeline {\n    encoderModel = null;\n    decoderModel = null;\n    imageEmbedding = null;\n    imagePositionalEmbedding = null;\n    currentImageSize = null;\n    resizedImageSize = null;\n    inputSize = 1024; // SAM default input size\n    modelsLoaded = false;\n    // Custom model URLs\n    encoderUrl;\n    decoderUrl;\n    constructor(config) {\n        super(config);\n        this.encoderUrl = DEFAULT_SAM_MODELS.encoder;\n        this.decoderUrl = DEFAULT_SAM_MODELS.decoder;\n    }\n    /**\n     * Check if models are loaded\n     */\n    get isModelsLoaded() {\n        return this.modelsLoaded;\n    }\n    /**\n     * Set custom model URLs\n     */\n    setModelUrls(encoder, decoder) {\n        this.encoderUrl = encoder;\n        this.decoderUrl = decoder;\n    }\n    /**\n     * Load both encoder and decoder models with progress callback\n     */\n    async loadModels(onProgress) {\n        if (this.modelsLoaded)\n            return;\n        // Load encoder\n        onProgress?.({ model: 'encoder', loaded: 0, total: 100, progress: 0 });\n        const encoderData = await this.fetchModelWithProgress(this.encoderUrl, (loaded, total) => {\n            onProgress?.({\n                model: 'encoder',\n                loaded,\n                total,\n                progress: Math.round((loaded / total) * 100),\n            });\n        });\n        this.encoderModel = await loadModelFromBuffer(encoderData, {\n            runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n        });\n        // Load decoder\n        onProgress?.({ model: 'decoder', loaded: 0, total: 100, progress: 0 });\n        const decoderData = await this.fetchModelWithProgress(this.decoderUrl, (loaded, total) => {\n            onProgress?.({\n                model: 'decoder',\n                loaded,\n                total,\n                progress: Math.round((loaded / total) * 100),\n            });\n        });\n        this.decoderModel = await loadModelFromBuffer(decoderData, {\n            runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n        });\n        this.modelsLoaded = true;\n    }\n    /**\n     * Fetch model with progress tracking\n     */\n    async fetchModelWithProgress(url, onProgress) {\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n        }\n        const contentLength = response.headers.get('content-length');\n        const total = contentLength ? parseInt(contentLength, 10) : 0;\n        if (!response.body) {\n            // Fallback if no streaming support\n            const buffer = await response.arrayBuffer();\n            onProgress(buffer.byteLength, buffer.byteLength);\n            return buffer;\n        }\n        const reader = response.body.getReader();\n        const chunks = [];\n        let loaded = 0;\n        while (true) {\n            const { done, value } = await reader.read();\n            if (done)\n                break;\n            chunks.push(value);\n            loaded += value.length;\n            onProgress(loaded, total || loaded);\n        }\n        // Combine chunks into ArrayBuffer\n        const buffer = new Uint8Array(loaded);\n        let offset = 0;\n        for (const chunk of chunks) {\n            buffer.set(chunk, offset);\n            offset += chunk.length;\n        }\n        return buffer.buffer;\n    }\n    /**\n     * Initialize pipeline (override to skip default model loading)\n     */\n    async initialize() {\n        if (this.isReady)\n            return;\n        // Don't call super.initialize() - we handle model loading separately\n        this.isReady = true;\n    }\n    /**\n     * Load encoder model (processes the image once)\n     */\n    async loadEncoder(modelUrl) {\n        this.encoderModel = await loadModel(modelUrl, {\n            runtime: 'wasm',\n        });\n    }\n    /**\n     * Load decoder model (processes prompts to generate masks)\n     */\n    async loadDecoder(modelUrl) {\n        this.decoderModel = await loadModel(modelUrl, {\n            runtime: 'wasm',\n        });\n    }\n    /**\n     * Set and encode the image (call once per image)\n     */\n    async setImage(image) {\n        if (!this.modelsLoaded) {\n            throw new Error('Models not loaded. Call loadModels() first.');\n        }\n        // Get image data\n        const imageData = await this.loadImage(image);\n        this.currentImageSize = {\n            width: imageData.width,\n            height: imageData.height,\n        };\n        // Preprocess image for SAM\n        const { tensor: inputTensor, resizedSize } = this.preprocessImage(imageData);\n        this.resizedImageSize = resizedSize;\n        // Run encoder\n        if (this.encoderModel) {\n            const outputs = await runInference(this.encoderModel, [inputTensor]);\n            // SlimSAM encoder outputs: [image_embeddings, image_positional_embeddings]\n            this.imageEmbedding = outputs[0];\n            this.imagePositionalEmbedding = outputs[1];\n            console.log('[SAM] Encoder outputs:', outputs.length);\n            console.log('[SAM] image_embeddings shape:', this.imageEmbedding.shape);\n            if (this.imagePositionalEmbedding) {\n                console.log('[SAM] image_positional_embeddings shape:', this.imagePositionalEmbedding.shape);\n            }\n        }\n        else {\n            throw new Error('Encoder model not loaded');\n        }\n    }\n    /**\n     * Segment the image with given prompts\n     */\n    async segment(options = {}) {\n        if (!this.imageEmbedding || !this.currentImageSize || !this.resizedImageSize) {\n            throw new Error('No image set. Call setImage() first.');\n        }\n        if (!this.decoderModel) {\n            throw new Error('Decoder model not loaded');\n        }\n        const startTime = performance.now();\n        const { points = [], boxes = [], maskThreshold = 0.0, returnAllMasks = false } = options;\n        // Prepare inputs for decoder\n        const decoderInputs = this.prepareDecoderInputs(points, boxes);\n        // Add image embeddings to inputs\n        decoderInputs.set('image_embeddings', this.imageEmbedding);\n        // Add positional embeddings (required by SlimSAM)\n        if (this.imagePositionalEmbedding) {\n            decoderInputs.set('image_positional_embeddings', this.imagePositionalEmbedding);\n        }\n        else {\n            throw new Error('image_positional_embeddings not available from encoder');\n        }\n        // Run decoder model with named inputs\n        const outputs = await runInferenceNamed(this.decoderModel, decoderInputs);\n        // SAM decoder outputs: [masks, iou_predictions]\n        const masks = outputs[0];\n        const scores = outputs[1];\n        // Post-process masks\n        const result = this.postprocessMasks(masks, scores, maskThreshold, returnAllMasks);\n        result.processingTime = performance.now() - startTime;\n        return result;\n    }\n    /**\n     * Run segmentation (implements BasePipeline interface)\n     */\n    async run(input, options) {\n        await this.setImage(input);\n        return this.segment(options);\n    }\n    /**\n     * Load image from various sources\n     */\n    async loadImage(input) {\n        // Handle different input types\n        if (typeof input === 'string') {\n            // URL or base64\n            return this.loadImageFromUrl(input);\n        }\n        else if (input instanceof HTMLImageElement) {\n            return this.imageElementToImageData(input);\n        }\n        else if (input instanceof HTMLCanvasElement) {\n            return this.canvasToImageData(input);\n        }\n        else if (input instanceof ImageData) {\n            return input;\n        }\n        else if (typeof ImageBitmap !== 'undefined' && input instanceof ImageBitmap) {\n            return this.imageBitmapToImageData(input);\n        }\n        throw new Error('Unsupported image input type');\n    }\n    /**\n     * Load image from URL\n     */\n    async loadImageFromUrl(url) {\n        return new Promise((resolve, reject) => {\n            const img = new Image();\n            img.crossOrigin = 'anonymous';\n            img.onload = () => {\n                const canvas = document.createElement('canvas');\n                canvas.width = img.width;\n                canvas.height = img.height;\n                const ctx = canvas.getContext('2d');\n                ctx.drawImage(img, 0, 0);\n                resolve(ctx.getImageData(0, 0, img.width, img.height));\n            };\n            img.onerror = reject;\n            img.src = url;\n        });\n    }\n    /**\n     * Convert HTMLImageElement to ImageData\n     */\n    imageElementToImageData(img) {\n        const canvas = document.createElement('canvas');\n        canvas.width = img.naturalWidth || img.width;\n        canvas.height = img.naturalHeight || img.height;\n        const ctx = canvas.getContext('2d');\n        ctx.drawImage(img, 0, 0);\n        return ctx.getImageData(0, 0, canvas.width, canvas.height);\n    }\n    /**\n     * Convert canvas to ImageData\n     */\n    canvasToImageData(canvas) {\n        const ctx = canvas.getContext('2d');\n        return ctx.getImageData(0, 0, canvas.width, canvas.height);\n    }\n    /**\n     * Convert ImageBitmap to ImageData\n     */\n    imageBitmapToImageData(bitmap) {\n        const canvas = document.createElement('canvas');\n        canvas.width = bitmap.width;\n        canvas.height = bitmap.height;\n        const ctx = canvas.getContext('2d');\n        ctx.drawImage(bitmap, 0, 0);\n        return ctx.getImageData(0, 0, canvas.width, canvas.height);\n    }\n    /**\n     * Preprocess image for SAM\n     */\n    preprocessImage(imageData) {\n        const { width, height } = imageData;\n        // Calculate resize dimensions (longest side = inputSize)\n        const scale = this.inputSize / Math.max(width, height);\n        const newWidth = Math.round(width * scale);\n        const newHeight = Math.round(height * scale);\n        // Create resized canvas with padding\n        const canvas = document.createElement('canvas');\n        canvas.width = this.inputSize;\n        canvas.height = this.inputSize;\n        const ctx = canvas.getContext('2d');\n        // Fill with padding color (SAM uses pixel mean)\n        ctx.fillStyle = `rgb(123.675, 116.28, 103.53)`;\n        ctx.fillRect(0, 0, this.inputSize, this.inputSize);\n        // Draw resized image (top-left aligned)\n        const tempCanvas = document.createElement('canvas');\n        tempCanvas.width = width;\n        tempCanvas.height = height;\n        const tempCtx = tempCanvas.getContext('2d');\n        tempCtx.putImageData(imageData, 0, 0);\n        ctx.drawImage(tempCanvas, 0, 0, newWidth, newHeight);\n        // Get pixel data\n        const resizedData = ctx.getImageData(0, 0, this.inputSize, this.inputSize);\n        // Convert to tensor (NCHW format, normalized with ImageNet mean/std)\n        const tensorData = new Float32Array(3 * this.inputSize * this.inputSize);\n        const mean = [123.675, 116.28, 103.53];\n        const std = [58.395, 57.12, 57.375];\n        for (let i = 0; i < this.inputSize * this.inputSize; i++) {\n            const pixelIdx = i * 4;\n            tensorData[i] = (resizedData.data[pixelIdx] - mean[0]) / std[0]; // R\n            tensorData[this.inputSize * this.inputSize + i] =\n                (resizedData.data[pixelIdx + 1] - mean[1]) / std[1]; // G\n            tensorData[2 * this.inputSize * this.inputSize + i] =\n                (resizedData.data[pixelIdx + 2] - mean[2]) / std[2]; // B\n        }\n        return {\n            tensor: new EdgeFlowTensor(tensorData, [1, 3, this.inputSize, this.inputSize], 'float32'),\n            resizedSize: { width: newWidth, height: newHeight },\n        };\n    }\n    /**\n     * Prepare decoder inputs (prompts) for SlimSAM\n     *\n     * SlimSAM prompt_encoder_mask_decoder expects these named inputs:\n     * - image_embeddings: [1, 256, 64, 64]\n     * - point_coords: [batch, num_points, 2]\n     * - point_labels: [batch, num_points]\n     * - mask_input: [batch, 1, 256, 256]\n     * - has_mask_input: [batch, 1]\n     * - orig_im_size: [2]\n     * - position_ids: [batch, num_points]\n     */\n    prepareDecoderInputs(points, boxes) {\n        const { width: resizedW, height: resizedH } = this.resizedImageSize;\n        // Scale factors for converting normalized coords to resized image coords\n        const scaleX = resizedW;\n        const scaleY = resizedH;\n        const allPoints = [];\n        const allLabels = [];\n        // Add point prompts\n        for (const point of points) {\n            allPoints.push(point.x * scaleX, point.y * scaleY);\n            allLabels.push(point.label);\n        }\n        // Add box prompts (as two corner points)\n        for (const box of boxes) {\n            // Top-left corner (label 2)\n            allPoints.push(box.x1 * scaleX, box.y1 * scaleY);\n            allLabels.push(2);\n            // Bottom-right corner (label 3)\n            allPoints.push(box.x2 * scaleX, box.y2 * scaleY);\n            allLabels.push(3);\n        }\n        // Default point if no prompts (center of image)\n        if (allPoints.length === 0) {\n            allPoints.push(resizedW / 2, resizedH / 2);\n            allLabels.push(1);\n        }\n        const numPoints = allLabels.length;\n        const inputs = new Map();\n        // input_points: [1, 1, num_points, 2] - SlimSAM format (float32)\n        inputs.set('input_points', new EdgeFlowTensor(new Float32Array(allPoints), [1, 1, numPoints, 2], 'float32'));\n        // input_labels: [1, 1, num_points] - SlimSAM format (int64)\n        inputs.set('input_labels', new EdgeFlowTensor(BigInt64Array.from(allLabels.map(l => BigInt(l))), [1, 1, numPoints], 'int64'));\n        // Note: image_embeddings and image_positional_embeddings are added in segment()\n        // SlimSAM decoder only needs: image_embeddings, image_positional_embeddings, input_points, input_labels\n        return inputs;\n    }\n    /**\n     * Post-process masks from decoder output\n     */\n    postprocessMasks(masks, scores, threshold, returnAllMasks) {\n        const { width, height } = this.currentImageSize;\n        const scoresData = scores.toFloat32Array();\n        const masksData = masks.toFloat32Array();\n        // SAM outputs multiple masks (usually 3)\n        const numMasks = scoresData.length;\n        const maskShape = masks.shape; // [1, num_masks, H, W]\n        const maskH = maskShape[2] ?? height;\n        const maskW = maskShape[3] ?? width;\n        // Find best mask by score\n        let bestIdx = 0;\n        let bestScore = scoresData[0] ?? 0;\n        for (let i = 1; i < numMasks; i++) {\n            if ((scoresData[i] ?? 0) > bestScore) {\n                bestScore = scoresData[i] ?? 0;\n                bestIdx = i;\n            }\n        }\n        // Extract and resize the best mask to original image size\n        const outputMask = this.resizeMask(masksData, bestIdx, maskW, maskH, width, height, threshold);\n        const result = {\n            mask: outputMask,\n            width,\n            height,\n            score: bestScore,\n        };\n        if (returnAllMasks && numMasks > 1) {\n            result.allMasks = [];\n            for (let m = 0; m < numMasks; m++) {\n                const mask = this.resizeMask(masksData, m, maskW, maskH, width, height, threshold);\n                result.allMasks.push({\n                    mask,\n                    score: scoresData[m] ?? 0,\n                });\n            }\n        }\n        return result;\n    }\n    /**\n     * Resize mask from model output size to original image size\n     */\n    resizeMask(masksData, maskIdx, srcW, srcH, dstW, dstH, threshold) {\n        const outputMask = new Uint8Array(dstW * dstH);\n        const maskOffset = maskIdx * srcW * srcH;\n        // Bilinear interpolation for resizing\n        for (let y = 0; y < dstH; y++) {\n            for (let x = 0; x < dstW; x++) {\n                // Map to source coordinates\n                const srcX = (x / dstW) * srcW;\n                const srcY = (y / dstH) * srcH;\n                // Bilinear interpolation\n                const x0 = Math.floor(srcX);\n                const x1 = Math.min(x0 + 1, srcW - 1);\n                const y0 = Math.floor(srcY);\n                const y1 = Math.min(y0 + 1, srcH - 1);\n                const xFrac = srcX - x0;\n                const yFrac = srcY - y0;\n                const v00 = masksData[maskOffset + y0 * srcW + x0] ?? 0;\n                const v01 = masksData[maskOffset + y0 * srcW + x1] ?? 0;\n                const v10 = masksData[maskOffset + y1 * srcW + x0] ?? 0;\n                const v11 = masksData[maskOffset + y1 * srcW + x1] ?? 0;\n                const value = v00 * (1 - xFrac) * (1 - yFrac) +\n                    v01 * xFrac * (1 - yFrac) +\n                    v10 * (1 - xFrac) * yFrac +\n                    v11 * xFrac * yFrac;\n                // Apply sigmoid and threshold\n                const sigmoid = 1 / (1 + Math.exp(-value));\n                outputMask[y * dstW + x] = sigmoid > threshold ? 255 : 0;\n            }\n        }\n        return outputMask;\n    }\n    /**\n     * Clear the current image embedding\n     */\n    clearImage() {\n        this.imageEmbedding = null;\n        this.imagePositionalEmbedding = null;\n        this.currentImageSize = null;\n        this.resizedImageSize = null;\n    }\n    /**\n     * Preprocess (required by BasePipeline)\n     */\n    async preprocess(input) {\n        const imageData = await this.loadImage(input);\n        const { tensor } = this.preprocessImage(imageData);\n        return [tensor];\n    }\n    /**\n     * Postprocess (required by BasePipeline)\n     */\n    async postprocess(_outputs, _options) {\n        // This is handled in segment() method\n        return {\n            mask: new Uint8Array(0),\n            width: 0,\n            height: 0,\n            score: 0,\n        };\n    }\n    /**\n     * Dispose resources\n     */\n    dispose() {\n        super.dispose();\n        this.encoderModel?.dispose();\n        this.decoderModel?.dispose();\n        this.imageEmbedding = null;\n        this.imagePositionalEmbedding = null;\n        this.currentImageSize = null;\n        this.resizedImageSize = null;\n        this.modelsLoaded = false;\n    }\n}\n// ============================================================================\n// Factory Function\n// ============================================================================\n/**\n * Create image segmentation pipeline\n */\nexport function createImageSegmentationPipeline(config = {}) {\n    return new ImageSegmentationPipeline({\n        task: 'image-segmentation',\n        model: config.model ?? 'slimsam',\n        runtime: config.runtime,\n        cache: config.cache ?? true,\n        quantization: config.quantization,\n    });\n}\n// Register pipeline\nregisterPipeline('image-segmentation', (config) => new ImageSegmentationPipeline(config));\n//# sourceMappingURL=image-segmentation.js.map"
  },
  {
    "path": "dist/pipelines/index.d.ts",
    "content": "/**\n * edgeFlow.js - Pipeline Exports\n */\nimport { RuntimeType, QuantizationType } from '../core/types.js';\nexport { BasePipeline, registerPipeline, getPipelineFactory, SENTIMENT_LABELS, EMOTION_LABELS, IMAGENET_LABELS, type PipelineResult, type TextClassificationResult, type FeatureExtractionResult, type ImageClassificationResult, type ObjectDetectionResult, } from './base.js';\nexport { TextClassificationPipeline, SentimentAnalysisPipeline, createTextClassificationPipeline, createSentimentAnalysisPipeline, type TextClassificationOptions, } from './text-classification.js';\nexport { FeatureExtractionPipeline, createFeatureExtractionPipeline, type FeatureExtractionOptions, } from './feature-extraction.js';\nexport { ImageClassificationPipeline, createImageClassificationPipeline, type ImageClassificationOptions, type ImageInput, } from './image-classification.js';\nexport { TextGenerationPipeline, createTextGenerationPipeline, type TextGenerationOptions, type TextGenerationResult, type GenerationStreamEvent, type ChatMessage, type ChatOptions, type ChatTemplateType, type LLMLoadProgress, } from './text-generation.js';\nexport { ObjectDetectionPipeline, createObjectDetectionPipeline, COCO_LABELS, type ObjectDetectionOptions, type Detection, type BoundingBox, } from './object-detection.js';\nexport { AutomaticSpeechRecognitionPipeline, createASRPipeline, type ASROptions, type ASRResult, type WordTimestamp, type ChunkTimestamp, } from './automatic-speech-recognition.js';\nexport { ZeroShotClassificationPipeline, createZeroShotClassificationPipeline, type ZeroShotClassificationOptions, type ZeroShotClassificationResult, } from './zero-shot-classification.js';\nexport { QuestionAnsweringPipeline, createQuestionAnsweringPipeline, type QuestionAnsweringOptions, type QuestionAnsweringResult, type QAInput, } from './question-answering.js';\nexport { ImageSegmentationPipeline, createImageSegmentationPipeline, type ImageSegmentationOptions, type ImageSegmentationResult, type PointPrompt, type BoxPrompt, type ModelLoadProgress, } from './image-segmentation.js';\n/**\n * Pipeline options for the factory function\n */\nexport interface PipelineFactoryOptions {\n    /** Model ID or URL */\n    model?: string;\n    /** Runtime to use */\n    runtime?: RuntimeType;\n    /** Enable caching */\n    cache?: boolean;\n    /** Quantization type */\n    quantization?: QuantizationType;\n    /** Custom labels for classification */\n    labels?: string[];\n}\n/**\n * Supported pipeline task mapping\n */\ntype PipelineTaskMap = {\n    'text-classification': TextClassificationPipeline;\n    'sentiment-analysis': SentimentAnalysisPipeline;\n    'feature-extraction': FeatureExtractionPipeline;\n    'image-classification': ImageClassificationPipeline;\n    'text-generation': TextGenerationPipeline;\n    'object-detection': ObjectDetectionPipeline;\n    'automatic-speech-recognition': AutomaticSpeechRecognitionPipeline;\n    'zero-shot-classification': ZeroShotClassificationPipeline;\n    'question-answering': QuestionAnsweringPipeline;\n    'image-segmentation': ImageSegmentationPipeline;\n};\nimport { TextClassificationPipeline, SentimentAnalysisPipeline } from './text-classification.js';\nimport { FeatureExtractionPipeline } from './feature-extraction.js';\nimport { ImageClassificationPipeline } from './image-classification.js';\nimport { TextGenerationPipeline } from './text-generation.js';\nimport { ObjectDetectionPipeline } from './object-detection.js';\nimport { AutomaticSpeechRecognitionPipeline } from './automatic-speech-recognition.js';\nimport { ZeroShotClassificationPipeline } from './zero-shot-classification.js';\nimport { QuestionAnsweringPipeline } from './question-answering.js';\nimport { ImageSegmentationPipeline } from './image-segmentation.js';\n/**\n * Create a pipeline for a specific task\n *\n * @example\n * ```typescript\n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n * const result = await sentiment.run('I love this product!');\n *\n * // Create an image classifier with custom model\n * const classifier = await pipeline('image-classification', {\n *   model: 'https://example.com/model.bin',\n * });\n * ```\n */\nexport declare function pipeline<T extends keyof PipelineTaskMap>(task: T, options?: PipelineFactoryOptions): Promise<PipelineTaskMap[T]>;\n/**\n * Create multiple pipelines at once\n */\nexport declare function createPipelines<T extends (keyof PipelineTaskMap)[]>(tasks: T, options?: PipelineFactoryOptions): Promise<{\n    [K in T[number]]: PipelineTaskMap[K];\n}>;\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/pipelines/index.js",
    "content": "/**\n * edgeFlow.js - Pipeline Exports\n */\nimport { getPluginPipeline } from '../core/plugin.js';\nimport { registerAllBackends } from '../backends/index.js';\n// Base\nexport { BasePipeline, registerPipeline, getPipelineFactory, SENTIMENT_LABELS, EMOTION_LABELS, IMAGENET_LABELS, } from './base.js';\n// Text Classification\nexport { TextClassificationPipeline, SentimentAnalysisPipeline, createTextClassificationPipeline, createSentimentAnalysisPipeline, } from './text-classification.js';\n// Feature Extraction\nexport { FeatureExtractionPipeline, createFeatureExtractionPipeline, } from './feature-extraction.js';\n// Image Classification\nexport { ImageClassificationPipeline, createImageClassificationPipeline, } from './image-classification.js';\n// Text Generation\nexport { TextGenerationPipeline, createTextGenerationPipeline, } from './text-generation.js';\n// Object Detection\nexport { ObjectDetectionPipeline, createObjectDetectionPipeline, COCO_LABELS, } from './object-detection.js';\n// Automatic Speech Recognition\nexport { AutomaticSpeechRecognitionPipeline, createASRPipeline, } from './automatic-speech-recognition.js';\n// Zero-shot Classification\nexport { ZeroShotClassificationPipeline, createZeroShotClassificationPipeline, } from './zero-shot-classification.js';\n// Question Answering\nexport { QuestionAnsweringPipeline, createQuestionAnsweringPipeline, } from './question-answering.js';\n// Image Segmentation\nexport { ImageSegmentationPipeline, createImageSegmentationPipeline, } from './image-segmentation.js';\n// Import pipeline classes\nimport { TextClassificationPipeline, SentimentAnalysisPipeline } from './text-classification.js';\nimport { FeatureExtractionPipeline } from './feature-extraction.js';\nimport { ImageClassificationPipeline } from './image-classification.js';\nimport { TextGenerationPipeline } from './text-generation.js';\nimport { ObjectDetectionPipeline } from './object-detection.js';\nimport { AutomaticSpeechRecognitionPipeline } from './automatic-speech-recognition.js';\nimport { ZeroShotClassificationPipeline } from './zero-shot-classification.js';\nimport { QuestionAnsweringPipeline } from './question-answering.js';\nimport { ImageSegmentationPipeline } from './image-segmentation.js';\n/**\n * Create a pipeline for a specific task\n *\n * @example\n * ```typescript\n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n * const result = await sentiment.run('I love this product!');\n *\n * // Create an image classifier with custom model\n * const classifier = await pipeline('image-classification', {\n *   model: 'https://example.com/model.bin',\n * });\n * ```\n */\nexport async function pipeline(task, options) {\n    // Guarantee backends are registered before any model loads.\n    // registerAllBackends() is synchronous and idempotent (safe to call repeatedly).\n    registerAllBackends();\n    const config = {\n        task: task,\n        model: options?.model ?? 'default',\n        runtime: options?.runtime,\n        cache: options?.cache ?? true,\n        quantization: options?.quantization,\n    };\n    let pipelineInstance;\n    switch (task) {\n        case 'text-classification':\n            pipelineInstance = new TextClassificationPipeline(config, options?.labels);\n            break;\n        case 'sentiment-analysis':\n            pipelineInstance = new SentimentAnalysisPipeline(config);\n            break;\n        case 'feature-extraction':\n            pipelineInstance = new FeatureExtractionPipeline(config);\n            break;\n        case 'image-classification':\n            pipelineInstance = new ImageClassificationPipeline(config, options?.labels);\n            break;\n        case 'text-generation':\n            pipelineInstance = new TextGenerationPipeline(config);\n            break;\n        case 'object-detection':\n            pipelineInstance = new ObjectDetectionPipeline(config, options?.labels);\n            break;\n        case 'automatic-speech-recognition':\n            pipelineInstance = new AutomaticSpeechRecognitionPipeline(config);\n            break;\n        case 'zero-shot-classification':\n            pipelineInstance = new ZeroShotClassificationPipeline(config);\n            break;\n        case 'question-answering':\n            pipelineInstance = new QuestionAnsweringPipeline(config);\n            break;\n        case 'image-segmentation':\n            pipelineInstance = new ImageSegmentationPipeline(config);\n            break;\n        default: {\n            // Check if a plugin provides this pipeline task\n            const pluginEntry = getPluginPipeline(task);\n            if (pluginEntry) {\n                pipelineInstance = pluginEntry.factory(config);\n                break;\n            }\n            throw new Error(`Unknown pipeline task: \"${task}\". ` +\n                `Register a plugin with registerPlugin() to add custom pipeline tasks.`);\n        }\n    }\n    // Initialize the pipeline\n    await pipelineInstance.initialize();\n    return pipelineInstance;\n}\n/**\n * Create multiple pipelines at once\n */\nexport async function createPipelines(tasks, options) {\n    const pipelines = await Promise.all(tasks.map(task => pipeline(task, options)));\n    const result = {};\n    for (let i = 0; i < tasks.length; i++) {\n        const task = tasks[i];\n        result[task] = pipelines[i];\n    }\n    return result;\n}\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/pipelines/object-detection.d.ts",
    "content": "/**\n * edgeFlow.js - Object Detection Pipeline\n *\n * Detect objects in images with bounding boxes and class labels.\n */\nimport { BasePipeline, ObjectDetectionResult } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { type ImageInput } from '../utils/preprocessor.js';\nexport interface ObjectDetectionOptions extends PipelineOptions {\n    threshold?: number;\n    topK?: number;\n    nms?: boolean;\n    iouThreshold?: number;\n}\nexport interface BoundingBox {\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n}\nexport interface Detection extends ObjectDetectionResult {\n    classId: number;\n    boxNormalized: BoundingBox;\n}\nexport declare const COCO_LABELS: string[];\nexport declare class ObjectDetectionPipeline extends BasePipeline<ImageInput | ImageInput[], Detection[]> {\n    private preprocessor;\n    private onnxModel;\n    private labels;\n    private modelUrl;\n    constructor(config?: PipelineConfig, labels?: string[]);\n    initialize(): Promise<void>;\n    setLabels(labels: string[]): void;\n    run(input: ImageInput | ImageInput[], options?: ObjectDetectionOptions): Promise<Detection[]>;\n    protected preprocess(input: ImageInput | ImageInput[]): Promise<EdgeFlowTensor[]>;\n    private runModelInference;\n    protected postprocess(outputs: EdgeFlowTensor[], options?: PipelineOptions): Promise<Detection[]>;\n    private parseDetections;\n    private nonMaxSuppression;\n    private computeIoU;\n}\nexport declare function createObjectDetectionPipeline(config?: PipelineConfig, labels?: string[]): ObjectDetectionPipeline;\n//# sourceMappingURL=object-detection.d.ts.map"
  },
  {
    "path": "dist/pipelines/object-detection.js",
    "content": "/**\n * edgeFlow.js - Object Detection Pipeline\n *\n * Detect objects in images with bounding boxes and class labels.\n */\nimport { BasePipeline, registerPipeline } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { ImagePreprocessor } from '../utils/preprocessor.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference } from '../core/runtime.js';\n// ============================================================================\n// Default Model (YOLOS-tiny, quantized)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/yolos-tiny/resolve/main/onnx/model_quantized.onnx',\n};\n// ============================================================================\n// COCO Labels\n// ============================================================================\nexport const COCO_LABELS = [\n    'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck',\n    'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench',\n    'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra',\n    'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',\n    'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove',\n    'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',\n    'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',\n    'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',\n    'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse',\n    'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink',\n    'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier',\n    'toothbrush'\n];\n// ============================================================================\n// Object Detection Pipeline\n// ============================================================================\nexport class ObjectDetectionPipeline extends BasePipeline {\n    preprocessor;\n    onnxModel = null;\n    labels;\n    modelUrl;\n    constructor(config, labels) {\n        super(config ?? {\n            task: 'object-detection',\n            model: 'default',\n        });\n        this.labels = labels ?? COCO_LABELS;\n        this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n        this.preprocessor = new ImagePreprocessor({\n            width: 640,\n            height: 640,\n            mean: [0.485, 0.456, 0.406],\n            std: [0.229, 0.224, 0.225],\n            channelFormat: 'CHW',\n        });\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    setLabels(labels) {\n        this.labels = labels;\n    }\n    async run(input, options) {\n        await this.initialize();\n        const tensorInputs = await this.preprocess(input);\n        const outputs = await this.runModelInference(tensorInputs);\n        return this.postprocess(outputs, options);\n    }\n    async preprocess(input) {\n        const inputs = Array.isArray(input) ? input : [input];\n        if (inputs.length === 1) {\n            const tensor = await this.preprocessor.process(inputs[0]);\n            return [new EdgeFlowTensor(tensor.toFloat32Array(), [1, ...tensor.shape], 'float32')];\n        }\n        return [await this.preprocessor.processBatch(inputs)];\n    }\n    async runModelInference(inputs) {\n        const outputs = await runInference(this.onnxModel, inputs);\n        return outputs;\n    }\n    async postprocess(outputs, options) {\n        const opts = options ?? {};\n        const threshold = opts.threshold ?? 0.5;\n        const topK = opts.topK ?? 100;\n        const nms = opts.nms ?? true;\n        const iouThreshold = opts.iouThreshold ?? 0.5;\n        if (!outputs[0]) {\n            return [];\n        }\n        const outputData = outputs[0].toFloat32Array();\n        const shape = [...outputs[0].shape];\n        const detections = this.parseDetections(outputData, shape, threshold);\n        let filtered = nms ? this.nonMaxSuppression(detections, iouThreshold) : detections;\n        filtered.sort((a, b) => b.score - a.score);\n        filtered = filtered.slice(0, topK);\n        return filtered;\n    }\n    parseDetections(data, shape, threshold) {\n        const detections = [];\n        const numBoxes = shape[1] ?? 0;\n        const boxSize = shape[2] ?? 0;\n        if (boxSize >= 5) {\n            const numClasses = boxSize - 5;\n            for (let i = 0; i < numBoxes; i++) {\n                const offset = i * boxSize;\n                const objectness = data[offset + 4] ?? 0;\n                if (objectness < threshold)\n                    continue;\n                let maxClassScore = 0;\n                let maxClassIdx = 0;\n                for (let c = 0; c < numClasses; c++) {\n                    const score = data[offset + 5 + c] ?? 0;\n                    if (score > maxClassScore) {\n                        maxClassScore = score;\n                        maxClassIdx = c;\n                    }\n                }\n                const confidence = objectness * maxClassScore;\n                if (confidence < threshold)\n                    continue;\n                const x = data[offset] ?? 0;\n                const y = data[offset + 1] ?? 0;\n                const w = data[offset + 2] ?? 0;\n                const h = data[offset + 3] ?? 0;\n                detections.push({\n                    label: this.labels[maxClassIdx] ?? `class_${maxClassIdx}`,\n                    score: confidence,\n                    classId: maxClassIdx,\n                    box: {\n                        x: Math.max(0, x - w / 2),\n                        y: Math.max(0, y - h / 2),\n                        width: w,\n                        height: h,\n                    },\n                    boxNormalized: {\n                        x: Math.max(0, x - w / 2),\n                        y: Math.max(0, y - h / 2),\n                        width: w,\n                        height: h,\n                    },\n                });\n            }\n        }\n        else if (boxSize === 4) {\n            for (let i = 0; i < numBoxes; i++) {\n                const offset = i * boxSize;\n                const x1 = data[offset] ?? 0;\n                const y1 = data[offset + 1] ?? 0;\n                const x2 = data[offset + 2] ?? 0;\n                const y2 = data[offset + 3] ?? 0;\n                detections.push({\n                    label: this.labels[0] ?? 'object',\n                    score: 1.0,\n                    classId: 0,\n                    box: {\n                        x: x1,\n                        y: y1,\n                        width: x2 - x1,\n                        height: y2 - y1,\n                    },\n                    boxNormalized: {\n                        x: x1,\n                        y: y1,\n                        width: x2 - x1,\n                        height: y2 - y1,\n                    },\n                });\n            }\n        }\n        return detections;\n    }\n    nonMaxSuppression(detections, iouThreshold) {\n        if (detections.length === 0)\n            return [];\n        const sorted = [...detections].sort((a, b) => b.score - a.score);\n        const selected = [];\n        const active = new Array(sorted.length).fill(true);\n        for (let i = 0; i < sorted.length; i++) {\n            if (!active[i])\n                continue;\n            const current = sorted[i];\n            selected.push(current);\n            for (let j = i + 1; j < sorted.length; j++) {\n                if (!active[j])\n                    continue;\n                const other = sorted[j];\n                if (current.classId !== other.classId)\n                    continue;\n                const iou = this.computeIoU(current.box, other.box);\n                if (iou > iouThreshold) {\n                    active[j] = false;\n                }\n            }\n        }\n        return selected;\n    }\n    computeIoU(a, b) {\n        const xOverlap = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));\n        const yOverlap = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));\n        const intersection = xOverlap * yOverlap;\n        const aArea = a.width * a.height;\n        const bArea = b.width * b.height;\n        const union = aArea + bArea - intersection;\n        return union > 0 ? intersection / union : 0;\n    }\n}\n// ============================================================================\n// Factory\n// ============================================================================\nexport function createObjectDetectionPipeline(config, labels) {\n    return new ObjectDetectionPipeline(config, labels);\n}\nregisterPipeline('object-detection', (config) => new ObjectDetectionPipeline(config));\n//# sourceMappingURL=object-detection.js.map"
  },
  {
    "path": "dist/pipelines/question-answering.d.ts",
    "content": "/**\n * edgeFlow.js - Question Answering Pipeline\n *\n * Extract answers from context given a question using real ONNX QA models.\n */\nimport { BasePipeline, PipelineResult } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nexport interface QAInput {\n    question: string;\n    context: string;\n}\nexport interface QuestionAnsweringOptions extends PipelineOptions {\n    maxAnswerLength?: number;\n    maxQuestionLength?: number;\n    topK?: number;\n    threshold?: number;\n    handleImpossible?: boolean;\n}\nexport interface QuestionAnsweringResult extends PipelineResult {\n    answer: string;\n    score: number;\n    start: number;\n    end: number;\n}\nexport declare class QuestionAnsweringPipeline extends BasePipeline<QAInput | QAInput[], QuestionAnsweringResult | QuestionAnsweringResult[]> {\n    private tokenizer;\n    private onnxModel;\n    private modelUrl;\n    private tokenizerUrl;\n    constructor(config?: PipelineConfig);\n    initialize(): Promise<void>;\n    setTokenizer(tokenizer: Tokenizer): void;\n    run(input: QAInput | QAInput[], options?: QuestionAnsweringOptions): Promise<QuestionAnsweringResult | QuestionAnsweringResult[]>;\n    private answerQuestion;\n    private tokenOffsetToCharOffset;\n    protected preprocess(input: QAInput | QAInput[]): Promise<EdgeFlowTensor[]>;\n    protected postprocess(outputs: EdgeFlowTensor[], _options?: PipelineOptions): Promise<QuestionAnsweringResult | QuestionAnsweringResult[]>;\n}\nexport declare function createQuestionAnsweringPipeline(config?: PipelineConfig): QuestionAnsweringPipeline;\n//# sourceMappingURL=question-answering.d.ts.map"
  },
  {
    "path": "dist/pipelines/question-answering.js",
    "content": "/**\n * edgeFlow.js - Question Answering Pipeline\n *\n * Extract answers from context given a question using real ONNX QA models.\n */\nimport { BasePipeline, registerPipeline } from './base.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\n// ============================================================================\n// Default Model (DistilBERT fine-tuned on SQuAD)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/onnx/model_quantized.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/tokenizer.json',\n};\n// ============================================================================\n// Question Answering Pipeline\n// ============================================================================\nexport class QuestionAnsweringPipeline extends BasePipeline {\n    tokenizer = null;\n    onnxModel = null;\n    modelUrl;\n    tokenizerUrl;\n    constructor(config) {\n        super(config ?? {\n            task: 'question-answering',\n            model: 'default',\n        });\n        this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n        this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.tokenizer) {\n            this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n        }\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    setTokenizer(tokenizer) {\n        this.tokenizer = tokenizer;\n    }\n    async run(input, options) {\n        await this.initialize();\n        const inputs = Array.isArray(input) ? input : [input];\n        const results = await Promise.all(inputs.map(i => this.answerQuestion(i, options ?? {})));\n        return Array.isArray(input) ? results : results[0];\n    }\n    async answerQuestion(input, options) {\n        const startTime = performance.now();\n        const { question, context } = input;\n        const maxAnswerLength = options.maxAnswerLength ?? 30;\n        const encoded = this.tokenizer.encode(question, {\n            textPair: context,\n            addSpecialTokens: true,\n            maxLength: 512,\n            truncation: true,\n            returnAttentionMask: true,\n            returnTokenTypeIds: true,\n        });\n        const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64');\n        const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))), [1, encoded.attentionMask.length], 'int64');\n        const namedInputs = new Map();\n        namedInputs.set('input_ids', inputIds);\n        namedInputs.set('attention_mask', attentionMask);\n        const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n        if (outputs.length < 2) {\n            return { answer: '', score: 0, start: 0, end: 0, processingTime: performance.now() - startTime };\n        }\n        const startLogits = outputs[0].toFloat32Array();\n        const endLogits = outputs[1].toFloat32Array();\n        const seqLen = startLogits.length;\n        const startProbs = softmax(new EdgeFlowTensor(new Float32Array(startLogits), [seqLen], 'float32')).toFloat32Array();\n        const endProbs = softmax(new EdgeFlowTensor(new Float32Array(endLogits), [seqLen], 'float32')).toFloat32Array();\n        // Find best start/end token positions\n        let bestStartIdx = 0;\n        let bestEndIdx = 0;\n        let bestScore = 0;\n        for (let s = 0; s < seqLen; s++) {\n            for (let e = s; e < Math.min(s + maxAnswerLength, seqLen); e++) {\n                const score = (startProbs[s] ?? 0) * (endProbs[e] ?? 0);\n                if (score > bestScore) {\n                    bestScore = score;\n                    bestStartIdx = s;\n                    bestEndIdx = e;\n                }\n            }\n        }\n        // Decode the answer span back to text\n        const answerTokenIds = encoded.inputIds.slice(bestStartIdx, bestEndIdx + 1);\n        const answer = this.tokenizer.decode(answerTokenIds, true);\n        // Map token positions back to character offsets in context\n        const charStart = this.tokenOffsetToCharOffset(context, question, encoded.inputIds, bestStartIdx);\n        const charEnd = this.tokenOffsetToCharOffset(context, question, encoded.inputIds, bestEndIdx) + 1;\n        return {\n            answer: answer || '',\n            score: bestScore,\n            start: charStart,\n            end: charEnd,\n            processingTime: performance.now() - startTime,\n        };\n    }\n    tokenOffsetToCharOffset(context, _question, inputIds, tokenIdx) {\n        // Approximate mapping: decode tokens up to this index and measure length\n        // For a production implementation you'd use the tokenizer's offset mapping.\n        const decoded = this.tokenizer.decode(inputIds.slice(0, tokenIdx + 1), true);\n        const contextStart = context.indexOf(decoded.trim().split(' ').pop() ?? '');\n        return contextStart >= 0 ? contextStart : 0;\n    }\n    async preprocess(input) {\n        const qaInput = Array.isArray(input) ? input[0] : input;\n        const encoded = this.tokenizer.encode(qaInput.question, {\n            textPair: qaInput.context,\n            addSpecialTokens: true,\n            maxLength: 512,\n            truncation: true,\n            returnAttentionMask: true,\n            returnTokenTypeIds: true,\n        });\n        return [\n            new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64'),\n            new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))), [1, encoded.attentionMask.length], 'int64'),\n        ];\n    }\n    async postprocess(outputs, _options) {\n        if (outputs.length < 2) {\n            return { answer: '', score: 0, start: 0, end: 0 };\n        }\n        const startLogits = outputs[0].toFloat32Array();\n        const endLogits = outputs[1].toFloat32Array();\n        const seqLen = startLogits.length;\n        const startProbs = softmax(new EdgeFlowTensor(startLogits, [seqLen], 'float32')).toFloat32Array();\n        const endProbs = softmax(new EdgeFlowTensor(endLogits, [seqLen], 'float32')).toFloat32Array();\n        let bestStart = 0;\n        let bestEnd = 0;\n        let bestScore = 0;\n        for (let start = 0; start < seqLen; start++) {\n            for (let end = start; end < Math.min(start + 30, seqLen); end++) {\n                const score = (startProbs[start] ?? 0) * (endProbs[end] ?? 0);\n                if (score > bestScore) {\n                    bestScore = score;\n                    bestStart = start;\n                    bestEnd = end;\n                }\n            }\n        }\n        return {\n            answer: '',\n            score: bestScore,\n            start: bestStart,\n            end: bestEnd,\n        };\n    }\n}\n// ============================================================================\n// Factory\n// ============================================================================\nexport function createQuestionAnsweringPipeline(config) {\n    return new QuestionAnsweringPipeline(config);\n}\nregisterPipeline('question-answering', (config) => new QuestionAnsweringPipeline(config));\n//# sourceMappingURL=question-answering.js.map"
  },
  {
    "path": "dist/pipelines/text-classification.d.ts",
    "content": "/**\n * edgeFlow.js - Text Classification Pipeline\n *\n * High-level API for text classification tasks including\n * sentiment analysis, topic classification, etc.\n */\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, TextClassificationResult } from './base.js';\nexport interface TextClassificationOptions extends PipelineOptions {\n    returnAllScores?: boolean;\n    labels?: string[];\n    topK?: number;\n}\nexport declare class TextClassificationPipeline extends BasePipeline<string | string[], TextClassificationResult | TextClassificationResult[]> {\n    private tokenizer;\n    private onnxModel;\n    private labels;\n    private modelUrl;\n    private tokenizerUrl;\n    constructor(config: PipelineConfig, labels?: string[]);\n    initialize(): Promise<void>;\n    setLabels(labels: string[]): void;\n    run(input: string | string[], options?: TextClassificationOptions): Promise<TextClassificationResult | TextClassificationResult[]>;\n    protected preprocess(input: string | string[]): Promise<EdgeFlowTensor[]>;\n    private runInference;\n    protected postprocess(outputs: EdgeFlowTensor[], options?: TextClassificationOptions): Promise<TextClassificationResult>;\n}\nexport declare class SentimentAnalysisPipeline extends TextClassificationPipeline {\n    constructor(config: PipelineConfig);\n    analyze(text: string | string[], options?: TextClassificationOptions): Promise<TextClassificationResult | TextClassificationResult[]>;\n}\nexport declare function createTextClassificationPipeline(config?: Partial<PipelineConfig>): TextClassificationPipeline;\nexport declare function createSentimentAnalysisPipeline(config?: Partial<PipelineConfig>): SentimentAnalysisPipeline;\n//# sourceMappingURL=text-classification.d.ts.map"
  },
  {
    "path": "dist/pipelines/text-classification.js",
    "content": "/**\n * edgeFlow.js - Text Classification Pipeline\n *\n * High-level API for text classification tasks including\n * sentiment analysis, topic classification, etc.\n */\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\nimport { BasePipeline, registerPipeline, SENTIMENT_LABELS, } from './base.js';\n// ============================================================================\n// Default Model (DistilBERT fine-tuned on SST-2)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/tokenizer.json',\n};\nconst DEFAULT_SST2_LABELS = ['NEGATIVE', 'POSITIVE'];\nexport class TextClassificationPipeline extends BasePipeline {\n    tokenizer = null;\n    onnxModel = null;\n    labels;\n    modelUrl;\n    tokenizerUrl;\n    constructor(config, labels) {\n        super(config);\n        this.labels = labels ?? DEFAULT_SST2_LABELS;\n        this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n        this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.tokenizer) {\n            this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n        }\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    setLabels(labels) {\n        this.labels = labels;\n    }\n    async run(input, options) {\n        const isBatch = Array.isArray(input);\n        const inputs = isBatch ? input : [input];\n        await this.initialize();\n        const startTime = performance.now();\n        const results = [];\n        for (const text of inputs) {\n            const tensorInputs = await this.preprocess(text);\n            const outputs = await this.runInference(tensorInputs);\n            const result = await this.postprocess(outputs, options);\n            results.push(result);\n        }\n        const processingTime = performance.now() - startTime;\n        for (const result of results) {\n            result.processingTime = processingTime / results.length;\n        }\n        return isBatch ? results : results[0];\n    }\n    async preprocess(input) {\n        const text = Array.isArray(input) ? input[0] : input;\n        const encoded = this.tokenizer.encode(text, {\n            maxLength: 128,\n            padding: 'max_length',\n            truncation: true,\n        });\n        const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64');\n        const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))), [1, encoded.attentionMask.length], 'int64');\n        return [inputIds, attentionMask];\n    }\n    async runInference(inputs) {\n        const namedInputs = new Map();\n        namedInputs.set('input_ids', inputs[0]);\n        namedInputs.set('attention_mask', inputs[1]);\n        const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n        return outputs;\n    }\n    async postprocess(outputs, options) {\n        const logits = outputs[0];\n        if (!logits) {\n            return { label: 'unknown', score: 0 };\n        }\n        const probs = softmax(logits, -1);\n        const probsArray = probs.toFloat32Array();\n        let maxIdx = 0;\n        let maxScore = probsArray[0] ?? 0;\n        for (let i = 1; i < probsArray.length; i++) {\n            if ((probsArray[i] ?? 0) > maxScore) {\n                maxScore = probsArray[i] ?? 0;\n                maxIdx = i;\n            }\n        }\n        const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n        return {\n            label,\n            score: maxScore,\n        };\n    }\n}\n// ============================================================================\n// Sentiment Analysis Pipeline\n// ============================================================================\nexport class SentimentAnalysisPipeline extends TextClassificationPipeline {\n    constructor(config) {\n        super(config, SENTIMENT_LABELS);\n    }\n    async analyze(text, options) {\n        return this.run(text, options);\n    }\n}\n// ============================================================================\n// Factory Functions\n// ============================================================================\nexport function createTextClassificationPipeline(config = {}) {\n    return new TextClassificationPipeline({\n        task: 'text-classification',\n        model: config.model ?? 'default',\n        runtime: config.runtime,\n        cache: config.cache ?? true,\n        quantization: config.quantization,\n    });\n}\nexport function createSentimentAnalysisPipeline(config = {}) {\n    return new SentimentAnalysisPipeline({\n        task: 'sentiment-analysis',\n        model: config.model ?? 'default',\n        runtime: config.runtime,\n        cache: config.cache ?? true,\n        quantization: config.quantization,\n    });\n}\nregisterPipeline('text-classification', (config) => new TextClassificationPipeline(config));\nregisterPipeline('sentiment-analysis', (config) => new SentimentAnalysisPipeline(config));\n//# sourceMappingURL=text-classification.js.map"
  },
  {
    "path": "dist/pipelines/text-generation.d.ts",
    "content": "/**\n * edgeFlow.js - Text Generation Pipeline\n *\n * Autoregressive text generation with streaming support.\n * Supports GPT-2, LLaMA, Mistral, and other causal LM models.\n * Includes chat/conversation support with message history.\n */\nimport { BasePipeline, PipelineResult } from './base.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\n/**\n * LLM model loading progress callback\n */\nexport interface LLMLoadProgress {\n    /** Stage: 'tokenizer' or 'model' */\n    stage: 'tokenizer' | 'model';\n    /** Bytes loaded */\n    loaded: number;\n    /** Total bytes */\n    total: number;\n    /** Progress percentage (0-100) */\n    progress: number;\n}\n/**\n * Chat message\n */\nexport interface ChatMessage {\n    /** Role: 'system', 'user', or 'assistant' */\n    role: 'system' | 'user' | 'assistant';\n    /** Message content */\n    content: string;\n}\n/**\n * Chat template type\n */\nexport type ChatTemplateType = 'chatml' | 'llama2' | 'llama3' | 'mistral' | 'phi3' | 'alpaca' | 'vicuna' | 'custom';\n/**\n * Text generation options\n */\nexport interface TextGenerationOptions {\n    /** Maximum number of new tokens to generate */\n    maxNewTokens?: number;\n    /** Maximum total length (prompt + generated) */\n    maxLength?: number;\n    /** Minimum number of new tokens to generate */\n    minNewTokens?: number;\n    /** Sampling temperature (higher = more random) */\n    temperature?: number;\n    /** Top-k sampling (0 = disabled) */\n    topK?: number;\n    /** Top-p (nucleus) sampling (1.0 = disabled) */\n    topP?: number;\n    /** Repetition penalty (1.0 = disabled) */\n    repetitionPenalty?: number;\n    /** Stop sequences */\n    stopSequences?: string[];\n    /** Whether to do sampling (false = greedy) */\n    doSample?: boolean;\n    /** Number of sequences to return */\n    numReturnSequences?: number;\n    /** Return full text (including prompt) */\n    returnFullText?: boolean;\n    /** Callback for each generated token */\n    onToken?: (token: string, tokenId: number) => void;\n}\n/**\n * Chat generation options\n */\nexport interface ChatOptions extends TextGenerationOptions {\n    /** System prompt */\n    systemPrompt?: string;\n    /** Chat template type */\n    templateType?: ChatTemplateType;\n    /** Custom template (if templateType is 'custom') */\n    customTemplate?: {\n        systemPrefix?: string;\n        systemSuffix?: string;\n        userPrefix?: string;\n        userSuffix?: string;\n        assistantPrefix?: string;\n        assistantSuffix?: string;\n        separator?: string;\n    };\n}\n/**\n * Text generation result\n */\nexport interface TextGenerationResult extends PipelineResult {\n    /** Generated text */\n    generatedText: string;\n    /** Full text (prompt + generated) if returnFullText is true */\n    fullText?: string;\n    /** Generated token IDs */\n    tokenIds: number[];\n    /** Number of tokens generated */\n    numTokens: number;\n}\n/**\n * Streaming generation event\n */\nexport interface GenerationStreamEvent {\n    /** Current token */\n    token: string;\n    /** Token ID */\n    tokenId: number;\n    /** Generated text so far */\n    generatedText: string;\n    /** Whether generation is complete */\n    done: boolean;\n}\n/**\n * TextGenerationPipeline - Autoregressive text generation\n *\n * @example\n * ```typescript\n * const generator = await pipeline('text-generation', 'Xenova/gpt2');\n *\n * // Simple generation\n * const result = await generator.run('Once upon a time');\n * console.log(result.generatedText);\n *\n * // Streaming generation\n * for await (const event of generator.stream('Hello, ')) {\n *   process.stdout.write(event.token);\n * }\n * ```\n */\nexport declare class TextGenerationPipeline extends BasePipeline<string | string[], TextGenerationResult | TextGenerationResult[]> {\n    private tokenizer;\n    private eosTokenId;\n    private llmModel;\n    private modelsLoaded;\n    private modelUrl;\n    private tokenizerUrl;\n    constructor(config?: PipelineConfig);\n    /**\n     * Check if model is loaded\n     */\n    get isModelLoaded(): boolean;\n    /**\n     * Set custom model URLs\n     */\n    setModelUrls(model: string, tokenizer: string): void;\n    /**\n     * Load model and tokenizer with progress callback\n     */\n    loadModel(onProgress?: (progress: LLMLoadProgress) => void): Promise<void>;\n    /**\n     * Fetch model with progress tracking\n     */\n    private fetchModelWithProgress;\n    /**\n     * Initialize pipeline (override to skip default model loading)\n     */\n    initialize(): Promise<void>;\n    /**\n     * Set tokenizer\n     */\n    setTokenizer(tokenizer: Tokenizer): void;\n    /**\n     * Preprocess - not used for text generation (handled in generateSingle)\n     */\n    protected preprocess(input: string | string[]): Promise<EdgeFlowTensor[]>;\n    /**\n     * Postprocess - not used for text generation (handled in generateSingle)\n     */\n    protected postprocess(_outputs: EdgeFlowTensor[], _options?: PipelineOptions): Promise<TextGenerationResult | TextGenerationResult[]>;\n    /**\n     * Generate text (non-streaming)\n     */\n    run(prompt: string | string[], options?: PipelineOptions & TextGenerationOptions): Promise<TextGenerationResult | TextGenerationResult[]>;\n    /**\n     * Generate text with streaming (async generator)\n     */\n    stream(prompt: string, options?: TextGenerationOptions): AsyncGenerator<GenerationStreamEvent>;\n    /**\n     * Generate a single sequence (non-streaming)\n     */\n    private generateSingle;\n    /**\n     * Generate next token using the model\n     */\n    private generateNextToken;\n    /**\n     * Greedy decoding (argmax)\n     */\n    private greedy;\n    /**\n     * Sample from probability distribution with top-k/top-p filtering\n     */\n    private sample;\n    private conversationHistory;\n    private chatTemplateType;\n    /**\n     * Set the chat template type\n     */\n    setChatTemplate(templateType: ChatTemplateType): void;\n    /**\n     * Apply chat template to messages\n     */\n    applyChatTemplate(messages: ChatMessage[], options?: ChatOptions): string;\n    /**\n     * ChatML template (used by many models including Qwen, Yi)\n     */\n    private applyChatMLTemplate;\n    /**\n     * Llama 2 template\n     */\n    private applyLlama2Template;\n    /**\n     * Llama 3 template\n     */\n    private applyLlama3Template;\n    /**\n     * Mistral template\n     */\n    private applyMistralTemplate;\n    /**\n     * Phi-3 template\n     */\n    private applyPhi3Template;\n    /**\n     * Alpaca template\n     */\n    private applyAlpacaTemplate;\n    /**\n     * Vicuna template\n     */\n    private applyVicunaTemplate;\n    /**\n     * Custom template\n     */\n    private applyCustomTemplate;\n    /**\n     * Chat with the model\n     *\n     * @example\n     * ```typescript\n     * const generator = await pipeline('text-generation', 'model');\n     *\n     * // Single turn\n     * const response = await generator.chat('Hello, how are you?');\n     *\n     * // Multi-turn with history\n     * const response1 = await generator.chat('What is AI?');\n     * const response2 = await generator.chat('Can you give an example?');\n     *\n     * // With system prompt\n     * const response = await generator.chat('Hello', {\n     *   systemPrompt: 'You are a helpful assistant.',\n     * });\n     * ```\n     */\n    chat(userMessage: string, options?: ChatOptions): Promise<TextGenerationResult>;\n    /**\n     * Stream chat response\n     */\n    chatStream(userMessage: string, options?: ChatOptions): AsyncGenerator<GenerationStreamEvent>;\n    /**\n     * Get conversation history\n     */\n    getConversationHistory(): ChatMessage[];\n    /**\n     * Set conversation history\n     */\n    setConversationHistory(messages: ChatMessage[]): void;\n    /**\n     * Clear conversation history\n     */\n    clearConversation(): void;\n    /**\n     * Remove last exchange (user message + assistant response)\n     */\n    undoLastExchange(): void;\n}\n/**\n * Create text generation pipeline\n */\nexport declare function createTextGenerationPipeline(config?: PipelineConfig): TextGenerationPipeline;\n//# sourceMappingURL=text-generation.d.ts.map"
  },
  {
    "path": "dist/pipelines/text-generation.js",
    "content": "/**\n * edgeFlow.js - Text Generation Pipeline\n *\n * Autoregressive text generation with streaming support.\n * Supports GPT-2, LLaMA, Mistral, and other causal LM models.\n * Includes chat/conversation support with message history.\n */\nimport { BasePipeline } from './base.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { runInferenceNamed, loadModelFromBuffer } from '../core/runtime.js';\n// ============================================================================\n// Default Model URLs (TinyLlama - quantized for browser)\n// ============================================================================\nconst DEFAULT_LLM_MODELS = {\n    model: 'https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/onnx/model_q4f16.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/tokenizer.json',\n};\n// ============================================================================\n// Text Generation Pipeline\n// ============================================================================\n/**\n * TextGenerationPipeline - Autoregressive text generation\n *\n * @example\n * ```typescript\n * const generator = await pipeline('text-generation', 'Xenova/gpt2');\n *\n * // Simple generation\n * const result = await generator.run('Once upon a time');\n * console.log(result.generatedText);\n *\n * // Streaming generation\n * for await (const event of generator.stream('Hello, ')) {\n *   process.stdout.write(event.token);\n * }\n * ```\n */\nexport class TextGenerationPipeline extends BasePipeline {\n    tokenizer = null;\n    eosTokenId = 50256; // GPT-2 default\n    llmModel = null;\n    modelsLoaded = false;\n    // Custom model URLs\n    modelUrl;\n    tokenizerUrl;\n    constructor(config) {\n        super(config ?? {\n            task: 'text-generation',\n            model: 'default',\n        });\n        this.modelUrl = DEFAULT_LLM_MODELS.model;\n        this.tokenizerUrl = DEFAULT_LLM_MODELS.tokenizer;\n    }\n    /**\n     * Check if model is loaded\n     */\n    get isModelLoaded() {\n        return this.modelsLoaded;\n    }\n    /**\n     * Set custom model URLs\n     */\n    setModelUrls(model, tokenizer) {\n        this.modelUrl = model;\n        this.tokenizerUrl = tokenizer;\n    }\n    /**\n     * Load model and tokenizer with progress callback\n     */\n    async loadModel(onProgress) {\n        if (this.modelsLoaded)\n            return;\n        // Load tokenizer first (small, fast)\n        onProgress?.({ stage: 'tokenizer', loaded: 0, total: 100, progress: 0 });\n        try {\n            const tokenizerResponse = await fetch(this.tokenizerUrl);\n            if (!tokenizerResponse.ok) {\n                throw new Error(`Failed to fetch tokenizer: ${tokenizerResponse.status}`);\n            }\n            const tokenizerJson = await tokenizerResponse.json();\n            this.tokenizer = await Tokenizer.fromJSON(tokenizerJson);\n            const specialIds = this.tokenizer.getSpecialTokenIds();\n            this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 2; // TinyLlama uses 2 as EOS\n            onProgress?.({ stage: 'tokenizer', loaded: 100, total: 100, progress: 100 });\n        }\n        catch (error) {\n            throw new Error(`Failed to load tokenizer: ${error}`);\n        }\n        // Load model with progress tracking\n        onProgress?.({ stage: 'model', loaded: 0, total: 100, progress: 0 });\n        const modelData = await this.fetchModelWithProgress(this.modelUrl, (loaded, total) => {\n            onProgress?.({\n                stage: 'model',\n                loaded,\n                total,\n                progress: Math.round((loaded / total) * 100),\n            });\n        });\n        this.llmModel = await loadModelFromBuffer(modelData, {\n            runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n        });\n        this.model = this.llmModel;\n        this.modelsLoaded = true;\n    }\n    /**\n     * Fetch model with progress tracking\n     */\n    async fetchModelWithProgress(url, onProgress) {\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n        }\n        const contentLength = response.headers.get('content-length');\n        const total = contentLength ? parseInt(contentLength, 10) : 0;\n        if (!response.body) {\n            // Fallback if no streaming support\n            const buffer = await response.arrayBuffer();\n            onProgress(buffer.byteLength, buffer.byteLength);\n            return buffer;\n        }\n        const reader = response.body.getReader();\n        const chunks = [];\n        let loaded = 0;\n        while (true) {\n            const { done, value } = await reader.read();\n            if (done)\n                break;\n            chunks.push(value);\n            loaded += value.length;\n            onProgress(loaded, total || loaded);\n        }\n        // Combine chunks into ArrayBuffer\n        const buffer = new Uint8Array(loaded);\n        let offset = 0;\n        for (const chunk of chunks) {\n            buffer.set(chunk, offset);\n            offset += chunk.length;\n        }\n        return buffer.buffer;\n    }\n    /**\n     * Initialize pipeline (override to skip default model loading)\n     */\n    async initialize() {\n        if (this.isReady)\n            return;\n        // Don't call super.initialize() - we handle model loading separately\n        this.isReady = true;\n    }\n    /**\n     * Set tokenizer\n     */\n    setTokenizer(tokenizer) {\n        this.tokenizer = tokenizer;\n        const specialIds = tokenizer.getSpecialTokenIds();\n        this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 50256;\n    }\n    /**\n     * Preprocess - not used for text generation (handled in generateSingle)\n     */\n    async preprocess(input) {\n        // For text generation, preprocessing is handled in generateNextToken\n        const text = Array.isArray(input) ? input[0] ?? '' : input;\n        if (!this.tokenizer) {\n            // Return dummy tensor if no tokenizer\n            return [new EdgeFlowTensor(new Float32Array([0]), [1], 'float32')];\n        }\n        const encoded = this.tokenizer.encode(text, {\n            addSpecialTokens: false,\n            padding: 'do_not_pad',\n        });\n        return [new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64')];\n    }\n    /**\n     * Postprocess - not used for text generation (handled in generateSingle)\n     */\n    async postprocess(_outputs, _options) {\n        // For text generation, postprocessing is handled in generateSingle\n        return {\n            generatedText: '',\n            tokenIds: [],\n            numTokens: 0,\n            processingTime: 0,\n        };\n    }\n    /**\n     * Generate text (non-streaming)\n     */\n    async run(prompt, options) {\n        await this.initialize();\n        const prompts = Array.isArray(prompt) ? prompt : [prompt];\n        const results = await Promise.all(prompts.map(p => this.generateSingle(p, options ?? {})));\n        return Array.isArray(prompt) ? results : results[0];\n    }\n    /**\n     * Generate text with streaming (async generator)\n     */\n    async *stream(prompt, options = {}) {\n        const startTime = performance.now();\n        if (!this.tokenizer) {\n            throw new Error('Tokenizer not set. Call setTokenizer() first.');\n        }\n        const { maxNewTokens = 50, maxLength = 512, temperature = 1.0, topK = 0, topP = 1.0, repetitionPenalty = 1.0, stopSequences = [], doSample = true, } = options;\n        // Encode prompt\n        const encoded = this.tokenizer.encode(prompt, {\n            addSpecialTokens: false,\n            padding: 'do_not_pad',\n            truncation: false,\n        });\n        let inputIds = [...encoded.inputIds];\n        const generatedIds = [];\n        let generatedText = '';\n        // Generation loop\n        for (let i = 0; i < maxNewTokens; i++) {\n            // Check max length\n            if (inputIds.length >= maxLength)\n                break;\n            // Run model forward pass\n            const nextTokenId = await this.generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample);\n            // Check for EOS\n            if (nextTokenId === this.eosTokenId) {\n                yield {\n                    token: '',\n                    tokenId: nextTokenId,\n                    generatedText,\n                    done: true,\n                };\n                break;\n            }\n            // Decode token\n            const token = this.tokenizer.decode([nextTokenId], true);\n            generatedIds.push(nextTokenId);\n            inputIds.push(nextTokenId);\n            generatedText += token;\n            // Call token callback\n            if (options.onToken) {\n                options.onToken(token, nextTokenId);\n            }\n            // Check stop sequences\n            let shouldStop = false;\n            for (const stopSeq of stopSequences) {\n                if (generatedText.endsWith(stopSeq)) {\n                    generatedText = generatedText.slice(0, -stopSeq.length);\n                    shouldStop = true;\n                    break;\n                }\n            }\n            yield {\n                token,\n                tokenId: nextTokenId,\n                generatedText,\n                done: shouldStop,\n            };\n            if (shouldStop)\n                break;\n        }\n        // Final event\n        const endTime = performance.now();\n        console.log(`Generation completed in ${(endTime - startTime).toFixed(2)}ms`);\n    }\n    /**\n     * Generate a single sequence (non-streaming)\n     */\n    async generateSingle(prompt, options) {\n        const startTime = performance.now();\n        if (!this.tokenizer) {\n            throw new Error('Tokenizer not set. Call setTokenizer() first.');\n        }\n        const { maxNewTokens = 50, maxLength = 512, temperature = 1.0, topK = 0, topP = 1.0, repetitionPenalty = 1.0, stopSequences = [], doSample = true, returnFullText = false, } = options;\n        // Encode prompt\n        const encoded = this.tokenizer.encode(prompt, {\n            addSpecialTokens: false,\n            padding: 'do_not_pad',\n            truncation: false,\n        });\n        let inputIds = [...encoded.inputIds];\n        const generatedIds = [];\n        // Generation loop\n        for (let i = 0; i < maxNewTokens; i++) {\n            // Check max length\n            if (inputIds.length >= maxLength)\n                break;\n            // Run model forward pass\n            const nextTokenId = await this.generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample);\n            // Check for EOS\n            if (nextTokenId === this.eosTokenId)\n                break;\n            // Add to sequence\n            generatedIds.push(nextTokenId);\n            inputIds.push(nextTokenId);\n            // Call token callback\n            if (options.onToken) {\n                const token = this.tokenizer.decode([nextTokenId], true);\n                options.onToken(token, nextTokenId);\n            }\n            // Check stop sequences\n            const currentText = this.tokenizer.decode(generatedIds, true);\n            let shouldStop = false;\n            for (const stopSeq of stopSequences) {\n                if (currentText.endsWith(stopSeq)) {\n                    shouldStop = true;\n                    break;\n                }\n            }\n            if (shouldStop)\n                break;\n        }\n        // Decode generated text\n        const generatedText = this.tokenizer.decode(generatedIds, true);\n        const endTime = performance.now();\n        return {\n            generatedText,\n            fullText: returnFullText ? prompt + generatedText : undefined,\n            tokenIds: generatedIds,\n            numTokens: generatedIds.length,\n            processingTime: endTime - startTime,\n        };\n    }\n    /**\n     * Generate next token using the model\n     */\n    async generateNextToken(inputIds, temperature, topK, topP, repetitionPenalty, doSample) {\n        if (!this.model) {\n            throw new Error('Model not loaded');\n        }\n        const seqLen = inputIds.length;\n        // Prepare named inputs\n        const inputs = new Map();\n        // input_ids: [1, seq_len]\n        inputs.set('input_ids', new EdgeFlowTensor(BigInt64Array.from(inputIds.map(id => BigInt(id))), [1, seqLen], 'int64'));\n        // attention_mask: [1, seq_len]\n        inputs.set('attention_mask', new EdgeFlowTensor(BigInt64Array.from(inputIds.map(() => BigInt(1))), [1, seqLen], 'int64'));\n        // position_ids: [1, seq_len] - sequential positions from 0 to seq_len-1\n        inputs.set('position_ids', new EdgeFlowTensor(BigInt64Array.from(Array.from({ length: seqLen }, (_, i) => BigInt(i))), [1, seqLen], 'int64'));\n        // TinyLlama has 22 layers with GQA (4 KV heads, head_dim=64)\n        // For first inference without cache, provide empty past_key_values\n        const numLayers = 22;\n        const numKVHeads = 4;\n        const headDim = 64;\n        for (let i = 0; i < numLayers; i++) {\n            // past_key_values.{i}.key: [batch, num_kv_heads, 0, head_dim]\n            inputs.set(`past_key_values.${i}.key`, new EdgeFlowTensor(new Float32Array(0), [1, numKVHeads, 0, headDim], 'float32'));\n            // past_key_values.{i}.value: [batch, num_kv_heads, 0, head_dim]\n            inputs.set(`past_key_values.${i}.value`, new EdgeFlowTensor(new Float32Array(0), [1, numKVHeads, 0, headDim], 'float32'));\n        }\n        // Run inference with named inputs\n        const outputs = await runInferenceNamed(this.model, inputs);\n        if (!outputs || outputs.length === 0) {\n            throw new Error('Model returned no outputs');\n        }\n        // Get logits for last token\n        const logits = outputs[0];\n        const logitsData = logits.toFloat32Array();\n        const vocabSize = logits.shape[logits.shape.length - 1] ?? 50257;\n        // Get logits for the last position\n        const lastPositionLogits = new Float32Array(vocabSize);\n        const offset = (inputIds.length - 1) * vocabSize;\n        for (let i = 0; i < vocabSize; i++) {\n            lastPositionLogits[i] = logitsData[offset + i] ?? 0;\n        }\n        // Apply repetition penalty\n        if (repetitionPenalty !== 1.0) {\n            for (const prevId of inputIds) {\n                if (prevId < vocabSize) {\n                    const score = lastPositionLogits[prevId] ?? 0;\n                    lastPositionLogits[prevId] = score > 0\n                        ? score / repetitionPenalty\n                        : score * repetitionPenalty;\n                }\n            }\n        }\n        // Apply temperature\n        if (temperature !== 1.0) {\n            for (let i = 0; i < vocabSize; i++) {\n                lastPositionLogits[i] = (lastPositionLogits[i] ?? 0) / temperature;\n            }\n        }\n        // Convert to probabilities\n        const logitsTensor = new EdgeFlowTensor(lastPositionLogits, [vocabSize], 'float32');\n        const probs = softmax(logitsTensor).toFloat32Array();\n        // Sample or greedy\n        if (doSample) {\n            return this.sample(probs, topK, topP);\n        }\n        else {\n            return this.greedy(probs);\n        }\n    }\n    /**\n     * Greedy decoding (argmax)\n     */\n    greedy(probs) {\n        let maxIdx = 0;\n        let maxProb = probs[0] ?? 0;\n        for (let i = 1; i < probs.length; i++) {\n            if ((probs[i] ?? 0) > maxProb) {\n                maxProb = probs[i] ?? 0;\n                maxIdx = i;\n            }\n        }\n        return maxIdx;\n    }\n    /**\n     * Sample from probability distribution with top-k/top-p filtering\n     */\n    sample(probs, topK, topP) {\n        // Create sorted indices\n        const indices = Array.from({ length: probs.length }, (_, i) => i);\n        indices.sort((a, b) => (probs[b] ?? 0) - (probs[a] ?? 0));\n        // Apply top-k filtering\n        let candidateIndices = indices;\n        if (topK > 0 && topK < probs.length) {\n            candidateIndices = indices.slice(0, topK);\n        }\n        // Apply top-p (nucleus) filtering\n        if (topP < 1.0) {\n            let cumulativeProb = 0;\n            const filtered = [];\n            for (const idx of candidateIndices) {\n                filtered.push(idx);\n                cumulativeProb += probs[idx] ?? 0;\n                if (cumulativeProb >= topP)\n                    break;\n            }\n            candidateIndices = filtered;\n        }\n        // Renormalize probabilities\n        let totalProb = 0;\n        for (const idx of candidateIndices) {\n            totalProb += probs[idx] ?? 0;\n        }\n        // Sample\n        const r = Math.random() * totalProb;\n        let cumulative = 0;\n        for (const idx of candidateIndices) {\n            cumulative += probs[idx] ?? 0;\n            if (cumulative >= r) {\n                return idx;\n            }\n        }\n        // Fallback\n        return candidateIndices[0] ?? 0;\n    }\n    // ==========================================================================\n    // Chat / Conversation Support\n    // ==========================================================================\n    conversationHistory = [];\n    chatTemplateType = 'chatml';\n    /**\n     * Set the chat template type\n     */\n    setChatTemplate(templateType) {\n        this.chatTemplateType = templateType;\n    }\n    /**\n     * Apply chat template to messages\n     */\n    applyChatTemplate(messages, options) {\n        const templateType = options?.templateType ?? this.chatTemplateType;\n        switch (templateType) {\n            case 'chatml':\n                return this.applyChatMLTemplate(messages);\n            case 'llama2':\n                return this.applyLlama2Template(messages);\n            case 'llama3':\n                return this.applyLlama3Template(messages);\n            case 'mistral':\n                return this.applyMistralTemplate(messages);\n            case 'phi3':\n                return this.applyPhi3Template(messages);\n            case 'alpaca':\n                return this.applyAlpacaTemplate(messages);\n            case 'vicuna':\n                return this.applyVicunaTemplate(messages);\n            case 'custom':\n                return this.applyCustomTemplate(messages, options?.customTemplate ?? {});\n            default:\n                return this.applyChatMLTemplate(messages);\n        }\n    }\n    /**\n     * ChatML template (used by many models including Qwen, Yi)\n     */\n    applyChatMLTemplate(messages) {\n        let prompt = '';\n        for (const msg of messages) {\n            prompt += `<|im_start|>${msg.role}\\n${msg.content}<|im_end|>\\n`;\n        }\n        prompt += '<|im_start|>assistant\\n';\n        return prompt;\n    }\n    /**\n     * Llama 2 template\n     */\n    applyLlama2Template(messages) {\n        let prompt = '';\n        let systemMsg = '';\n        for (const msg of messages) {\n            if (msg.role === 'system') {\n                systemMsg = msg.content;\n            }\n            else if (msg.role === 'user') {\n                if (systemMsg) {\n                    prompt += `<s>[INST] <<SYS>>\\n${systemMsg}\\n<</SYS>>\\n\\n${msg.content} [/INST]`;\n                    systemMsg = '';\n                }\n                else {\n                    prompt += `<s>[INST] ${msg.content} [/INST]`;\n                }\n            }\n            else if (msg.role === 'assistant') {\n                prompt += ` ${msg.content} </s>`;\n            }\n        }\n        return prompt;\n    }\n    /**\n     * Llama 3 template\n     */\n    applyLlama3Template(messages) {\n        let prompt = '<|begin_of_text|>';\n        for (const msg of messages) {\n            prompt += `<|start_header_id|>${msg.role}<|end_header_id|>\\n\\n${msg.content}<|eot_id|>`;\n        }\n        prompt += '<|start_header_id|>assistant<|end_header_id|>\\n\\n';\n        return prompt;\n    }\n    /**\n     * Mistral template\n     */\n    applyMistralTemplate(messages) {\n        let prompt = '<s>';\n        for (const msg of messages) {\n            if (msg.role === 'user') {\n                prompt += `[INST] ${msg.content} [/INST]`;\n            }\n            else if (msg.role === 'assistant') {\n                prompt += ` ${msg.content}</s>`;\n            }\n            else if (msg.role === 'system') {\n                prompt += `[INST] ${msg.content}\\n`;\n            }\n        }\n        return prompt;\n    }\n    /**\n     * Phi-3 template\n     */\n    applyPhi3Template(messages) {\n        let prompt = '';\n        for (const msg of messages) {\n            prompt += `<|${msg.role}|>\\n${msg.content}<|end|>\\n`;\n        }\n        prompt += '<|assistant|>\\n';\n        return prompt;\n    }\n    /**\n     * Alpaca template\n     */\n    applyAlpacaTemplate(messages) {\n        let prompt = '';\n        let instruction = '';\n        let input = '';\n        for (const msg of messages) {\n            if (msg.role === 'system') {\n                instruction = msg.content;\n            }\n            else if (msg.role === 'user') {\n                input = msg.content;\n            }\n        }\n        if (instruction) {\n            prompt = `### Instruction:\\n${instruction}\\n\\n`;\n        }\n        if (input) {\n            prompt += `### Input:\\n${input}\\n\\n`;\n        }\n        prompt += '### Response:\\n';\n        return prompt;\n    }\n    /**\n     * Vicuna template\n     */\n    applyVicunaTemplate(messages) {\n        let prompt = '';\n        for (const msg of messages) {\n            if (msg.role === 'system') {\n                prompt += `${msg.content}\\n\\n`;\n            }\n            else if (msg.role === 'user') {\n                prompt += `USER: ${msg.content}\\n`;\n            }\n            else if (msg.role === 'assistant') {\n                prompt += `ASSISTANT: ${msg.content}\\n`;\n            }\n        }\n        prompt += 'ASSISTANT:';\n        return prompt;\n    }\n    /**\n     * Custom template\n     */\n    applyCustomTemplate(messages, template) {\n        const { systemPrefix = '', systemSuffix = '\\n', userPrefix = 'User: ', userSuffix = '\\n', assistantPrefix = 'Assistant: ', assistantSuffix = '\\n', separator = '', } = template;\n        let prompt = '';\n        for (let i = 0; i < messages.length; i++) {\n            const msg = messages[i];\n            if (i > 0)\n                prompt += separator;\n            switch (msg.role) {\n                case 'system':\n                    prompt += `${systemPrefix}${msg.content}${systemSuffix}`;\n                    break;\n                case 'user':\n                    prompt += `${userPrefix}${msg.content}${userSuffix}`;\n                    break;\n                case 'assistant':\n                    prompt += `${assistantPrefix}${msg.content}${assistantSuffix}`;\n                    break;\n            }\n        }\n        prompt += assistantPrefix;\n        return prompt;\n    }\n    /**\n     * Chat with the model\n     *\n     * @example\n     * ```typescript\n     * const generator = await pipeline('text-generation', 'model');\n     *\n     * // Single turn\n     * const response = await generator.chat('Hello, how are you?');\n     *\n     * // Multi-turn with history\n     * const response1 = await generator.chat('What is AI?');\n     * const response2 = await generator.chat('Can you give an example?');\n     *\n     * // With system prompt\n     * const response = await generator.chat('Hello', {\n     *   systemPrompt: 'You are a helpful assistant.',\n     * });\n     * ```\n     */\n    async chat(userMessage, options) {\n        // Add system message if provided and not already present\n        if (options?.systemPrompt &&\n            (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== 'system')) {\n            this.conversationHistory.unshift({\n                role: 'system',\n                content: options.systemPrompt,\n            });\n        }\n        // Add user message\n        this.conversationHistory.push({\n            role: 'user',\n            content: userMessage,\n        });\n        // Apply chat template\n        const prompt = this.applyChatTemplate(this.conversationHistory, options);\n        // Generate response\n        const result = await this.run(prompt, {\n            ...options,\n            stopSequences: [\n                ...(options?.stopSequences ?? []),\n                '<|im_end|>',\n                '<|end|>',\n                '<|eot_id|>',\n                '</s>',\n                '\\n\\nUser:',\n                '\\n\\nHuman:',\n            ],\n        });\n        // Add assistant response to history\n        const response = Array.isArray(result) ? result[0] : result;\n        this.conversationHistory.push({\n            role: 'assistant',\n            content: response.generatedText.trim(),\n        });\n        return response;\n    }\n    /**\n     * Stream chat response\n     */\n    async *chatStream(userMessage, options) {\n        // Add system message if provided\n        if (options?.systemPrompt &&\n            (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== 'system')) {\n            this.conversationHistory.unshift({\n                role: 'system',\n                content: options.systemPrompt,\n            });\n        }\n        // Add user message\n        this.conversationHistory.push({\n            role: 'user',\n            content: userMessage,\n        });\n        // Apply chat template\n        const prompt = this.applyChatTemplate(this.conversationHistory, options);\n        // Stream response\n        let fullResponse = '';\n        for await (const event of this.stream(prompt, {\n            ...options,\n            stopSequences: [\n                ...(options?.stopSequences ?? []),\n                '<|im_end|>',\n                '<|end|>',\n                '<|eot_id|>',\n                '</s>',\n            ],\n        })) {\n            fullResponse = event.generatedText;\n            yield event;\n        }\n        // Add assistant response to history\n        this.conversationHistory.push({\n            role: 'assistant',\n            content: fullResponse.trim(),\n        });\n    }\n    /**\n     * Get conversation history\n     */\n    getConversationHistory() {\n        return [...this.conversationHistory];\n    }\n    /**\n     * Set conversation history\n     */\n    setConversationHistory(messages) {\n        this.conversationHistory = [...messages];\n    }\n    /**\n     * Clear conversation history\n     */\n    clearConversation() {\n        this.conversationHistory = [];\n    }\n    /**\n     * Remove last exchange (user message + assistant response)\n     */\n    undoLastExchange() {\n        // Remove assistant message\n        if (this.conversationHistory.length > 0 &&\n            this.conversationHistory[this.conversationHistory.length - 1]?.role === 'assistant') {\n            this.conversationHistory.pop();\n        }\n        // Remove user message\n        if (this.conversationHistory.length > 0 &&\n            this.conversationHistory[this.conversationHistory.length - 1]?.role === 'user') {\n            this.conversationHistory.pop();\n        }\n    }\n}\n// ============================================================================\n// Factory Functions\n// ============================================================================\n/**\n * Create text generation pipeline\n */\nexport function createTextGenerationPipeline(config) {\n    return new TextGenerationPipeline(config);\n}\n//# sourceMappingURL=text-generation.js.map"
  },
  {
    "path": "dist/pipelines/zero-shot-classification.d.ts",
    "content": "/**\n * edgeFlow.js - Zero-shot Classification Pipeline\n *\n * Classify text into any set of labels without fine-tuning,\n * using a real NLI (Natural Language Inference) model.\n */\nimport { BasePipeline, PipelineResult } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions } from '../core/types.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nexport interface ZeroShotClassificationOptions extends PipelineOptions {\n    multiLabel?: boolean;\n    hypothesisTemplate?: string;\n}\nexport interface ZeroShotClassificationResult extends PipelineResult {\n    sequence: string;\n    labels: string[];\n    scores: number[];\n}\nexport interface ZeroShotInput {\n    text: string | string[];\n    candidateLabels: string[];\n}\nexport declare class ZeroShotClassificationPipeline extends BasePipeline<ZeroShotInput, ZeroShotClassificationResult | ZeroShotClassificationResult[]> {\n    private tokenizer;\n    private onnxModel;\n    private hypothesisTemplate;\n    private modelUrl;\n    private tokenizerUrl;\n    constructor(config?: PipelineConfig);\n    initialize(): Promise<void>;\n    setTokenizer(tokenizer: Tokenizer): void;\n    classify(text: string | string[], candidateLabels: string[], options?: ZeroShotClassificationOptions): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]>;\n    run(input: ZeroShotInput, options?: PipelineOptions): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]>;\n    private classifySingle;\n    /**\n     * Score a single hypothesis using the real NLI ONNX model.\n     * Returns the entailment logit.\n     */\n    private scoreHypothesis;\n    protected preprocess(input: ZeroShotInput): Promise<EdgeFlowTensor[]>;\n    protected postprocess(_outputs: EdgeFlowTensor[], _options?: PipelineOptions): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]>;\n}\nexport declare function createZeroShotClassificationPipeline(config?: PipelineConfig): ZeroShotClassificationPipeline;\n//# sourceMappingURL=zero-shot-classification.d.ts.map"
  },
  {
    "path": "dist/pipelines/zero-shot-classification.js",
    "content": "/**\n * edgeFlow.js - Zero-shot Classification Pipeline\n *\n * Classify text into any set of labels without fine-tuning,\n * using a real NLI (Natural Language Inference) model.\n */\nimport { BasePipeline, registerPipeline } from './base.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\n// ============================================================================\n// Default Model (DistilBART fine-tuned on MNLI)\n// ============================================================================\nconst DEFAULT_MODELS = {\n    model: 'https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/onnx/model_quantized.onnx',\n    tokenizer: 'https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/tokenizer.json',\n};\n// NLI output indices: [contradiction, neutral, entailment]\nconst ENTAILMENT_IDX = 2;\n// ============================================================================\n// Zero-shot Classification Pipeline\n// ============================================================================\nexport class ZeroShotClassificationPipeline extends BasePipeline {\n    tokenizer = null;\n    onnxModel = null;\n    hypothesisTemplate = 'This text is about {label}.';\n    modelUrl;\n    tokenizerUrl;\n    constructor(config) {\n        super(config ?? {\n            task: 'zero-shot-classification',\n            model: 'default',\n        });\n        this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n        this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n    }\n    async initialize() {\n        await super.initialize();\n        if (!this.tokenizer) {\n            this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n        }\n        if (!this.onnxModel) {\n            const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n            this.onnxModel = await loadModelFromBuffer(modelData);\n        }\n    }\n    setTokenizer(tokenizer) {\n        this.tokenizer = tokenizer;\n    }\n    async classify(text, candidateLabels, options) {\n        return this.run({ text, candidateLabels }, options);\n    }\n    async run(input, options) {\n        await this.initialize();\n        const { text, candidateLabels } = input;\n        const opts = options ?? {};\n        const texts = Array.isArray(text) ? text : [text];\n        const template = opts.hypothesisTemplate ?? this.hypothesisTemplate;\n        const multiLabel = opts.multiLabel ?? false;\n        const results = await Promise.all(texts.map(t => this.classifySingle(t, candidateLabels, template, multiLabel)));\n        return Array.isArray(text) ? results : results[0];\n    }\n    async classifySingle(text, candidateLabels, template, multiLabel) {\n        const startTime = performance.now();\n        const hypotheses = candidateLabels.map(label => template.replace('{label}', label));\n        const scores = [];\n        for (const hypothesis of hypotheses) {\n            const score = await this.scoreHypothesis(text, hypothesis);\n            scores.push(score);\n        }\n        let normalizedScores;\n        if (multiLabel) {\n            normalizedScores = scores.map(s => 1 / (1 + Math.exp(-s)));\n        }\n        else {\n            const tensor = new EdgeFlowTensor(new Float32Array(scores), [scores.length], 'float32');\n            normalizedScores = Array.from(softmax(tensor).toFloat32Array());\n        }\n        const indexed = candidateLabels.map((label, i) => ({\n            label,\n            score: normalizedScores[i] ?? 0,\n        }));\n        indexed.sort((a, b) => b.score - a.score);\n        return {\n            sequence: text,\n            labels: indexed.map(i => i.label),\n            scores: indexed.map(i => i.score),\n            processingTime: performance.now() - startTime,\n        };\n    }\n    /**\n     * Score a single hypothesis using the real NLI ONNX model.\n     * Returns the entailment logit.\n     */\n    async scoreHypothesis(premise, hypothesis) {\n        const encoded = this.tokenizer.encode(premise, {\n            textPair: hypothesis,\n            addSpecialTokens: true,\n            maxLength: 512,\n            truncation: true,\n            returnAttentionMask: true,\n        });\n        const inputIds = new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64');\n        const attentionMask = new EdgeFlowTensor(BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))), [1, encoded.attentionMask.length], 'int64');\n        const namedInputs = new Map();\n        namedInputs.set('input_ids', inputIds);\n        namedInputs.set('attention_mask', attentionMask);\n        const outputs = await runInferenceNamed(this.onnxModel, namedInputs);\n        const logits = outputs[0].toFloat32Array();\n        // Return entailment logit (index 2 in [contradiction, neutral, entailment])\n        return logits[ENTAILMENT_IDX] ?? 0;\n    }\n    async preprocess(input) {\n        const { text, candidateLabels } = input;\n        const firstText = Array.isArray(text) ? text[0] ?? '' : text;\n        const firstLabel = candidateLabels[0] ?? '';\n        const encoded = this.tokenizer.encode(firstText, {\n            textPair: this.hypothesisTemplate.replace('{label}', firstLabel),\n            addSpecialTokens: true,\n            maxLength: 512,\n        });\n        return [new EdgeFlowTensor(BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))), [1, encoded.inputIds.length], 'int64')];\n    }\n    async postprocess(_outputs, _options) {\n        return {\n            sequence: '',\n            labels: [],\n            scores: [],\n        };\n    }\n}\n// ============================================================================\n// Factory\n// ============================================================================\nexport function createZeroShotClassificationPipeline(config) {\n    return new ZeroShotClassificationPipeline(config);\n}\nregisterPipeline('zero-shot-classification', (config) => new ZeroShotClassificationPipeline(config));\n//# sourceMappingURL=zero-shot-classification.js.map"
  },
  {
    "path": "dist/tools/benchmark.d.ts",
    "content": "/**\n * edgeFlow.js - Benchmark Utilities\n *\n * Performance testing and comparison tools.\n */\nexport interface BenchmarkOptions {\n    /** Number of warmup runs (default: 3) */\n    warmupRuns?: number;\n    /** Number of measured runs (default: 10) */\n    runs?: number;\n    /** Whether to log progress (default: true) */\n    verbose?: boolean;\n    /** Timeout per run in ms (default: 30000) */\n    timeout?: number;\n    /** Name for this benchmark */\n    name?: string;\n}\nexport interface BenchmarkResult {\n    name: string;\n    /** Average time in ms */\n    avgTime: number;\n    /** Median time in ms */\n    medianTime: number;\n    /** Minimum time in ms */\n    minTime: number;\n    /** Maximum time in ms */\n    maxTime: number;\n    /** Standard deviation in ms */\n    stdDev: number;\n    /** 95th percentile in ms */\n    p95: number;\n    /** 99th percentile in ms */\n    p99: number;\n    /** Throughput (ops/sec) */\n    throughput: number;\n    /** All individual run times */\n    times: number[];\n    /** Number of runs */\n    totalRuns: number;\n    /** Number of failed runs */\n    failedRuns: number;\n}\nexport interface CompareBenchmarkResult {\n    baseline: BenchmarkResult;\n    comparison: BenchmarkResult;\n    speedup: number;\n    percentFaster: number;\n    winner: 'baseline' | 'comparison' | 'tie';\n}\n/**\n * Run a benchmark on an async function\n */\nexport declare function benchmark(fn: () => Promise<unknown> | unknown, options?: BenchmarkOptions): Promise<BenchmarkResult>;\n/**\n * Compare two benchmarks\n */\nexport declare function compareBenchmarks(baseline: () => Promise<unknown> | unknown, comparison: () => Promise<unknown> | unknown, options?: BenchmarkOptions): Promise<CompareBenchmarkResult>;\n/**\n * Run multiple benchmarks in a suite\n */\nexport declare function benchmarkSuite(suite: Record<string, () => Promise<unknown> | unknown>, options?: BenchmarkOptions): Promise<Record<string, BenchmarkResult>>;\n/**\n * Format benchmark result as a table string\n */\nexport declare function formatBenchmarkResult(result: BenchmarkResult): string;\n/**\n * Format comparison result\n */\nexport declare function formatComparisonResult(result: CompareBenchmarkResult): string;\nexport interface MemoryBenchmarkResult {\n    name: string;\n    peakMemory: number;\n    avgMemory: number;\n    memoryDelta: number;\n}\n/**\n * Benchmark memory usage\n */\nexport declare function benchmarkMemory(fn: () => Promise<unknown> | unknown, options?: {\n    name?: string;\n    runs?: number;\n}): Promise<MemoryBenchmarkResult>;\ndeclare const _default: {\n    benchmark: typeof benchmark;\n    compareBenchmarks: typeof compareBenchmarks;\n    benchmarkSuite: typeof benchmarkSuite;\n    benchmarkMemory: typeof benchmarkMemory;\n    formatBenchmarkResult: typeof formatBenchmarkResult;\n    formatComparisonResult: typeof formatComparisonResult;\n};\nexport default _default;\n//# sourceMappingURL=benchmark.d.ts.map"
  },
  {
    "path": "dist/tools/benchmark.js",
    "content": "/**\n * edgeFlow.js - Benchmark Utilities\n *\n * Performance testing and comparison tools.\n */\n// ============================================================================\n// Benchmark Functions\n// ============================================================================\n/**\n * Run a benchmark on an async function\n */\nexport async function benchmark(fn, options = {}) {\n    const { warmupRuns = 3, runs = 10, verbose = false, timeout = 30000, name = 'benchmark', } = options;\n    const times = [];\n    let failedRuns = 0;\n    // Warmup\n    if (verbose)\n        console.log(`[${name}] Running ${warmupRuns} warmup iterations...`);\n    for (let i = 0; i < warmupRuns; i++) {\n        try {\n            await Promise.race([\n                Promise.resolve(fn()),\n                new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout)),\n            ]);\n        }\n        catch {\n            // Warmup failures are ignored\n        }\n    }\n    // Measured runs\n    if (verbose)\n        console.log(`[${name}] Running ${runs} measured iterations...`);\n    for (let i = 0; i < runs; i++) {\n        try {\n            const start = performance.now();\n            await Promise.race([\n                Promise.resolve(fn()),\n                new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout)),\n            ]);\n            const end = performance.now();\n            times.push(end - start);\n            if (verbose)\n                console.log(`  Run ${i + 1}: ${(end - start).toFixed(2)}ms`);\n        }\n        catch (error) {\n            failedRuns++;\n            if (verbose)\n                console.log(`  Run ${i + 1}: FAILED - ${error}`);\n        }\n    }\n    if (times.length === 0) {\n        throw new Error(`All ${runs} runs failed`);\n    }\n    // Calculate statistics\n    const sorted = [...times].sort((a, b) => a - b);\n    const sum = times.reduce((a, b) => a + b, 0);\n    const avg = sum / times.length;\n    const variance = times.reduce((sum, t) => sum + Math.pow(t - avg, 2), 0) / times.length;\n    const stdDev = Math.sqrt(variance);\n    const result = {\n        name,\n        avgTime: avg,\n        medianTime: sorted[Math.floor(sorted.length / 2)] ?? 0,\n        minTime: sorted[0] ?? 0,\n        maxTime: sorted[sorted.length - 1] ?? 0,\n        stdDev,\n        p95: sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1] ?? 0,\n        p99: sorted[Math.floor(sorted.length * 0.99)] ?? sorted[sorted.length - 1] ?? 0,\n        throughput: 1000 / avg,\n        times,\n        totalRuns: runs,\n        failedRuns,\n    };\n    if (verbose) {\n        console.log(`\\n[${name}] Results:`);\n        console.log(`  Avg: ${result.avgTime.toFixed(2)}ms`);\n        console.log(`  Median: ${result.medianTime.toFixed(2)}ms`);\n        console.log(`  Min: ${result.minTime.toFixed(2)}ms`);\n        console.log(`  Max: ${result.maxTime.toFixed(2)}ms`);\n        console.log(`  Std Dev: ${result.stdDev.toFixed(2)}ms`);\n        console.log(`  P95: ${result.p95.toFixed(2)}ms`);\n        console.log(`  Throughput: ${result.throughput.toFixed(2)} ops/sec`);\n    }\n    return result;\n}\n/**\n * Compare two benchmarks\n */\nexport async function compareBenchmarks(baseline, comparison, options = {}) {\n    const baselineResult = await benchmark(baseline, {\n        ...options,\n        name: options.name ? `${options.name} (baseline)` : 'baseline'\n    });\n    const comparisonResult = await benchmark(comparison, {\n        ...options,\n        name: options.name ? `${options.name} (comparison)` : 'comparison'\n    });\n    const speedup = baselineResult.avgTime / comparisonResult.avgTime;\n    const percentFaster = ((baselineResult.avgTime - comparisonResult.avgTime) / baselineResult.avgTime) * 100;\n    let winner;\n    if (Math.abs(percentFaster) < 5) {\n        winner = 'tie';\n    }\n    else if (percentFaster > 0) {\n        winner = 'comparison';\n    }\n    else {\n        winner = 'baseline';\n    }\n    return {\n        baseline: baselineResult,\n        comparison: comparisonResult,\n        speedup,\n        percentFaster,\n        winner,\n    };\n}\n/**\n * Run multiple benchmarks in a suite\n */\nexport async function benchmarkSuite(suite, options = {}) {\n    const results = {};\n    for (const [name, fn] of Object.entries(suite)) {\n        console.log(`\\n=== ${name} ===`);\n        results[name] = await benchmark(fn, { ...options, name, verbose: true });\n    }\n    return results;\n}\n/**\n * Format benchmark result as a table string\n */\nexport function formatBenchmarkResult(result) {\n    return `\n┌─────────────────────────────────────────┐\n│ ${result.name.padEnd(39)} │\n├─────────────────────────────────────────┤\n│ Avg Time:    ${result.avgTime.toFixed(2).padStart(10)}ms             │\n│ Median:      ${result.medianTime.toFixed(2).padStart(10)}ms             │\n│ Min Time:    ${result.minTime.toFixed(2).padStart(10)}ms             │\n│ Max Time:    ${result.maxTime.toFixed(2).padStart(10)}ms             │\n│ Std Dev:     ${result.stdDev.toFixed(2).padStart(10)}ms             │\n│ P95:         ${result.p95.toFixed(2).padStart(10)}ms             │\n│ P99:         ${result.p99.toFixed(2).padStart(10)}ms             │\n│ Throughput:  ${result.throughput.toFixed(2).padStart(10)} ops/sec     │\n│ Runs:        ${result.totalRuns.toString().padStart(10)} (${result.failedRuns} failed)  │\n└─────────────────────────────────────────┘\n  `.trim();\n}\n/**\n * Format comparison result\n */\nexport function formatComparisonResult(result) {\n    const arrow = result.percentFaster > 0 ? '↑' : result.percentFaster < 0 ? '↓' : '=';\n    const winnerText = result.winner === 'comparison'\n        ? 'Comparison is faster!'\n        : result.winner === 'baseline'\n            ? 'Baseline is faster!'\n            : 'Results are similar';\n    return `\n┌─────────────────────────────────────────────────────┐\n│                  BENCHMARK COMPARISON               │\n├─────────────────────────────────────────────────────┤\n│ Baseline:    ${result.baseline.avgTime.toFixed(2).padStart(10)}ms                       │\n│ Comparison:  ${result.comparison.avgTime.toFixed(2).padStart(10)}ms                       │\n├─────────────────────────────────────────────────────┤\n│ Speedup:     ${result.speedup.toFixed(2).padStart(10)}x                        │\n│ Difference:  ${arrow} ${Math.abs(result.percentFaster).toFixed(1).padStart(8)}%                      │\n├─────────────────────────────────────────────────────┤\n│ Winner: ${winnerText.padEnd(42)} │\n└─────────────────────────────────────────────────────┘\n  `.trim();\n}\n/**\n * Benchmark memory usage\n */\nexport async function benchmarkMemory(fn, options = {}) {\n    const { name = 'memory-benchmark', runs = 5 } = options;\n    // Note: Memory APIs are limited in browsers\n    // This is a simplified version that works when performance.memory is available\n    const getMemory = () => {\n        if (typeof performance !== 'undefined' && 'memory' in performance) {\n            return performance.memory.usedJSHeapSize;\n        }\n        return 0;\n    };\n    const memoryReadings = [];\n    const initialMemory = getMemory();\n    for (let i = 0; i < runs; i++) {\n        await fn();\n        memoryReadings.push(getMemory());\n    }\n    const peakMemory = Math.max(...memoryReadings);\n    const avgMemory = memoryReadings.reduce((a, b) => a + b, 0) / memoryReadings.length;\n    const memoryDelta = avgMemory - initialMemory;\n    return {\n        name,\n        peakMemory,\n        avgMemory,\n        memoryDelta,\n    };\n}\n// ============================================================================\n// Export\n// ============================================================================\nexport default {\n    benchmark,\n    compareBenchmarks,\n    benchmarkSuite,\n    benchmarkMemory,\n    formatBenchmarkResult,\n    formatComparisonResult,\n};\n//# sourceMappingURL=benchmark.js.map"
  },
  {
    "path": "dist/tools/debugger.d.ts",
    "content": "/**\n * edgeFlow.js - Visual Debugging Tools\n *\n * In-browser debugging and visualization utilities for ML models.\n */\nimport { EdgeFlowTensor } from '../core/index.js';\n/**\n * Debugger configuration\n */\nexport interface DebuggerConfig {\n    /** Enable logging */\n    logging?: boolean;\n    /** Log level */\n    logLevel?: 'debug' | 'info' | 'warn' | 'error';\n    /** Enable tensor inspection */\n    inspectTensors?: boolean;\n    /** Maximum values to display per tensor */\n    maxDisplayValues?: number;\n    /** Enable performance tracking */\n    trackPerformance?: boolean;\n    /** Custom logger function */\n    logger?: (level: string, message: string, data?: unknown) => void;\n}\n/**\n * Tensor inspection result\n */\nexport interface TensorInspection {\n    name: string;\n    shape: number[];\n    dtype: string;\n    size: number;\n    memoryBytes: number;\n    stats: TensorStats;\n    sample: number[];\n    histogram?: HistogramData;\n}\n/**\n * Tensor statistics\n */\nexport interface TensorStats {\n    min: number;\n    max: number;\n    mean: number;\n    std: number;\n    zeros: number;\n    nans: number;\n    infinities: number;\n    sparsity: number;\n}\n/**\n * Histogram data\n */\nexport interface HistogramData {\n    bins: number[];\n    counts: number[];\n    binEdges: number[];\n}\n/**\n * Inference trace\n */\nexport interface InferenceTrace {\n    id: string;\n    modelId: string;\n    timestamp: number;\n    inputs: TensorInspection[];\n    outputs: TensorInspection[];\n    duration: number;\n    memoryUsed: number;\n    operations: OperationTrace[];\n}\n/**\n * Operation trace\n */\nexport interface OperationTrace {\n    name: string;\n    type: string;\n    duration: number;\n    inputShapes: number[][];\n    outputShapes: number[][];\n    attributes?: Record<string, unknown>;\n}\n/**\n * Debug event\n */\nexport interface DebugEvent {\n    type: 'tensor' | 'inference' | 'error' | 'warning' | 'info' | 'performance';\n    timestamp: number;\n    data: unknown;\n    message: string;\n}\n/**\n * Performance metrics\n */\nexport interface PerformanceMetrics {\n    inferenceCount: number;\n    totalInferenceTime: number;\n    averageInferenceTime: number;\n    minInferenceTime: number;\n    maxInferenceTime: number;\n    peakMemoryUsage: number;\n    currentMemoryUsage: number;\n    tensorAllocations: number;\n    tensorDeallocations: number;\n}\n/**\n * Inspect a tensor\n */\nexport declare function inspectTensor(tensor: EdgeFlowTensor, name?: string, options?: {\n    histogram?: boolean;\n    maxSample?: number;\n}): TensorInspection;\n/**\n * Format tensor inspection for display\n */\nexport declare function formatTensorInspection(inspection: TensorInspection): string;\n/**\n * Visual debugger for edgeFlow.js\n */\nexport declare class EdgeFlowDebugger {\n    private config;\n    private events;\n    private traces;\n    private performanceMetrics;\n    private listeners;\n    private isEnabled;\n    constructor(config?: DebuggerConfig);\n    /**\n     * Default logger\n     */\n    private defaultLogger;\n    /**\n     * Log a message\n     */\n    log(level: string, message: string, data?: unknown): void;\n    /**\n     * Add debug event\n     */\n    private addEvent;\n    /**\n     * Enable debugger\n     */\n    enable(): void;\n    /**\n     * Disable debugger\n     */\n    disable(): void;\n    /**\n     * Subscribe to events\n     */\n    on(type: string, callback: (event: DebugEvent) => void): () => void;\n    /**\n     * Inspect and log a tensor\n     */\n    inspectTensor(tensor: EdgeFlowTensor, name?: string): TensorInspection;\n    /**\n     * Start tracing an inference\n     */\n    startTrace(modelId: string): string;\n    /**\n     * Add input to trace\n     */\n    traceInput(traceId: string, tensor: EdgeFlowTensor, name: string): void;\n    /**\n     * Add output to trace\n     */\n    traceOutput(traceId: string, tensor: EdgeFlowTensor, name: string): void;\n    /**\n     * Add operation to trace\n     */\n    traceOperation(traceId: string, operation: OperationTrace): void;\n    /**\n     * End trace\n     */\n    endTrace(traceId: string): InferenceTrace | undefined;\n    /**\n     * Record tensor allocation\n     */\n    recordAllocation(tensor: EdgeFlowTensor): void;\n    /**\n     * Record tensor deallocation\n     */\n    recordDeallocation(tensor: EdgeFlowTensor): void;\n    /**\n     * Get performance metrics\n     */\n    getPerformanceMetrics(): PerformanceMetrics;\n    /**\n     * Get all events\n     */\n    getEvents(): DebugEvent[];\n    /**\n     * Get all traces\n     */\n    getTraces(): InferenceTrace[];\n    /**\n     * Get trace by ID\n     */\n    getTrace(traceId: string): InferenceTrace | undefined;\n    /**\n     * Clear all data\n     */\n    clear(): void;\n    /**\n     * Export debug data\n     */\n    export(): {\n        events: DebugEvent[];\n        traces: InferenceTrace[];\n        metrics: PerformanceMetrics;\n        timestamp: number;\n    };\n    /**\n     * Generate summary report\n     */\n    generateReport(): string;\n}\n/**\n * Get or create the global debugger instance\n */\nexport declare function getDebugger(config?: DebuggerConfig): EdgeFlowDebugger;\n/**\n * Enable debugging\n */\nexport declare function enableDebugging(config?: DebuggerConfig): EdgeFlowDebugger;\n/**\n * Disable debugging\n */\nexport declare function disableDebugging(): void;\n/**\n * Create ASCII histogram\n */\nexport declare function createAsciiHistogram(histogram: HistogramData, width?: number, height?: number): string;\n/**\n * Create tensor heatmap (for 2D tensors)\n */\nexport declare function createTensorHeatmap(tensor: EdgeFlowTensor, width?: number): string;\n/**\n * Create model architecture visualization\n */\nexport declare function visualizeModelArchitecture(layers: Array<{\n    name: string;\n    type: string;\n    inputShape: number[];\n    outputShape: number[];\n}>): string;\ndeclare const _default: {\n    EdgeFlowDebugger: typeof EdgeFlowDebugger;\n    getDebugger: typeof getDebugger;\n    enableDebugging: typeof enableDebugging;\n    disableDebugging: typeof disableDebugging;\n    inspectTensor: typeof inspectTensor;\n    formatTensorInspection: typeof formatTensorInspection;\n    createAsciiHistogram: typeof createAsciiHistogram;\n    createTensorHeatmap: typeof createTensorHeatmap;\n    visualizeModelArchitecture: typeof visualizeModelArchitecture;\n};\nexport default _default;\n//# sourceMappingURL=debugger.d.ts.map"
  },
  {
    "path": "dist/tools/debugger.js",
    "content": "/**\n * edgeFlow.js - Visual Debugging Tools\n *\n * In-browser debugging and visualization utilities for ML models.\n */\n// ============================================================================\n// Tensor Inspection\n// ============================================================================\n/**\n * Calculate tensor statistics\n */\nfunction calculateTensorStats(data) {\n    const arr = data instanceof Float32Array ? data : new Float32Array(data);\n    let min = Infinity;\n    let max = -Infinity;\n    let sum = 0;\n    let zeros = 0;\n    let nans = 0;\n    let infinities = 0;\n    for (let i = 0; i < arr.length; i++) {\n        const val = arr[i] ?? 0;\n        if (isNaN(val)) {\n            nans++;\n            continue;\n        }\n        if (!isFinite(val)) {\n            infinities++;\n            continue;\n        }\n        min = Math.min(min, val);\n        max = Math.max(max, val);\n        sum += val;\n        if (val === 0)\n            zeros++;\n    }\n    const validCount = arr.length - nans - infinities;\n    const mean = validCount > 0 ? sum / validCount : 0;\n    // Calculate std\n    let varianceSum = 0;\n    for (let i = 0; i < arr.length; i++) {\n        const val = arr[i] ?? 0;\n        if (!isNaN(val) && isFinite(val)) {\n            varianceSum += Math.pow(val - mean, 2);\n        }\n    }\n    const std = validCount > 0 ? Math.sqrt(varianceSum / validCount) : 0;\n    return {\n        min: min === Infinity ? 0 : min,\n        max: max === -Infinity ? 0 : max,\n        mean,\n        std,\n        zeros,\n        nans,\n        infinities,\n        sparsity: zeros / arr.length,\n    };\n}\n/**\n * Create histogram from data\n */\nfunction createHistogram(data, bins = 50) {\n    const arr = data instanceof Float32Array ? data : new Float32Array(data);\n    // Find min/max (excluding NaN/Inf)\n    let min = Infinity;\n    let max = -Infinity;\n    for (let i = 0; i < arr.length; i++) {\n        const val = arr[i] ?? 0;\n        if (!isNaN(val) && isFinite(val)) {\n            min = Math.min(min, val);\n            max = Math.max(max, val);\n        }\n    }\n    if (min === Infinity || max === -Infinity || min === max) {\n        return { bins: [min || 0], counts: [arr.length], binEdges: [min || 0, max || 0] };\n    }\n    const binWidth = (max - min) / bins;\n    const counts = new Array(bins).fill(0);\n    const binEdges = new Array(bins + 1);\n    for (let i = 0; i <= bins; i++) {\n        binEdges[i] = min + i * binWidth;\n    }\n    for (let i = 0; i < arr.length; i++) {\n        const val = arr[i] ?? 0;\n        if (!isNaN(val) && isFinite(val)) {\n            const binIndex = Math.min(Math.floor((val - min) / binWidth), bins - 1);\n            counts[binIndex]++;\n        }\n    }\n    return {\n        bins: binEdges.slice(0, -1).map((e, i) => (e + binEdges[i + 1]) / 2),\n        counts,\n        binEdges,\n    };\n}\n/**\n * Inspect a tensor\n */\nexport function inspectTensor(tensor, name = 'tensor', options = {}) {\n    const { histogram = true, maxSample = 10 } = options;\n    const data = tensor.toFloat32Array();\n    const shape = tensor.shape;\n    const size = tensor.size;\n    // Get sample of values\n    const sampleIndices = [];\n    const step = Math.max(1, Math.floor(size / maxSample));\n    for (let i = 0; i < size && sampleIndices.length < maxSample; i += step) {\n        sampleIndices.push(i);\n    }\n    const sample = sampleIndices.map(i => data[i] ?? 0);\n    // Calculate memory (assuming float32)\n    const bytesPerElement = tensor.dtype === 'float32' ? 4\n        : tensor.dtype === 'int32' ? 4\n            : tensor.dtype === 'int64' ? 8\n                : 4;\n    const memoryBytes = size * bytesPerElement;\n    return {\n        name,\n        shape,\n        dtype: tensor.dtype,\n        size,\n        memoryBytes,\n        stats: calculateTensorStats(data),\n        sample,\n        histogram: histogram ? createHistogram(data) : undefined,\n    };\n}\n/**\n * Format tensor inspection for display\n */\nexport function formatTensorInspection(inspection) {\n    const { name, shape, dtype, size, memoryBytes, stats, sample } = inspection;\n    const lines = [\n        `┌─ Tensor: ${name} ─────────────────────────────`,\n        `│ Shape: [${shape.join(', ')}]`,\n        `│ Dtype: ${dtype}`,\n        `│ Size: ${size.toLocaleString()} elements`,\n        `│ Memory: ${formatBytes(memoryBytes)}`,\n        `├─ Statistics ─────────────────────────────────`,\n        `│ Min: ${stats.min.toFixed(6)}`,\n        `│ Max: ${stats.max.toFixed(6)}`,\n        `│ Mean: ${stats.mean.toFixed(6)}`,\n        `│ Std: ${stats.std.toFixed(6)}`,\n        `│ Sparsity: ${(stats.sparsity * 100).toFixed(2)}%`,\n    ];\n    if (stats.nans > 0) {\n        lines.push(`│ ⚠️ NaN values: ${stats.nans}`);\n    }\n    if (stats.infinities > 0) {\n        lines.push(`│ ⚠️ Infinity values: ${stats.infinities}`);\n    }\n    lines.push(`├─ Sample Values ──────────────────────────────`);\n    lines.push(`│ [${sample.map(v => v.toFixed(4)).join(', ')}]`);\n    lines.push(`└──────────────────────────────────────────────`);\n    return lines.join('\\n');\n}\n/**\n * Format bytes to human readable\n */\nfunction formatBytes(bytes) {\n    if (bytes < 1024)\n        return `${bytes} B`;\n    if (bytes < 1024 * 1024)\n        return `${(bytes / 1024).toFixed(2)} KB`;\n    if (bytes < 1024 * 1024 * 1024)\n        return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n    return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n// ============================================================================\n// Visual Debugger Class\n// ============================================================================\n/**\n * Visual debugger for edgeFlow.js\n */\nexport class EdgeFlowDebugger {\n    config;\n    events = [];\n    traces = [];\n    performanceMetrics;\n    listeners = new Map();\n    isEnabled = true;\n    constructor(config = {}) {\n        this.config = {\n            logging: config.logging ?? true,\n            logLevel: config.logLevel ?? 'info',\n            inspectTensors: config.inspectTensors ?? true,\n            maxDisplayValues: config.maxDisplayValues ?? 10,\n            trackPerformance: config.trackPerformance ?? true,\n            logger: config.logger ?? this.defaultLogger.bind(this),\n        };\n        this.performanceMetrics = {\n            inferenceCount: 0,\n            totalInferenceTime: 0,\n            averageInferenceTime: 0,\n            minInferenceTime: Infinity,\n            maxInferenceTime: 0,\n            peakMemoryUsage: 0,\n            currentMemoryUsage: 0,\n            tensorAllocations: 0,\n            tensorDeallocations: 0,\n        };\n    }\n    /**\n     * Default logger\n     */\n    defaultLogger(level, message, data) {\n        const timestamp = new Date().toISOString();\n        const prefix = `[edgeFlow.js ${timestamp}] [${level.toUpperCase()}]`;\n        switch (level) {\n            case 'debug':\n                console.debug(prefix, message, data ?? '');\n                break;\n            case 'info':\n                console.info(prefix, message, data ?? '');\n                break;\n            case 'warn':\n                console.warn(prefix, message, data ?? '');\n                break;\n            case 'error':\n                console.error(prefix, message, data ?? '');\n                break;\n            default:\n                console.log(prefix, message, data ?? '');\n        }\n    }\n    /**\n     * Log a message\n     */\n    log(level, message, data) {\n        if (!this.isEnabled || !this.config.logging)\n            return;\n        const levels = ['debug', 'info', 'warn', 'error'];\n        const configLevel = levels.indexOf(this.config.logLevel);\n        const msgLevel = levels.indexOf(level);\n        if (msgLevel >= configLevel) {\n            this.config.logger(level, message, data);\n        }\n    }\n    /**\n     * Add debug event\n     */\n    addEvent(event) {\n        this.events.push(event);\n        // Notify listeners\n        const listeners = this.listeners.get(event.type) ?? [];\n        for (const listener of listeners) {\n            listener(event);\n        }\n        // Keep only last 1000 events\n        if (this.events.length > 1000) {\n            this.events = this.events.slice(-1000);\n        }\n    }\n    /**\n     * Enable debugger\n     */\n    enable() {\n        this.isEnabled = true;\n        this.log('info', 'Debugger enabled');\n    }\n    /**\n     * Disable debugger\n     */\n    disable() {\n        this.isEnabled = false;\n    }\n    /**\n     * Subscribe to events\n     */\n    on(type, callback) {\n        const listeners = this.listeners.get(type) ?? [];\n        listeners.push(callback);\n        this.listeners.set(type, listeners);\n        return () => {\n            const idx = listeners.indexOf(callback);\n            if (idx !== -1)\n                listeners.splice(idx, 1);\n        };\n    }\n    /**\n     * Inspect and log a tensor\n     */\n    inspectTensor(tensor, name = 'tensor') {\n        const inspection = inspectTensor(tensor, name, {\n            histogram: true,\n            maxSample: this.config.maxDisplayValues,\n        });\n        if (this.config.inspectTensors) {\n            this.log('debug', `Tensor: ${name}`, inspection);\n            this.addEvent({\n                type: 'tensor',\n                timestamp: Date.now(),\n                message: `Inspected tensor: ${name}`,\n                data: inspection,\n            });\n            // Check for issues\n            if (inspection.stats.nans > 0) {\n                this.log('warn', `Tensor \"${name}\" contains ${inspection.stats.nans} NaN values`);\n            }\n            if (inspection.stats.infinities > 0) {\n                this.log('warn', `Tensor \"${name}\" contains ${inspection.stats.infinities} Infinity values`);\n            }\n        }\n        return inspection;\n    }\n    /**\n     * Start tracing an inference\n     */\n    startTrace(modelId) {\n        const id = `trace_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n        const trace = {\n            id,\n            modelId,\n            timestamp: Date.now(),\n            inputs: [],\n            outputs: [],\n            duration: 0,\n            memoryUsed: 0,\n            operations: [],\n        };\n        this.traces.push(trace);\n        this.log('debug', `Started trace: ${id} for model: ${modelId}`);\n        return id;\n    }\n    /**\n     * Add input to trace\n     */\n    traceInput(traceId, tensor, name) {\n        const trace = this.traces.find(t => t.id === traceId);\n        if (!trace)\n            return;\n        trace.inputs.push(inspectTensor(tensor, name));\n    }\n    /**\n     * Add output to trace\n     */\n    traceOutput(traceId, tensor, name) {\n        const trace = this.traces.find(t => t.id === traceId);\n        if (!trace)\n            return;\n        trace.outputs.push(inspectTensor(tensor, name));\n    }\n    /**\n     * Add operation to trace\n     */\n    traceOperation(traceId, operation) {\n        const trace = this.traces.find(t => t.id === traceId);\n        if (!trace)\n            return;\n        trace.operations.push(operation);\n    }\n    /**\n     * End trace\n     */\n    endTrace(traceId) {\n        const trace = this.traces.find(t => t.id === traceId);\n        if (!trace)\n            return;\n        trace.duration = Date.now() - trace.timestamp;\n        // Update performance metrics\n        this.performanceMetrics.inferenceCount++;\n        this.performanceMetrics.totalInferenceTime += trace.duration;\n        this.performanceMetrics.averageInferenceTime =\n            this.performanceMetrics.totalInferenceTime / this.performanceMetrics.inferenceCount;\n        this.performanceMetrics.minInferenceTime =\n            Math.min(this.performanceMetrics.minInferenceTime, trace.duration);\n        this.performanceMetrics.maxInferenceTime =\n            Math.max(this.performanceMetrics.maxInferenceTime, trace.duration);\n        this.log('info', `Trace completed: ${traceId}`, {\n            duration: `${trace.duration}ms`,\n            inputs: trace.inputs.length,\n            outputs: trace.outputs.length,\n            operations: trace.operations.length,\n        });\n        this.addEvent({\n            type: 'inference',\n            timestamp: Date.now(),\n            message: `Inference completed in ${trace.duration}ms`,\n            data: trace,\n        });\n        return trace;\n    }\n    /**\n     * Record tensor allocation\n     */\n    recordAllocation(tensor) {\n        if (!this.config.trackPerformance)\n            return;\n        this.performanceMetrics.tensorAllocations++;\n        const memory = tensor.size * 4; // Assume float32\n        this.performanceMetrics.currentMemoryUsage += memory;\n        this.performanceMetrics.peakMemoryUsage = Math.max(this.performanceMetrics.peakMemoryUsage, this.performanceMetrics.currentMemoryUsage);\n    }\n    /**\n     * Record tensor deallocation\n     */\n    recordDeallocation(tensor) {\n        if (!this.config.trackPerformance)\n            return;\n        this.performanceMetrics.tensorDeallocations++;\n        const memory = tensor.size * 4;\n        this.performanceMetrics.currentMemoryUsage -= memory;\n    }\n    /**\n     * Get performance metrics\n     */\n    getPerformanceMetrics() {\n        return { ...this.performanceMetrics };\n    }\n    /**\n     * Get all events\n     */\n    getEvents() {\n        return [...this.events];\n    }\n    /**\n     * Get all traces\n     */\n    getTraces() {\n        return [...this.traces];\n    }\n    /**\n     * Get trace by ID\n     */\n    getTrace(traceId) {\n        return this.traces.find(t => t.id === traceId);\n    }\n    /**\n     * Clear all data\n     */\n    clear() {\n        this.events = [];\n        this.traces = [];\n        this.performanceMetrics = {\n            inferenceCount: 0,\n            totalInferenceTime: 0,\n            averageInferenceTime: 0,\n            minInferenceTime: Infinity,\n            maxInferenceTime: 0,\n            peakMemoryUsage: 0,\n            currentMemoryUsage: 0,\n            tensorAllocations: 0,\n            tensorDeallocations: 0,\n        };\n    }\n    /**\n     * Export debug data\n     */\n    export() {\n        return {\n            events: this.getEvents(),\n            traces: this.getTraces(),\n            metrics: this.getPerformanceMetrics(),\n            timestamp: Date.now(),\n        };\n    }\n    /**\n     * Generate summary report\n     */\n    generateReport() {\n        const metrics = this.getPerformanceMetrics();\n        const traces = this.getTraces();\n        const lines = [\n            '╔══════════════════════════════════════════════════════════════════╗',\n            '║               edgeFlow.js Debug Report                          ║',\n            '╠══════════════════════════════════════════════════════════════════╣',\n            '║ Performance Metrics                                             ║',\n            '╟──────────────────────────────────────────────────────────────────╢',\n            `║ Total Inferences:     ${metrics.inferenceCount.toString().padStart(10)}                          ║`,\n            `║ Average Time:         ${metrics.averageInferenceTime.toFixed(2).padStart(10)}ms                       ║`,\n            `║ Min Time:             ${(metrics.minInferenceTime === Infinity ? 0 : metrics.minInferenceTime).toFixed(2).padStart(10)}ms                       ║`,\n            `║ Max Time:             ${metrics.maxInferenceTime.toFixed(2).padStart(10)}ms                       ║`,\n            `║ Peak Memory:          ${formatBytes(metrics.peakMemoryUsage).padStart(10)}                          ║`,\n            `║ Current Memory:       ${formatBytes(metrics.currentMemoryUsage).padStart(10)}                          ║`,\n            `║ Tensor Allocations:   ${metrics.tensorAllocations.toString().padStart(10)}                          ║`,\n            `║ Tensor Deallocations: ${metrics.tensorDeallocations.toString().padStart(10)}                          ║`,\n            '╟──────────────────────────────────────────────────────────────────╢',\n            '║ Recent Traces                                                   ║',\n            '╟──────────────────────────────────────────────────────────────────╢',\n        ];\n        const recentTraces = traces.slice(-5);\n        for (const trace of recentTraces) {\n            lines.push(`║ ${trace.id.slice(0, 20).padEnd(20)} | ${trace.duration.toFixed(2).padStart(8)}ms | ${trace.modelId.slice(0, 20).padEnd(20)} ║`);\n        }\n        if (recentTraces.length === 0) {\n            lines.push('║ No traces recorded                                              ║');\n        }\n        lines.push('╚══════════════════════════════════════════════════════════════════╝');\n        return lines.join('\\n');\n    }\n}\n// ============================================================================\n// Global Debugger Instance\n// ============================================================================\nlet globalDebugger = null;\n/**\n * Get or create the global debugger instance\n */\nexport function getDebugger(config) {\n    if (!globalDebugger || config) {\n        globalDebugger = new EdgeFlowDebugger(config);\n    }\n    return globalDebugger;\n}\n/**\n * Enable debugging\n */\nexport function enableDebugging(config) {\n    const debugger_ = getDebugger(config);\n    debugger_.enable();\n    return debugger_;\n}\n/**\n * Disable debugging\n */\nexport function disableDebugging() {\n    globalDebugger?.disable();\n}\n// ============================================================================\n// Visualization Helpers\n// ============================================================================\n/**\n * Create ASCII histogram\n */\nexport function createAsciiHistogram(histogram, width = 50, height = 10) {\n    const { counts, binEdges } = histogram;\n    const maxCount = Math.max(...counts);\n    if (maxCount === 0)\n        return 'No data to display';\n    const lines = [];\n    // Scale counts to height\n    const scaled = counts.map(c => Math.round((c / maxCount) * height));\n    // Create rows\n    for (let row = height; row > 0; row--) {\n        let line = row === height ? `${maxCount.toString().padStart(6)} │` : '       │';\n        for (let col = 0; col < width && col < scaled.length; col++) {\n            line += (scaled[col] ?? 0) >= row ? '█' : ' ';\n        }\n        lines.push(line);\n    }\n    // X axis\n    lines.push('       └' + '─'.repeat(Math.min(width, scaled.length)));\n    // Labels\n    const minLabel = (binEdges[0] ?? 0).toFixed(2);\n    const maxLabel = (binEdges[binEdges.length - 1] ?? 0).toFixed(2);\n    lines.push(`        ${minLabel}${' '.repeat(Math.max(0, Math.min(width, scaled.length) - minLabel.length - maxLabel.length))}${maxLabel}`);\n    return lines.join('\\n');\n}\n/**\n * Create tensor heatmap (for 2D tensors)\n */\nexport function createTensorHeatmap(tensor, width = 40) {\n    const shape = tensor.shape;\n    if (shape.length !== 2) {\n        return 'Heatmap only supports 2D tensors';\n    }\n    const [rows, cols] = shape;\n    if (rows === undefined || cols === undefined) {\n        return 'Invalid tensor shape';\n    }\n    const data = tensor.toFloat32Array();\n    // Find min/max\n    let min = Infinity;\n    let max = -Infinity;\n    for (let i = 0; i < data.length; i++) {\n        const val = data[i] ?? 0;\n        if (!isNaN(val) && isFinite(val)) {\n            min = Math.min(min, val);\n            max = Math.max(max, val);\n        }\n    }\n    const range = max - min;\n    const chars = [' ', '░', '▒', '▓', '█'];\n    const lines = [];\n    const scaleX = Math.max(1, Math.ceil(cols / width));\n    const displayCols = Math.min(cols, width);\n    for (let r = 0; r < rows; r++) {\n        let line = '';\n        for (let c = 0; c < displayCols; c++) {\n            const idx = r * cols + c * scaleX;\n            const val = data[idx] ?? 0;\n            const normalized = range > 0 ? (val - min) / range : 0;\n            const charIdx = Math.floor(normalized * (chars.length - 1));\n            line += chars[charIdx];\n        }\n        lines.push(line);\n    }\n    return lines.join('\\n');\n}\n/**\n * Create model architecture visualization\n */\nexport function visualizeModelArchitecture(layers) {\n    const lines = [];\n    lines.push('┌─────────────────────────────────────────────────────────────────────┐');\n    lines.push('│                        Model Architecture                          │');\n    lines.push('├─────────────────────────────────────────────────────────────────────┤');\n    for (let i = 0; i < layers.length; i++) {\n        const layer = layers[i];\n        const inputStr = `[${layer.inputShape.join('×')}]`;\n        const outputStr = `[${layer.outputShape.join('×')}]`;\n        lines.push(`│ ${(i + 1).toString().padStart(2)}. ${layer.name.padEnd(20)} │ ${layer.type.padEnd(15)} │`);\n        lines.push(`│     ${inputStr.padEnd(15)} → ${outputStr.padEnd(15)}                   │`);\n        if (i < layers.length - 1) {\n            lines.push('│                           ↓                                        │');\n        }\n    }\n    lines.push('└─────────────────────────────────────────────────────────────────────┘');\n    return lines.join('\\n');\n}\n// ============================================================================\n// Exports\n// ============================================================================\nexport default {\n    EdgeFlowDebugger,\n    getDebugger,\n    enableDebugging,\n    disableDebugging,\n    inspectTensor,\n    formatTensorInspection,\n    createAsciiHistogram,\n    createTensorHeatmap,\n    visualizeModelArchitecture,\n};\n//# sourceMappingURL=debugger.js.map"
  },
  {
    "path": "dist/tools/index.d.ts",
    "content": "/**\n * edgeFlow.js - Tools and Utilities\n *\n * Model optimization, quantization, and analysis tools.\n */\nimport { LoadedModel, QuantizationType } from '../core/types.js';\n/**\n * Quantization options\n */\nexport interface QuantizationOptions {\n    /** Quantization method */\n    method: QuantizationType;\n    /** Calibration data for calibrated quantization */\n    calibrationData?: Float32Array[];\n    /** Whether to quantize weights only */\n    weightsOnly?: boolean;\n    /** Layers to exclude from quantization */\n    excludeLayers?: string[];\n}\n/**\n * Quantization result\n */\nexport interface QuantizationResult {\n    /** Quantized model data */\n    modelData: ArrayBuffer;\n    /** Original size in bytes */\n    originalSize: number;\n    /** Quantized size in bytes */\n    quantizedSize: number;\n    /** Compression ratio */\n    compressionRatio: number;\n    /** Quantization statistics */\n    stats: {\n        layersQuantized: number;\n        layersSkipped: number;\n    };\n}\n/**\n * Quantize a model\n *\n * @example\n * ```typescript\n * const quantized = await quantize(model, {\n *   method: 'int8',\n *   calibrationData: samples,\n * });\n * ```\n */\nexport declare function quantize(model: LoadedModel | ArrayBuffer, options: QuantizationOptions): Promise<QuantizationResult>;\n/**\n * Pruning options\n */\nexport interface PruningOptions {\n    /** Target sparsity (0-1) */\n    sparsity: number;\n    /** Pruning method */\n    method?: 'magnitude' | 'random' | 'structured';\n    /** Layers to exclude */\n    excludeLayers?: string[];\n}\n/**\n * Pruning result\n */\nexport interface PruningResult {\n    /** Pruned model data */\n    modelData: ArrayBuffer;\n    /** Achieved sparsity */\n    actualSparsity: number;\n    /** Number of parameters pruned */\n    parametersPruned: number;\n    /** Total parameters */\n    totalParameters: number;\n}\n/**\n * Prune model weights\n */\nexport declare function prune(model: LoadedModel | ArrayBuffer, options: PruningOptions): Promise<PruningResult>;\n/**\n * Model analysis result\n */\nexport interface ModelAnalysis {\n    /** Total number of parameters */\n    totalParameters: number;\n    /** Model size in bytes */\n    sizeBytes: number;\n    /** Layer information */\n    layers: Array<{\n        name: string;\n        type: string;\n        parameters: number;\n        inputShape: number[];\n        outputShape: number[];\n    }>;\n    /** Estimated FLOPs */\n    estimatedFlops: number;\n    /** Memory requirements */\n    memoryRequirements: {\n        weights: number;\n        activations: number;\n        total: number;\n    };\n}\n/**\n * Analyze a model\n */\nexport declare function analyzeModel(model: LoadedModel | ArrayBuffer): Promise<ModelAnalysis>;\n/**\n * Benchmark options\n */\nexport interface BenchmarkOptions {\n    /** Number of warmup runs */\n    warmupRuns?: number;\n    /** Number of benchmark runs */\n    runs?: number;\n    /** Input shape */\n    inputShape?: number[];\n}\n/**\n * Benchmark result\n */\nexport interface BenchmarkResult {\n    /** Average inference time in ms */\n    avgTime: number;\n    /** Minimum inference time in ms */\n    minTime: number;\n    /** Maximum inference time in ms */\n    maxTime: number;\n    /** Standard deviation */\n    stdDev: number;\n    /** Throughput (inferences per second) */\n    throughput: number;\n    /** All run times */\n    times: number[];\n}\n/**\n * Benchmark model inference\n */\nexport declare function benchmark(runFn: () => Promise<void>, options?: BenchmarkOptions): Promise<BenchmarkResult>;\nexport { benchmark as runBenchmark, compareBenchmarks, benchmarkSuite, benchmarkMemory, formatBenchmarkResult, formatComparisonResult, } from './benchmark.js';\nexport type { BenchmarkOptions as DetailedBenchmarkOptions, BenchmarkResult as DetailedBenchmarkResult, CompareBenchmarkResult, MemoryBenchmarkResult, } from './benchmark.js';\nexport { quantizeModel, quantizeTensor, dequantizeTensor, pruneModel, pruneTensor, analyzeModel as analyzeModelDetailed, exportModel as exportModelAdvanced, dequantizeInt8, dequantizeUint8, dequantizeFloat16, float16ToFloat32, } from './quantization.js';\nexport type { QuantizationType as QuantizationMethod, QuantizationOptions as AdvancedQuantizationOptions, QuantizationProgress, QuantizationResult as AdvancedQuantizationResult, LayerQuantizationStats, QuantizationStats, PruningOptions as AdvancedPruningOptions, PruningResult as AdvancedPruningResult, ModelAnalysis as DetailedModelAnalysis, ExportFormat, ExportOptions, } from './quantization.js';\nexport { EdgeFlowDebugger, getDebugger, enableDebugging, disableDebugging, inspectTensor, formatTensorInspection, createAsciiHistogram, createTensorHeatmap, visualizeModelArchitecture, } from './debugger.js';\nexport type { DebuggerConfig, TensorInspection, TensorStats, HistogramData, InferenceTrace, OperationTrace, DebugEvent, PerformanceMetrics as DebugPerformanceMetrics, } from './debugger.js';\nexport { PerformanceMonitor, getMonitor, startMonitoring, stopMonitoring, generateDashboardHTML, generateAsciiDashboard, } from './monitor.js';\nexport type { MonitorConfig, PerformanceSample, InferenceMetrics, MemoryMetrics, SystemMetrics, AlertConfig, AlertEvent, WidgetData, } from './monitor.js';\n/**\n * Export model to different formats\n */\nexport declare function exportModel(model: LoadedModel | ArrayBuffer, format: 'onnx' | 'json' | 'binary'): Promise<ArrayBuffer | string>;\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/tools/index.js",
    "content": "/**\n * edgeFlow.js - Tools and Utilities\n *\n * Model optimization, quantization, and analysis tools.\n */\n/**\n * Quantize a model\n *\n * @example\n * ```typescript\n * const quantized = await quantize(model, {\n *   method: 'int8',\n *   calibrationData: samples,\n * });\n * ```\n */\nexport async function quantize(model, options) {\n    // Get model data\n    const modelData = model instanceof ArrayBuffer\n        ? model\n        : await getModelData(model);\n    const originalSize = modelData.byteLength;\n    // Apply quantization based on method\n    let quantizedData;\n    let layersQuantized = 0;\n    let layersSkipped = 0;\n    switch (options.method) {\n        case 'int8':\n            ({ data: quantizedData, layersQuantized, layersSkipped } =\n                quantizeInt8(modelData, options));\n            break;\n        case 'uint8':\n            ({ data: quantizedData, layersQuantized, layersSkipped } =\n                quantizeUint8(modelData, options));\n            break;\n        case 'float16':\n            ({ data: quantizedData, layersQuantized, layersSkipped } =\n                quantizeFloat16(modelData, options));\n            break;\n        case 'int4':\n            ({ data: quantizedData, layersQuantized, layersSkipped } =\n                quantizeInt4(modelData, options));\n            break;\n        default:\n            quantizedData = modelData;\n    }\n    return {\n        modelData: quantizedData,\n        originalSize,\n        quantizedSize: quantizedData.byteLength,\n        compressionRatio: originalSize / quantizedData.byteLength,\n        stats: {\n            layersQuantized,\n            layersSkipped,\n        },\n    };\n}\n/**\n * Placeholder for getting model data\n */\nasync function getModelData(_model) {\n    // In production, this would extract the model weights\n    return new ArrayBuffer(0);\n}\n/**\n * INT8 quantization\n */\nfunction quantizeInt8(data, _options) {\n    // Simplified INT8 quantization\n    const input = new Float32Array(data);\n    const output = new Int8Array(input.length);\n    // Find scale\n    let max = 0;\n    for (let i = 0; i < input.length; i++) {\n        const abs = Math.abs(input[i] ?? 0);\n        if (abs > max)\n            max = abs;\n    }\n    const scale = max / 127;\n    // Quantize\n    for (let i = 0; i < input.length; i++) {\n        output[i] = Math.round((input[i] ?? 0) / scale);\n    }\n    return {\n        data: output.buffer,\n        layersQuantized: 1,\n        layersSkipped: 0,\n    };\n}\n/**\n * UINT8 quantization\n */\nfunction quantizeUint8(data, _options) {\n    const input = new Float32Array(data);\n    const output = new Uint8Array(input.length);\n    // Find min/max\n    let min = Infinity, max = -Infinity;\n    for (let i = 0; i < input.length; i++) {\n        const val = input[i] ?? 0;\n        if (val < min)\n            min = val;\n        if (val > max)\n            max = val;\n    }\n    const scale = (max - min) / 255;\n    // Quantize\n    for (let i = 0; i < input.length; i++) {\n        output[i] = Math.round(((input[i] ?? 0) - min) / scale);\n    }\n    return {\n        data: output.buffer,\n        layersQuantized: 1,\n        layersSkipped: 0,\n    };\n}\n/**\n * Float16 quantization\n */\nfunction quantizeFloat16(data, _options) {\n    const input = new Float32Array(data);\n    const output = new Uint16Array(input.length);\n    // Convert float32 to float16\n    for (let i = 0; i < input.length; i++) {\n        output[i] = float32ToFloat16(input[i] ?? 0);\n    }\n    return {\n        data: output.buffer,\n        layersQuantized: 1,\n        layersSkipped: 0,\n    };\n}\n/**\n * INT4 quantization\n */\nfunction quantizeInt4(data, _options) {\n    const input = new Float32Array(data);\n    // Pack two INT4 values per byte\n    const output = new Uint8Array(Math.ceil(input.length / 2));\n    // Find scale\n    let max = 0;\n    for (let i = 0; i < input.length; i++) {\n        const abs = Math.abs(input[i] ?? 0);\n        if (abs > max)\n            max = abs;\n    }\n    const scale = max / 7; // INT4 range: -8 to 7\n    // Quantize and pack\n    for (let i = 0; i < input.length; i += 2) {\n        const val1 = Math.round((input[i] ?? 0) / scale) + 8;\n        const val2 = Math.round((input[i + 1] ?? 0) / scale) + 8;\n        output[i / 2] = ((val1 & 0xF) << 4) | (val2 & 0xF);\n    }\n    return {\n        data: output.buffer,\n        layersQuantized: 1,\n        layersSkipped: 0,\n    };\n}\n/**\n * Convert float32 to float16\n */\nfunction float32ToFloat16(value) {\n    const floatView = new Float32Array(1);\n    const int32View = new Int32Array(floatView.buffer);\n    floatView[0] = value;\n    const x = int32View[0] ?? 0;\n    let bits = (x >> 16) & 0x8000; // sign\n    let m = (x >> 12) & 0x07ff; // mantissa\n    const e = (x >> 23) & 0xff; // exponent\n    if (e < 103) {\n        // Too small, return zero\n        return bits;\n    }\n    if (e > 142) {\n        // Too large, return infinity\n        bits |= 0x7c00;\n        bits |= ((e === 255) ? 0 : 1) && (x & 0x007fffff);\n        return bits;\n    }\n    if (e < 113) {\n        // Denormalized\n        m |= 0x0800;\n        bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);\n        return bits;\n    }\n    bits |= ((e - 112) << 10) | (m >> 1);\n    bits += m & 1;\n    return bits;\n}\n/**\n * Prune model weights\n */\nexport async function prune(model, options) {\n    const modelData = model instanceof ArrayBuffer\n        ? model\n        : await getModelData(model);\n    const weights = new Float32Array(modelData);\n    const total = weights.length;\n    // Calculate threshold for magnitude pruning\n    const magnitudes = weights.map(Math.abs);\n    const sorted = [...magnitudes].sort((a, b) => a - b);\n    const thresholdIdx = Math.floor(options.sparsity * sorted.length);\n    const threshold = sorted[thresholdIdx] ?? 0;\n    // Prune weights\n    let pruned = 0;\n    for (let i = 0; i < weights.length; i++) {\n        if (Math.abs(weights[i] ?? 0) < threshold) {\n            weights[i] = 0;\n            pruned++;\n        }\n    }\n    return {\n        modelData: weights.buffer,\n        actualSparsity: pruned / total,\n        parametersPruned: pruned,\n        totalParameters: total,\n    };\n}\n/**\n * Analyze a model\n */\nexport async function analyzeModel(model) {\n    // Simplified analysis\n    const size = model instanceof ArrayBuffer\n        ? model.byteLength\n        : model.metadata.sizeBytes;\n    const estimatedParams = Math.floor(size / 4); // Assume float32\n    return {\n        totalParameters: estimatedParams,\n        sizeBytes: size,\n        layers: [],\n        estimatedFlops: estimatedParams * 2, // Rough estimate\n        memoryRequirements: {\n            weights: size,\n            activations: size * 0.1, // Rough estimate\n            total: size * 1.1,\n        },\n    };\n}\n/**\n * Benchmark model inference\n */\nexport async function benchmark(runFn, options = {}) {\n    const { warmupRuns = 3, runs = 10, } = options;\n    // Warmup\n    for (let i = 0; i < warmupRuns; i++) {\n        await runFn();\n    }\n    // Benchmark\n    const times = [];\n    for (let i = 0; i < runs; i++) {\n        const start = performance.now();\n        await runFn();\n        times.push(performance.now() - start);\n    }\n    // Calculate statistics\n    const sum = times.reduce((a, b) => a + b, 0);\n    const avgTime = sum / times.length;\n    const minTime = Math.min(...times);\n    const maxTime = Math.max(...times);\n    const squaredDiffs = times.map(t => Math.pow(t - avgTime, 2));\n    const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / times.length;\n    const stdDev = Math.sqrt(avgSquaredDiff);\n    return {\n        avgTime,\n        minTime,\n        maxTime,\n        stdDev,\n        throughput: 1000 / avgTime,\n        times,\n    };\n}\n// ============================================================================\n// Re-export benchmark utilities\n// ============================================================================\nexport { benchmark as runBenchmark, compareBenchmarks, benchmarkSuite, benchmarkMemory, formatBenchmarkResult, formatComparisonResult, } from './benchmark.js';\n// ============================================================================\n// Re-export advanced quantization tools\n// ============================================================================\nexport { quantizeModel, quantizeTensor, dequantizeTensor, pruneModel, pruneTensor, analyzeModel as analyzeModelDetailed, exportModel as exportModelAdvanced, dequantizeInt8, dequantizeUint8, dequantizeFloat16, float16ToFloat32, } from './quantization.js';\n// ============================================================================\n// Re-export debugging tools\n// ============================================================================\nexport { EdgeFlowDebugger, getDebugger, enableDebugging, disableDebugging, inspectTensor, formatTensorInspection, createAsciiHistogram, createTensorHeatmap, visualizeModelArchitecture, } from './debugger.js';\n// ============================================================================\n// Re-export monitoring tools\n// ============================================================================\nexport { PerformanceMonitor, getMonitor, startMonitoring, stopMonitoring, generateDashboardHTML, generateAsciiDashboard, } from './monitor.js';\n// ============================================================================\n// Export Utilities\n// ============================================================================\n/**\n * Export model to different formats\n */\nexport async function exportModel(model, format) {\n    const modelData = model instanceof ArrayBuffer\n        ? model\n        : await getModelData(model);\n    switch (format) {\n        case 'json':\n            // Export as JSON (for small models)\n            const array = new Float32Array(modelData);\n            return JSON.stringify(Array.from(array));\n        case 'binary':\n        case 'onnx':\n        default:\n            return modelData;\n    }\n}\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/tools/monitor.d.ts",
    "content": "/**\n * edgeFlow.js - Performance Monitoring Dashboard\n *\n * Real-time performance monitoring and metrics visualization.\n */\n/**\n * Monitor configuration\n */\nexport interface MonitorConfig {\n    /** Enable monitoring (default: true) */\n    enabled?: boolean;\n    /** Sampling interval in ms (default: 1000) */\n    sampleInterval?: number;\n    /** History size (number of samples to keep) */\n    historySize?: number;\n    /** Enable memory monitoring (default: true) */\n    monitorMemory?: boolean;\n    /** Enable FPS monitoring (default: true) */\n    monitorFPS?: boolean;\n    /** Custom metric collectors */\n    collectors?: Array<() => Record<string, number>>;\n}\n/**\n * Performance sample\n */\nexport interface PerformanceSample {\n    timestamp: number;\n    inference: InferenceMetrics;\n    memory: MemoryMetrics;\n    system: SystemMetrics;\n    custom: Record<string, number>;\n}\n/**\n * Inference metrics\n */\nexport interface InferenceMetrics {\n    /** Inferences in the last interval */\n    count: number;\n    /** Average inference time (ms) */\n    avgTime: number;\n    /** Min inference time (ms) */\n    minTime: number;\n    /** Max inference time (ms) */\n    maxTime: number;\n    /** Throughput (inferences per second) */\n    throughput: number;\n    /** Queue length */\n    queueLength: number;\n    /** Active inferences */\n    activeCount: number;\n}\n/**\n * Memory metrics\n */\nexport interface MemoryMetrics {\n    /** Used JS heap size (bytes) */\n    usedHeap: number;\n    /** Total JS heap size (bytes) */\n    totalHeap: number;\n    /** Heap limit (bytes) */\n    heapLimit: number;\n    /** Heap usage percentage */\n    heapUsage: number;\n    /** Tensor memory (bytes) */\n    tensorMemory: number;\n    /** Cache memory (bytes) */\n    cacheMemory: number;\n}\n/**\n * System metrics\n */\nexport interface SystemMetrics {\n    /** Frames per second */\n    fps: number;\n    /** CPU usage estimate (0-1) */\n    cpuUsage: number;\n    /** Time since last sample (ms) */\n    deltaTime: number;\n    /** Browser info */\n    userAgent: string;\n    /** WebGPU available */\n    webgpuAvailable: boolean;\n    /** WebNN available */\n    webnnAvailable: boolean;\n}\n/**\n * Alert configuration\n */\nexport interface AlertConfig {\n    /** Metric name */\n    metric: string;\n    /** Threshold value */\n    threshold: number;\n    /** Comparison operator */\n    operator: '>' | '<' | '>=' | '<=' | '==' | '!=';\n    /** Alert message */\n    message: string;\n    /** Alert level */\n    level: 'info' | 'warn' | 'error';\n}\n/**\n * Alert event\n */\nexport interface AlertEvent {\n    config: AlertConfig;\n    value: number;\n    timestamp: number;\n}\n/**\n * Dashboard widget data\n */\nexport interface WidgetData {\n    type: 'chart' | 'gauge' | 'counter' | 'text';\n    title: string;\n    data: unknown;\n}\n/**\n * Performance monitor for edgeFlow.js\n */\nexport declare class PerformanceMonitor {\n    private config;\n    private samples;\n    private isRunning;\n    private intervalId;\n    private alerts;\n    private alertListeners;\n    private sampleListeners;\n    private inferenceCount;\n    private inferenceTimes;\n    private queueLength;\n    private activeCount;\n    private frameCount;\n    private lastFrameTime;\n    private fps;\n    private rafId;\n    private tensorMemory;\n    private cacheMemory;\n    constructor(config?: MonitorConfig);\n    /**\n     * Start monitoring\n     */\n    start(): void;\n    /**\n     * Stop monitoring\n     */\n    stop(): void;\n    /**\n     * Monitor FPS\n     */\n    private monitorFPS;\n    /**\n     * Collect a performance sample\n     */\n    private collectSample;\n    /**\n     * Collect memory metrics\n     */\n    private collectMemoryMetrics;\n    /**\n     * Collect system metrics\n     */\n    private collectSystemMetrics;\n    /**\n     * Estimate CPU usage based on inference times\n     */\n    private estimateCPUUsage;\n    /**\n     * Check alerts\n     */\n    private checkAlerts;\n    /**\n     * Get metric value from sample\n     */\n    private getMetricValue;\n    /**\n     * Record an inference\n     */\n    recordInference(duration: number): void;\n    /**\n     * Update queue length\n     */\n    updateQueueLength(length: number): void;\n    /**\n     * Update active count\n     */\n    updateActiveCount(count: number): void;\n    /**\n     * Update tensor memory\n     */\n    updateTensorMemory(bytes: number): void;\n    /**\n     * Update cache memory\n     */\n    updateCacheMemory(bytes: number): void;\n    /**\n     * Add an alert\n     */\n    addAlert(config: AlertConfig): void;\n    /**\n     * Remove an alert\n     */\n    removeAlert(metric: string): void;\n    /**\n     * Subscribe to alerts\n     */\n    onAlert(callback: (alert: AlertEvent) => void): () => void;\n    /**\n     * Subscribe to samples\n     */\n    onSample(callback: (sample: PerformanceSample) => void): () => void;\n    /**\n     * Get current sample\n     */\n    getCurrentSample(): PerformanceSample | undefined;\n    /**\n     * Get all samples\n     */\n    getSamples(): PerformanceSample[];\n    /**\n     * Get samples in time range\n     */\n    getSamplesInRange(startTime: number, endTime: number): PerformanceSample[];\n    /**\n     * Get summary statistics\n     */\n    getSummary(): {\n        avgInferenceTime: number;\n        avgThroughput: number;\n        avgMemoryUsage: number;\n        avgFPS: number;\n        totalInferences: number;\n        uptime: number;\n    };\n    /**\n     * Clear all data\n     */\n    clear(): void;\n    /**\n     * Export data\n     */\n    export(): {\n        samples: PerformanceSample[];\n        summary: {\n            avgInferenceTime: number;\n            avgThroughput: number;\n            avgMemoryUsage: number;\n            avgFPS: number;\n            totalInferences: number;\n            uptime: number;\n        };\n        config: MonitorConfig;\n        timestamp: number;\n    };\n}\n/**\n * Generate HTML dashboard\n */\nexport declare function generateDashboardHTML(monitor: PerformanceMonitor): string;\n/**\n * Generate ASCII dashboard\n */\nexport declare function generateAsciiDashboard(monitor: PerformanceMonitor): string;\n/**\n * Get or create global monitor\n */\nexport declare function getMonitor(config?: MonitorConfig): PerformanceMonitor;\n/**\n * Start monitoring\n */\nexport declare function startMonitoring(config?: MonitorConfig): PerformanceMonitor;\n/**\n * Stop monitoring\n */\nexport declare function stopMonitoring(): void;\ndeclare const _default: {\n    PerformanceMonitor: typeof PerformanceMonitor;\n    getMonitor: typeof getMonitor;\n    startMonitoring: typeof startMonitoring;\n    stopMonitoring: typeof stopMonitoring;\n    generateDashboardHTML: typeof generateDashboardHTML;\n    generateAsciiDashboard: typeof generateAsciiDashboard;\n};\nexport default _default;\n//# sourceMappingURL=monitor.d.ts.map"
  },
  {
    "path": "dist/tools/monitor.js",
    "content": "/**\n * edgeFlow.js - Performance Monitoring Dashboard\n *\n * Real-time performance monitoring and metrics visualization.\n */\n// ============================================================================\n// Performance Monitor\n// ============================================================================\n/**\n * Performance monitor for edgeFlow.js\n */\nexport class PerformanceMonitor {\n    config;\n    samples = [];\n    isRunning = false;\n    intervalId = null;\n    alerts = [];\n    alertListeners = [];\n    sampleListeners = [];\n    // Inference tracking\n    inferenceCount = 0;\n    inferenceTimes = [];\n    queueLength = 0;\n    activeCount = 0;\n    // FPS tracking\n    frameCount = 0;\n    lastFrameTime = 0;\n    fps = 0;\n    rafId = null;\n    // Memory tracking\n    tensorMemory = 0;\n    cacheMemory = 0;\n    constructor(config = {}) {\n        this.config = {\n            enabled: config.enabled ?? true,\n            sampleInterval: config.sampleInterval ?? 1000,\n            historySize: config.historySize ?? 60,\n            monitorMemory: config.monitorMemory ?? true,\n            monitorFPS: config.monitorFPS ?? true,\n            collectors: config.collectors ?? [],\n        };\n    }\n    /**\n     * Start monitoring\n     */\n    start() {\n        if (this.isRunning)\n            return;\n        this.isRunning = true;\n        // Start sampling\n        this.intervalId = setInterval(() => {\n            this.collectSample();\n        }, this.config.sampleInterval);\n        // Start FPS monitoring\n        if (this.config.monitorFPS && typeof requestAnimationFrame !== 'undefined') {\n            this.lastFrameTime = performance.now();\n            this.frameCount = 0;\n            this.monitorFPS();\n        }\n    }\n    /**\n     * Stop monitoring\n     */\n    stop() {\n        this.isRunning = false;\n        if (this.intervalId) {\n            clearInterval(this.intervalId);\n            this.intervalId = null;\n        }\n        if (this.rafId) {\n            cancelAnimationFrame(this.rafId);\n            this.rafId = null;\n        }\n    }\n    /**\n     * Monitor FPS\n     */\n    monitorFPS() {\n        if (!this.isRunning)\n            return;\n        this.frameCount++;\n        const now = performance.now();\n        const elapsed = now - this.lastFrameTime;\n        if (elapsed >= 1000) {\n            this.fps = Math.round((this.frameCount * 1000) / elapsed);\n            this.frameCount = 0;\n            this.lastFrameTime = now;\n        }\n        this.rafId = requestAnimationFrame(() => this.monitorFPS());\n    }\n    /**\n     * Collect a performance sample\n     */\n    collectSample() {\n        const now = Date.now();\n        // Calculate inference metrics\n        const avgTime = this.inferenceTimes.length > 0\n            ? this.inferenceTimes.reduce((a, b) => a + b, 0) / this.inferenceTimes.length\n            : 0;\n        const minTime = this.inferenceTimes.length > 0\n            ? Math.min(...this.inferenceTimes)\n            : 0;\n        const maxTime = this.inferenceTimes.length > 0\n            ? Math.max(...this.inferenceTimes)\n            : 0;\n        const throughput = this.inferenceCount / (this.config.sampleInterval / 1000);\n        const inference = {\n            count: this.inferenceCount,\n            avgTime,\n            minTime,\n            maxTime,\n            throughput,\n            queueLength: this.queueLength,\n            activeCount: this.activeCount,\n        };\n        // Collect memory metrics\n        const memory = this.collectMemoryMetrics();\n        // Collect system metrics\n        const system = this.collectSystemMetrics();\n        // Collect custom metrics\n        const custom = {};\n        for (const collector of this.config.collectors) {\n            try {\n                Object.assign(custom, collector());\n            }\n            catch {\n                // Ignore collector errors\n            }\n        }\n        const sample = {\n            timestamp: now,\n            inference,\n            memory,\n            system,\n            custom,\n        };\n        // Add to history\n        this.samples.push(sample);\n        if (this.samples.length > this.config.historySize) {\n            this.samples.shift();\n        }\n        // Check alerts\n        this.checkAlerts(sample);\n        // Notify listeners\n        for (const listener of this.sampleListeners) {\n            listener(sample);\n        }\n        // Reset counters\n        this.inferenceCount = 0;\n        this.inferenceTimes = [];\n    }\n    /**\n     * Collect memory metrics\n     */\n    collectMemoryMetrics() {\n        let usedHeap = 0;\n        let totalHeap = 0;\n        let heapLimit = 0;\n        if (typeof performance !== 'undefined' && 'memory' in performance) {\n            const memory = performance.memory;\n            usedHeap = memory.usedJSHeapSize;\n            totalHeap = memory.totalJSHeapSize;\n            heapLimit = memory.jsHeapSizeLimit;\n        }\n        return {\n            usedHeap,\n            totalHeap,\n            heapLimit,\n            heapUsage: heapLimit > 0 ? usedHeap / heapLimit : 0,\n            tensorMemory: this.tensorMemory,\n            cacheMemory: this.cacheMemory,\n        };\n    }\n    /**\n     * Collect system metrics\n     */\n    collectSystemMetrics() {\n        const lastSample = this.samples[this.samples.length - 1];\n        const deltaTime = lastSample\n            ? Date.now() - lastSample.timestamp\n            : this.config.sampleInterval;\n        // Check WebGPU availability\n        let webgpuAvailable = false;\n        if (typeof navigator !== 'undefined' && 'gpu' in navigator) {\n            webgpuAvailable = true;\n        }\n        // Check WebNN availability\n        let webnnAvailable = false;\n        if (typeof navigator !== 'undefined' && 'ml' in navigator) {\n            webnnAvailable = true;\n        }\n        return {\n            fps: this.fps,\n            cpuUsage: this.estimateCPUUsage(),\n            deltaTime,\n            userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',\n            webgpuAvailable,\n            webnnAvailable,\n        };\n    }\n    /**\n     * Estimate CPU usage based on inference times\n     */\n    estimateCPUUsage() {\n        if (this.inferenceTimes.length === 0)\n            return 0;\n        const totalTime = this.inferenceTimes.reduce((a, b) => a + b, 0);\n        return Math.min(1, totalTime / this.config.sampleInterval);\n    }\n    /**\n     * Check alerts\n     */\n    checkAlerts(sample) {\n        for (const alert of this.alerts) {\n            const value = this.getMetricValue(sample, alert.metric);\n            if (value === undefined)\n                continue;\n            let triggered = false;\n            switch (alert.operator) {\n                case '>':\n                    triggered = value > alert.threshold;\n                    break;\n                case '<':\n                    triggered = value < alert.threshold;\n                    break;\n                case '>=':\n                    triggered = value >= alert.threshold;\n                    break;\n                case '<=':\n                    triggered = value <= alert.threshold;\n                    break;\n                case '==':\n                    triggered = value === alert.threshold;\n                    break;\n                case '!=':\n                    triggered = value !== alert.threshold;\n                    break;\n            }\n            if (triggered) {\n                const event = {\n                    config: alert,\n                    value,\n                    timestamp: sample.timestamp,\n                };\n                for (const listener of this.alertListeners) {\n                    listener(event);\n                }\n            }\n        }\n    }\n    /**\n     * Get metric value from sample\n     */\n    getMetricValue(sample, metric) {\n        const parts = metric.split('.');\n        let value = sample;\n        for (const part of parts) {\n            if (value && typeof value === 'object' && part in value) {\n                value = value[part];\n            }\n            else {\n                return undefined;\n            }\n        }\n        return typeof value === 'number' ? value : undefined;\n    }\n    /**\n     * Record an inference\n     */\n    recordInference(duration) {\n        this.inferenceCount++;\n        this.inferenceTimes.push(duration);\n    }\n    /**\n     * Update queue length\n     */\n    updateQueueLength(length) {\n        this.queueLength = length;\n    }\n    /**\n     * Update active count\n     */\n    updateActiveCount(count) {\n        this.activeCount = count;\n    }\n    /**\n     * Update tensor memory\n     */\n    updateTensorMemory(bytes) {\n        this.tensorMemory = bytes;\n    }\n    /**\n     * Update cache memory\n     */\n    updateCacheMemory(bytes) {\n        this.cacheMemory = bytes;\n    }\n    /**\n     * Add an alert\n     */\n    addAlert(config) {\n        this.alerts.push(config);\n    }\n    /**\n     * Remove an alert\n     */\n    removeAlert(metric) {\n        this.alerts = this.alerts.filter(a => a.metric !== metric);\n    }\n    /**\n     * Subscribe to alerts\n     */\n    onAlert(callback) {\n        this.alertListeners.push(callback);\n        return () => {\n            const idx = this.alertListeners.indexOf(callback);\n            if (idx !== -1)\n                this.alertListeners.splice(idx, 1);\n        };\n    }\n    /**\n     * Subscribe to samples\n     */\n    onSample(callback) {\n        this.sampleListeners.push(callback);\n        return () => {\n            const idx = this.sampleListeners.indexOf(callback);\n            if (idx !== -1)\n                this.sampleListeners.splice(idx, 1);\n        };\n    }\n    /**\n     * Get current sample\n     */\n    getCurrentSample() {\n        return this.samples[this.samples.length - 1];\n    }\n    /**\n     * Get all samples\n     */\n    getSamples() {\n        return [...this.samples];\n    }\n    /**\n     * Get samples in time range\n     */\n    getSamplesInRange(startTime, endTime) {\n        return this.samples.filter(s => s.timestamp >= startTime && s.timestamp <= endTime);\n    }\n    /**\n     * Get summary statistics\n     */\n    getSummary() {\n        if (this.samples.length === 0) {\n            return {\n                avgInferenceTime: 0,\n                avgThroughput: 0,\n                avgMemoryUsage: 0,\n                avgFPS: 0,\n                totalInferences: 0,\n                uptime: 0,\n            };\n        }\n        const avgInferenceTime = this.samples.reduce((sum, s) => sum + s.inference.avgTime, 0) / this.samples.length;\n        const avgThroughput = this.samples.reduce((sum, s) => sum + s.inference.throughput, 0) / this.samples.length;\n        const avgMemoryUsage = this.samples.reduce((sum, s) => sum + s.memory.heapUsage, 0) / this.samples.length;\n        const avgFPS = this.samples.reduce((sum, s) => sum + s.system.fps, 0) / this.samples.length;\n        const totalInferences = this.samples.reduce((sum, s) => sum + s.inference.count, 0);\n        const firstSample = this.samples[0];\n        const lastSample = this.samples[this.samples.length - 1];\n        const uptime = lastSample.timestamp - firstSample.timestamp;\n        return {\n            avgInferenceTime,\n            avgThroughput,\n            avgMemoryUsage,\n            avgFPS,\n            totalInferences,\n            uptime,\n        };\n    }\n    /**\n     * Clear all data\n     */\n    clear() {\n        this.samples = [];\n        this.inferenceCount = 0;\n        this.inferenceTimes = [];\n        this.queueLength = 0;\n        this.activeCount = 0;\n        this.tensorMemory = 0;\n        this.cacheMemory = 0;\n    }\n    /**\n     * Export data\n     */\n    export() {\n        return {\n            samples: this.getSamples(),\n            summary: this.getSummary(),\n            config: this.config,\n            timestamp: Date.now(),\n        };\n    }\n}\n// ============================================================================\n// Dashboard Generator\n// ============================================================================\n/**\n * Generate HTML dashboard\n */\nexport function generateDashboardHTML(monitor) {\n    const summary = monitor.getSummary();\n    const samples = monitor.getSamples();\n    const lastSample = samples[samples.length - 1];\n    const formatBytes = (bytes) => {\n        if (bytes < 1024)\n            return `${bytes} B`;\n        if (bytes < 1024 * 1024)\n            return `${(bytes / 1024).toFixed(1)} KB`;\n        if (bytes < 1024 * 1024 * 1024)\n            return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n        return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n    };\n    const formatDuration = (ms) => {\n        if (ms < 1000)\n            return `${ms.toFixed(0)}ms`;\n        if (ms < 60000)\n            return `${(ms / 1000).toFixed(1)}s`;\n        return `${(ms / 60000).toFixed(1)}m`;\n    };\n    return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>edgeFlow.js Performance Dashboard</title>\n  <style>\n    :root {\n      --bg-primary: #0d1117;\n      --bg-secondary: #161b22;\n      --bg-tertiary: #21262d;\n      --text-primary: #f0f6fc;\n      --text-secondary: #8b949e;\n      --accent: #58a6ff;\n      --success: #3fb950;\n      --warning: #d29922;\n      --error: #f85149;\n    }\n    \n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n    \n    body {\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n      background: var(--bg-primary);\n      color: var(--text-primary);\n      line-height: 1.6;\n    }\n    \n    .dashboard {\n      max-width: 1400px;\n      margin: 0 auto;\n      padding: 24px;\n    }\n    \n    header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 32px;\n      padding-bottom: 16px;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    h1 {\n      font-size: 24px;\n      font-weight: 600;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n    \n    .status {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      font-size: 14px;\n      color: var(--text-secondary);\n    }\n    \n    .status-dot {\n      width: 8px;\n      height: 8px;\n      border-radius: 50%;\n      background: var(--success);\n    }\n    \n    .grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      gap: 20px;\n      margin-bottom: 32px;\n    }\n    \n    .card {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n    }\n    \n    .card-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .card-title {\n      font-size: 14px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .card-value {\n      font-size: 36px;\n      font-weight: 700;\n      font-variant-numeric: tabular-nums;\n    }\n    \n    .card-value.small {\n      font-size: 24px;\n    }\n    \n    .card-unit {\n      font-size: 14px;\n      color: var(--text-secondary);\n      margin-left: 4px;\n    }\n    \n    .card-change {\n      font-size: 12px;\n      padding: 4px 8px;\n      border-radius: 4px;\n    }\n    \n    .card-change.up {\n      background: rgba(63, 185, 80, 0.2);\n      color: var(--success);\n    }\n    \n    .card-change.down {\n      background: rgba(248, 81, 73, 0.2);\n      color: var(--error);\n    }\n    \n    .progress-bar {\n      height: 8px;\n      background: var(--bg-tertiary);\n      border-radius: 4px;\n      overflow: hidden;\n      margin-top: 12px;\n    }\n    \n    .progress-fill {\n      height: 100%;\n      border-radius: 4px;\n      transition: width 0.3s ease;\n    }\n    \n    .progress-fill.blue { background: var(--accent); }\n    .progress-fill.green { background: var(--success); }\n    .progress-fill.yellow { background: var(--warning); }\n    .progress-fill.red { background: var(--error); }\n    \n    .chart-container {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n      margin-bottom: 20px;\n    }\n    \n    .chart-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .chart-title {\n      font-size: 16px;\n      font-weight: 600;\n    }\n    \n    .chart {\n      height: 200px;\n      position: relative;\n    }\n    \n    .chart-line {\n      stroke: var(--accent);\n      stroke-width: 2;\n      fill: none;\n    }\n    \n    .chart-area {\n      fill: url(#chartGradient);\n      opacity: 0.3;\n    }\n    \n    .chart-grid {\n      stroke: var(--bg-tertiary);\n      stroke-width: 1;\n    }\n    \n    .table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n    \n    .table th,\n    .table td {\n      padding: 12px 16px;\n      text-align: left;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    .table th {\n      font-size: 12px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .table td {\n      font-variant-numeric: tabular-nums;\n    }\n    \n    footer {\n      text-align: center;\n      padding: 24px;\n      color: var(--text-secondary);\n      font-size: 14px;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"dashboard\">\n    <header>\n      <h1>\n        <svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\">\n          <rect width=\"32\" height=\"32\" rx=\"8\" fill=\"var(--accent)\"/>\n          <path d=\"M8 16L14 10L20 16L26 10\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n          <path d=\"M8 22L14 16L20 22L26 16\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\"/>\n        </svg>\n        edgeFlow.js Performance Dashboard\n      </h1>\n      <div class=\"status\">\n        <div class=\"status-dot\"></div>\n        Running for ${formatDuration(summary.uptime)}\n      </div>\n    </header>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Total Inferences</span>\n        </div>\n        <div class=\"card-value\">${summary.totalInferences.toLocaleString()}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg Inference Time</span>\n        </div>\n        <div class=\"card-value\">${summary.avgInferenceTime.toFixed(1)}<span class=\"card-unit\">ms</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Throughput</span>\n        </div>\n        <div class=\"card-value\">${summary.avgThroughput.toFixed(1)}<span class=\"card-unit\">ops/s</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg FPS</span>\n        </div>\n        <div class=\"card-value\">${Math.round(summary.avgFPS)}</div>\n      </div>\n    </div>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Memory Usage</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.usedHeap ?? 0)}</div>\n        <div class=\"progress-bar\">\n          <div class=\"progress-fill ${summary.avgMemoryUsage > 0.8 ? 'red' : summary.avgMemoryUsage > 0.6 ? 'yellow' : 'green'}\" \n               style=\"width: ${(summary.avgMemoryUsage * 100).toFixed(0)}%\"></div>\n        </div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Tensor Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.tensorMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Cache Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.cacheMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Queue Length</span>\n        </div>\n        <div class=\"card-value small\">${lastSample?.inference.queueLength ?? 0}</div>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Inference Time History</span>\n      </div>\n      <div class=\"chart\">\n        <svg width=\"100%\" height=\"100%\" viewBox=\"0 0 600 200\" preserveAspectRatio=\"none\">\n          <defs>\n            <linearGradient id=\"chartGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stop-color=\"var(--accent)\" stop-opacity=\"0.5\"/>\n              <stop offset=\"100%\" stop-color=\"var(--accent)\" stop-opacity=\"0\"/>\n            </linearGradient>\n          </defs>\n          ${generateChartPath(samples)}\n        </svg>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Recent Samples</span>\n      </div>\n      <table class=\"table\">\n        <thead>\n          <tr>\n            <th>Time</th>\n            <th>Inferences</th>\n            <th>Avg Time</th>\n            <th>Throughput</th>\n            <th>Memory</th>\n            <th>FPS</th>\n          </tr>\n        </thead>\n        <tbody>\n          ${samples.slice(-10).reverse().map(s => `\n            <tr>\n              <td>${new Date(s.timestamp).toLocaleTimeString()}</td>\n              <td>${s.inference.count}</td>\n              <td>${s.inference.avgTime.toFixed(2)}ms</td>\n              <td>${s.inference.throughput.toFixed(1)}/s</td>\n              <td>${formatBytes(s.memory.usedHeap)}</td>\n              <td>${s.system.fps}</td>\n            </tr>\n          `).join('')}\n        </tbody>\n      </table>\n    </div>\n    \n    <footer>\n      Generated at ${new Date().toLocaleString()} | edgeFlow.js Performance Monitor\n    </footer>\n  </div>\n</body>\n</html>\n  `.trim();\n}\n/**\n * Generate SVG chart path\n */\nfunction generateChartPath(samples) {\n    if (samples.length < 2)\n        return '';\n    const width = 600;\n    const height = 180;\n    const padding = 10;\n    const times = samples.map(s => s.inference.avgTime);\n    const maxTime = Math.max(...times, 1);\n    const points = samples.map((s, i) => {\n        const x = padding + (i / (samples.length - 1)) * (width - 2 * padding);\n        const y = height - padding - (s.inference.avgTime / maxTime) * (height - 2 * padding);\n        return `${x},${y}`;\n    });\n    const linePath = `M ${points.join(' L ')}`;\n    const areaPath = `M ${padding},${height - padding} L ${points.join(' L ')} L ${width - padding},${height - padding} Z`;\n    // Grid lines\n    const gridLines = [];\n    for (let i = 0; i <= 4; i++) {\n        const y = padding + (i / 4) * (height - 2 * padding);\n        gridLines.push(`<line class=\"chart-grid\" x1=\"${padding}\" y1=\"${y}\" x2=\"${width - padding}\" y2=\"${y}\"/>`);\n    }\n    return `\n    ${gridLines.join('\\n')}\n    <path class=\"chart-area\" d=\"${areaPath}\"/>\n    <path class=\"chart-line\" d=\"${linePath}\"/>\n  `;\n}\n/**\n * Generate ASCII dashboard\n */\nexport function generateAsciiDashboard(monitor) {\n    const summary = monitor.getSummary();\n    const samples = monitor.getSamples();\n    const lastSample = samples[samples.length - 1];\n    const formatBytes = (bytes) => {\n        if (bytes < 1024)\n            return `${bytes} B`;\n        if (bytes < 1024 * 1024)\n            return `${(bytes / 1024).toFixed(1)} KB`;\n        if (bytes < 1024 * 1024 * 1024)\n            return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n        return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n    };\n    const bar = (value, max, width = 20) => {\n        const filled = Math.round((value / max) * width);\n        return '█'.repeat(filled) + '░'.repeat(width - filled);\n    };\n    const lines = [\n        '╔══════════════════════════════════════════════════════════════════════════╗',\n        '║             edgeFlow.js Performance Monitor Dashboard                   ║',\n        '╠══════════════════════════════════════════════════════════════════════════╣',\n        '║                                                                          ║',\n        `║  Total Inferences:  ${summary.totalInferences.toString().padStart(10)}                                      ║`,\n        `║  Avg Inference:     ${summary.avgInferenceTime.toFixed(2).padStart(10)}ms                                     ║`,\n        `║  Throughput:        ${summary.avgThroughput.toFixed(2).padStart(10)} ops/s                                 ║`,\n        `║  Avg FPS:           ${Math.round(summary.avgFPS).toString().padStart(10)}                                      ║`,\n        '║                                                                          ║',\n        '╟──────────────────────────────────────────────────────────────────────────╢',\n        '║ Memory Usage                                                             ║',\n        `║  Heap:    ${bar(summary.avgMemoryUsage, 1)} ${(summary.avgMemoryUsage * 100).toFixed(0).padStart(3)}%            ║`,\n        `║  Used:    ${formatBytes(lastSample?.memory.usedHeap ?? 0).padStart(10)}                                          ║`,\n        `║  Tensor:  ${formatBytes(lastSample?.memory.tensorMemory ?? 0).padStart(10)}                                          ║`,\n        `║  Cache:   ${formatBytes(lastSample?.memory.cacheMemory ?? 0).padStart(10)}                                          ║`,\n        '║                                                                          ║',\n        '╟──────────────────────────────────────────────────────────────────────────╢',\n        '║ Inference Time History (last 30 samples)                                 ║',\n        '║                                                                          ║',\n    ];\n    // Add mini chart\n    const recentSamples = samples.slice(-30);\n    if (recentSamples.length > 0) {\n        const times = recentSamples.map(s => s.inference.avgTime);\n        const maxTime = Math.max(...times, 1);\n        const chartHeight = 5;\n        for (let row = chartHeight; row > 0; row--) {\n            let line = '║  ';\n            for (const time of times) {\n                const height = Math.ceil((time / maxTime) * chartHeight);\n                line += height >= row ? '▓' : ' ';\n            }\n            lines.push(line.padEnd(76) + '║');\n        }\n        lines.push('║  ' + '─'.repeat(30) + '                                            ║');\n    }\n    lines.push('║                                                                          ║');\n    lines.push(`║  Last updated: ${new Date().toLocaleString().padEnd(40)}             ║`);\n    lines.push('╚══════════════════════════════════════════════════════════════════════════╝');\n    return lines.join('\\n');\n}\n// ============================================================================\n// Global Instance\n// ============================================================================\nlet globalMonitor = null;\n/**\n * Get or create global monitor\n */\nexport function getMonitor(config) {\n    if (!globalMonitor || config) {\n        globalMonitor = new PerformanceMonitor(config);\n    }\n    return globalMonitor;\n}\n/**\n * Start monitoring\n */\nexport function startMonitoring(config) {\n    const monitor = getMonitor(config);\n    monitor.start();\n    return monitor;\n}\n/**\n * Stop monitoring\n */\nexport function stopMonitoring() {\n    globalMonitor?.stop();\n}\n// ============================================================================\n// Exports\n// ============================================================================\nexport default {\n    PerformanceMonitor,\n    getMonitor,\n    startMonitoring,\n    stopMonitoring,\n    generateDashboardHTML,\n    generateAsciiDashboard,\n};\n//# sourceMappingURL=monitor.js.map"
  },
  {
    "path": "dist/tools/quantization.d.ts",
    "content": "/**\n * edgeFlow.js - Model Compression & Quantization Tools\n *\n * In-browser model quantization and compression utilities.\n * Supports dynamic quantization (no calibration data needed).\n */\nimport { EdgeFlowTensor } from '../core/index.js';\n/**\n * Quantization type\n */\nexport type QuantizationType = 'int8' | 'uint8' | 'int4' | 'float16' | 'dynamic';\n/**\n * Quantization options\n */\nexport interface QuantizationOptions {\n    /** Quantization type */\n    type: QuantizationType;\n    /** Layers/ops to skip quantization (by name pattern) */\n    skipPatterns?: (string | RegExp)[];\n    /** Per-channel quantization (more accurate, larger model) */\n    perChannel?: boolean;\n    /** Symmetric quantization (simpler, slightly less accurate) */\n    symmetric?: boolean;\n    /** Progress callback */\n    onProgress?: (progress: QuantizationProgress) => void;\n    /** Minimum tensor size to quantize (in elements) */\n    minTensorSize?: number;\n    /** Keep original weights for comparison */\n    keepOriginal?: boolean;\n}\n/**\n * Quantization progress\n */\nexport interface QuantizationProgress {\n    stage: 'analyzing' | 'quantizing' | 'packing' | 'complete';\n    current: number;\n    total: number;\n    percent: number;\n    layerName?: string;\n}\n/**\n * Quantization result\n */\nexport interface QuantizationResult {\n    /** Quantized model data */\n    data: ArrayBuffer;\n    /** Original model size in bytes */\n    originalSize: number;\n    /** Quantized model size in bytes */\n    quantizedSize: number;\n    /** Compression ratio */\n    compressionRatio: number;\n    /** Number of tensors quantized */\n    tensorsQuantized: number;\n    /** Number of tensors skipped */\n    tensorsSkipped: number;\n    /** Quantization statistics per layer */\n    layerStats: LayerQuantizationStats[];\n    /** Overall statistics */\n    stats: QuantizationStats;\n}\n/**\n * Layer quantization statistics\n */\nexport interface LayerQuantizationStats {\n    name: string;\n    originalDtype: string;\n    quantizedDtype: string;\n    originalSize: number;\n    quantizedSize: number;\n    scale: number | number[];\n    zeroPoint: number | number[];\n    minValue: number;\n    maxValue: number;\n    skipped: boolean;\n    skipReason?: string;\n}\n/**\n * Overall quantization statistics\n */\nexport interface QuantizationStats {\n    totalParameters: number;\n    quantizedParameters: number;\n    averageScale: number;\n    minScale: number;\n    maxScale: number;\n    errorEstimate: number;\n}\n/**\n * Dequantize int8 data back to float32\n */\nexport declare function dequantizeInt8(data: Int8Array, scale: number | Float32Array, zeroPoint: number | Int32Array, perChannel?: boolean, channelSize?: number): Float32Array;\n/**\n * Dequantize uint8 data back to float32\n */\nexport declare function dequantizeUint8(data: Uint8Array, scale: number | Float32Array, zeroPoint: number | Int32Array, perChannel?: boolean, channelSize?: number): Float32Array;\n/**\n * Convert float16 bits back to float32\n */\nexport declare function float16ToFloat32(value: number): number;\n/**\n * Dequantize float16 data back to float32\n */\nexport declare function dequantizeFloat16(data: Uint16Array): Float32Array;\n/**\n * Quantize a model\n */\nexport declare function quantizeModel(modelData: ArrayBuffer, options: QuantizationOptions): Promise<QuantizationResult>;\n/**\n * Quantize a single EdgeFlowTensor\n */\nexport declare function quantizeTensor(tensor: EdgeFlowTensor, type: QuantizationType, options?: {\n    symmetric?: boolean;\n    perChannel?: boolean;\n}): {\n    tensor: EdgeFlowTensor;\n    scale: number | number[];\n    zeroPoint: number | number[];\n};\n/**\n * Dequantize a tensor back to float32\n */\nexport declare function dequantizeTensor(tensor: EdgeFlowTensor, scale: number | number[], zeroPoint: number | number[], type: QuantizationType): EdgeFlowTensor;\n/**\n * Pruning options\n */\nexport interface PruningOptions {\n    /** Pruning ratio (0-1, default: 0.5 = 50% sparsity) */\n    ratio?: number;\n    /** Pruning method */\n    method?: 'magnitude' | 'random' | 'structured';\n    /** For structured pruning: dimension to prune along */\n    dim?: number;\n    /** Minimum absolute value to keep */\n    threshold?: number;\n    /** Progress callback */\n    onProgress?: (progress: {\n        current: number;\n        total: number;\n        percent: number;\n    }) => void;\n}\n/**\n * Pruning result\n */\nexport interface PruningResult {\n    /** Pruned model data */\n    data: ArrayBuffer;\n    /** Original size */\n    originalSize: number;\n    /** Pruned size (sparse representation) */\n    prunedSize: number;\n    /** Sparsity ratio achieved */\n    sparsity: number;\n    /** Number of parameters pruned */\n    parametersPruned: number;\n    /** Total parameters */\n    totalParameters: number;\n}\n/**\n * Prune a tensor using magnitude-based pruning\n */\nexport declare function pruneTensor(tensor: EdgeFlowTensor, options?: PruningOptions): {\n    tensor: EdgeFlowTensor;\n    mask: EdgeFlowTensor;\n    sparsity: number;\n};\n/**\n * Prune a model\n */\nexport declare function pruneModel(modelData: ArrayBuffer, options?: PruningOptions): Promise<PruningResult>;\n/**\n * Model analysis result\n */\nexport interface ModelAnalysis {\n    /** Total model size in bytes */\n    totalSize: number;\n    /** Number of tensors */\n    tensorCount: number;\n    /** Total number of parameters */\n    totalParameters: number;\n    /** Parameter breakdown by dtype */\n    dtypeBreakdown: Record<string, {\n        count: number;\n        size: number;\n    }>;\n    /** Largest tensors */\n    largestTensors: Array<{\n        name: string;\n        size: number;\n        shape: number[];\n    }>;\n    /** Estimated memory usage at runtime */\n    estimatedMemory: number;\n    /** Recommended quantization type */\n    recommendedQuantization: QuantizationType;\n    /** Estimated size after quantization */\n    estimatedQuantizedSizes: Record<QuantizationType, number>;\n}\n/**\n * Analyze a model\n */\nexport declare function analyzeModel(modelData: ArrayBuffer): Promise<ModelAnalysis>;\n/**\n * Export format\n */\nexport type ExportFormat = 'onnx' | 'tflite' | 'edgeflow';\n/**\n * Export options\n */\nexport interface ExportOptions {\n    format: ExportFormat;\n    optimize?: boolean;\n    quantize?: QuantizationType;\n}\n/**\n * Export a model to different formats\n * Note: This is a placeholder - real implementation would require proper format conversion\n */\nexport declare function exportModel(modelData: ArrayBuffer, options: ExportOptions): Promise<ArrayBuffer>;\ndeclare const _default: {\n    quantizeModel: typeof quantizeModel;\n    quantizeTensor: typeof quantizeTensor;\n    dequantizeTensor: typeof dequantizeTensor;\n    pruneModel: typeof pruneModel;\n    pruneTensor: typeof pruneTensor;\n    analyzeModel: typeof analyzeModel;\n    exportModel: typeof exportModel;\n    dequantizeInt8: typeof dequantizeInt8;\n    dequantizeUint8: typeof dequantizeUint8;\n    dequantizeFloat16: typeof dequantizeFloat16;\n    float16ToFloat32: typeof float16ToFloat32;\n};\nexport default _default;\n//# sourceMappingURL=quantization.d.ts.map"
  },
  {
    "path": "dist/tools/quantization.js",
    "content": "/**\n * edgeFlow.js - Model Compression & Quantization Tools\n *\n * In-browser model quantization and compression utilities.\n * Supports dynamic quantization (no calibration data needed).\n */\nimport { EdgeFlowTensor } from '../core/index.js';\n// ============================================================================\n// Quantization Core\n// ============================================================================\n/**\n * Calculate quantization parameters for a tensor\n */\nfunction calculateQuantParams(data, bits, symmetric, perChannel, channelAxis = 0, shape = []) {\n    const qmin = symmetric ? -(1 << (bits - 1)) : 0;\n    const qmax = symmetric ? (1 << (bits - 1)) - 1 : (1 << bits) - 1;\n    if (perChannel && shape.length > 1) {\n        // Per-channel quantization\n        const numChannels = shape[channelAxis] ?? 1;\n        const scales = new Float32Array(numChannels);\n        const zeroPoints = new Int32Array(numChannels);\n        const channelSize = data.length / numChannels;\n        let globalMin = Infinity;\n        let globalMax = -Infinity;\n        for (let c = 0; c < numChannels; c++) {\n            let min = Infinity;\n            let max = -Infinity;\n            for (let i = 0; i < channelSize; i++) {\n                const idx = c * channelSize + i;\n                const val = data[idx] ?? 0;\n                min = Math.min(min, val);\n                max = Math.max(max, val);\n            }\n            globalMin = Math.min(globalMin, min);\n            globalMax = Math.max(globalMax, max);\n            if (symmetric) {\n                const absMax = Math.max(Math.abs(min), Math.abs(max));\n                scales[c] = absMax / qmax;\n                zeroPoints[c] = 0;\n            }\n            else {\n                scales[c] = (max - min) / (qmax - qmin);\n                zeroPoints[c] = Math.round(qmin - min / (scales[c] || 1));\n            }\n            // Avoid division by zero\n            if (scales[c] === 0)\n                scales[c] = 1;\n        }\n        return { scale: scales, zeroPoint: zeroPoints, min: globalMin, max: globalMax };\n    }\n    else {\n        // Per-tensor quantization\n        let min = Infinity;\n        let max = -Infinity;\n        for (let i = 0; i < data.length; i++) {\n            const val = data[i] ?? 0;\n            min = Math.min(min, val);\n            max = Math.max(max, val);\n        }\n        let scale;\n        let zeroPoint;\n        if (symmetric) {\n            const absMax = Math.max(Math.abs(min), Math.abs(max));\n            scale = absMax / qmax;\n            zeroPoint = 0;\n        }\n        else {\n            scale = (max - min) / (qmax - qmin);\n            zeroPoint = Math.round(qmin - min / (scale || 1));\n        }\n        // Avoid division by zero\n        if (scale === 0)\n            scale = 1;\n        return { scale, zeroPoint, min, max };\n    }\n}\n/**\n * Quantize float32 data to int8\n */\nfunction quantizeToInt8(data, scale, zeroPoint, perChannel, channelSize = data.length) {\n    const result = new Int8Array(data.length);\n    if (perChannel && scale instanceof Float32Array) {\n        const numChannels = scale.length;\n        for (let c = 0; c < numChannels; c++) {\n            const s = scale[c] ?? 1;\n            const zp = zeroPoint[c] ?? 0;\n            for (let i = 0; i < channelSize; i++) {\n                const idx = c * channelSize + i;\n                const val = data[idx] ?? 0;\n                result[idx] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n            }\n        }\n    }\n    else {\n        const s = scale;\n        const zp = zeroPoint;\n        for (let i = 0; i < data.length; i++) {\n            const val = data[i] ?? 0;\n            result[i] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n        }\n    }\n    return result;\n}\n/**\n * Quantize float32 data to uint8\n */\nfunction quantizeToUint8(data, scale, zeroPoint, perChannel, channelSize = data.length) {\n    const result = new Uint8Array(data.length);\n    if (perChannel && scale instanceof Float32Array) {\n        const numChannels = scale.length;\n        for (let c = 0; c < numChannels; c++) {\n            const s = scale[c] ?? 1;\n            const zp = zeroPoint[c] ?? 0;\n            for (let i = 0; i < channelSize; i++) {\n                const idx = c * channelSize + i;\n                const val = data[idx] ?? 0;\n                result[idx] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n            }\n        }\n    }\n    else {\n        const s = scale;\n        const zp = zeroPoint;\n        for (let i = 0; i < data.length; i++) {\n            const val = data[i] ?? 0;\n            result[i] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n        }\n    }\n    return result;\n}\n/**\n * Quantize float32 data to int4 (packed as uint8, 2 values per byte)\n */\nfunction quantizeToInt4(data, scale, zeroPoint) {\n    const packedLength = Math.ceil(data.length / 2);\n    const result = new Uint8Array(packedLength);\n    for (let i = 0; i < data.length; i += 2) {\n        const val1 = data[i] ?? 0;\n        const val2 = data[i + 1] ?? 0;\n        // Quantize to range [-8, 7] then shift to [0, 15]\n        const q1 = Math.max(0, Math.min(15, Math.round(val1 / scale + zeroPoint + 8)));\n        const q2 = Math.max(0, Math.min(15, Math.round(val2 / scale + zeroPoint + 8)));\n        // Pack two 4-bit values into one byte\n        result[i >> 1] = (q1 << 4) | q2;\n    }\n    return result;\n}\n/**\n * Convert float32 to float16 (stored in Uint16Array)\n */\nfunction quantizeToFloat16(data) {\n    const result = new Uint16Array(data.length);\n    for (let i = 0; i < data.length; i++) {\n        result[i] = float32ToFloat16(data[i] ?? 0);\n    }\n    return result;\n}\n/**\n * Convert a single float32 value to float16 bits\n */\nfunction float32ToFloat16(value) {\n    const float32View = new Float32Array(1);\n    const int32View = new Int32Array(float32View.buffer);\n    float32View[0] = value;\n    const f = int32View[0];\n    const sign = (f >> 16) & 0x8000;\n    const exponent = ((f >> 23) & 0xff) - 127 + 15;\n    const mantissa = f & 0x7fffff;\n    if (exponent <= 0) {\n        // Denormalized or zero\n        if (exponent < -10) {\n            return sign;\n        }\n        const m = (mantissa | 0x800000) >> (1 - exponent);\n        return sign | (m >> 13);\n    }\n    else if (exponent >= 31) {\n        // Overflow to infinity\n        return sign | 0x7c00;\n    }\n    return sign | (exponent << 10) | (mantissa >> 13);\n}\n/**\n * Dequantize int8 data back to float32\n */\nexport function dequantizeInt8(data, scale, zeroPoint, perChannel = false, channelSize = data.length) {\n    const result = new Float32Array(data.length);\n    if (perChannel && scale instanceof Float32Array) {\n        const numChannels = scale.length;\n        for (let c = 0; c < numChannels; c++) {\n            const s = scale[c] ?? 1;\n            const zp = zeroPoint[c] ?? 0;\n            for (let i = 0; i < channelSize; i++) {\n                const idx = c * channelSize + i;\n                result[idx] = ((data[idx] ?? 0) - zp) * s;\n            }\n        }\n    }\n    else {\n        const s = scale;\n        const zp = zeroPoint;\n        for (let i = 0; i < data.length; i++) {\n            result[i] = ((data[i] ?? 0) - zp) * s;\n        }\n    }\n    return result;\n}\n/**\n * Dequantize uint8 data back to float32\n */\nexport function dequantizeUint8(data, scale, zeroPoint, perChannel = false, channelSize = data.length) {\n    const result = new Float32Array(data.length);\n    if (perChannel && scale instanceof Float32Array) {\n        const numChannels = scale.length;\n        for (let c = 0; c < numChannels; c++) {\n            const s = scale[c] ?? 1;\n            const zp = zeroPoint[c] ?? 0;\n            for (let i = 0; i < channelSize; i++) {\n                const idx = c * channelSize + i;\n                result[idx] = ((data[idx] ?? 0) - zp) * s;\n            }\n        }\n    }\n    else {\n        const s = scale;\n        const zp = zeroPoint;\n        for (let i = 0; i < data.length; i++) {\n            result[i] = ((data[i] ?? 0) - zp) * s;\n        }\n    }\n    return result;\n}\n/**\n * Convert float16 bits back to float32\n */\nexport function float16ToFloat32(value) {\n    const sign = (value & 0x8000) >> 15;\n    const exponent = (value & 0x7c00) >> 10;\n    const mantissa = value & 0x03ff;\n    if (exponent === 0) {\n        if (mantissa === 0) {\n            return sign === 0 ? 0 : -0;\n        }\n        // Denormalized\n        return (sign === 0 ? 1 : -1) * Math.pow(2, -14) * (mantissa / 1024);\n    }\n    else if (exponent === 31) {\n        if (mantissa === 0) {\n            return sign === 0 ? Infinity : -Infinity;\n        }\n        return NaN;\n    }\n    return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + mantissa / 1024);\n}\n/**\n * Dequantize float16 data back to float32\n */\nexport function dequantizeFloat16(data) {\n    const result = new Float32Array(data.length);\n    for (let i = 0; i < data.length; i++) {\n        result[i] = float16ToFloat32(data[i] ?? 0);\n    }\n    return result;\n}\n/**\n * Parse ONNX model to extract weights\n * Note: This is a simplified parser for demonstration\n */\nfunction parseModelWeights(modelData) {\n    // Check if it's an ONNX model by magic number\n    // const view = new DataView(modelData); // Reserved for future ONNX header parsing\n    const weights = [];\n    // Simple heuristic: look for float32 arrays in the buffer\n    // In a real implementation, we'd use proper ONNX parsing\n    const float32Array = new Float32Array(modelData);\n    // Create a single weight tensor from the model data\n    // This is a placeholder - real implementation would parse ONNX properly\n    weights.push({\n        name: 'model_weights',\n        data: float32Array,\n        shape: [float32Array.length],\n        dtype: 'float32',\n    });\n    return weights;\n}\n/**\n * Serialize quantized model to ArrayBuffer\n */\nfunction serializeQuantizedModel(model) {\n    // Create a simple binary format:\n    // Header: version (4 bytes) + type (4 bytes) + originalSize (8 bytes) + numWeights (4 bytes)\n    // For each weight: nameLen (4) + name + shapeLen (4) + shape + dtypeLen (4) + dtype + \n    //                  origDtypeLen (4) + origDtype + hasScale (1) + scale + hasZP (1) + zp + dataLen (8) + data\n    const encoder = new TextEncoder();\n    // Calculate total size\n    let totalSize = 20; // Header\n    for (const weight of model.weights) {\n        const nameBytes = encoder.encode(weight.name);\n        const dtypeBytes = encoder.encode(weight.dtype);\n        const origDtypeBytes = encoder.encode(weight.originalDtype);\n        totalSize += 4 + nameBytes.length; // name\n        totalSize += 4 + weight.shape.length * 4; // shape\n        totalSize += 4 + dtypeBytes.length; // dtype\n        totalSize += 4 + origDtypeBytes.length; // originalDtype\n        totalSize += 1; // hasScale\n        if (weight.scale !== undefined) {\n            totalSize += Array.isArray(weight.scale) ? 4 + weight.scale.length * 4 : 4;\n        }\n        totalSize += 1; // hasZeroPoint\n        if (weight.zeroPoint !== undefined) {\n            totalSize += Array.isArray(weight.zeroPoint) ? 4 + weight.zeroPoint.length * 4 : 4;\n        }\n        totalSize += 8 + weight.data.byteLength; // data\n    }\n    const buffer = new ArrayBuffer(totalSize);\n    const view = new DataView(buffer);\n    const uint8 = new Uint8Array(buffer);\n    let offset = 0;\n    // Write header\n    view.setUint32(offset, model.version, true);\n    offset += 4;\n    view.setUint32(offset, ['int8', 'uint8', 'int4', 'float16', 'dynamic'].indexOf(model.quantizationType), true);\n    offset += 4;\n    // Write originalSize as two 32-bit integers (for 64-bit compatibility)\n    view.setUint32(offset, model.originalSize & 0xFFFFFFFF, true);\n    offset += 4;\n    view.setUint32(offset, (model.originalSize / 0x100000000) >>> 0, true);\n    offset += 4;\n    view.setUint32(offset, model.weights.length, true);\n    offset += 4;\n    // Write weights\n    for (const weight of model.weights) {\n        const nameBytes = encoder.encode(weight.name);\n        const dtypeBytes = encoder.encode(weight.dtype);\n        const origDtypeBytes = encoder.encode(weight.originalDtype);\n        // Name\n        view.setUint32(offset, nameBytes.length, true);\n        offset += 4;\n        uint8.set(nameBytes, offset);\n        offset += nameBytes.length;\n        // Shape\n        view.setUint32(offset, weight.shape.length, true);\n        offset += 4;\n        for (const dim of weight.shape) {\n            view.setInt32(offset, dim, true);\n            offset += 4;\n        }\n        // Dtype\n        view.setUint32(offset, dtypeBytes.length, true);\n        offset += 4;\n        uint8.set(dtypeBytes, offset);\n        offset += dtypeBytes.length;\n        // Original dtype\n        view.setUint32(offset, origDtypeBytes.length, true);\n        offset += 4;\n        uint8.set(origDtypeBytes, offset);\n        offset += origDtypeBytes.length;\n        // Scale\n        if (weight.scale !== undefined) {\n            view.setUint8(offset, 1);\n            offset += 1;\n            if (Array.isArray(weight.scale)) {\n                view.setUint32(offset, weight.scale.length, true);\n                offset += 4;\n                for (const s of weight.scale) {\n                    view.setFloat32(offset, s, true);\n                    offset += 4;\n                }\n            }\n            else {\n                view.setUint32(offset, 1, true);\n                offset += 4;\n                view.setFloat32(offset, weight.scale, true);\n                offset += 4;\n            }\n        }\n        else {\n            view.setUint8(offset, 0);\n            offset += 1;\n        }\n        // Zero point\n        if (weight.zeroPoint !== undefined) {\n            view.setUint8(offset, 1);\n            offset += 1;\n            if (Array.isArray(weight.zeroPoint)) {\n                view.setUint32(offset, weight.zeroPoint.length, true);\n                offset += 4;\n                for (const zp of weight.zeroPoint) {\n                    view.setInt32(offset, zp, true);\n                    offset += 4;\n                }\n            }\n            else {\n                view.setUint32(offset, 1, true);\n                offset += 4;\n                view.setInt32(offset, weight.zeroPoint, true);\n                offset += 4;\n            }\n        }\n        else {\n            view.setUint8(offset, 0);\n            offset += 1;\n        }\n        // Data\n        const dataLow = weight.data.byteLength & 0xFFFFFFFF;\n        const dataHigh = (weight.data.byteLength / 0x100000000) >>> 0;\n        view.setUint32(offset, dataLow, true);\n        offset += 4;\n        view.setUint32(offset, dataHigh, true);\n        offset += 4;\n        uint8.set(new Uint8Array(weight.data), offset);\n        offset += weight.data.byteLength;\n    }\n    return buffer;\n}\n/**\n * Quantize a model\n */\nexport async function quantizeModel(modelData, options) {\n    const { type, skipPatterns = [], perChannel = false, symmetric = true, onProgress, minTensorSize = 100, } = options;\n    const originalSize = modelData.byteLength;\n    const layerStats = [];\n    let tensorsQuantized = 0;\n    let tensorsSkipped = 0;\n    // Parse model weights\n    onProgress?.({ stage: 'analyzing', current: 0, total: 1, percent: 0 });\n    const weights = parseModelWeights(modelData);\n    const quantizedWeights = [];\n    let totalParams = 0;\n    let quantizedParams = 0;\n    const scales = [];\n    // Quantize each weight tensor\n    for (let i = 0; i < weights.length; i++) {\n        const weight = weights[i];\n        const percent = ((i + 1) / weights.length) * 100;\n        onProgress?.({\n            stage: 'quantizing',\n            current: i + 1,\n            total: weights.length,\n            percent,\n            layerName: weight.name,\n        });\n        totalParams += weight.data.length;\n        // Check if should skip\n        const shouldSkip = weight.data.length < minTensorSize ||\n            skipPatterns.some(pattern => {\n                if (typeof pattern === 'string') {\n                    return weight.name.includes(pattern);\n                }\n                return pattern.test(weight.name);\n            });\n        if (shouldSkip) {\n            tensorsSkipped++;\n            layerStats.push({\n                name: weight.name,\n                originalDtype: weight.dtype,\n                quantizedDtype: weight.dtype,\n                originalSize: weight.data.byteLength,\n                quantizedSize: weight.data.byteLength,\n                scale: 1,\n                zeroPoint: 0,\n                minValue: Math.min(...weight.data),\n                maxValue: Math.max(...weight.data),\n                skipped: true,\n                skipReason: weight.data.length < minTensorSize\n                    ? 'Tensor too small'\n                    : 'Matched skip pattern',\n            });\n            quantizedWeights.push({\n                name: weight.name,\n                data: weight.data.buffer.slice(0),\n                shape: weight.shape,\n                dtype: weight.dtype,\n                originalDtype: weight.dtype,\n            });\n            continue;\n        }\n        // Calculate quantization parameters\n        const bits = type === 'int4' ? 4 : 8;\n        const params = calculateQuantParams(weight.data, bits, symmetric, perChannel, 0, weight.shape);\n        // Quantize data\n        let quantizedData;\n        let quantizedDtype;\n        switch (type) {\n            case 'int8':\n                const int8Data = quantizeToInt8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n                quantizedData = int8Data.buffer.slice(0);\n                quantizedDtype = 'int8';\n                break;\n            case 'uint8':\n                const uint8Data = quantizeToUint8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n                quantizedData = uint8Data.buffer.slice(0);\n                quantizedDtype = 'uint8';\n                break;\n            case 'int4':\n                const int4Data = quantizeToInt4(weight.data, params.scale, params.zeroPoint);\n                quantizedData = int4Data.buffer.slice(0);\n                quantizedDtype = 'int4';\n                break;\n            case 'float16':\n                const fp16Data = quantizeToFloat16(weight.data);\n                quantizedData = fp16Data.buffer.slice(0);\n                quantizedDtype = 'float16';\n                break;\n            case 'dynamic':\n            default:\n                // Dynamic quantization: use int8 for weights\n                const dynData = quantizeToInt8(weight.data, params.scale, params.zeroPoint, perChannel, perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length);\n                quantizedData = dynData.buffer.slice(0);\n                quantizedDtype = 'int8';\n                break;\n        }\n        tensorsQuantized++;\n        quantizedParams += weight.data.length;\n        const scaleValue = params.scale instanceof Float32Array\n            ? Array.from(params.scale)\n            : params.scale;\n        const zpValue = params.zeroPoint instanceof Int32Array\n            ? Array.from(params.zeroPoint)\n            : params.zeroPoint;\n        if (typeof scaleValue === 'number') {\n            scales.push(scaleValue);\n        }\n        else {\n            scales.push(...scaleValue);\n        }\n        layerStats.push({\n            name: weight.name,\n            originalDtype: weight.dtype,\n            quantizedDtype,\n            originalSize: weight.data.byteLength,\n            quantizedSize: quantizedData.byteLength,\n            scale: scaleValue,\n            zeroPoint: zpValue,\n            minValue: params.min,\n            maxValue: params.max,\n            skipped: false,\n        });\n        quantizedWeights.push({\n            name: weight.name,\n            data: quantizedData,\n            shape: weight.shape,\n            dtype: quantizedDtype,\n            originalDtype: weight.dtype,\n            scale: scaleValue,\n            zeroPoint: zpValue,\n        });\n    }\n    // Pack into final format\n    onProgress?.({ stage: 'packing', current: 0, total: 1, percent: 0 });\n    const quantizedModel = {\n        version: 1,\n        quantizationType: type,\n        originalSize,\n        weights: quantizedWeights,\n    };\n    const quantizedData = serializeQuantizedModel(quantizedModel);\n    onProgress?.({ stage: 'complete', current: 1, total: 1, percent: 100 });\n    // Calculate statistics\n    const avgScale = scales.length > 0\n        ? scales.reduce((a, b) => a + b, 0) / scales.length\n        : 1;\n    const minScale = scales.length > 0 ? Math.min(...scales) : 1;\n    const maxScale = scales.length > 0 ? Math.max(...scales) : 1;\n    // Estimate quantization error (very rough approximation)\n    const bitsReduction = type === 'int4' ? 8 : type === 'float16' ? 2 : 4;\n    const errorEstimate = avgScale / bitsReduction;\n    return {\n        data: quantizedData,\n        originalSize,\n        quantizedSize: quantizedData.byteLength,\n        compressionRatio: originalSize / quantizedData.byteLength,\n        tensorsQuantized,\n        tensorsSkipped,\n        layerStats,\n        stats: {\n            totalParameters: totalParams,\n            quantizedParameters: quantizedParams,\n            averageScale: avgScale,\n            minScale,\n            maxScale,\n            errorEstimate,\n        },\n    };\n}\n// ============================================================================\n// Tensor Quantization (for individual tensors)\n// ============================================================================\n/**\n * Quantize a single EdgeFlowTensor\n */\nexport function quantizeTensor(tensor, type, options = {}) {\n    const { symmetric = true, perChannel = false } = options;\n    const data = tensor.toFloat32Array();\n    const shape = tensor.shape;\n    const bits = type === 'int4' ? 4 : 8;\n    const params = calculateQuantParams(data, bits, symmetric, perChannel, 0, shape);\n    let quantizedData;\n    let dtype;\n    switch (type) {\n        case 'int8':\n            quantizedData = quantizeToInt8(data, params.scale, params.zeroPoint, perChannel);\n            dtype = 'int32'; // Store as int32 since we don't have int8 dtype\n            break;\n        case 'uint8':\n            quantizedData = quantizeToUint8(data, params.scale, params.zeroPoint, perChannel);\n            dtype = 'int32';\n            break;\n        case 'float16':\n            quantizedData = quantizeToFloat16(data);\n            dtype = 'float32'; // Will be stored differently\n            break;\n        default:\n            quantizedData = quantizeToInt8(data, params.scale, params.zeroPoint, perChannel);\n            dtype = 'int32';\n    }\n    const scaleValue = params.scale instanceof Float32Array\n        ? Array.from(params.scale)\n        : params.scale;\n    const zpValue = params.zeroPoint instanceof Int32Array\n        ? Array.from(params.zeroPoint)\n        : params.zeroPoint;\n    return {\n        tensor: new EdgeFlowTensor(Array.from(quantizedData), shape, dtype),\n        scale: scaleValue,\n        zeroPoint: zpValue,\n    };\n}\n/**\n * Dequantize a tensor back to float32\n */\nexport function dequantizeTensor(tensor, scale, zeroPoint, type) {\n    const data = tensor.toArray();\n    const shape = tensor.shape;\n    let dequantizedData;\n    const scaleArr = Array.isArray(scale) ? new Float32Array(scale) : scale;\n    const zpArr = Array.isArray(zeroPoint) ? new Int32Array(zeroPoint) : zeroPoint;\n    const perChannel = Array.isArray(scale);\n    switch (type) {\n        case 'int8':\n            dequantizedData = dequantizeInt8(new Int8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n            break;\n        case 'uint8':\n            dequantizedData = dequantizeUint8(new Uint8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n            break;\n        case 'float16':\n            dequantizedData = dequantizeFloat16(new Uint16Array(data.map(Number)));\n            break;\n        default:\n            dequantizedData = dequantizeInt8(new Int8Array(data.map(Number)), scaleArr, zpArr, perChannel);\n    }\n    return new EdgeFlowTensor(Array.from(dequantizedData), shape, 'float32');\n}\n/**\n * Prune a tensor using magnitude-based pruning\n */\nexport function pruneTensor(tensor, options = {}) {\n    const { ratio = 0.5, method = 'magnitude', threshold } = options;\n    const data = tensor.toFloat32Array();\n    const shape = tensor.shape;\n    const mask = new Float32Array(data.length);\n    const prunedData = new Float32Array(data.length);\n    let prunedCount = 0;\n    if (method === 'magnitude') {\n        // Get threshold based on ratio\n        const absValues = Array.from(data).map(Math.abs).sort((a, b) => a - b);\n        const thresholdIndex = Math.floor(absValues.length * ratio);\n        const computedThreshold = threshold ?? (absValues[thresholdIndex] ?? 0);\n        for (let i = 0; i < data.length; i++) {\n            if (Math.abs(data[i] ?? 0) > computedThreshold) {\n                mask[i] = 1;\n                prunedData[i] = data[i] ?? 0;\n            }\n            else {\n                mask[i] = 0;\n                prunedData[i] = 0;\n                prunedCount++;\n            }\n        }\n    }\n    else if (method === 'random') {\n        for (let i = 0; i < data.length; i++) {\n            if (Math.random() > ratio) {\n                mask[i] = 1;\n                prunedData[i] = data[i] ?? 0;\n            }\n            else {\n                mask[i] = 0;\n                prunedData[i] = 0;\n                prunedCount++;\n            }\n        }\n    }\n    return {\n        tensor: new EdgeFlowTensor(Array.from(prunedData), shape, 'float32'),\n        mask: new EdgeFlowTensor(Array.from(mask), shape, 'float32'),\n        sparsity: prunedCount / data.length,\n    };\n}\n/**\n * Prune a model\n */\nexport async function pruneModel(modelData, options = {}) {\n    const { onProgress } = options;\n    onProgress?.({ current: 0, total: 1, percent: 0 });\n    // This is a simplified implementation\n    // Real implementation would parse the model properly\n    const weights = parseModelWeights(modelData);\n    let totalParams = 0;\n    let prunedParams = 0;\n    for (const weight of weights) {\n        totalParams += weight.data.length;\n        const tensor = new EdgeFlowTensor(Array.from(weight.data), weight.shape, 'float32');\n        const { sparsity } = pruneTensor(tensor, options);\n        prunedParams += Math.floor(weight.data.length * sparsity);\n    }\n    onProgress?.({ current: 1, total: 1, percent: 100 });\n    return {\n        data: modelData, // In a real implementation, we'd create a sparse format\n        originalSize: modelData.byteLength,\n        prunedSize: modelData.byteLength, // Would be smaller with sparse format\n        sparsity: prunedParams / totalParams,\n        parametersPruned: prunedParams,\n        totalParameters: totalParams,\n    };\n}\n/**\n * Analyze a model\n */\nexport async function analyzeModel(modelData) {\n    const weights = parseModelWeights(modelData);\n    const totalSize = modelData.byteLength;\n    const dtypeBreakdown = {};\n    let totalParams = 0;\n    const tensorInfos = [];\n    for (const weight of weights) {\n        totalParams += weight.data.length;\n        const bytesPerElement = weight.dtype === 'float32' ? 4\n            : weight.dtype === 'float16' ? 2\n                : weight.dtype === 'int8' ? 1\n                    : 4;\n        const size = weight.data.length * bytesPerElement;\n        if (!dtypeBreakdown[weight.dtype]) {\n            dtypeBreakdown[weight.dtype] = { count: 0, size: 0 };\n        }\n        dtypeBreakdown[weight.dtype].count++;\n        dtypeBreakdown[weight.dtype].size += size;\n        tensorInfos.push({\n            name: weight.name,\n            size,\n            shape: weight.shape,\n        });\n    }\n    // Sort by size and get top 10\n    tensorInfos.sort((a, b) => b.size - a.size);\n    const largestTensors = tensorInfos.slice(0, 10);\n    // Estimate quantized sizes\n    const estimatedQuantizedSizes = {\n        int8: Math.ceil(totalSize / 4),\n        uint8: Math.ceil(totalSize / 4),\n        int4: Math.ceil(totalSize / 8),\n        float16: Math.ceil(totalSize / 2),\n        dynamic: Math.ceil(totalSize / 4),\n    };\n    // Recommend quantization based on model size\n    let recommendedQuantization = 'dynamic';\n    if (totalSize > 500 * 1024 * 1024) {\n        recommendedQuantization = 'int4';\n    }\n    else if (totalSize > 100 * 1024 * 1024) {\n        recommendedQuantization = 'int8';\n    }\n    else if (totalSize > 50 * 1024 * 1024) {\n        recommendedQuantization = 'float16';\n    }\n    return {\n        totalSize,\n        tensorCount: weights.length,\n        totalParameters: totalParams,\n        dtypeBreakdown,\n        largestTensors,\n        estimatedMemory: totalParams * 4, // Assuming float32 at runtime\n        recommendedQuantization,\n        estimatedQuantizedSizes,\n    };\n}\n/**\n * Export a model to different formats\n * Note: This is a placeholder - real implementation would require proper format conversion\n */\nexport async function exportModel(modelData, options) {\n    const { format, quantize } = options;\n    // Apply quantization if requested\n    let data = modelData;\n    if (quantize) {\n        const result = await quantizeModel(modelData, { type: quantize });\n        data = result.data;\n    }\n    // Format conversion would happen here\n    // For now, we just return the (possibly quantized) data\n    switch (format) {\n        case 'edgeflow':\n            return data;\n        case 'onnx':\n            // Would convert to ONNX format\n            return data;\n        case 'tflite':\n            // Would convert to TFLite format\n            return data;\n        default:\n            return data;\n    }\n}\n// ============================================================================\n// Exports\n// ============================================================================\nexport default {\n    quantizeModel,\n    quantizeTensor,\n    dequantizeTensor,\n    pruneModel,\n    pruneTensor,\n    analyzeModel,\n    exportModel,\n    dequantizeInt8,\n    dequantizeUint8,\n    dequantizeFloat16,\n    float16ToFloat32,\n};\n//# sourceMappingURL=quantization.js.map"
  },
  {
    "path": "dist/utils/cache.d.ts",
    "content": "/**\n * edgeFlow.js - Caching Utilities\n *\n * Smart caching for models, tensors, and inference results.\n */\n/**\n * Cache strategy types\n */\nexport type CacheStrategy = 'lru' | 'lfu' | 'fifo' | 'ttl';\n/**\n * Cache options\n */\nexport interface CacheOptions {\n    /** Cache strategy */\n    strategy?: CacheStrategy;\n    /** Maximum cache size in bytes */\n    maxSize?: number;\n    /** Maximum number of entries */\n    maxEntries?: number;\n    /** Default TTL in milliseconds */\n    ttl?: number;\n    /** Enable persistence to IndexedDB */\n    persistent?: boolean;\n    /** Cache name for persistence */\n    name?: string;\n}\n/**\n * Cache statistics\n */\nexport interface CacheStats {\n    /** Number of entries */\n    entries: number;\n    /** Total size in bytes */\n    size: number;\n    /** Cache hits */\n    hits: number;\n    /** Cache misses */\n    misses: number;\n    /** Hit rate (0-1) */\n    hitRate: number;\n}\n/**\n * Cache - Generic cache implementation\n */\nexport declare class Cache<T> {\n    private readonly options;\n    private readonly cache;\n    private currentSize;\n    private hits;\n    private misses;\n    constructor(options?: CacheOptions);\n    /**\n     * Get value from cache\n     */\n    get(key: string): T | undefined;\n    /**\n     * Set value in cache\n     */\n    set(key: string, value: T, size: number, ttl?: number): void;\n    /**\n     * Check if key exists\n     */\n    has(key: string): boolean;\n    /**\n     * Delete entry\n     */\n    delete(key: string): boolean;\n    /**\n     * Clear the cache\n     */\n    clear(): void;\n    /**\n     * Get cache statistics\n     */\n    getStats(): CacheStats;\n    /**\n     * Evict an entry based on strategy\n     */\n    private evict;\n    /**\n     * Find least recently used entry\n     */\n    private findLRU;\n    /**\n     * Find least frequently used entry\n     */\n    private findLFU;\n    /**\n     * Find oldest entry (FIFO)\n     */\n    private findOldest;\n    /**\n     * Find expired entry\n     */\n    private findExpired;\n    /**\n     * Load cache from IndexedDB\n     */\n    private loadFromStorage;\n    /**\n     * Save cache to IndexedDB\n     */\n    private saveToStorage;\n    /**\n     * Clear IndexedDB storage\n     */\n    private clearStorage;\n    /**\n     * Open IndexedDB database\n     */\n    private openDB;\n}\n/**\n * InferenceCache - Cache for inference results\n */\nexport declare class InferenceCache extends Cache<Float32Array> {\n    /**\n     * Generate cache key from input\n     */\n    generateKey(modelId: string, input: Float32Array | number[]): string;\n    /**\n     * Simple hash function for arrays\n     */\n    private hashArray;\n}\n/**\n * Model download cache using Cache API\n */\nexport declare class ModelDownloadCache {\n    private readonly cacheName;\n    private cache;\n    constructor(cacheName?: string);\n    /**\n     * Initialize cache\n     */\n    private ensureCache;\n    /**\n     * Get cached response\n     */\n    get(url: string): Promise<Response | undefined>;\n    /**\n     * Store response in cache\n     */\n    put(url: string, response: Response): Promise<void>;\n    /**\n     * Delete cached response\n     */\n    delete(url: string): Promise<boolean>;\n    /**\n     * Clear all cached models\n     */\n    clear(): Promise<void>;\n    /**\n     * Get all cached URLs\n     */\n    keys(): Promise<string[]>;\n}\n/**\n * Create a cache with common presets\n */\nexport declare function createCache<T>(preset?: 'small' | 'medium' | 'large' | 'custom', options?: CacheOptions): Cache<T>;\n//# sourceMappingURL=cache.d.ts.map"
  },
  {
    "path": "dist/utils/cache.js",
    "content": "/**\n * edgeFlow.js - Caching Utilities\n *\n * Smart caching for models, tensors, and inference results.\n */\n// ============================================================================\n// Cache Implementation\n// ============================================================================\n/**\n * Cache - Generic cache implementation\n */\nexport class Cache {\n    options;\n    cache = new Map();\n    currentSize = 0;\n    hits = 0;\n    misses = 0;\n    constructor(options = {}) {\n        this.options = {\n            strategy: options.strategy ?? 'lru',\n            maxSize: options.maxSize ?? 100 * 1024 * 1024, // 100MB\n            maxEntries: options.maxEntries ?? 1000,\n            ttl: options.ttl ?? 0, // 0 = no TTL\n            persistent: options.persistent ?? false,\n            name: options.name ?? 'edgeflow-cache',\n        };\n        // Load from persistent storage if enabled\n        if (this.options.persistent) {\n            this.loadFromStorage();\n        }\n    }\n    /**\n     * Get value from cache\n     */\n    get(key) {\n        const entry = this.cache.get(key);\n        if (!entry) {\n            this.misses++;\n            return undefined;\n        }\n        // Check TTL\n        if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n            this.delete(key);\n            this.misses++;\n            return undefined;\n        }\n        // Update access stats\n        entry.accessedAt = Date.now();\n        entry.accessCount++;\n        this.hits++;\n        return entry.value;\n    }\n    /**\n     * Set value in cache\n     */\n    set(key, value, size, ttl) {\n        // Remove existing entry if present\n        if (this.cache.has(key)) {\n            this.delete(key);\n        }\n        // Evict entries if necessary\n        while ((this.currentSize + size > this.options.maxSize ||\n            this.cache.size >= this.options.maxEntries) &&\n            this.cache.size > 0) {\n            this.evict();\n        }\n        // Determine TTL value\n        const entryTtl = ttl !== undefined ? ttl : (this.options.ttl > 0 ? this.options.ttl : undefined);\n        // Add new entry\n        const entry = {\n            value,\n            size,\n            createdAt: Date.now(),\n            accessedAt: Date.now(),\n            accessCount: 1,\n            ttl: entryTtl,\n        };\n        this.cache.set(key, entry);\n        this.currentSize += size;\n        // Persist if enabled\n        if (this.options.persistent) {\n            this.saveToStorage();\n        }\n    }\n    /**\n     * Check if key exists\n     */\n    has(key) {\n        const entry = this.cache.get(key);\n        if (!entry)\n            return false;\n        // Check TTL\n        if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n            this.delete(key);\n            return false;\n        }\n        return true;\n    }\n    /**\n     * Delete entry\n     */\n    delete(key) {\n        const entry = this.cache.get(key);\n        if (entry) {\n            this.currentSize -= entry.size;\n            this.cache.delete(key);\n            if (this.options.persistent) {\n                this.saveToStorage();\n            }\n            return true;\n        }\n        return false;\n    }\n    /**\n     * Clear the cache\n     */\n    clear() {\n        this.cache.clear();\n        this.currentSize = 0;\n        this.hits = 0;\n        this.misses = 0;\n        if (this.options.persistent) {\n            this.clearStorage();\n        }\n    }\n    /**\n     * Get cache statistics\n     */\n    getStats() {\n        const total = this.hits + this.misses;\n        return {\n            entries: this.cache.size,\n            size: this.currentSize,\n            hits: this.hits,\n            misses: this.misses,\n            hitRate: total > 0 ? this.hits / total : 0,\n        };\n    }\n    /**\n     * Evict an entry based on strategy\n     */\n    evict() {\n        let keyToEvict = null;\n        switch (this.options.strategy) {\n            case 'lru':\n                keyToEvict = this.findLRU();\n                break;\n            case 'lfu':\n                keyToEvict = this.findLFU();\n                break;\n            case 'fifo':\n                keyToEvict = this.findOldest();\n                break;\n            case 'ttl':\n                keyToEvict = this.findExpired() ?? this.findOldest();\n                break;\n        }\n        if (keyToEvict) {\n            this.delete(keyToEvict);\n        }\n    }\n    /**\n     * Find least recently used entry\n     */\n    findLRU() {\n        let oldest = null;\n        let oldestTime = Infinity;\n        for (const [key, entry] of this.cache) {\n            if (entry.accessedAt < oldestTime) {\n                oldestTime = entry.accessedAt;\n                oldest = key;\n            }\n        }\n        return oldest;\n    }\n    /**\n     * Find least frequently used entry\n     */\n    findLFU() {\n        let lfu = null;\n        let minCount = Infinity;\n        for (const [key, entry] of this.cache) {\n            if (entry.accessCount < minCount) {\n                minCount = entry.accessCount;\n                lfu = key;\n            }\n        }\n        return lfu;\n    }\n    /**\n     * Find oldest entry (FIFO)\n     */\n    findOldest() {\n        let oldest = null;\n        let oldestTime = Infinity;\n        for (const [key, entry] of this.cache) {\n            if (entry.createdAt < oldestTime) {\n                oldestTime = entry.createdAt;\n                oldest = key;\n            }\n        }\n        return oldest;\n    }\n    /**\n     * Find expired entry\n     */\n    findExpired() {\n        const now = Date.now();\n        for (const [key, entry] of this.cache) {\n            if (entry.ttl && now - entry.createdAt > entry.ttl) {\n                return key;\n            }\n        }\n        return null;\n    }\n    /**\n     * Load cache from IndexedDB\n     */\n    async loadFromStorage() {\n        if (typeof indexedDB === 'undefined')\n            return;\n        try {\n            const db = await this.openDB();\n            const tx = db.transaction('cache', 'readonly');\n            const store = tx.objectStore('cache');\n            const request = store.getAll();\n            return new Promise((resolve, reject) => {\n                request.onsuccess = () => {\n                    const entries = request.result;\n                    for (const { key, entry } of entries) {\n                        this.cache.set(key, entry);\n                        this.currentSize += entry.size;\n                    }\n                    resolve();\n                };\n                request.onerror = () => reject(request.error);\n            });\n        }\n        catch {\n            // Ignore storage errors\n        }\n    }\n    /**\n     * Save cache to IndexedDB\n     */\n    async saveToStorage() {\n        if (typeof indexedDB === 'undefined')\n            return;\n        try {\n            const db = await this.openDB();\n            const tx = db.transaction('cache', 'readwrite');\n            const store = tx.objectStore('cache');\n            // Clear existing entries\n            store.clear();\n            // Add current entries\n            for (const [key, entry] of this.cache) {\n                store.put({ key, entry });\n            }\n            return new Promise((resolve, reject) => {\n                tx.oncomplete = () => resolve();\n                tx.onerror = () => reject(tx.error);\n            });\n        }\n        catch {\n            // Ignore storage errors\n        }\n    }\n    /**\n     * Clear IndexedDB storage\n     */\n    async clearStorage() {\n        if (typeof indexedDB === 'undefined')\n            return;\n        try {\n            const db = await this.openDB();\n            const tx = db.transaction('cache', 'readwrite');\n            const store = tx.objectStore('cache');\n            store.clear();\n        }\n        catch {\n            // Ignore storage errors\n        }\n    }\n    /**\n     * Open IndexedDB database\n     */\n    openDB() {\n        return new Promise((resolve, reject) => {\n            const request = indexedDB.open(this.options.name, 1);\n            request.onupgradeneeded = () => {\n                const db = request.result;\n                if (!db.objectStoreNames.contains('cache')) {\n                    db.createObjectStore('cache', { keyPath: 'key' });\n                }\n            };\n            request.onsuccess = () => resolve(request.result);\n            request.onerror = () => reject(request.error);\n        });\n    }\n}\n// ============================================================================\n// Inference Result Cache\n// ============================================================================\n/**\n * InferenceCache - Cache for inference results\n */\nexport class InferenceCache extends Cache {\n    /**\n     * Generate cache key from input\n     */\n    generateKey(modelId, input) {\n        // Create hash from input data\n        const inputArray = Array.isArray(input) ? input : Array.from(input);\n        const hash = this.hashArray(inputArray);\n        return `${modelId}:${hash}`;\n    }\n    /**\n     * Simple hash function for arrays\n     */\n    hashArray(arr) {\n        let hash = 0;\n        const sample = arr.length > 100\n            ? arr.filter((_, i) => i % Math.floor(arr.length / 100) === 0)\n            : arr;\n        for (let i = 0; i < sample.length; i++) {\n            const value = sample[i] ?? 0;\n            hash = ((hash << 5) - hash) + (value * 1000 | 0);\n            hash |= 0;\n        }\n        return hash.toString(36);\n    }\n}\n// ============================================================================\n// Model Cache\n// ============================================================================\n/**\n * Model download cache using Cache API\n */\nexport class ModelDownloadCache {\n    cacheName;\n    cache = null;\n    constructor(cacheName = 'edgeflow-models') {\n        this.cacheName = cacheName;\n    }\n    /**\n     * Initialize cache\n     */\n    async ensureCache() {\n        if (!this.cache) {\n            if (typeof caches === 'undefined') {\n                throw new Error('Cache API is not available');\n            }\n            this.cache = await caches.open(this.cacheName);\n        }\n        return this.cache;\n    }\n    /**\n     * Get cached response\n     */\n    async get(url) {\n        try {\n            const cache = await this.ensureCache();\n            return await cache.match(url) ?? undefined;\n        }\n        catch {\n            return undefined;\n        }\n    }\n    /**\n     * Store response in cache\n     */\n    async put(url, response) {\n        try {\n            const cache = await this.ensureCache();\n            await cache.put(url, response.clone());\n        }\n        catch {\n            // Ignore cache errors\n        }\n    }\n    /**\n     * Delete cached response\n     */\n    async delete(url) {\n        try {\n            const cache = await this.ensureCache();\n            return await cache.delete(url);\n        }\n        catch {\n            return false;\n        }\n    }\n    /**\n     * Clear all cached models\n     */\n    async clear() {\n        try {\n            await caches.delete(this.cacheName);\n            this.cache = null;\n        }\n        catch {\n            // Ignore cache errors\n        }\n    }\n    /**\n     * Get all cached URLs\n     */\n    async keys() {\n        try {\n            const cache = await this.ensureCache();\n            const requests = await cache.keys();\n            return requests.map(r => r.url);\n        }\n        catch {\n            return [];\n        }\n    }\n}\n// ============================================================================\n// Factory Functions\n// ============================================================================\n/**\n * Create a cache with common presets\n */\nexport function createCache(preset = 'medium', options = {}) {\n    const presets = {\n        small: {\n            maxSize: 10 * 1024 * 1024, // 10MB\n            maxEntries: 100,\n        },\n        medium: {\n            maxSize: 100 * 1024 * 1024, // 100MB\n            maxEntries: 500,\n        },\n        large: {\n            maxSize: 500 * 1024 * 1024, // 500MB\n            maxEntries: 2000,\n        },\n        custom: {},\n    };\n    return new Cache({ ...presets[preset], ...options });\n}\n//# sourceMappingURL=cache.js.map"
  },
  {
    "path": "dist/utils/hub.d.ts",
    "content": "/**\n * edgeFlow.js - Hugging Face Hub Integration\n *\n * Automatically download models, tokenizers, and configs from Hugging Face Hub.\n */\nimport { type DownloadProgress } from './model-loader.js';\nimport { Tokenizer } from './tokenizer.js';\n/**\n * Hub options\n */\nexport interface HubOptions {\n    /** HuggingFace API endpoint (default: https://huggingface.co) */\n    endpoint?: string;\n    /** Model revision/branch (default: main) */\n    revision?: string;\n    /** Subfolder within the repo */\n    subfolder?: string;\n    /** Enable caching */\n    cache?: boolean;\n    /** Force re-download */\n    forceDownload?: boolean;\n    /** Progress callback */\n    onProgress?: (progress: HubDownloadProgress) => void;\n    /** HuggingFace API token (for private repos) */\n    token?: string;\n}\n/**\n * Download progress for hub\n */\nexport interface HubDownloadProgress {\n    /** Current file being downloaded */\n    file: string;\n    /** File index (1-based) */\n    fileIndex: number;\n    /** Total files */\n    totalFiles: number;\n    /** File download progress */\n    fileProgress: DownloadProgress;\n    /** Overall progress (0-100) */\n    overallProgress: number;\n}\n/**\n * Model info from config.json\n */\nexport interface ModelConfig {\n    model_type?: string;\n    architectures?: string[];\n    hidden_size?: number;\n    num_attention_heads?: number;\n    num_hidden_layers?: number;\n    vocab_size?: number;\n    max_position_embeddings?: number;\n    type_vocab_size?: number;\n    id2label?: Record<string, string>;\n    label2id?: Record<string, number>;\n    [key: string]: unknown;\n}\n/**\n * Downloaded model bundle\n */\nexport interface ModelBundle {\n    /** Model ID */\n    modelId: string;\n    /** Model data (ArrayBuffer) */\n    modelData: ArrayBuffer;\n    /** Tokenizer instance */\n    tokenizer?: Tokenizer;\n    /** Model config */\n    config?: ModelConfig;\n    /** Model files info */\n    files: {\n        model?: string;\n        tokenizer?: string;\n        config?: string;\n    };\n}\n/**\n * Download a file from HuggingFace Hub\n */\nexport declare function downloadFile(modelId: string, filename: string, options?: HubOptions): Promise<ArrayBuffer>;\n/**\n * Download JSON file from HuggingFace Hub\n */\nexport declare function downloadJson<T = unknown>(modelId: string, filename: string, options?: HubOptions): Promise<T>;\n/**\n * Download tokenizer from HuggingFace Hub\n */\nexport declare function downloadTokenizer(modelId: string, options?: HubOptions): Promise<Tokenizer>;\n/**\n * Download model config from HuggingFace Hub\n */\nexport declare function downloadConfig(modelId: string, options?: HubOptions): Promise<ModelConfig>;\n/**\n * Download complete model bundle (model + tokenizer + config)\n */\nexport declare function downloadModel(modelId: string, options?: HubOptions): Promise<ModelBundle>;\n/**\n * Load a model from HuggingFace Hub\n *\n * @example\n * ```typescript\n * // Load a sentiment analysis model\n * const bundle = await fromHub('Xenova/distilbert-base-uncased-finetuned-sst-2-english');\n *\n * // Use with edgeFlow\n * const model = await loadModelFromBuffer(bundle.modelData);\n * const tokens = bundle.tokenizer.encode('I love this!');\n * ```\n */\nexport declare function fromHub(modelId: string, options?: HubOptions): Promise<ModelBundle>;\n/**\n * Check if a model exists on HuggingFace Hub\n */\nexport declare function modelExists(modelId: string, options?: HubOptions): Promise<boolean>;\n/**\n * Get model info from HuggingFace Hub\n */\nexport declare function getModelInfo(modelId: string, options?: HubOptions): Promise<{\n    hasOnnx: boolean;\n    onnxFile?: string;\n    hasTokenizer: boolean;\n    hasConfig: boolean;\n    config?: ModelConfig;\n}>;\n/**\n * Pre-configured popular models\n */\nexport declare const POPULAR_MODELS: {\n    readonly 'sentiment-analysis': \"Xenova/distilbert-base-uncased-finetuned-sst-2-english\";\n    readonly 'text-classification': \"Xenova/distilbert-base-uncased-finetuned-sst-2-english\";\n    readonly 'feature-extraction': \"Xenova/all-MiniLM-L6-v2\";\n    readonly 'sentence-similarity': \"Xenova/all-MiniLM-L6-v2\";\n    readonly 'question-answering': \"Xenova/distilbert-base-cased-distilled-squad\";\n    readonly ner: \"Xenova/bert-base-NER\";\n    readonly 'token-classification': \"Xenova/bert-base-NER\";\n    readonly 'text-generation': \"Xenova/gpt2\";\n    readonly 'translation-en-fr': \"Xenova/t5-small\";\n    readonly 'translation-en-de': \"Xenova/t5-small\";\n    readonly summarization: \"Xenova/distilbart-cnn-6-6\";\n    readonly 'fill-mask': \"Xenova/bert-base-uncased\";\n    readonly 'image-classification': \"Xenova/vit-base-patch16-224\";\n    readonly 'object-detection': \"Xenova/detr-resnet-50\";\n    readonly 'image-segmentation': \"Xenova/segformer-b0-finetuned-ade-512-512\";\n    readonly 'zero-shot-classification': \"Xenova/mobilebert-uncased-mnli\";\n    readonly 'automatic-speech-recognition': \"Xenova/whisper-tiny.en\";\n    readonly 'text-to-speech': \"Xenova/speecht5_tts\";\n};\nexport type PopularModelTask = keyof typeof POPULAR_MODELS;\n/**\n * Get the default model ID for a task\n */\nexport declare function getDefaultModel(task: PopularModelTask): string;\n/**\n * Load a model by task name\n *\n * @example\n * ```typescript\n * const bundle = await fromTask('sentiment-analysis');\n * ```\n */\nexport declare function fromTask(task: PopularModelTask, options?: HubOptions): Promise<ModelBundle>;\n//# sourceMappingURL=hub.d.ts.map"
  },
  {
    "path": "dist/utils/hub.js",
    "content": "/**\n * edgeFlow.js - Hugging Face Hub Integration\n *\n * Automatically download models, tokenizers, and configs from Hugging Face Hub.\n */\nimport { loadModelData, isModelCached } from './model-loader.js';\nimport { Tokenizer } from './tokenizer.js';\nimport { EdgeFlowError, ErrorCodes } from '../core/types.js';\n// ============================================================================\n// Constants\n// ============================================================================\nconst DEFAULT_ENDPOINT = 'https://huggingface.co';\nconst DEFAULT_REVISION = 'main';\n/**\n * Common ONNX model file patterns (in order of preference)\n */\nconst ONNX_MODEL_FILES = [\n    'model.onnx',\n    'model_quantized.onnx',\n    'model_int8.onnx',\n    'model_uint8.onnx',\n    'model_fp16.onnx',\n    'onnx/model.onnx',\n    'onnx/model_quantized.onnx',\n];\n// ============================================================================\n// Hub API\n// ============================================================================\n/**\n * Build URL for a file in a HuggingFace repo\n */\nfunction buildFileUrl(modelId, filename, options = {}) {\n    const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;\n    const revision = options.revision ?? DEFAULT_REVISION;\n    const subfolder = options.subfolder ? `${options.subfolder}/` : '';\n    return `${endpoint}/${modelId}/resolve/${revision}/${subfolder}${filename}`;\n}\n/**\n * Fetch with optional auth token\n */\nasync function fetchWithAuth(url, token) {\n    const headers = {};\n    if (token) {\n        headers['Authorization'] = `Bearer ${token}`;\n    }\n    const response = await fetch(url, { headers });\n    return response;\n}\n/**\n * Check if a file exists in a repo\n */\nasync function fileExists(modelId, filename, options = {}) {\n    const url = buildFileUrl(modelId, filename, options);\n    try {\n        const response = await fetchWithAuth(url, options.token);\n        // HuggingFace returns 302 redirect for existing files\n        return response.ok || response.status === 302;\n    }\n    catch {\n        return false;\n    }\n}\n/**\n * Find the best ONNX model file in a repo\n */\nasync function findOnnxModel(modelId, options = {}) {\n    // Try common file patterns\n    for (const filename of ONNX_MODEL_FILES) {\n        if (await fileExists(modelId, filename, options)) {\n            return filename;\n        }\n    }\n    return null;\n}\n/**\n * Download a file from HuggingFace Hub\n */\nexport async function downloadFile(modelId, filename, options = {}) {\n    const url = buildFileUrl(modelId, filename, options);\n    // Use model loader for caching and resume support\n    return loadModelData(url, {\n        cache: options.cache ?? true,\n        forceDownload: options.forceDownload ?? false,\n        onProgress: options.onProgress ? (progress) => {\n            options.onProgress({\n                file: filename,\n                fileIndex: 1,\n                totalFiles: 1,\n                fileProgress: progress,\n                overallProgress: progress.percent,\n            });\n        } : undefined,\n    });\n}\n/**\n * Download JSON file from HuggingFace Hub\n */\nexport async function downloadJson(modelId, filename, options = {}) {\n    const url = buildFileUrl(modelId, filename, options);\n    // Check cache first\n    if (options.cache !== false && !options.forceDownload) {\n        const cached = await isModelCached(url);\n        if (cached) {\n            const data = await loadModelData(url, { cache: true });\n            const text = new TextDecoder().decode(data);\n            return JSON.parse(text);\n        }\n    }\n    // Fetch directly for small JSON files\n    const response = await fetchWithAuth(url, options.token);\n    if (!response.ok) {\n        throw new EdgeFlowError(`Failed to download ${filename} from ${modelId}: ${response.status}`, ErrorCodes.MODEL_NOT_FOUND);\n    }\n    return response.json();\n}\n/**\n * Download tokenizer from HuggingFace Hub\n */\nexport async function downloadTokenizer(modelId, options = {}) {\n    const url = buildFileUrl(modelId, 'tokenizer.json', options);\n    return Tokenizer.fromUrl(url);\n}\n/**\n * Download model config from HuggingFace Hub\n */\nexport async function downloadConfig(modelId, options = {}) {\n    return downloadJson(modelId, 'config.json', options);\n}\n/**\n * Download complete model bundle (model + tokenizer + config)\n */\nexport async function downloadModel(modelId, options = {}) {\n    const files = {};\n    const totalSteps = 3; // model, tokenizer, config\n    let currentStep = 0;\n    const reportProgress = (file, progress) => {\n        if (options.onProgress) {\n            const baseProgress = (currentStep / totalSteps) * 100;\n            const stepProgress = (progress.percent / totalSteps);\n            options.onProgress({\n                file,\n                fileIndex: currentStep + 1,\n                totalFiles: totalSteps,\n                fileProgress: progress,\n                overallProgress: baseProgress + stepProgress,\n            });\n        }\n    };\n    // 1. Find and download ONNX model\n    console.log(`🔍 Finding ONNX model in ${modelId}...`);\n    const modelFile = await findOnnxModel(modelId, options);\n    if (!modelFile) {\n        throw new EdgeFlowError(`No ONNX model found in ${modelId}. Please ensure the model has an ONNX file.`, ErrorCodes.MODEL_NOT_FOUND, { modelId, triedFiles: ONNX_MODEL_FILES });\n    }\n    files.model = modelFile;\n    console.log(`📦 Downloading model: ${modelFile}`);\n    const modelData = await downloadFile(modelId, modelFile, {\n        ...options,\n        onProgress: (p) => reportProgress(modelFile, p.fileProgress),\n    });\n    currentStep = 1;\n    // 2. Download tokenizer (optional)\n    let tokenizer;\n    try {\n        console.log(`📝 Downloading tokenizer...`);\n        files.tokenizer = 'tokenizer.json';\n        tokenizer = await downloadTokenizer(modelId, options);\n        console.log(`✓ Tokenizer loaded`);\n    }\n    catch (error) {\n        console.warn(`⚠️ No tokenizer found for ${modelId}`);\n    }\n    currentStep = 2;\n    // 3. Download config (optional)\n    let config;\n    try {\n        console.log(`⚙️ Downloading config...`);\n        files.config = 'config.json';\n        config = await downloadConfig(modelId, options);\n        console.log(`✓ Config loaded`);\n    }\n    catch (error) {\n        console.warn(`⚠️ No config found for ${modelId}`);\n    }\n    currentStep = 3;\n    if (options.onProgress) {\n        options.onProgress({\n            file: 'complete',\n            fileIndex: totalSteps,\n            totalFiles: totalSteps,\n            fileProgress: { loaded: 1, total: 1, percent: 100, speed: 0, eta: 0 },\n            overallProgress: 100,\n        });\n    }\n    console.log(`✅ Model bundle downloaded: ${modelId}`);\n    return {\n        modelId,\n        modelData,\n        tokenizer,\n        config,\n        files,\n    };\n}\n// ============================================================================\n// High-level API\n// ============================================================================\n/**\n * Load a model from HuggingFace Hub\n *\n * @example\n * ```typescript\n * // Load a sentiment analysis model\n * const bundle = await fromHub('Xenova/distilbert-base-uncased-finetuned-sst-2-english');\n *\n * // Use with edgeFlow\n * const model = await loadModelFromBuffer(bundle.modelData);\n * const tokens = bundle.tokenizer.encode('I love this!');\n * ```\n */\nexport async function fromHub(modelId, options = {}) {\n    return downloadModel(modelId, options);\n}\n/**\n * Check if a model exists on HuggingFace Hub\n */\nexport async function modelExists(modelId, options = {}) {\n    try {\n        // Try to find an ONNX model\n        const modelFile = await findOnnxModel(modelId, options);\n        return modelFile !== null;\n    }\n    catch {\n        return false;\n    }\n}\n/**\n * Get model info from HuggingFace Hub\n */\nexport async function getModelInfo(modelId, options = {}) {\n    const [onnxFile, hasTokenizer, config] = await Promise.all([\n        findOnnxModel(modelId, options),\n        fileExists(modelId, 'tokenizer.json', options),\n        downloadConfig(modelId, options).catch(() => undefined),\n    ]);\n    return {\n        hasOnnx: onnxFile !== null,\n        onnxFile: onnxFile ?? undefined,\n        hasTokenizer,\n        hasConfig: config !== undefined,\n        config,\n    };\n}\n// ============================================================================\n// Popular Models Registry\n// ============================================================================\n/**\n * Pre-configured popular models\n */\nexport const POPULAR_MODELS = {\n    // Text Classification / Sentiment\n    'sentiment-analysis': 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n    'text-classification': 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n    // Feature Extraction\n    'feature-extraction': 'Xenova/all-MiniLM-L6-v2',\n    'sentence-similarity': 'Xenova/all-MiniLM-L6-v2',\n    // Question Answering\n    'question-answering': 'Xenova/distilbert-base-cased-distilled-squad',\n    // Token Classification\n    'ner': 'Xenova/bert-base-NER',\n    'token-classification': 'Xenova/bert-base-NER',\n    // Text Generation\n    'text-generation': 'Xenova/gpt2',\n    // Translation\n    'translation-en-fr': 'Xenova/t5-small',\n    'translation-en-de': 'Xenova/t5-small',\n    // Summarization\n    'summarization': 'Xenova/distilbart-cnn-6-6',\n    // Fill Mask\n    'fill-mask': 'Xenova/bert-base-uncased',\n    // Image Classification\n    'image-classification': 'Xenova/vit-base-patch16-224',\n    // Object Detection\n    'object-detection': 'Xenova/detr-resnet-50',\n    // Image Segmentation\n    'image-segmentation': 'Xenova/segformer-b0-finetuned-ade-512-512',\n    // Zero-shot Classification\n    'zero-shot-classification': 'Xenova/mobilebert-uncased-mnli',\n    // Speech Recognition\n    'automatic-speech-recognition': 'Xenova/whisper-tiny.en',\n    // Text-to-Speech\n    'text-to-speech': 'Xenova/speecht5_tts',\n};\n/**\n * Get the default model ID for a task\n */\nexport function getDefaultModel(task) {\n    return POPULAR_MODELS[task];\n}\n/**\n * Load a model by task name\n *\n * @example\n * ```typescript\n * const bundle = await fromTask('sentiment-analysis');\n * ```\n */\nexport async function fromTask(task, options = {}) {\n    const modelId = getDefaultModel(task);\n    return downloadModel(modelId, options);\n}\n//# sourceMappingURL=hub.js.map"
  },
  {
    "path": "dist/utils/index.d.ts",
    "content": "/**\n * edgeFlow.js - Utilities Exports\n */\nexport { Tokenizer, createBasicTokenizer, loadTokenizer, loadTokenizerFromHub, type TokenizerModel, type TokenizerOptions, } from './tokenizer.js';\nexport { ImagePreprocessor, AudioPreprocessor, preprocessText, createImagePreprocessor, createAudioPreprocessor, type ImagePreprocessorOptions, type AudioPreprocessorOptions, type TextPreprocessorOptions, } from './preprocessor.js';\nexport { Cache, InferenceCache, ModelDownloadCache, createCache, type CacheStrategy, type CacheOptions, type CacheStats, } from './cache.js';\nexport { loadModelData, preloadModel, preloadModels, isModelCached, getCachedModel, deleteCachedModel, clearModelCache, getModelCacheStats, getPreloadStatus, cancelPreload, getPreloadedModel, type DownloadProgress, type ModelLoaderOptions, type PreloadOptions, } from './model-loader.js';\nexport { fromHub, fromTask, downloadModel, downloadFile, downloadTokenizer, downloadConfig, modelExists, getModelInfo, getDefaultModel, POPULAR_MODELS, type HubOptions, type HubDownloadProgress, type ModelConfig, type ModelBundle, type PopularModelTask, } from './hub.js';\nexport { OfflineManager, getOfflineManager, initOffline, isOffline, isPWASupported, generateServiceWorker, generateManifest, type OfflineConfig, type OfflineStatus, type CachedModelInfo, } from './offline.js';\n//# sourceMappingURL=index.d.ts.map"
  },
  {
    "path": "dist/utils/index.js",
    "content": "/**\n * edgeFlow.js - Utilities Exports\n */\n// Tokenizer\nexport { Tokenizer, createBasicTokenizer, loadTokenizer, loadTokenizerFromHub, } from './tokenizer.js';\n// Preprocessor\nexport { ImagePreprocessor, AudioPreprocessor, preprocessText, createImagePreprocessor, createAudioPreprocessor, } from './preprocessor.js';\n// Cache\nexport { Cache, InferenceCache, ModelDownloadCache, createCache, } from './cache.js';\n// Model Loader (Preloading, Sharding, Resume, Caching)\nexport { loadModelData, preloadModel, preloadModels, isModelCached, getCachedModel, deleteCachedModel, clearModelCache, getModelCacheStats, getPreloadStatus, cancelPreload, getPreloadedModel, } from './model-loader.js';\n// HuggingFace Hub Integration\nexport { fromHub, fromTask, downloadModel, downloadFile, downloadTokenizer, downloadConfig, modelExists, getModelInfo, getDefaultModel, POPULAR_MODELS, } from './hub.js';\n// Offline/PWA Support\nexport { OfflineManager, getOfflineManager, initOffline, isOffline, isPWASupported, generateServiceWorker, generateManifest, } from './offline.js';\n//# sourceMappingURL=index.js.map"
  },
  {
    "path": "dist/utils/model-loader.d.ts",
    "content": "/**\n * edgeFlow.js - Advanced Model Loader\n *\n * Features:\n * - Preloading: Background model loading\n * - Sharding: Split large files into chunks for download\n * - Resume Download: Continue download from where it left off\n * - Model Caching: IndexedDB storage for large models\n */\n/**\n * Download progress information\n */\nexport interface DownloadProgress {\n    /** Downloaded bytes */\n    loaded: number;\n    /** Total bytes (0 if unknown) */\n    total: number;\n    /** Progress percentage (0-100) */\n    percent: number;\n    /** Download speed in bytes/sec */\n    speed: number;\n    /** Estimated time remaining in ms */\n    eta: number;\n    /** Current chunk index (for sharded downloads) */\n    currentChunk?: number;\n    /** Total chunks (for sharded downloads) */\n    totalChunks?: number;\n}\n/**\n * Model loader options\n */\nexport interface ModelLoaderOptions {\n    /** Enable caching (default: true) */\n    cache?: boolean;\n    /** Cache name for IndexedDB (default: 'edgeflow-models') */\n    cacheName?: string;\n    /** Enable resume download (default: true) */\n    resumable?: boolean;\n    /** Chunk size for sharded downloads in bytes (default: 5MB) */\n    chunkSize?: number;\n    /** Progress callback */\n    onProgress?: (progress: DownloadProgress) => void;\n    /** Number of parallel download connections (default: 4) */\n    parallelConnections?: number;\n    /** Request timeout in ms (default: 30000) */\n    timeout?: number;\n    /** Force re-download even if cached */\n    forceDownload?: boolean;\n}\n/**\n * Preload options\n */\nexport interface PreloadOptions extends ModelLoaderOptions {\n    /** Priority (higher = more important, default: 0) */\n    priority?: number;\n}\n/**\n * Load model data with caching, sharding, and resume support\n */\nexport declare function loadModelData(url: string, options?: ModelLoaderOptions): Promise<ArrayBuffer>;\n/**\n * Preload a model in the background\n */\nexport declare function preloadModel(url: string, options?: PreloadOptions): Promise<ArrayBuffer>;\n/**\n * Preload multiple models\n */\nexport declare function preloadModels(urls: Array<{\n    url: string;\n    priority?: number;\n}>, options?: Omit<PreloadOptions, 'priority'>): Promise<ArrayBuffer[]>;\n/**\n * Check if a model is cached\n */\nexport declare function isModelCached(url: string): Promise<boolean>;\n/**\n * Get cached model data\n */\nexport declare function getCachedModel(url: string): Promise<ArrayBuffer | null>;\n/**\n * Delete a cached model\n */\nexport declare function deleteCachedModel(url: string): Promise<void>;\n/**\n * Clear all cached models\n */\nexport declare function clearModelCache(): Promise<void>;\n/**\n * Get model cache statistics\n */\nexport declare function getModelCacheStats(): Promise<{\n    models: number;\n    totalSize: number;\n}>;\n/**\n * Get preload status\n */\nexport declare function getPreloadStatus(url: string): 'pending' | 'loading' | 'complete' | 'error' | 'not_found';\n/**\n * Cancel a preload\n */\nexport declare function cancelPreload(url: string): void;\n/**\n * Get preloaded model (or wait for preload to complete)\n */\nexport declare function getPreloadedModel(url: string): Promise<ArrayBuffer | null>;\n//# sourceMappingURL=model-loader.d.ts.map"
  },
  {
    "path": "dist/utils/model-loader.js",
    "content": "/**\n * edgeFlow.js - Advanced Model Loader\n *\n * Features:\n * - Preloading: Background model loading\n * - Sharding: Split large files into chunks for download\n * - Resume Download: Continue download from where it left off\n * - Model Caching: IndexedDB storage for large models\n */\n// ============================================================================\n// IndexedDB Model Cache\n// ============================================================================\nconst DB_NAME = 'edgeflow-model-cache';\nconst DB_VERSION = 1;\nconst STORE_META = 'meta';\nconst STORE_CHUNKS = 'chunks';\nconst STORE_STATE = 'download-state';\n/**\n * IndexedDB-based model cache for large files\n */\nclass ModelCache {\n    db = null;\n    dbPromise = null;\n    /**\n     * Open the database\n     */\n    async openDB() {\n        if (this.db)\n            return this.db;\n        if (this.dbPromise)\n            return this.dbPromise;\n        this.dbPromise = new Promise((resolve, reject) => {\n            const request = indexedDB.open(DB_NAME, DB_VERSION);\n            request.onupgradeneeded = (event) => {\n                const db = event.target.result;\n                // Model metadata store\n                if (!db.objectStoreNames.contains(STORE_META)) {\n                    db.createObjectStore(STORE_META, { keyPath: 'url' });\n                }\n                // Chunk data store\n                if (!db.objectStoreNames.contains(STORE_CHUNKS)) {\n                    const chunkStore = db.createObjectStore(STORE_CHUNKS, { keyPath: ['url', 'index'] });\n                    chunkStore.createIndex('url', 'url', { unique: false });\n                }\n                // Download state store (for resume)\n                if (!db.objectStoreNames.contains(STORE_STATE)) {\n                    db.createObjectStore(STORE_STATE, { keyPath: 'url' });\n                }\n            };\n            request.onsuccess = () => {\n                this.db = request.result;\n                resolve(this.db);\n            };\n            request.onerror = () => reject(request.error);\n        });\n        return this.dbPromise;\n    }\n    /**\n     * Get cached model metadata\n     */\n    async getMeta(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_META, 'readonly');\n            const store = tx.objectStore(STORE_META);\n            const request = store.get(url);\n            request.onsuccess = () => resolve(request.result ?? null);\n            request.onerror = () => reject(request.error);\n        });\n    }\n    /**\n     * Save model metadata (with quota error handling)\n     */\n    async saveMeta(meta) {\n        try {\n            await this.putInStore(STORE_META, meta);\n        }\n        catch (err) {\n            if (this.isQuotaError(err)) {\n                await this.evictOldest(meta.size);\n                try {\n                    await this.putInStore(STORE_META, meta);\n                }\n                catch {\n                    console.warn('[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache.');\n                }\n            }\n            else {\n                throw err;\n            }\n        }\n    }\n    /**\n     * Save a chunk (with quota error handling)\n     */\n    async saveChunk(url, index, data) {\n        try {\n            await this.putInStore(STORE_CHUNKS, { url, index, data });\n        }\n        catch (err) {\n            if (this.isQuotaError(err)) {\n                await this.evictOldest(data.byteLength);\n                try {\n                    await this.putInStore(STORE_CHUNKS, { url, index, data });\n                }\n                catch {\n                    console.warn('[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache for chunk.');\n                }\n            }\n            else {\n                throw err;\n            }\n        }\n    }\n    /**\n     * Generic put helper\n     */\n    async putInStore(storeName, value) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(storeName, 'readwrite');\n            const store = tx.objectStore(storeName);\n            store.put(value);\n            tx.oncomplete = () => resolve();\n            tx.onerror = () => reject(tx.error);\n        });\n    }\n    /**\n     * Detect IndexedDB quota exceeded errors\n     */\n    isQuotaError(err) {\n        if (err instanceof DOMException) {\n            return err.name === 'QuotaExceededError' || err.code === 22;\n        }\n        return false;\n    }\n    /**\n     * Evict oldest cached models to free space.\n     * Deletes models by ascending `cachedAt` until at least `bytesNeeded` is freed.\n     */\n    async evictOldest(bytesNeeded) {\n        const db = await this.openDB();\n        const allMeta = await new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_META, 'readonly');\n            const store = tx.objectStore(STORE_META);\n            const request = store.getAll();\n            request.onsuccess = () => resolve(request.result ?? []);\n            request.onerror = () => reject(request.error);\n        });\n        allMeta.sort((a, b) => a.cachedAt - b.cachedAt);\n        let freed = 0;\n        for (const meta of allMeta) {\n            if (freed >= bytesNeeded)\n                break;\n            await this.deleteModel(meta.url);\n            freed += meta.size;\n        }\n    }\n    /**\n     * Get all chunks for a URL\n     */\n    async getChunks(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_CHUNKS, 'readonly');\n            const store = tx.objectStore(STORE_CHUNKS);\n            const index = store.index('url');\n            const request = index.getAll(url);\n            request.onsuccess = () => {\n                const results = request.result;\n                // Sort by index and extract data\n                results.sort((a, b) => a.index - b.index);\n                resolve(results.map(r => r.data));\n            };\n            request.onerror = () => reject(request.error);\n        });\n    }\n    /**\n     * Get complete model data (merged chunks)\n     */\n    async getModel(url) {\n        const meta = await this.getMeta(url);\n        if (!meta || !meta.complete)\n            return null;\n        const chunks = await this.getChunks(url);\n        if (chunks.length === 0)\n            return null;\n        // Merge chunks\n        const totalSize = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n        const result = new Uint8Array(totalSize);\n        let offset = 0;\n        for (const chunk of chunks) {\n            result.set(new Uint8Array(chunk), offset);\n            offset += chunk.byteLength;\n        }\n        return result.buffer;\n    }\n    /**\n     * Save download state (for resume, with quota handling)\n     */\n    async saveDownloadState(state) {\n        try {\n            await this.putInStore(STORE_STATE, state);\n        }\n        catch (err) {\n            if (this.isQuotaError(err)) {\n                console.warn('[edgeFlow.js] IndexedDB quota exceeded saving download state; resume may not work.');\n            }\n            else {\n                throw err;\n            }\n        }\n    }\n    /**\n     * Get download state\n     */\n    async getDownloadState(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_STATE, 'readonly');\n            const store = tx.objectStore(STORE_STATE);\n            const request = store.get(url);\n            request.onsuccess = () => resolve(request.result ?? null);\n            request.onerror = () => reject(request.error);\n        });\n    }\n    /**\n     * Delete download state\n     */\n    async deleteDownloadState(url) {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_STATE, 'readwrite');\n            const store = tx.objectStore(STORE_STATE);\n            store.delete(url);\n            tx.oncomplete = () => resolve();\n            tx.onerror = () => reject(tx.error);\n        });\n    }\n    /**\n     * Delete cached model\n     */\n    async deleteModel(url) {\n        const db = await this.openDB();\n        // Delete metadata\n        await new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_META, 'readwrite');\n            const store = tx.objectStore(STORE_META);\n            store.delete(url);\n            tx.oncomplete = () => resolve();\n            tx.onerror = () => reject(tx.error);\n        });\n        // Delete chunks\n        const chunks = await this.getChunks(url);\n        if (chunks.length > 0) {\n            await new Promise((resolve, reject) => {\n                const tx = db.transaction(STORE_CHUNKS, 'readwrite');\n                const store = tx.objectStore(STORE_CHUNKS);\n                const index = store.index('url');\n                const request = index.openCursor(IDBKeyRange.only(url));\n                request.onsuccess = (event) => {\n                    const cursor = event.target.result;\n                    if (cursor) {\n                        cursor.delete();\n                        cursor.continue();\n                    }\n                };\n                tx.oncomplete = () => resolve();\n                tx.onerror = () => reject(tx.error);\n            });\n        }\n        // Delete download state\n        await this.deleteDownloadState(url);\n    }\n    /**\n     * Clear all cached models\n     */\n    async clear() {\n        const db = await this.openDB();\n        const stores = [STORE_META, STORE_CHUNKS, STORE_STATE];\n        for (const storeName of stores) {\n            await new Promise((resolve, reject) => {\n                const tx = db.transaction(storeName, 'readwrite');\n                const store = tx.objectStore(storeName);\n                store.clear();\n                tx.oncomplete = () => resolve();\n                tx.onerror = () => reject(tx.error);\n            });\n        }\n    }\n    /**\n     * Get cache statistics\n     */\n    async getStats() {\n        const db = await this.openDB();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction(STORE_META, 'readonly');\n            const store = tx.objectStore(STORE_META);\n            const request = store.getAll();\n            request.onsuccess = () => {\n                const metas = request.result;\n                resolve({\n                    models: metas.filter(m => m.complete).length,\n                    totalSize: metas.reduce((sum, m) => sum + (m.complete ? m.size : 0), 0),\n                });\n            };\n            request.onerror = () => reject(request.error);\n        });\n    }\n}\n// Global cache instance\nconst modelCache = new ModelCache();\n// ============================================================================\n// Advanced Model Loader\n// ============================================================================\n/**\n * Check if server supports Range requests\n */\nasync function supportsRangeRequests(url) {\n    try {\n        const response = await fetch(url, { method: 'HEAD' });\n        const acceptRanges = response.headers.get('Accept-Ranges');\n        const contentLength = response.headers.get('Content-Length');\n        const etag = response.headers.get('ETag') ?? undefined;\n        return {\n            supports: acceptRanges === 'bytes',\n            size: contentLength ? parseInt(contentLength, 10) : 0,\n            etag,\n        };\n    }\n    catch {\n        return { supports: false, size: 0 };\n    }\n}\n/**\n * Download a single chunk using Range request\n */\nasync function downloadChunk(url, start, end, timeout) {\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n    try {\n        const response = await fetch(url, {\n            headers: { Range: `bytes=${start}-${end}` },\n            signal: controller.signal,\n        });\n        if (response.status !== 206 && response.status !== 200) {\n            throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n        }\n        return await response.arrayBuffer();\n    }\n    finally {\n        clearTimeout(timeoutId);\n    }\n}\n/**\n * Download model with sharding and resume support\n */\nasync function downloadWithResume(url, options) {\n    const { chunkSize = 5 * 1024 * 1024, // 5MB\n    parallelConnections = 4, timeout = 30000, onProgress, } = options;\n    // Check server capabilities\n    const { supports: supportsRange, size: totalSize, etag } = await supportsRangeRequests(url);\n    // If no Range support or small file, download normally\n    if (!supportsRange || totalSize < chunkSize * 2) {\n        return downloadSimple(url, timeout, onProgress);\n    }\n    // Check for existing download state\n    let state = await modelCache.getDownloadState(url);\n    // Initialize or reset state if needed\n    if (!state || (etag && state.totalSize !== totalSize)) {\n        const numChunks = Math.ceil(totalSize / chunkSize);\n        const chunks = [];\n        for (let i = 0; i < numChunks; i++) {\n            const start = i * chunkSize;\n            const end = Math.min(start + chunkSize - 1, totalSize - 1);\n            chunks.push({ index: i, start, end, downloaded: false });\n        }\n        state = {\n            url,\n            totalSize,\n            downloadedSize: 0,\n            chunks,\n            startedAt: Date.now(),\n        };\n        // Clear any existing chunks\n        await modelCache.deleteModel(url);\n    }\n    // Download remaining chunks\n    const pendingChunks = state.chunks.filter(c => !c.downloaded);\n    let downloadedSize = state.downloadedSize;\n    const startTime = Date.now();\n    let lastProgressTime = startTime;\n    let lastDownloadedSize = downloadedSize;\n    // Progress tracking\n    const reportProgress = () => {\n        if (!onProgress)\n            return;\n        const now = Date.now();\n        const elapsed = (now - lastProgressTime) / 1000;\n        const bytesDownloaded = downloadedSize - lastDownloadedSize;\n        const speed = elapsed > 0 ? bytesDownloaded / elapsed : 0;\n        const remaining = totalSize - downloadedSize;\n        const eta = speed > 0 ? (remaining / speed) * 1000 : 0;\n        onProgress({\n            loaded: downloadedSize,\n            total: totalSize,\n            percent: (downloadedSize / totalSize) * 100,\n            speed,\n            eta,\n            currentChunk: state.chunks.filter(c => c.downloaded).length,\n            totalChunks: state.chunks.length,\n        });\n        lastProgressTime = now;\n        lastDownloadedSize = downloadedSize;\n    };\n    // Download chunks in parallel\n    const downloadQueue = [...pendingChunks];\n    const inProgress = new Map();\n    while (downloadQueue.length > 0 || inProgress.size > 0) {\n        // Start new downloads up to parallelConnections limit\n        while (downloadQueue.length > 0 && inProgress.size < parallelConnections) {\n            const chunk = downloadQueue.shift();\n            const downloadPromise = (async () => {\n                try {\n                    const data = await downloadChunk(url, chunk.start, chunk.end, timeout);\n                    await modelCache.saveChunk(url, chunk.index, data);\n                    chunk.downloaded = true;\n                    downloadedSize += data.byteLength;\n                    // Update state periodically\n                    state.downloadedSize = downloadedSize;\n                    await modelCache.saveDownloadState(state);\n                    reportProgress();\n                }\n                finally {\n                    inProgress.delete(chunk.index);\n                }\n            })();\n            inProgress.set(chunk.index, downloadPromise);\n        }\n        // Wait for at least one to complete\n        if (inProgress.size > 0) {\n            await Promise.race(inProgress.values());\n        }\n    }\n    // All chunks downloaded, merge them\n    const chunks = await modelCache.getChunks(url);\n    const result = new Uint8Array(totalSize);\n    let offset = 0;\n    for (const chunk of chunks) {\n        result.set(new Uint8Array(chunk), offset);\n        offset += chunk.byteLength;\n    }\n    // Save metadata and cleanup state\n    await modelCache.saveMeta({\n        url,\n        size: totalSize,\n        etag,\n        cachedAt: Date.now(),\n        chunks: chunks.length,\n        complete: true,\n    });\n    await modelCache.deleteDownloadState(url);\n    return result.buffer;\n}\n/**\n * Simple download without sharding\n */\nasync function downloadSimple(url, timeout, onProgress) {\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n    try {\n        const response = await fetch(url, { signal: controller.signal });\n        if (!response.ok) {\n            throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n        }\n        const contentLength = response.headers.get('Content-Length');\n        const total = contentLength ? parseInt(contentLength, 10) : 0;\n        if (!response.body || !onProgress || total === 0) {\n            return await response.arrayBuffer();\n        }\n        // Stream with progress\n        const reader = response.body.getReader();\n        const chunks = [];\n        let loaded = 0;\n        const startTime = Date.now();\n        while (true) {\n            const { done, value } = await reader.read();\n            if (done)\n                break;\n            chunks.push(value);\n            loaded += value.length;\n            const elapsed = (Date.now() - startTime) / 1000;\n            const speed = elapsed > 0 ? loaded / elapsed : 0;\n            const remaining = total - loaded;\n            const eta = speed > 0 ? (remaining / speed) * 1000 : 0;\n            onProgress({\n                loaded,\n                total,\n                percent: (loaded / total) * 100,\n                speed,\n                eta,\n            });\n        }\n        // Merge chunks\n        const result = new Uint8Array(loaded);\n        let offset = 0;\n        for (const chunk of chunks) {\n            result.set(chunk, offset);\n            offset += chunk.length;\n        }\n        return result.buffer;\n    }\n    finally {\n        clearTimeout(timeoutId);\n    }\n}\n/**\n * Preload manager for background model loading\n */\nclass PreloadManager {\n    tasks = new Map();\n    queue = [];\n    maxConcurrent = 2;\n    activeCount = 0;\n    /**\n     * Preload a model in the background\n     */\n    preload(url, options = {}) {\n        // Check if already preloading\n        const existing = this.tasks.get(url);\n        if (existing) {\n            return existing.promise;\n        }\n        // Create task\n        let resolve;\n        let reject;\n        const promise = new Promise((res, rej) => {\n            resolve = res;\n            reject = rej;\n        });\n        const task = {\n            url,\n            priority: options.priority ?? 0,\n            options,\n            promise,\n            resolve,\n            reject,\n            status: 'pending',\n        };\n        this.tasks.set(url, task);\n        // Insert into queue based on priority\n        const insertIndex = this.queue.findIndex(u => {\n            const t = this.tasks.get(u);\n            return t && t.priority < task.priority;\n        });\n        if (insertIndex === -1) {\n            this.queue.push(url);\n        }\n        else {\n            this.queue.splice(insertIndex, 0, url);\n        }\n        // Process queue\n        this.processQueue();\n        return promise;\n    }\n    /**\n     * Process the preload queue\n     */\n    async processQueue() {\n        while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {\n            const url = this.queue.shift();\n            if (!url)\n                break;\n            const task = this.tasks.get(url);\n            if (!task || task.status !== 'pending')\n                continue;\n            this.activeCount++;\n            task.status = 'loading';\n            this.downloadTask(task).finally(() => {\n                this.activeCount--;\n                this.processQueue();\n            });\n        }\n    }\n    /**\n     * Download a preload task\n     */\n    async downloadTask(task) {\n        try {\n            const data = await loadModelData(task.url, task.options);\n            task.status = 'complete';\n            task.resolve(data);\n        }\n        catch (error) {\n            task.status = 'error';\n            task.reject(error instanceof Error ? error : new Error(String(error)));\n        }\n    }\n    /**\n     * Check if a model is preloaded\n     */\n    isPreloaded(url) {\n        const task = this.tasks.get(url);\n        return task?.status === 'complete';\n    }\n    /**\n     * Get preload status\n     */\n    getStatus(url) {\n        const task = this.tasks.get(url);\n        return task?.status ?? 'not_found';\n    }\n    /**\n     * Get preloaded model data\n     */\n    async get(url) {\n        const task = this.tasks.get(url);\n        if (!task)\n            return null;\n        if (task.status === 'complete' || task.status === 'loading') {\n            return task.promise;\n        }\n        return null;\n    }\n    /**\n     * Cancel preload\n     */\n    cancel(url) {\n        const task = this.tasks.get(url);\n        if (task && task.status === 'pending') {\n            this.tasks.delete(url);\n            this.queue = this.queue.filter(u => u !== url);\n            task.reject(new Error('Preload cancelled'));\n        }\n    }\n    /**\n     * Clear all preloads\n     */\n    clear() {\n        for (const [, task] of this.tasks) {\n            if (task.status === 'pending') {\n                task.reject(new Error('Preload cleared'));\n            }\n        }\n        this.tasks.clear();\n        this.queue = [];\n    }\n}\n// Global preload manager\nconst preloadManager = new PreloadManager();\n// ============================================================================\n// Public API\n// ============================================================================\n/**\n * Load model data with caching, sharding, and resume support\n */\nexport async function loadModelData(url, options = {}) {\n    const { cache = true, forceDownload = false, resumable = true, } = options;\n    // Check cache first\n    if (cache && !forceDownload) {\n        const cached = await modelCache.getModel(url);\n        if (cached) {\n            // Validate: reject cached content that is clearly an HTTP error page\n            // (HTML starts with '<', JSON error starts with '{').  Valid ONNX\n            // protobuf binaries always have high-bit or control bytes first.\n            const firstByte = new Uint8Array(cached)[0];\n            const isHtmlOrText = firstByte === 0x3c /* '<' */ || firstByte === 0x7b /* '{' */;\n            if (isHtmlOrText || cached.byteLength < 1024) {\n                console.warn(`[edgeFlow.js] Cached model for ${url} appears corrupt (${cached.byteLength} bytes, first byte 0x${firstByte?.toString(16)}). Evicting and re-downloading.`);\n                await modelCache.deleteModel(url);\n            }\n            else {\n                console.log(`✓ Model loaded from cache: ${url}`);\n                options.onProgress?.({\n                    loaded: cached.byteLength,\n                    total: cached.byteLength,\n                    percent: 100,\n                    speed: 0,\n                    eta: 0,\n                });\n                return cached;\n            }\n        }\n    }\n    // Download with resume support\n    let data;\n    if (resumable) {\n        data = await downloadWithResume(url, options);\n    }\n    else {\n        data = await downloadSimple(url, options.timeout ?? 30000, options.onProgress);\n    }\n    // Cache the result\n    if (cache) {\n        // For simple downloads, save as single chunk\n        if (!resumable) {\n            await modelCache.saveChunk(url, 0, data);\n            await modelCache.saveMeta({\n                url,\n                size: data.byteLength,\n                cachedAt: Date.now(),\n                chunks: 1,\n                complete: true,\n            });\n        }\n    }\n    return data;\n}\n/**\n * Preload a model in the background\n */\nexport function preloadModel(url, options = {}) {\n    return preloadManager.preload(url, options);\n}\n/**\n * Preload multiple models\n */\nexport function preloadModels(urls, options = {}) {\n    return Promise.all(urls.map(({ url, priority }) => preloadManager.preload(url, { ...options, priority })));\n}\n/**\n * Check if a model is cached\n */\nexport async function isModelCached(url) {\n    const meta = await modelCache.getMeta(url);\n    return meta?.complete ?? false;\n}\n/**\n * Get cached model data\n */\nexport async function getCachedModel(url) {\n    return modelCache.getModel(url);\n}\n/**\n * Delete a cached model\n */\nexport async function deleteCachedModel(url) {\n    return modelCache.deleteModel(url);\n}\n/**\n * Clear all cached models\n */\nexport async function clearModelCache() {\n    return modelCache.clear();\n}\n/**\n * Get model cache statistics\n */\nexport async function getModelCacheStats() {\n    return modelCache.getStats();\n}\n/**\n * Get preload status\n */\nexport function getPreloadStatus(url) {\n    return preloadManager.getStatus(url);\n}\n/**\n * Cancel a preload\n */\nexport function cancelPreload(url) {\n    preloadManager.cancel(url);\n}\n/**\n * Get preloaded model (or wait for preload to complete)\n */\nexport async function getPreloadedModel(url) {\n    return preloadManager.get(url);\n}\n//# sourceMappingURL=model-loader.js.map"
  },
  {
    "path": "dist/utils/offline.d.ts",
    "content": "/**\n * edgeFlow.js - Offline/PWA Support\n *\n * Utilities for offline-first ML inference.\n */\nexport interface OfflineConfig {\n    /** Enable offline mode (default: true) */\n    enabled?: boolean;\n    /** Cache models for offline use (default: true) */\n    cacheModels?: boolean;\n    /** Cache model config/tokenizer (default: true) */\n    cacheConfig?: boolean;\n    /** Maximum cache size in bytes (default: 500MB) */\n    maxCacheSize?: number;\n    /** Models to preload for offline use */\n    preloadModels?: string[];\n    /** Service worker path (if using custom SW) */\n    serviceWorkerPath?: string;\n}\nexport interface OfflineStatus {\n    /** Whether the browser is online */\n    isOnline: boolean;\n    /** Whether offline mode is available */\n    offlineReady: boolean;\n    /** Number of cached models */\n    cachedModels: number;\n    /** Total cache size in bytes */\n    cacheSize: number;\n    /** Service worker status */\n    serviceWorker: 'active' | 'installing' | 'waiting' | 'none';\n}\nexport interface CachedModelInfo {\n    url: string;\n    size: number;\n    cachedAt: Date;\n    lastAccessed: Date;\n    modelId?: string;\n}\n/**\n * Offline manager for PWA support\n */\nexport declare class OfflineManager {\n    private config;\n    private onlineListeners;\n    private isInitialized;\n    constructor(config?: OfflineConfig);\n    /**\n     * Initialize offline support\n     */\n    initialize(): Promise<void>;\n    /**\n     * Register service worker\n     */\n    private registerServiceWorker;\n    /**\n     * Preload models for offline use\n     */\n    preloadForOffline(modelUrls: string[]): Promise<void>;\n    /**\n     * Get offline status\n     */\n    getStatus(): Promise<OfflineStatus>;\n    /**\n     * Get list of cached models\n     */\n    getCachedModels(): Promise<CachedModelInfo[]>;\n    /**\n     * Check if a model is available offline\n     */\n    isModelAvailableOffline(url: string): Promise<boolean>;\n    /**\n     * Remove model from offline cache\n     */\n    removeFromOffline(url: string): Promise<void>;\n    /**\n     * Clear all offline data\n     */\n    clearOfflineData(): Promise<void>;\n    /**\n     * Check available storage\n     */\n    getStorageInfo(): Promise<{\n        quota: number;\n        usage: number;\n        available: number;\n    }>;\n    /**\n     * Request persistent storage\n     */\n    requestPersistentStorage(): Promise<boolean>;\n    /**\n     * Add online status listener\n     */\n    onOnlineStatusChange(listener: (online: boolean) => void): () => void;\n    /**\n     * Check if currently online\n     */\n    isOnline(): boolean;\n    /**\n     * Notify listeners of online status change\n     */\n    private notifyOnlineStatus;\n    /**\n     * Open IndexedDB\n     */\n    private openDatabase;\n}\n/**\n * Generate service worker code\n */\nexport declare function generateServiceWorker(options?: {\n    cacheName?: string;\n    modelUrls?: string[];\n    cacheFirst?: boolean;\n}): string;\n/**\n * Generate PWA manifest\n */\nexport declare function generateManifest(options?: {\n    name: string;\n    shortName?: string;\n    description?: string;\n    themeColor?: string;\n    backgroundColor?: string;\n    icons?: Array<{\n        src: string;\n        sizes: string;\n        type: string;\n    }>;\n}): object;\n/**\n * Get the global offline manager instance\n */\nexport declare function getOfflineManager(config?: OfflineConfig): OfflineManager;\n/**\n * Initialize offline support\n */\nexport declare function initOffline(config?: OfflineConfig): Promise<OfflineStatus>;\n/**\n * Check if running in offline mode\n */\nexport declare function isOffline(): boolean;\n/**\n * Check if PWA features are supported\n */\nexport declare function isPWASupported(): boolean;\n//# sourceMappingURL=offline.d.ts.map"
  },
  {
    "path": "dist/utils/offline.js",
    "content": "/**\n * edgeFlow.js - Offline/PWA Support\n *\n * Utilities for offline-first ML inference.\n */\n// ============================================================================\n// Offline Manager\n// ============================================================================\n/**\n * Offline manager for PWA support\n */\nexport class OfflineManager {\n    config;\n    onlineListeners = new Set();\n    isInitialized = false;\n    constructor(config = {}) {\n        this.config = {\n            enabled: config.enabled ?? true,\n            cacheModels: config.cacheModels ?? true,\n            cacheConfig: config.cacheConfig ?? true,\n            maxCacheSize: config.maxCacheSize ?? 500 * 1024 * 1024, // 500MB\n            preloadModels: config.preloadModels ?? [],\n            serviceWorkerPath: config.serviceWorkerPath ?? '/edgeflow-sw.js',\n        };\n    }\n    /**\n     * Initialize offline support\n     */\n    async initialize() {\n        if (this.isInitialized)\n            return;\n        // Listen for online/offline events\n        if (typeof window !== 'undefined') {\n            window.addEventListener('online', () => this.notifyOnlineStatus(true));\n            window.addEventListener('offline', () => this.notifyOnlineStatus(false));\n        }\n        // Register service worker if available\n        if (this.config.enabled && 'serviceWorker' in navigator) {\n            try {\n                await this.registerServiceWorker();\n            }\n            catch (error) {\n                console.warn('Service worker registration failed:', error);\n            }\n        }\n        // Preload models for offline use\n        if (this.config.preloadModels.length > 0) {\n            await this.preloadForOffline(this.config.preloadModels);\n        }\n        this.isInitialized = true;\n    }\n    /**\n     * Register service worker\n     */\n    async registerServiceWorker() {\n        if (!('serviceWorker' in navigator)) {\n            throw new Error('Service workers not supported');\n        }\n        try {\n            const registration = await navigator.serviceWorker.register(this.config.serviceWorkerPath, { scope: '/' });\n            console.log('edgeFlow.js service worker registered:', registration.scope);\n            // Handle updates\n            registration.onupdatefound = () => {\n                const newWorker = registration.installing;\n                if (newWorker) {\n                    newWorker.onstatechange = () => {\n                        if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {\n                            console.log('New edgeFlow.js service worker available');\n                        }\n                    };\n                }\n            };\n        }\n        catch (error) {\n            throw new Error(`Service worker registration failed: ${error}`);\n        }\n    }\n    /**\n     * Preload models for offline use\n     */\n    async preloadForOffline(modelUrls) {\n        const { loadModelData } = await import('./model-loader.js');\n        for (const url of modelUrls) {\n            try {\n                console.log(`Preloading for offline: ${url}`);\n                await loadModelData(url, { cache: true });\n                console.log(`✓ Cached: ${url}`);\n            }\n            catch (error) {\n                console.warn(`Failed to cache ${url}:`, error);\n            }\n        }\n    }\n    /**\n     * Get offline status\n     */\n    async getStatus() {\n        const { getModelCacheStats } = await import('./model-loader.js');\n        const stats = await getModelCacheStats();\n        let swStatus = 'none';\n        if ('serviceWorker' in navigator) {\n            const registration = await navigator.serviceWorker.getRegistration();\n            if (registration) {\n                if (registration.active)\n                    swStatus = 'active';\n                else if (registration.installing)\n                    swStatus = 'installing';\n                else if (registration.waiting)\n                    swStatus = 'waiting';\n            }\n        }\n        return {\n            isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n            offlineReady: stats.models > 0,\n            cachedModels: stats.models,\n            cacheSize: stats.totalSize,\n            serviceWorker: swStatus,\n        };\n    }\n    /**\n     * Get list of cached models\n     */\n    async getCachedModels() {\n        // Query IndexedDB for cached model metadata\n        const db = await this.openDatabase();\n        return new Promise((resolve, reject) => {\n            const tx = db.transaction('meta', 'readonly');\n            const store = tx.objectStore('meta');\n            const request = store.getAll();\n            request.onsuccess = () => {\n                const models = (request.result || []).map((meta) => ({\n                    url: meta['url'],\n                    size: meta['size'],\n                    cachedAt: new Date(meta['cachedAt']),\n                    lastAccessed: new Date(meta['lastAccessed'] || meta['cachedAt']),\n                    modelId: meta['modelId'],\n                }));\n                resolve(models);\n            };\n            request.onerror = () => reject(request.error);\n        });\n    }\n    /**\n     * Check if a model is available offline\n     */\n    async isModelAvailableOffline(url) {\n        const { isModelCached } = await import('./model-loader.js');\n        return isModelCached(url);\n    }\n    /**\n     * Remove model from offline cache\n     */\n    async removeFromOffline(url) {\n        const { deleteCachedModel } = await import('./model-loader.js');\n        await deleteCachedModel(url);\n    }\n    /**\n     * Clear all offline data\n     */\n    async clearOfflineData() {\n        const { clearModelCache } = await import('./model-loader.js');\n        await clearModelCache();\n    }\n    /**\n     * Check available storage\n     */\n    async getStorageInfo() {\n        if ('storage' in navigator && 'estimate' in navigator.storage) {\n            const estimate = await navigator.storage.estimate();\n            return {\n                quota: estimate.quota ?? 0,\n                usage: estimate.usage ?? 0,\n                available: (estimate.quota ?? 0) - (estimate.usage ?? 0),\n            };\n        }\n        return { quota: 0, usage: 0, available: 0 };\n    }\n    /**\n     * Request persistent storage\n     */\n    async requestPersistentStorage() {\n        if ('storage' in navigator && 'persist' in navigator.storage) {\n            return await navigator.storage.persist();\n        }\n        return false;\n    }\n    /**\n     * Add online status listener\n     */\n    onOnlineStatusChange(listener) {\n        this.onlineListeners.add(listener);\n        return () => this.onlineListeners.delete(listener);\n    }\n    /**\n     * Check if currently online\n     */\n    isOnline() {\n        return typeof navigator !== 'undefined' ? navigator.onLine : true;\n    }\n    /**\n     * Notify listeners of online status change\n     */\n    notifyOnlineStatus(online) {\n        this.onlineListeners.forEach(listener => listener(online));\n    }\n    /**\n     * Open IndexedDB\n     */\n    async openDatabase() {\n        return new Promise((resolve, reject) => {\n            const request = indexedDB.open('edgeflow-model-cache', 1);\n            request.onsuccess = () => resolve(request.result);\n            request.onerror = () => reject(request.error);\n        });\n    }\n}\n// ============================================================================\n// Service Worker Template\n// ============================================================================\n/**\n * Generate service worker code\n */\nexport function generateServiceWorker(options = {}) {\n    const { cacheName = 'edgeflow-v1', modelUrls = [], cacheFirst = true, } = options;\n    return `\n// edgeFlow.js Service Worker\n// Auto-generated - customize as needed\n\nconst CACHE_NAME = '${cacheName}';\nconst MODEL_URLS = ${JSON.stringify(modelUrls)};\n\n// Install event - cache core files\nself.addEventListener('install', (event) => {\n  event.waitUntil(\n    caches.open(CACHE_NAME)\n      .then((cache) => {\n        console.log('[edgeFlow SW] Caching core files');\n        return cache.addAll([\n          '/',\n          '/edgeflow.browser.min.js',\n          ...MODEL_URLS,\n        ]);\n      })\n      .then(() => self.skipWaiting())\n  );\n});\n\n// Activate event - cleanup old caches\nself.addEventListener('activate', (event) => {\n  event.waitUntil(\n    caches.keys()\n      .then((cacheNames) => {\n        return Promise.all(\n          cacheNames\n            .filter((name) => name !== CACHE_NAME)\n            .map((name) => caches.delete(name))\n        );\n      })\n      .then(() => self.clients.claim())\n  );\n});\n\n// Fetch event - ${cacheFirst ? 'cache first' : 'network first'} strategy\nself.addEventListener('fetch', (event) => {\n  const url = new URL(event.request.url);\n  \n  // Only handle same-origin and model requests\n  if (url.origin !== location.origin && !isModelRequest(url)) {\n    return;\n  }\n  \n  ${cacheFirst ? `\n  // Cache first strategy\n  event.respondWith(\n    caches.match(event.request)\n      .then((cached) => {\n        if (cached) {\n          return cached;\n        }\n        return fetch(event.request)\n          .then((response) => {\n            if (response.ok && shouldCache(event.request)) {\n              const clone = response.clone();\n              caches.open(CACHE_NAME)\n                .then((cache) => cache.put(event.request, clone));\n            }\n            return response;\n          });\n      })\n  );\n  ` : `\n  // Network first strategy\n  event.respondWith(\n    fetch(event.request)\n      .then((response) => {\n        if (response.ok && shouldCache(event.request)) {\n          const clone = response.clone();\n          caches.open(CACHE_NAME)\n            .then((cache) => cache.put(event.request, clone));\n        }\n        return response;\n      })\n      .catch(() => caches.match(event.request))\n  );\n  `}\n});\n\n// Check if request is for a model file\nfunction isModelRequest(url) {\n  return url.pathname.endsWith('.onnx') ||\n         url.pathname.endsWith('.bin') ||\n         url.hostname.includes('huggingface.co');\n}\n\n// Check if response should be cached\nfunction shouldCache(request) {\n  const url = new URL(request.url);\n  return request.method === 'GET' && (\n    url.pathname.endsWith('.js') ||\n    url.pathname.endsWith('.onnx') ||\n    url.pathname.endsWith('.bin') ||\n    url.pathname.endsWith('.json')\n  );\n}\n\n// Handle messages from main thread\nself.addEventListener('message', (event) => {\n  if (event.data.type === 'SKIP_WAITING') {\n    self.skipWaiting();\n  }\n  if (event.data.type === 'CACHE_MODEL') {\n    cacheModel(event.data.url);\n  }\n});\n\n// Cache a model URL\nasync function cacheModel(url) {\n  const cache = await caches.open(CACHE_NAME);\n  try {\n    const response = await fetch(url);\n    if (response.ok) {\n      await cache.put(url, response);\n      console.log('[edgeFlow SW] Cached model:', url);\n    }\n  } catch (error) {\n    console.error('[edgeFlow SW] Failed to cache model:', url, error);\n  }\n}\n  `.trim();\n}\n/**\n * Generate PWA manifest\n */\nexport function generateManifest(options = { name: 'edgeFlow.js App' }) {\n    return {\n        name: options.name,\n        short_name: options.shortName ?? options.name,\n        description: options.description ?? 'ML-powered application built with edgeFlow.js',\n        start_url: '/',\n        display: 'standalone',\n        theme_color: options.themeColor ?? '#4F46E5',\n        background_color: options.backgroundColor ?? '#FFFFFF',\n        icons: options.icons ?? [\n            { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },\n            { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },\n        ],\n        categories: ['utilities', 'productivity'],\n    };\n}\n// ============================================================================\n// Singleton Instance\n// ============================================================================\nlet offlineManager = null;\n/**\n * Get the global offline manager instance\n */\nexport function getOfflineManager(config) {\n    if (!offlineManager) {\n        offlineManager = new OfflineManager(config);\n    }\n    return offlineManager;\n}\n/**\n * Initialize offline support\n */\nexport async function initOffline(config) {\n    const manager = getOfflineManager(config);\n    await manager.initialize();\n    return manager.getStatus();\n}\n/**\n * Check if running in offline mode\n */\nexport function isOffline() {\n    return typeof navigator !== 'undefined' ? !navigator.onLine : false;\n}\n/**\n * Check if PWA features are supported\n */\nexport function isPWASupported() {\n    return typeof window !== 'undefined' &&\n        'serviceWorker' in navigator &&\n        'caches' in window;\n}\n//# sourceMappingURL=offline.js.map"
  },
  {
    "path": "dist/utils/preprocessor.d.ts",
    "content": "/**\n * edgeFlow.js - Preprocessor\n *\n * Data preprocessing utilities for images, audio, and other data types.\n * Supports HuggingFace preprocessor_config.json format.\n */\nimport { EdgeFlowTensor } from '../core/tensor.js';\n/**\n * Image input types\n */\nexport type ImageInput = HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData | Blob | File | string;\n/**\n * Audio input types\n */\nexport type AudioInput = AudioBuffer | Float32Array | ArrayBuffer | Blob | File | string;\n/**\n * Image preprocessing options\n */\nexport interface ImagePreprocessorOptions {\n    /** Target width (or size for square) */\n    width?: number;\n    /** Target height */\n    height?: number;\n    /** Single size for square output (sets both width and height) */\n    size?: number;\n    /** Resize mode */\n    resizeMode?: 'stretch' | 'contain' | 'cover' | 'pad' | 'shortest_edge' | 'longest_edge';\n    /** Normalization mean */\n    mean?: [number, number, number];\n    /** Normalization std */\n    std?: [number, number, number];\n    /** Rescale factor (applied before normalization) */\n    rescaleFactor?: number;\n    /** Convert to grayscale */\n    grayscale?: boolean;\n    /** Channel format */\n    channelFormat?: 'CHW' | 'HWC';\n    /** Output data type */\n    dtype?: 'float32' | 'uint8';\n    /** Do resize */\n    doResize?: boolean;\n    /** Do rescale */\n    doRescale?: boolean;\n    /** Do normalize */\n    doNormalize?: boolean;\n    /** Do center crop */\n    doCenterCrop?: boolean;\n    /** Center crop size */\n    cropSize?: number | {\n        width: number;\n        height: number;\n    };\n    /** Padding color for 'pad' mode (RGB 0-255) */\n    paddingColor?: [number, number, number];\n}\n/**\n * ImagePreprocessor - Process images for model input\n *\n * Supports HuggingFace preprocessor_config.json format.\n */\nexport declare class ImagePreprocessor {\n    private readonly options;\n    private canvas;\n    private ctx;\n    constructor(options?: ImagePreprocessorOptions);\n    /**\n     * Load from HuggingFace preprocessor_config.json\n     */\n    static fromConfig(config: Record<string, unknown>): ImagePreprocessor;\n    /**\n     * Load from HuggingFace Hub\n     */\n    static fromUrl(url: string): Promise<ImagePreprocessor>;\n    /**\n     * Load from HuggingFace Hub by model ID\n     */\n    static fromHuggingFace(modelId: string, options?: {\n        revision?: string;\n    }): Promise<ImagePreprocessor>;\n    /**\n     * Initialize canvas (lazy)\n     */\n    private ensureCanvas;\n    /**\n     * Process an image\n     */\n    process(input: ImageInput): Promise<EdgeFlowTensor>;\n    /**\n     * Process multiple images (batch)\n     */\n    processBatch(inputs: ImageInput[]): Promise<EdgeFlowTensor>;\n    /**\n     * Load image from URL or base64\n     */\n    private loadFromUrl;\n    /**\n     * Load image from Blob/File\n     */\n    private loadFromBlob;\n    /**\n     * Center crop image\n     */\n    private centerCrop;\n    /**\n     * Convert image element to ImageData\n     */\n    private toImageData;\n    /**\n     * Resize image data\n     */\n    private resize;\n    /**\n     * Convert ImageData to tensor\n     */\n    private toTensor;\n    /**\n     * Get current options\n     */\n    getOptions(): ImagePreprocessorOptions;\n}\n/**\n * Audio preprocessing options\n */\nexport interface AudioPreprocessorOptions {\n    /** Target sample rate */\n    sampleRate?: number;\n    /** Number of mel bins */\n    nMels?: number;\n    /** FFT size */\n    nFft?: number;\n    /** Hop length */\n    hopLength?: number;\n    /** Whether to normalize */\n    normalize?: boolean;\n    /** Maximum duration in seconds */\n    maxDuration?: number;\n}\n/**\n * AudioPreprocessor - Process audio for model input\n *\n * Supports Whisper and other audio model preprocessing.\n */\nexport declare class AudioPreprocessor {\n    private readonly options;\n    private audioContext;\n    constructor(options?: AudioPreprocessorOptions);\n    /**\n     * Load from HuggingFace feature_extractor config\n     */\n    static fromConfig(config: Record<string, unknown>): AudioPreprocessor;\n    /**\n     * Load from HuggingFace Hub\n     */\n    static fromHuggingFace(modelId: string, options?: {\n        revision?: string;\n    }): Promise<AudioPreprocessor>;\n    /**\n     * Initialize audio context (lazy)\n     */\n    private ensureAudioContext;\n    /**\n     * Process audio data\n     */\n    process(input: AudioInput): Promise<EdgeFlowTensor>;\n    /**\n     * Process raw waveform (for models that don't need mel spectrogram)\n     */\n    processRaw(input: AudioInput): Promise<EdgeFlowTensor>;\n    /**\n     * Load audio from URL\n     */\n    private loadFromUrl;\n    /**\n     * Load audio from Blob/File\n     */\n    private loadFromBlob;\n    /**\n     * Decode audio data\n     */\n    private decodeAudioData;\n    /**\n     * Convert AudioBuffer to Float32Array\n     */\n    private audioBufferToFloat32;\n    /**\n     * Normalize audio\n     */\n    private normalizeAudio;\n    /**\n     * Compute mel spectrogram (simplified implementation)\n     */\n    private computeMelSpectrogram;\n    /**\n     * Dispose resources\n     */\n    dispose(): void;\n}\n/**\n * Text preprocessing options\n */\nexport interface TextPreprocessorOptions {\n    /** Convert to lowercase */\n    lowercase?: boolean;\n    /** Remove punctuation */\n    removePunctuation?: boolean;\n    /** Remove extra whitespace */\n    normalizeWhitespace?: boolean;\n    /** Maximum length in characters */\n    maxLength?: number;\n}\n/**\n * Preprocess text\n */\nexport declare function preprocessText(text: string, options?: TextPreprocessorOptions): string;\n/**\n * Create image preprocessor with common presets\n */\nexport declare function createImagePreprocessor(preset?: 'imagenet' | 'clip' | 'vit' | 'custom', options?: ImagePreprocessorOptions): ImagePreprocessor;\n/**\n * Create audio preprocessor with common presets\n */\nexport declare function createAudioPreprocessor(preset?: 'whisper' | 'wav2vec' | 'custom', options?: AudioPreprocessorOptions): AudioPreprocessor;\n//# sourceMappingURL=preprocessor.d.ts.map"
  },
  {
    "path": "dist/utils/preprocessor.js",
    "content": "/**\n * edgeFlow.js - Preprocessor\n *\n * Data preprocessing utilities for images, audio, and other data types.\n * Supports HuggingFace preprocessor_config.json format.\n */\nimport { EdgeFlowTensor } from '../core/tensor.js';\n/**\n * Default image preprocessing options (ImageNet style)\n */\nconst DEFAULT_IMAGE_OPTIONS = {\n    width: 224,\n    height: 224,\n    resizeMode: 'cover',\n    mean: [0.485, 0.456, 0.406],\n    std: [0.229, 0.224, 0.225],\n    rescaleFactor: 1 / 255,\n    grayscale: false,\n    channelFormat: 'CHW',\n    dtype: 'float32',\n    doResize: true,\n    doRescale: true,\n    doNormalize: true,\n    doCenterCrop: false,\n    paddingColor: [0, 0, 0],\n};\n/**\n * ImagePreprocessor - Process images for model input\n *\n * Supports HuggingFace preprocessor_config.json format.\n */\nexport class ImagePreprocessor {\n    options;\n    canvas = null;\n    ctx = null;\n    constructor(options = {}) {\n        // Handle size option\n        const size = options.size;\n        const width = options.width ?? size ?? DEFAULT_IMAGE_OPTIONS.width;\n        const height = options.height ?? size ?? DEFAULT_IMAGE_OPTIONS.height;\n        this.options = {\n            ...DEFAULT_IMAGE_OPTIONS,\n            ...options,\n            width,\n            height,\n            size: size ?? width,\n            cropSize: options.cropSize ?? options.size ?? width,\n        };\n    }\n    /**\n     * Load from HuggingFace preprocessor_config.json\n     */\n    static fromConfig(config) {\n        const options = {};\n        // Map HuggingFace config to our options\n        const size = config['size'];\n        if (size !== undefined) {\n            if (typeof size === 'number') {\n                options.size = size;\n            }\n            else if (typeof size === 'object' && size !== null) {\n                const sizeObj = size;\n                options.width = sizeObj.width ?? sizeObj.shortest_edge;\n                options.height = sizeObj.height ?? sizeObj.shortest_edge;\n            }\n        }\n        const cropSize = config['crop_size'];\n        if (cropSize !== undefined) {\n            if (typeof cropSize === 'number') {\n                options.cropSize = cropSize;\n            }\n            else if (typeof cropSize === 'object' && cropSize !== null) {\n                const cropObj = cropSize;\n                options.cropSize = { width: cropObj.width ?? 224, height: cropObj.height ?? 224 };\n            }\n        }\n        const imageMean = config['image_mean'];\n        if (Array.isArray(imageMean)) {\n            options.mean = imageMean;\n        }\n        const imageStd = config['image_std'];\n        if (Array.isArray(imageStd)) {\n            options.std = imageStd;\n        }\n        const rescaleFactor = config['rescale_factor'];\n        if (typeof rescaleFactor === 'number') {\n            options.rescaleFactor = rescaleFactor;\n        }\n        const doResize = config['do_resize'];\n        if (typeof doResize === 'boolean') {\n            options.doResize = doResize;\n        }\n        const doRescale = config['do_rescale'];\n        if (typeof doRescale === 'boolean') {\n            options.doRescale = doRescale;\n        }\n        const doNormalize = config['do_normalize'];\n        if (typeof doNormalize === 'boolean') {\n            options.doNormalize = doNormalize;\n        }\n        const doCenterCrop = config['do_center_crop'];\n        if (typeof doCenterCrop === 'boolean') {\n            options.doCenterCrop = doCenterCrop;\n        }\n        if (config['resample'] !== undefined) {\n            // Map HuggingFace resample to our resize mode\n            options.resizeMode = 'cover';\n        }\n        return new ImagePreprocessor(options);\n    }\n    /**\n     * Load from HuggingFace Hub\n     */\n    static async fromUrl(url) {\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new Error(`Failed to load preprocessor config from ${url}`);\n        }\n        const config = await response.json();\n        return ImagePreprocessor.fromConfig(config);\n    }\n    /**\n     * Load from HuggingFace Hub by model ID\n     */\n    static async fromHuggingFace(modelId, options) {\n        const revision = options?.revision ?? 'main';\n        const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n        return ImagePreprocessor.fromUrl(url);\n    }\n    /**\n     * Initialize canvas (lazy)\n     */\n    ensureCanvas() {\n        if (!this.canvas) {\n            if (typeof document !== 'undefined') {\n                this.canvas = document.createElement('canvas');\n                this.ctx = this.canvas.getContext('2d');\n            }\n            else {\n                throw new Error('ImagePreprocessor requires a browser environment');\n            }\n        }\n    }\n    /**\n     * Process an image\n     */\n    async process(input) {\n        let imageData;\n        if (typeof input === 'string') {\n            // Load from URL or base64\n            imageData = await this.loadFromUrl(input);\n        }\n        else if (input instanceof Blob || input instanceof File) {\n            imageData = await this.loadFromBlob(input);\n        }\n        else if (input instanceof ImageData) {\n            imageData = input;\n        }\n        else {\n            // HTMLImageElement, HTMLCanvasElement, ImageBitmap\n            imageData = this.toImageData(input);\n        }\n        // Apply preprocessing pipeline\n        let processed = imageData;\n        // 1. Resize\n        if (this.options.doResize) {\n            processed = this.resize(processed);\n        }\n        // 2. Center crop\n        if (this.options.doCenterCrop) {\n            processed = this.centerCrop(processed);\n        }\n        // 3. Convert to tensor (with rescale and normalize)\n        return this.toTensor(processed);\n    }\n    /**\n     * Process multiple images (batch)\n     */\n    async processBatch(inputs) {\n        const tensors = await Promise.all(inputs.map(input => this.process(input)));\n        // Stack tensors into batch\n        const batchSize = tensors.length;\n        const firstTensor = tensors[0];\n        if (!firstTensor) {\n            return new EdgeFlowTensor(new Float32Array(0), [0], 'float32');\n        }\n        const channels = firstTensor.shape[0] ?? 3;\n        const height = firstTensor.shape[1] ?? this.options.height;\n        const width = firstTensor.shape[2] ?? this.options.width;\n        const batchData = new Float32Array(batchSize * channels * height * width);\n        for (let i = 0; i < tensors.length; i++) {\n            const t = tensors[i];\n            if (t) {\n                batchData.set(t.toFloat32Array(), i * channels * height * width);\n            }\n        }\n        return new EdgeFlowTensor(batchData, [batchSize, channels, height, width], 'float32');\n    }\n    /**\n     * Load image from URL or base64\n     */\n    async loadFromUrl(url) {\n        return new Promise((resolve, reject) => {\n            const img = new Image();\n            img.crossOrigin = 'anonymous';\n            img.onload = () => {\n                resolve(this.toImageData(img));\n            };\n            img.onerror = () => {\n                reject(new Error(`Failed to load image from ${url}`));\n            };\n            img.src = url;\n        });\n    }\n    /**\n     * Load image from Blob/File\n     */\n    async loadFromBlob(blob) {\n        const url = URL.createObjectURL(blob);\n        try {\n            return await this.loadFromUrl(url);\n        }\n        finally {\n            URL.revokeObjectURL(url);\n        }\n    }\n    /**\n     * Center crop image\n     */\n    centerCrop(imageData) {\n        const cropSize = this.options.cropSize;\n        let cropWidth;\n        let cropHeight;\n        if (typeof cropSize === 'number') {\n            cropWidth = cropSize;\n            cropHeight = cropSize;\n        }\n        else {\n            cropWidth = cropSize.width;\n            cropHeight = cropSize.height;\n        }\n        const srcX = Math.max(0, Math.floor((imageData.width - cropWidth) / 2));\n        const srcY = Math.max(0, Math.floor((imageData.height - cropHeight) / 2));\n        this.ensureCanvas();\n        // Draw source image\n        const srcCanvas = document.createElement('canvas');\n        srcCanvas.width = imageData.width;\n        srcCanvas.height = imageData.height;\n        const srcCtx = srcCanvas.getContext('2d');\n        srcCtx.putImageData(imageData, 0, 0);\n        // Crop\n        this.canvas.width = cropWidth;\n        this.canvas.height = cropHeight;\n        this.ctx.drawImage(srcCanvas, srcX, srcY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);\n        return this.ctx.getImageData(0, 0, cropWidth, cropHeight);\n    }\n    /**\n     * Convert image element to ImageData\n     */\n    toImageData(source) {\n        this.ensureCanvas();\n        const { width, height } = source;\n        this.canvas.width = width;\n        this.canvas.height = height;\n        this.ctx.drawImage(source, 0, 0);\n        return this.ctx.getImageData(0, 0, width, height);\n    }\n    /**\n     * Resize image data\n     */\n    resize(imageData) {\n        const { width, height, resizeMode } = this.options;\n        this.ensureCanvas();\n        // Calculate resize dimensions\n        let srcX = 0, srcY = 0, srcW = imageData.width, srcH = imageData.height;\n        let dstX = 0, dstY = 0, dstW = width, dstH = height;\n        if (resizeMode === 'contain') {\n            const scale = Math.min(width / imageData.width, height / imageData.height);\n            dstW = Math.round(imageData.width * scale);\n            dstH = Math.round(imageData.height * scale);\n            dstX = Math.round((width - dstW) / 2);\n            dstY = Math.round((height - dstH) / 2);\n        }\n        else if (resizeMode === 'cover') {\n            const scale = Math.max(width / imageData.width, height / imageData.height);\n            srcW = Math.round(width / scale);\n            srcH = Math.round(height / scale);\n            srcX = Math.round((imageData.width - srcW) / 2);\n            srcY = Math.round((imageData.height - srcH) / 2);\n        }\n        // Create temp canvas for source\n        const srcCanvas = document.createElement('canvas');\n        srcCanvas.width = imageData.width;\n        srcCanvas.height = imageData.height;\n        const srcCtx = srcCanvas.getContext('2d');\n        srcCtx.putImageData(imageData, 0, 0);\n        // Draw to output canvas\n        this.canvas.width = width;\n        this.canvas.height = height;\n        // Fill with black for padding modes\n        if (resizeMode === 'contain' || resizeMode === 'pad') {\n            this.ctx.fillStyle = 'black';\n            this.ctx.fillRect(0, 0, width, height);\n        }\n        this.ctx.drawImage(srcCanvas, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH);\n        return this.ctx.getImageData(0, 0, width, height);\n    }\n    /**\n     * Convert ImageData to tensor\n     */\n    toTensor(imageData) {\n        const { mean, std, grayscale, channelFormat, dtype, doRescale, rescaleFactor, doNormalize } = this.options;\n        const height = imageData.height;\n        const width = imageData.width;\n        const channels = grayscale ? 1 : 3;\n        const data = new Float32Array(channels * height * width);\n        const pixels = imageData.data;\n        for (let y = 0; y < height; y++) {\n            for (let x = 0; x < width; x++) {\n                const pixelIdx = (y * width + x) * 4;\n                if (grayscale) {\n                    // Convert to grayscale\n                    let gray = (0.299 * (pixels[pixelIdx] ?? 0) +\n                        0.587 * (pixels[pixelIdx + 1] ?? 0) +\n                        0.114 * (pixels[pixelIdx + 2] ?? 0));\n                    if (doRescale) {\n                        gray *= rescaleFactor;\n                    }\n                    if (doNormalize) {\n                        gray = (gray - (mean[0] ?? 0)) / (std[0] ?? 1);\n                    }\n                    const idx = y * width + x;\n                    data[idx] = gray;\n                }\n                else if (channelFormat === 'CHW') {\n                    // Channel-first format (used by most PyTorch models)\n                    for (let c = 0; c < 3; c++) {\n                        let value = pixels[pixelIdx + c] ?? 0;\n                        if (doRescale) {\n                            value *= rescaleFactor;\n                        }\n                        if (doNormalize) {\n                            value = (value - (mean[c] ?? 0)) / (std[c] ?? 1);\n                        }\n                        const idx = c * height * width + y * width + x;\n                        data[idx] = value;\n                    }\n                }\n                else {\n                    // HWC format (used by TensorFlow models)\n                    for (let c = 0; c < 3; c++) {\n                        let value = pixels[pixelIdx + c] ?? 0;\n                        if (doRescale) {\n                            value *= rescaleFactor;\n                        }\n                        if (doNormalize) {\n                            value = (value - (mean[c] ?? 0)) / (std[c] ?? 1);\n                        }\n                        const idx = y * width * 3 + x * 3 + c;\n                        data[idx] = value;\n                    }\n                }\n            }\n        }\n        const shape = channelFormat === 'CHW'\n            ? [channels, height, width]\n            : [height, width, channels];\n        return new EdgeFlowTensor(data, shape, dtype);\n    }\n    /**\n     * Get current options\n     */\n    getOptions() {\n        return { ...this.options };\n    }\n}\n/**\n * Default audio options\n */\nconst DEFAULT_AUDIO_OPTIONS = {\n    sampleRate: 16000,\n    nMels: 80,\n    nFft: 400,\n    hopLength: 160,\n    normalize: true,\n    maxDuration: 30,\n};\n/**\n * AudioPreprocessor - Process audio for model input\n *\n * Supports Whisper and other audio model preprocessing.\n */\nexport class AudioPreprocessor {\n    options;\n    audioContext = null;\n    constructor(options = {}) {\n        this.options = { ...DEFAULT_AUDIO_OPTIONS, ...options };\n    }\n    /**\n     * Load from HuggingFace feature_extractor config\n     */\n    static fromConfig(config) {\n        const options = {};\n        const samplingRate = config['sampling_rate'];\n        if (typeof samplingRate === 'number') {\n            options.sampleRate = samplingRate;\n        }\n        const featureSize = config['feature_size'];\n        if (typeof featureSize === 'number') {\n            options.nMels = featureSize;\n        }\n        const nFft = config['n_fft'];\n        if (typeof nFft === 'number') {\n            options.nFft = nFft;\n        }\n        const hopLength = config['hop_length'];\n        if (typeof hopLength === 'number') {\n            options.hopLength = hopLength;\n        }\n        return new AudioPreprocessor(options);\n    }\n    /**\n     * Load from HuggingFace Hub\n     */\n    static async fromHuggingFace(modelId, options) {\n        const revision = options?.revision ?? 'main';\n        const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new Error(`Failed to load audio config from ${url}`);\n        }\n        const config = await response.json();\n        return AudioPreprocessor.fromConfig(config);\n    }\n    /**\n     * Initialize audio context (lazy)\n     */\n    ensureAudioContext() {\n        if (!this.audioContext) {\n            if (typeof AudioContext !== 'undefined') {\n                this.audioContext = new AudioContext({ sampleRate: this.options.sampleRate });\n            }\n            else {\n                throw new Error('AudioPreprocessor requires Web Audio API support');\n            }\n        }\n    }\n    /**\n     * Process audio data\n     */\n    async process(input) {\n        let audioData;\n        if (typeof input === 'string') {\n            // Load from URL\n            audioData = await this.loadFromUrl(input);\n        }\n        else if (input instanceof Blob || input instanceof File) {\n            // Load from Blob/File\n            audioData = await this.loadFromBlob(input);\n        }\n        else if (input instanceof AudioBuffer) {\n            audioData = this.audioBufferToFloat32(input);\n        }\n        else if (input instanceof Float32Array) {\n            audioData = input;\n        }\n        else {\n            // ArrayBuffer - decode\n            audioData = await this.decodeAudioData(input);\n        }\n        // Resample if needed\n        // For now, assume input is at target sample rate\n        // Normalize\n        if (this.options.normalize) {\n            audioData = this.normalizeAudio(audioData);\n        }\n        // Truncate if needed\n        const maxSamples = this.options.maxDuration * this.options.sampleRate;\n        if (audioData.length > maxSamples) {\n            audioData = audioData.slice(0, maxSamples);\n        }\n        // Compute mel spectrogram (simplified)\n        const melSpec = this.computeMelSpectrogram(audioData);\n        return melSpec;\n    }\n    /**\n     * Process raw waveform (for models that don't need mel spectrogram)\n     */\n    async processRaw(input) {\n        let audioData;\n        if (typeof input === 'string') {\n            audioData = await this.loadFromUrl(input);\n        }\n        else if (input instanceof Blob || input instanceof File) {\n            audioData = await this.loadFromBlob(input);\n        }\n        else if (input instanceof AudioBuffer) {\n            audioData = this.audioBufferToFloat32(input);\n        }\n        else if (input instanceof Float32Array) {\n            audioData = input;\n        }\n        else {\n            audioData = await this.decodeAudioData(input);\n        }\n        // Normalize\n        if (this.options.normalize) {\n            audioData = this.normalizeAudio(audioData);\n        }\n        // Truncate/pad\n        const maxSamples = this.options.maxDuration * this.options.sampleRate;\n        if (audioData.length > maxSamples) {\n            audioData = audioData.slice(0, maxSamples);\n        }\n        return new EdgeFlowTensor(audioData, [1, audioData.length], 'float32');\n    }\n    /**\n     * Load audio from URL\n     */\n    async loadFromUrl(url) {\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new Error(`Failed to load audio from ${url}`);\n        }\n        const arrayBuffer = await response.arrayBuffer();\n        return this.decodeAudioData(arrayBuffer);\n    }\n    /**\n     * Load audio from Blob/File\n     */\n    async loadFromBlob(blob) {\n        const arrayBuffer = await blob.arrayBuffer();\n        return this.decodeAudioData(arrayBuffer);\n    }\n    /**\n     * Decode audio data\n     */\n    async decodeAudioData(data) {\n        this.ensureAudioContext();\n        const audioBuffer = await this.audioContext.decodeAudioData(data.slice(0)); // Clone to avoid detached buffer\n        return this.audioBufferToFloat32(audioBuffer);\n    }\n    /**\n     * Convert AudioBuffer to Float32Array\n     */\n    audioBufferToFloat32(buffer) {\n        // Get first channel\n        const channelData = buffer.getChannelData(0);\n        return new Float32Array(channelData);\n    }\n    /**\n     * Normalize audio\n     */\n    normalizeAudio(data) {\n        let max = 0;\n        for (let i = 0; i < data.length; i++) {\n            const abs = Math.abs(data[i] ?? 0);\n            if (abs > max)\n                max = abs;\n        }\n        if (max > 0) {\n            const result = new Float32Array(data.length);\n            for (let i = 0; i < data.length; i++) {\n                result[i] = (data[i] ?? 0) / max;\n            }\n            return result;\n        }\n        return data;\n    }\n    /**\n     * Compute mel spectrogram (simplified implementation)\n     */\n    computeMelSpectrogram(audio) {\n        const { nMels, nFft, hopLength } = this.options;\n        // Calculate number of frames\n        const numFrames = Math.floor((audio.length - nFft) / hopLength) + 1;\n        if (numFrames <= 0) {\n            // Return empty spectrogram for very short audio\n            return new EdgeFlowTensor(new Float32Array(nMels), [1, nMels], 'float32');\n        }\n        const melSpec = new Float32Array(numFrames * nMels);\n        // Simplified mel spectrogram computation\n        // In production, use proper FFT and mel filterbank\n        for (let frame = 0; frame < numFrames; frame++) {\n            const start = frame * hopLength;\n            // Compute frame energy (simplified - not real FFT)\n            for (let mel = 0; mel < nMels; mel++) {\n                let energy = 0;\n                const freqStart = Math.floor((mel / nMels) * (nFft / 2));\n                const freqEnd = Math.floor(((mel + 1) / nMels) * (nFft / 2));\n                for (let i = freqStart; i < Math.min(freqEnd, nFft); i++) {\n                    const sample = audio[start + i] ?? 0;\n                    energy += sample * sample;\n                }\n                // Convert to log scale\n                melSpec[frame * nMels + mel] = Math.log(energy + 1e-10);\n            }\n        }\n        return new EdgeFlowTensor(melSpec, [numFrames, nMels], 'float32');\n    }\n    /**\n     * Dispose resources\n     */\n    dispose() {\n        if (this.audioContext) {\n            this.audioContext.close();\n            this.audioContext = null;\n        }\n    }\n}\n/**\n * Preprocess text\n */\nexport function preprocessText(text, options = {}) {\n    const { lowercase = true, removePunctuation = false, normalizeWhitespace = true, maxLength, } = options;\n    let result = text;\n    if (lowercase) {\n        result = result.toLowerCase();\n    }\n    if (removePunctuation) {\n        result = result.replace(/[^\\w\\s]/g, '');\n    }\n    if (normalizeWhitespace) {\n        result = result.replace(/\\s+/g, ' ').trim();\n    }\n    if (maxLength && result.length > maxLength) {\n        result = result.slice(0, maxLength);\n    }\n    return result;\n}\n// ============================================================================\n// Factory Functions\n// ============================================================================\n/**\n * Create image preprocessor with common presets\n */\nexport function createImagePreprocessor(preset = 'imagenet', options = {}) {\n    const presets = {\n        imagenet: {\n            width: 224,\n            height: 224,\n            mean: [0.485, 0.456, 0.406],\n            std: [0.229, 0.224, 0.225],\n        },\n        clip: {\n            width: 224,\n            height: 224,\n            mean: [0.48145466, 0.4578275, 0.40821073],\n            std: [0.26862954, 0.26130258, 0.27577711],\n        },\n        vit: {\n            width: 224,\n            height: 224,\n            mean: [0.5, 0.5, 0.5],\n            std: [0.5, 0.5, 0.5],\n        },\n        custom: {},\n    };\n    return new ImagePreprocessor({ ...presets[preset], ...options });\n}\n/**\n * Create audio preprocessor with common presets\n */\nexport function createAudioPreprocessor(preset = 'whisper', options = {}) {\n    const presets = {\n        whisper: {\n            sampleRate: 16000,\n            nMels: 80,\n            nFft: 400,\n            hopLength: 160,\n        },\n        wav2vec: {\n            sampleRate: 16000,\n            normalize: true,\n        },\n        custom: {},\n    };\n    return new AudioPreprocessor({ ...presets[preset], ...options });\n}\n//# sourceMappingURL=preprocessor.js.map"
  },
  {
    "path": "dist/utils/tokenizer.d.ts",
    "content": "/**\n * edgeFlow.js - Tokenizer\n *\n * Full-featured tokenizer supporting HuggingFace tokenizer.json format.\n * Supports BPE, WordPiece, and Unigram tokenization.\n */\nimport { TokenizerConfig, TokenizedOutput } from '../core/types.js';\nexport type TokenizerModel = 'BPE' | 'WordPiece' | 'Unigram' | 'basic';\nexport interface TokenizerOptions {\n    addSpecialTokens?: boolean;\n    maxLength?: number;\n    padding?: 'max_length' | 'longest' | 'do_not_pad';\n    truncation?: boolean;\n    returnAttentionMask?: boolean;\n    returnTokenTypeIds?: boolean;\n    textPair?: string;\n}\n/**\n * HuggingFace tokenizer.json format\n */\ninterface HFTokenizerJSON {\n    version?: string;\n    truncation?: {\n        max_length: number;\n        strategy: string;\n    };\n    padding?: {\n        strategy: string;\n        pad_id: number;\n        pad_token: string;\n    };\n    added_tokens?: Array<{\n        id: number;\n        content: string;\n        single_word: boolean;\n        lstrip: boolean;\n        rstrip: boolean;\n        normalized: boolean;\n        special: boolean;\n    }>;\n    normalizer?: {\n        type: string;\n        lowercase?: boolean;\n        strip_accents?: boolean;\n        [key: string]: unknown;\n    };\n    pre_tokenizer?: {\n        type: string;\n        [key: string]: unknown;\n    };\n    post_processor?: {\n        type: string;\n        single?: Array<{\n            id: string;\n            type_id: number;\n        } | {\n            SpecialToken: {\n                id: string;\n                type_id: number;\n            };\n        } | {\n            Sequence: {\n                id: string;\n                type_id: number;\n            };\n        }>;\n        pair?: Array<{\n            id: string;\n            type_id: number;\n        } | {\n            SpecialToken: {\n                id: string;\n                type_id: number;\n            };\n        } | {\n            Sequence: {\n                id: string;\n                type_id: number;\n            };\n        }>;\n        special_tokens?: Record<string, {\n            id: string;\n            ids: number[];\n            tokens: string[];\n        }>;\n        [key: string]: unknown;\n    };\n    decoder?: {\n        type: string;\n        [key: string]: unknown;\n    };\n    model: {\n        type: string;\n        vocab?: Record<string, number>;\n        merges?: string[];\n        unk_token?: string;\n        continuing_subword_prefix?: string;\n        end_of_word_suffix?: string;\n        fuse_unk?: boolean;\n        byte_fallback?: boolean;\n        [key: string]: unknown;\n    };\n}\n/**\n * Tokenizer - Full-featured tokenizer supporting HuggingFace format\n */\nexport declare class Tokenizer {\n    private vocab;\n    private reverseVocab;\n    private merges;\n    private addedTokens;\n    private specialTokens;\n    private modelType;\n    private unkToken;\n    private continuingSubwordPrefix;\n    private padTokenId;\n    private unkTokenId;\n    private clsTokenId?;\n    private sepTokenId?;\n    private maskTokenId?;\n    private bosTokenId?;\n    private eosTokenId?;\n    private maxLength;\n    private doLowerCase;\n    private stripAccents;\n    private postProcessor?;\n    private byteEncoder;\n    private byteDecoder;\n    constructor();\n    /**\n     * Initialize byte encoder/decoder for BPE\n     */\n    private initByteEncoder;\n    /**\n     * Load from HuggingFace tokenizer.json\n     */\n    static fromJSON(json: HFTokenizerJSON | string): Promise<Tokenizer>;\n    /**\n     * Load from URL (tokenizer.json)\n     */\n    static fromUrl(url: string): Promise<Tokenizer>;\n    /**\n     * Load from HuggingFace Hub\n     */\n    static fromHuggingFace(modelId: string, options?: {\n        revision?: string;\n    }): Promise<Tokenizer>;\n    /**\n     * Normalize text\n     */\n    private normalize;\n    /**\n     * Pre-tokenize text (split into words)\n     */\n    private preTokenize;\n    /**\n     * Encode text to bytes (for BPE)\n     */\n    private textToBytes;\n    /**\n     * Decode bytes to text (for BPE)\n     */\n    private bytesToText;\n    /**\n     * Get BPE pairs from word\n     */\n    private getPairs;\n    /**\n     * Apply BPE to a word\n     */\n    private bpe;\n    /**\n     * WordPiece tokenization\n     */\n    private wordPiece;\n    /**\n     * Tokenize a single word\n     */\n    private tokenizeWord;\n    /**\n     * Greedy longest-match tokenizer for SentencePiece Unigram models.\n     * Adds the U+2581 (▁) word-start prefix expected by SPM-based models.\n     */\n    private unigramTokenize;\n    /**\n     * Main tokenization\n     */\n    private tokenize;\n    /**\n     * Convert tokens to IDs\n     */\n    private convertTokensToIds;\n    /**\n     * Convert IDs to tokens\n     */\n    private convertIdsToTokens;\n    /**\n     * Apply post-processing (add special tokens)\n     */\n    private postProcess;\n    /**\n     * Encode text\n     */\n    encode(text: string, options?: TokenizerOptions): TokenizedOutput;\n    /**\n     * Batch encode\n     */\n    encodeBatch(texts: string[], options?: TokenizerOptions): TokenizedOutput[];\n    /**\n     * Decode IDs to text\n     */\n    decode(ids: number[], skipSpecialTokens?: boolean): string;\n    /**\n     * Decode batch\n     */\n    decodeBatch(batchIds: number[][], skipSpecialTokens?: boolean): string[];\n    /**\n     * Get vocabulary size\n     */\n    get vocabSize(): number;\n    /**\n     * Get special token IDs\n     */\n    getSpecialTokenIds(): {\n        padTokenId: number;\n        unkTokenId: number;\n        clsTokenId?: number;\n        sepTokenId?: number;\n        maskTokenId?: number;\n        bosTokenId?: number;\n        eosTokenId?: number;\n    };\n    /**\n     * Get config\n     */\n    getConfig(): TokenizerConfig;\n    /**\n     * Check if token is special\n     */\n    isSpecialToken(token: string): boolean;\n    /**\n     * Get token ID\n     */\n    getTokenId(token: string): number | undefined;\n    /**\n     * Get token from ID\n     */\n    getToken(id: number): string | undefined;\n}\n/**\n * Create a basic English tokenizer (for testing)\n */\nexport declare function createBasicTokenizer(): Tokenizer;\n/**\n * Load tokenizer from URL\n */\nexport declare function loadTokenizer(url: string): Promise<Tokenizer>;\n/**\n * Load tokenizer from HuggingFace Hub\n */\nexport declare function loadTokenizerFromHub(modelId: string, options?: {\n    revision?: string;\n}): Promise<Tokenizer>;\nexport {};\n//# sourceMappingURL=tokenizer.d.ts.map"
  },
  {
    "path": "dist/utils/tokenizer.js",
    "content": "/**\n * edgeFlow.js - Tokenizer\n *\n * Full-featured tokenizer supporting HuggingFace tokenizer.json format.\n * Supports BPE, WordPiece, and Unigram tokenization.\n */\nimport { EdgeFlowError, ErrorCodes, } from '../core/types.js';\n// ============================================================================\n// Tokenizer Implementation\n// ============================================================================\n/**\n * Tokenizer - Full-featured tokenizer supporting HuggingFace format\n */\nexport class Tokenizer {\n    vocab = new Map();\n    reverseVocab = new Map();\n    merges = new Map();\n    addedTokens = new Map();\n    specialTokens = new Set();\n    modelType = 'BPE';\n    unkToken = '[UNK]';\n    continuingSubwordPrefix = '##';\n    // Special token IDs\n    padTokenId = 0;\n    unkTokenId = 0;\n    clsTokenId;\n    sepTokenId;\n    maskTokenId;\n    bosTokenId;\n    eosTokenId;\n    // Config\n    maxLength = 512;\n    doLowerCase = false;\n    stripAccents = false;\n    // Post-processor config\n    postProcessor;\n    // Byte encoder for BPE\n    byteEncoder = new Map();\n    byteDecoder = new Map();\n    constructor() {\n        this.initByteEncoder();\n    }\n    /**\n     * Initialize byte encoder/decoder for BPE\n     */\n    initByteEncoder() {\n        const bytes = [];\n        // Printable ASCII\n        for (let i = 33; i <= 126; i++)\n            bytes.push(i);\n        for (let i = 161; i <= 172; i++)\n            bytes.push(i);\n        for (let i = 174; i <= 255; i++)\n            bytes.push(i);\n        const chars = [...bytes];\n        let n = 0;\n        for (let i = 0; i < 256; i++) {\n            if (!bytes.includes(i)) {\n                bytes.push(i);\n                chars.push(256 + n);\n                n++;\n            }\n        }\n        for (let i = 0; i < bytes.length; i++) {\n            const byte = bytes[i];\n            const char = String.fromCharCode(chars[i]);\n            this.byteEncoder.set(byte, char);\n            this.byteDecoder.set(char, byte);\n        }\n    }\n    /**\n     * Load from HuggingFace tokenizer.json\n     */\n    static async fromJSON(json) {\n        const tokenizer = new Tokenizer();\n        const data = typeof json === 'string' ? JSON.parse(json) : json;\n        // Load model config\n        if (data.model) {\n            tokenizer.modelType = data.model.type;\n            // Load vocabulary.\n            // BPE/WordPiece: vocab is an object { token: id }.\n            // Unigram (SentencePiece): vocab is an array of [token, score] pairs\n            // where the array *index* is the token ID.\n            if (data.model.vocab) {\n                if (Array.isArray(data.model.vocab)) {\n                    // Unigram format\n                    const unigramVocab = data.model.vocab;\n                    for (let i = 0; i < unigramVocab.length; i++) {\n                        const entry = unigramVocab[i];\n                        const token = Array.isArray(entry) ? entry[0] : entry;\n                        tokenizer.vocab.set(token, i);\n                        tokenizer.reverseVocab.set(i, token);\n                    }\n                }\n                else {\n                    for (const [token, id] of Object.entries(data.model.vocab)) {\n                        tokenizer.vocab.set(token, id);\n                        tokenizer.reverseVocab.set(id, token);\n                    }\n                }\n            }\n            // Load merges for BPE\n            if (data.model.merges) {\n                for (let i = 0; i < data.model.merges.length; i++) {\n                    tokenizer.merges.set(data.model.merges[i], i);\n                }\n            }\n            // Model-specific config\n            tokenizer.unkToken = data.model.unk_token ?? '[UNK]';\n            tokenizer.continuingSubwordPrefix = data.model.continuing_subword_prefix ?? '##';\n        }\n        // Load added tokens\n        if (data.added_tokens) {\n            for (const token of data.added_tokens) {\n                tokenizer.addedTokens.set(token.content, token.id);\n                tokenizer.reverseVocab.set(token.id, token.content);\n                if (token.special) {\n                    tokenizer.specialTokens.add(token.content);\n                }\n                // Detect special token types\n                const content = token.content.toLowerCase();\n                if (content.includes('pad'))\n                    tokenizer.padTokenId = token.id;\n                if (content.includes('unk'))\n                    tokenizer.unkTokenId = token.id;\n                if (content.includes('cls') || content === '[cls]')\n                    tokenizer.clsTokenId = token.id;\n                if (content.includes('sep') || content === '[sep]')\n                    tokenizer.sepTokenId = token.id;\n                if (content.includes('mask'))\n                    tokenizer.maskTokenId = token.id;\n                if (content.includes('bos') || content === '<s>')\n                    tokenizer.bosTokenId = token.id;\n                if (content.includes('eos') || content === '</s>')\n                    tokenizer.eosTokenId = token.id;\n            }\n        }\n        // Load normalizer config\n        if (data.normalizer) {\n            tokenizer.doLowerCase = data.normalizer.lowercase ?? false;\n            tokenizer.stripAccents = data.normalizer.strip_accents ?? false;\n        }\n        // Load truncation config\n        if (data.truncation) {\n            tokenizer.maxLength = data.truncation.max_length;\n        }\n        // Load post-processor\n        if (data.post_processor) {\n            tokenizer.postProcessor = data.post_processor;\n        }\n        return tokenizer;\n    }\n    /**\n     * Load from URL (tokenizer.json)\n     */\n    static async fromUrl(url) {\n        const response = await fetch(url);\n        if (!response.ok) {\n            throw new EdgeFlowError(`Failed to load tokenizer from ${url}: ${response.status}`, ErrorCodes.MODEL_NOT_FOUND);\n        }\n        const json = await response.json();\n        return Tokenizer.fromJSON(json);\n    }\n    /**\n     * Load from HuggingFace Hub\n     */\n    static async fromHuggingFace(modelId, options) {\n        const revision = options?.revision ?? 'main';\n        const url = `https://huggingface.co/${modelId}/resolve/${revision}/tokenizer.json`;\n        return Tokenizer.fromUrl(url);\n    }\n    /**\n     * Normalize text\n     */\n    normalize(text) {\n        let result = text;\n        if (this.doLowerCase) {\n            result = result.toLowerCase();\n        }\n        if (this.stripAccents) {\n            result = result.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n        }\n        // Normalize whitespace\n        result = result.replace(/\\s+/g, ' ').trim();\n        return result;\n    }\n    /**\n     * Pre-tokenize text (split into words)\n     */\n    preTokenize(text) {\n        // GPT-2 style: split on whitespace and punctuation, keeping them\n        const pattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+/gu;\n        const matches = text.match(pattern);\n        return matches ?? [text];\n    }\n    /**\n     * Encode text to bytes (for BPE)\n     */\n    textToBytes(text) {\n        const encoder = new TextEncoder();\n        const bytes = encoder.encode(text);\n        return Array.from(bytes).map(b => this.byteEncoder.get(b) ?? '').join('');\n    }\n    /**\n     * Decode bytes to text (for BPE)\n     */\n    bytesToText(text) {\n        const bytes = new Uint8Array(text.split('').map(c => this.byteDecoder.get(c) ?? 0));\n        const decoder = new TextDecoder('utf-8', { fatal: false });\n        return decoder.decode(bytes);\n    }\n    /**\n     * Get BPE pairs from word\n     */\n    getPairs(word) {\n        const pairs = new Set();\n        for (let i = 0; i < word.length - 1; i++) {\n            pairs.add(`${word[i]} ${word[i + 1]}`);\n        }\n        return pairs;\n    }\n    /**\n     * Apply BPE to a word\n     */\n    bpe(token) {\n        if (this.vocab.has(token)) {\n            return [token];\n        }\n        let word = token.split('');\n        let pairs = this.getPairs(word);\n        if (pairs.size === 0) {\n            return [token];\n        }\n        while (true) {\n            // Find the pair with lowest merge rank\n            let minPair = null;\n            let minRank = Infinity;\n            for (const pair of pairs) {\n                const rank = this.merges.get(pair);\n                if (rank !== undefined && rank < minRank) {\n                    minRank = rank;\n                    minPair = pair;\n                }\n            }\n            if (minPair === null)\n                break;\n            const parts = minPair.split(' ');\n            const first = parts[0];\n            const second = parts[1];\n            if (!first || !second)\n                break;\n            const newWord = [];\n            let i = 0;\n            while (i < word.length) {\n                const j = word.indexOf(first, i);\n                if (j === -1) {\n                    newWord.push(...word.slice(i));\n                    break;\n                }\n                newWord.push(...word.slice(i, j));\n                if (word[j] === first && j < word.length - 1 && word[j + 1] === second) {\n                    newWord.push(first + second);\n                    i = j + 2;\n                }\n                else {\n                    newWord.push(word[j]);\n                    i = j + 1;\n                }\n            }\n            word = newWord;\n            if (word.length === 1)\n                break;\n            pairs = this.getPairs(word);\n        }\n        return word;\n    }\n    /**\n     * WordPiece tokenization\n     */\n    wordPiece(word) {\n        if (this.vocab.has(word)) {\n            return [word];\n        }\n        const tokens = [];\n        let start = 0;\n        while (start < word.length) {\n            let end = word.length;\n            let curSubstr = null;\n            while (start < end) {\n                let substr = word.slice(start, end);\n                if (start > 0) {\n                    substr = this.continuingSubwordPrefix + substr;\n                }\n                if (this.vocab.has(substr)) {\n                    curSubstr = substr;\n                    break;\n                }\n                end--;\n            }\n            if (curSubstr === null) {\n                tokens.push(this.unkToken);\n                start++;\n            }\n            else {\n                tokens.push(curSubstr);\n                start = end;\n            }\n        }\n        return tokens;\n    }\n    /**\n     * Tokenize a single word\n     */\n    tokenizeWord(word) {\n        // Check added tokens first\n        if (this.addedTokens.has(word)) {\n            return [word];\n        }\n        switch (this.modelType) {\n            case 'BPE': {\n                // Convert to byte representation\n                const byteStr = this.textToBytes(word);\n                return this.bpe(byteStr);\n            }\n            case 'WordPiece':\n                return this.wordPiece(word);\n            case 'Unigram':\n                return this.unigramTokenize(word);\n            default:\n                return this.vocab.has(word) ? [word] : [this.unkToken];\n        }\n    }\n    /**\n     * Greedy longest-match tokenizer for SentencePiece Unigram models.\n     * Adds the U+2581 (▁) word-start prefix expected by SPM-based models.\n     */\n    unigramTokenize(word) {\n        // SentencePiece prepends ▁ to words that follow a space (i.e. the\n        // tokenizer receives individual words, so all of them get the prefix).\n        const prefixedWord = '\\u2581' + word;\n        const tokens = [];\n        let start = 0;\n        const text = prefixedWord;\n        while (start < text.length) {\n            let end = text.length;\n            let found = false;\n            // Greedy longest-match scan\n            while (end > start) {\n                const sub = text.slice(start, end);\n                if (this.vocab.has(sub)) {\n                    tokens.push(sub);\n                    start = end;\n                    found = true;\n                    break;\n                }\n                end--;\n            }\n            if (!found) {\n                // Emit the single character (or unk if it's not in vocab either)\n                const ch = text[start];\n                tokens.push(this.vocab.has(ch) ? ch : this.unkToken);\n                start++;\n            }\n        }\n        return tokens.length > 0 ? tokens : [this.unkToken];\n    }\n    /**\n     * Main tokenization\n     */\n    tokenize(text) {\n        // Normalize\n        const normalized = this.normalize(text);\n        // Check for added tokens (special tokens)\n        const tokens = [];\n        let remaining = normalized;\n        // Sort added tokens by length (longest first) for greedy matching\n        const sortedAddedTokens = Array.from(this.addedTokens.keys())\n            .sort((a, b) => b.length - a.length);\n        // Split by added tokens\n        for (const addedToken of sortedAddedTokens) {\n            if (remaining.includes(addedToken)) {\n                const parts = remaining.split(addedToken);\n                const newRemaining = [];\n                for (let i = 0; i < parts.length; i++) {\n                    if (parts[i]) {\n                        newRemaining.push(parts[i]);\n                    }\n                    if (i < parts.length - 1) {\n                        tokens.push(addedToken);\n                    }\n                }\n                remaining = newRemaining.join(' ');\n            }\n        }\n        // Pre-tokenize remaining text\n        if (remaining.trim()) {\n            const words = this.preTokenize(remaining);\n            for (const word of words) {\n                if (!word)\n                    continue;\n                const wordTokens = this.tokenizeWord(word);\n                tokens.push(...wordTokens);\n            }\n        }\n        return tokens;\n    }\n    /**\n     * Convert tokens to IDs\n     */\n    convertTokensToIds(tokens) {\n        return tokens.map(token => {\n            // Check added tokens first\n            const addedId = this.addedTokens.get(token);\n            if (addedId !== undefined)\n                return addedId;\n            // Check vocabulary\n            const vocabId = this.vocab.get(token);\n            if (vocabId !== undefined)\n                return vocabId;\n            // Return UNK\n            return this.unkTokenId;\n        });\n    }\n    /**\n     * Convert IDs to tokens\n     */\n    convertIdsToTokens(ids) {\n        return ids.map(id => this.reverseVocab.get(id) ?? this.unkToken);\n    }\n    /**\n     * Apply post-processing (add special tokens)\n     */\n    postProcess(ids, pairIds) {\n        if (!this.postProcessor) {\n            // Default: [CLS] tokens [SEP] or [CLS] tokens [SEP] pair [SEP]\n            const result = [];\n            const typeIds = [];\n            if (this.clsTokenId !== undefined) {\n                result.push(this.clsTokenId);\n                typeIds.push(0);\n            }\n            result.push(...ids);\n            typeIds.push(...ids.map(() => 0));\n            if (this.sepTokenId !== undefined) {\n                result.push(this.sepTokenId);\n                typeIds.push(0);\n            }\n            if (pairIds) {\n                result.push(...pairIds);\n                typeIds.push(...pairIds.map(() => 1));\n                if (this.sepTokenId !== undefined) {\n                    result.push(this.sepTokenId);\n                    typeIds.push(1);\n                }\n            }\n            return { ids: result, typeIds };\n        }\n        // Use post-processor config\n        const template = pairIds ? this.postProcessor.pair : this.postProcessor.single;\n        if (!template) {\n            return { ids, typeIds: ids.map(() => 0) };\n        }\n        const result = [];\n        const typeIds = [];\n        for (const item of template) {\n            if ('SpecialToken' in item) {\n                const specialToken = this.postProcessor.special_tokens?.[item.SpecialToken.id];\n                if (specialToken) {\n                    result.push(...specialToken.ids);\n                    typeIds.push(...specialToken.ids.map(() => item.SpecialToken.type_id));\n                }\n            }\n            else if ('Sequence' in item) {\n                const seqIds = item.Sequence.id === 'A' ? ids : pairIds ?? [];\n                result.push(...seqIds);\n                typeIds.push(...seqIds.map(() => item.Sequence.type_id));\n            }\n        }\n        return { ids: result, typeIds };\n    }\n    /**\n     * Encode text\n     */\n    encode(text, options = {}) {\n        const { addSpecialTokens = true, maxLength = this.maxLength, padding = 'max_length', truncation = true, returnAttentionMask = true, returnTokenTypeIds = false, textPair, } = options;\n        // Tokenize\n        const tokens = this.tokenize(text);\n        let inputIds = this.convertTokensToIds(tokens);\n        // Tokenize pair if provided\n        let pairIds;\n        if (textPair) {\n            const pairTokens = this.tokenize(textPair);\n            pairIds = this.convertTokensToIds(pairTokens);\n        }\n        // Post-process (add special tokens)\n        let tokenTypeIds;\n        if (addSpecialTokens) {\n            const processed = this.postProcess(inputIds, pairIds);\n            inputIds = processed.ids;\n            if (returnTokenTypeIds) {\n                tokenTypeIds = processed.typeIds;\n            }\n        }\n        else if (pairIds) {\n            inputIds = [...inputIds, ...pairIds];\n            if (returnTokenTypeIds) {\n                tokenTypeIds = [...inputIds.map(() => 0), ...pairIds.map(() => 1)];\n            }\n        }\n        // Truncate\n        if (truncation && inputIds.length > maxLength) {\n            inputIds = inputIds.slice(0, maxLength);\n            if (tokenTypeIds) {\n                tokenTypeIds = tokenTypeIds.slice(0, maxLength);\n            }\n        }\n        // Create attention mask\n        let attentionMask = [];\n        if (returnAttentionMask) {\n            attentionMask = inputIds.map(() => 1);\n        }\n        // Padding\n        if (padding === 'max_length' && inputIds.length < maxLength) {\n            const padLength = maxLength - inputIds.length;\n            inputIds = [...inputIds, ...new Array(padLength).fill(this.padTokenId)];\n            if (returnAttentionMask) {\n                attentionMask = [...attentionMask, ...new Array(padLength).fill(0)];\n            }\n            if (tokenTypeIds) {\n                tokenTypeIds = [...tokenTypeIds, ...new Array(padLength).fill(0)];\n            }\n        }\n        const result = {\n            inputIds,\n            attentionMask,\n        };\n        if (returnTokenTypeIds && tokenTypeIds) {\n            result.tokenTypeIds = tokenTypeIds;\n        }\n        return result;\n    }\n    /**\n     * Batch encode\n     */\n    encodeBatch(texts, options = {}) {\n        // For 'longest' padding, first encode all without padding\n        if (options.padding === 'longest') {\n            const encodings = texts.map(t => this.encode(t, { ...options, padding: 'do_not_pad' }));\n            const maxLen = Math.max(...encodings.map(e => e.inputIds.length));\n            return texts.map(t => this.encode(t, { ...options, maxLength: maxLen, padding: 'max_length' }));\n        }\n        return texts.map(t => this.encode(t, options));\n    }\n    /**\n     * Decode IDs to text\n     */\n    decode(ids, skipSpecialTokens = true) {\n        let tokens = this.convertIdsToTokens(ids);\n        // Filter special tokens\n        if (skipSpecialTokens) {\n            tokens = tokens.filter(t => !this.specialTokens.has(t));\n        }\n        // Join tokens\n        let text = tokens.join('');\n        // For BPE, decode bytes\n        if (this.modelType === 'BPE') {\n            text = this.bytesToText(text);\n        }\n        // For WordPiece, handle ## prefix\n        if (this.modelType === 'WordPiece') {\n            text = text.replace(new RegExp(this.continuingSubwordPrefix, 'g'), '');\n        }\n        // Clean up whitespace\n        text = text.replace(/\\s+/g, ' ').trim();\n        return text;\n    }\n    /**\n     * Decode batch\n     */\n    decodeBatch(batchIds, skipSpecialTokens = true) {\n        return batchIds.map(ids => this.decode(ids, skipSpecialTokens));\n    }\n    /**\n     * Get vocabulary size\n     */\n    get vocabSize() {\n        return this.vocab.size + this.addedTokens.size;\n    }\n    /**\n     * Get special token IDs\n     */\n    getSpecialTokenIds() {\n        return {\n            padTokenId: this.padTokenId,\n            unkTokenId: this.unkTokenId,\n            clsTokenId: this.clsTokenId,\n            sepTokenId: this.sepTokenId,\n            maskTokenId: this.maskTokenId,\n            bosTokenId: this.bosTokenId,\n            eosTokenId: this.eosTokenId,\n        };\n    }\n    /**\n     * Get config\n     */\n    getConfig() {\n        return {\n            vocabSize: this.vocabSize,\n            maxLength: this.maxLength,\n            padTokenId: this.padTokenId,\n            unkTokenId: this.unkTokenId,\n            clsTokenId: this.clsTokenId,\n            sepTokenId: this.sepTokenId,\n            maskTokenId: this.maskTokenId,\n            bosTokenId: this.bosTokenId,\n            eosTokenId: this.eosTokenId,\n        };\n    }\n    /**\n     * Check if token is special\n     */\n    isSpecialToken(token) {\n        return this.specialTokens.has(token);\n    }\n    /**\n     * Get token ID\n     */\n    getTokenId(token) {\n        return this.addedTokens.get(token) ?? this.vocab.get(token);\n    }\n    /**\n     * Get token from ID\n     */\n    getToken(id) {\n        return this.reverseVocab.get(id);\n    }\n}\n// ============================================================================\n// Factory Functions\n// ============================================================================\n/**\n * Create a basic English tokenizer (for testing)\n */\nexport function createBasicTokenizer() {\n    const tokenizer = new Tokenizer();\n    return tokenizer;\n}\n/**\n * Load tokenizer from URL\n */\nexport async function loadTokenizer(url) {\n    return Tokenizer.fromUrl(url);\n}\n/**\n * Load tokenizer from HuggingFace Hub\n */\nexport async function loadTokenizerFromHub(modelId, options) {\n    return Tokenizer.fromHuggingFace(modelId, options);\n}\n//# sourceMappingURL=tokenizer.js.map"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "content": "import { defineConfig } from 'vitepress';\n\nexport default defineConfig({\n  title: 'edgeFlow.js',\n  description: 'Production runtime for browser ML inference',\n  base: '/',\n\n  themeConfig: {\n    logo: '/logo.svg',\n    nav: [\n      { text: 'Guide', link: '/guide/installation' },\n      { text: 'API', link: '/api/pipeline' },\n      { text: 'Cookbook', link: '/cookbook/transformers-adapter' },\n      {\n        text: 'v0.1.0',\n        items: [\n          { text: 'Changelog', link: '/changelog' },\n          { text: 'GitHub', link: 'https://github.com/s-zx/edgeflow.js' },\n        ],\n      },\n    ],\n\n    sidebar: {\n      '/guide/': [\n        {\n          text: 'Getting Started',\n          items: [\n            { text: 'Installation', link: '/guide/installation' },\n            { text: 'Quick Start', link: '/guide/quickstart' },\n            { text: 'Core Concepts', link: '/guide/concepts' },\n          ],\n        },\n        {\n          text: 'Architecture',\n          items: [\n            { text: 'Overview', link: '/guide/architecture' },\n            { text: 'Plugin System', link: '/guide/plugins' },\n            { text: 'Device Profiling', link: '/guide/device-profiling' },\n          ],\n        },\n      ],\n      '/api/': [\n        {\n          text: 'API Reference',\n          items: [\n            { text: 'pipeline()', link: '/api/pipeline' },\n            { text: 'compose() / parallel()', link: '/api/composer' },\n            { text: 'Tensor', link: '/api/tensor' },\n            { text: 'Tokenizer', link: '/api/tokenizer' },\n            { text: 'Model Loader', link: '/api/model-loader' },\n            { text: 'Scheduler', link: '/api/scheduler' },\n            { text: 'Memory', link: '/api/memory' },\n          ],\n        },\n      ],\n      '/cookbook/': [\n        {\n          text: 'Recipes',\n          items: [\n            { text: 'transformers.js Adapter', link: '/cookbook/transformers-adapter' },\n            { text: 'Pipeline Composition', link: '/cookbook/composition' },\n            { text: 'Offline-First App', link: '/cookbook/offline' },\n            { text: 'Multi-Model Dashboard', link: '/cookbook/multi-model' },\n          ],\n        },\n      ],\n    },\n\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/s-zx/edgeflow.js' },\n      { icon: 'npm', link: 'https://www.npmjs.com/package/edgeflowjs' },\n    ],\n\n    search: { provider: 'local' },\n\n    footer: {\n      message: 'Released under the MIT License.',\n      copyright: 'Copyright 2026 edgeFlow.js Contributors',\n    },\n  },\n});\n"
  },
  {
    "path": "docs/api/model-loader.md",
    "content": "# Model Loader API\n\n## 模型加载\n\n### loadModel()\n\n加载模型并准备推理。\n\n```typescript\nasync function loadModel(\n  url: string,\n  options?: ModelLoaderOptions\n): Promise<LoadedModel>\n```\n\n### loadModelData()\n\n加载模型数据（ArrayBuffer），支持缓存和断点续传。\n\n```typescript\nasync function loadModelData(\n  url: string,\n  options?: ModelLoaderOptions\n): Promise<ArrayBuffer>\n```\n\n### ModelLoaderOptions\n\n```typescript\ninterface ModelLoaderOptions {\n  // 启用缓存（默认: true）\n  cache?: boolean;\n  \n  // 强制重新下载\n  forceDownload?: boolean;\n  \n  // 启用断点续传（默认: true）\n  resumable?: boolean;\n  \n  // 分片大小（默认: 5MB）\n  chunkSize?: number;\n  \n  // 并行下载连接数（默认: 4）\n  parallelConnections?: number;\n  \n  // 超时时间（默认: 30000ms）\n  timeout?: number;\n  \n  // 进度回调\n  onProgress?: (progress: DownloadProgress) => void;\n}\n```\n\n### DownloadProgress\n\n```typescript\ninterface DownloadProgress {\n  loaded: number;      // 已下载字节数\n  total: number;       // 总字节数\n  percent: number;     // 进度百分比 (0-100)\n  speed: number;       // 下载速度 (bytes/sec)\n  eta: number;         // 预计剩余时间 (ms)\n  currentChunk?: number;  // 当前分片\n  totalChunks?: number;   // 总分片数\n}\n```\n\n### 示例\n\n```typescript\nimport { loadModelData } from 'edgeflowjs';\n\nconst modelData = await loadModelData(\n  'https://example.com/model.onnx',\n  {\n    resumable: true,\n    chunkSize: 10 * 1024 * 1024, // 10MB\n    parallelConnections: 4,\n    onProgress: (p) => {\n      console.log(`${p.percent.toFixed(1)}%`);\n      console.log(`Speed: ${(p.speed / 1024 / 1024).toFixed(2)} MB/s`);\n    }\n  }\n);\n```\n\n---\n\n## 预加载\n\n### preloadModel()\n\n后台预加载单个模型。\n\n```typescript\nfunction preloadModel(\n  url: string,\n  options?: PreloadOptions\n): Promise<ArrayBuffer>\n```\n\n### preloadModels()\n\n预加载多个模型。\n\n```typescript\nfunction preloadModels(\n  urls: Array<{ url: string; priority?: number }>,\n  options?: Omit<PreloadOptions, 'priority'>\n): Promise<ArrayBuffer[]>\n```\n\n### PreloadOptions\n\n```typescript\ninterface PreloadOptions extends ModelLoaderOptions {\n  // 优先级（越大越优先，默认: 0）\n  priority?: number;\n}\n```\n\n### 示例\n\n```typescript\nimport { preloadModel, preloadModels } from 'edgeflowjs';\n\n// 预加载单个模型\npreloadModel('https://example.com/model1.onnx', { priority: 10 });\n\n// 预加载多个模型\npreloadModels([\n  { url: 'https://example.com/model1.onnx', priority: 10 },\n  { url: 'https://example.com/model2.onnx', priority: 5 },\n]);\n```\n\n---\n\n## 预加载状态\n\n### getPreloadStatus()\n\n获取预加载状态。\n\n```typescript\nfunction getPreloadStatus(\n  url: string\n): 'pending' | 'loading' | 'complete' | 'error' | 'not_found'\n```\n\n### getPreloadedModel()\n\n获取预加载的模型数据。\n\n```typescript\nasync function getPreloadedModel(\n  url: string\n): Promise<ArrayBuffer | null>\n```\n\n### cancelPreload()\n\n取消预加载。\n\n```typescript\nfunction cancelPreload(url: string): void\n```\n\n---\n\n## 缓存管理\n\n### isModelCached()\n\n检查模型是否已缓存。\n\n```typescript\nasync function isModelCached(url: string): Promise<boolean>\n```\n\n### getCachedModel()\n\n获取缓存的模型数据。\n\n```typescript\nasync function getCachedModel(url: string): Promise<ArrayBuffer | null>\n```\n\n### deleteCachedModel()\n\n删除指定的缓存模型。\n\n```typescript\nasync function deleteCachedModel(url: string): Promise<void>\n```\n\n### clearModelCache()\n\n清除所有缓存的模型。\n\n```typescript\nasync function clearModelCache(): Promise<void>\n```\n\n### getModelCacheStats()\n\n获取缓存统计。\n\n```typescript\nasync function getModelCacheStats(): Promise<{\n  models: number;     // 缓存的模型数量\n  totalSize: number;  // 总大小（字节）\n}>\n```\n\n### 示例\n\n```typescript\nimport { \n  isModelCached, \n  getCachedModel, \n  clearModelCache,\n  getModelCacheStats \n} from 'edgeflowjs';\n\n// 检查缓存\nif (await isModelCached(modelUrl)) {\n  console.log('模型已缓存');\n}\n\n// 获取统计\nconst stats = await getModelCacheStats();\nconsole.log(`${stats.models} 个模型，共 ${stats.totalSize} 字节`);\n\n// 清除缓存\nawait clearModelCache();\n```\n\n---\n\n## HuggingFace Hub\n\n### fromHub()\n\n从 HuggingFace Hub 加载模型包。\n\n```typescript\nasync function fromHub(\n  modelId: string,\n  options?: { revision?: string; cache?: boolean }\n): Promise<ModelBundle>\n```\n\n### fromTask()\n\n按任务加载推荐模型。\n\n```typescript\nasync function fromTask(\n  task: PipelineTask,\n  options?: { modelId?: string; revision?: string; cache?: boolean }\n): Promise<ModelBundle>\n```\n\n### ModelBundle\n\n```typescript\ninterface ModelBundle {\n  modelUrl: string;\n  tokenizer?: Tokenizer;\n  preprocessor?: ImagePreprocessor | AudioPreprocessor;\n  config?: Record<string, unknown>;\n  modelId: string;\n}\n```\n\n### 示例\n\n```typescript\nimport { fromHub, fromTask } from 'edgeflowjs';\n\n// 按模型 ID\nconst bundle = await fromHub('bert-base-uncased');\nconsole.log(bundle.tokenizer);\n\n// 按任务\nconst bundle = await fromTask('text-classification');\n```\n\n---\n\n## 类型定义\n\n```typescript\n// 加载的模型\ninterface LoadedModel {\n  id: string;\n  url?: string;\n  metadata: ModelMetadata;\n  run(inputs: EdgeFlowTensor[]): Promise<EdgeFlowTensor[]>;\n  dispose(): void;\n}\n\n// 模型元数据\ninterface ModelMetadata {\n  name?: string;\n  inputs: TensorInfo[];\n  outputs: TensorInfo[];\n  runtime?: string;\n}\n\n// 张量信息\ninterface TensorInfo {\n  name: string;\n  shape: number[];\n  dtype: DataType;\n}\n```\n"
  },
  {
    "path": "docs/api/pipeline.md",
    "content": "# Pipeline API\n\n## pipeline()\n\n创建指定任务的 Pipeline。\n\n```typescript\nfunction pipeline(\n  task: PipelineTask,\n  options?: PipelineOptions\n): Promise<BasePipeline>\n```\n\n### 参数\n\n| 参数 | 类型 | 描述 |\n|------|------|------|\n| task | `PipelineTask` | 任务类型 |\n| options | `PipelineOptions` | 配置选项 |\n\n### PipelineTask\n\n```typescript\ntype PipelineTask = \n  | 'text-classification'\n  | 'sentiment-analysis'\n  | 'feature-extraction'\n  | 'image-classification'\n  | 'text-generation'\n  | 'object-detection'\n  | 'automatic-speech-recognition'\n  | 'zero-shot-classification'\n  | 'question-answering';\n```\n\n### PipelineOptions\n\n```typescript\ninterface PipelineOptions {\n  // 模型 ID（HuggingFace Hub）\n  modelId?: string;\n  \n  // 模型 URL\n  modelUrl?: string;\n  \n  // 执行后端\n  runtime?: 'auto' | 'webgpu' | 'webnn' | 'wasm' | 'onnx';\n  \n  // 是否启用缓存\n  cache?: boolean;\n  \n  // 加载进度回调\n  onProgress?: (progress: number) => void;\n}\n```\n\n### 示例\n\n```typescript\n// 基本用法\nconst classifier = await pipeline('text-classification');\n\n// 自定义模型\nconst classifier = await pipeline('text-classification', {\n  modelId: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'\n});\n\n// 指定后端\nconst classifier = await pipeline('text-classification', {\n  runtime: 'webgpu'\n});\n```\n\n---\n\n## TextClassificationPipeline\n\n文本分类 Pipeline。\n\n### run()\n\n```typescript\nasync run(\n  input: string | string[],\n  options?: TextClassificationOptions\n): Promise<TextClassificationResult | TextClassificationResult[]>\n```\n\n#### TextClassificationOptions\n\n```typescript\ninterface TextClassificationOptions {\n  // 返回前 K 个结果\n  topK?: number;\n  \n  // 置信度阈值\n  threshold?: number;\n}\n```\n\n#### TextClassificationResult\n\n```typescript\ninterface TextClassificationResult {\n  label: string;\n  score: number;\n}\n```\n\n### 示例\n\n```typescript\nconst classifier = await pipeline('text-classification');\n\n// 单个输入\nconst result = await classifier.run('I love this!');\n// { label: 'positive', score: 0.98 }\n\n// 批量输入\nconst results = await classifier.run(['Good', 'Bad']);\n// [{ label: 'positive', ... }, { label: 'negative', ... }]\n\n// 返回多个结果\nconst results = await classifier.run('Interesting', { topK: 3 });\n```\n\n---\n\n## TextGenerationPipeline\n\n文本生成 Pipeline。\n\n### run()\n\n```typescript\nasync run(\n  input: string,\n  options?: TextGenerationOptions\n): Promise<TextGenerationResult>\n```\n\n### stream()\n\n流式生成文本。\n\n```typescript\nasync *stream(\n  input: string,\n  options?: TextGenerationOptions\n): AsyncGenerator<GenerationStreamEvent>\n```\n\n#### TextGenerationOptions\n\n```typescript\ninterface TextGenerationOptions {\n  // 最大生成 token 数\n  maxNewTokens?: number;\n  \n  // 温度（越高越随机）\n  temperature?: number;\n  \n  // Top-K 采样\n  topK?: number;\n  \n  // Top-P (nucleus) 采样\n  topP?: number;\n  \n  // 重复惩罚\n  repetitionPenalty?: number;\n  \n  // 停止词\n  stopSequences?: string[];\n  \n  // 是否使用采样\n  doSample?: boolean;\n}\n```\n\n### 示例\n\n```typescript\nconst generator = await pipeline('text-generation');\n\n// 基本生成\nconst result = await generator.run('Once upon a time', {\n  maxNewTokens: 50\n});\nconsole.log(result.generatedText);\n\n// 流式生成\nfor await (const event of generator.stream('Hello, ')) {\n  process.stdout.write(event.token);\n  if (event.done) break;\n}\n```\n\n---\n\n## FeatureExtractionPipeline\n\n特征提取 Pipeline。\n\n### run()\n\n```typescript\nasync run(\n  input: string | string[],\n  options?: FeatureExtractionOptions\n): Promise<FeatureExtractionResult | FeatureExtractionResult[]>\n```\n\n#### FeatureExtractionOptions\n\n```typescript\ninterface FeatureExtractionOptions {\n  // 池化策略\n  pooling?: 'mean' | 'cls' | 'none';\n  \n  // 是否归一化\n  normalize?: boolean;\n}\n```\n\n### 示例\n\n```typescript\nconst extractor = await pipeline('feature-extraction');\n\nconst result = await extractor.run('Hello world', {\n  pooling: 'mean',\n  normalize: true\n});\nconsole.log(result.embeddings); // Float32Array\n```\n\n---\n\n## ImageClassificationPipeline\n\n图像分类 Pipeline。\n\n### run()\n\n```typescript\nasync run(\n  input: ImageInput | ImageInput[],\n  options?: ImageClassificationOptions\n): Promise<ImageClassificationResult | ImageClassificationResult[]>\n\ntype ImageInput = string | HTMLImageElement | HTMLCanvasElement | ImageData;\n```\n\n### 示例\n\n```typescript\nconst classifier = await pipeline('image-classification');\n\n// 从 URL\nconst result = await classifier.run('https://example.com/cat.jpg');\n\n// 从 HTMLImageElement\nconst img = document.getElementById('myImage');\nconst result = await classifier.run(img);\n```\n\n---\n\n## QuestionAnsweringPipeline\n\n问答 Pipeline。\n\n### run()\n\n```typescript\nasync run(\n  input: { question: string; context: string },\n  options?: QuestionAnsweringOptions\n): Promise<QuestionAnsweringResult>\n```\n\n### 示例\n\n```typescript\nconst qa = await pipeline('question-answering');\n\nconst result = await qa.run({\n  question: 'What is the capital of France?',\n  context: 'Paris is the capital and largest city of France.'\n});\nconsole.log(result.answer); // 'Paris'\n```\n\n---\n\n## ZeroShotClassificationPipeline\n\n零样本分类 Pipeline。\n\n### classify()\n\n```typescript\nasync classify(\n  text: string,\n  candidateLabels: string[],\n  options?: ZeroShotOptions\n): Promise<ZeroShotClassificationResult>\n```\n\n### 示例\n\n```typescript\nconst classifier = await pipeline('zero-shot-classification');\n\nconst result = await classifier.classify(\n  'I love playing soccer',\n  ['sports', 'music', 'technology']\n);\nconsole.log(result.labels);  // ['sports', 'music', 'technology']\nconsole.log(result.scores);  // [0.92, 0.05, 0.03]\n```\n\n---\n\n## Pipeline 共有方法\n\n### dispose()\n\n释放 Pipeline 占用的资源。\n\n```typescript\npipeline.dispose(): void\n```\n\n### initialize()\n\n手动初始化 Pipeline（通常由 `pipeline()` 自动调用）。\n\n```typescript\nawait pipeline.initialize(): Promise<void>\n```\n"
  },
  {
    "path": "docs/api/tensor.md",
    "content": "# Tensor API\n\n## EdgeFlowTensor\n\n核心张量类，用于存储和操作多维数组。\n\n### 构造函数\n\n```typescript\nnew EdgeFlowTensor(\n  data: TypedArray | number[],\n  shape: number[],\n  dtype?: DataType\n)\n```\n\n#### 参数\n\n| 参数 | 类型 | 描述 |\n|------|------|------|\n| data | `TypedArray \\| number[]` | 数据 |\n| shape | `number[]` | 形状 |\n| dtype | `DataType` | 数据类型（默认: `'float32'`） |\n\n#### DataType\n\n```typescript\ntype DataType = \n  | 'float32' \n  | 'float16' \n  | 'int32' \n  | 'int64' \n  | 'uint8' \n  | 'int8' \n  | 'bool';\n```\n\n### 属性\n\n| 属性 | 类型 | 描述 |\n|------|------|------|\n| `id` | `string` | 唯一标识符 |\n| `shape` | `readonly number[]` | 张量形状 |\n| `dtype` | `DataType` | 数据类型 |\n| `size` | `number` | 元素总数 |\n| `data` | `TypedArray` | 底层数据 |\n| `isDisposed` | `boolean` | 是否已释放 |\n\n### 示例\n\n```typescript\nimport { EdgeFlowTensor } from 'edgeflowjs';\n\n// 创建 1D 张量\nconst t1 = new EdgeFlowTensor([1, 2, 3, 4], [4]);\n\n// 创建 2D 张量\nconst t2 = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\n\n// 指定数据类型\nconst int64Tensor = new EdgeFlowTensor([1, 2, 3], [3], 'int64');\n```\n\n---\n\n## 数据访问\n\n### get()\n\n获取指定索引的元素。\n\n```typescript\nget(...indices: number[]): number\n```\n\n```typescript\nconst tensor = new EdgeFlowTensor([1, 2, 3, 4], [2, 2]);\ntensor.get(0, 0); // 1\ntensor.get(1, 1); // 4\n```\n\n### set()\n\n设置指定索引的元素。\n\n```typescript\nset(value: number, ...indices: number[]): void\n```\n\n```typescript\ntensor.set(99, 0, 0);\ntensor.get(0, 0); // 99\n```\n\n### toArray()\n\n转换为普通数组。\n\n```typescript\ntoArray(): number[]\n```\n\n### toFloat32Array()\n\n转换为 Float32Array。\n\n```typescript\ntoFloat32Array(): Float32Array\n```\n\n---\n\n## 形状操作\n\n### reshape()\n\n改变张量形状（不改变数据）。\n\n```typescript\nreshape(newShape: number[]): EdgeFlowTensor\n```\n\n```typescript\nconst t = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\nconst reshaped = t.reshape([3, 2]);\n// shape: [3, 2]\n```\n\n### transpose()\n\n转置 2D 张量。\n\n```typescript\ntranspose(): EdgeFlowTensor\n```\n\n```typescript\nconst t = new EdgeFlowTensor([1, 2, 3, 4], [2, 2]);\nconst transposed = t.transpose();\n// [[1, 3], [2, 4]]\n```\n\n---\n\n## 克隆与释放\n\n### clone()\n\n创建张量的深拷贝。\n\n```typescript\nclone(): EdgeFlowTensor\n```\n\n```typescript\nconst original = new EdgeFlowTensor([1, 2, 3], [3]);\nconst cloned = original.clone();\n// 修改 original 不影响 cloned\n```\n\n### dispose()\n\n释放张量占用的资源。\n\n```typescript\ndispose(): void\n```\n\n```typescript\nconst tensor = new EdgeFlowTensor([1, 2, 3], [3]);\ntensor.dispose();\nconsole.log(tensor.isDisposed); // true\n```\n\n::: warning\n释放后的张量不能再使用，调用任何方法都会抛出错误。\n:::\n\n---\n\n## 辅助函数\n\n### tensor()\n\n创建张量的便捷函数。\n\n```typescript\nimport { tensor } from 'edgeflowjs';\n\nconst t = tensor([1, 2, 3, 4], [2, 2]);\n```\n\n### zeros()\n\n创建全零张量。\n\n```typescript\nimport { zeros } from 'edgeflowjs';\n\nconst t = zeros([3, 3]); // 3x3 全零矩阵\n```\n\n### ones()\n\n创建全一张量。\n\n```typescript\nimport { ones } from 'edgeflowjs';\n\nconst t = ones([2, 4]); // 2x4 全一矩阵\n```\n\n---\n\n## 类型定义\n\n```typescript\n// 张量接口\ninterface Tensor {\n  readonly id: string;\n  readonly shape: Shape;\n  readonly dtype: DataType;\n  readonly size: number;\n  readonly data: TypedArray;\n  readonly isDisposed: boolean;\n  \n  get(...indices: number[]): number;\n  set(value: number, ...indices: number[]): void;\n  reshape(newShape: Shape): Tensor;\n  transpose(): Tensor;\n  clone(): Tensor;\n  dispose(): void;\n  toArray(): number[];\n}\n\n// 形状类型\ntype Shape = readonly number[];\n\n// TypedArray 类型\ntype TypedArray = \n  | Float32Array \n  | Int32Array \n  | BigInt64Array \n  | Uint8Array \n  | Int8Array;\n```\n"
  },
  {
    "path": "docs/api/tokenizer.md",
    "content": "# Tokenizer API\n\n## Tokenizer\n\n文本分词器，支持 BPE 和 WordPiece 算法。\n\n### 静态方法\n\n#### fromJSON()\n\n从 JSON 配置创建分词器。\n\n```typescript\nstatic async fromJSON(\n  json: HFTokenizerJSON | string\n): Promise<Tokenizer>\n```\n\n#### fromUrl()\n\n从 URL 加载分词器。\n\n```typescript\nstatic async fromUrl(url: string): Promise<Tokenizer>\n```\n\n#### fromHuggingFace()\n\n从 HuggingFace Hub 加载分词器。\n\n```typescript\nstatic async fromHuggingFace(\n  modelId: string,\n  options?: { revision?: string }\n): Promise<Tokenizer>\n```\n\n### 示例\n\n```typescript\nimport { Tokenizer } from 'edgeflowjs';\n\n// 从 HuggingFace\nconst tokenizer = await Tokenizer.fromHuggingFace('bert-base-uncased');\n\n// 从 URL\nconst tokenizer = await Tokenizer.fromUrl(\n  'https://huggingface.co/bert-base-uncased/raw/main/tokenizer.json'\n);\n```\n\n---\n\n## encode()\n\n将文本编码为 token ID。\n\n```typescript\nencode(\n  text: string,\n  options?: TokenizerOptions\n): TokenizedOutput\n```\n\n### TokenizerOptions\n\n```typescript\ninterface TokenizerOptions {\n  // 是否添加特殊 token（如 [CLS], [SEP]）\n  addSpecialTokens?: boolean;\n  \n  // 最大长度\n  maxLength?: number;\n  \n  // 填充策略\n  padding?: 'max_length' | 'longest' | false;\n  \n  // 是否截断\n  truncation?: boolean;\n  \n  // 是否返回 attention mask\n  returnAttentionMask?: boolean;\n  \n  // 是否返回 token type IDs\n  returnTokenTypeIds?: boolean;\n}\n```\n\n### TokenizedOutput\n\n```typescript\ninterface TokenizedOutput {\n  inputIds: number[];\n  attentionMask?: number[];\n  tokenTypeIds?: number[];\n}\n```\n\n### 示例\n\n```typescript\nconst encoded = tokenizer.encode('Hello world', {\n  addSpecialTokens: true,\n  maxLength: 128,\n  padding: 'max_length',\n  returnAttentionMask: true,\n});\n\nconsole.log(encoded.inputIds);      // [101, 7592, 2088, 102, 0, ...]\nconsole.log(encoded.attentionMask); // [1, 1, 1, 1, 0, ...]\n```\n\n---\n\n## encodeBatch()\n\n批量编码多个文本。\n\n```typescript\nencodeBatch(\n  texts: string[],\n  options?: TokenizerOptions\n): TokenizedOutput[]\n```\n\n### 示例\n\n```typescript\nconst batch = tokenizer.encodeBatch(['Hello', 'World'], {\n  padding: 'longest',\n});\n// 两个编码结果长度相同\n```\n\n---\n\n## decode()\n\n将 token ID 解码为文本。\n\n```typescript\ndecode(\n  ids: number[],\n  skipSpecialTokens?: boolean\n): string\n```\n\n### 示例\n\n```typescript\nconst text = tokenizer.decode([101, 7592, 2088, 102], true);\nconsole.log(text); // \"hello world\"\n```\n\n---\n\n## decodeBatch()\n\n批量解码。\n\n```typescript\ndecodeBatch(\n  batchIds: number[][],\n  skipSpecialTokens?: boolean\n): string[]\n```\n\n---\n\n## Token/ID 转换\n\n### getTokenId()\n\n获取 token 对应的 ID。\n\n```typescript\ngetTokenId(token: string): number | undefined\n```\n\n### getToken()\n\n获取 ID 对应的 token。\n\n```typescript\ngetToken(id: number): string | undefined\n```\n\n### 示例\n\n```typescript\ntokenizer.getTokenId('hello');  // 7592\ntokenizer.getToken(7592);       // 'hello'\n```\n\n---\n\n## 特殊 Token\n\n### isSpecialToken()\n\n判断是否为特殊 token。\n\n```typescript\nisSpecialToken(token: string): boolean\n```\n\n### getSpecialTokenIds()\n\n获取特殊 token ID 映射。\n\n```typescript\ngetSpecialTokenIds(): {\n  padTokenId: number;\n  unkTokenId: number;\n  clsTokenId?: number;\n  sepTokenId?: number;\n  maskTokenId?: number;\n  bosTokenId?: number;\n  eosTokenId?: number;\n}\n```\n\n---\n\n## 配置信息\n\n### getConfig()\n\n获取分词器配置。\n\n```typescript\ngetConfig(): {\n  vocabSize: number;\n  maxLength: number;\n  padToken?: string;\n  unkToken?: string;\n  // ...\n}\n```\n\n### vocabSize\n\n词汇表大小。\n\n```typescript\nreadonly vocabSize: number\n```\n\n---\n\n## 类型定义\n\n```typescript\n// HuggingFace tokenizer.json 格式\ninterface HFTokenizerJSON {\n  version: string;\n  truncation?: object;\n  padding?: object;\n  added_tokens: Array<{\n    id: number;\n    content: string;\n    special: boolean;\n  }>;\n  normalizer?: object;\n  pre_tokenizer?: object;\n  post_processor?: object;\n  decoder?: object;\n  model: {\n    type: 'WordPiece' | 'BPE' | 'Unigram';\n    vocab: Record<string, number>;\n    unk_token?: string;\n    // ...\n  };\n}\n```\n"
  },
  {
    "path": "docs/cookbook/composition.md",
    "content": "# Pipeline Composition\n\nChain multiple ML models together to build complex workflows. No other browser ML framework supports this natively.\n\n## Sequential Composition\n\nProcess data through a sequence of models. Each stage's output feeds the next stage's input.\n\n```typescript\nimport { compose } from 'edgeflowjs';\n\nconst analyzer = compose([\n  { task: 'automatic-speech-recognition' },\n  {\n    task: 'text-classification',\n    transform: (asrResult: any) => asrResult.text,\n  },\n]);\n\nconst { output, stages, totalTime } = await analyzer.run(audioBlob);\n// stages[0] = ASR result\n// stages[1] = classification result\n// output    = final classification\n```\n\n## Parallel Composition\n\nRun multiple models on the same input simultaneously:\n\n```typescript\nimport { parallel } from 'edgeflowjs';\n\nconst multiAnalysis = parallel([\n  { task: 'text-classification' },\n  { task: 'feature-extraction' },\n  {\n    task: 'zero-shot-classification',\n    transform: (text) => ({\n      text,\n      candidateLabels: ['tech', 'sports', 'politics'],\n    }),\n  },\n]);\n\nconst { outputs, totalTime } = await multiAnalysis.run('Breaking news today');\n// outputs[0] = classification result\n// outputs[1] = embedding result\n// outputs[2] = zero-shot result\n```\n\n## Transform Functions\n\nUse `transform` to reshape data between stages:\n\n```typescript\ncompose([\n  { task: 'image-segmentation' },\n  {\n    task: 'image-classification',\n    transform: (segResult: any) => {\n      // Extract the largest segment and classify it\n      return segResult.masks[0].croppedImage;\n    },\n  },\n]);\n```\n\n## Resource Management\n\nComposed pipelines support `dispose()` to clean up all underlying models:\n\n```typescript\nconst pipeline = compose([...]);\nconst result = await pipeline.run(input);\n\n// When done\npipeline.dispose(); // disposes all stage pipelines\n```\n\n## API\n\n| Function | Description |\n|----------|-------------|\n| `compose(stages)` | Chain stages sequentially (output → input) |\n| `parallel(stages)` | Run stages concurrently on the same input |\n| `ComposedPipeline.run(input)` | Execute the full chain |\n| `ComposedPipeline.dispose()` | Clean up all pipelines |\n| `ComposedPipeline.length` | Number of stages |\n"
  },
  {
    "path": "docs/cookbook/transformers-adapter.md",
    "content": "# transformers.js Adapter\n\nUse edgeFlow.js as an orchestration layer on top of [transformers.js](https://huggingface.co/docs/transformers.js) to access 1000+ HuggingFace models with scheduling, caching, and memory management.\n\n## Installation\n\n```bash\nnpm install edgeflowjs @xenova/transformers\n```\n\n## Setup\n\n```typescript\nimport { pipeline as tfPipeline } from '@xenova/transformers';\nimport { useTransformersBackend, pipeline, configureScheduler } from 'edgeflowjs';\n\n// Register transformers.js as the inference backend\nuseTransformersBackend({\n  pipelineFactory: tfPipeline,\n  device: 'webgpu',    // GPU acceleration\n  dtype: 'fp16',       // Half precision for speed\n});\n\n// Optional: configure edgeFlow.js scheduling\nconfigureScheduler({\n  maxConcurrentTasks: 4,\n  maxConcurrentPerModel: 1,\n  maxRetries: 2,\n  circuitBreaker: true,\n});\n```\n\n## Usage\n\nAfter setup, use the standard edgeFlow.js API. All inference calls go through transformers.js but with edgeFlow.js orchestration:\n\n```typescript\n// Sentiment analysis with scheduling + caching\nconst classifier = await pipeline('text-classification', {\n  model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n});\n\nconst result = await classifier.run('I love edgeFlow.js!');\n```\n\n## Why Use the Adapter?\n\n| Scenario | transformers.js alone | With edgeFlow.js adapter |\n|----------|----------------------|--------------------------|\n| Run 5 models at once | Uncontrolled memory | Scheduled with limits |\n| Same input repeated | Recomputed | Cached |\n| Model download interrupted | Restart | Resume from last chunk |\n| Task cancellation | Not possible | `task.cancel()` |\n| Performance monitoring | Manual | Built-in dashboard |\n\n## Advanced: Direct Pipeline Access\n\nFor advanced use, access the transformers.js pipeline directly:\n\n```typescript\nimport { getTransformersAdapter } from 'edgeflowjs';\n\nconst adapter = getTransformersAdapter();\nif (adapter) {\n  const modelId = await adapter.loadPipeline(\n    'text-classification',\n    'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n  );\n\n  const result = await adapter.runDirect(modelId, 'Hello world');\n}\n```\n"
  },
  {
    "path": "docs/guide/architecture.md",
    "content": "# Architecture Overview\n\nedgeFlow.js is a **production orchestration layer** for browser ML inference. It does not compete with inference engines like ONNX Runtime or transformers.js — it wraps them and adds the features real applications need.\n\n## Layer Diagram\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                     Your Application                     │\n├──────────────────────────────────────────────────────────┤\n│                      edgeFlow.js                         │\n│  ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐  │\n│  │  Scheduler │ │ Memory   │ │  Composer │ │  Plugin  │  │\n│  │  (priority,│ │ Manager  │ │  (chain / │ │  System  │  │\n│  │  retry,    │ │ (scopes, │ │  parallel)│ │          │  │\n│  │  circuit   │ │  GC,     │ │           │ │          │  │\n│  │  breaker)  │ │  leak    │ │           │ │          │  │\n│  │            │ │  detect) │ │           │ │          │  │\n│  └────────────┘ └──────────┘ └───────────┘ └──────────┘  │\n│  ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐  │\n│  │  Worker    │ │  Cache   │ │  Device   │ │  Monitor │  │\n│  │  Pool      │ │  (LRU,   │ │  Profiler │ │  (perf,  │  │\n│  │            │ │  IndexDB)│ │           │ │  alerts) │  │\n│  └────────────┘ └──────────┘ └───────────┘ └──────────┘  │\n├──────────────────────────────────────────────────────────┤\n│              Inference Backends (pluggable)              │\n│  ┌────────────┐ ┌─────────────────────┐ ┌──────────────┐ │\n│  │ ONNX       │ │ transformers.js     │ │ Custom       │ │\n│  │ Runtime    │ │ Adapter             │ │ Backend      │ │\n│  └────────────┘ └─────────────────────┘ └──────────────┘ │\n└──────────────────────────────────────────────────────────┘\n```\n\n## Key Design Decisions\n\n### Backend Agnosticism\n\nedgeFlow.js does not lock you into a single inference engine. The `Runtime` interface is intentionally minimal:\n\n```typescript\ninterface Runtime {\n  name: RuntimeType;\n  capabilities: RuntimeCapabilities;\n  initialize(): Promise<void>;\n  isAvailable(): Promise<boolean>;\n  loadModel(data: ArrayBuffer, options?): Promise<LoadedModel>;\n  run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n  dispose(): void;\n}\n```\n\nAny engine that can implement this interface can be plugged in. The built-in ONNX Runtime backend and the transformers.js adapter are both implementations of this interface.\n\n### Scheduler-First Architecture\n\nEvery inference call goes through the `InferenceScheduler`:\n\n1. Tasks are enqueued with a priority (`critical`, `high`, `normal`, `low`).\n2. The scheduler respects per-model and global concurrency limits.\n3. Failed tasks are optionally retried with exponential backoff.\n4. A circuit breaker per model prevents cascading failures.\n\n### Memory Scopes\n\nInspired by RAII patterns, `withMemoryScope()` ensures tensors and models are automatically disposed:\n\n```typescript\nconst result = await withMemoryScope(async (scope) => {\n  const a = scope.track(tensor([1, 2, 3]));\n  const b = scope.track(tensor([4, 5, 6]));\n  const output = add(a, b);\n  return scope.keep(output); // keep this, dispose the rest\n});\n```\n\n### Plugin Extensibility\n\nThird-party plugins can register new pipeline tasks, backends, and middleware without modifying edgeFlow.js source. See [Plugin System](/guide/plugins).\n"
  },
  {
    "path": "docs/guide/concepts.md",
    "content": "# 核心概念\n\n## 架构概述\n\nedgeFlow.js 采用分层架构设计：\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                         你的应用                          │\n├──────────────────────────────────────────────────────────┤\n│                      edgeFlow.js                         │\n│  ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐  │\n│  │  Scheduler │ │ Memory   │ │  Composer │ │  Plugin  │  │\n│  │  (priority,│ │ Manager  │ │  (chain / │ │  System  │  │\n│  │  retry,    │ │ (scopes, │ │  parallel)│ │          │  │\n│  │  circuit   │ │  GC,     │ │           │ │          │  │\n│  │  breaker)  │ │  leak    │ │           │ │          │  │\n│  │            │ │  detect) │ │           │ │          │  │\n│  └────────────┘ └──────────┘ └───────────┘ └──────────┘  │\n│  ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐  │\n│  │  Worker    │ │  Cache   │ │  Device   │ │  Monitor │  │\n│  │  Pool      │ │  (LRU,   │ │  Profiler │ │  (perf,  │  │\n│  │            │ │  IndexDB)│ │           │ │  alerts) │  │\n│  └────────────┘ └──────────┘ └───────────┘ └──────────┘  │\n├──────────────────────────────────────────────────────────┤\n│              推理后端（可插拔）                            │\n│  ┌────────────┐ ┌─────────────────────┐ ┌──────────────┐ │\n│  │ ONNX       │ │ transformers.js     │ │ Custom       │ │\n│  │ Runtime    │ │ Adapter             │ │ Backend      │ │\n│  └────────────┘ └─────────────────────┘ └──────────────┘ │\n└──────────────────────────────────────────────────────────┘\n```\n\n## Pipeline\n\nPipeline 是使用 edgeFlow.js 的主要方式。每个 Pipeline 封装了：\n\n- **模型加载** - 自动从 HuggingFace 或本地加载模型\n- **预处理** - 将输入转换为模型所需格式\n- **推理执行** - 运行模型推理\n- **后处理** - 将输出转换为易用的格式\n\n```typescript\nconst pipeline = await pipeline('text-classification');\n// pipeline 内部处理了所有复杂性\nconst result = await pipeline.run('Hello world');\n```\n\n## Tensor\n\nTensor（张量）是多维数组，是机器学习的基础数据结构。\n\n```typescript\nimport { tensor, EdgeFlowTensor } from 'edgeflowjs';\n\n// 创建 1D 张量\nconst t1 = new EdgeFlowTensor([1, 2, 3, 4], [4]);\n\n// 创建 2D 张量\nconst t2 = new EdgeFlowTensor([1, 2, 3, 4], [2, 2]);\n\n// 支持的数据类型\nconst float32 = new EdgeFlowTensor([1.5, 2.5], [2], 'float32');\nconst int64 = new EdgeFlowTensor([1, 2], [2], 'int64');\nconst uint8 = new EdgeFlowTensor([0, 255], [2], 'uint8');\n```\n\n## Scheduler（调度器）\n\n调度器管理并发推理任务：\n\n- **优先级队列** - 高优先级任务先执行\n- **并发控制** - 限制同时运行的任务数\n- **模型隔离** - 每个模型独立的并发限制\n\n```typescript\nimport { getScheduler, TaskPriority } from 'edgeflowjs';\n\nconst scheduler = getScheduler();\n\n// 高优先级任务\nconst task = scheduler.schedule('model-id', async () => {\n  return await runInference();\n}, TaskPriority.HIGH);\n\nawait task.wait();\n```\n\n## Memory Manager（内存管理器）\n\n自动跟踪和管理 GPU/CPU 内存：\n\n```typescript\nimport { getMemoryManager } from 'edgeflowjs';\n\nconst mm = getMemoryManager();\n\n// 查看统计\nconsole.log(mm.getStats());\n// {\n//   allocated: 50000000,  // 50MB\n//   used: 45000000,       // 45MB\n//   peak: 52000000,       // 52MB\n//   tensorCount: 12\n// }\n\n// 手动触发垃圾回收\nmm.gc();\n\n// 检测可能的内存泄漏\nconst leaks = mm.detectLeaks(60000); // 超过 1 分钟的资源\n```\n\n## Backend（后端）\n\nedgeFlow.js 支持多种执行后端：\n\n| 后端 | 描述 | 性能 | 兼容性 |\n|------|------|------|--------|\n| WebGPU | GPU 加速 | ⭐⭐⭐ | Chrome 113+ |\n| WebNN | 硬件加速 | ⭐⭐⭐ | Chrome 113+ |\n| WASM | WebAssembly | ⭐⭐ | 所有浏览器 |\n| ONNX | ONNX Runtime | ⭐⭐⭐ | 所有浏览器 |\n\n后端自动选择最佳可用选项：\n\n```typescript\nconst pipeline = await pipeline('text-classification', {\n  runtime: 'auto' // 默认：自动选择\n});\n\n// 或指定后端\nconst pipeline = await pipeline('text-classification', {\n  runtime: 'webgpu'\n});\n```\n\n## Model Cache（模型缓存）\n\n模型自动缓存到 IndexedDB：\n\n```typescript\nimport { isModelCached, getCachedModel, clearModelCache } from 'edgeflowjs';\n\n// 检查是否已缓存\nif (await isModelCached('https://example.com/model.onnx')) {\n  console.log('模型已缓存');\n}\n\n// 清除缓存\nawait clearModelCache();\n```\n\n## Tokenizer（分词器）\n\n分词器将文本转换为模型可处理的数字：\n\n```typescript\nimport { Tokenizer } from 'edgeflowjs';\n\n// 从 HuggingFace 加载\nconst tokenizer = await Tokenizer.fromHuggingFace('bert-base-uncased');\n\n// 编码\nconst encoded = tokenizer.encode('Hello world', {\n  addSpecialTokens: true,\n  maxLength: 128,\n  padding: 'max_length',\n});\n// { inputIds: [101, 7592, 2088, 102, 0, 0, ...], attentionMask: [...] }\n\n// 解码\nconst text = tokenizer.decode(encoded.inputIds);\n```\n\n## 下一步\n\n- [Pipeline API](../api/pipeline.md)\n- [性能优化](../advanced/performance.md)\n"
  },
  {
    "path": "docs/guide/device-profiling.md",
    "content": "# Device Profiling\n\nedgeFlow.js can automatically profile the current device and recommend optimal model variants.\n\n## Quick Start\n\n```typescript\nimport { getDeviceProfile, recommendModelVariant } from 'edgeflowjs';\n\nconst profile = await getDeviceProfile();\nconsole.log(profile.tier);   // 'high' | 'medium' | 'low'\nconsole.log(profile.webgpu); // true | false\nconsole.log(profile.cores);  // e.g. 8\n\nconst rec = await recommendModelVariant();\nconsole.log(rec.quantization);      // 'float16' | 'int8'\nconsole.log(rec.executionProvider);  // 'webgpu' | 'wasm'\nconsole.log(rec.batchSize);          // e.g. 32\n```\n\n## Device Tiers\n\n| Tier | Criteria | Example Devices |\n|------|----------|-----------------|\n| **high** | WebGPU + 8+ cores + 8+ GB RAM | Desktop with dedicated GPU |\n| **medium** | 4+ cores + 4+ GB RAM | Modern laptop, high-end mobile |\n| **low** | Everything else | Older devices, low-end mobile |\n\n## Using with Pipelines\n\n```typescript\nconst profile = await getDeviceProfile();\nconst rec = await recommendModelVariant();\n\nconst classifier = await pipeline('text-classification', {\n  model: `my-model-${rec.quantization}`,\n  runtime: rec.executionProvider === 'webgpu' ? 'webgpu' : 'wasm',\n});\n```\n\n## API\n\n| Function | Description |\n|----------|-------------|\n| `getDeviceProfile()` | Returns `DeviceProfile` with tier, cores, memory, GPU info |\n| `recommendQuantization(profile)` | Returns best `QuantizationType` for the given profile |\n| `recommendModelVariant()` | Returns full `ModelRecommendation` (quant, provider, batch, worker) |\n| `resetDeviceProfile()` | Clears the cached profile (useful for testing) |\n"
  },
  {
    "path": "docs/guide/installation.md",
    "content": "# 安装\n\n## 通过包管理器安装\n\n### npm\n\n```bash\nnpm install edgeflowjs\n```\n\n### yarn\n\n```bash\nyarn add edgeflowjs\n```\n\n### pnpm\n\n```bash\npnpm add edgeflowjs\n```\n\n## 通过 CDN 使用\n\n```html\n<script type=\"module\">\n  import * as edgeFlow from 'https://cdn.jsdelivr.net/npm/edgeflowjs/dist/edgeflow.browser.min.js';\n  \n  // 使用 edgeFlow\n  const pipeline = await edgeFlow.pipeline('text-classification');\n</script>\n```\n\n## 浏览器兼容性\n\n| 浏览器 | WebGPU | WebNN | WASM |\n|--------|--------|-------|------|\n| Chrome 113+ | ✅ | ✅ | ✅ |\n| Edge 113+ | ✅ | ✅ | ✅ |\n| Firefox 118+ | ⚠️ | ❌ | ✅ |\n| Safari 17+ | ⚠️ | ❌ | ✅ |\n\n## TypeScript 支持\n\nedgeFlow.js 使用 TypeScript 编写，提供完整的类型定义：\n\n```typescript\nimport { pipeline, EdgeFlowTensor, Tokenizer } from 'edgeflowjs';\nimport type { PipelineOptions, TextClassificationResult } from 'edgeflowjs';\n```\n\n## 下一步\n\n- [快速入门](./quickstart.md)\n- [核心概念](./concepts.md)\n"
  },
  {
    "path": "docs/guide/plugins.md",
    "content": "# Plugin System\n\nedgeFlow.js supports plugins that register custom pipelines, backends, and middleware at runtime.\n\n## Creating a Plugin\n\n```typescript\nimport { registerPlugin, type EdgeFlowPlugin } from 'edgeflowjs';\n\nconst myPlugin: EdgeFlowPlugin = {\n  name: 'edgeflow-plugin-whisper',\n  version: '1.0.0',\n\n  pipelines: {\n    'whisper-transcribe': {\n      factory: (config) => new WhisperPipeline(config),\n      description: 'Transcribe audio using Whisper',\n    },\n  },\n\n  setup() {\n    console.log('Whisper plugin loaded');\n  },\n};\n\nawait registerPlugin(myPlugin);\n```\n\nAfter registration, the pipeline is available via:\n\n```typescript\nconst transcriber = await pipeline('whisper-transcribe');\n```\n\n## Plugin Structure\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `string` | Unique plugin identifier |\n| `version` | `string` | Semver version |\n| `pipelines` | `Record<string, PluginPipelineEntry>` | Pipeline factories keyed by task name |\n| `backends` | `Record<string, PluginBackendEntry>` | Backend factories keyed by runtime name |\n| `middleware` | `PluginMiddleware[]` | Before/after inference hooks |\n| `setup` | `() => void \\| Promise<void>` | One-time initialisation |\n\n## Middleware\n\nMiddleware runs before and/or after every inference call:\n\n```typescript\nregisterPlugin({\n  name: 'logger',\n  version: '1.0.0',\n  middleware: [{\n    name: 'inference-logger',\n    before: (ctx) => {\n      console.log(`Running inference on ${ctx.modelId}`);\n      return ctx.inputs;\n    },\n    after: (ctx) => {\n      console.log(`Inference complete on ${ctx.modelId}`);\n      return ctx.outputs;\n    },\n  }],\n});\n```\n\n## Managing Plugins\n\n```typescript\nimport { listPlugins, unregisterPlugin } from 'edgeflowjs';\n\nconsole.log(listPlugins());\n// [{ name: 'edgeflow-plugin-whisper', version: '1.0.0' }]\n\nunregisterPlugin('edgeflow-plugin-whisper');\n```\n"
  },
  {
    "path": "docs/guide/quickstart.md",
    "content": "# 快速入门\n\n本指南将帮助你在 5 分钟内开始使用 edgeFlow.js。\n\n## 基本用法\n\n### 1. 创建 Pipeline\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// 创建文本分类 Pipeline\nconst classifier = await pipeline('text-classification');\n```\n\n### 2. 运行推理\n\n```typescript\nconst result = await classifier.run('I love this product!');\nconsole.log(result);\n// { label: 'positive', score: 0.98 }\n```\n\n### 3. 批量处理\n\n```typescript\nconst results = await classifier.run([\n  'Great product!',\n  'Terrible experience.',\n  'It was okay.',\n]);\n// 返回数组结果\n```\n\n## 支持的任务\n\n| 任务 | Pipeline 名称 | 示例 |\n|------|--------------|------|\n| 文本分类 | `text-classification` | 情感分析 |\n| 特征提取 | `feature-extraction` | 文本嵌入 |\n| 图像分类 | `image-classification` | 图片识别 |\n| 文本生成 | `text-generation` | 续写文本 |\n| 目标检测 | `object-detection` | 检测物体 |\n| 语音识别 | `automatic-speech-recognition` | 语音转文字 |\n| 零样本分类 | `zero-shot-classification` | 无训练分类 |\n| 问答 | `question-answering` | 阅读理解 |\n\n## 使用自定义模型\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\n// 从 HuggingFace 加载自定义模型\nconst classifier = await pipeline('text-classification', {\n  modelId: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'\n});\n```\n\n## 直接从 HuggingFace Hub 加载\n\n```typescript\nimport { fromHub } from 'edgeflowjs';\n\n// 加载模型包（包含模型、分词器、配置）\nconst bundle = await fromHub('bert-base-uncased');\nconsole.log(bundle.tokenizer); // Tokenizer 实例\nconsole.log(bundle.config);    // 模型配置\n```\n\n## 张量操作\n\n```typescript\nimport { tensor } from 'edgeflowjs';\n\n// 创建张量\nconst a = tensor([1, 2, 3, 4], [2, 2]);\nconst b = tensor([5, 6, 7, 8], [2, 2]);\n\n// 矩阵运算\nconst c = a.reshape([4]);\nconst d = a.transpose();\n\n// 清理\na.dispose();\nb.dispose();\n```\n\n## 内存管理\n\n```typescript\nimport { pipeline, getMemoryStats } from 'edgeflowjs';\n\nconst model = await pipeline('text-classification');\nawait model.run('test');\n\n// 检查内存使用\nconsole.log(getMemoryStats());\n\n// 清理\nmodel.dispose();\n```\n\n## 下一步\n\n- [核心概念](./concepts.md) - 深入了解框架架构\n- [API 参考](../api/pipeline.md) - 完整 API 文档\n- [教程](../tutorials/text-classification.md) - 更多示例\n"
  },
  {
    "path": "docs/index.md",
    "content": "# edgeFlow.js 文档\n\n欢迎使用 edgeFlow.js，一个轻量级、高性能的浏览器端机器学习推理框架。\n\n## 快速开始\n\n- [安装](./guide/installation.md)\n- [快速入门](./guide/quickstart.md)\n- [核心概念](./guide/concepts.md)\n\n## API 参考\n\n- [Pipeline API](./api/pipeline.md)\n- [Tensor API](./api/tensor.md)\n- [Tokenizer API](./api/tokenizer.md)\n- [Model Loader API](./api/model-loader.md)\n\n## 教程\n\n- [文本分类](./tutorials/text-classification.md)\n- [图像分类](./tutorials/image-classification.md)\n- [文本生成](./tutorials/text-generation.md)\n- [Web Worker 使用](./tutorials/web-workers.md)\n\n## 进阶\n\n- [性能优化](./advanced/performance.md)\n- [内存管理](./advanced/memory.md)\n- [自定义后端](./advanced/custom-backend.md)\n"
  },
  {
    "path": "docs/tutorials/text-classification.md",
    "content": "# 文本分类教程\n\n本教程将介绍如何使用 edgeFlow.js 进行文本分类任务，如情感分析。\n\n## 基本用法\n\n### 1. 创建 Pipeline\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nconst classifier = await pipeline('text-classification');\n```\n\n### 2. 运行分类\n\n```typescript\nconst result = await classifier.run('I love this product!');\nconsole.log(result);\n// { label: 'positive', score: 0.98 }\n```\n\n## 批量分类\n\n一次性处理多个文本：\n\n```typescript\nconst texts = [\n  'Great product, highly recommended!',\n  'Terrible experience, never again.',\n  'It was okay, nothing special.',\n];\n\nconst results = await classifier.run(texts);\nresults.forEach((result, i) => {\n  console.log(`${texts[i]}: ${result.label} (${result.score.toFixed(2)})`);\n});\n```\n\n## 使用自定义模型\n\n```typescript\nconst classifier = await pipeline('text-classification', {\n  modelId: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'\n});\n```\n\n## 获取多个结果\n\n使用 `topK` 参数获取多个分类结果：\n\n```typescript\nconst results = await classifier.run('The movie was interesting', {\n  topK: 3\n});\n// 返回前 3 个可能的分类\n```\n\n## 多语言支持\n\n```typescript\n// 使用多语言模型\nconst classifier = await pipeline('text-classification', {\n  modelId: 'nlptown/bert-base-multilingual-uncased-sentiment'\n});\n\n// 支持多种语言\nconst results = await classifier.run([\n  'This is great!',      // English\n  'C\\'est magnifique!',  // French\n  '太棒了！',             // Chinese\n]);\n```\n\n## 实时应用示例\n\n### 评论情感分析\n\n```typescript\nimport { pipeline } from 'edgeflowjs';\n\nasync function analyzeComments(comments: string[]) {\n  const classifier = await pipeline('text-classification');\n  \n  const results = await classifier.run(comments);\n  \n  const summary = {\n    positive: 0,\n    negative: 0,\n    neutral: 0,\n  };\n  \n  results.forEach(r => {\n    if (r.score > 0.7) {\n      summary[r.label.toLowerCase()]++;\n    } else {\n      summary.neutral++;\n    }\n  });\n  \n  console.log('评论分析:', summary);\n  \n  classifier.dispose();\n}\n```\n\n### 表单验证\n\n```typescript\nasync function validateFeedback(text: string): Promise<boolean> {\n  const classifier = await pipeline('text-classification');\n  const result = await classifier.run(text);\n  \n  // 拒绝过于负面的内容\n  if (result.label === 'negative' && result.score > 0.9) {\n    return false;\n  }\n  return true;\n}\n```\n\n## 性能优化\n\n### 预加载模型\n\n```typescript\nimport { preloadModel, pipeline } from 'edgeflowjs';\n\n// 页面加载时预加载\npreloadModel('https://example.com/model.onnx');\n\n// 用户点击时立即可用\nbutton.onclick = async () => {\n  const classifier = await pipeline('text-classification');\n  // 模型已预加载，立即可用\n};\n```\n\n### 复用 Pipeline\n\n```typescript\n// ❌ 不好：每次都创建新 Pipeline\nasync function classify(text: string) {\n  const classifier = await pipeline('text-classification');\n  const result = await classifier.run(text);\n  classifier.dispose();\n  return result;\n}\n\n// ✅ 好：复用 Pipeline\nlet classifier: TextClassificationPipeline | null = null;\n\nasync function classify(text: string) {\n  if (!classifier) {\n    classifier = await pipeline('text-classification');\n  }\n  return classifier.run(text);\n}\n```\n\n## 错误处理\n\n```typescript\ntry {\n  const result = await classifier.run(text);\n  console.log(result);\n} catch (error) {\n  if (error.code === 'MODEL_NOT_FOUND') {\n    console.error('模型未找到');\n  } else if (error.code === 'INFERENCE_FAILED') {\n    console.error('推理失败:', error.message);\n  }\n}\n```\n\n## 完整示例\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n  <title>情感分析</title>\n</head>\n<body>\n  <textarea id=\"input\" placeholder=\"输入要分析的文本...\"></textarea>\n  <button id=\"analyze\">分析</button>\n  <div id=\"result\"></div>\n\n  <script type=\"module\">\n    import { pipeline } from 'https://cdn.jsdelivr.net/npm/edgeflowjs/dist/edgeflow.browser.min.js';\n    \n    let classifier = null;\n    \n    document.getElementById('analyze').onclick = async () => {\n      const text = document.getElementById('input').value;\n      const resultDiv = document.getElementById('result');\n      \n      if (!text) return;\n      \n      resultDiv.textContent = '分析中...';\n      \n      try {\n        if (!classifier) {\n          classifier = await pipeline('text-classification');\n        }\n        \n        const result = await classifier.run(text);\n        \n        const emoji = result.label === 'positive' ? '😊' : '😔';\n        resultDiv.textContent = `${emoji} ${result.label} (${(result.score * 100).toFixed(1)}%)`;\n      } catch (error) {\n        resultDiv.textContent = '分析失败: ' + error.message;\n      }\n    };\n  </script>\n</body>\n</html>\n```\n\n## 下一步\n\n- [特征提取](./feature-extraction.md)\n- [图像分类](./image-classification.md)\n- [API 参考](../api/pipeline.md)\n"
  },
  {
    "path": "examples/basic-usage.ts",
    "content": "/**\n * edgeFlow.js — Basic Usage Example\n *\n * Demonstrates the core APIs: pipeline creation, text generation,\n * image segmentation, scheduling, and memory management.\n */\n\nimport {\n  pipeline,\n  getScheduler,\n  getMemoryStats,\n  withMemoryScope,\n  configureScheduler,\n  gc,\n} from 'edgeflowjs';\n\n// ---------------------------------------------------------------------------\n// 1. Text Generation (production-ready pipeline)\n// ---------------------------------------------------------------------------\n\nasync function textGenerationExample() {\n  const generator = await pipeline('text-generation');\n\n  // Simple generation\n  const result = await generator.run('Once upon a time', {\n    maxNewTokens: 50,\n    temperature: 0.8,\n  });\n\n  console.log('Generated:', result);\n\n  // Streaming\n  for await (const event of generator.stream('Hello, ')) {\n    process.stdout.write(event.token);\n    if (event.done) break;\n  }\n\n  generator.dispose();\n}\n\n// ---------------------------------------------------------------------------\n// 2. Task Scheduling\n// ---------------------------------------------------------------------------\n\nasync function schedulerExample() {\n  configureScheduler({\n    maxConcurrentTasks: 2,\n    maxConcurrentPerModel: 1,\n    defaultTimeout: 30_000,\n  });\n\n  const scheduler = getScheduler();\n\n  // Schedule tasks with different priorities\n  const high = scheduler.schedule('model-a', async () => 'high-result', 'high');\n  const low = scheduler.schedule('model-a', async () => 'low-result', 'low');\n  const critical = scheduler.schedule(\n    'model-b',\n    async () => 'critical-result',\n    'critical',\n  );\n\n  const results = await Promise.all([high.wait(), low.wait(), critical.wait()]);\n  console.log('Scheduler results:', results);\n\n  // Cancel a pending task\n  const task = scheduler.schedule('model-a', async () => 'will-cancel');\n  task.cancel();\n  console.log('Task status:', task.status); // 'cancelled'\n}\n\n// ---------------------------------------------------------------------------\n// 3. Memory Management\n// ---------------------------------------------------------------------------\n\nasync function memoryExample() {\n  const result = await withMemoryScope(async (scope) => {\n    const gen = await pipeline('text-generation');\n    scope.track(gen);\n\n    const output = await gen.run('test', { maxNewTokens: 10 });\n    return output;\n    // gen is auto-disposed when scope exits\n  });\n\n  console.log('Scoped result:', result);\n  console.log('Memory stats:', getMemoryStats());\n\n  gc();\n}\n\n// ---------------------------------------------------------------------------\n// Run all examples\n// ---------------------------------------------------------------------------\n\nasync function main() {\n  console.log('=== edgeFlow.js Basic Usage ===\\n');\n\n  await textGenerationExample();\n  await schedulerExample();\n  await memoryExample();\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "examples/multi-model-dashboard/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>edgeFlow.js — Multi-Model Dashboard</title>\n  <style>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n    :root {\n      --bg: #0a0a0f;\n      --surface: #14141f;\n      --border: #1e1e30;\n      --text: #e4e4ef;\n      --muted: #8888aa;\n      --accent: #7c5cfc;\n      --green: #34d399;\n      --red: #f87171;\n      --yellow: #fbbf24;\n      --radius: 12px;\n    }\n\n    body {\n      font-family: 'Inter', system-ui, -apple-system, sans-serif;\n      background: var(--bg);\n      color: var(--text);\n      min-height: 100vh;\n      padding: 2rem;\n    }\n\n    h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }\n    .subtitle { color: var(--muted); font-size: 0.875rem; margin-bottom: 2rem; }\n\n    .grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));\n      gap: 1.25rem;\n    }\n\n    .card {\n      background: var(--surface);\n      border: 1px solid var(--border);\n      border-radius: var(--radius);\n      padding: 1.25rem;\n    }\n\n    .card h2 {\n      font-size: 0.9rem;\n      font-weight: 600;\n      margin-bottom: 1rem;\n      display: flex;\n      align-items: center;\n      gap: 0.5rem;\n    }\n\n    .badge {\n      font-size: 0.65rem;\n      padding: 2px 8px;\n      border-radius: 99px;\n      text-transform: uppercase;\n      font-weight: 700;\n      letter-spacing: 0.05em;\n    }\n    .badge.running { background: var(--green); color: #000; }\n    .badge.queued  { background: var(--yellow); color: #000; }\n    .badge.idle    { background: var(--border); color: var(--muted); }\n    .badge.error   { background: var(--red); color: #000; }\n\n    .stat-row {\n      display: flex;\n      justify-content: space-between;\n      padding: 0.4rem 0;\n      border-bottom: 1px solid var(--border);\n      font-size: 0.8rem;\n    }\n    .stat-row:last-child { border-bottom: none; }\n    .stat-label { color: var(--muted); }\n    .stat-value { font-weight: 600; font-variant-numeric: tabular-nums; }\n\n    button {\n      background: var(--accent);\n      color: #fff;\n      border: none;\n      border-radius: 8px;\n      padding: 0.5rem 1rem;\n      font-size: 0.8rem;\n      font-weight: 600;\n      cursor: pointer;\n      margin-top: 0.75rem;\n      width: 100%;\n      transition: opacity 0.15s;\n    }\n    button:hover { opacity: 0.85; }\n    button:disabled { opacity: 0.4; cursor: not-allowed; }\n\n    .log {\n      background: #0d0d16;\n      border-radius: 8px;\n      padding: 0.75rem;\n      font-family: 'Fira Code', 'Cascadia Code', monospace;\n      font-size: 0.7rem;\n      max-height: 200px;\n      overflow-y: auto;\n      margin-top: 0.75rem;\n      line-height: 1.6;\n      color: var(--muted);\n    }\n    .log .ok  { color: var(--green); }\n    .log .err { color: var(--red); }\n    .log .wrn { color: var(--yellow); }\n  </style>\n</head>\n<body>\n  <h1>Multi-Model Orchestration Dashboard</h1>\n  <p class=\"subtitle\">\n    Demonstrates edgeFlow.js scheduling, concurrency limits, cancellation, and\n    memory management across multiple simultaneous models.\n  </p>\n\n  <div class=\"grid\">\n    <!-- Scheduler Stats -->\n    <div class=\"card\" id=\"scheduler-card\">\n      <h2>Scheduler <span class=\"badge idle\" id=\"sched-badge\">idle</span></h2>\n      <div class=\"stat-row\"><span class=\"stat-label\">Total tasks</span>     <span class=\"stat-value\" id=\"s-total\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Running</span>         <span class=\"stat-value\" id=\"s-running\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Pending</span>         <span class=\"stat-value\" id=\"s-pending\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Completed</span>       <span class=\"stat-value\" id=\"s-completed\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Failed</span>          <span class=\"stat-value\" id=\"s-failed\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Cancelled</span>       <span class=\"stat-value\" id=\"s-cancelled\">0</span></div>\n      <button onclick=\"launchBurst()\">Launch 20-task burst</button>\n    </div>\n\n    <!-- Memory Stats -->\n    <div class=\"card\" id=\"memory-card\">\n      <h2>Memory Manager</h2>\n      <div class=\"stat-row\"><span class=\"stat-label\">Allocated</span>       <span class=\"stat-value\" id=\"m-alloc\">0 B</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Peak</span>            <span class=\"stat-value\" id=\"m-peak\">0 B</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Tensors</span>         <span class=\"stat-value\" id=\"m-tensors\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Models</span>          <span class=\"stat-value\" id=\"m-models\">0</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Device Memory</span>   <span class=\"stat-value\" id=\"m-device\">—</span></div>\n      <button onclick=\"runGC()\">Force GC</button>\n    </div>\n\n    <!-- Device Profile -->\n    <div class=\"card\" id=\"device-card\">\n      <h2>Device Profile</h2>\n      <div class=\"stat-row\"><span class=\"stat-label\">Tier</span>            <span class=\"stat-value\" id=\"d-tier\">—</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Cores</span>           <span class=\"stat-value\" id=\"d-cores\">—</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">WebGPU</span>          <span class=\"stat-value\" id=\"d-webgpu\">—</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Rec. Batch</span>      <span class=\"stat-value\" id=\"d-batch\">—</span></div>\n      <div class=\"stat-row\"><span class=\"stat-label\">Rec. Quant</span>      <span class=\"stat-value\" id=\"d-quant\">—</span></div>\n      <button onclick=\"profileDevice()\">Re-profile</button>\n    </div>\n\n    <!-- Live Log -->\n    <div class=\"card\" style=\"grid-column: 1 / -1;\">\n      <h2>Live Event Log</h2>\n      <div class=\"log\" id=\"log\"></div>\n      <button onclick=\"clearLog()\">Clear</button>\n    </div>\n  </div>\n\n  <script type=\"module\">\n    import * as edgeFlow from '/dist/edgeflow.browser.js';\n\n    window.edgeFlow = edgeFlow;\n\n    // ---- Scheduler setup ----\n    edgeFlow.configureScheduler({\n      maxConcurrentTasks: 4,\n      maxConcurrentPerModel: 2,\n      maxRetries: 1,\n      retryBaseDelay: 500,\n      circuitBreaker: true,\n      circuitBreakerThreshold: 5,\n    });\n\n    const scheduler = edgeFlow.getScheduler();\n\n    scheduler.on('inference:start',    (e) => log(`Task ${e.data.taskId} started`, 'ok'));\n    scheduler.on('inference:complete', (e) => log(`Task ${e.data.taskId} done (${e.data.duration}ms)`, 'ok'));\n    scheduler.on('inference:error',    (e) => log(`Task ${e.data.taskId} error: ${e.data.error}`, 'err'));\n\n    // ---- Helpers ----\n    function $(id) { return document.getElementById(id); }\n\n    function fmt(bytes) {\n      if (bytes < 1024) return bytes + ' B';\n      if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';\n      return (bytes / 1024 / 1024).toFixed(1) + ' MB';\n    }\n\n    function log(msg, cls = '') {\n      const el = $('log');\n      const line = document.createElement('div');\n      line.className = cls;\n      line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;\n      el.appendChild(line);\n      el.scrollTop = el.scrollHeight;\n    }\n\n    window.clearLog = () => { $('log').innerHTML = ''; };\n\n    // ---- Stats refresh ----\n    function refresh() {\n      const s = scheduler.getStats();\n      $('s-total').textContent     = s.totalTasks;\n      $('s-running').textContent   = s.runningTasks;\n      $('s-pending').textContent   = s.pendingTasks;\n      $('s-completed').textContent = s.completedTasks;\n      $('s-failed').textContent    = s.failedTasks;\n      $('s-cancelled').textContent = s.cancelledTasks;\n\n      const badge = $('sched-badge');\n      if (s.runningTasks > 0) { badge.textContent = 'running'; badge.className = 'badge running'; }\n      else if (s.pendingTasks > 0) { badge.textContent = 'queued'; badge.className = 'badge queued'; }\n      else { badge.textContent = 'idle'; badge.className = 'badge idle'; }\n\n      const m = edgeFlow.getMemoryStats();\n      $('m-alloc').textContent   = fmt(m.allocated);\n      $('m-peak').textContent    = fmt(m.peak);\n      $('m-tensors').textContent = m.tensorCount;\n      $('m-models').textContent  = m.modelCount;\n\n      const dm = edgeFlow.getMemoryManager().getDeviceMemory();\n      $('m-device').textContent = dm != null ? dm + ' GiB' : 'N/A';\n    }\n\n    setInterval(refresh, 500);\n\n    // ---- Task burst ----\n    window.launchBurst = () => {\n      log('Launching 20-task burst across 4 models...', 'wrn');\n      const priorities = ['critical', 'high', 'normal', 'low'];\n      for (let i = 0; i < 20; i++) {\n        const modelId = `model-${String.fromCharCode(65 + (i % 4))}`;\n        const priority = priorities[i % 4];\n        const delay = 200 + Math.random() * 800;\n        const fail = i === 7; // task 7 always fails to test retry\n\n        scheduler.schedule(\n          modelId,\n          async () => {\n            await new Promise(r => setTimeout(r, delay));\n            if (fail) throw new Error('simulated failure');\n            return `result-${i}`;\n          },\n          priority,\n        );\n      }\n    };\n\n    // ---- GC ----\n    window.runGC = () => {\n      edgeFlow.gc();\n      log('GC triggered', 'wrn');\n      refresh();\n    };\n\n    // ---- Device profile ----\n    window.profileDevice = async () => {\n      edgeFlow.resetDeviceProfile();\n      const p = await edgeFlow.getDeviceProfile();\n      const rec = await edgeFlow.recommendModelVariant();\n      $('d-tier').textContent   = p.tier;\n      $('d-cores').textContent  = p.cores;\n      $('d-webgpu').textContent = p.webgpu ? 'Yes' : 'No';\n      $('d-batch').textContent  = p.recommendedBatchSize;\n      $('d-quant').textContent  = rec.quantization;\n      log(`Device profiled: ${p.tier} tier, ${p.cores} cores, WebGPU=${p.webgpu}`, 'ok');\n    };\n\n    // ---- Init ----\n    log('Dashboard loaded');\n    profileDevice();\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/offline-notepad/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>edgeFlow.js — Offline AI Notepad</title>\n  <style>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n    :root {\n      --bg: #fafaf9;\n      --surface: #ffffff;\n      --border: #e7e5e4;\n      --text: #1c1917;\n      --muted: #78716c;\n      --accent: #7c3aed;\n      --green: #059669;\n      --radius: 10px;\n    }\n\n    body {\n      font-family: 'Inter', system-ui, -apple-system, sans-serif;\n      background: var(--bg);\n      color: var(--text);\n      max-width: 700px;\n      margin: 0 auto;\n      padding: 2rem 1.5rem;\n      min-height: 100vh;\n    }\n\n    header {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      margin-bottom: 2rem;\n    }\n\n    h1 { font-size: 1.25rem; }\n\n    .status {\n      font-size: 0.75rem;\n      padding: 4px 10px;\n      border-radius: 99px;\n      font-weight: 600;\n    }\n    .status.online  { background: #d1fae5; color: var(--green); }\n    .status.offline { background: #fef3c7; color: #92400e; }\n\n    textarea {\n      width: 100%;\n      min-height: 200px;\n      padding: 1rem;\n      border: 1px solid var(--border);\n      border-radius: var(--radius);\n      font-family: inherit;\n      font-size: 0.95rem;\n      resize: vertical;\n      line-height: 1.7;\n      background: var(--surface);\n    }\n    textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }\n\n    .actions {\n      display: flex;\n      gap: 0.75rem;\n      margin-top: 1rem;\n    }\n\n    button {\n      background: var(--accent);\n      color: #fff;\n      border: none;\n      border-radius: 8px;\n      padding: 0.6rem 1.2rem;\n      font-size: 0.8rem;\n      font-weight: 600;\n      cursor: pointer;\n    }\n    button:hover { opacity: 0.9; }\n    button.secondary {\n      background: var(--surface);\n      color: var(--text);\n      border: 1px solid var(--border);\n    }\n\n    .result {\n      margin-top: 1.5rem;\n      padding: 1rem;\n      background: var(--surface);\n      border: 1px solid var(--border);\n      border-radius: var(--radius);\n      font-size: 0.85rem;\n      line-height: 1.6;\n      white-space: pre-wrap;\n      min-height: 60px;\n    }\n\n    .result h3 {\n      font-size: 0.75rem;\n      text-transform: uppercase;\n      letter-spacing: 0.05em;\n      color: var(--muted);\n      margin-bottom: 0.5rem;\n    }\n\n    .info {\n      margin-top: 2rem;\n      font-size: 0.8rem;\n      color: var(--muted);\n      line-height: 1.6;\n    }\n  </style>\n</head>\n<body>\n  <header>\n    <h1>Offline AI Notepad</h1>\n    <span class=\"status\" id=\"status\">checking...</span>\n  </header>\n\n  <textarea id=\"editor\" placeholder=\"Write something and let AI analyze it...\">edgeFlow.js makes browser ML inference production-ready with task scheduling, memory management, and smart caching.</textarea>\n\n  <div class=\"actions\">\n    <button onclick=\"analyze()\">Analyze Text</button>\n    <button onclick=\"summarize()\" class=\"secondary\">Auto-complete</button>\n    <button onclick=\"clearResult()\" class=\"secondary\">Clear</button>\n  </div>\n\n  <div class=\"result\" id=\"result\">\n    <h3>AI Analysis</h3>\n    <span id=\"result-text\">Click \"Analyze Text\" to start.</span>\n  </div>\n\n  <div class=\"info\">\n    <strong>How this works:</strong> All ML inference runs entirely in your browser.\n    Models are cached in IndexedDB after first download — the notepad works offline after that.\n    edgeFlow.js handles scheduling, memory, and caching automatically.\n  </div>\n\n  <script type=\"module\">\n    import * as edgeFlow from '/dist/edgeflow.browser.js';\n\n    // Network status\n    function updateStatus() {\n      const el = document.getElementById('status');\n      if (navigator.onLine) {\n        el.textContent = 'online';\n        el.className = 'status online';\n      } else {\n        el.textContent = 'offline';\n        el.className = 'status offline';\n      }\n    }\n    window.addEventListener('online', updateStatus);\n    window.addEventListener('offline', updateStatus);\n    updateStatus();\n\n    const resultEl = document.getElementById('result-text');\n\n    // Save to localStorage\n    const editor = document.getElementById('editor');\n    editor.value = localStorage.getItem('notepad-text') || editor.value;\n    editor.addEventListener('input', () => {\n      localStorage.setItem('notepad-text', editor.value);\n    });\n\n    window.analyze = async () => {\n      const text = editor.value.trim();\n      if (!text) return;\n\n      resultEl.textContent = 'Analyzing...';\n\n      try {\n        const classifier = await edgeFlow.pipeline('sentiment-analysis');\n        const result = await classifier.run(text);\n        const stats = edgeFlow.getMemoryStats();\n\n        resultEl.textContent =\n          `Sentiment: ${result.label} (${(result.score * 100).toFixed(1)}%)\\n` +\n          `Processing time: ${result.processingTime?.toFixed(1)}ms\\n` +\n          `Memory: ${(stats.allocated / 1024).toFixed(0)} KB allocated, ${stats.tensorCount} tensors\\n\\n` +\n          `Word count: ${text.split(/\\s+/).length}\\n` +\n          `Character count: ${text.length}`;\n      } catch (err) {\n        resultEl.textContent = `Error: ${err.message}`;\n      }\n    };\n\n    window.summarize = async () => {\n      const text = editor.value.trim();\n      if (!text) return;\n\n      resultEl.textContent = 'Loading text generation model (this may take a moment on first run)...';\n\n      try {\n        const generator = await edgeFlow.pipeline('text-generation');\n        const prompt = text.slice(-100); // last 100 chars as prompt\n        const result = await generator.run(prompt, {\n          maxNewTokens: 30,\n          temperature: 0.7,\n        });\n\n        if (result && result.generatedText) {\n          editor.value += result.generatedText;\n          localStorage.setItem('notepad-text', editor.value);\n          resultEl.textContent = `Generated ${result.generatedText.length} characters.`;\n        } else {\n          resultEl.textContent = 'Model loaded but no output generated.';\n        }\n      } catch (err) {\n        resultEl.textContent = `Error: ${err.message}`;\n      }\n    };\n\n    window.clearResult = () => {\n      resultEl.textContent = 'Click \"Analyze Text\" to start.';\n    };\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/orchestration.ts",
    "content": "/**\n * edgeFlow.js — Orchestration Example\n *\n * Demonstrates what makes edgeFlow.js unique: production orchestration\n * features that no other browser ML framework provides.\n */\n\nimport {\n  pipeline,\n  configureScheduler,\n  getScheduler,\n  getMemoryStats,\n  withMemoryScope,\n  preloadModel,\n  getPreloadStatus,\n  isModelCached,\n  loadModelData,\n  type TextGenerationPipeline,\n} from 'edgeflowjs';\n\n// ---------------------------------------------------------------------------\n// 1. Concurrent Model Management\n// ---------------------------------------------------------------------------\n\nasync function concurrentModelsExample() {\n  console.log('--- Concurrent Model Management ---');\n\n  // Limit concurrency to prevent OOM on constrained devices\n  configureScheduler({\n    maxConcurrentTasks: 4,\n    maxConcurrentPerModel: 1,\n  });\n\n  const scheduler = getScheduler();\n\n  // Schedule 10 tasks — scheduler ensures only 4 run at a time\n  const tasks = Array.from({ length: 10 }, (_, i) =>\n    scheduler.schedule(\n      `model-${i % 3}`, // distribute across 3 \"models\"\n      async () => {\n        await new Promise((r) => setTimeout(r, 100));\n        return `result-${i}`;\n      },\n      i < 3 ? 'high' : 'normal',\n    ),\n  );\n\n  const results = await Promise.all(tasks.map((t) => t.wait()));\n  console.log(`Completed ${results.length} tasks`);\n  console.log('Stats:', scheduler.getStats());\n}\n\n// ---------------------------------------------------------------------------\n// 2. Smart Model Caching & Preloading\n// ---------------------------------------------------------------------------\n\nasync function cachingExample() {\n  console.log('\\n--- Smart Caching & Preloading ---');\n\n  const modelUrl = 'https://huggingface.co/example/model/resolve/main/model.onnx';\n\n  // Preload models in the background while the user interacts with the UI\n  preloadModel(modelUrl, { priority: 10 });\n  console.log('Preload status:', getPreloadStatus(modelUrl));\n\n  // Check if a model is already cached (IndexedDB)\n  const cached = await isModelCached(modelUrl);\n  console.log('Is cached:', cached);\n\n  // Download with resume support — if interrupted, picks up where it left off\n  try {\n    const data = await loadModelData(modelUrl, {\n      resumable: true,\n      chunkSize: 5 * 1024 * 1024,\n      onProgress: (p) => {\n        console.log(`Download: ${p.percent.toFixed(1)}% — ${(p.speed / 1e6).toFixed(1)} MB/s`);\n      },\n    });\n    console.log(`Downloaded ${data.byteLength} bytes`);\n  } catch {\n    console.log('Download failed (expected in example)');\n  }\n}\n\n// ---------------------------------------------------------------------------\n// 3. Memory Scopes — Automatic Cleanup\n// ---------------------------------------------------------------------------\n\nasync function memoryScopeExample() {\n  console.log('\\n--- Memory Scopes ---');\n\n  const before = getMemoryStats();\n  console.log('Before:', before);\n\n  await withMemoryScope(async (scope) => {\n    // Everything tracked in the scope is automatically disposed on exit\n    const gen = (await pipeline('text-generation')) as TextGenerationPipeline;\n    scope.track(gen);\n\n    // Nested scopes for fine-grained control\n    const innerResult = await withMemoryScope(async (inner) => {\n      // inner resources cleaned up first, then outer\n      return 'inner-done';\n    });\n\n    console.log('Inner scope result:', innerResult);\n  });\n\n  const after = getMemoryStats();\n  console.log('After:', after);\n}\n\n// ---------------------------------------------------------------------------\n// 4. Task Cancellation & Timeouts\n// ---------------------------------------------------------------------------\n\nasync function cancellationExample() {\n  console.log('\\n--- Cancellation & Timeouts ---');\n\n  const scheduler = getScheduler();\n\n  // Schedule with timeout — auto-fails if takes too long\n  const timedTask = scheduler.scheduleWithTimeout(\n    'slow-model',\n    async () => {\n      await new Promise((r) => setTimeout(r, 60_000));\n      return 'done';\n    },\n    5_000, // 5 second timeout\n    'normal',\n  );\n\n  // Cancel programmatically\n  const cancelableTask = scheduler.schedule(\n    'model-x',\n    async () => {\n      await new Promise((r) => setTimeout(r, 10_000));\n      return 'result';\n    },\n  );\n\n  // User navigates away — cancel pending work\n  cancelableTask.cancel();\n  console.log('Cancelled task status:', cancelableTask.status);\n\n  // Timeout will fire for timedTask\n  try {\n    await timedTask.wait();\n  } catch (e) {\n    console.log('Timed out as expected:', (e as Error).message);\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\nasync function main() {\n  console.log('=== edgeFlow.js Orchestration Demo ===\\n');\n  await concurrentModelsExample();\n  await cachingExample();\n  await memoryScopeExample();\n  await cancellationExample();\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"edgeflowjs\",\n  \"version\": \"0.1.0\",\n  \"description\": \"Lightweight, high-performance browser ML inference framework with native concurrency support\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./core\": {\n      \"import\": \"./dist/core/index.js\",\n      \"types\": \"./dist/core/index.d.ts\"\n    },\n    \"./backends\": {\n      \"import\": \"./dist/backends/index.js\",\n      \"types\": \"./dist/backends/index.d.ts\"\n    },\n    \"./pipelines\": {\n      \"import\": \"./dist/pipelines/index.js\",\n      \"types\": \"./dist/pipelines/index.d.ts\"\n    },\n    \"./tools\": {\n      \"import\": \"./dist/tools/index.js\",\n      \"types\": \"./dist/tools/index.d.ts\"\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc && npm run build:browser\",\n    \"build:browser\": \"node scripts/build-browser.js\",\n    \"dev\": \"tsc --watch\",\n    \"clean\": \"rm -rf dist\",\n    \"lint\": \"eslint src --ext .ts\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"test:unit\": \"vitest run tests/unit\",\n    \"test:integration\": \"vitest run tests/integration\",\n    \"test:coverage\": \"vitest run --coverage\",\n    \"demo\": \"npm run build && node demo/server.js\",\n    \"demo:server\": \"node demo/server.js\",\n    \"docs:dev\": \"vitepress dev docs\",\n    \"docs:build\": \"vitepress build docs\",\n    \"docs:preview\": \"vitepress preview docs\",\n    \"test:e2e\": \"playwright test\",\n    \"prepublishOnly\": \"npm run clean && npm run build && npm run test\"\n  },\n  \"keywords\": [\n    \"machine-learning\",\n    \"ml\",\n    \"ai\",\n    \"inference\",\n    \"webgpu\",\n    \"webnn\",\n    \"browser\",\n    \"edge\",\n    \"transformers\",\n    \"neural-network\"\n  ],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/s-zx/edgeflow.js\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/s-zx/edgeflow.js/issues\"\n  },\n  \"homepage\": \"https://edgeflow.js.org\",\n  \"peerDependencies\": {\n    \"onnxruntime-web\": \"^1.17.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"onnxruntime-web\": {\n      \"optional\": true\n    }\n  },\n  \"devDependencies\": {\n    \"onnxruntime-web\": \"^1.17.0\",\n    \"@playwright/test\": \"^1.58.2\",\n    \"@types/node\": \"^20.10.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.13.0\",\n    \"@typescript-eslint/parser\": \"^6.13.0\",\n    \"@vitest/coverage-v8\": \"^1.6.0\",\n    \"esbuild\": \"^0.20.0\",\n    \"eslint\": \"^8.55.0\",\n    \"happy-dom\": \"^20.4.0\",\n    \"typescript\": \"^5.3.0\",\n    \"vitest\": \"^1.0.0\"\n  },\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"sideEffects\": false\n}\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\nexport default defineConfig({\n  testDir: './tests/e2e',\n  testMatch: '**/*.spec.ts',\n  fullyParallel: true,\n  retries: 1,\n  reporter: 'html',\n  use: {\n    baseURL: 'http://localhost:3000',\n    trace: 'on-first-retry',\n  },\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n  ],\n  webServer: {\n    command: 'npm run demo:server',\n    url: 'http://localhost:3000',\n    reuseExistingServer: !process.env.CI,\n    timeout: 30_000,\n  },\n});\n"
  },
  {
    "path": "scripts/build-browser.js",
    "content": "/**\n * Build script for browser bundle\n */\nimport * as esbuild from 'esbuild';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst rootDir = join(__dirname, '..');\n\nasync function build() {\n  try {\n    // Build browser bundle\n    // onnxruntime-web is a direct dependency, marked external for bundler handling\n    await esbuild.build({\n      entryPoints: [join(rootDir, 'dist/index.js')],\n      bundle: true,\n      format: 'esm',\n      outfile: join(rootDir, 'dist/edgeflow.browser.js'),\n      platform: 'browser',\n      target: ['es2020'],\n      sourcemap: true,\n      minify: false,\n      external: ['onnxruntime-web'], // External: user's bundler will handle this\n      define: {\n        'process.env.NODE_ENV': '\"production\"',\n      },\n      banner: {\n        js: '/* edgeFlow.js - Browser Bundle */\\n',\n      },\n    });\n\n    // Build minified version\n    await esbuild.build({\n      entryPoints: [join(rootDir, 'dist/index.js')],\n      bundle: true,\n      format: 'esm',\n      outfile: join(rootDir, 'dist/edgeflow.browser.min.js'),\n      platform: 'browser',\n      target: ['es2020'],\n      sourcemap: true,\n      minify: true,\n      external: ['onnxruntime-web'],\n      define: {\n        'process.env.NODE_ENV': '\"production\"',\n      },\n    });\n\n    console.log('✓ Browser bundles created successfully');\n    console.log('  - dist/edgeflow.browser.js');\n    console.log('  - dist/edgeflow.browser.min.js');\n  } catch (error) {\n    console.error('Build failed:', error);\n    process.exit(1);\n  }\n}\n\nbuild();\n"
  },
  {
    "path": "src/backends/index.ts",
    "content": "/**\n * edgeFlow.js - Backend Exports\n */\n\n// WebGPU Backend (planned - skeleton only)\nexport { WebGPURuntime, createWebGPURuntime } from './webgpu.js';\n\n// WebNN Backend (planned - skeleton only)\nexport { WebNNRuntime, createWebNNRuntime } from './webnn.js';\n\n// WASM Backend (basic tensor ops)\nexport { WASMRuntime, createWASMRuntime } from './wasm.js';\n\n// ONNX Runtime Backend (real model inference)\nexport { ONNXRuntime, createONNXRuntime, isOnnxAvailable } from './onnx.js';\n\n// transformers.js Adapter Backend\nexport {\n  TransformersAdapterRuntime,\n  useTransformersBackend,\n  getTransformersAdapter,\n  type TransformersAdapterOptions,\n  type TransformersPipelineFactory,\n} from './transformers-adapter.js';\n\n// Re-export types\nexport type { Runtime, RuntimeType, RuntimeCapabilities } from '../core/types.js';\n\nimport { registerRuntime } from '../core/runtime.js';\nimport { createONNXRuntime } from './onnx.js';\n\n/**\n * Register all available backends.\n *\n * Always registers the ONNX Runtime factory synchronously so there is no\n * async race between registration and the first pipeline() call.\n * `ONNXRuntime.isAvailable()` is called lazily by RuntimeManager when it\n * selects a backend, so if onnxruntime-web is not installed the runtime is\n * simply skipped at that point.\n */\nexport function registerAllBackends(): void {\n  registerRuntime('wasm', createONNXRuntime);\n}\n\n/**\n * Auto-register backends on module load (synchronous — no race condition).\n */\nregisterAllBackends();\n"
  },
  {
    "path": "src/backends/onnx.ts",
    "content": "/**\n * edgeFlow.js - ONNX Runtime Backend\n * \n * Uses onnxruntime-web for real ONNX model inference.\n * onnxruntime-web is an optional peer dependency loaded dynamically.\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n  DataType,\n} from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n\n// Lazy-loaded onnxruntime-web module\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet ort: any = null;\n\nasync function getOrt(): Promise<any> {\n  if (ort) return ort;\n  try {\n    // Import the WASM-only sub-path so Vite rewrites the bare specifier\n    // to ort.wasm.bundle.min.mjs. This avoids loading the JSEP/WebGPU\n    // worker module (jsep.mjs) that ort.bundle.min.mjs eagerly fetches\n    // whenever navigator.gpu exists — which causes a 404 in dev servers\n    // that restrict ES module imports from /public.\n    ort = await import('onnxruntime-web/wasm');\n    return ort;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Check whether onnxruntime-web is importable.\n */\nexport async function isOnnxAvailable(): Promise<boolean> {\n  return (await getOrt()) != null;\n}\n\n// ============================================================================\n// ONNX Session Storage\n// ============================================================================\n\ninterface ONNXSessionData {\n  session: any; // ort.InferenceSession\n  inputNames: string[];\n  outputNames: string[];\n}\n\nconst sessionStore: Map<string, ONNXSessionData> = new Map();\n\n// ============================================================================\n// ONNX Runtime Implementation\n// ============================================================================\n\n/**\n * ONNXRuntime - Real ONNX model inference using onnxruntime-web\n */\nexport class ONNXRuntime implements Runtime {\n  readonly name: RuntimeType = 'wasm'; // Register as wasm since it's the fallback\n  \n  private initialized = false;\n  private executionProvider: 'webgpu' | 'wasm' = 'wasm';\n\n  get capabilities(): RuntimeCapabilities {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: this.executionProvider === 'webgpu',\n      dynamicShapes: true,\n      maxBatchSize: 32,\n      availableMemory: 512 * 1024 * 1024, // 512MB\n    };\n  }\n\n  /**\n   * Check if ONNX Runtime is available (peer dependency installed)\n   */\n  async isAvailable(): Promise<boolean> {\n    return isOnnxAvailable();\n  }\n\n  /**\n   * Initialize the ONNX runtime\n   */\n  async initialize(): Promise<void> {\n    if (this.initialized) return;\n\n    const ortModule = await getOrt();\n    if (!ortModule) {\n      throw new EdgeFlowError(\n        'onnxruntime-web is not installed. Install it with: npm install onnxruntime-web',\n        ErrorCodes.RUNTIME_NOT_AVAILABLE\n      );\n    }\n\n    // Configure WASM backend for browser use.\n    // numThreads=1 disables multi-threading so ort only needs the plain\n    // .wasm binary — the worker .mjs file is never requested, which avoids\n    // Vite's restriction on importing files from /public as ES modules.\n    // Consumers should copy onnxruntime-web/dist/*.wasm to public/ort/.\n    if (typeof window !== 'undefined' && ortModule.env?.wasm) {\n      (ortModule.env.wasm as any).wasmPaths = '/ort/';\n      (ortModule.env.wasm as any).numThreads = 1;\n    }\n\n    this.initialized = true;\n  }\n\n  /**\n   * Load a model from ArrayBuffer\n   */\n  async loadModel(\n    modelData: ArrayBuffer,\n    options: ModelLoadOptions = {}\n  ): Promise<LoadedModel> {\n    if (!this.initialized) {\n      await this.initialize();\n    }\n\n    try {\n      const ortModule = await getOrt();\n      if (!ortModule) {\n        throw new Error('onnxruntime-web is not installed');\n      }\n\n      // WASM-only execution provider — WebGPU acceleration can be added\n      // later via the dedicated WebGPURuntime backend.\n      const sessionOptions = {\n        executionProviders: ['wasm'],\n        graphOptimizationLevel: 'all',\n      };\n\n      const modelBytes = new Uint8Array(modelData);\n\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      const session: any = await ortModule.InferenceSession.create(modelBytes, sessionOptions);\n      \n      // Get input/output names\n      const inputNames = session.inputNames;\n      const outputNames = session.outputNames;\n\n      // Generate model ID\n      const modelId = `onnx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n\n      // Store session\n      sessionStore.set(modelId, {\n        session,\n        inputNames: [...inputNames],\n        outputNames: [...outputNames],\n      });\n\n      // Create metadata\n      const metadata: ModelMetadata = {\n        name: options.metadata?.name ?? 'onnx-model',\n        version: '1.0.0',\n        inputs: inputNames.map((name: string) => ({\n          name,\n          dtype: 'float32' as DataType,\n          shape: [-1], // Dynamic shape\n        })),\n        outputs: outputNames.map((name: string) => ({\n          name,\n          dtype: 'float32' as DataType,\n          shape: [-1],\n        })),\n        sizeBytes: modelData.byteLength,\n        quantization: options.quantization ?? 'float32',\n        format: 'onnx',\n      };\n\n      // Create model instance\n      const model = new LoadedModelImpl(\n        metadata,\n        'wasm',\n        () => this.unloadModel(modelId)\n      );\n\n      // Override the ID to match our stored session\n      Object.defineProperty(model, 'id', { value: modelId, writable: false });\n\n      // Track in memory manager\n      getMemoryManager().trackModel(model, () => model.dispose());\n\n      return model;\n    } catch (error) {\n      throw new EdgeFlowError(\n        `Failed to load ONNX model: ${error instanceof Error ? error.message : String(error)}`,\n        ErrorCodes.MODEL_LOAD_FAILED,\n        { error }\n      );\n    }\n  }\n\n  /**\n   * Run inference\n   */\n  async run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]> {\n    const sessionData = sessionStore.get(model.id);\n    if (!sessionData) {\n      throw new EdgeFlowError(\n        `ONNX session not found for model ${model.id}`,\n        ErrorCodes.MODEL_NOT_LOADED,\n        { modelId: model.id }\n      );\n    }\n\n    const { session, inputNames, outputNames } = sessionData;\n\n    try {\n      const ortModule = await getOrt();\n      const feeds: Record<string, any> = {};\n      \n      for (let i = 0; i < Math.min(inputs.length, inputNames.length); i++) {\n        const inputName = inputNames[i];\n        const inputTensor = inputs[i] as EdgeFlowTensor;\n        \n        if (inputName && inputTensor) {\n          const dtype = inputTensor.dtype;\n          let ortTensor: any;\n          \n          if (dtype === 'int64') {\n            const data = inputTensor.data as unknown as BigInt64Array;\n            ortTensor = new ortModule.Tensor('int64', data, inputTensor.shape as number[]);\n          } else if (dtype === 'int32') {\n            const data = inputTensor.data as Int32Array;\n            ortTensor = new ortModule.Tensor('int32', data, inputTensor.shape as number[]);\n          } else {\n            const data = inputTensor.toFloat32Array();\n            ortTensor = new ortModule.Tensor('float32', data, inputTensor.shape as number[]);\n          }\n          \n          feeds[inputName] = ortTensor;\n        }\n      }\n\n      const results = await session.run(feeds);\n\n      // Convert outputs to EdgeFlowTensor\n      const outputs: Tensor[] = [];\n      \n      for (const outputName of outputNames) {\n        const ortTensor = results[outputName];\n        if (ortTensor) {\n          const data = ortTensor.data as Float32Array;\n          const shape = Array.from(ortTensor.dims).map(d => Number(d));\n          outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, 'float32'));\n        }\n      }\n\n      return outputs;\n    } catch (error) {\n      throw new EdgeFlowError(\n        `ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`,\n        ErrorCodes.INFERENCE_FAILED,\n        { modelId: model.id, error }\n      );\n    }\n  }\n\n  /**\n   * Run inference with named inputs\n   */\n  async runNamed(model: LoadedModel, namedInputs: Map<string, Tensor>): Promise<Tensor[]> {\n    const sessionData = sessionStore.get(model.id);\n    if (!sessionData) {\n      throw new EdgeFlowError(\n        `ONNX session not found for model ${model.id}`,\n        ErrorCodes.MODEL_NOT_LOADED,\n        { modelId: model.id }\n      );\n    }\n\n    const { session, inputNames, outputNames } = sessionData;\n\n    try {\n      const ortModule = await getOrt();\n      const feeds: Record<string, any> = {};\n      \n      for (const [inputName, inputTensor] of namedInputs) {\n        const tensor = inputTensor as EdgeFlowTensor;\n        const dtype = tensor.dtype;\n        let ortTensor: any;\n        \n        if (dtype === 'int64') {\n          const data = tensor.data as unknown as BigInt64Array;\n          ortTensor = new ortModule.Tensor('int64', data, tensor.shape as number[]);\n        } else if (dtype === 'int32') {\n          const data = tensor.data as Int32Array;\n          ortTensor = new ortModule.Tensor('int32', data, tensor.shape as number[]);\n        } else {\n          const data = tensor.toFloat32Array();\n          ortTensor = new ortModule.Tensor('float32', data, tensor.shape as number[]);\n        }\n        \n        feeds[inputName] = ortTensor;\n      }\n\n      const results = await session.run(feeds);\n\n      // Convert outputs to EdgeFlowTensor\n      const outputs: Tensor[] = [];\n      \n      for (const outputName of outputNames) {\n        const ortTensor = results[outputName];\n        if (ortTensor) {\n          const data = ortTensor.data as Float32Array;\n          const shape = Array.from(ortTensor.dims).map(d => Number(d));\n          outputs.push(new EdgeFlowTensor(new Float32Array(data), shape, 'float32'));\n        }\n      }\n\n      return outputs;\n    } catch (error) {\n      throw new EdgeFlowError(\n        `ONNX inference failed: ${error instanceof Error ? error.message : String(error)}`,\n        ErrorCodes.INFERENCE_FAILED,\n        { modelId: model.id, expectedInputs: inputNames, providedInputs: Array.from(namedInputs.keys()), error }\n      );\n    }\n  }\n\n  /**\n   * Unload a model\n   */\n  private async unloadModel(modelId: string): Promise<void> {\n    const sessionData = sessionStore.get(modelId);\n    if (sessionData) {\n      // Release session will be handled by GC\n      sessionStore.delete(modelId);\n    }\n  }\n\n  /**\n   * Dispose the runtime\n   */\n  dispose(): void {\n    // Clear all sessions\n    sessionStore.clear();\n    this.initialized = false;\n  }\n}\n\n/**\n * Create ONNX runtime factory\n */\nexport function createONNXRuntime(): Runtime {\n  return new ONNXRuntime();\n}\n"
  },
  {
    "path": "src/backends/transformers-adapter.ts",
    "content": "/**\n * edgeFlow.js - transformers.js Adapter Backend\n *\n * Wraps transformers.js (by Hugging Face) as an inference backend, giving\n * users access to 1000+ HuggingFace models while adding edgeFlow.js's\n * orchestration layer (scheduling, caching, memory management, workers).\n *\n * @example\n * ```typescript\n * import { useTransformersBackend } from 'edgeflowjs';\n * import { pipeline as tfPipeline } from '@xenova/transformers';\n *\n * // Register the adapter\n * useTransformersBackend();\n *\n * // Now use edgeFlow.js pipeline API — inference delegates to transformers.js\n * const classifier = await pipeline('text-classification', {\n *   model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n * });\n *\n * // edgeFlow.js handles scheduling, batching, memory, caching\n * const results = await classifier.runBatch(thousandsOfTexts);\n * ```\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n} from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\nimport { registerRuntime } from '../core/runtime.js';\n\n// ---------------------------------------------------------------------------\n// Types for the transformers.js interop\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal interface for a transformers.js pipeline instance.\n * We avoid importing @xenova/transformers directly so edgeFlow.js\n * does not add it as a hard dependency.\n */\ninterface TransformersPipelineInstance {\n  (input: unknown, options?: unknown): Promise<unknown>;\n  dispose?: () => Promise<void> | void;\n}\n\n/**\n * A factory that creates a transformers.js pipeline.\n * Users pass this so we don't hard-depend on the library.\n */\nexport type TransformersPipelineFactory = (\n  task: string,\n  model?: string,\n  options?: Record<string, unknown>,\n) => Promise<TransformersPipelineInstance>;\n\n/**\n * Options for configuring the transformers.js adapter.\n */\nexport interface TransformersAdapterOptions {\n  /** The pipeline factory from transformers.js (e.g. the `pipeline` function) */\n  pipelineFactory: TransformersPipelineFactory;\n  /** Default device ('webgpu' | 'wasm' | 'cpu') — passed to transformers.js */\n  device?: string;\n  /** Default dtype ('fp32' | 'fp16' | 'q8' | 'q4') */\n  dtype?: string;\n  /** Cache directory (browser IndexedDB path) */\n  cacheDir?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Session store: maps model IDs to transformers.js pipeline instances\n// ---------------------------------------------------------------------------\n\nconst sessionStore = new Map<string, {\n  instance: TransformersPipelineInstance;\n  task: string;\n  model: string;\n}>();\n\nlet adapterOptions: TransformersAdapterOptions | null = null;\n\n// ---------------------------------------------------------------------------\n// Runtime implementation\n// ---------------------------------------------------------------------------\n\nexport class TransformersAdapterRuntime implements Runtime {\n  readonly name: RuntimeType = 'wasm'; // registers under the wasm slot\n\n  get capabilities(): RuntimeCapabilities {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: true,\n      maxBatchSize: 128,\n      availableMemory: 1024 * 1024 * 1024,\n    };\n  }\n\n  async isAvailable(): Promise<boolean> {\n    return adapterOptions?.pipelineFactory != null;\n  }\n\n  async initialize(): Promise<void> {\n    if (!adapterOptions?.pipelineFactory) {\n      throw new EdgeFlowError(\n        'TransformersAdapterRuntime requires a pipelineFactory. ' +\n        'Call useTransformersBackend({ pipelineFactory }) first.',\n        ErrorCodes.RUNTIME_INIT_FAILED,\n      );\n    }\n  }\n\n  async loadModel(\n    modelData: ArrayBuffer,\n    options: ModelLoadOptions = {},\n  ): Promise<LoadedModel> {\n    // modelData is unused — transformers.js downloads its own models.\n    // Instead the model identifier comes via metadata.name or the URL.\n    const modelName = options.metadata?.name ?? 'default';\n\n    const metadata: ModelMetadata = {\n      name: modelName,\n      version: '1.0.0',\n      inputs: [{ name: 'input', dtype: 'float32', shape: [-1] }],\n      outputs: [{ name: 'output', dtype: 'float32', shape: [-1] }],\n      sizeBytes: modelData.byteLength || 0,\n      quantization: options.quantization ?? 'float32',\n      format: 'onnx',\n    };\n\n    const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n\n    const model = new LoadedModelImpl(metadata, this.name, () => {\n      const session = sessionStore.get(modelId);\n      if (session?.instance.dispose) {\n        session.instance.dispose();\n      }\n      sessionStore.delete(modelId);\n    });\n\n    getMemoryManager().trackModel(model, () => model.dispose());\n    return model;\n  }\n\n  /**\n   * Load a transformers.js pipeline by task + model name\n   * (called by the higher-level adapter pipeline, not via the\n   * standard loadModel path).\n   */\n  async loadPipeline(\n    task: string,\n    model: string,\n    pipelineOptions?: Record<string, unknown>,\n  ): Promise<string> {\n    if (!adapterOptions?.pipelineFactory) {\n      throw new EdgeFlowError(\n        'Adapter not initialised',\n        ErrorCodes.RUNTIME_NOT_INITIALIZED,\n      );\n    }\n\n    const opts: Record<string, unknown> = { ...pipelineOptions };\n    if (adapterOptions.device) opts['device'] = adapterOptions.device;\n    if (adapterOptions.dtype) opts['dtype'] = adapterOptions.dtype;\n\n    const instance = await adapterOptions.pipelineFactory(task, model, opts);\n    const modelId = `tjs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n    sessionStore.set(modelId, { instance, task, model });\n\n    return modelId;\n  }\n\n  /**\n   * Run inference by passing the raw input to the transformers.js pipeline.\n   * The result is returned as a single EdgeFlowTensor wrapping the JSON-encoded output\n   * (since transformers.js returns task-specific objects, not raw tensors).\n   */\n  async run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]> {\n    const session = sessionStore.get(model.id);\n    if (!session) {\n      throw new EdgeFlowError(\n        `No transformers.js session for model ${model.id}`,\n        ErrorCodes.MODEL_NOT_LOADED,\n      );\n    }\n\n    // Reconstruct input from tensor (simple: use the float data as-is)\n    const inputData = inputs[0]?.toFloat32Array() ?? new Float32Array(0);\n    const result = await session.instance(inputData);\n\n    // Wrap the result in a tensor — downstream pipelines can interpret it\n    const resultArray = Array.isArray(result)\n      ? new Float32Array(result.flat(Infinity) as number[])\n      : new Float32Array([0]);\n\n    return [new EdgeFlowTensor(resultArray, [resultArray.length], 'float32')];\n  }\n\n  /**\n   * High-level: run the transformers.js pipeline directly with arbitrary input.\n   * Returns the raw result object (not a tensor).\n   */\n  async runDirect(\n    modelId: string,\n    input: unknown,\n    options?: Record<string, unknown>,\n  ): Promise<unknown> {\n    const session = sessionStore.get(modelId);\n    if (!session) {\n      throw new EdgeFlowError(\n        `No transformers.js session for model ${modelId}`,\n        ErrorCodes.MODEL_NOT_LOADED,\n      );\n    }\n    return session.instance(input, options);\n  }\n\n  dispose(): void {\n    for (const [id, session] of sessionStore) {\n      if (session.instance.dispose) {\n        session.instance.dispose();\n      }\n      sessionStore.delete(id);\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nlet adapterRuntime: TransformersAdapterRuntime | null = null;\n\n/**\n * Register the transformers.js adapter as the default inference backend.\n *\n * @example\n * ```typescript\n * import { pipeline } from '@xenova/transformers';\n * import { useTransformersBackend } from 'edgeflowjs';\n *\n * useTransformersBackend({\n *   pipelineFactory: pipeline,\n *   device: 'webgpu',\n *   dtype: 'fp16',\n * });\n * ```\n */\nexport function useTransformersBackend(options: TransformersAdapterOptions): void {\n  adapterOptions = options;\n  adapterRuntime = new TransformersAdapterRuntime();\n  registerRuntime('wasm', () => adapterRuntime!);\n}\n\n/**\n * Get the adapter runtime instance (for advanced use).\n */\nexport function getTransformersAdapter(): TransformersAdapterRuntime | null {\n  return adapterRuntime;\n}\n"
  },
  {
    "path": "src/backends/wasm.ts",
    "content": "/**\n * edgeFlow.js - WebAssembly Backend\n * \n * Pure WASM runtime for universal browser support.\n * Features:\n * - Universal compatibility\n * - SIMD acceleration when available\n * - Memory-efficient execution\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n} from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor, softmax as tensorSoftmax, relu as tensorRelu, sigmoid as tensorSigmoid } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n\n// ============================================================================\n// WASM Types\n// ============================================================================\n\n/**\n * WASM module instance\n */\ninterface WASMModule {\n  memory: WebAssembly.Memory;\n  exports: WASMExports;\n}\n\n/**\n * WASM exported functions\n */\ninterface WASMExports {\n  // Memory management\n  malloc(size: number): number;\n  free(ptr: number): void;\n  \n  // Tensor operations\n  matmul_f32(\n    a: number, aRows: number, aCols: number,\n    b: number, bRows: number, bCols: number,\n    out: number\n  ): void;\n  \n  add_f32(a: number, b: number, out: number, size: number): void;\n  mul_f32(a: number, b: number, out: number, size: number): void;\n  relu_f32(input: number, output: number, size: number): void;\n  sigmoid_f32(input: number, output: number, size: number): void;\n  softmax_f32(input: number, output: number, size: number): void;\n  \n  // SIMD variants (when available)\n  matmul_f32_simd?(\n    a: number, aRows: number, aCols: number,\n    b: number, bRows: number, bCols: number,\n    out: number\n  ): void;\n}\n\n/**\n * WASM model data structure\n */\ninterface WASMModelData {\n  /** Weight buffers */\n  weights: Map<string, { ptr: number; size: number; data: Float32Array }>;\n  /** Model configuration */\n  config: WASMModelConfig;\n  /** Layer execution order */\n  executionOrder: string[];\n}\n\n/**\n * Model configuration\n */\ninterface WASMModelConfig {\n  name: string;\n  version: string;\n  layers: WASMLayerConfig[];\n  inputs: { name: string; shape: number[]; dtype: string }[];\n  outputs: { name: string; shape: number[]; dtype: string }[];\n}\n\n/**\n * Layer configuration\n */\ninterface WASMLayerConfig {\n  name: string;\n  type: string;\n  inputShape: number[];\n  outputShape: number[];\n  weights?: string[];\n  params?: Record<string, unknown>;\n}\n\n// ============================================================================\n// WASM Runtime Implementation\n// ============================================================================\n\n/**\n * WASMRuntime - Pure WebAssembly inference runtime\n */\nexport class WASMRuntime implements Runtime {\n  readonly name: RuntimeType = 'wasm';\n  \n  private module: WASMModule | null = null;\n  private simdSupported = false;\n  private models: Map<string, WASMModelData> = new Map();\n  private initialized = false;\n\n  get capabilities(): RuntimeCapabilities {\n    return {\n      concurrency: false, // WASM is single-threaded by default\n      quantization: true,\n      float16: false,\n      dynamicShapes: true,\n      maxBatchSize: 16,\n      availableMemory: 128 * 1024 * 1024, // 128MB default\n    };\n  }\n\n  /**\n   * Check if WASM is available\n   */\n  async isAvailable(): Promise<boolean> {\n    if (typeof WebAssembly === 'undefined') return false;\n\n    try {\n      // Check if we can instantiate a minimal WASM module\n      const bytes = new Uint8Array([\n        0x00, 0x61, 0x73, 0x6d, // Magic number\n        0x01, 0x00, 0x00, 0x00, // Version\n      ]);\n      await WebAssembly.instantiate(bytes);\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Initialize the WASM runtime\n   */\n  async initialize(): Promise<void> {\n    if (this.initialized) return;\n\n    // Check SIMD support\n    this.simdSupported = await this.checkSIMDSupport();\n\n    // Create memory pool\n    const memory = new WebAssembly.Memory({\n      initial: 256,  // 16MB initial\n      maximum: 2048, // 128MB maximum\n    });\n\n    // Compile and instantiate the WASM module\n    // In production, this would load an actual WASM binary\n    // For now, we use a pure JS fallback\n    this.module = {\n      memory,\n      exports: this.createJSFallback(memory),\n    };\n\n    this.initialized = true;\n  }\n\n  /**\n   * Check SIMD support\n   */\n  private async checkSIMDSupport(): Promise<boolean> {\n    try {\n      // SIMD detection via feature detection\n      const simdTest = new Uint8Array([\n        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n        0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, 0x03,\n        0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00,\n        0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0b\n      ]);\n      await WebAssembly.instantiate(simdTest);\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Create JavaScript fallback for WASM operations\n   */\n  private createJSFallback(memory: WebAssembly.Memory): WASMExports {\n    let nextPtr = 0;\n    const allocations: Map<number, number> = new Map();\n\n    return {\n      malloc: (size: number): number => {\n        const ptr = nextPtr;\n        nextPtr += size;\n        allocations.set(ptr, size);\n        return ptr;\n      },\n\n      free: (ptr: number): void => {\n        allocations.delete(ptr);\n      },\n\n      matmul_f32: (\n        aPtr: number, aRows: number, aCols: number,\n        bPtr: number, _bRows: number, bCols: number,\n        outPtr: number\n      ): void => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n\n        for (let i = 0; i < aRows; i++) {\n          for (let j = 0; j < bCols; j++) {\n            let sum = 0;\n            for (let k = 0; k < aCols; k++) {\n              sum += (view[aOffset + i * aCols + k] ?? 0) * (view[bOffset + k * bCols + j] ?? 0);\n            }\n            view[outOffset + i * bCols + j] = sum;\n          }\n        }\n      },\n\n      add_f32: (aPtr: number, bPtr: number, outPtr: number, size: number): void => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[aOffset + i] ?? 0) + (view[bOffset + i] ?? 0);\n        }\n      },\n\n      mul_f32: (aPtr: number, bPtr: number, outPtr: number, size: number): void => {\n        const view = new Float32Array(memory.buffer);\n        const aOffset = aPtr / 4;\n        const bOffset = bPtr / 4;\n        const outOffset = outPtr / 4;\n\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[aOffset + i] ?? 0) * (view[bOffset + i] ?? 0);\n        }\n      },\n\n      relu_f32: (inputPtr: number, outputPtr: number, size: number): void => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = Math.max(0, view[inOffset + i] ?? 0);\n        }\n      },\n\n      sigmoid_f32: (inputPtr: number, outputPtr: number, size: number): void => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = 1 / (1 + Math.exp(-(view[inOffset + i] ?? 0)));\n        }\n      },\n\n      softmax_f32: (inputPtr: number, outputPtr: number, size: number): void => {\n        const view = new Float32Array(memory.buffer);\n        const inOffset = inputPtr / 4;\n        const outOffset = outputPtr / 4;\n\n        // Find max for numerical stability\n        let max = -Infinity;\n        for (let i = 0; i < size; i++) {\n          if ((view[inOffset + i] ?? 0) > max) max = view[inOffset + i] ?? 0;\n        }\n\n        // Compute exp and sum\n        let sum = 0;\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = Math.exp((view[inOffset + i] ?? 0) - max);\n          sum += view[outOffset + i] ?? 0;\n        }\n\n        // Normalize\n        for (let i = 0; i < size; i++) {\n          view[outOffset + i] = (view[outOffset + i] ?? 0) / sum;\n        }\n      },\n    };\n  }\n\n  /**\n   * Load a model\n   */\n  async loadModel(\n    modelData: ArrayBuffer,\n    options: ModelLoadOptions = {}\n  ): Promise<LoadedModel> {\n    this.ensureInitialized();\n\n    // Parse model configuration\n    const config = this.parseModelConfig(modelData);\n\n    // Extract and store weights\n    const wasmData: WASMModelData = {\n      weights: new Map(),\n      config,\n      executionOrder: config.layers.map(l => l.name),\n    };\n\n    // Load weights into memory\n    await this.loadWeights(modelData, wasmData);\n\n    const modelId = `wasm_${Date.now().toString(36)}`;\n    this.models.set(modelId, wasmData);\n\n    // Create metadata\n    const metadata: ModelMetadata = {\n      name: config.name || options.metadata?.name || 'unknown',\n      version: config.version || '1.0.0',\n      inputs: config.inputs.map(i => ({\n        name: i.name,\n        dtype: i.dtype as 'float32',\n        shape: i.shape,\n      })),\n      outputs: config.outputs.map(o => ({\n        name: o.name,\n        dtype: o.dtype as 'float32',\n        shape: o.shape,\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? 'float32',\n      format: 'edgeflow',\n    };\n\n    // Create model instance\n    const model = new LoadedModelImpl(\n      metadata,\n      'wasm',\n      () => this.unloadModel(modelId)\n    );\n\n    // Track in memory manager\n    getMemoryManager().trackModel(model, () => model.dispose());\n\n    return model;\n  }\n\n  /**\n   * Run inference\n   */\n  async run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]> {\n    this.ensureInitialized();\n    \n    // Execute model layers\n    return this.executeModel(inputs, model.metadata);\n  }\n\n  /**\n   * Execute model\n   */\n  private async executeModel(inputs: Tensor[], metadata: ModelMetadata): Promise<Tensor[]> {\n    const outputs: Tensor[] = [];\n\n    for (const outputSpec of metadata.outputs) {\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      \n      // Process based on output requirements\n      // This is a simplified implementation\n      let outputTensor: EdgeFlowTensor;\n\n      if (inputs.length > 0 && inputs[0]) {\n        const inputTensor = inputs[0] as EdgeFlowTensor;\n        \n        // Apply transformations based on layer types\n        // For demo, apply softmax to classification outputs\n        if (outputSpec.name.includes('logits') || outputSpec.name.includes('class')) {\n          outputTensor = tensorSoftmax(inputTensor) as EdgeFlowTensor;\n        } else if (outputSpec.name.includes('relu')) {\n          outputTensor = tensorRelu(inputTensor);\n        } else if (outputSpec.name.includes('sigmoid')) {\n          outputTensor = tensorSigmoid(inputTensor);\n        } else {\n          // Identity or feature extraction\n          const outputData = new Float32Array(outputSize);\n          const inputData = inputTensor.toFloat32Array();\n          for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n            outputData[i] = inputData[i] ?? 0;\n          }\n          outputTensor = new EdgeFlowTensor(outputData, outputSpec.shape, 'float32');\n        }\n      } else {\n        outputTensor = new EdgeFlowTensor(new Float32Array(outputSize), outputSpec.shape, 'float32');\n      }\n\n      outputs.push(outputTensor);\n    }\n\n    return outputs;\n  }\n\n  /**\n   * Parse model configuration\n   */\n  private parseModelConfig(data: ArrayBuffer): WASMModelConfig {\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(2048, data.byteLength)));\n      \n      if (text.trim().startsWith('{')) {\n        let jsonEnd = text.indexOf('\\n---\\n');\n        if (jsonEnd === -1) {\n          // Try to parse as pure JSON\n          try {\n            return JSON.parse(text) as WASMModelConfig;\n          } catch {\n            jsonEnd = data.byteLength;\n          }\n        }\n        \n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr) as WASMModelConfig;\n      }\n    } catch {\n      // Not JSON format\n    }\n\n    return {\n      name: 'unknown',\n      version: '1.0.0',\n      layers: [],\n      inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n      outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n    };\n  }\n\n  /**\n   * Load weights into WASM memory\n   */\n  private async loadWeights(\n    _modelData: ArrayBuffer,\n    _wasmData: WASMModelData\n  ): Promise<void> {\n    // In a full implementation, extract and load weights\n    // This is a placeholder\n  }\n\n  /**\n   * Unload a model\n   */\n  private unloadModel(modelId: string): void {\n    const modelData = this.models.get(modelId);\n    if (modelData && this.module) {\n      // Free weight buffers\n      for (const weight of modelData.weights.values()) {\n        this.module.exports.free(weight.ptr);\n      }\n    }\n    this.models.delete(modelId);\n  }\n\n  /**\n   * Ensure runtime is initialized\n   */\n  private ensureInitialized(): void {\n    if (!this.initialized || !this.module) {\n      throw new EdgeFlowError(\n        'WASM runtime is not initialized',\n        ErrorCodes.RUNTIME_NOT_INITIALIZED\n      );\n    }\n  }\n\n  /**\n   * Check if SIMD is supported\n   */\n  hasSIMDSupport(): boolean {\n    return this.simdSupported;\n  }\n\n  /**\n   * Dispose the runtime\n   */\n  dispose(): void {\n    // Free all model weights\n    for (const modelId of this.models.keys()) {\n      this.unloadModel(modelId);\n    }\n\n    this.module = null;\n    this.initialized = false;\n  }\n}\n\n/**\n * Create WASM runtime factory\n */\nexport function createWASMRuntime(): Runtime {\n  return new WASMRuntime();\n}\n"
  },
  {
    "path": "src/backends/webgpu.ts",
    "content": "/**\n * edgeFlow.js - WebGPU Backend\n * \n * **Status: Planned** - This is a skeleton implementation that initializes\n * WebGPU and creates compute pipelines but does not perform real model\n * inference. For GPU-accelerated inference, use the ONNX Runtime backend\n * which supports WebGPU via its execution providers.\n * \n * This backend is intended for future custom WebGPU compute shader\n * implementations that bypass ONNX Runtime for specialized ops.\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n} from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n\n// ============================================================================\n// WebGPU Type Declarations\n// ============================================================================\n\n// Declare WebGPU types for environments without @webgpu/types\ndeclare global {\n  interface Navigator {\n    gpu?: GPU;\n  }\n  \n  interface GPU {\n    requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null>;\n  }\n  \n  interface GPURequestAdapterOptions {\n    powerPreference?: 'low-power' | 'high-performance';\n  }\n  \n  interface GPUAdapter {\n    requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice>;\n  }\n  \n  interface GPUDeviceDescriptor {\n    requiredFeatures?: string[];\n    requiredLimits?: Record<string, number>;\n  }\n  \n  interface GPUDevice {\n    limits: GPULimits;\n    lost: Promise<GPUDeviceLostInfo>;\n    createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer;\n    createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule;\n    createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout;\n    createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout;\n    createComputePipeline(descriptor: GPUComputePipelineDescriptor): GPUComputePipeline;\n    destroy(): void;\n  }\n  \n  interface GPULimits {\n    maxBufferSize: number;\n  }\n  \n  interface GPUDeviceLostInfo {\n    message: string;\n    reason: string;\n  }\n  \n  interface GPUBuffer {\n    destroy(): void;\n  }\n  \n  interface GPUShaderModule {}\n  interface GPUBindGroupLayout {}\n  interface GPUPipelineLayout {}\n  interface GPUComputePipeline {}\n  \n  interface GPUBufferDescriptor {\n    size: number;\n    usage: number;\n  }\n  \n  interface GPUShaderModuleDescriptor {\n    code: string;\n  }\n  \n  interface GPUBindGroupLayoutDescriptor {\n    entries: GPUBindGroupLayoutEntry[];\n  }\n  \n  interface GPUBindGroupLayoutEntry {\n    binding: number;\n    visibility: number;\n    buffer?: { type: string };\n  }\n  \n  interface GPUPipelineLayoutDescriptor {\n    bindGroupLayouts: GPUBindGroupLayout[];\n  }\n  \n  interface GPUComputePipelineDescriptor {\n    layout: GPUPipelineLayout;\n    compute: {\n      module: GPUShaderModule;\n      entryPoint: string;\n    };\n  }\n}\n\n// WebGPU constants\nconst GPUBufferUsage = {\n  STORAGE: 0x0080,\n  COPY_SRC: 0x0004,\n  COPY_DST: 0x0008,\n  MAP_READ: 0x0001,\n};\n\nconst GPUShaderStage = {\n  COMPUTE: 0x0004,\n};\n\n// ============================================================================\n// WebGPU Types\n// ============================================================================\n\n/**\n * WebGPU model data structure\n */\ninterface WebGPUModelData {\n  /** Shader modules */\n  shaders: Map<string, GPUShaderModule>;\n  /** Compute pipelines */\n  pipelines: Map<string, GPUComputePipeline>;\n  /** Weight buffers */\n  weights: Map<string, GPUBuffer>;\n  /** Bind group layouts */\n  bindGroupLayouts: GPUBindGroupLayout[];\n  /** Model configuration */\n  config: ModelConfig;\n}\n\n/**\n * Model configuration from model file\n */\ninterface ModelConfig {\n  name: string;\n  version: string;\n  layers: LayerConfig[];\n  inputs: { name: string; shape: number[]; dtype: string }[];\n  outputs: { name: string; shape: number[]; dtype: string }[];\n}\n\n/**\n * Layer configuration\n */\ninterface LayerConfig {\n  name: string;\n  type: string;\n  inputs: string[];\n  outputs: string[];\n  params: Record<string, unknown>;\n}\n\n// ============================================================================\n// WebGPU Runtime Implementation\n// ============================================================================\n\n/**\n * WebGPURuntime - GPU-accelerated inference runtime\n */\nexport class WebGPURuntime implements Runtime {\n  readonly name: RuntimeType = 'webgpu';\n  \n  private adapter: GPUAdapter | null = null;\n  private device: GPUDevice | null = null;\n  private models: Map<string, WebGPUModelData> = new Map();\n  private initialized = false;\n\n  get capabilities(): RuntimeCapabilities {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: false,\n      maxBatchSize: 64,\n      availableMemory: this.device?.limits.maxBufferSize ?? 256 * 1024 * 1024,\n    };\n  }\n\n  /**\n   * Check if WebGPU is available\n   */\n  async isAvailable(): Promise<boolean> {\n    if (typeof navigator === 'undefined') return false;\n    if (!navigator.gpu) return false;\n\n    try {\n      const adapter = await navigator.gpu.requestAdapter();\n      return adapter !== null;\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Initialize the WebGPU runtime\n   */\n  async initialize(): Promise<void> {\n    if (this.initialized) return;\n\n    if (!navigator.gpu) {\n      throw new EdgeFlowError(\n        'WebGPU is not supported in this browser',\n        ErrorCodes.RUNTIME_NOT_AVAILABLE\n      );\n    }\n\n    // Request adapter\n    this.adapter = await navigator.gpu.requestAdapter({\n      powerPreference: 'high-performance',\n    });\n\n    if (!this.adapter) {\n      throw new EdgeFlowError(\n        'Failed to get WebGPU adapter',\n        ErrorCodes.RUNTIME_INIT_FAILED\n      );\n    }\n\n    // Request device\n    this.device = await this.adapter.requestDevice({\n      requiredFeatures: [],\n      requiredLimits: {},\n    });\n\n    // Handle device loss\n    this.device.lost.then((info: GPUDeviceLostInfo) => {\n      console.error('WebGPU device was lost:', info.message);\n      this.initialized = false;\n      this.device = null;\n    });\n\n    this.initialized = true;\n  }\n\n  /**\n   * Load a model\n   */\n  async loadModel(\n    modelData: ArrayBuffer,\n    options: ModelLoadOptions = {}\n  ): Promise<LoadedModel> {\n    this.ensureInitialized();\n\n    // Parse model data\n    const config = this.parseModelData(modelData);\n\n    // Create shader modules and pipelines\n    const webgpuData: WebGPUModelData = {\n      shaders: new Map(),\n      pipelines: new Map(),\n      weights: new Map(),\n      bindGroupLayouts: [],\n      config,\n    };\n\n    // Extract and upload weights\n    await this.uploadWeights(modelData, webgpuData);\n\n    // Create compute pipelines for each layer\n    await this.createPipelines(webgpuData);\n\n    // Generate model ID\n    const modelId = `webgpu_${Date.now().toString(36)}`;\n    this.models.set(modelId, webgpuData);\n\n    // Create metadata\n    const metadata: ModelMetadata = {\n      name: config.name || options.metadata?.name || 'unknown',\n      version: config.version,\n      inputs: config.inputs.map(i => ({\n        name: i.name,\n        dtype: i.dtype as 'float32',\n        shape: i.shape,\n      })),\n      outputs: config.outputs.map(o => ({\n        name: o.name,\n        dtype: o.dtype as 'float32',\n        shape: o.shape,\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? 'float32',\n      format: 'edgeflow',\n    };\n\n    // Create model instance\n    const model = new LoadedModelImpl(\n      metadata,\n      'webgpu',\n      () => this.unloadModel(modelId)\n    );\n\n    // Track in memory manager\n    getMemoryManager().trackModel(model, () => model.dispose());\n\n    return model;\n  }\n\n  /**\n   * Run inference\n   */\n  async run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]> {\n    this.ensureInitialized();\n\n    // For now, use a simple fallback implementation\n    // In a full implementation, this would execute the compute pipelines\n    return this.executeModel(inputs, model.metadata);\n  }\n\n  /**\n   * Execute model (simplified implementation)\n   */\n  private async executeModel(inputs: Tensor[], metadata: ModelMetadata): Promise<Tensor[]> {\n    // This is a simplified implementation\n    // A full implementation would:\n    // 1. Upload input tensors to GPU buffers\n    // 2. Execute compute pipelines in topological order\n    // 3. Read back output tensors\n\n    const device = this.device!;\n    const outputs: Tensor[] = [];\n\n    for (const outputSpec of metadata.outputs) {\n      // Create output buffer\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      const outputBuffer = device.createBuffer({\n        size: outputSize * 4, // float32\n        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n      });\n\n      // Create staging buffer for readback\n      const stagingBuffer = device.createBuffer({\n        size: outputSize * 4,\n        usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n      });\n\n      // For now, return zeros (placeholder)\n      // In production, execute actual compute pipelines\n      const outputData = new Float32Array(outputSize);\n      \n      // Simulate some computation based on inputs\n      if (inputs.length > 0 && inputs[0]) {\n        const inputData = inputs[0].toFloat32Array();\n        for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n          outputData[i] = (inputData[i] ?? 0);\n        }\n      }\n\n      outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, 'float32'));\n\n      // Cleanup\n      outputBuffer.destroy();\n      stagingBuffer.destroy();\n    }\n\n    return outputs;\n  }\n\n  /**\n   * Parse model data\n   */\n  private parseModelData(data: ArrayBuffer): ModelConfig {\n    // Try to parse as JSON first (for our custom format)\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n      \n      // Check if it starts with JSON\n      if (text.trim().startsWith('{')) {\n        // Find the JSON header end\n        let jsonEnd = text.indexOf('\\n---\\n');\n        if (jsonEnd === -1) jsonEnd = data.byteLength;\n        \n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr) as ModelConfig;\n      }\n    } catch {\n      // Not JSON format\n    }\n\n    // Return default config for unknown formats\n    return {\n      name: 'unknown',\n      version: '1.0.0',\n      layers: [],\n      inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n      outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n    };\n  }\n\n  /**\n   * Upload weights to GPU\n   */\n  private async uploadWeights(\n    _data: ArrayBuffer,\n    modelData: WebGPUModelData\n  ): Promise<void> {\n    const device = this.device!;\n\n    // In a full implementation, parse weight data from the model file\n    // and upload to GPU buffers\n    \n    // Placeholder: create empty weight buffer\n    const weightsBuffer = device.createBuffer({\n      size: 1024,\n      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n    });\n\n    modelData.weights.set('default', weightsBuffer);\n  }\n\n  /**\n   * Create compute pipelines\n   */\n  private async createPipelines(modelData: WebGPUModelData): Promise<void> {\n    const device = this.device!;\n\n    // Create a general-purpose compute shader\n    const shaderCode = /* wgsl */ `\n      @group(0) @binding(0) var<storage, read> input: array<f32>;\n      @group(0) @binding(1) var<storage, read_write> output: array<f32>;\n      \n      @compute @workgroup_size(64)\n      fn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n        let idx = gid.x;\n        if (idx < arrayLength(&input)) {\n          output[idx] = input[idx];\n        }\n      }\n    `;\n\n    const shaderModule = device.createShaderModule({\n      code: shaderCode,\n    });\n\n    modelData.shaders.set('default', shaderModule);\n\n    // Create bind group layout\n    const bindGroupLayout = device.createBindGroupLayout({\n      entries: [\n        {\n          binding: 0,\n          visibility: GPUShaderStage.COMPUTE,\n          buffer: { type: 'read-only-storage' },\n        },\n        {\n          binding: 1,\n          visibility: GPUShaderStage.COMPUTE,\n          buffer: { type: 'storage' },\n        },\n      ],\n    });\n\n    modelData.bindGroupLayouts.push(bindGroupLayout);\n\n    // Create pipeline layout\n    const pipelineLayout = device.createPipelineLayout({\n      bindGroupLayouts: [bindGroupLayout],\n    });\n\n    // Create compute pipeline\n    const pipeline = device.createComputePipeline({\n      layout: pipelineLayout,\n      compute: {\n        module: shaderModule,\n        entryPoint: 'main',\n      },\n    });\n\n    modelData.pipelines.set('default', pipeline);\n  }\n\n  /**\n   * Unload a model\n   */\n  private unloadModel(modelId: string): void {\n    const modelData = this.models.get(modelId);\n    if (modelData) {\n      // Destroy GPU buffers\n      for (const buffer of modelData.weights.values()) {\n        buffer.destroy();\n      }\n      this.models.delete(modelId);\n    }\n  }\n\n  /**\n   * Ensure runtime is initialized\n   */\n  private ensureInitialized(): void {\n    if (!this.initialized || !this.device) {\n      throw new EdgeFlowError(\n        'WebGPU runtime is not initialized',\n        ErrorCodes.RUNTIME_NOT_INITIALIZED\n      );\n    }\n  }\n\n  /**\n   * Dispose the runtime\n   */\n  dispose(): void {\n    // Unload all models\n    for (const modelId of this.models.keys()) {\n      this.unloadModel(modelId);\n    }\n\n    // Destroy device\n    if (this.device) {\n      this.device.destroy();\n      this.device = null;\n    }\n\n    this.adapter = null;\n    this.initialized = false;\n  }\n}\n\n/**\n * Create WebGPU runtime factory\n */\nexport function createWebGPURuntime(): Runtime {\n  return new WebGPURuntime();\n}\n"
  },
  {
    "path": "src/backends/webnn.ts",
    "content": "/**\n * edgeFlow.js - WebNN Backend\n * \n * **Status: Planned** - This is a skeleton implementation that initializes\n * a WebNN context but does not perform real model inference or graph building.\n * For hardware-accelerated inference, use the ONNX Runtime backend which\n * supports WebNN via its execution providers when available.\n * \n * This backend is intended for future native WebNN graph building support.\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n} from '../core/types.js';\nimport { LoadedModelImpl } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { getMemoryManager } from '../core/memory.js';\n\n// ============================================================================\n// WebNN Type Definitions (since WebNN types may not be globally available)\n// ============================================================================\n\n/**\n * WebNN context type\n */\ntype MLContextType = 'default' | 'gpu' | 'cpu' | 'npu';\n\n/**\n * WebNN operand descriptor\n */\ninterface MLOperandDescriptor {\n  dataType: 'float32' | 'float16' | 'int32' | 'uint32' | 'int8' | 'uint8';\n  dimensions: number[];\n}\n\n/**\n * WebNN context options\n */\ninterface MLContextOptions {\n  deviceType?: MLContextType;\n  powerPreference?: 'default' | 'high-performance' | 'low-power';\n}\n\n// Extend Navigator for WebNN\ndeclare global {\n  interface Navigator {\n    ml?: {\n      createContext(options?: MLContextOptions): Promise<MLContext>;\n    };\n  }\n  \n  interface MLContext {\n    compute(\n      graph: MLGraph,\n      inputs: Record<string, ArrayBufferView>,\n      outputs: Record<string, ArrayBufferView>\n    ): Promise<Record<string, ArrayBufferView>>;\n  }\n  \n  interface MLGraph {\n    // Graph interface\n  }\n  \n  interface MLGraphBuilder {\n    input(name: string, desc: MLOperandDescriptor): MLOperand;\n    constant(desc: MLOperandDescriptor, data: ArrayBufferView): MLOperand;\n    build(outputs: Record<string, MLOperand>): Promise<MLGraph>;\n    \n    // Operations\n    add(a: MLOperand, b: MLOperand): MLOperand;\n    sub(a: MLOperand, b: MLOperand): MLOperand;\n    mul(a: MLOperand, b: MLOperand): MLOperand;\n    div(a: MLOperand, b: MLOperand): MLOperand;\n    matmul(a: MLOperand, b: MLOperand): MLOperand;\n    relu(x: MLOperand): MLOperand;\n    sigmoid(x: MLOperand): MLOperand;\n    tanh(x: MLOperand): MLOperand;\n    softmax(x: MLOperand): MLOperand;\n    reshape(x: MLOperand, newShape: number[]): MLOperand;\n    transpose(x: MLOperand, permutation?: number[]): MLOperand;\n  }\n  \n  interface MLOperand {\n    // Operand interface\n  }\n}\n\n// ============================================================================\n// WebNN Model Data\n// ============================================================================\n\n/**\n * WebNN model data structure\n */\ninterface WebNNModelData {\n  /** Compiled graph */\n  graph: MLGraph;\n  /** Graph builder (for potential graph modifications) */\n  builder: MLGraphBuilder;\n  /** Input names and shapes */\n  inputNames: string[];\n  /** Output names and shapes */\n  outputNames: string[];\n  /** Model configuration */\n  config: WebNNModelConfig;\n}\n\n/**\n * Model configuration\n */\ninterface WebNNModelConfig {\n  name: string;\n  version: string;\n  inputs: { name: string; shape: number[]; dtype: string }[];\n  outputs: { name: string; shape: number[]; dtype: string }[];\n}\n\n// ============================================================================\n// WebNN Runtime Implementation\n// ============================================================================\n\n/**\n * WebNNRuntime - Browser-native neural network runtime\n */\nexport class WebNNRuntime implements Runtime {\n  readonly name: RuntimeType = 'webnn';\n  \n  private context: MLContext | null = null;\n  private models: Map<string, WebNNModelData> = new Map();\n  private initialized = false;\n  private deviceType: MLContextType = 'default';\n\n  get capabilities(): RuntimeCapabilities {\n    return {\n      concurrency: true,\n      quantization: true,\n      float16: true,\n      dynamicShapes: false,\n      maxBatchSize: 32,\n      availableMemory: 256 * 1024 * 1024, // Estimated\n    };\n  }\n\n  /**\n   * Check if WebNN is available\n   */\n  async isAvailable(): Promise<boolean> {\n    if (typeof navigator === 'undefined') return false;\n    if (!navigator.ml) return false;\n\n    try {\n      const context = await navigator.ml.createContext({ deviceType: 'default' });\n      return context !== null;\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Initialize the WebNN runtime\n   */\n  async initialize(): Promise<void> {\n    if (this.initialized) return;\n\n    if (!navigator.ml) {\n      throw new EdgeFlowError(\n        'WebNN is not supported in this browser',\n        ErrorCodes.RUNTIME_NOT_AVAILABLE\n      );\n    }\n\n    // Try to get GPU context first, fallback to CPU\n    try {\n      this.context = await navigator.ml.createContext({ \n        deviceType: 'gpu',\n        powerPreference: 'high-performance',\n      });\n      this.deviceType = 'gpu';\n    } catch {\n      try {\n        this.context = await navigator.ml.createContext({ deviceType: 'cpu' });\n        this.deviceType = 'cpu';\n      } catch (error) {\n        throw new EdgeFlowError(\n          `Failed to create WebNN context: ${error instanceof Error ? error.message : String(error)}`,\n          ErrorCodes.RUNTIME_INIT_FAILED\n        );\n      }\n    }\n\n    this.initialized = true;\n  }\n\n  /**\n   * Load a model\n   */\n  async loadModel(\n    modelData: ArrayBuffer,\n    options: ModelLoadOptions = {}\n  ): Promise<LoadedModel> {\n    this.ensureInitialized();\n\n    // Parse model configuration\n    const config = this.parseModelConfig(modelData);\n\n    // Note: Full WebNN implementation would build the graph here\n    // This is a placeholder that creates minimal metadata\n    \n    const modelId = `webnn_${Date.now().toString(36)}`;\n\n    // Create metadata\n    const metadata: ModelMetadata = {\n      name: config.name || options.metadata?.name || 'unknown',\n      version: config.version || '1.0.0',\n      inputs: config.inputs.map(i => ({\n        name: i.name,\n        dtype: i.dtype as 'float32',\n        shape: i.shape,\n      })),\n      outputs: config.outputs.map(o => ({\n        name: o.name,\n        dtype: o.dtype as 'float32',\n        shape: o.shape,\n      })),\n      sizeBytes: modelData.byteLength,\n      quantization: options.quantization ?? 'float32',\n      format: 'edgeflow',\n    };\n\n    // Create model instance\n    const model = new LoadedModelImpl(\n      metadata,\n      'webnn',\n      () => this.unloadModel(modelId)\n    );\n\n    // Track in memory manager\n    getMemoryManager().trackModel(model, () => model.dispose());\n\n    return model;\n  }\n\n  /**\n   * Run inference\n   */\n  async run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]> {\n    this.ensureInitialized();\n\n    // Simplified implementation - in production, would use compiled graph\n    return this.executeModel(inputs, model.metadata);\n  }\n\n  /**\n   * Execute model (simplified implementation)\n   */\n  private async executeModel(inputs: Tensor[], metadata: ModelMetadata): Promise<Tensor[]> {\n    const outputs: Tensor[] = [];\n\n    // For each expected output\n    for (const outputSpec of metadata.outputs) {\n      const outputSize = outputSpec.shape.reduce((a, b) => a * b, 1);\n      const outputData = new Float32Array(outputSize);\n\n      // Simple passthrough for demo (real impl would use WebNN compute)\n      if (inputs.length > 0 && inputs[0]) {\n        const inputData = inputs[0].toFloat32Array();\n        for (let i = 0; i < Math.min(outputSize, inputData.length); i++) {\n          outputData[i] = inputData[i] ?? 0;\n        }\n      }\n\n      outputs.push(new EdgeFlowTensor(outputData, outputSpec.shape, 'float32'));\n    }\n\n    return outputs;\n  }\n\n  /**\n   * Parse model configuration\n   */\n  private parseModelConfig(data: ArrayBuffer): WebNNModelConfig {\n    try {\n      const decoder = new TextDecoder();\n      const text = decoder.decode(new Uint8Array(data, 0, Math.min(1024, data.byteLength)));\n      \n      if (text.trim().startsWith('{')) {\n        let jsonEnd = text.indexOf('\\n---\\n');\n        if (jsonEnd === -1) jsonEnd = data.byteLength;\n        \n        const jsonStr = decoder.decode(new Uint8Array(data, 0, jsonEnd));\n        return JSON.parse(jsonStr) as WebNNModelConfig;\n      }\n    } catch {\n      // Not JSON format\n    }\n\n    return {\n      name: 'unknown',\n      version: '1.0.0',\n      inputs: [{ name: 'input', shape: [-1, 768], dtype: 'float32' }],\n      outputs: [{ name: 'output', shape: [-1, 768], dtype: 'float32' }],\n    };\n  }\n\n  /**\n   * Unload a model\n   */\n  private unloadModel(modelId: string): void {\n    this.models.delete(modelId);\n  }\n\n  /**\n   * Ensure runtime is initialized\n   */\n  private ensureInitialized(): void {\n    if (!this.initialized || !this.context) {\n      throw new EdgeFlowError(\n        'WebNN runtime is not initialized',\n        ErrorCodes.RUNTIME_NOT_INITIALIZED\n      );\n    }\n  }\n\n  /**\n   * Get device type\n   */\n  getDeviceType(): MLContextType {\n    return this.deviceType;\n  }\n\n  /**\n   * Dispose the runtime\n   */\n  dispose(): void {\n    this.models.clear();\n    this.context = null;\n    this.initialized = false;\n  }\n}\n\n/**\n * Create WebNN runtime factory\n */\nexport function createWebNNRuntime(): Runtime {\n  return new WebNNRuntime();\n}\n"
  },
  {
    "path": "src/core/composer.ts",
    "content": "/**\n * edgeFlow.js - Pipeline Composer\n *\n * Chain multiple pipelines together to build complex multi-model workflows.\n * Each stage's output is transformed and fed as input to the next stage.\n *\n * @example\n * ```typescript\n * import { compose } from 'edgeflowjs';\n *\n * const speechTranslator = compose([\n *   { task: 'automatic-speech-recognition' },\n *   { task: 'translation', options: { srcLang: 'en', tgtLang: 'zh' } },\n * ]);\n *\n * const result = await speechTranslator.run(audioBlob);\n * // result.stages = [asrResult, translationResult]\n * // result.output  = final translation text\n * ```\n */\n\nimport { pipeline, type PipelineFactoryOptions } from '../pipelines/index.js';\nimport type { PipelineTask } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A single stage in a composed pipeline.\n */\nexport interface CompositionStage {\n  /** The pipeline task to run */\n  task: PipelineTask | (string & {});\n  /** Model override for this stage */\n  model?: string;\n  /** Extra options forwarded to `pipeline()` */\n  options?: PipelineFactoryOptions;\n  /**\n   * Optional transform applied to the previous stage's output before it is\n   * passed as input to this stage. If omitted, the raw output is forwarded.\n   */\n  transform?: (previousOutput: unknown) => unknown;\n  /**\n   * Options forwarded to the pipeline's `run()` call.\n   */\n  runOptions?: Record<string, unknown>;\n}\n\n/**\n * Result from running a composed pipeline.\n */\nexport interface CompositionResult {\n  /** The final output from the last stage */\n  output: unknown;\n  /** Intermediate results for every stage (index-aligned with stages) */\n  stages: unknown[];\n  /** Total wall-clock time in milliseconds */\n  totalTime: number;\n  /** Per-stage timing */\n  stageTimes: number[];\n}\n\n/**\n * A composed (chained) pipeline.\n */\nexport interface ComposedPipeline {\n  /** Execute the full chain with the given initial input */\n  run(input: unknown): Promise<CompositionResult>;\n  /** Dispose all underlying pipeline instances */\n  dispose(): void;\n  /** Number of stages */\n  readonly length: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Compose multiple pipeline stages into a single sequential chain.\n *\n * The output of each stage is fed as the input to the next stage. Use the\n * optional `transform` hook in a stage to reshape data between stages.\n *\n * All pipelines are lazily initialised on the first `run()` call and cached\n * for subsequent calls.\n *\n * @param stages - Ordered list of pipeline stages\n * @returns A composed pipeline that can be run end-to-end\n *\n * @example\n * ```typescript\n * const ocrPipeline = compose([\n *   { task: 'image-to-text' },\n *   {\n *     task: 'text-classification',\n *     transform: (ocrResult: any) => ocrResult.text,\n *   },\n * ]);\n *\n * const { output, stages, totalTime } = await ocrPipeline.run(imageElement);\n * ```\n */\nexport function compose(stages: CompositionStage[]): ComposedPipeline {\n  if (stages.length === 0) {\n    throw new Error('[edgeFlow.js] compose() requires at least one stage');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let pipelineInstances: any[] | null = null;\n\n  async function ensureInitialised() {\n    if (pipelineInstances) return pipelineInstances;\n\n    pipelineInstances = await Promise.all(\n      stages.map((stage) =>\n        pipeline(stage.task as Parameters<typeof pipeline>[0], {\n          model: stage.model,\n          ...stage.options,\n        }),\n      ),\n    );\n    return pipelineInstances;\n  }\n\n  return {\n    get length() {\n      return stages.length;\n    },\n\n    async run(input: unknown): Promise<CompositionResult> {\n      const instances = await ensureInitialised();\n      const stageResults: unknown[] = [];\n      const stageTimes: number[] = [];\n      let current = input;\n      const wallStart = performance.now();\n\n      for (let i = 0; i < stages.length; i++) {\n        const stage = stages[i]!;\n        const inst = instances[i]!;\n\n        // Apply transform from previous stage output if provided\n        if (stage.transform) {\n          current = stage.transform(current);\n        }\n\n        const t0 = performance.now();\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        current = await (inst as any).run(current, stage.runOptions);\n        stageTimes.push(performance.now() - t0);\n        stageResults.push(current);\n      }\n\n      return {\n        output: current,\n        stages: stageResults,\n        totalTime: performance.now() - wallStart,\n        stageTimes,\n      };\n    },\n\n    dispose() {\n      if (pipelineInstances) {\n        for (const inst of pipelineInstances) {\n          if (inst && typeof inst.dispose === 'function') {\n            inst.dispose();\n          }\n        }\n        pipelineInstances = null;\n      }\n    },\n  };\n}\n\n/**\n * Run stages in parallel (fan-out) and collect all results.\n *\n * Unlike `compose` (which is sequential), `parallel` runs every stage\n * independently with the same input and returns an array of results.\n *\n * @example\n * ```typescript\n * const analyzer = parallel([\n *   { task: 'text-classification' },\n *   { task: 'feature-extraction' },\n *   { task: 'zero-shot-classification',\n *     transform: (text) => ({ text, candidateLabels: ['news', 'sports'] }) },\n * ]);\n *\n * const results = await analyzer.run('Breaking: team wins championship');\n * ```\n */\nexport function parallel(\n  stages: CompositionStage[],\n): {\n  run(input: unknown): Promise<{ outputs: unknown[]; totalTime: number }>;\n  dispose(): void;\n} {\n  if (stages.length === 0) {\n    throw new Error('[edgeFlow.js] parallel() requires at least one stage');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let pipelineInstances: any[] | null = null;\n\n  async function ensureInitialised() {\n    if (pipelineInstances) return pipelineInstances;\n    pipelineInstances = await Promise.all(\n      stages.map((s) =>\n        pipeline(s.task as Parameters<typeof pipeline>[0], {\n          model: s.model,\n          ...s.options,\n        }),\n      ),\n    );\n    return pipelineInstances;\n  }\n\n  return {\n    async run(input: unknown) {\n      const instances = await ensureInitialised();\n      const t0 = performance.now();\n\n      const outputs = await Promise.all(\n        stages.map((stage, i) => {\n          const stageInput = stage.transform ? stage.transform(input) : input;\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          return (instances[i] as any).run(stageInput, stage.runOptions);\n        }),\n      );\n\n      return { outputs, totalTime: performance.now() - t0 };\n    },\n\n    dispose() {\n      if (pipelineInstances) {\n        for (const inst of pipelineInstances) {\n          if (inst && typeof inst.dispose === 'function') {\n            inst.dispose();\n          }\n        }\n        pipelineInstances = null;\n      }\n    },\n  };\n}\n"
  },
  {
    "path": "src/core/device-profiler.ts",
    "content": "/**\n * edgeFlow.js - Device Profiler\n *\n * Automatically profiles the current device and recommends optimal model\n * variants (quantization level, batch size, execution provider).\n *\n * @example\n * ```typescript\n * import { getDeviceProfile, recommendQuantization } from 'edgeflowjs';\n *\n * const profile = await getDeviceProfile();\n * console.log(profile.tier); // 'high' | 'medium' | 'low'\n *\n * const quant = recommendQuantization(profile);\n * console.log(quant); // 'fp16' | 'int8' | 'int4'\n * ```\n */\n\nimport type { QuantizationType } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Device capability tier.\n */\nexport type DeviceTier = 'high' | 'medium' | 'low';\n\n/**\n * Profiled device information.\n */\nexport interface DeviceProfile {\n  /** Capability tier */\n  tier: DeviceTier;\n  /** Number of logical CPU cores */\n  cores: number;\n  /** Device memory in GiB (navigator.deviceMemory, may be null) */\n  memoryGiB: number | null;\n  /** Whether WebGPU is available */\n  webgpu: boolean;\n  /** Whether WebNN is available */\n  webnn: boolean;\n  /** Recommended max batch size */\n  recommendedBatchSize: number;\n  /** Recommended concurrency limit */\n  recommendedConcurrency: number;\n  /** Whether the device is mobile */\n  mobile: boolean;\n  /** Raw GPU adapter info (if WebGPU available) */\n  gpuInfo?: string;\n}\n\n/**\n * Model variant recommendation.\n */\nexport interface ModelRecommendation {\n  /** Recommended quantization */\n  quantization: QuantizationType;\n  /** Recommended execution provider */\n  executionProvider: 'webgpu' | 'wasm';\n  /** Recommended batch size */\n  batchSize: number;\n  /** Whether to enable worker-based inference */\n  useWorker: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Profiling\n// ---------------------------------------------------------------------------\n\nlet cachedProfile: DeviceProfile | null = null;\n\n/**\n * Profile the current device. Results are cached after the first call.\n */\nexport async function getDeviceProfile(): Promise<DeviceProfile> {\n  if (cachedProfile) return cachedProfile;\n\n  const cores = typeof navigator !== 'undefined'\n    ? navigator.hardwareConcurrency ?? 2\n    : 2;\n\n  const memoryGiB = typeof navigator !== 'undefined' && 'deviceMemory' in navigator\n    ? (navigator as { deviceMemory?: number }).deviceMemory ?? null\n    : null;\n\n  const mobile = typeof navigator !== 'undefined'\n    ? /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent)\n    : false;\n\n  let webgpu = false;\n  let gpuInfo: string | undefined;\n  if (typeof navigator !== 'undefined' && 'gpu' in navigator) {\n    try {\n      const adapter = await (navigator as Navigator & { gpu: { requestAdapter: () => Promise<unknown> } }).gpu.requestAdapter();\n      webgpu = adapter != null;\n      if (adapter && typeof adapter === 'object') {\n        try {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          const info = (adapter as any)['info'];\n          if (info) {\n            gpuInfo = `${info['vendor'] ?? ''} ${info['architecture'] ?? ''}`.trim() || undefined;\n          }\n        } catch {\n          // info not available\n        }\n      }\n    } catch {\n      // WebGPU not available\n    }\n  }\n\n  let webnn = false;\n  if (typeof navigator !== 'undefined' && 'ml' in navigator) {\n    try {\n      const ml = (navigator as Navigator & { ml?: { createContext: () => Promise<unknown> } }).ml;\n      if (ml) {\n        const ctx = await ml.createContext();\n        webnn = ctx != null;\n      }\n    } catch {\n      // WebNN not available\n    }\n  }\n\n  // Determine tier\n  let tier: DeviceTier;\n  if (webgpu && cores >= 8 && (memoryGiB === null || memoryGiB >= 8)) {\n    tier = 'high';\n  } else if (cores >= 4 && (memoryGiB === null || memoryGiB >= 4)) {\n    tier = 'medium';\n  } else {\n    tier = 'low';\n  }\n\n  // Mobile devices get capped even if specs look good\n  if (mobile && tier === 'high') {\n    tier = 'medium';\n  }\n\n  const recommendedBatchSize = tier === 'high' ? 32 : tier === 'medium' ? 8 : 1;\n  const recommendedConcurrency = tier === 'high' ? 4 : tier === 'medium' ? 2 : 1;\n\n  cachedProfile = {\n    tier,\n    cores,\n    memoryGiB,\n    webgpu,\n    webnn,\n    recommendedBatchSize,\n    recommendedConcurrency,\n    mobile,\n    gpuInfo,\n  };\n\n  return cachedProfile;\n}\n\n/**\n * Recommend the best quantization level for the current device.\n */\nexport function recommendQuantization(profile: DeviceProfile): QuantizationType {\n  if (profile.tier === 'high' && profile.webgpu) return 'float16';\n  if (profile.tier === 'medium') return 'int8';\n  return 'int8'; // low-tier: most aggressive\n}\n\n/**\n * Get full model variant recommendations for the current device.\n */\nexport async function recommendModelVariant(): Promise<ModelRecommendation> {\n  const profile = await getDeviceProfile();\n\n  return {\n    quantization: recommendQuantization(profile),\n    executionProvider: profile.webgpu ? 'webgpu' : 'wasm',\n    batchSize: profile.recommendedBatchSize,\n    useWorker: profile.cores >= 4,\n  };\n}\n\n/**\n * Reset the cached profile (useful for testing).\n */\nexport function resetDeviceProfile(): void {\n  cachedProfile = null;\n}\n"
  },
  {
    "path": "src/core/index.ts",
    "content": "/**\n * edgeFlow.js - Core Module Exports\n */\n\n// Types\nexport * from './types.js';\n\n// Tensor\nexport {\n  EdgeFlowTensor,\n  tensor,\n  zeros,\n  ones,\n  full,\n  random,\n  randn,\n  arange,\n  linspace,\n  eye,\n  add,\n  sub,\n  mul,\n  div,\n  matmul,\n  softmax,\n  relu,\n  sigmoid,\n  tanh,\n  sum,\n  mean,\n  argmax,\n  concat,\n} from './tensor.js';\n\n// Scheduler\nexport {\n  InferenceScheduler,\n  getScheduler,\n  setScheduler,\n  configureScheduler,\n} from './scheduler.js';\n\n// Memory\nexport {\n  MemoryManager,\n  MemoryScope,\n  ModelCache,\n  withMemoryScope,\n  withMemoryScopeSync,\n  getMemoryManager,\n  getMemoryStats,\n  release,\n  gc,\n} from './memory.js';\n\n// Runtime\nexport {\n  RuntimeManager,\n  LoadedModelImpl,\n  loadModel,\n  loadModelFromBuffer,\n  runInference,\n  runBatchInference,\n  getRuntimeManager,\n  registerRuntime,\n  getBestRuntime,\n  getAvailableRuntimes,\n} from './runtime.js';\n\n// Plugin System\nexport {\n  registerPlugin,\n  getPluginPipeline,\n  getPluginMiddleware,\n  listPlugins,\n  unregisterPlugin,\n  type EdgeFlowPlugin,\n  type PluginPipelineEntry,\n  type PluginBackendEntry,\n  type PluginMiddleware,\n} from './plugin.js';\n\n// Device Profiler\nexport {\n  getDeviceProfile,\n  recommendQuantization,\n  recommendModelVariant,\n  resetDeviceProfile,\n  type DeviceProfile,\n  type DeviceTier,\n  type ModelRecommendation,\n} from './device-profiler.js';\n\n// Composer\nexport {\n  compose,\n  parallel,\n  type CompositionStage,\n  type CompositionResult,\n  type ComposedPipeline,\n} from './composer.js';\n\n// Worker\nexport {\n  InferenceWorker,\n  WorkerPool,\n  getWorkerPool,\n  runInWorker,\n  isWorkerSupported,\n  serializeTensor,\n  deserializeTensor,\n  type WorkerMessage,\n  type WorkerMessageType,\n  type LoadModelRequest,\n  type InferenceRequest,\n  type SerializedTensor,\n  type WorkerPoolOptions,\n} from './worker.js';\n"
  },
  {
    "path": "src/core/memory.ts",
    "content": "/**\n * edgeFlow.js - Memory Management\n * \n * Efficient memory management for tensors and models.\n * Features:\n * - Memory pooling\n * - Automatic garbage collection\n * - Memory tracking and statistics\n * - Leak detection\n */\n\nimport {\n  Tensor,\n  LoadedModel,\n  MemoryStats,\n  MemoryPoolConfig,\n  EventType,\n  EventListener,\n  EdgeFlowEvent,\n} from './types.js';\n\n// ============================================================================\n// Memory Tracking\n// ============================================================================\n\n/**\n * Tracked resource info\n */\ninterface TrackedResource {\n  id: string;\n  type: 'tensor' | 'model';\n  size: number;\n  createdAt: number;\n  stackTrace?: string;\n}\n\n/**\n * Default memory pool configuration\n */\nconst DEFAULT_POOL_CONFIG: Required<MemoryPoolConfig> = {\n  initialSize: 64 * 1024 * 1024, // 64MB\n  maxSize: 512 * 1024 * 1024, // 512MB\n  growthFactor: 1.5,\n  autoGC: true,\n  gcThreshold: 0.8, // 80%\n};\n\n// ============================================================================\n// Memory Manager\n// ============================================================================\n\n/**\n * MemoryManager - Central memory management\n * \n * Provides:\n * - Resource tracking\n * - Memory statistics\n * - Garbage collection coordination\n * - Memory warning events\n */\nexport class MemoryManager {\n  private static instance: MemoryManager | null = null;\n  \n  private readonly config: Required<MemoryPoolConfig>;\n  private readonly resources: Map<string, TrackedResource> = new Map();\n  private readonly disposers: Map<string, () => void> = new Map();\n  private readonly listeners: Map<EventType, Set<EventListener>> = new Map();\n  \n  private allocated = 0;\n  private peak = 0;\n  private gcScheduled = false;\n  private disposed = false;\n\n  private constructor(config: MemoryPoolConfig = {}) {\n    this.config = { ...DEFAULT_POOL_CONFIG, ...config };\n  }\n\n  /**\n   * Get singleton instance\n   */\n  static getInstance(): MemoryManager {\n    if (!MemoryManager.instance) {\n      MemoryManager.instance = new MemoryManager();\n    }\n    return MemoryManager.instance;\n  }\n\n  /**\n   * Configure the memory manager\n   */\n  static configure(config: MemoryPoolConfig): void {\n    if (MemoryManager.instance) {\n      console.warn('MemoryManager already initialized, configuration may not apply');\n    }\n    MemoryManager.instance = new MemoryManager(config);\n  }\n\n  /**\n   * Track a tensor\n   */\n  track(tensor: Tensor, disposer?: () => void): void {\n    if (this.disposed) return;\n\n    const size = this.estimateTensorSize(tensor);\n    \n    this.resources.set(tensor.id, {\n      id: tensor.id,\n      type: 'tensor',\n      size,\n      createdAt: Date.now(),\n      stackTrace: this.captureStackTrace(),\n    });\n\n    if (disposer) {\n      this.disposers.set(tensor.id, disposer);\n    }\n\n    this.allocated += size;\n    this.peak = Math.max(this.peak, this.allocated);\n\n    this.checkMemoryThreshold();\n  }\n\n  /**\n   * Track a model\n   */\n  trackModel(model: LoadedModel, disposer?: () => void): void {\n    if (this.disposed) return;\n\n    const size = model.metadata.sizeBytes;\n    \n    this.resources.set(model.id, {\n      id: model.id,\n      type: 'model',\n      size,\n      createdAt: Date.now(),\n      stackTrace: this.captureStackTrace(),\n    });\n\n    if (disposer) {\n      this.disposers.set(model.id, disposer);\n    }\n\n    this.allocated += size;\n    this.peak = Math.max(this.peak, this.allocated);\n\n    this.checkMemoryThreshold();\n  }\n\n  /**\n   * Untrack a resource\n   */\n  untrack(id: string): void {\n    const resource = this.resources.get(id);\n    if (resource) {\n      this.allocated -= resource.size;\n      this.resources.delete(id);\n      this.disposers.delete(id);\n    }\n  }\n\n  /**\n   * Release a resource\n   */\n  release(resourceOrId: Tensor | LoadedModel | string): void {\n    const id = typeof resourceOrId === 'string' ? resourceOrId : resourceOrId.id;\n    \n    const disposer = this.disposers.get(id);\n    if (disposer) {\n      try {\n        disposer();\n      } catch (error) {\n        console.error('Error disposing resource:', error);\n      }\n    }\n\n    this.untrack(id);\n  }\n\n  /**\n   * Estimate tensor memory size\n   */\n  private estimateTensorSize(tensor: Tensor): number {\n    const bytesPerElement = this.getBytesPerElement(tensor.dtype);\n    return tensor.size * bytesPerElement;\n  }\n\n  /**\n   * Get bytes per element for a data type\n   */\n  private getBytesPerElement(dtype: string): number {\n    switch (dtype) {\n      case 'float32':\n        return 4;\n      case 'float16':\n        return 2;\n      case 'int32':\n        return 4;\n      case 'int64':\n        return 8;\n      case 'uint8':\n      case 'int8':\n      case 'bool':\n        return 1;\n      default:\n        return 4;\n    }\n  }\n\n  /**\n   * Capture stack trace for debugging\n   */\n  private captureStackTrace(): string | undefined {\n    if (typeof Error.captureStackTrace === 'function') {\n      const obj: { stack?: string } = {};\n      Error.captureStackTrace(obj, this.captureStackTrace);\n      return obj.stack;\n    }\n    return new Error().stack;\n  }\n\n  /**\n   * Check if memory threshold is exceeded\n   */\n  private checkMemoryThreshold(): void {\n    if (!this.config.autoGC) return;\n\n    const usage = this.allocated / this.config.maxSize;\n    \n    if (usage >= this.config.gcThreshold && !this.gcScheduled) {\n      this.gcScheduled = true;\n      this.emit('memory:warning', {\n        allocated: this.allocated,\n        maxSize: this.config.maxSize,\n        usage,\n      });\n\n      // Schedule GC on next tick\n      setTimeout(() => {\n        this.gc();\n        this.gcScheduled = false;\n      }, 0);\n    }\n  }\n\n  /**\n   * Garbage collection helper.\n   *\n   * Identifies stale resources and optionally evicts them.\n   * @param evict - If true, actually dispose stale resources (default: false)\n   * @param maxAge - Resources older than this (ms) are considered stale (default: 5 min)\n   */\n  gc(evict = false, maxAge = 5 * 60 * 1000): void {\n    this.emit('memory:gc', { before: this.allocated });\n\n    const now = Date.now();\n    const staleIds: string[] = [];\n\n    for (const [id, resource] of this.resources) {\n      if (now - resource.createdAt > maxAge) {\n        staleIds.push(id);\n      }\n    }\n\n    if (evict) {\n      for (const id of staleIds) {\n        this.release(id);\n      }\n    }\n\n    this.emit('memory:gc', {\n      after: this.allocated,\n      evicted: evict ? staleIds.length : 0,\n      potentialCleanup: staleIds.length,\n    });\n  }\n\n  /**\n   * Query actual browser memory usage via performance.measureUserAgentSpecificMemory()\n   * (Chrome 89+, requires cross-origin isolation). Returns null if unavailable.\n   */\n  async measureBrowserMemory(): Promise<{\n    bytes: number;\n    breakdown: Array<{ bytes: number; types: string[] }>;\n  } | null> {\n    try {\n      if (\n        typeof performance !== 'undefined' &&\n        'measureUserAgentSpecificMemory' in performance\n      ) {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        const result = await (performance as any).measureUserAgentSpecificMemory();\n        return result;\n      }\n    } catch {\n      // Not available or not cross-origin isolated\n    }\n    return null;\n  }\n\n  /**\n   * Get the device's total memory hint (navigator.deviceMemory).\n   * Returns null if unavailable. Value is in GiB, rounded (e.g. 4, 8).\n   */\n  getDeviceMemory(): number | null {\n    try {\n      if (typeof navigator !== 'undefined' && 'deviceMemory' in navigator) {\n        return (navigator as { deviceMemory?: number }).deviceMemory ?? null;\n      }\n    } catch {\n      // Not available\n    }\n    return null;\n  }\n\n  /**\n   * Get memory statistics\n   */\n  getStats(): MemoryStats {\n    let tensorCount = 0;\n    let modelCount = 0;\n\n    for (const resource of this.resources.values()) {\n      if (resource.type === 'tensor') {\n        tensorCount++;\n      } else {\n        modelCount++;\n      }\n    }\n\n    return {\n      allocated: this.allocated,\n      used: this.allocated, // In JS, allocated = used\n      peak: this.peak,\n      tensorCount,\n      modelCount,\n    };\n  }\n\n  /**\n   * Get detailed resource list (for debugging)\n   */\n  getResourceDetails(): TrackedResource[] {\n    return Array.from(this.resources.values());\n  }\n\n  /**\n   * Check for potential memory leaks\n   */\n  detectLeaks(maxAge: number = 10 * 60 * 1000): TrackedResource[] {\n    const now = Date.now();\n    const potentialLeaks: TrackedResource[] = [];\n\n    for (const resource of this.resources.values()) {\n      if (now - resource.createdAt > maxAge) {\n        potentialLeaks.push(resource);\n      }\n    }\n\n    return potentialLeaks;\n  }\n\n  /**\n   * Add event listener\n   */\n  on<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener as EventListener);\n  }\n\n  /**\n   * Remove event listener\n   */\n  off<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener as EventListener);\n    }\n  }\n\n  /**\n   * Emit event\n   */\n  private emit<T>(type: EventType, data: T): void {\n    const event: EdgeFlowEvent<T> = {\n      type,\n      timestamp: Date.now(),\n      data,\n    };\n\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error('Error in event listener:', error);\n        }\n      }\n    }\n  }\n\n  /**\n   * Reset statistics\n   */\n  resetStats(): void {\n    this.peak = this.allocated;\n  }\n\n  /**\n   * Dispose all resources\n   */\n  disposeAll(): void {\n    for (const id of this.resources.keys()) {\n      this.release(id);\n    }\n  }\n\n  /**\n   * Dispose the manager\n   */\n  dispose(): void {\n    this.disposeAll();\n    this.disposed = true;\n    this.listeners.clear();\n    MemoryManager.instance = null;\n  }\n}\n\n// ============================================================================\n// Memory Scope (RAII-like pattern)\n// ============================================================================\n\n/**\n * Memory scope for automatic resource cleanup\n * \n * Usage:\n * ```typescript\n * const result = await withMemoryScope(async (scope) => {\n *   const tensor1 = scope.track(createTensor(...));\n *   const tensor2 = scope.track(createTensor(...));\n *   // Process tensors\n *   return computeResult(tensor1, tensor2);\n * });\n * // tensor1 and tensor2 are automatically disposed\n * ```\n */\nexport class MemoryScope {\n  private resources: Array<{ dispose: () => void }> = [];\n  private children: MemoryScope[] = [];\n  private parent: MemoryScope | null = null;\n\n  constructor(parent?: MemoryScope) {\n    if (parent) {\n      this.parent = parent;\n      parent.children.push(this);\n    }\n  }\n\n  /**\n   * Track a resource in this scope\n   */\n  track<T extends { dispose: () => void }>(resource: T): T {\n    this.resources.push(resource);\n    return resource;\n  }\n\n  /**\n   * Create a child scope\n   */\n  createChild(): MemoryScope {\n    return new MemoryScope(this);\n  }\n\n  /**\n   * Keep a resource (don't dispose it when scope ends)\n   */\n  keep<T extends { dispose: () => void }>(resource: T): T {\n    const index = this.resources.indexOf(resource);\n    if (index !== -1) {\n      this.resources.splice(index, 1);\n    }\n    return resource;\n  }\n\n  /**\n   * Dispose all resources in this scope\n   */\n  dispose(): void {\n    // Dispose children first\n    for (const child of this.children) {\n      child.dispose();\n    }\n    this.children = [];\n\n    // Dispose resources in reverse order\n    for (let i = this.resources.length - 1; i >= 0; i--) {\n      try {\n        this.resources[i]?.dispose();\n      } catch (error) {\n        console.error('Error disposing resource in scope:', error);\n      }\n    }\n    this.resources = [];\n\n    // Remove from parent\n    if (this.parent) {\n      const index = this.parent.children.indexOf(this);\n      if (index !== -1) {\n        this.parent.children.splice(index, 1);\n      }\n      this.parent = null;\n    }\n  }\n}\n\n/**\n * Execute a function with automatic memory cleanup\n */\nexport async function withMemoryScope<T>(\n  fn: (scope: MemoryScope) => Promise<T>\n): Promise<T> {\n  const scope = new MemoryScope();\n  try {\n    return await fn(scope);\n  } finally {\n    scope.dispose();\n  }\n}\n\n/**\n * Synchronous version of withMemoryScope\n */\nexport function withMemoryScopeSync<T>(\n  fn: (scope: MemoryScope) => T\n): T {\n  const scope = new MemoryScope();\n  try {\n    return fn(scope);\n  } finally {\n    scope.dispose();\n  }\n}\n\n// ============================================================================\n// LRU Cache for Models\n// ============================================================================\n\n/**\n * LRU Cache for loaded models\n */\nexport class ModelCache {\n  private readonly maxSize: number;\n  private readonly maxModels: number;\n  private readonly cache: Map<string, { model: LoadedModel; size: number; lastAccess: number }> = new Map();\n  private currentSize = 0;\n\n  constructor(options: { maxSize?: number; maxModels?: number } = {}) {\n    this.maxSize = options.maxSize ?? 256 * 1024 * 1024; // 256MB default\n    this.maxModels = options.maxModels ?? 5;\n  }\n\n  /**\n   * Get a model from cache\n   */\n  get(key: string): LoadedModel | undefined {\n    const entry = this.cache.get(key);\n    if (entry) {\n      entry.lastAccess = Date.now();\n      return entry.model;\n    }\n    return undefined;\n  }\n\n  /**\n   * Add a model to cache\n   */\n  set(key: string, model: LoadedModel): void {\n    const size = model.metadata.sizeBytes;\n\n    // Check if we need to evict\n    while (\n      (this.currentSize + size > this.maxSize || this.cache.size >= this.maxModels) &&\n      this.cache.size > 0\n    ) {\n      this.evictLRU();\n    }\n\n    // Add to cache\n    this.cache.set(key, {\n      model,\n      size,\n      lastAccess: Date.now(),\n    });\n    this.currentSize += size;\n  }\n\n  /**\n   * Remove a model from cache\n   */\n  delete(key: string): boolean {\n    const entry = this.cache.get(key);\n    if (entry) {\n      entry.model.dispose();\n      this.currentSize -= entry.size;\n      this.cache.delete(key);\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Check if model is in cache\n   */\n  has(key: string): boolean {\n    return this.cache.has(key);\n  }\n\n  /**\n   * Evict least recently used model\n   */\n  private evictLRU(): void {\n    let oldestKey: string | null = null;\n    let oldestTime = Infinity;\n\n    for (const [key, entry] of this.cache) {\n      if (entry.lastAccess < oldestTime) {\n        oldestTime = entry.lastAccess;\n        oldestKey = key;\n      }\n    }\n\n    if (oldestKey) {\n      this.delete(oldestKey);\n    }\n  }\n\n  /**\n   * Clear the cache\n   */\n  clear(): void {\n    for (const entry of this.cache.values()) {\n      entry.model.dispose();\n    }\n    this.cache.clear();\n    this.currentSize = 0;\n  }\n\n  /**\n   * Get cache statistics\n   */\n  getStats(): { size: number; count: number; maxSize: number; maxModels: number } {\n    return {\n      size: this.currentSize,\n      count: this.cache.size,\n      maxSize: this.maxSize,\n      maxModels: this.maxModels,\n    };\n  }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Get memory manager instance\n */\nexport function getMemoryManager(): MemoryManager {\n  return MemoryManager.getInstance();\n}\n\n/**\n * Get memory statistics\n */\nexport function getMemoryStats(): MemoryStats {\n  return MemoryManager.getInstance().getStats();\n}\n\n/**\n * Release a resource\n */\nexport function release(resource: Tensor | LoadedModel): void {\n  MemoryManager.getInstance().release(resource);\n}\n\n/**\n * Force garbage collection hint\n */\nexport function gc(): void {\n  MemoryManager.getInstance().gc();\n}\n"
  },
  {
    "path": "src/core/plugin.ts",
    "content": "/**\n * edgeFlow.js - Plugin System\n *\n * Register custom pipelines, backends, and middleware via plugins.\n *\n * @example\n * ```typescript\n * import { registerPlugin } from 'edgeflowjs';\n *\n * registerPlugin({\n *   name: 'edgeflow-plugin-whisper',\n *   version: '1.0.0',\n *   pipelines: {\n *     'whisper-transcribe': {\n *       factory: (config) => new WhisperPipeline(config),\n *     },\n *   },\n * });\n *\n * // Now available via pipeline('whisper-transcribe')\n * ```\n */\n\nimport type { PipelineConfig, Runtime, RuntimeType } from './types.js';\nimport { registerRuntime } from './runtime.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A pipeline factory registered by a plugin.\n */\nexport interface PluginPipelineEntry {\n  /** Factory that creates a pipeline instance */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  factory: (config: PipelineConfig) => any;\n  /** Optional description */\n  description?: string;\n}\n\n/**\n * A backend registered by a plugin.\n */\nexport interface PluginBackendEntry {\n  /** Factory that creates a runtime instance */\n  factory: () => Runtime;\n  /** Optional description */\n  description?: string;\n}\n\n/**\n * Middleware that runs before/after inference.\n */\nexport interface PluginMiddleware {\n  /** Unique name */\n  name: string;\n  /** Called before inference with (model, inputs). Return modified inputs. */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  before?: (ctx: { modelId: string; inputs: any }) => any | Promise<any>;\n  /** Called after inference with (model, outputs). Return modified outputs. */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  after?: (ctx: { modelId: string; outputs: any }) => any | Promise<any>;\n}\n\n/**\n * Plugin definition.\n */\nexport interface EdgeFlowPlugin {\n  /** Unique plugin name (e.g. 'edgeflow-plugin-whisper') */\n  name: string;\n  /** Plugin version (semver) */\n  version: string;\n  /** Pipelines contributed by this plugin */\n  pipelines?: Record<string, PluginPipelineEntry>;\n  /** Backends contributed by this plugin */\n  backends?: Record<string, PluginBackendEntry>;\n  /** Middleware contributed by this plugin */\n  middleware?: PluginMiddleware[];\n  /** Called once when the plugin is registered */\n  setup?: () => void | Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst registeredPlugins = new Map<string, EdgeFlowPlugin>();\nconst pluginPipelines = new Map<string, PluginPipelineEntry>();\nconst pluginMiddleware: PluginMiddleware[] = [];\n\n/**\n * Register a plugin. Pipelines and backends are made available immediately.\n */\nexport async function registerPlugin(plugin: EdgeFlowPlugin): Promise<void> {\n  if (registeredPlugins.has(plugin.name)) {\n    console.warn(`[edgeFlow.js] Plugin \"${plugin.name}\" is already registered — skipping.`);\n    return;\n  }\n\n  // Run setup hook\n  if (plugin.setup) {\n    await plugin.setup();\n  }\n\n  // Register pipelines\n  if (plugin.pipelines) {\n    for (const [task, entry] of Object.entries(plugin.pipelines)) {\n      pluginPipelines.set(task, entry);\n    }\n  }\n\n  // Register backends\n  if (plugin.backends) {\n    for (const [name, entry] of Object.entries(plugin.backends)) {\n      registerRuntime(name as RuntimeType, entry.factory);\n    }\n  }\n\n  // Register middleware\n  if (plugin.middleware) {\n    pluginMiddleware.push(...plugin.middleware);\n  }\n\n  registeredPlugins.set(plugin.name, plugin);\n}\n\n/**\n * Look up a pipeline factory registered by any plugin.\n * Returns undefined if no plugin provides this task.\n */\nexport function getPluginPipeline(task: string): PluginPipelineEntry | undefined {\n  return pluginPipelines.get(task);\n}\n\n/**\n * Get all registered middleware.\n */\nexport function getPluginMiddleware(): ReadonlyArray<PluginMiddleware> {\n  return pluginMiddleware;\n}\n\n/**\n * List all registered plugins.\n */\nexport function listPlugins(): Array<{ name: string; version: string }> {\n  return Array.from(registeredPlugins.values()).map(p => ({\n    name: p.name,\n    version: p.version,\n  }));\n}\n\n/**\n * Unregister a plugin by name.\n */\nexport function unregisterPlugin(name: string): boolean {\n  const plugin = registeredPlugins.get(name);\n  if (!plugin) return false;\n\n  // Remove pipelines\n  if (plugin.pipelines) {\n    for (const task of Object.keys(plugin.pipelines)) {\n      pluginPipelines.delete(task);\n    }\n  }\n\n  // Remove middleware\n  if (plugin.middleware) {\n    for (const mw of plugin.middleware) {\n      const idx = pluginMiddleware.indexOf(mw);\n      if (idx !== -1) pluginMiddleware.splice(idx, 1);\n    }\n  }\n\n  registeredPlugins.delete(name);\n  return true;\n}\n"
  },
  {
    "path": "src/core/runtime.ts",
    "content": "/**\n * edgeFlow.js - Runtime Management\n * \n * Manages runtime backends and automatic selection.\n * Provides unified interface for different compute backends.\n */\n\nimport {\n  Runtime,\n  RuntimeType,\n  RuntimeCapabilities,\n  LoadedModel,\n  ModelLoadOptions,\n  ModelMetadata,\n  Tensor,\n  EdgeFlowError,\n  ErrorCodes,\n  EventType,\n  EventListener,\n  EdgeFlowEvent,\n} from './types.js';\nimport { getScheduler } from './scheduler.js';\nimport { getMemoryManager } from './memory.js';\n\n// ============================================================================\n// Runtime Registry\n// ============================================================================\n\n/**\n * Registered runtime factories\n */\nconst runtimeFactories: Map<RuntimeType, () => Runtime> = new Map();\n\n/**\n * Cached runtime instances\n */\nconst runtimeInstances: Map<RuntimeType, Runtime> = new Map();\n\n/**\n * Runtime priority order (higher priority first)\n */\nconst RUNTIME_PRIORITY: RuntimeType[] = ['webgpu', 'webnn', 'wasm'];\n\n// ============================================================================\n// Runtime Manager\n// ============================================================================\n\n/**\n * RuntimeManager - Manages runtime selection and lifecycle\n * \n * Features:\n * - Automatic best runtime selection\n * - Runtime registration\n * - Capability detection\n * - Fallback handling\n */\nexport class RuntimeManager {\n  private static instance: RuntimeManager | null = null;\n  \n  private readonly listeners: Map<EventType, Set<EventListener>> = new Map();\n  private defaultRuntime: RuntimeType = 'auto';\n\n  private constructor() {}\n\n  /**\n   * Get singleton instance\n   */\n  static getInstance(): RuntimeManager {\n    if (!RuntimeManager.instance) {\n      RuntimeManager.instance = new RuntimeManager();\n    }\n    return RuntimeManager.instance;\n  }\n\n  /**\n   * Register a runtime factory\n   */\n  register(type: RuntimeType, factory: () => Runtime): void {\n    runtimeFactories.set(type, factory);\n  }\n\n  /**\n   * Get a runtime instance\n   */\n  async getRuntime(type: RuntimeType = 'auto'): Promise<Runtime> {\n    if (type === 'auto') {\n      return this.getBestRuntime();\n    }\n\n    // Check if already instantiated\n    let runtime = runtimeInstances.get(type);\n    if (runtime) {\n      return runtime;\n    }\n\n    // Create new instance\n    const factory = runtimeFactories.get(type);\n    if (!factory) {\n      throw new EdgeFlowError(\n        `Runtime '${type}' is not registered`,\n        ErrorCodes.RUNTIME_NOT_AVAILABLE,\n        { runtime: type }\n      );\n    }\n\n    runtime = factory();\n    \n    // Check availability\n    const available = await runtime.isAvailable();\n    if (!available) {\n      throw new EdgeFlowError(\n        `Runtime '${type}' is not available in this environment`,\n        ErrorCodes.RUNTIME_NOT_AVAILABLE,\n        { runtime: type }\n      );\n    }\n\n    // Initialize\n    try {\n      await runtime.initialize();\n    } catch (error) {\n      throw new EdgeFlowError(\n        `Failed to initialize runtime '${type}': ${error instanceof Error ? error.message : String(error)}`,\n        ErrorCodes.RUNTIME_INIT_FAILED,\n        { runtime: type, error }\n      );\n    }\n\n    runtimeInstances.set(type, runtime);\n    this.emit('runtime:ready', { runtime: type });\n\n    return runtime;\n  }\n\n  /**\n   * Get the best available runtime\n   */\n  async getBestRuntime(): Promise<Runtime> {\n    for (const type of RUNTIME_PRIORITY) {\n      try {\n        // Check if already available\n        const existing = runtimeInstances.get(type);\n        if (existing) {\n          return existing;\n        }\n\n        // Try to create and initialize\n        const factory = runtimeFactories.get(type);\n        if (!factory) continue;\n\n        const runtime = factory();\n        const available = await runtime.isAvailable();\n        \n        if (available) {\n          await runtime.initialize();\n          runtimeInstances.set(type, runtime);\n          this.emit('runtime:ready', { runtime: type });\n          return runtime;\n        }\n      } catch {\n        // Try next runtime\n        continue;\n      }\n    }\n\n    throw new EdgeFlowError(\n      'No runtime available. Please ensure WebGPU, WebNN, or WASM is supported.',\n      ErrorCodes.RUNTIME_NOT_AVAILABLE,\n      { triedRuntimes: RUNTIME_PRIORITY }\n    );\n  }\n\n  /**\n   * Check which runtimes are available\n   */\n  async detectAvailableRuntimes(): Promise<Map<RuntimeType, boolean>> {\n    const results = new Map<RuntimeType, boolean>();\n\n    for (const type of RUNTIME_PRIORITY) {\n      const factory = runtimeFactories.get(type);\n      if (!factory) {\n        results.set(type, false);\n        continue;\n      }\n\n      try {\n        const runtime = factory();\n        results.set(type, await runtime.isAvailable());\n      } catch {\n        results.set(type, false);\n      }\n    }\n\n    return results;\n  }\n\n  /**\n   * Get capabilities of a runtime\n   */\n  async getCapabilities(type: RuntimeType): Promise<RuntimeCapabilities> {\n    const runtime = await this.getRuntime(type);\n    return runtime.capabilities;\n  }\n\n  /**\n   * Set default runtime\n   */\n  setDefaultRuntime(type: RuntimeType): void {\n    this.defaultRuntime = type;\n  }\n\n  /**\n   * Get default runtime type\n   */\n  getDefaultRuntimeType(): RuntimeType {\n    return this.defaultRuntime;\n  }\n\n  /**\n   * Dispose a specific runtime\n   */\n  disposeRuntime(type: RuntimeType): void {\n    const runtime = runtimeInstances.get(type);\n    if (runtime) {\n      runtime.dispose();\n      runtimeInstances.delete(type);\n    }\n  }\n\n  /**\n   * Dispose all runtimes\n   */\n  disposeAll(): void {\n    for (const [type, runtime] of runtimeInstances) {\n      runtime.dispose();\n      runtimeInstances.delete(type);\n    }\n  }\n\n  /**\n   * Add event listener\n   */\n  on<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener as EventListener);\n  }\n\n  /**\n   * Remove event listener\n   */\n  off<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener as EventListener);\n    }\n  }\n\n  /**\n   * Emit event\n   */\n  private emit<T>(type: EventType, data: T): void {\n    const event: EdgeFlowEvent<T> = {\n      type,\n      timestamp: Date.now(),\n      data,\n    };\n\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error('Error in event listener:', error);\n        }\n      }\n    }\n  }\n}\n\n// ============================================================================\n// Model Loader\n// ============================================================================\n\n/**\n * Model instance counter\n */\nlet modelIdCounter = 0;\n\n/**\n * Generate unique model ID\n */\nfunction generateModelId(): string {\n  return `model_${++modelIdCounter}_${Date.now().toString(36)}`;\n}\n\n/**\n * LoadedModelImpl - Implementation of LoadedModel interface\n */\nexport class LoadedModelImpl implements LoadedModel {\n  readonly id: string;\n  readonly metadata: ModelMetadata;\n  readonly runtime: RuntimeType;\n  \n  private _isLoaded = true;\n  private readonly _dispose: () => void;\n\n  constructor(\n    metadata: ModelMetadata,\n    runtime: RuntimeType,\n    dispose: () => void\n  ) {\n    this.id = generateModelId();\n    this.metadata = metadata;\n    this.runtime = runtime;\n    this._dispose = dispose;\n  }\n\n  get isLoaded(): boolean {\n    return this._isLoaded;\n  }\n\n  dispose(): void {\n    if (this._isLoaded) {\n      this._isLoaded = false;\n      this._dispose();\n      getMemoryManager().untrack(this.id);\n    }\n  }\n}\n\n// ============================================================================\n// Model Loading Functions\n// ============================================================================\n\n/**\n * Load model from URL with advanced loading support\n * (caching, sharding, resume download)\n */\nexport async function loadModel(\n  url: string,\n  options: ModelLoadOptions & { \n    runtime?: RuntimeType;\n    cache?: boolean;\n    resumable?: boolean;\n    chunkSize?: number;\n    forceDownload?: boolean;\n  } = {}\n): Promise<LoadedModel> {\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(options.runtime ?? 'auto');\n\n  // Import model loader dynamically to avoid circular dependencies\n  const { loadModelData } = await import('../utils/model-loader.js');\n\n  // Use advanced model loader with caching and resume support\n  const modelData = await loadModelData(url, {\n    cache: options.cache ?? true,\n    resumable: options.resumable ?? true,\n    chunkSize: options.chunkSize,\n    forceDownload: options.forceDownload,\n    onProgress: options.onProgress ? (progress) => {\n      options.onProgress!(progress.percent / 100);\n    } : undefined,\n  });\n\n  // Load into runtime\n  const model = await runtime.loadModel(modelData, options);\n\n  return model;\n}\n\n/**\n * Load model from ArrayBuffer\n */\nexport async function loadModelFromBuffer(\n  data: ArrayBuffer,\n  options: ModelLoadOptions & { runtime?: RuntimeType } = {}\n): Promise<LoadedModel> {\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(options.runtime ?? 'auto');\n  return runtime.loadModel(data, options);\n}\n\n// ============================================================================\n// Inference Functions\n// ============================================================================\n\n/**\n * Run inference on a model\n */\nexport async function runInference(\n  model: LoadedModel,\n  inputs: Tensor[]\n): Promise<Tensor[]> {\n  if (!model.isLoaded) {\n    throw new EdgeFlowError(\n      'Model has been disposed',\n      ErrorCodes.MODEL_NOT_LOADED,\n      { modelId: model.id }\n    );\n  }\n\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n  \n  // Use scheduler for execution\n  const scheduler = getScheduler();\n  const task = scheduler.schedule(model.id, () => runtime.run(model, inputs));\n  \n  return task.wait();\n}\n\n/**\n * Run inference with named inputs\n */\nexport async function runInferenceNamed(\n  model: LoadedModel,\n  namedInputs: Map<string, Tensor>\n): Promise<Tensor[]> {\n  if (!model.isLoaded) {\n    throw new EdgeFlowError(\n      'Model has been disposed',\n      ErrorCodes.MODEL_NOT_LOADED,\n      { modelId: model.id }\n    );\n  }\n\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n  \n  // Check if runtime supports named inputs\n  if (!('runNamed' in runtime)) {\n    throw new EdgeFlowError(\n      'Runtime does not support named inputs',\n      ErrorCodes.INFERENCE_FAILED,\n      { modelId: model.id }\n    );\n  }\n  \n  // Use scheduler for execution\n  const scheduler = getScheduler();\n  const task = scheduler.schedule(model.id, () => \n    (runtime as any).runNamed(model, namedInputs)\n  );\n  \n  return task.wait() as Promise<Tensor[]>;\n}\n\n/**\n * Run inference with batch processing\n */\nexport async function runBatchInference(\n  model: LoadedModel,\n  batches: Tensor[][]\n): Promise<Tensor[][]> {\n  const scheduler = getScheduler();\n  const manager = RuntimeManager.getInstance();\n  const runtime = await manager.getRuntime(model.runtime);\n\n  // Schedule all batches\n  const tasks = batches.map(inputs =>\n    scheduler.schedule(model.id, () => runtime.run(model, inputs))\n  );\n\n  // Wait for all to complete\n  return Promise.all(tasks.map(task => task.wait()));\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Get runtime manager instance\n */\nexport function getRuntimeManager(): RuntimeManager {\n  return RuntimeManager.getInstance();\n}\n\n/**\n * Register a runtime\n */\nexport function registerRuntime(type: RuntimeType, factory: () => Runtime): void {\n  RuntimeManager.getInstance().register(type, factory);\n}\n\n/**\n * Get the best available runtime\n */\nexport async function getBestRuntime(): Promise<Runtime> {\n  return RuntimeManager.getInstance().getBestRuntime();\n}\n\n/**\n * Check available runtimes\n */\nexport async function getAvailableRuntimes(): Promise<Map<RuntimeType, boolean>> {\n  return RuntimeManager.getInstance().detectAvailableRuntimes();\n}\n"
  },
  {
    "path": "src/core/scheduler.ts",
    "content": "/**\n * edgeFlow.js - Inference Scheduler\n * \n * Task scheduler for managing concurrent inference execution.\n * Supports priority queues, model-level isolation, and batch processing.\n */\n\nimport {\n  InferenceTask,\n  TaskPriority,\n  TaskStatus,\n  SchedulerOptions,\n  EdgeFlowError,\n  ErrorCodes,\n  EventType,\n  EventListener,\n  EdgeFlowEvent,\n} from './types.js';\n\n// ============================================================================\n// Task Implementation\n// ============================================================================\n\n/**\n * Internal task implementation\n */\nclass Task<T = unknown> implements InferenceTask<T> {\n  readonly id: string;\n  readonly modelId: string;\n  readonly priority: TaskPriority;\n  readonly createdAt: number;\n  \n  private _status: TaskStatus = 'pending';\n  private _startedAt?: number;\n  private _completedAt?: number;\n  private _result?: T;\n  private _error?: Error;\n  private _executor: () => Promise<T>;\n  private _resolvers: Array<{\n    resolve: (value: T) => void;\n    reject: (error: Error) => void;\n  }> = [];\n  private _cancelled = false;\n\n  constructor(\n    id: string,\n    modelId: string,\n    priority: TaskPriority,\n    executor: () => Promise<T>\n  ) {\n    this.id = id;\n    this.modelId = modelId;\n    this.priority = priority;\n    this.createdAt = Date.now();\n    this._executor = executor;\n  }\n\n  get status(): TaskStatus {\n    return this._status;\n  }\n\n  get startedAt(): number | undefined {\n    return this._startedAt;\n  }\n\n  get completedAt(): number | undefined {\n    return this._completedAt;\n  }\n\n  get result(): T | undefined {\n    return this._result;\n  }\n\n  get error(): Error | undefined {\n    return this._error;\n  }\n\n  /**\n   * Cancel the task\n   */\n  cancel(): void {\n    if (this._status === 'pending') {\n      this._cancelled = true;\n      this._status = 'cancelled';\n      this._completedAt = Date.now();\n      \n      const cancelError = new EdgeFlowError(\n        'Task was cancelled',\n        ErrorCodes.INFERENCE_CANCELLED,\n        { taskId: this.id }\n      );\n      \n      for (const { reject } of this._resolvers) {\n        reject(cancelError);\n      }\n      this._resolvers = [];\n    }\n  }\n\n  /**\n   * Wait for task completion\n   */\n  wait(): Promise<T> {\n    if (this._status === 'completed') {\n      return Promise.resolve(this._result as T);\n    }\n    \n    if (this._status === 'failed') {\n      return Promise.reject(this._error);\n    }\n    \n    if (this._status === 'cancelled') {\n      return Promise.reject(new EdgeFlowError(\n        'Task was cancelled',\n        ErrorCodes.INFERENCE_CANCELLED,\n        { taskId: this.id }\n      ));\n    }\n\n    return new Promise<T>((resolve, reject) => {\n      this._resolvers.push({ resolve, reject });\n    });\n  }\n\n  /**\n   * Execute the task\n   */\n  async execute(): Promise<void> {\n    if (this._cancelled) {\n      return;\n    }\n\n    this._status = 'running';\n    this._startedAt = Date.now();\n\n    try {\n      this._result = await this._executor();\n      this._status = 'completed';\n      this._completedAt = Date.now();\n      \n      for (const { resolve } of this._resolvers) {\n        resolve(this._result);\n      }\n    } catch (err) {\n      this._error = err instanceof Error ? err : new Error(String(err));\n      this._status = 'failed';\n      this._completedAt = Date.now();\n      \n      for (const { reject } of this._resolvers) {\n        reject(this._error);\n      }\n    }\n    \n    this._resolvers = [];\n  }\n}\n\n// ============================================================================\n// Priority Queue Implementation\n// ============================================================================\n\n/**\n * Priority mapping for comparison\n */\nconst PRIORITY_ORDER: Record<TaskPriority, number> = {\n  critical: 0,\n  high: 1,\n  normal: 2,\n  low: 3,\n};\n\n/**\n * Priority queue for tasks\n */\nclass PriorityQueue<T extends Task> {\n  private items: T[] = [];\n\n  get length(): number {\n    return this.items.length;\n  }\n\n  isEmpty(): boolean {\n    return this.items.length === 0;\n  }\n\n  /**\n   * Add item to queue with priority ordering\n   */\n  enqueue(item: T): void {\n    let inserted = false;\n    \n    for (let i = 0; i < this.items.length; i++) {\n      const currentItem = this.items[i];\n      if (currentItem && PRIORITY_ORDER[item.priority] < PRIORITY_ORDER[currentItem.priority]) {\n        this.items.splice(i, 0, item);\n        inserted = true;\n        break;\n      }\n    }\n    \n    if (!inserted) {\n      this.items.push(item);\n    }\n  }\n\n  /**\n   * Remove and return highest priority item\n   */\n  dequeue(): T | undefined {\n    return this.items.shift();\n  }\n\n  /**\n   * Peek at highest priority item without removing\n   */\n  peek(): T | undefined {\n    return this.items[0];\n  }\n\n  /**\n   * Remove a specific item by ID\n   */\n  remove(id: string): T | undefined {\n    const index = this.items.findIndex(item => item.id === id);\n    if (index !== -1) {\n      const [removed] = this.items.splice(index, 1);\n      return removed;\n    }\n    return undefined;\n  }\n\n  /**\n   * Get all items\n   */\n  getAll(): T[] {\n    return [...this.items];\n  }\n\n  /**\n   * Clear the queue\n   */\n  clear(): void {\n    this.items = [];\n  }\n}\n\n// ============================================================================\n// Batch Collector\n// ============================================================================\n\n/**\n * Collects tasks for batch processing\n */\nclass BatchCollector<T> {\n  private tasks: Task<T>[] = [];\n  private timer: ReturnType<typeof setTimeout> | null = null;\n  private readonly maxSize: number;\n  private readonly timeout: number;\n  private readonly onBatch: (tasks: Task<T>[]) => void;\n\n  constructor(\n    maxSize: number,\n    timeout: number,\n    onBatch: (tasks: Task<T>[]) => void\n  ) {\n    this.maxSize = maxSize;\n    this.timeout = timeout;\n    this.onBatch = onBatch;\n  }\n\n  add(task: Task<T>): void {\n    this.tasks.push(task);\n\n    if (this.tasks.length >= this.maxSize) {\n      this.flush();\n    } else if (!this.timer) {\n      this.timer = setTimeout(() => this.flush(), this.timeout);\n    }\n  }\n\n  flush(): void {\n    if (this.timer) {\n      clearTimeout(this.timer);\n      this.timer = null;\n    }\n\n    if (this.tasks.length > 0) {\n      const batch = this.tasks;\n      this.tasks = [];\n      this.onBatch(batch);\n    }\n  }\n\n  clear(): void {\n    if (this.timer) {\n      clearTimeout(this.timer);\n      this.timer = null;\n    }\n    this.tasks = [];\n  }\n}\n\n// ============================================================================\n// Inference Scheduler\n// ============================================================================\n\n// Counter for task IDs\nlet taskIdCounter = 0;\n\n/**\n * Generate unique task ID\n */\nfunction generateTaskId(): string {\n  return `task_${++taskIdCounter}_${Date.now().toString(36)}`;\n}\n\n/**\n * Circuit breaker state per model\n */\ninterface CircuitState {\n  failures: number;\n  state: 'closed' | 'open' | 'half-open';\n  lastFailure: number;\n}\n\n/**\n * Default scheduler options\n */\nconst DEFAULT_OPTIONS: Required<SchedulerOptions> = {\n  maxConcurrentTasks: 4,\n  maxConcurrentPerModel: 1,\n  defaultTimeout: 30000,\n  enableBatching: false,\n  maxBatchSize: 32,\n  batchTimeout: 50,\n  maxRetries: 0,\n  retryBaseDelay: 1000,\n  circuitBreaker: false,\n  circuitBreakerThreshold: 5,\n  circuitBreakerResetTimeout: 30000,\n};\n\n/**\n * InferenceScheduler - Manages concurrent task execution\n * \n * Features:\n * - Priority-based task scheduling\n * - Model-level concurrency control\n * - Optional batch processing\n * - Task cancellation\n * - Event emission\n */\nexport class InferenceScheduler {\n  private readonly options: Required<SchedulerOptions>;\n  private readonly queues: Map<string, PriorityQueue<Task>> = new Map();\n  private readonly runningTasks: Map<string, Set<string>> = new Map();\n  private readonly allTasks: Map<string, Task> = new Map();\n  private readonly batchers: Map<string, BatchCollector<unknown>> = new Map();\n  private readonly listeners: Map<EventType, Set<EventListener>> = new Map();\n  private readonly circuits: Map<string, CircuitState> = new Map();\n  private globalRunningCount = 0;\n  private isProcessing = false;\n  private disposed = false;\n\n  constructor(options: SchedulerOptions = {}) {\n    this.options = { ...DEFAULT_OPTIONS, ...options };\n  }\n\n  /**\n   * Get circuit breaker state for a model, creating default if absent\n   */\n  private getCircuit(modelId: string): CircuitState {\n    let c = this.circuits.get(modelId);\n    if (!c) {\n      c = { failures: 0, state: 'closed', lastFailure: 0 };\n      this.circuits.set(modelId, c);\n    }\n    return c;\n  }\n\n  /**\n   * Check if the circuit for a model allows new tasks\n   */\n  private isCircuitOpen(modelId: string): boolean {\n    if (!this.options.circuitBreaker) return false;\n    const c = this.getCircuit(modelId);\n    if (c.state === 'closed') return false;\n    if (c.state === 'open') {\n      if (Date.now() - c.lastFailure > this.options.circuitBreakerResetTimeout) {\n        c.state = 'half-open';\n        return false; // allow one probe\n      }\n      return true;\n    }\n    return false; // half-open allows one\n  }\n\n  /**\n   * Record a success for circuit breaker\n   */\n  private circuitSuccess(modelId: string): void {\n    if (!this.options.circuitBreaker) return;\n    const c = this.getCircuit(modelId);\n    c.failures = 0;\n    c.state = 'closed';\n  }\n\n  /**\n   * Record a failure for circuit breaker\n   */\n  private circuitFailure(modelId: string): void {\n    if (!this.options.circuitBreaker) return;\n    const c = this.getCircuit(modelId);\n    c.failures++;\n    c.lastFailure = Date.now();\n    if (c.failures >= this.options.circuitBreakerThreshold) {\n      c.state = 'open';\n      this.emit('inference:error', {\n        modelId,\n        error: new Error(`Circuit breaker opened after ${c.failures} consecutive failures`),\n      });\n    }\n  }\n\n  /**\n   * Get or create queue for a model\n   */\n  private getQueue(modelId: string): PriorityQueue<Task> {\n    let queue = this.queues.get(modelId);\n    if (!queue) {\n      queue = new PriorityQueue<Task>();\n      this.queues.set(modelId, queue);\n    }\n    return queue;\n  }\n\n  /**\n   * Get or create running set for a model\n   */\n  private getRunningSet(modelId: string): Set<string> {\n    let running = this.runningTasks.get(modelId);\n    if (!running) {\n      running = new Set<string>();\n      this.runningTasks.set(modelId, running);\n    }\n    return running;\n  }\n\n  /**\n   * Check if we can start a new task for a model\n   */\n  private canStartTask(modelId: string): boolean {\n    if (this.globalRunningCount >= this.options.maxConcurrentTasks) {\n      return false;\n    }\n\n    const running = this.runningTasks.get(modelId);\n    if (running && running.size >= this.options.maxConcurrentPerModel) {\n      return false;\n    }\n\n    return true;\n  }\n\n  /**\n   * Process pending tasks\n   */\n  private async processQueue(): Promise<void> {\n    if (this.isProcessing || this.disposed) {\n      return;\n    }\n\n    this.isProcessing = true;\n\n    try {\n      // Find tasks that can be started\n      const tasksToStart: Task[] = [];\n\n      for (const [modelId, queue] of this.queues) {\n        while (!queue.isEmpty() && this.canStartTask(modelId)) {\n          const task = queue.dequeue();\n          if (task && task.status === 'pending') {\n            tasksToStart.push(task);\n            \n            const running = this.getRunningSet(modelId);\n            running.add(task.id);\n            this.globalRunningCount++;\n          }\n        }\n      }\n\n      // Execute tasks concurrently\n      await Promise.all(\n        tasksToStart.map(async (task) => {\n          this.emit('inference:start', { taskId: task.id, modelId: task.modelId });\n\n          try {\n            await task.execute();\n            this.emit('inference:complete', {\n              taskId: task.id,\n              modelId: task.modelId,\n              duration: (task.completedAt ?? 0) - (task.startedAt ?? 0),\n            });\n          } catch (error) {\n            this.emit('inference:error', {\n              taskId: task.id,\n              modelId: task.modelId,\n              error,\n            });\n          } finally {\n            // Clean up\n            const running = this.runningTasks.get(task.modelId);\n            if (running) {\n              running.delete(task.id);\n            }\n            this.globalRunningCount--;\n          }\n        })\n      );\n    } finally {\n      this.isProcessing = false;\n    }\n\n    // Check if there are more tasks to process\n    let hasPending = false;\n    for (const queue of this.queues.values()) {\n      if (!queue.isEmpty()) {\n        hasPending = true;\n        break;\n      }\n    }\n\n    if (hasPending) {\n      // Use setImmediate-like behavior for next tick processing\n      setTimeout(() => this.processQueue(), 0);\n    }\n  }\n\n  /**\n   * Schedule a task for execution\n   */\n  schedule<T>(\n    modelId: string,\n    executor: () => Promise<T>,\n    priority: TaskPriority = 'normal'\n  ): InferenceTask<T> {\n    if (this.disposed) {\n      throw new EdgeFlowError(\n        'Scheduler has been disposed',\n        ErrorCodes.RUNTIME_NOT_INITIALIZED\n      );\n    }\n\n    if (this.isCircuitOpen(modelId)) {\n      throw new EdgeFlowError(\n        `Circuit breaker is open for model ${modelId} — too many consecutive failures. ` +\n        `Retry after ${this.options.circuitBreakerResetTimeout}ms.`,\n        ErrorCodes.INFERENCE_FAILED,\n        { modelId },\n      );\n    }\n\n    // Wrap executor with retry logic\n    const maxRetries = this.options.maxRetries;\n    const baseDelay = this.options.retryBaseDelay;\n    const wrappedExecutor = maxRetries > 0\n      ? async (): Promise<T> => {\n          let lastError: Error | undefined;\n          for (let attempt = 0; attempt <= maxRetries; attempt++) {\n            try {\n              const result = await executor();\n              this.circuitSuccess(modelId);\n              return result;\n            } catch (err) {\n              lastError = err instanceof Error ? err : new Error(String(err));\n              this.circuitFailure(modelId);\n              if (attempt < maxRetries) {\n                const delay = baseDelay * Math.pow(2, attempt);\n                await new Promise(r => setTimeout(r, delay));\n              }\n            }\n          }\n          throw lastError!;\n        }\n      : async (): Promise<T> => {\n          try {\n            const result = await executor();\n            this.circuitSuccess(modelId);\n            return result;\n          } catch (err) {\n            this.circuitFailure(modelId);\n            throw err;\n          }\n        };\n\n    const task = new Task<T>(\n      generateTaskId(),\n      modelId,\n      priority,\n      wrappedExecutor\n    );\n\n    this.allTasks.set(task.id, task as Task);\n\n    const queue = this.getQueue(modelId);\n    queue.enqueue(task as Task);\n\n    this.processQueue();\n\n    return task;\n  }\n\n  /**\n   * Schedule with timeout\n   */\n  scheduleWithTimeout<T>(\n    modelId: string,\n    executor: () => Promise<T>,\n    timeout: number = this.options.defaultTimeout,\n    priority: TaskPriority = 'normal'\n  ): InferenceTask<T> {\n    const timeoutExecutor = (): Promise<T> => {\n      return new Promise<T>((resolve, reject) => {\n        const timer = setTimeout(() => {\n          reject(new EdgeFlowError(\n            `Task timed out after ${timeout}ms`,\n            ErrorCodes.INFERENCE_TIMEOUT,\n            { timeout }\n          ));\n        }, timeout);\n\n        executor()\n          .then(result => {\n            clearTimeout(timer);\n            resolve(result);\n          })\n          .catch(error => {\n            clearTimeout(timer);\n            reject(error);\n          });\n      });\n    };\n\n    return this.schedule(modelId, timeoutExecutor, priority);\n  }\n\n  /**\n   * Schedule multiple tasks and wait for all\n   */\n  async scheduleAll<T>(\n    tasks: Array<{\n      modelId: string;\n      executor: () => Promise<T>;\n      priority?: TaskPriority;\n    }>\n  ): Promise<T[]> {\n    const scheduledTasks = tasks.map(({ modelId, executor, priority }) =>\n      this.schedule<T>(modelId, executor, priority)\n    );\n\n    return Promise.all(scheduledTasks.map(task => task.wait()));\n  }\n\n  /**\n   * Get task by ID\n   */\n  getTask(taskId: string): InferenceTask | undefined {\n    return this.allTasks.get(taskId);\n  }\n\n  /**\n   * Cancel a task\n   */\n  cancelTask(taskId: string): boolean {\n    const task = this.allTasks.get(taskId);\n    if (task && task.status === 'pending') {\n      task.cancel();\n      \n      // Remove from queue\n      for (const queue of this.queues.values()) {\n        queue.remove(taskId);\n      }\n      \n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Cancel all tasks for a model\n   */\n  cancelAllForModel(modelId: string): number {\n    const queue = this.queues.get(modelId);\n    if (!queue) return 0;\n\n    let cancelled = 0;\n    for (const task of queue.getAll()) {\n      if (task.status === 'pending') {\n        task.cancel();\n        cancelled++;\n      }\n    }\n    queue.clear();\n    \n    return cancelled;\n  }\n\n  /**\n   * Get statistics\n   */\n  getStats(): {\n    totalTasks: number;\n    pendingTasks: number;\n    runningTasks: number;\n    completedTasks: number;\n    failedTasks: number;\n    cancelledTasks: number;\n    queuedByModel: Record<string, number>;\n  } {\n    const stats = {\n      totalTasks: this.allTasks.size,\n      pendingTasks: 0,\n      runningTasks: 0,\n      completedTasks: 0,\n      failedTasks: 0,\n      cancelledTasks: 0,\n      queuedByModel: {} as Record<string, number>,\n    };\n\n    for (const task of this.allTasks.values()) {\n      switch (task.status) {\n        case 'pending':\n          stats.pendingTasks++;\n          break;\n        case 'running':\n          stats.runningTasks++;\n          break;\n        case 'completed':\n          stats.completedTasks++;\n          break;\n        case 'failed':\n          stats.failedTasks++;\n          break;\n        case 'cancelled':\n          stats.cancelledTasks++;\n          break;\n      }\n    }\n\n    for (const [modelId, queue] of this.queues) {\n      stats.queuedByModel[modelId] = queue.length;\n    }\n\n    return stats;\n  }\n\n  /**\n   * Add event listener\n   */\n  on<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    let listeners = this.listeners.get(event);\n    if (!listeners) {\n      listeners = new Set();\n      this.listeners.set(event, listeners);\n    }\n    listeners.add(listener as EventListener);\n  }\n\n  /**\n   * Remove event listener\n   */\n  off<T = unknown>(event: EventType, listener: EventListener<T>): void {\n    const listeners = this.listeners.get(event);\n    if (listeners) {\n      listeners.delete(listener as EventListener);\n    }\n  }\n\n  /**\n   * Emit event\n   */\n  private emit<T>(type: EventType, data: T): void {\n    const event: EdgeFlowEvent<T> = {\n      type,\n      timestamp: Date.now(),\n      data,\n    };\n\n    const listeners = this.listeners.get(type);\n    if (listeners) {\n      for (const listener of listeners) {\n        try {\n          listener(event);\n        } catch (error) {\n          console.error('Error in event listener:', error);\n        }\n      }\n    }\n  }\n\n  /**\n   * Clear completed/failed/cancelled tasks from history\n   */\n  clearHistory(): void {\n    for (const [taskId, task] of this.allTasks) {\n      if (\n        task.status === 'completed' ||\n        task.status === 'failed' ||\n        task.status === 'cancelled'\n      ) {\n        this.allTasks.delete(taskId);\n      }\n    }\n  }\n\n  /**\n   * Dispose the scheduler\n   */\n  dispose(): void {\n    this.disposed = true;\n\n    // Cancel all pending tasks\n    for (const queue of this.queues.values()) {\n      for (const task of queue.getAll()) {\n        task.cancel();\n      }\n      queue.clear();\n    }\n\n    // Clear batchers\n    for (const batcher of this.batchers.values()) {\n      batcher.clear();\n    }\n\n    this.queues.clear();\n    this.runningTasks.clear();\n    this.allTasks.clear();\n    this.batchers.clear();\n    this.listeners.clear();\n  }\n}\n\n// ============================================================================\n// Global Scheduler Instance\n// ============================================================================\n\nlet globalScheduler: InferenceScheduler | null = null;\n\n/**\n * Get the global scheduler instance\n */\nexport function getScheduler(): InferenceScheduler {\n  if (!globalScheduler) {\n    globalScheduler = new InferenceScheduler();\n  }\n  return globalScheduler;\n}\n\n/**\n * Set the global scheduler instance\n */\nexport function setScheduler(scheduler: InferenceScheduler): void {\n  if (globalScheduler) {\n    globalScheduler.dispose();\n  }\n  globalScheduler = scheduler;\n}\n\n/**\n * Configure the global scheduler\n */\nexport function configureScheduler(options: SchedulerOptions): void {\n  setScheduler(new InferenceScheduler(options));\n}\n"
  },
  {
    "path": "src/core/tensor.ts",
    "content": "/**\n * edgeFlow.js - Tensor Implementation\n * \n * Lightweight tensor implementation with efficient memory management.\n */\n\nimport { \n  Tensor, \n  DataType, \n  Shape, \n  TypedArray,\n  EdgeFlowError,\n  ErrorCodes \n} from './types.js';\n\n// Counter for generating unique tensor IDs\nlet tensorIdCounter = 0;\n\n/**\n * Generate a unique tensor ID\n */\nfunction generateTensorId(): string {\n  return `tensor_${++tensorIdCounter}_${Date.now().toString(36)}`;\n}\n\n/**\n * Get the typed array constructor for a data type\n */\nfunction getTypedArrayConstructor(dtype: DataType): new (length: number) => TypedArray {\n  switch (dtype) {\n    case 'float32':\n      return Float32Array;\n    case 'float16':\n      // Float16 not natively supported, use Float32Array\n      return Float32Array;\n    case 'int32':\n      return Int32Array;\n    case 'int64':\n      return BigInt64Array as unknown as new (length: number) => TypedArray;\n    case 'uint8':\n    case 'bool':\n      return Uint8Array;\n    case 'int8':\n      return Int8Array;\n    default:\n      throw new EdgeFlowError(\n        `Unsupported data type: ${dtype}`,\n        ErrorCodes.INVALID_ARGUMENT,\n        { dtype }\n      );\n  }\n}\n\n/**\n * Calculate the total number of elements from shape\n */\nfunction calculateSize(shape: Shape): number {\n  if (shape.length === 0) return 1; // Scalar\n  return shape.reduce((acc, dim) => acc * dim, 1);\n}\n\n/**\n * Validate tensor shape\n */\nfunction validateShape(shape: Shape): void {\n  for (let i = 0; i < shape.length; i++) {\n    const dim = shape[i];\n    if (dim === undefined || !Number.isInteger(dim) || dim < 0) {\n      throw new EdgeFlowError(\n        `Invalid shape dimension at index ${i}: ${dim}`,\n        ErrorCodes.INVALID_ARGUMENT,\n        { shape, index: i, dimension: dim }\n      );\n    }\n  }\n}\n\n/**\n * EdgeFlowTensor - Core tensor implementation\n */\nexport class EdgeFlowTensor implements Tensor {\n  readonly id: string;\n  readonly dtype: DataType;\n  readonly shape: Shape;\n  readonly size: number;\n  private _data: TypedArray;\n  private _isDisposed: boolean = false;\n\n  constructor(\n    data: TypedArray | number[],\n    shape: Shape,\n    dtype: DataType = 'float32'\n  ) {\n    validateShape(shape);\n    \n    this.id = generateTensorId();\n    this.dtype = dtype;\n    this.shape = Object.freeze([...shape]) as Shape;\n    this.size = calculateSize(this.shape);\n\n    // Validate data size matches shape\n    const expectedSize = this.size;\n    if (data.length !== expectedSize) {\n      throw new EdgeFlowError(\n        `Data length (${data.length}) does not match shape ${JSON.stringify(shape)} (expected ${expectedSize})`,\n        ErrorCodes.TENSOR_SHAPE_MISMATCH,\n        { dataLength: data.length, expectedSize, shape }\n      );\n    }\n\n    // Convert to appropriate typed array\n    if (data instanceof Array) {\n      const TypedArrayCtor = getTypedArrayConstructor(dtype);\n      this._data = new TypedArrayCtor(data.length);\n      \n      if (dtype === 'int64') {\n        // BigInt64Array requires BigInt values\n        const bigIntData = this._data as unknown as BigInt64Array;\n        for (let i = 0; i < data.length; i++) {\n          bigIntData[i] = BigInt(Math.round(data[i] ?? 0));\n        }\n      } else {\n        for (let i = 0; i < data.length; i++) {\n          (this._data as Float32Array)[i] = data[i] ?? 0;\n        }\n      }\n    } else {\n      this._data = data;\n    }\n  }\n\n  get data(): TypedArray {\n    this.checkDisposed();\n    return this._data;\n  }\n\n  get isDisposed(): boolean {\n    return this._isDisposed;\n  }\n\n  /**\n   * Check if tensor has been disposed\n   */\n  private checkDisposed(): void {\n    if (this._isDisposed) {\n      throw new EdgeFlowError(\n        'Cannot access disposed tensor',\n        ErrorCodes.TENSOR_DISPOSED,\n        { tensorId: this.id }\n      );\n    }\n  }\n\n  /**\n   * Convert to Float32Array\n   */\n  toFloat32Array(): Float32Array {\n    this.checkDisposed();\n    \n    if (this._data instanceof Float32Array) {\n      return this._data;\n    }\n    \n    const result = new Float32Array(this.size);\n    for (let i = 0; i < this.size; i++) {\n      result[i] = Number(this._data[i] ?? 0);\n    }\n    return result;\n  }\n\n  /**\n   * Convert to regular array\n   */\n  toArray(): number[] {\n    this.checkDisposed();\n    if (this.dtype === 'int64') {\n      // BigInt64Array needs special handling\n      const bigIntData = this._data as unknown as BigInt64Array;\n      const result: number[] = [];\n      for (let i = 0; i < bigIntData.length; i++) {\n        result.push(Number(bigIntData[i]));\n      }\n      return result;\n    }\n    return Array.from(this._data as Float32Array);\n  }\n\n  /**\n   * Clone the tensor\n   */\n  clone(): EdgeFlowTensor {\n    this.checkDisposed();\n    \n    const TypedArrayCtor = this._data.constructor as new (data: TypedArray) => TypedArray;\n    const clonedData = new TypedArrayCtor(this._data);\n    return new EdgeFlowTensor(clonedData, this.shape, this.dtype);\n  }\n\n  /**\n   * Dispose the tensor and free memory\n   */\n  dispose(): void {\n    if (!this._isDisposed) {\n      this._isDisposed = true;\n      // Help garbage collection - use Object.assign to avoid type issues\n      Object.assign(this, { _data: null });\n    }\n  }\n\n  /**\n   * Get value at specific indices\n   */\n  get(...indices: number[]): number {\n    this.checkDisposed();\n    \n    if (indices.length !== this.shape.length) {\n      throw new EdgeFlowError(\n        `Expected ${this.shape.length} indices, got ${indices.length}`,\n        ErrorCodes.INVALID_ARGUMENT,\n        { expectedIndices: this.shape.length, gotIndices: indices.length }\n      );\n    }\n\n    let flatIndex = 0;\n    let stride = 1;\n    \n    for (let i = this.shape.length - 1; i >= 0; i--) {\n      const idx = indices[i] ?? 0;\n      const dim = this.shape[i] ?? 1;\n      \n      if (idx < 0 || idx >= dim) {\n        throw new EdgeFlowError(\n          `Index ${idx} out of bounds for dimension ${i} with size ${dim}`,\n          ErrorCodes.INVALID_ARGUMENT,\n          { index: idx, dimension: i, size: dim }\n        );\n      }\n      \n      flatIndex += idx * stride;\n      stride *= dim;\n    }\n\n    return Number(this._data[flatIndex] ?? 0);\n  }\n\n  /**\n   * Set value at specific indices\n   */\n  set(value: number, ...indices: number[]): void {\n    this.checkDisposed();\n    \n    if (indices.length !== this.shape.length) {\n      throw new EdgeFlowError(\n        `Expected ${this.shape.length} indices, got ${indices.length}`,\n        ErrorCodes.INVALID_ARGUMENT,\n        { expectedIndices: this.shape.length, gotIndices: indices.length }\n      );\n    }\n\n    let flatIndex = 0;\n    let stride = 1;\n    \n    for (let i = this.shape.length - 1; i >= 0; i--) {\n      const idx = indices[i] ?? 0;\n      const dim = this.shape[i] ?? 1;\n      \n      if (idx < 0 || idx >= dim) {\n        throw new EdgeFlowError(\n          `Index ${idx} out of bounds for dimension ${i} with size ${dim}`,\n          ErrorCodes.INVALID_ARGUMENT,\n          { index: idx, dimension: i, size: dim }\n        );\n      }\n      \n      flatIndex += idx * stride;\n      stride *= dim;\n    }\n\n    (this._data as Float32Array)[flatIndex] = value;\n  }\n\n  /**\n   * Reshape the tensor (returns new tensor)\n   */\n  reshape(newShape: Shape): EdgeFlowTensor {\n    this.checkDisposed();\n    \n    const newSize = calculateSize(newShape);\n    if (newSize !== this.size) {\n      throw new EdgeFlowError(\n        `Cannot reshape tensor of size ${this.size} to shape ${JSON.stringify(newShape)} (size ${newSize})`,\n        ErrorCodes.TENSOR_SHAPE_MISMATCH,\n        { currentSize: this.size, newSize, newShape }\n      );\n    }\n\n    const TypedArrayCtor = this._data.constructor as new (data: TypedArray) => TypedArray;\n    const clonedData = new TypedArrayCtor(this._data);\n    return new EdgeFlowTensor(clonedData, newShape, this.dtype);\n  }\n\n  /**\n   * Transpose the tensor (2D only for now)\n   */\n  transpose(): EdgeFlowTensor {\n    this.checkDisposed();\n    \n    if (this.shape.length !== 2) {\n      throw new EdgeFlowError(\n        'Transpose is currently only supported for 2D tensors',\n        ErrorCodes.NOT_IMPLEMENTED,\n        { shape: this.shape }\n      );\n    }\n\n    const [rows, cols] = this.shape as [number, number];\n    const result = new Float32Array(this.size);\n    \n    for (let i = 0; i < rows; i++) {\n      for (let j = 0; j < cols; j++) {\n        result[j * rows + i] = Number(this._data[i * cols + j] ?? 0);\n      }\n    }\n\n    return new EdgeFlowTensor(result, [cols, rows], this.dtype);\n  }\n\n  /**\n   * Create string representation\n   */\n  toString(): string {\n    return `Tensor(shape=[${this.shape.join(', ')}], dtype=${this.dtype})`;\n  }\n}\n\n// ============================================================================\n// Tensor Factory Functions\n// ============================================================================\n\n/**\n * Create a tensor from data\n */\nexport function tensor(\n  data: TypedArray | number[] | number[][],\n  shape?: Shape,\n  dtype: DataType = 'float32'\n): EdgeFlowTensor {\n  // Handle nested arrays\n  if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {\n    const rows = data.length;\n    const cols = (data[0] as number[]).length;\n    const flatData: number[] = [];\n    \n    for (const row of data as number[][]) {\n      if (row.length !== cols) {\n        throw new EdgeFlowError(\n          'Nested arrays must have consistent dimensions',\n          ErrorCodes.INVALID_ARGUMENT\n        );\n      }\n      flatData.push(...row);\n    }\n    \n    return new EdgeFlowTensor(flatData, shape ?? [rows, cols], dtype);\n  }\n\n  // Infer shape if not provided\n  const inferredShape = shape ?? [data.length];\n  return new EdgeFlowTensor(data as TypedArray | number[], inferredShape, dtype);\n}\n\n/**\n * Create a tensor filled with zeros\n */\nexport function zeros(shape: Shape, dtype: DataType = 'float32'): EdgeFlowTensor {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\n\n/**\n * Create a tensor filled with ones\n */\nexport function ones(shape: Shape, dtype: DataType = 'float32'): EdgeFlowTensor {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  data.fill(1 as never);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\n\n/**\n * Create a tensor filled with a specific value\n */\nexport function full(\n  shape: Shape, \n  value: number, \n  dtype: DataType = 'float32'\n): EdgeFlowTensor {\n  const size = calculateSize(shape);\n  const TypedArrayCtor = getTypedArrayConstructor(dtype);\n  const data = new TypedArrayCtor(size);\n  data.fill(value as never);\n  return new EdgeFlowTensor(data, shape, dtype);\n}\n\n/**\n * Create a tensor with random values between 0 and 1\n */\nexport function random(shape: Shape, dtype: DataType = 'float32'): EdgeFlowTensor {\n  const size = calculateSize(shape);\n  const data = new Float32Array(size);\n  for (let i = 0; i < size; i++) {\n    data[i] = Math.random();\n  }\n  return new EdgeFlowTensor(data, shape, dtype);\n}\n\n/**\n * Create a tensor with random values from normal distribution\n */\nexport function randn(shape: Shape, dtype: DataType = 'float32'): EdgeFlowTensor {\n  const size = calculateSize(shape);\n  const data = new Float32Array(size);\n  \n  // Box-Muller transform for normal distribution\n  for (let i = 0; i < size; i += 2) {\n    const u1 = Math.random();\n    const u2 = Math.random();\n    const r = Math.sqrt(-2 * Math.log(u1));\n    const theta = 2 * Math.PI * u2;\n    \n    data[i] = r * Math.cos(theta);\n    if (i + 1 < size) {\n      data[i + 1] = r * Math.sin(theta);\n    }\n  }\n  \n  return new EdgeFlowTensor(data, shape, dtype);\n}\n\n/**\n * Create a 1D tensor with evenly spaced values\n */\nexport function arange(\n  start: number, \n  stop?: number, \n  step: number = 1, \n  dtype: DataType = 'float32'\n): EdgeFlowTensor {\n  if (stop === undefined) {\n    stop = start;\n    start = 0;\n  }\n  \n  const size = Math.ceil((stop - start) / step);\n  const data = new Float32Array(size);\n  \n  for (let i = 0; i < size; i++) {\n    data[i] = start + i * step;\n  }\n  \n  return new EdgeFlowTensor(data, [size], dtype);\n}\n\n/**\n * Create a 1D tensor with evenly spaced values (specify number of points)\n */\nexport function linspace(\n  start: number, \n  stop: number, \n  num: number = 50, \n  dtype: DataType = 'float32'\n): EdgeFlowTensor {\n  const data = new Float32Array(num);\n  const step = (stop - start) / (num - 1);\n  \n  for (let i = 0; i < num; i++) {\n    data[i] = start + i * step;\n  }\n  \n  return new EdgeFlowTensor(data, [num], dtype);\n}\n\n/**\n * Create an identity matrix\n */\nexport function eye(n: number, dtype: DataType = 'float32'): EdgeFlowTensor {\n  const data = new Float32Array(n * n);\n  \n  for (let i = 0; i < n; i++) {\n    data[i * n + i] = 1;\n  }\n  \n  return new EdgeFlowTensor(data, [n, n], dtype);\n}\n\n// ============================================================================\n// Tensor Operations\n// ============================================================================\n\n/**\n * Element-wise addition\n */\nexport function add(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor {\n  if (typeof b === 'number') {\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result[i] = (aData[i] ?? 0) + b;\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n  }\n\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\n      'Tensor sizes must match for element-wise operations',\n      ErrorCodes.TENSOR_SHAPE_MISMATCH,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  \n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) + (bData[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n\n/**\n * Element-wise subtraction\n */\nexport function sub(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor {\n  if (typeof b === 'number') {\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result[i] = (aData[i] ?? 0) - b;\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n  }\n\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\n      'Tensor sizes must match for element-wise operations',\n      ErrorCodes.TENSOR_SHAPE_MISMATCH,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  \n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) - (bData[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n\n/**\n * Element-wise multiplication\n */\nexport function mul(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor {\n  if (typeof b === 'number') {\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result[i] = (aData[i] ?? 0) * b;\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n  }\n\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\n      'Tensor sizes must match for element-wise operations',\n      ErrorCodes.TENSOR_SHAPE_MISMATCH,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  \n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) * (bData[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n\n/**\n * Element-wise division\n */\nexport function div(a: EdgeFlowTensor, b: EdgeFlowTensor | number): EdgeFlowTensor {\n  if (typeof b === 'number') {\n    const result = new Float32Array(a.size);\n    const aData = a.toFloat32Array();\n    for (let i = 0; i < a.size; i++) {\n      result[i] = (aData[i] ?? 0) / b;\n    }\n    return new EdgeFlowTensor(result, a.shape, a.dtype);\n  }\n\n  if (a.size !== b.size) {\n    throw new EdgeFlowError(\n      'Tensor sizes must match for element-wise operations',\n      ErrorCodes.TENSOR_SHAPE_MISMATCH,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const result = new Float32Array(a.size);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n  \n  for (let i = 0; i < a.size; i++) {\n    result[i] = (aData[i] ?? 0) / (bData[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, a.shape, a.dtype);\n}\n\n/**\n * Matrix multiplication (2D tensors)\n */\nexport function matmul(a: EdgeFlowTensor, b: EdgeFlowTensor): EdgeFlowTensor {\n  if (a.shape.length !== 2 || b.shape.length !== 2) {\n    throw new EdgeFlowError(\n      'matmul requires 2D tensors',\n      ErrorCodes.INVALID_ARGUMENT,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const [m, k1] = a.shape as [number, number];\n  const [k2, n] = b.shape as [number, number];\n\n  if (k1 !== k2) {\n    throw new EdgeFlowError(\n      `Matrix dimensions incompatible for multiplication: (${m}x${k1}) @ (${k2}x${n})`,\n      ErrorCodes.TENSOR_SHAPE_MISMATCH,\n      { aShape: a.shape, bShape: b.shape }\n    );\n  }\n\n  const result = new Float32Array(m * n);\n  const aData = a.toFloat32Array();\n  const bData = b.toFloat32Array();\n\n  for (let i = 0; i < m; i++) {\n    for (let j = 0; j < n; j++) {\n      let sum = 0;\n      for (let k = 0; k < k1; k++) {\n        sum += (aData[i * k1 + k] ?? 0) * (bData[k * n + j] ?? 0);\n      }\n      result[i * n + j] = sum;\n    }\n  }\n\n  return new EdgeFlowTensor(result, [m, n], a.dtype);\n}\n\n/**\n * Softmax activation\n */\nexport function softmax(t: EdgeFlowTensor, axis: number = -1): EdgeFlowTensor {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  \n  // Handle negative axis\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  \n  if (actualAxis < 0 || actualAxis >= t.shape.length) {\n    throw new EdgeFlowError(\n      `Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`,\n      ErrorCodes.INVALID_ARGUMENT,\n      { axis, shape: t.shape }\n    );\n  }\n\n  // For 1D tensors\n  if (t.shape.length === 1) {\n    let max = -Infinity;\n    for (let i = 0; i < t.size; i++) {\n      if ((data[i] ?? 0) > max) max = data[i] ?? 0;\n    }\n    \n    let sum = 0;\n    for (let i = 0; i < t.size; i++) {\n      result[i] = Math.exp((data[i] ?? 0) - max);\n      sum += result[i] ?? 0;\n    }\n    \n    for (let i = 0; i < t.size; i++) {\n      result[i] = (result[i] ?? 0) / sum;\n    }\n    \n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n  }\n\n  // For 2D tensors along last axis\n  if (t.shape.length === 2 && actualAxis === 1) {\n    const [rows, cols] = t.shape as [number, number];\n    \n    for (let i = 0; i < rows; i++) {\n      let max = -Infinity;\n      for (let j = 0; j < cols; j++) {\n        if ((data[i * cols + j] ?? 0) > max) max = data[i * cols + j] ?? 0;\n      }\n      \n      let sum = 0;\n      for (let j = 0; j < cols; j++) {\n        result[i * cols + j] = Math.exp((data[i * cols + j] ?? 0) - max);\n        sum += result[i * cols + j] ?? 0;\n      }\n      \n      for (let j = 0; j < cols; j++) {\n        result[i * cols + j] = (result[i * cols + j] ?? 0) / sum;\n      }\n    }\n    \n    return new EdgeFlowTensor(result, t.shape, t.dtype);\n  }\n\n  throw new EdgeFlowError(\n    'Softmax currently only supports 1D tensors or 2D tensors along the last axis',\n    ErrorCodes.NOT_IMPLEMENTED,\n    { shape: t.shape, axis }\n  );\n}\n\n/**\n * ReLU activation\n */\nexport function relu(t: EdgeFlowTensor): EdgeFlowTensor {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  \n  for (let i = 0; i < t.size; i++) {\n    result[i] = Math.max(0, data[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n\n/**\n * Sigmoid activation\n */\nexport function sigmoid(t: EdgeFlowTensor): EdgeFlowTensor {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  \n  for (let i = 0; i < t.size; i++) {\n    result[i] = 1 / (1 + Math.exp(-(data[i] ?? 0)));\n  }\n  \n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n\n/**\n * Tanh activation\n */\nexport function tanh(t: EdgeFlowTensor): EdgeFlowTensor {\n  const data = t.toFloat32Array();\n  const result = new Float32Array(t.size);\n  \n  for (let i = 0; i < t.size; i++) {\n    result[i] = Math.tanh(data[i] ?? 0);\n  }\n  \n  return new EdgeFlowTensor(result, t.shape, t.dtype);\n}\n\n/**\n * Sum all elements or along an axis\n */\nexport function sum(t: EdgeFlowTensor, axis?: number): EdgeFlowTensor | number {\n  const data = t.toFloat32Array();\n  \n  if (axis === undefined) {\n    let total = 0;\n    for (let i = 0; i < t.size; i++) {\n      total += data[i] ?? 0;\n    }\n    return total;\n  }\n\n  // Handle negative axis\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  \n  if (actualAxis < 0 || actualAxis >= t.shape.length) {\n    throw new EdgeFlowError(\n      `Invalid axis ${axis} for tensor with ${t.shape.length} dimensions`,\n      ErrorCodes.INVALID_ARGUMENT,\n      { axis, shape: t.shape }\n    );\n  }\n\n  // Calculate new shape\n  const newShape = [...t.shape];\n  newShape.splice(actualAxis, 1);\n  \n  if (newShape.length === 0) {\n    let total = 0;\n    for (let i = 0; i < t.size; i++) {\n      total += data[i] ?? 0;\n    }\n    return total;\n  }\n\n  // For 2D sum along axis\n  if (t.shape.length === 2) {\n    const [rows, cols] = t.shape as [number, number];\n    \n    if (actualAxis === 0) {\n      const result = new Float32Array(cols);\n      for (let j = 0; j < cols; j++) {\n        for (let i = 0; i < rows; i++) {\n          result[j] = (result[j] ?? 0) + (data[i * cols + j] ?? 0);\n        }\n      }\n      return new EdgeFlowTensor(result, [cols], t.dtype);\n    } else {\n      const result = new Float32Array(rows);\n      for (let i = 0; i < rows; i++) {\n        for (let j = 0; j < cols; j++) {\n          result[i] = (result[i] ?? 0) + (data[i * cols + j] ?? 0);\n        }\n      }\n      return new EdgeFlowTensor(result, [rows], t.dtype);\n    }\n  }\n\n  throw new EdgeFlowError(\n    'Sum along axis currently only supports up to 2D tensors',\n    ErrorCodes.NOT_IMPLEMENTED,\n    { shape: t.shape, axis }\n  );\n}\n\n/**\n * Mean of all elements or along an axis\n */\nexport function mean(t: EdgeFlowTensor, axis?: number): EdgeFlowTensor | number {\n  if (axis === undefined) {\n    return (sum(t) as number) / t.size;\n  }\n\n  const result = sum(t, axis);\n  if (typeof result === 'number') {\n    return result / (t.shape[axis] ?? 1);\n  }\n\n  const axisSize = t.shape[axis] ?? 1;\n  return div(result, axisSize);\n}\n\n/**\n * Argmax - return index of maximum value\n */\nexport function argmax(t: EdgeFlowTensor, axis?: number): number | EdgeFlowTensor {\n  const data = t.toFloat32Array();\n  \n  if (axis === undefined) {\n    let maxIdx = 0;\n    let maxVal = data[0] ?? -Infinity;\n    \n    for (let i = 1; i < t.size; i++) {\n      if ((data[i] ?? -Infinity) > maxVal) {\n        maxVal = data[i] ?? -Infinity;\n        maxIdx = i;\n      }\n    }\n    return maxIdx;\n  }\n\n  // Handle negative axis\n  const actualAxis = axis < 0 ? t.shape.length + axis : axis;\n  \n  // For 2D along last axis\n  if (t.shape.length === 2 && actualAxis === 1) {\n    const [rows, cols] = t.shape as [number, number];\n    const result = new Float32Array(rows);\n    \n    for (let i = 0; i < rows; i++) {\n      let maxIdx = 0;\n      let maxVal = data[i * cols] ?? -Infinity;\n      \n      for (let j = 1; j < cols; j++) {\n        if ((data[i * cols + j] ?? -Infinity) > maxVal) {\n          maxVal = data[i * cols + j] ?? -Infinity;\n          maxIdx = j;\n        }\n      }\n      result[i] = maxIdx;\n    }\n    \n    return new EdgeFlowTensor(result, [rows], 'int32');\n  }\n\n  throw new EdgeFlowError(\n    'Argmax along axis currently only supports 2D tensors along the last axis',\n    ErrorCodes.NOT_IMPLEMENTED,\n    { shape: t.shape, axis }\n  );\n}\n\n/**\n * Concatenate tensors along an axis\n */\nexport function concat(tensors: EdgeFlowTensor[], axis: number = 0): EdgeFlowTensor {\n  if (tensors.length === 0) {\n    throw new EdgeFlowError(\n      'Cannot concatenate empty array of tensors',\n      ErrorCodes.INVALID_ARGUMENT\n    );\n  }\n\n  if (tensors.length === 1) {\n    return tensors[0]?.clone() ?? zeros([0]);\n  }\n\n  const first = tensors[0];\n  if (!first) {\n    throw new EdgeFlowError('First tensor is undefined', ErrorCodes.INVALID_ARGUMENT);\n  }\n\n  // Handle negative axis\n  const actualAxis = axis < 0 ? first.shape.length + axis : axis;\n\n  // Validate shapes\n  for (let i = 1; i < tensors.length; i++) {\n    const t = tensors[i];\n    if (!t) continue;\n    \n    if (t.shape.length !== first.shape.length) {\n      throw new EdgeFlowError(\n        'All tensors must have the same number of dimensions',\n        ErrorCodes.TENSOR_SHAPE_MISMATCH\n      );\n    }\n    \n    for (let j = 0; j < first.shape.length; j++) {\n      if (j !== actualAxis && first.shape[j] !== t.shape[j]) {\n        throw new EdgeFlowError(\n          `Shape mismatch at dimension ${j}`,\n          ErrorCodes.TENSOR_SHAPE_MISMATCH\n        );\n      }\n    }\n  }\n\n  // Calculate new shape\n  const newShape = [...first.shape];\n  let totalAxisSize = 0;\n  for (const t of tensors) {\n    if (t) totalAxisSize += t.shape[actualAxis] ?? 0;\n  }\n  newShape[actualAxis] = totalAxisSize;\n\n  // For 1D concatenation\n  if (first.shape.length === 1) {\n    const result = new Float32Array(totalAxisSize);\n    let offset = 0;\n    \n    for (const t of tensors) {\n      if (!t) continue;\n      result.set(t.toFloat32Array(), offset);\n      offset += t.size;\n    }\n    \n    return new EdgeFlowTensor(result, newShape, first.dtype);\n  }\n\n  throw new EdgeFlowError(\n    'Concatenation currently only supports 1D tensors',\n    ErrorCodes.NOT_IMPLEMENTED\n  );\n}\n"
  },
  {
    "path": "src/core/types.ts",
    "content": "/**\n * edgeFlow.js - Core Type Definitions\n * \n * This file contains all the core types used throughout the framework.\n */\n\n// ============================================================================\n// Tensor Types\n// ============================================================================\n\n/**\n * Supported data types for tensors\n */\nexport type DataType = \n  | 'float32' \n  | 'float16' \n  | 'int32' \n  | 'int64' \n  | 'uint8' \n  | 'int8' \n  | 'bool';\n\n/**\n * TypedArray types used for tensor data\n */\nexport type TypedArray = \n  | Float32Array \n  | Float64Array \n  | Int32Array \n  | BigInt64Array \n  | Uint8Array \n  | Int8Array;\n\n/**\n * Tensor shape definition\n */\nexport type Shape = readonly number[];\n\n/**\n * Tensor interface\n */\nexport interface Tensor {\n  /** Unique identifier for the tensor */\n  readonly id: string;\n  /** Data type of the tensor */\n  readonly dtype: DataType;\n  /** Shape of the tensor */\n  readonly shape: Shape;\n  /** Total number of elements */\n  readonly size: number;\n  /** Underlying data */\n  readonly data: TypedArray;\n  /** Get data as Float32Array */\n  toFloat32Array(): Float32Array;\n  /** Get data as array */\n  toArray(): number[];\n  /** Clone the tensor */\n  clone(): Tensor;\n  /** Dispose the tensor and free memory */\n  dispose(): void;\n  /** Check if tensor has been disposed */\n  readonly isDisposed: boolean;\n}\n\n// ============================================================================\n// Runtime Types\n// ============================================================================\n\n/**\n * Supported runtime backends\n */\nexport type RuntimeType = 'webgpu' | 'webnn' | 'wasm' | 'auto';\n\n/**\n * Runtime capability flags\n */\nexport interface RuntimeCapabilities {\n  /** Supports concurrent execution */\n  concurrency: boolean;\n  /** Supports quantized models */\n  quantization: boolean;\n  /** Supports float16 */\n  float16: boolean;\n  /** Supports dynamic shapes */\n  dynamicShapes: boolean;\n  /** Maximum batch size */\n  maxBatchSize: number;\n  /** Available memory in bytes */\n  availableMemory: number;\n}\n\n/**\n * Runtime interface that all backends must implement\n */\nexport interface Runtime {\n  /** Runtime name */\n  readonly name: RuntimeType;\n  /** Runtime capabilities */\n  readonly capabilities: RuntimeCapabilities;\n  /** Initialize the runtime */\n  initialize(): Promise<void>;\n  /** Check if runtime is available in current environment */\n  isAvailable(): Promise<boolean>;\n  /** Load a model from ArrayBuffer */\n  loadModel(modelData: ArrayBuffer, options?: ModelLoadOptions): Promise<LoadedModel>;\n  /** Run inference */\n  run(model: LoadedModel, inputs: Tensor[]): Promise<Tensor[]>;\n  /** Run inference with named inputs (optional) */\n  runNamed?(model: LoadedModel, namedInputs: Map<string, Tensor>): Promise<Tensor[]>;\n  /** Dispose the runtime and free resources */\n  dispose(): void;\n}\n\n// ============================================================================\n// Model Types\n// ============================================================================\n\n/**\n * Model format types\n */\nexport type ModelFormat = 'onnx' | 'edgeflow' | 'safetensors';\n\n/**\n * Model quantization types\n */\nexport type QuantizationType = 'float32' | 'float16' | 'int8' | 'uint8' | 'int4';\n\n/**\n * Model metadata\n */\nexport interface ModelMetadata {\n  /** Model name/identifier */\n  name: string;\n  /** Model version */\n  version?: string;\n  /** Model description */\n  description?: string;\n  /** Model author */\n  author?: string;\n  /** Model license */\n  license?: string;\n  /** Model tags */\n  tags?: string[];\n  /** Input specifications */\n  inputs: ModelIOSpec[];\n  /** Output specifications */\n  outputs: ModelIOSpec[];\n  /** Model size in bytes */\n  sizeBytes: number;\n  /** Quantization type */\n  quantization: QuantizationType;\n  /** Model format */\n  format: ModelFormat;\n}\n\n/**\n * Model input/output specification\n */\nexport interface ModelIOSpec {\n  /** Name of the input/output */\n  name: string;\n  /** Data type */\n  dtype: DataType;\n  /** Shape (use -1 for dynamic dimensions) */\n  shape: number[];\n  /** Optional description */\n  description?: string;\n}\n\n/**\n * Options for loading a model\n */\nexport interface ModelLoadOptions {\n  /** Target quantization (convert during load) */\n  quantization?: QuantizationType;\n  /** Custom metadata */\n  metadata?: Partial<ModelMetadata>;\n  /** Enable caching */\n  cache?: boolean;\n  /** Progress callback */\n  onProgress?: (progress: number) => void;\n}\n\n/**\n * Loaded model instance\n */\nexport interface LoadedModel {\n  /** Unique model instance ID */\n  readonly id: string;\n  /** Model metadata */\n  readonly metadata: ModelMetadata;\n  /** Check if model is loaded */\n  readonly isLoaded: boolean;\n  /** Runtime this model is loaded on */\n  readonly runtime: RuntimeType;\n  /** Dispose the model and free resources */\n  dispose(): void;\n}\n\n// ============================================================================\n// Scheduler Types\n// ============================================================================\n\n/**\n * Task priority levels\n */\nexport type TaskPriority = 'low' | 'normal' | 'high' | 'critical';\n\n/**\n * Task status\n */\nexport type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n\n/**\n * Inference task definition\n */\nexport interface InferenceTask<T = unknown> {\n  /** Unique task ID */\n  readonly id: string;\n  /** Model ID this task is for */\n  readonly modelId: string;\n  /** Task priority */\n  readonly priority: TaskPriority;\n  /** Task status */\n  readonly status: TaskStatus;\n  /** Creation timestamp */\n  readonly createdAt: number;\n  /** Start timestamp (when running) */\n  readonly startedAt?: number;\n  /** Completion timestamp */\n  readonly completedAt?: number;\n  /** Task result (when completed) */\n  readonly result?: T;\n  /** Task error (when failed) */\n  readonly error?: Error;\n  /** Cancel the task */\n  cancel(): void;\n  /** Wait for task completion */\n  wait(): Promise<T>;\n}\n\n/**\n * Scheduler options\n */\nexport interface SchedulerOptions {\n  /** Maximum concurrent tasks across all models */\n  maxConcurrentTasks?: number;\n  /** Maximum concurrent tasks per model */\n  maxConcurrentPerModel?: number;\n  /** Default task timeout in milliseconds */\n  defaultTimeout?: number;\n  /** Enable task batching */\n  enableBatching?: boolean;\n  /** Maximum batch size */\n  maxBatchSize?: number;\n  /** Batch timeout in milliseconds */\n  batchTimeout?: number;\n  /** Maximum retry attempts for failed tasks (default: 0 = no retry) */\n  maxRetries?: number;\n  /** Base delay between retries in ms (exponential backoff) */\n  retryBaseDelay?: number;\n  /** Enable circuit breaker per model (default: false) */\n  circuitBreaker?: boolean;\n  /** Consecutive failures before the circuit opens (default: 5) */\n  circuitBreakerThreshold?: number;\n  /** Time in ms before the circuit half-opens to test (default: 30000) */\n  circuitBreakerResetTimeout?: number;\n}\n\n// ============================================================================\n// Memory Types\n// ============================================================================\n\n/**\n * Memory statistics\n */\nexport interface MemoryStats {\n  /** Total allocated memory in bytes */\n  allocated: number;\n  /** Currently used memory in bytes */\n  used: number;\n  /** Peak memory usage in bytes */\n  peak: number;\n  /** Number of active tensors */\n  tensorCount: number;\n  /** Number of loaded models */\n  modelCount: number;\n}\n\n/**\n * Memory pool configuration\n */\nexport interface MemoryPoolConfig {\n  /** Initial pool size in bytes */\n  initialSize?: number;\n  /** Maximum pool size in bytes */\n  maxSize?: number;\n  /** Growth factor when expanding */\n  growthFactor?: number;\n  /** Enable automatic garbage collection */\n  autoGC?: boolean;\n  /** GC threshold (percentage of max size) */\n  gcThreshold?: number;\n}\n\n// ============================================================================\n// Pipeline Types\n// ============================================================================\n\n/**\n * Supported pipeline tasks\n */\nexport type PipelineTask = \n  | 'text-classification'\n  | 'token-classification'\n  | 'question-answering'\n  | 'fill-mask'\n  | 'text-generation'\n  | 'text2text-generation'\n  | 'summarization'\n  | 'translation'\n  | 'feature-extraction'\n  | 'sentiment-analysis'\n  | 'zero-shot-classification'\n  | 'image-classification'\n  | 'object-detection'\n  | 'image-segmentation'\n  | 'depth-estimation'\n  | 'image-to-text'\n  | 'audio-classification'\n  | 'automatic-speech-recognition'\n  | 'text-to-speech';\n\n/**\n * Pipeline configuration\n */\nexport interface PipelineConfig {\n  /** Task type */\n  task: PipelineTask;\n  /** Model ID or path */\n  model: string;\n  /** Runtime to use */\n  runtime?: RuntimeType;\n  /** Enable caching */\n  cache?: boolean;\n  /** Quantization type */\n  quantization?: QuantizationType;\n  /** Device to use */\n  device?: 'cpu' | 'gpu';\n  /** Custom tokenizer config */\n  tokenizer?: TokenizerConfig;\n}\n\n/**\n * Pipeline options passed during inference\n */\nexport interface PipelineOptions {\n  /** Batch size */\n  batchSize?: number;\n  /** Top K results */\n  topK?: number;\n  /** Temperature for generation */\n  temperature?: number;\n  /** Maximum length for generation */\n  maxLength?: number;\n  /** Task timeout in milliseconds */\n  timeout?: number;\n}\n\n// ============================================================================\n// Tokenizer Types\n// ============================================================================\n\n/**\n * Tokenizer configuration\n */\nexport interface TokenizerConfig {\n  /** Vocabulary size */\n  vocabSize: number;\n  /** Maximum sequence length */\n  maxLength: number;\n  /** Padding token ID */\n  padTokenId: number;\n  /** Unknown token ID */\n  unkTokenId: number;\n  /** Start of sequence token ID */\n  bosTokenId?: number;\n  /** End of sequence token ID */\n  eosTokenId?: number;\n  /** Separator token ID */\n  sepTokenId?: number;\n  /** CLS token ID */\n  clsTokenId?: number;\n  /** Mask token ID */\n  maskTokenId?: number;\n}\n\n/**\n * Tokenized output\n */\nexport interface TokenizedOutput {\n  /** Input IDs */\n  inputIds: number[];\n  /** Attention mask */\n  attentionMask: number[];\n  /** Token type IDs (for segment embeddings) */\n  tokenTypeIds?: number[];\n  /** Special tokens mask */\n  specialTokensMask?: number[];\n  /** Offset mapping (for token-level tasks) */\n  offsetMapping?: [number, number][];\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Base error class for edgeFlow errors\n */\nexport class EdgeFlowError extends Error {\n  constructor(\n    message: string,\n    public readonly code: string,\n    public readonly details?: Record<string, unknown>\n  ) {\n    super(message);\n    this.name = 'EdgeFlowError';\n  }\n}\n\n/**\n * Error codes\n */\nexport const ErrorCodes = {\n  // Runtime errors\n  RUNTIME_NOT_AVAILABLE: 'RUNTIME_NOT_AVAILABLE',\n  RUNTIME_INIT_FAILED: 'RUNTIME_INIT_FAILED',\n  RUNTIME_NOT_INITIALIZED: 'RUNTIME_NOT_INITIALIZED',\n  \n  // Model errors\n  MODEL_NOT_FOUND: 'MODEL_NOT_FOUND',\n  MODEL_LOAD_FAILED: 'MODEL_LOAD_FAILED',\n  MODEL_INVALID_FORMAT: 'MODEL_INVALID_FORMAT',\n  MODEL_NOT_LOADED: 'MODEL_NOT_LOADED',\n  \n  // Inference errors\n  INFERENCE_FAILED: 'INFERENCE_FAILED',\n  INFERENCE_TIMEOUT: 'INFERENCE_TIMEOUT',\n  INFERENCE_CANCELLED: 'INFERENCE_CANCELLED',\n  \n  // Memory errors\n  OUT_OF_MEMORY: 'OUT_OF_MEMORY',\n  MEMORY_LEAK_DETECTED: 'MEMORY_LEAK_DETECTED',\n  \n  // Tensor errors\n  TENSOR_SHAPE_MISMATCH: 'TENSOR_SHAPE_MISMATCH',\n  TENSOR_DTYPE_MISMATCH: 'TENSOR_DTYPE_MISMATCH',\n  TENSOR_DISPOSED: 'TENSOR_DISPOSED',\n  \n  // Pipeline errors\n  PIPELINE_NOT_SUPPORTED: 'PIPELINE_NOT_SUPPORTED',\n  PIPELINE_INPUT_INVALID: 'PIPELINE_INPUT_INVALID',\n  \n  // General errors\n  INVALID_ARGUMENT: 'INVALID_ARGUMENT',\n  NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',\n  UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n} as const;\n\nexport type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Event types emitted by edgeFlow\n */\nexport type EventType = \n  | 'model:loading'\n  | 'model:loaded'\n  | 'model:unloaded'\n  | 'inference:start'\n  | 'inference:complete'\n  | 'inference:error'\n  | 'memory:warning'\n  | 'memory:gc'\n  | 'runtime:ready'\n  | 'runtime:error';\n\n/**\n * Event payload interface\n */\nexport interface EdgeFlowEvent<T = unknown> {\n  type: EventType;\n  timestamp: number;\n  data: T;\n}\n\n/**\n * Event listener function type\n */\nexport type EventListener<T = unknown> = (event: EdgeFlowEvent<T>) => void;\n"
  },
  {
    "path": "src/core/worker.ts",
    "content": "/**\n * edgeFlow.js - Web Worker Support\n * \n * Run inference in a Web Worker to avoid blocking the main thread.\n */\n\nimport type { Tensor, RuntimeType } from './types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Worker message types\n */\nexport type WorkerMessageType =\n  | 'init'\n  | 'load_model'\n  | 'run_inference'\n  | 'dispose'\n  | 'ready'\n  | 'result'\n  | 'error'\n  | 'progress';\n\n/**\n * Worker message structure\n */\nexport interface WorkerMessage {\n  id: string;\n  type: WorkerMessageType;\n  payload?: unknown;\n}\n\n/**\n * Worker request for loading a model\n */\nexport interface LoadModelRequest {\n  url: string;\n  options?: {\n    runtime?: RuntimeType;\n    cache?: boolean;\n  };\n}\n\n/**\n * Worker request for running inference\n */\nexport interface InferenceRequest {\n  modelId: string;\n  inputs: SerializedTensor[];\n}\n\n/**\n * Serialized tensor for transfer\n */\nexport interface SerializedTensor {\n  data: ArrayBuffer;\n  shape: number[];\n  dtype: string;\n}\n\n/**\n * Worker pool options\n */\nexport interface WorkerPoolOptions {\n  /** Number of workers (default: navigator.hardwareConcurrency or 4) */\n  numWorkers?: number;\n  /** Worker script URL (default: auto-detect) */\n  workerUrl?: string;\n}\n\n// ============================================================================\n// Tensor Serialization\n// ============================================================================\n\n/**\n * Serialize a tensor for transfer to worker\n */\nexport function serializeTensor(tensor: Tensor): SerializedTensor {\n  const data = tensor.toFloat32Array();\n  // Create a copy of the ArrayBuffer\n  const buffer = new ArrayBuffer(data.byteLength);\n  new Float32Array(buffer).set(data);\n  return {\n    data: buffer,\n    shape: [...tensor.shape],\n    dtype: tensor.dtype,\n  };\n}\n\n/**\n * Deserialize a tensor from worker.\n * Uses a lazy import to avoid circular dependency issues.\n */\nexport async function deserializeTensor(serialized: SerializedTensor): Promise<Tensor> {\n  const { EdgeFlowTensor } = await import('./tensor.js');\n  const data = new Float32Array(serialized.data);\n  return new EdgeFlowTensor(data, serialized.shape, serialized.dtype as 'float32');\n}\n\n/**\n * Synchronous deserialisation used internally where async is not feasible.\n * Requires EdgeFlowTensor to be passed in to avoid require().\n */\nexport function deserializeTensorSync(\n  serialized: SerializedTensor,\n  TensorClass: new (data: Float32Array, shape: number[], dtype: string) => Tensor,\n): Tensor {\n  const data = new Float32Array(serialized.data);\n  return new TensorClass(data, serialized.shape, serialized.dtype);\n}\n\n// ============================================================================\n// Worker Manager\n// ============================================================================\n\nexport type WorkerHealthState = 'alive' | 'dead' | 'restarting';\n\nconst MAX_RESTART_ATTEMPTS = 3;\nconst RESTART_BASE_DELAY_MS = 1000;\n\n/**\n * InferenceWorker - Wrapper for a single Web Worker with auto-restart\n */\nexport class InferenceWorker {\n  private worker: Worker | null = null;\n  private pendingRequests: Map<string, {\n    resolve: (result: unknown) => void;\n    reject: (error: Error) => void;\n  }> = new Map();\n  private isReady = false;\n  private readyPromise: Promise<void>;\n  private readyResolve!: () => void;\n  private workerUrl: string | undefined;\n  private _health: WorkerHealthState = 'alive';\n  private restartAttempts = 0;\n\n  constructor(workerUrl?: string) {\n    this.workerUrl = workerUrl;\n    this.readyPromise = new Promise(resolve => {\n      this.readyResolve = resolve;\n    });\n    \n    this.initWorker(workerUrl);\n  }\n\n  get health(): WorkerHealthState {\n    return this._health;\n  }\n\n  /**\n   * Initialize the worker\n   */\n  private initWorker(workerUrl?: string): void {\n    const url = workerUrl ?? this.createWorkerBlob();\n    \n    this.worker = new Worker(url, { type: 'module' });\n    \n    this.worker.onmessage = (event: MessageEvent<WorkerMessage>) => {\n      this.handleMessage(event.data);\n    };\n    \n    this.worker.onerror = (error) => {\n      console.error('Worker error:', error);\n      this.handleCrash();\n    };\n\n    this.worker.onmessageerror = () => {\n      this.handleCrash();\n    };\n  }\n\n  /**\n   * Handle worker crash: reject pending, mark dead, attempt restart\n   */\n  private handleCrash(): void {\n    this._health = 'dead';\n    this.isReady = false;\n    \n    const crashError = new Error('Worker crashed');\n    for (const [, { reject }] of this.pendingRequests) {\n      reject(crashError);\n    }\n    this.pendingRequests.clear();\n\n    this.attemptRestart();\n  }\n\n  /**\n   * Restart the worker with exponential backoff\n   */\n  private attemptRestart(): void {\n    if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {\n      console.error(`Worker failed to restart after ${MAX_RESTART_ATTEMPTS} attempts`);\n      return;\n    }\n\n    this._health = 'restarting';\n    const delay = RESTART_BASE_DELAY_MS * Math.pow(2, this.restartAttempts);\n    this.restartAttempts++;\n\n    setTimeout(() => {\n      this.restart();\n    }, delay);\n  }\n\n  /**\n   * Restart: terminate old, create new\n   */\n  restart(): void {\n    if (this.worker) {\n      try { this.worker.terminate(); } catch { /* already dead */ }\n      this.worker = null;\n    }\n\n    this.readyPromise = new Promise(resolve => {\n      this.readyResolve = resolve;\n    });\n    this.isReady = false;\n\n    try {\n      this.initWorker(this.workerUrl);\n      this._health = 'alive';\n      this.restartAttempts = 0;\n    } catch {\n      this._health = 'dead';\n      this.attemptRestart();\n    }\n  }\n\n  /**\n   * Create worker code as blob URL\n   */\n  private createWorkerBlob(): string {\n    const workerCode = `\n      // edgeFlow.js Worker\n      let models = new Map();\n      let ort = null;\n      \n      // Load ONNX Runtime\n      async function loadOrt() {\n        if (ort) return ort;\n        ort = await import('https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/esm/ort.min.js');\n        return ort;\n      }\n      \n      // Handle messages\n      self.onmessage = async (event) => {\n        const { id, type, payload } = event.data;\n        \n        try {\n          switch (type) {\n            case 'init': {\n              await loadOrt();\n              self.postMessage({ id, type: 'ready' });\n              break;\n            }\n            \n            case 'load_model': {\n              await loadOrt();\n              const { url, options } = payload;\n              const response = await fetch(url);\n              const arrayBuffer = await response.arrayBuffer();\n              const session = await ort.InferenceSession.create(\n                new Uint8Array(arrayBuffer),\n                { executionProviders: ['wasm'] }\n              );\n              const modelId = 'model_' + Date.now();\n              models.set(modelId, session);\n              self.postMessage({\n                id,\n                type: 'result',\n                payload: { modelId }\n              });\n              break;\n            }\n            \n            case 'run_inference': {\n              const { modelId, inputs } = payload;\n              const session = models.get(modelId);\n              if (!session) {\n                throw new Error('Model not found: ' + modelId);\n              }\n              \n              // Prepare inputs\n              const feeds = {};\n              const inputNames = session.inputNames;\n              for (let i = 0; i < inputs.length && i < inputNames.length; i++) {\n                const input = inputs[i];\n                const data = new Float32Array(input.data);\n                feeds[inputNames[i]] = new ort.Tensor(input.dtype, data, input.shape);\n              }\n              \n              // Run inference\n              const results = await session.run(feeds);\n              \n              // Serialize outputs\n              const outputs = [];\n              for (const name of session.outputNames) {\n                const tensor = results[name];\n                outputs.push({\n                  data: tensor.data.buffer.slice(0),\n                  shape: tensor.dims,\n                  dtype: tensor.type\n                });\n              }\n              \n              self.postMessage(\n                { id, type: 'result', payload: { outputs } },\n                outputs.map(o => o.data)\n              );\n              break;\n            }\n            \n            case 'dispose': {\n              const { modelId } = payload;\n              const session = models.get(modelId);\n              if (session) {\n                // session.release(); // Not available in all versions\n                models.delete(modelId);\n              }\n              self.postMessage({ id, type: 'result', payload: { success: true } });\n              break;\n            }\n          }\n        } catch (error) {\n          self.postMessage({\n            id,\n            type: 'error',\n            payload: { message: error.message }\n          });\n        }\n      };\n    `;\n    \n    const blob = new Blob([workerCode], { type: 'application/javascript' });\n    return URL.createObjectURL(blob);\n  }\n\n  /**\n   * Handle worker message\n   */\n  private handleMessage(message: WorkerMessage): void {\n    if (message.type === 'ready') {\n      this.isReady = true;\n      this.readyResolve();\n      return;\n    }\n\n    const request = this.pendingRequests.get(message.id);\n    if (!request) return;\n\n    this.pendingRequests.delete(message.id);\n\n    if (message.type === 'error') {\n      const payload = message.payload as { message: string };\n      request.reject(new Error(payload.message));\n    } else {\n      request.resolve(message.payload);\n    }\n  }\n\n  /**\n   * Send a request to the worker\n   */\n  private async sendRequest<T>(type: WorkerMessageType, payload?: unknown): Promise<T> {\n    if (!this.worker) {\n      throw new Error('Worker not initialized');\n    }\n\n    const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n    \n    return new Promise<T>((resolve, reject) => {\n      this.pendingRequests.set(id, { resolve: resolve as (r: unknown) => void, reject });\n      \n      const message: WorkerMessage = { id, type, payload };\n      \n      // Transfer ArrayBuffers for efficiency\n      const transfers: Transferable[] = [];\n      if (payload && typeof payload === 'object' && 'inputs' in payload) {\n        const inputs = (payload as InferenceRequest).inputs;\n        for (const input of inputs) {\n          if (input.data instanceof ArrayBuffer) {\n            transfers.push(input.data);\n          }\n        }\n      }\n      \n      this.worker!.postMessage(message, transfers);\n    });\n  }\n\n  /**\n   * Initialize the worker\n   */\n  async init(): Promise<void> {\n    if (this.isReady) return;\n    await this.sendRequest('init');\n    await this.readyPromise;\n  }\n\n  /**\n   * Load a model\n   */\n  async loadModel(url: string, options?: { runtime?: RuntimeType; cache?: boolean }): Promise<string> {\n    await this.init();\n    const result = await this.sendRequest<{ modelId: string }>('load_model', { url, options });\n    return result.modelId;\n  }\n\n  /**\n   * Run inference\n   */\n  async runInference(modelId: string, inputs: Tensor[]): Promise<Tensor[]> {\n    const serializedInputs = inputs.map(serializeTensor);\n    const result = await this.sendRequest<{ outputs: SerializedTensor[] }>(\n      'run_inference',\n      { modelId, inputs: serializedInputs }\n    );\n    return Promise.all(result.outputs.map(deserializeTensor));\n  }\n\n  /**\n   * Dispose a model\n   */\n  async dispose(modelId: string): Promise<void> {\n    await this.sendRequest('dispose', { modelId });\n  }\n\n  /**\n   * Terminate the worker\n   */\n  terminate(): void {\n    if (this.worker) {\n      this.worker.terminate();\n      this.worker = null;\n    }\n    this.pendingRequests.clear();\n  }\n}\n\n// ============================================================================\n// Worker Pool\n// ============================================================================\n\n/**\n * WorkerPool - Manage multiple workers for parallel inference.\n * Automatically falls back to healthy workers when one is dead.\n */\nexport class WorkerPool {\n  private workers: InferenceWorker[] = [];\n  private currentIndex = 0;\n  private modelAssignments: Map<string, number> = new Map();\n  private poolOptions: WorkerPoolOptions;\n\n  constructor(options: WorkerPoolOptions = {}) {\n    this.poolOptions = options;\n    const numWorkers = options.numWorkers ??\n      (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 4) ?? 4;\n    \n    for (let i = 0; i < numWorkers; i++) {\n      this.workers.push(new InferenceWorker(options.workerUrl));\n    }\n  }\n\n  /**\n   * Get next healthy worker (round-robin, skipping dead ones)\n   */\n  private getNextHealthyWorker(): InferenceWorker {\n    const len = this.workers.length;\n    for (let attempt = 0; attempt < len; attempt++) {\n      const worker = this.workers[this.currentIndex]!;\n      this.currentIndex = (this.currentIndex + 1) % len;\n      if (worker.health === 'alive') return worker;\n    }\n    // All dead — try restarting first one and return it\n    const worker = this.workers[0]!;\n    if (worker.health === 'dead') worker.restart();\n    return worker;\n  }\n\n  /**\n   * Get worker for a specific model, falling back to any healthy worker\n   */\n  private getWorkerForModel(modelId: string): InferenceWorker {\n    const index = this.modelAssignments.get(modelId);\n    if (index !== undefined) {\n      const worker = this.workers[index]!;\n      if (worker.health === 'alive') return worker;\n      // Assigned worker is dead — pick a healthy one and reassign\n      const replacement = this.getNextHealthyWorker();\n      this.modelAssignments.set(modelId, this.workers.indexOf(replacement));\n      return replacement;\n    }\n    return this.getNextHealthyWorker();\n  }\n\n  /**\n   * Replace a worker at a given index with a fresh one\n   */\n  replaceWorker(index: number): void {\n    if (index < 0 || index >= this.workers.length) return;\n    const old = this.workers[index]!;\n    old.terminate();\n    this.workers[index] = new InferenceWorker(this.poolOptions.workerUrl);\n  }\n\n  /**\n   * Initialize all workers\n   */\n  async init(): Promise<void> {\n    await Promise.all(this.workers.map(w => w.init()));\n  }\n\n  /**\n   * Load a model on a worker\n   */\n  async loadModel(\n    url: string,\n    options?: { runtime?: RuntimeType; cache?: boolean }\n  ): Promise<string> {\n    const worker = this.getNextHealthyWorker();\n    const modelId = await worker.loadModel(url, options);\n    this.modelAssignments.set(modelId, this.workers.indexOf(worker));\n    return modelId;\n  }\n\n  /**\n   * Run inference (auto-retries on a healthy worker if assigned one is dead)\n   */\n  async runInference(modelId: string, inputs: Tensor[]): Promise<Tensor[]> {\n    const worker = this.getWorkerForModel(modelId);\n    return worker.runInference(modelId, inputs);\n  }\n\n  /**\n   * Run inference on multiple inputs in parallel\n   */\n  async runBatch(\n    modelId: string,\n    batchInputs: Tensor[][]\n  ): Promise<Tensor[][]> {\n    const results = await Promise.all(\n      batchInputs.map((inputs, i) => {\n        const worker = this.workers[i % this.workers.length]!;\n        return worker.runInference(modelId, inputs);\n      })\n    );\n    return results;\n  }\n\n  /**\n   * Dispose a model\n   */\n  async dispose(modelId: string): Promise<void> {\n    const worker = this.getWorkerForModel(modelId);\n    await worker.dispose(modelId);\n    this.modelAssignments.delete(modelId);\n  }\n\n  /**\n   * Terminate all workers\n   */\n  terminate(): void {\n    for (const worker of this.workers) {\n      worker.terminate();\n    }\n    this.workers = [];\n    this.modelAssignments.clear();\n  }\n\n  /**\n   * Get number of workers\n   */\n  get size(): number {\n    return this.workers.length;\n  }\n}\n\n// ============================================================================\n// Global Instance\n// ============================================================================\n\nlet globalWorkerPool: WorkerPool | null = null;\n\n/**\n * Get or create global worker pool\n */\nexport function getWorkerPool(options?: WorkerPoolOptions): WorkerPool {\n  if (!globalWorkerPool) {\n    globalWorkerPool = new WorkerPool(options);\n  }\n  return globalWorkerPool;\n}\n\n/**\n * Run inference in a worker\n */\nexport async function runInWorker(\n  modelUrl: string,\n  inputs: Tensor[],\n  options?: { cache?: boolean }\n): Promise<Tensor[]> {\n  const pool = getWorkerPool();\n  await pool.init();\n  \n  const modelId = await pool.loadModel(modelUrl, options);\n  const outputs = await pool.runInference(modelId, inputs);\n  \n  return outputs;\n}\n\n/**\n * Check if Web Workers are supported\n */\nexport function isWorkerSupported(): boolean {\n  return typeof Worker !== 'undefined';\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "/**\n * edgeFlow.js\n * \n * Lightweight, high-performance browser ML inference framework\n * with native concurrency support.\n * \n * @example\n * ```typescript\n * import { pipeline } from 'edgeflow';\n * \n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n * \n * // Run inference\n * const result = await sentiment.run('I love this product!');\n * console.log(result); // { label: 'positive', score: 0.98 }\n * \n * // Batch processing\n * const results = await sentiment.run([\n *   'This is amazing!',\n *   'This is terrible.'\n * ]);\n * \n * // Concurrent execution with different models\n * const classifier = await pipeline('text-classification');\n * const extractor = await pipeline('feature-extraction');\n * \n * const [classification, features] = await Promise.all([\n *   classifier.run('Sample text'),\n *   extractor.run('Sample text')\n * ]);\n * ```\n * \n * @packageDocumentation\n */\n\n// ============================================================================\n// Core Exports\n// ============================================================================\n\n// Types\nexport type {\n  // Tensor types\n  DataType,\n  TypedArray,\n  Shape,\n  Tensor,\n  \n  // Runtime types\n  RuntimeType,\n  RuntimeCapabilities,\n  Runtime,\n  \n  // Model types\n  ModelFormat,\n  QuantizationType,\n  ModelMetadata,\n  ModelIOSpec,\n  ModelLoadOptions,\n  LoadedModel,\n  \n  // Scheduler types\n  TaskPriority,\n  TaskStatus,\n  InferenceTask,\n  SchedulerOptions,\n  \n  // Memory types\n  MemoryStats,\n  MemoryPoolConfig,\n  \n  // Pipeline types\n  PipelineTask,\n  PipelineConfig,\n  PipelineOptions,\n  \n  // Tokenizer types\n  TokenizerConfig,\n  TokenizedOutput,\n  \n  // Event types\n  EventType,\n  EdgeFlowEvent,\n  EventListener,\n  \n  // Error types\n  ErrorCode,\n} from './core/types.js';\n\n// Error class\nexport { EdgeFlowError, ErrorCodes } from './core/types.js';\n\n// Tensor operations\nexport {\n  EdgeFlowTensor,\n  tensor,\n  zeros,\n  ones,\n  full,\n  random,\n  randn,\n  arange,\n  linspace,\n  eye,\n  add,\n  sub,\n  mul,\n  div,\n  matmul,\n  softmax,\n  relu,\n  sigmoid,\n  tanh,\n  sum,\n  mean,\n  argmax,\n  concat,\n} from './core/tensor.js';\n\n// Scheduler\nexport {\n  InferenceScheduler,\n  getScheduler,\n  setScheduler,\n  configureScheduler,\n} from './core/scheduler.js';\n\n// Memory management\nexport {\n  MemoryManager,\n  MemoryScope,\n  ModelCache,\n  withMemoryScope,\n  withMemoryScopeSync,\n  getMemoryManager,\n  getMemoryStats,\n  release,\n  gc,\n} from './core/memory.js';\n\n// Plugin system\nexport {\n  registerPlugin,\n  getPluginPipeline,\n  getPluginMiddleware,\n  listPlugins,\n  unregisterPlugin,\n  type EdgeFlowPlugin,\n  type PluginPipelineEntry,\n  type PluginBackendEntry,\n  type PluginMiddleware,\n} from './core/plugin.js';\n\n// Device profiling\nexport {\n  getDeviceProfile,\n  recommendQuantization,\n  recommendModelVariant,\n  resetDeviceProfile,\n  type DeviceProfile,\n  type DeviceTier,\n  type ModelRecommendation,\n} from './core/device-profiler.js';\n\n// Pipeline composition\nexport {\n  compose,\n  parallel,\n  type CompositionStage,\n  type CompositionResult,\n  type ComposedPipeline,\n} from './core/composer.js';\n\n// Runtime management\nexport {\n  RuntimeManager,\n  LoadedModelImpl,\n  loadModel,\n  loadModelFromBuffer,\n  runInference,\n  runBatchInference,\n  getRuntimeManager,\n  registerRuntime,\n  getBestRuntime,\n  getAvailableRuntimes,\n} from './core/runtime.js';\n\n// ============================================================================\n// Backend Exports\n// ============================================================================\n\nexport {\n  WebGPURuntime,\n  createWebGPURuntime,\n  WebNNRuntime,\n  createWebNNRuntime,\n  WASMRuntime,\n  createWASMRuntime,\n  registerAllBackends,\n  \n  // transformers.js adapter\n  TransformersAdapterRuntime,\n  useTransformersBackend,\n  getTransformersAdapter,\n  type TransformersAdapterOptions,\n  type TransformersPipelineFactory,\n} from './backends/index.js';\n\n// ============================================================================\n// Pipeline Exports\n// ============================================================================\n\nexport {\n  // Factory function\n  pipeline,\n  createPipelines,\n  \n  // Base classes\n  BasePipeline,\n  registerPipeline,\n  getPipelineFactory,\n  \n  // Labels\n  SENTIMENT_LABELS,\n  EMOTION_LABELS,\n  IMAGENET_LABELS,\n  \n  // Result types\n  type PipelineResult,\n  type TextClassificationResult,\n  type FeatureExtractionResult,\n  type ImageClassificationResult,\n  type ObjectDetectionResult,\n  \n  // Pipelines\n  TextClassificationPipeline,\n  SentimentAnalysisPipeline,\n  FeatureExtractionPipeline,\n  ImageClassificationPipeline,\n  TextGenerationPipeline,\n  ImageSegmentationPipeline,\n  \n  // Factory functions\n  createTextClassificationPipeline,\n  createSentimentAnalysisPipeline,\n  createFeatureExtractionPipeline,\n  createImageClassificationPipeline,\n  createTextGenerationPipeline,\n  createImageSegmentationPipeline,\n  \n  // Options types\n  type PipelineFactoryOptions,\n  type TextClassificationOptions,\n  type FeatureExtractionOptions,\n  type ImageClassificationOptions,\n  type ImageInput,\n  \n  // Text Generation types\n  type TextGenerationOptions,\n  type TextGenerationResult,\n  type GenerationStreamEvent,\n  type ChatMessage,\n  type ChatOptions,\n  type ChatTemplateType,\n  type LLMLoadProgress,\n  \n  // Image Segmentation types\n  type ImageSegmentationOptions,\n  type ImageSegmentationResult,\n  type PointPrompt,\n  type BoxPrompt,\n  type ModelLoadProgress,\n} from './pipelines/index.js';\n\n// ============================================================================\n// Utility Exports\n// ============================================================================\n\nexport {\n  // Tokenizer\n  Tokenizer,\n  createBasicTokenizer,\n  loadTokenizer,\n  loadTokenizerFromHub,\n  type TokenizerModel,\n  type TokenizerOptions,\n  \n  // Preprocessor\n  ImagePreprocessor,\n  AudioPreprocessor,\n  preprocessText,\n  createImagePreprocessor,\n  createAudioPreprocessor,\n  type ImagePreprocessorOptions,\n  type AudioPreprocessorOptions,\n  type TextPreprocessorOptions,\n  \n  // Cache\n  Cache,\n  InferenceCache,\n  ModelDownloadCache,\n  createCache,\n  type CacheStrategy,\n  type CacheOptions,\n  type CacheStats,\n  \n  // Model Loader (Preloading, Sharding, Resume, Caching)\n  loadModelData,\n  preloadModel,\n  preloadModels,\n  isModelCached,\n  getCachedModel,\n  deleteCachedModel,\n  clearModelCache,\n  getModelCacheStats,\n  getPreloadStatus,\n  cancelPreload,\n  getPreloadedModel,\n  type DownloadProgress,\n  type ModelLoaderOptions,\n  type PreloadOptions,\n  \n  // HuggingFace Hub Integration\n  fromHub,\n  fromTask,\n  downloadModel,\n  downloadTokenizer,\n  downloadConfig,\n  modelExists,\n  getModelInfo,\n  getDefaultModel,\n  POPULAR_MODELS,\n  type HubOptions,\n  type HubDownloadProgress,\n  type ModelConfig,\n  type ModelBundle,\n  type PopularModelTask,\n} from './utils/index.js';\n\n// ============================================================================\n// Tools Exports\n// ============================================================================\n\nexport {\n  // Quantization (basic)\n  quantize,\n  type QuantizationOptions,\n  type QuantizationResult,\n  \n  // Pruning (basic)\n  prune,\n  type PruningOptions,\n  type PruningResult,\n  \n  // Analysis (basic)\n  analyzeModel,\n  type ModelAnalysis,\n  \n  // Benchmarking (basic)\n  benchmark,\n  type BenchmarkOptions,\n  type BenchmarkResult,\n  \n  // Export\n  exportModel,\n  \n  // Advanced Quantization\n  quantizeModel,\n  quantizeTensor,\n  dequantizeTensor,\n  pruneModel,\n  pruneTensor,\n  analyzeModelDetailed,\n  exportModelAdvanced,\n  dequantizeInt8,\n  dequantizeUint8,\n  dequantizeFloat16,\n  float16ToFloat32,\n  type QuantizationMethod,\n  type AdvancedQuantizationOptions,\n  type QuantizationProgress,\n  type AdvancedQuantizationResult,\n  type LayerQuantizationStats,\n  type QuantizationStats,\n  type AdvancedPruningOptions,\n  type AdvancedPruningResult,\n  type DetailedModelAnalysis,\n  type ExportFormat,\n  type ExportOptions,\n  \n  // Debugging Tools\n  EdgeFlowDebugger,\n  getDebugger,\n  enableDebugging,\n  disableDebugging,\n  inspectTensor,\n  formatTensorInspection,\n  createAsciiHistogram,\n  createTensorHeatmap,\n  visualizeModelArchitecture,\n  type DebuggerConfig,\n  type TensorInspection,\n  type TensorStats,\n  type HistogramData,\n  type InferenceTrace,\n  type OperationTrace,\n  type DebugEvent,\n  type DebugPerformanceMetrics,\n  \n  // Performance Monitor\n  PerformanceMonitor,\n  getMonitor,\n  startMonitoring,\n  stopMonitoring,\n  generateDashboardHTML,\n  generateAsciiDashboard,\n  type MonitorConfig,\n  type PerformanceSample,\n  type InferenceMetrics,\n  type MemoryMetrics,\n  type SystemMetrics,\n  type AlertConfig,\n  type AlertEvent,\n  type WidgetData,\n  \n  // Benchmark utilities\n  runBenchmark,\n  compareBenchmarks,\n  benchmarkSuite,\n  benchmarkMemory,\n  formatBenchmarkResult,\n  formatComparisonResult,\n  type DetailedBenchmarkOptions,\n  type DetailedBenchmarkResult,\n  type CompareBenchmarkResult,\n  type MemoryBenchmarkResult,\n} from './tools/index.js';\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Check if edgeFlow is supported in the current environment\n */\nexport async function isSupported(): Promise<boolean> {\n  const runtimes = await getAvailableRuntimes();\n  return Array.from(runtimes.values()).some(v => v);\n}\n\n/**\n * Get the best available runtime type\n */\nexport async function getBestRuntimeType(): Promise<RuntimeType | null> {\n  const runtimes = await getAvailableRuntimes();\n  \n  if (runtimes.get('webgpu')) return 'webgpu';\n  if (runtimes.get('webnn')) return 'webnn';\n  if (runtimes.get('wasm')) return 'wasm';\n  \n  return null;\n}\n\n/**\n * Preload models for faster subsequent loading\n */\nexport async function preload(\n  models: string[]\n): Promise<void> {\n  const cache = new ModelDownloadCache();\n  \n  await Promise.all(models.map(async (url) => {\n    if (!(await cache.get(url))) {\n      const response = await fetch(url);\n      if (response.ok) {\n        await cache.put(url, response);\n      }\n    }\n  }));\n}\n\n// ============================================================================\n// Version Info\n// ============================================================================\n\n/**\n * edgeFlow.js version\n */\nexport const VERSION = '0.1.0';\n\n/**\n * Get framework info\n */\nexport async function getInfo(): Promise<{\n  version: string;\n  runtimes: Record<RuntimeType, boolean>;\n  features: string[];\n}> {\n  const runtimes = await getAvailableRuntimes();\n  \n  return {\n    version: VERSION,\n    runtimes: {\n      webgpu: runtimes.get('webgpu') ?? false,\n      webnn: runtimes.get('webnn') ?? false,\n      wasm: runtimes.get('wasm') ?? false,\n      auto: true,\n    },\n    features: [\n      'concurrent-execution',\n      'batch-processing',\n      'memory-management',\n      'model-caching',\n      'quantization',\n    ],\n  };\n}\n\n// Re-export RuntimeType for convenience\nimport { RuntimeType } from './core/types.js';\nimport { getAvailableRuntimes } from './core/runtime.js';\nimport { ModelDownloadCache } from './utils/cache.js';\n"
  },
  {
    "path": "src/pipelines/automatic-speech-recognition.ts",
    "content": "/**\n * edgeFlow.js - Automatic Speech Recognition Pipeline\n * \n * Transcribe audio to text using Whisper ONNX models (encoder + decoder).\n */\n\nimport { BasePipeline, PipelineResult, registerPipeline } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions, LoadedModel } from '../core/types.js';\nimport { AudioPreprocessor, type AudioInput } from '../utils/preprocessor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference, runInferenceNamed } from '../core/runtime.js';\n\n// ============================================================================\n// Default Model (Whisper-tiny, quantized encoder + decoder)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  encoder: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/encoder_model_quantized.onnx',\n  decoder: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/onnx/decoder_model_merged_quantized.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/whisper-tiny/resolve/main/tokenizer.json',\n};\n\n// Whisper special tokens\nconst SOT_TOKEN = 50258;           // <|startoftranscript|>\nconst TRANSLATE_TOKEN = 50358;     // <|translate|>\nconst TRANSCRIBE_TOKEN = 50359;    // <|transcribe|>\nconst EOT_TOKEN = 50257;           // <|endoftext|>\nconst NO_TIMESTAMPS_TOKEN = 50363; // <|notimestamps|>\nconst EN_TOKEN = 50259;            // <|en|>\n\nconst MAX_DECODER_TOKENS = 448;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ASROptions extends PipelineOptions {\n  language?: string;\n  task?: 'transcribe' | 'translate';\n  returnTimestamps?: boolean | 'word' | 'chunk';\n  maxDuration?: number;\n  chunkDuration?: number;\n  chunkOverlap?: number;\n}\n\nexport interface WordTimestamp {\n  word: string;\n  start: number;\n  end: number;\n  confidence?: number;\n}\n\nexport interface ChunkTimestamp {\n  text: string;\n  start: number;\n  end: number;\n}\n\nexport interface ASRResult extends PipelineResult {\n  text: string;\n  language?: string;\n  words?: WordTimestamp[];\n  chunks?: ChunkTimestamp[];\n}\n\n// ============================================================================\n// ASR Pipeline\n// ============================================================================\n\nexport class AutomaticSpeechRecognitionPipeline extends BasePipeline<AudioInput | AudioInput[], ASRResult | ASRResult[]> {\n  private audioPreprocessor: AudioPreprocessor;\n  private tokenizer: Tokenizer | null = null;\n  private encoderModel: LoadedModel | null = null;\n  private decoderModel: LoadedModel | null = null;\n  private encoderUrl: string;\n  private decoderUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config?: PipelineConfig) {\n    super(config ?? {\n      task: 'automatic-speech-recognition',\n      model: 'default',\n    });\n\n    this.encoderUrl = DEFAULT_MODELS.encoder;\n    this.decoderUrl = DEFAULT_MODELS.decoder;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n\n    this.audioPreprocessor = new AudioPreprocessor({\n      sampleRate: 16000,\n      nMels: 80,\n      nFft: 400,\n      hopLength: 160,\n      maxDuration: 30,\n    });\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n\n    if (!this.encoderModel) {\n      const data = await loadModelData(this.encoderUrl, { cache: this.config.cache ?? true });\n      this.encoderModel = await loadModelFromBuffer(data);\n    }\n\n    if (!this.decoderModel) {\n      const data = await loadModelData(this.decoderUrl, { cache: this.config.cache ?? true });\n      this.decoderModel = await loadModelFromBuffer(data);\n    }\n  }\n\n  setTokenizer(tokenizer: Tokenizer): void {\n    this.tokenizer = tokenizer;\n  }\n\n  override async run(\n    input: AudioInput | AudioInput[],\n    options?: PipelineOptions\n  ): Promise<ASRResult | ASRResult[]> {\n    await this.initialize();\n\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    const opts = options as ASROptions ?? {};\n\n    const results: ASRResult[] = [];\n    for (const audio of inputs) {\n      const result = await this.transcribeSingle(audio, opts);\n      results.push(result);\n    }\n\n    return isBatch ? results : results[0]!;\n  }\n\n  private async transcribeSingle(audio: AudioInput, options: ASROptions): Promise<ASRResult> {\n    const startTime = performance.now();\n\n    // 1. Preprocess audio → mel spectrogram\n    const melTensor = await this.audioPreprocessor.process(audio);\n    const melInput = new EdgeFlowTensor(\n      melTensor.toFloat32Array(),\n      [1, ...melTensor.shape],\n      'float32'\n    );\n\n    // 2. Run encoder\n    const encoderOutputs = await runInference(this.encoderModel!, [melInput]);\n    const encoderHidden = encoderOutputs[0] as EdgeFlowTensor;\n\n    // 3. Autoregressive decoder loop\n    const task = options.task ?? 'transcribe';\n    const initialTokens = this.buildInitialTokens(task, options.language);\n\n    const generatedTokens = await this.autoregressiveDecode(\n      encoderHidden,\n      initialTokens,\n    );\n\n    // 4. Decode tokens to text\n    const text = this.tokenizer!.decode(generatedTokens, true);\n\n    const result: ASRResult = {\n      text: text.trim(),\n      processingTime: performance.now() - startTime,\n    };\n\n    if (options.returnTimestamps) {\n      result.chunks = this.extractTimestamps(generatedTokens, text);\n    }\n\n    return result;\n  }\n\n  private buildInitialTokens(task: 'transcribe' | 'translate', language?: string): number[] {\n    const tokens = [SOT_TOKEN];\n    tokens.push(language ? this.getLanguageToken(language) : EN_TOKEN);\n    tokens.push(task === 'translate' ? TRANSLATE_TOKEN : TRANSCRIBE_TOKEN);\n    tokens.push(NO_TIMESTAMPS_TOKEN);\n    return tokens;\n  }\n\n  private getLanguageToken(language: string): number {\n    // Whisper language tokens start at 50259 for English\n    const langMap: Record<string, number> = {\n      en: 50259, zh: 50260, de: 50261, es: 50262, ru: 50263,\n      ko: 50264, fr: 50265, ja: 50266, pt: 50267, tr: 50268,\n      pl: 50269, ca: 50270, nl: 50271, ar: 50272, sv: 50273,\n      it: 50274, id: 50275, hi: 50276, fi: 50277, vi: 50278,\n    };\n    return langMap[language.toLowerCase()] ?? EN_TOKEN;\n  }\n\n  /**\n   * Autoregressive decoder loop similar to text-generation.\n   * Feeds encoder hidden states + growing token sequence to decoder.\n   */\n  private async autoregressiveDecode(\n    encoderHidden: EdgeFlowTensor,\n    initialTokens: number[],\n  ): Promise<number[]> {\n    const tokens = [...initialTokens];\n\n    for (let step = 0; step < MAX_DECODER_TOKENS; step++) {\n      const decoderInputIds = new EdgeFlowTensor(\n        BigInt64Array.from(tokens.map(t => BigInt(t))),\n        [1, tokens.length],\n        'int64'\n      );\n\n      const namedInputs = new Map<string, EdgeFlowTensor>();\n      namedInputs.set('input_ids', decoderInputIds);\n      namedInputs.set('encoder_hidden_states', encoderHidden);\n\n      const decoderOutputs = await runInferenceNamed(this.decoderModel!, namedInputs);\n      const logits = (decoderOutputs[0] as EdgeFlowTensor).toFloat32Array();\n\n      // Get logits for the last token position\n      const vocabSize = logits.length / tokens.length;\n      const lastTokenLogits = logits.slice((tokens.length - 1) * vocabSize);\n\n      // Greedy: argmax\n      let bestId = 0;\n      let bestVal = lastTokenLogits[0] ?? -Infinity;\n      for (let i = 1; i < lastTokenLogits.length; i++) {\n        if ((lastTokenLogits[i] ?? -Infinity) > bestVal) {\n          bestVal = lastTokenLogits[i] ?? -Infinity;\n          bestId = i;\n        }\n      }\n\n      if (bestId === EOT_TOKEN) break;\n\n      tokens.push(bestId);\n    }\n\n    // Strip initial tokens to return only generated tokens\n    return tokens.slice(initialTokens.length);\n  }\n\n  private extractTimestamps(\n    _tokenIds: number[],\n    text: string\n  ): ChunkTimestamp[] {\n    // Simplified timestamp extraction: split by punctuation\n    const words = text.split(/\\s+/).filter(w => w.length > 0);\n    const chunks: ChunkTimestamp[] = [];\n\n    const wordsPerSecond = 2.5;\n    let chunkText = '';\n    let chunkStart = 0;\n\n    for (let i = 0; i < words.length; i++) {\n      chunkText += (chunkText ? ' ' : '') + words[i];\n\n      if ((i + 1) % 5 === 0 || i === words.length - 1) {\n        const duration = chunkText.split(/\\s+/).length / wordsPerSecond;\n        chunks.push({\n          text: chunkText,\n          start: chunkStart,\n          end: chunkStart + duration,\n        });\n        chunkStart = chunkStart + duration;\n        chunkText = '';\n      }\n    }\n\n    return chunks;\n  }\n\n  async processLongAudio(\n    audio: AudioInput,\n    options: ASROptions = {}\n  ): Promise<ASRResult> {\n    const chunkDuration = options.chunkDuration ?? 30;\n    const chunkOverlap = options.chunkOverlap ?? 5;\n\n    const rawTensor = await this.audioPreprocessor.processRaw(audio);\n    const audioData = rawTensor.toFloat32Array();\n    const sampleRate = 16000;\n\n    const chunkSamples = chunkDuration * sampleRate;\n    const overlapSamples = chunkOverlap * sampleRate;\n    const stepSamples = chunkSamples - overlapSamples;\n\n    const chunks: ASRResult[] = [];\n\n    for (let start = 0; start < audioData.length; start += stepSamples) {\n      const end = Math.min(start + chunkSamples, audioData.length);\n      const chunkAudio = audioData.slice(start, end);\n\n      const chunkResult = await this.run(\n        new Float32Array(chunkAudio),\n        options\n      ) as ASRResult;\n\n      if (chunkResult.chunks) {\n        const timeOffset = start / sampleRate;\n        chunkResult.chunks = chunkResult.chunks.map(c => ({\n          ...c,\n          start: c.start + timeOffset,\n          end: c.end + timeOffset,\n        }));\n      }\n\n      chunks.push(chunkResult);\n    }\n\n    const mergedText = chunks.map(c => c.text).join(' ');\n    const mergedChunks = chunks.flatMap(c => c.chunks ?? []);\n\n    return {\n      text: mergedText,\n      chunks: mergedChunks,\n    };\n  }\n\n  protected async preprocess(input: AudioInput | AudioInput[]): Promise<EdgeFlowTensor[]> {\n    const inputs = Array.isArray(input) ? input : [input];\n\n    const tensors = await Promise.all(\n      inputs.map(audio => this.audioPreprocessor.process(audio))\n    );\n\n    if (tensors.length === 1) {\n      const t = tensors[0]!;\n      return [new EdgeFlowTensor(\n        t.toFloat32Array(),\n        [1, ...t.shape],\n        'float32'\n      )];\n    }\n\n    return tensors;\n  }\n\n  protected async postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: PipelineOptions\n  ): Promise<ASRResult | ASRResult[]> {\n    const opts = options as ASROptions ?? {};\n    const returnTimestamps = opts.returnTimestamps ?? false;\n\n    if (!outputs[0]) {\n      return { text: '' };\n    }\n\n    const outputData = outputs[0].toFloat32Array();\n    const shape = outputs[0].shape;\n\n    const text = this.decodeOutput(outputData, shape);\n\n    const result: ASRResult = { text };\n\n    if (returnTimestamps) {\n      result.chunks = this.extractTimestamps([], text);\n    }\n\n    return result;\n  }\n\n  private decodeOutput(data: Float32Array, shape: readonly number[]): string {\n    const seqLen = shape[1] ?? data.length;\n    const vocabSize = shape[2] ?? 1;\n\n    const tokenIds: number[] = [];\n\n    if (vocabSize > 1) {\n      for (let i = 0; i < seqLen; i++) {\n        const offset = i * vocabSize;\n        let maxIdx = 0;\n        let maxVal = data[offset] ?? -Infinity;\n\n        for (let j = 1; j < vocabSize; j++) {\n          if ((data[offset + j] ?? -Infinity) > maxVal) {\n            maxVal = data[offset + j] ?? -Infinity;\n            maxIdx = j;\n          }\n        }\n        tokenIds.push(maxIdx);\n      }\n    } else {\n      for (let i = 0; i < data.length; i++) {\n        tokenIds.push(Math.round(data[i] ?? 0));\n      }\n    }\n\n    if (this.tokenizer) {\n      return this.tokenizer.decode(tokenIds, true);\n    }\n\n    return tokenIds.join(' ');\n  }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createASRPipeline(config?: PipelineConfig): AutomaticSpeechRecognitionPipeline {\n  return new AutomaticSpeechRecognitionPipeline(config);\n}\n\nregisterPipeline('automatic-speech-recognition', (config) => new AutomaticSpeechRecognitionPipeline(config));\n"
  },
  {
    "path": "src/pipelines/base.ts",
    "content": "/**\n * edgeFlow.js - Base Pipeline\n * \n * Base class and utilities for all pipeline implementations.\n */\n\nimport {\n  LoadedModel,\n  PipelineConfig,\n  PipelineOptions,\n  PipelineTask,\n} from '../core/types.js';\nimport { loadModel, runInference } from '../core/runtime.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { ModelCache } from '../core/memory.js';\nimport { ModelDownloadCache } from '../utils/cache.js';\n\n// ============================================================================\n// Pipeline Types\n// ============================================================================\n\n/**\n * Pipeline result base interface\n */\nexport interface PipelineResult {\n  /** Processing time in milliseconds */\n  processingTime?: number;\n}\n\n/**\n * Text classification result\n */\nexport interface TextClassificationResult extends PipelineResult {\n  label: string;\n  score: number;\n}\n\n/**\n * Feature extraction result\n */\nexport interface FeatureExtractionResult extends PipelineResult {\n  embeddings: number[];\n}\n\n/**\n * Image classification result\n */\nexport interface ImageClassificationResult extends PipelineResult {\n  label: string;\n  score: number;\n}\n\n/**\n * Object detection result\n */\nexport interface ObjectDetectionResult extends PipelineResult {\n  label: string;\n  score: number;\n  box: { x: number; y: number; width: number; height: number };\n}\n\n// ============================================================================\n// Base Pipeline Class\n// ============================================================================\n\n/**\n * BasePipeline - Abstract base class for all pipelines\n */\nexport abstract class BasePipeline<TInput, TOutput extends PipelineResult | PipelineResult[]> {\n  protected model: LoadedModel | null = null;\n  protected readonly config: PipelineConfig;\n  protected readonly modelCache: ModelCache;\n  protected readonly downloadCache: ModelDownloadCache;\n  protected isReady = false;\n\n  constructor(config: PipelineConfig) {\n    this.config = config;\n    this.modelCache = new ModelCache();\n    this.downloadCache = new ModelDownloadCache();\n  }\n\n  /**\n   * Initialize the pipeline (load model).\n   *\n   * Skips model loading when `config.model === 'default'` — concrete\n   * subclasses that define their own DEFAULT_MODELS handle all model\n   * loading in their overridden `initialize()` methods, so the base\n   * should not attempt to fetch a URL called \"default\".\n   */\n  async initialize(): Promise<void> {\n    if (this.isReady && this.model) return;\n\n    // Skip generic model loading for subclasses that manage their own models.\n    if (this.config.model === 'default') {\n      this.isReady = true;\n      return;\n    }\n\n    // Check model cache first\n    const cachedModel = this.modelCache.get(this.config.model);\n    if (cachedModel) {\n      this.model = cachedModel;\n      this.isReady = true;\n      return;\n    }\n\n    // Load model using the explicit URL from config\n    this.model = await this.loadModelWithCache(this.config.model);\n    this.isReady = true;\n  }\n\n  /**\n   * Load model with caching\n   */\n  protected async loadModelWithCache(modelPath: string): Promise<LoadedModel> {\n    // Try download cache first\n    const cachedResponse = await this.downloadCache.get(modelPath);\n    if (cachedResponse) {\n      // Use cached data\n    }\n\n    // Download and cache (or use mock for now)\n    try {\n      const response = await fetch(modelPath);\n      if (response.ok) {\n        // Cache the response\n        await this.downloadCache.put(modelPath, response.clone());\n      }\n    } catch {\n      // Ignore fetch errors for demo\n    }\n\n    // Load into runtime\n    return loadModel(modelPath, {\n      runtime: this.config.runtime,\n      quantization: this.config.quantization,\n      cache: this.config.cache,\n    });\n  }\n\n  /**\n   * Run inference (single input)\n   */\n  async run(input: TInput, options?: PipelineOptions): Promise<TOutput> {\n    await this.initialize();\n    \n    const startTime = performance.now();\n    \n    // Preprocess\n    const preprocessed = await this.preprocess(input);\n    \n    // Run inference\n    const outputs = await runInference(this.model!, preprocessed);\n    \n    // Postprocess\n    const result = await this.postprocess(outputs as EdgeFlowTensor[], options);\n    \n    if (result && typeof result === 'object' && 'processingTime' in result) {\n      (result as PipelineResult).processingTime = performance.now() - startTime;\n    }\n    \n    return result;\n  }\n\n  /**\n   * Run batch inference\n   */\n  async runBatch(inputs: TInput[], options?: PipelineOptions): Promise<TOutput[]> {\n    await this.initialize();\n    \n    // Process all inputs\n    const results = await Promise.all(\n      inputs.map(input => this.run(input, options))\n    );\n    \n    return results;\n  }\n\n  /**\n   * Preprocess input - must be implemented by subclasses\n   */\n  protected abstract preprocess(input: TInput): Promise<EdgeFlowTensor[]>;\n\n  /**\n   * Postprocess output - must be implemented by subclasses\n   */\n  protected abstract postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: PipelineOptions\n  ): Promise<TOutput>;\n\n  /**\n   * Get the task type\n   */\n  get task(): PipelineTask {\n    return this.config.task;\n  }\n\n  /**\n   * Check if pipeline is ready\n   */\n  get ready(): boolean {\n    return this.isReady;\n  }\n\n  /**\n   * Dispose the pipeline\n   */\n  dispose(): void {\n    if (this.model) {\n      this.model.dispose();\n      this.model = null;\n    }\n    this.isReady = false;\n  }\n}\n\n// ============================================================================\n// Pipeline Registry\n// ============================================================================\n\n/**\n * Pipeline factory function type\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PipelineFactory = (config: PipelineConfig) => BasePipeline<any, any>;\n\n/**\n * Registered pipeline factories\n */\nconst pipelineFactories: Map<PipelineTask, PipelineFactory> = new Map();\n\n/**\n * Register a pipeline factory\n */\nexport function registerPipeline(task: PipelineTask, factory: PipelineFactory): void {\n  pipelineFactories.set(task, factory);\n}\n\n/**\n * Get a pipeline factory\n */\nexport function getPipelineFactory(task: PipelineTask): PipelineFactory | undefined {\n  return pipelineFactories.get(task);\n}\n\n// ============================================================================\n// Default Label Maps\n// ============================================================================\n\n/**\n * Common sentiment labels\n */\nexport const SENTIMENT_LABELS = ['negative', 'positive'];\n\n/**\n * Common emotion labels\n */\nexport const EMOTION_LABELS = [\n  'anger', 'disgust', 'fear', 'joy', 'sadness', 'surprise', 'neutral'\n];\n\n/**\n * ImageNet top-10 labels (for demo)\n */\nexport const IMAGENET_LABELS = [\n  'tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead',\n  'electric ray', 'stingray', 'cock', 'hen', 'ostrich'\n];\n"
  },
  {
    "path": "src/pipelines/feature-extraction.ts",
    "content": "/**\n * edgeFlow.js - Feature Extraction Pipeline\n * \n * Extract embeddings/features from text using sentence-transformer models.\n */\n\nimport {\n  PipelineConfig,\n  PipelineOptions,\n  LoadedModel,\n} from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\nimport {\n  BasePipeline,\n  FeatureExtractionResult,\n  registerPipeline,\n} from './base.js';\n\n// ============================================================================\n// Default Model (all-MiniLM-L6-v2, 384-dim sentence embeddings)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model_quantized.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/tokenizer.json',\n};\n\nconst DEFAULT_EMBEDDING_DIM = 384;\n\n// ============================================================================\n// Feature Extraction Pipeline\n// ============================================================================\n\nexport interface FeatureExtractionOptions extends PipelineOptions {\n  pooling?: 'mean' | 'max' | 'cls' | 'none';\n  normalize?: boolean;\n  outputDim?: number;\n}\n\nexport class FeatureExtractionPipeline extends BasePipeline<\n  string | string[],\n  FeatureExtractionResult | FeatureExtractionResult[]\n> {\n  private tokenizer: Tokenizer | null = null;\n  private onnxModel: LoadedModel | null = null;\n  private embeddingDim: number;\n  private modelUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config: PipelineConfig, embeddingDim: number = DEFAULT_EMBEDDING_DIM) {\n    super(config);\n    this.embeddingDim = embeddingDim;\n    this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  override async run(\n    input: string | string[],\n    options?: FeatureExtractionOptions\n  ): Promise<FeatureExtractionResult | FeatureExtractionResult[]> {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    \n    await this.initialize();\n    \n    const startTime = performance.now();\n    const results: FeatureExtractionResult[] = [];\n\n    for (const text of inputs) {\n      const tensorInputs = await this.preprocess(text);\n      const outputs = await this.runInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n\n    return isBatch ? results : results[0]!;\n  }\n\n  protected override async preprocess(input: string | string[]): Promise<EdgeFlowTensor[]> {\n    const text = Array.isArray(input) ? input[0]! : input;\n    \n    const encoded = this.tokenizer!.encode(text, {\n      maxLength: 128,\n      padding: 'max_length',\n      truncation: true,\n    });\n\n    const inputIds = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, encoded.inputIds.length],\n      'int64'\n    );\n\n    const attentionMask = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))),\n      [1, encoded.attentionMask.length],\n      'int64'\n    );\n\n    const tokenTypeIds = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(() => BigInt(0))),\n      [1, encoded.inputIds.length],\n      'int64'\n    );\n\n    return [inputIds, attentionMask, tokenTypeIds];\n  }\n\n  private async runInference(inputs: EdgeFlowTensor[]): Promise<EdgeFlowTensor[]> {\n    const namedInputs = new Map<string, EdgeFlowTensor>();\n    namedInputs.set('input_ids', inputs[0]!);\n    namedInputs.set('attention_mask', inputs[1]!);\n    namedInputs.set('token_type_ids', inputs[2]!);\n\n    const outputs = await runInferenceNamed(this.onnxModel!, namedInputs);\n    return outputs as EdgeFlowTensor[];\n  }\n\n  protected override async postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: FeatureExtractionOptions\n  ): Promise<FeatureExtractionResult> {\n    const hiddenStates = outputs[0];\n    if (!hiddenStates) {\n      return { embeddings: [] };\n    }\n\n    const pooling = options?.pooling ?? 'mean';\n    const normalize = options?.normalize ?? true;\n\n    let embeddings: number[];\n\n    switch (pooling) {\n      case 'cls':\n        embeddings = this.extractCLSEmbedding(hiddenStates);\n        break;\n      case 'max':\n        embeddings = this.maxPooling(hiddenStates);\n        break;\n      case 'none':\n        embeddings = hiddenStates.toArray();\n        break;\n      case 'mean':\n      default:\n        embeddings = this.meanPooling(hiddenStates);\n        break;\n    }\n\n    if (normalize) {\n      embeddings = this.normalizeVector(embeddings);\n    }\n\n    if (options?.outputDim && options.outputDim < embeddings.length) {\n      embeddings = embeddings.slice(0, options.outputDim);\n    }\n\n    return { embeddings };\n  }\n\n  private extractCLSEmbedding(hiddenStates: EdgeFlowTensor): number[] {\n    const data = hiddenStates.toFloat32Array();\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    return Array.from(data.slice(0, embeddingDim));\n  }\n\n  private meanPooling(hiddenStates: EdgeFlowTensor): number[] {\n    const data = hiddenStates.toFloat32Array();\n    const seqLen = hiddenStates.shape[1] ?? 1;\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    \n    const result = new Float32Array(embeddingDim);\n    for (let i = 0; i < seqLen; i++) {\n      for (let j = 0; j < embeddingDim; j++) {\n        result[j] = (result[j] ?? 0) + (data[i * embeddingDim + j] ?? 0) / seqLen;\n      }\n    }\n    return Array.from(result);\n  }\n\n  private maxPooling(hiddenStates: EdgeFlowTensor): number[] {\n    const data = hiddenStates.toFloat32Array();\n    const seqLen = hiddenStates.shape[1] ?? 1;\n    const embeddingDim = hiddenStates.shape[2] ?? this.embeddingDim;\n    \n    const result = new Array(embeddingDim).fill(-Infinity) as number[];\n    for (let i = 0; i < seqLen; i++) {\n      for (let j = 0; j < embeddingDim; j++) {\n        const val = data[i * embeddingDim + j] ?? 0;\n        if (val > (result[j] ?? -Infinity)) {\n          result[j] = val;\n        }\n      }\n    }\n    return result;\n  }\n\n  private normalizeVector(vec: number[]): number[] {\n    let norm = 0;\n    for (const v of vec) {\n      norm += v * v;\n    }\n    norm = Math.sqrt(norm);\n    if (norm === 0) return vec;\n    return vec.map(v => v / norm);\n  }\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\nexport function createFeatureExtractionPipeline(\n  config: Partial<PipelineConfig> = {}\n): FeatureExtractionPipeline {\n  return new FeatureExtractionPipeline({\n    task: 'feature-extraction',\n    model: config.model ?? 'default',\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization,\n  });\n}\n\nregisterPipeline('feature-extraction', (config) => new FeatureExtractionPipeline(config));\n"
  },
  {
    "path": "src/pipelines/image-classification.ts",
    "content": "/**\n * edgeFlow.js - Image Classification Pipeline\n * \n * Classify images into categories using vision models.\n */\n\nimport {\n  PipelineConfig,\n  PipelineOptions,\n  LoadedModel,\n} from '../core/types.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { ImagePreprocessor, createImagePreprocessor } from '../utils/preprocessor.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference } from '../core/runtime.js';\nimport {\n  BasePipeline,\n  ImageClassificationResult,\n  registerPipeline,\n  IMAGENET_LABELS,\n} from './base.js';\n\n// ============================================================================\n// Default Model (MobileViT-small, quantized)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/mobilevit-small/resolve/main/onnx/model_quantized.onnx',\n};\n\n\n// ============================================================================\n// Image Classification Pipeline\n// ============================================================================\n\nexport interface ImageClassificationOptions extends PipelineOptions {\n  returnAllScores?: boolean;\n  labels?: string[];\n  topK?: number;\n}\n\nexport type ImageInput =\n  | HTMLImageElement\n  | HTMLCanvasElement\n  | ImageBitmap\n  | ImageData\n  | string;\n\nexport class ImageClassificationPipeline extends BasePipeline<\n  ImageInput | ImageInput[],\n  ImageClassificationResult | ImageClassificationResult[]\n> {\n  private preprocessor: ImagePreprocessor | null = null;\n  private onnxModel: LoadedModel | null = null;\n  private labels: string[];\n  private modelUrl: string;\n\n  constructor(\n    config: PipelineConfig,\n    labels?: string[],\n    _numClasses: number = 1000\n  ) {\n    super(config);\n    this.labels = labels ?? IMAGENET_LABELS;\n    this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.preprocessor) {\n      this.preprocessor = createImagePreprocessor('imagenet');\n    }\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  setLabels(labels: string[]): void {\n    this.labels = labels;\n  }\n\n  override async run(\n    input: ImageInput | ImageInput[],\n    options?: ImageClassificationOptions\n  ): Promise<ImageClassificationResult | ImageClassificationResult[]> {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n\n    await this.initialize();\n\n    const startTime = performance.now();\n    const results: ImageClassificationResult[] = [];\n\n    for (const image of inputs) {\n      const tensorInputs = await this.preprocess(image);\n      const outputs = await this.runModelInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n\n    return isBatch ? results : results[0]!;\n  }\n\n  protected override async preprocess(input: ImageInput | ImageInput[]): Promise<EdgeFlowTensor[]> {\n    const image = Array.isArray(input) ? input[0]! : input;\n    const tensor = await this.preprocessor!.process(image);\n\n    if (tensor.shape.length === 3) {\n      return [tensor.reshape([1, ...tensor.shape])];\n    }\n    return [tensor];\n  }\n\n  private async runModelInference(inputs: EdgeFlowTensor[]): Promise<EdgeFlowTensor[]> {\n    const outputs = await runInference(this.onnxModel!, inputs);\n    return outputs as EdgeFlowTensor[];\n  }\n\n  protected override async postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: ImageClassificationOptions\n  ): Promise<ImageClassificationResult> {\n    const logits = outputs[0];\n    if (!logits) {\n      return { label: 'unknown', score: 0 };\n    }\n\n    const probs = softmax(logits, -1) as EdgeFlowTensor;\n    const probsArray = probs.toFloat32Array();\n\n    let maxIdx = 0;\n    let maxScore = probsArray[0] ?? 0;\n\n    for (let i = 1; i < probsArray.length; i++) {\n      if ((probsArray[i] ?? 0) > maxScore) {\n        maxScore = probsArray[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n\n    const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n\n    return { label, score: maxScore };\n  }\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\nexport function createImageClassificationPipeline(\n  config: Partial<PipelineConfig> = {},\n  labels?: string[]\n): ImageClassificationPipeline {\n  return new ImageClassificationPipeline(\n    {\n      task: 'image-classification',\n      model: config.model ?? 'default',\n      runtime: config.runtime,\n      cache: config.cache ?? true,\n      quantization: config.quantization,\n    },\n    labels\n  );\n}\n\nregisterPipeline('image-classification', (config) => new ImageClassificationPipeline(config));\n"
  },
  {
    "path": "src/pipelines/image-segmentation.ts",
    "content": "/**\n * edgeFlow.js - Image Segmentation Pipeline\n * \n * Interactive image segmentation using SAM (Segment Anything Model).\n * Supports point prompts and bounding box prompts.\n */\n\nimport {\n  PipelineConfig,\n  PipelineOptions,\n  LoadedModel,\n} from '../core/types.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { BasePipeline, PipelineResult, registerPipeline } from './base.js';\nimport { loadModel, loadModelFromBuffer, runInference, runInferenceNamed } from '../core/runtime.js';\n\n// ============================================================================\n// Default Model URLs (SlimSAM - quantized for browser)\n// ============================================================================\n\nconst DEFAULT_SAM_MODELS = {\n  encoder: 'https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/vision_encoder_quantized.onnx',\n  decoder: 'https://huggingface.co/Xenova/slimsam-77-uniform/resolve/main/onnx/prompt_encoder_mask_decoder_quantized.onnx',\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Point prompt for segmentation\n */\nexport interface PointPrompt {\n  /** X coordinate (0-1 normalized) */\n  x: number;\n  /** Y coordinate (0-1 normalized) */\n  y: number;\n  /** 1 for foreground (include), 0 for background (exclude) */\n  label: 0 | 1;\n}\n\n/**\n * Box prompt for segmentation\n */\nexport interface BoxPrompt {\n  /** Top-left X (0-1 normalized) */\n  x1: number;\n  /** Top-left Y (0-1 normalized) */\n  y1: number;\n  /** Bottom-right X (0-1 normalized) */\n  x2: number;\n  /** Bottom-right Y (0-1 normalized) */\n  y2: number;\n}\n\n/**\n * Model loading progress callback\n */\nexport interface ModelLoadProgress {\n  /** Model name (encoder or decoder) */\n  model: 'encoder' | 'decoder';\n  /** Bytes loaded */\n  loaded: number;\n  /** Total bytes */\n  total: number;\n  /** Progress percentage (0-100) */\n  progress: number;\n}\n\n/**\n * Segmentation options\n */\nexport interface ImageSegmentationOptions extends PipelineOptions {\n  /** Point prompts */\n  points?: PointPrompt[];\n  /** Box prompts */\n  boxes?: BoxPrompt[];\n  /** Return all masks or just the best one */\n  returnAllMasks?: boolean;\n  /** Mask threshold (0-1) */\n  maskThreshold?: number;\n}\n\n/**\n * Segmentation result\n */\nexport interface ImageSegmentationResult extends PipelineResult {\n  /** Segmentation mask (Uint8Array, 0 or 255) */\n  mask: Uint8Array;\n  /** Mask width */\n  width: number;\n  /** Mask height */\n  height: number;\n  /** Confidence score */\n  score: number;\n  /** All masks if returnAllMasks is true */\n  allMasks?: Array<{ mask: Uint8Array; score: number }>;\n}\n\n/**\n * Image input types\n */\nexport type ImageInput =\n  | HTMLImageElement\n  | HTMLCanvasElement\n  | ImageBitmap\n  | ImageData\n  | string; // URL or base64\n\n// ============================================================================\n// Image Segmentation Pipeline\n// ============================================================================\n\n/**\n * ImageSegmentationPipeline - Interactive image segmentation\n * \n * Uses SAM-style models for point/box prompted segmentation.\n * \n * @example\n * ```typescript\n * const segmenter = createImageSegmentationPipeline();\n * \n * // Load models with progress callback\n * await segmenter.loadModels((progress) => {\n *   console.log(`Loading ${progress.model}: ${progress.progress}%`);\n * });\n * \n * // Set image and segment\n * await segmenter.setImage(imageElement);\n * const result = await segmenter.segment({\n *   points: [{ x: 0.5, y: 0.5, label: 1 }]\n * });\n * ```\n */\nexport class ImageSegmentationPipeline extends BasePipeline<\n  ImageInput,\n  ImageSegmentationResult\n> {\n  private encoderModel: LoadedModel | null = null;\n  private decoderModel: LoadedModel | null = null;\n  private imageEmbedding: EdgeFlowTensor | null = null;\n  private imagePositionalEmbedding: EdgeFlowTensor | null = null;\n  private currentImageSize: { width: number; height: number } | null = null;\n  private resizedImageSize: { width: number; height: number } | null = null;\n  private inputSize: number = 1024; // SAM default input size\n  private modelsLoaded: boolean = false;\n  \n  // Custom model URLs\n  private encoderUrl: string;\n  private decoderUrl: string;\n\n  constructor(config: PipelineConfig) {\n    super(config);\n    this.encoderUrl = DEFAULT_SAM_MODELS.encoder;\n    this.decoderUrl = DEFAULT_SAM_MODELS.decoder;\n  }\n\n  /**\n   * Check if models are loaded\n   */\n  get isModelsLoaded(): boolean {\n    return this.modelsLoaded;\n  }\n\n  /**\n   * Set custom model URLs\n   */\n  setModelUrls(encoder: string, decoder: string): void {\n    this.encoderUrl = encoder;\n    this.decoderUrl = decoder;\n  }\n\n  /**\n   * Load both encoder and decoder models with progress callback\n   */\n  async loadModels(\n    onProgress?: (progress: ModelLoadProgress) => void\n  ): Promise<void> {\n    if (this.modelsLoaded) return;\n\n    // Load encoder\n    onProgress?.({ model: 'encoder', loaded: 0, total: 100, progress: 0 });\n    \n    const encoderData = await this.fetchModelWithProgress(\n      this.encoderUrl,\n      (loaded, total) => {\n        onProgress?.({\n          model: 'encoder',\n          loaded,\n          total,\n          progress: Math.round((loaded / total) * 100),\n        });\n      }\n    );\n    \n    this.encoderModel = await loadModelFromBuffer(encoderData, {\n      runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n\n    // Load decoder\n    onProgress?.({ model: 'decoder', loaded: 0, total: 100, progress: 0 });\n    \n    const decoderData = await this.fetchModelWithProgress(\n      this.decoderUrl,\n      (loaded, total) => {\n        onProgress?.({\n          model: 'decoder',\n          loaded,\n          total,\n          progress: Math.round((loaded / total) * 100),\n        });\n      }\n    );\n    \n    this.decoderModel = await loadModelFromBuffer(decoderData, {\n      runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n\n    this.modelsLoaded = true;\n  }\n\n  /**\n   * Fetch model with progress tracking\n   */\n  private async fetchModelWithProgress(\n    url: string,\n    onProgress: (loaded: number, total: number) => void\n  ): Promise<ArrayBuffer> {\n    const response = await fetch(url);\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n    }\n\n    const contentLength = response.headers.get('content-length');\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n\n    if (!response.body) {\n      // Fallback if no streaming support\n      const buffer = await response.arrayBuffer();\n      onProgress(buffer.byteLength, buffer.byteLength);\n      return buffer;\n    }\n\n    const reader = response.body.getReader();\n    const chunks: Uint8Array[] = [];\n    let loaded = 0;\n\n    while (true) {\n      const { done, value } = await reader.read();\n      \n      if (done) break;\n      \n      chunks.push(value);\n      loaded += value.length;\n      onProgress(loaded, total || loaded);\n    }\n\n    // Combine chunks into ArrayBuffer\n    const buffer = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      buffer.set(chunk, offset);\n      offset += chunk.length;\n    }\n\n    return buffer.buffer;\n  }\n\n  /**\n   * Initialize pipeline (override to skip default model loading)\n   */\n  override async initialize(): Promise<void> {\n    if (this.isReady) return;\n    // Don't call super.initialize() - we handle model loading separately\n    this.isReady = true;\n  }\n\n  /**\n   * Load encoder model (processes the image once)\n   */\n  async loadEncoder(modelUrl: string): Promise<void> {\n    this.encoderModel = await loadModel(modelUrl, {\n      runtime: 'wasm',\n    });\n  }\n\n  /**\n   * Load decoder model (processes prompts to generate masks)\n   */\n  async loadDecoder(modelUrl: string): Promise<void> {\n    this.decoderModel = await loadModel(modelUrl, {\n      runtime: 'wasm',\n    });\n  }\n\n  /**\n   * Set and encode the image (call once per image)\n   */\n  async setImage(image: ImageInput): Promise<void> {\n    if (!this.modelsLoaded) {\n      throw new Error('Models not loaded. Call loadModels() first.');\n    }\n\n    // Get image data\n    const imageData = await this.loadImage(image);\n    this.currentImageSize = {\n      width: imageData.width,\n      height: imageData.height,\n    };\n\n    // Preprocess image for SAM\n    const { tensor: inputTensor, resizedSize } = this.preprocessImage(imageData);\n    this.resizedImageSize = resizedSize;\n\n    // Run encoder\n    if (this.encoderModel) {\n      const outputs = await runInference(this.encoderModel, [inputTensor]);\n      // SlimSAM encoder outputs: [image_embeddings, image_positional_embeddings]\n      this.imageEmbedding = outputs[0] as EdgeFlowTensor;\n      this.imagePositionalEmbedding = outputs[1] as EdgeFlowTensor;\n      console.log('[SAM] Encoder outputs:', outputs.length);\n      console.log('[SAM] image_embeddings shape:', this.imageEmbedding.shape);\n      if (this.imagePositionalEmbedding) {\n        console.log('[SAM] image_positional_embeddings shape:', this.imagePositionalEmbedding.shape);\n      }\n    } else {\n      throw new Error('Encoder model not loaded');\n    }\n  }\n\n  /**\n   * Segment the image with given prompts\n   */\n  async segment(options: ImageSegmentationOptions = {}): Promise<ImageSegmentationResult> {\n    if (!this.imageEmbedding || !this.currentImageSize || !this.resizedImageSize) {\n      throw new Error('No image set. Call setImage() first.');\n    }\n\n    if (!this.decoderModel) {\n      throw new Error('Decoder model not loaded');\n    }\n\n    const startTime = performance.now();\n    const { points = [], boxes = [], maskThreshold = 0.0, returnAllMasks = false } = options;\n\n    // Prepare inputs for decoder\n    const decoderInputs = this.prepareDecoderInputs(points, boxes);\n    \n    // Add image embeddings to inputs\n    decoderInputs.set('image_embeddings', this.imageEmbedding!);\n    \n    // Add positional embeddings (required by SlimSAM)\n    if (this.imagePositionalEmbedding) {\n      decoderInputs.set('image_positional_embeddings', this.imagePositionalEmbedding);\n    } else {\n      throw new Error('image_positional_embeddings not available from encoder');\n    }\n\n    // Run decoder model with named inputs\n    const outputs = await runInferenceNamed(this.decoderModel, decoderInputs);\n\n    // SAM decoder outputs: [masks, iou_predictions]\n    const masks = outputs[0] as EdgeFlowTensor;\n    const scores = outputs[1] as EdgeFlowTensor;\n\n    // Post-process masks\n    const result = this.postprocessMasks(masks, scores, maskThreshold, returnAllMasks);\n    result.processingTime = performance.now() - startTime;\n\n    return result;\n  }\n\n  /**\n   * Run segmentation (implements BasePipeline interface)\n   */\n  override async run(\n    input: ImageInput,\n    options?: ImageSegmentationOptions\n  ): Promise<ImageSegmentationResult> {\n    await this.setImage(input);\n    return this.segment(options);\n  }\n\n  /**\n   * Load image from various sources\n   */\n  private async loadImage(input: ImageInput): Promise<ImageData> {\n    // Handle different input types\n    if (typeof input === 'string') {\n      // URL or base64\n      return this.loadImageFromUrl(input);\n    } else if (input instanceof HTMLImageElement) {\n      return this.imageElementToImageData(input);\n    } else if (input instanceof HTMLCanvasElement) {\n      return this.canvasToImageData(input);\n    } else if (input instanceof ImageData) {\n      return input;\n    } else if (typeof ImageBitmap !== 'undefined' && input instanceof ImageBitmap) {\n      return this.imageBitmapToImageData(input);\n    }\n    throw new Error('Unsupported image input type');\n  }\n\n  /**\n   * Load image from URL\n   */\n  private async loadImageFromUrl(url: string): Promise<ImageData> {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.crossOrigin = 'anonymous';\n      img.onload = () => {\n        const canvas = document.createElement('canvas');\n        canvas.width = img.width;\n        canvas.height = img.height;\n        const ctx = canvas.getContext('2d')!;\n        ctx.drawImage(img, 0, 0);\n        resolve(ctx.getImageData(0, 0, img.width, img.height));\n      };\n      img.onerror = reject;\n      img.src = url;\n    });\n  }\n\n  /**\n   * Convert HTMLImageElement to ImageData\n   */\n  private imageElementToImageData(img: HTMLImageElement): ImageData {\n    const canvas = document.createElement('canvas');\n    canvas.width = img.naturalWidth || img.width;\n    canvas.height = img.naturalHeight || img.height;\n    const ctx = canvas.getContext('2d')!;\n    ctx.drawImage(img, 0, 0);\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n\n  /**\n   * Convert canvas to ImageData\n   */\n  private canvasToImageData(canvas: HTMLCanvasElement): ImageData {\n    const ctx = canvas.getContext('2d')!;\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n\n  /**\n   * Convert ImageBitmap to ImageData\n   */\n  private imageBitmapToImageData(bitmap: ImageBitmap): ImageData {\n    const canvas = document.createElement('canvas');\n    canvas.width = bitmap.width;\n    canvas.height = bitmap.height;\n    const ctx = canvas.getContext('2d')!;\n    ctx.drawImage(bitmap, 0, 0);\n    return ctx.getImageData(0, 0, canvas.width, canvas.height);\n  }\n\n  /**\n   * Preprocess image for SAM\n   */\n  private preprocessImage(imageData: ImageData): { \n    tensor: EdgeFlowTensor; \n    resizedSize: { width: number; height: number };\n  } {\n    const { width, height } = imageData;\n\n    // Calculate resize dimensions (longest side = inputSize)\n    const scale = this.inputSize / Math.max(width, height);\n    const newWidth = Math.round(width * scale);\n    const newHeight = Math.round(height * scale);\n\n    // Create resized canvas with padding\n    const canvas = document.createElement('canvas');\n    canvas.width = this.inputSize;\n    canvas.height = this.inputSize;\n    const ctx = canvas.getContext('2d')!;\n\n    // Fill with padding color (SAM uses pixel mean)\n    ctx.fillStyle = `rgb(123.675, 116.28, 103.53)`;\n    ctx.fillRect(0, 0, this.inputSize, this.inputSize);\n\n    // Draw resized image (top-left aligned)\n    const tempCanvas = document.createElement('canvas');\n    tempCanvas.width = width;\n    tempCanvas.height = height;\n    const tempCtx = tempCanvas.getContext('2d')!;\n    tempCtx.putImageData(imageData, 0, 0);\n    ctx.drawImage(tempCanvas, 0, 0, newWidth, newHeight);\n\n    // Get pixel data\n    const resizedData = ctx.getImageData(0, 0, this.inputSize, this.inputSize);\n\n    // Convert to tensor (NCHW format, normalized with ImageNet mean/std)\n    const tensorData = new Float32Array(3 * this.inputSize * this.inputSize);\n    const mean = [123.675, 116.28, 103.53];\n    const std = [58.395, 57.12, 57.375];\n\n    for (let i = 0; i < this.inputSize * this.inputSize; i++) {\n      const pixelIdx = i * 4;\n      tensorData[i] = (resizedData.data[pixelIdx]! - mean[0]!) / std[0]!; // R\n      tensorData[this.inputSize * this.inputSize + i] =\n        (resizedData.data[pixelIdx + 1]! - mean[1]!) / std[1]!; // G\n      tensorData[2 * this.inputSize * this.inputSize + i] =\n        (resizedData.data[pixelIdx + 2]! - mean[2]!) / std[2]!; // B\n    }\n\n    return {\n      tensor: new EdgeFlowTensor(tensorData, [1, 3, this.inputSize, this.inputSize], 'float32'),\n      resizedSize: { width: newWidth, height: newHeight },\n    };\n  }\n\n  /**\n   * Prepare decoder inputs (prompts) for SlimSAM\n   * \n   * SlimSAM prompt_encoder_mask_decoder expects these named inputs:\n   * - image_embeddings: [1, 256, 64, 64]\n   * - point_coords: [batch, num_points, 2]\n   * - point_labels: [batch, num_points]\n   * - mask_input: [batch, 1, 256, 256]\n   * - has_mask_input: [batch, 1]\n   * - orig_im_size: [2]\n   * - position_ids: [batch, num_points]\n   */\n  private prepareDecoderInputs(\n    points: PointPrompt[],\n    boxes: BoxPrompt[]\n  ): Map<string, EdgeFlowTensor> {\n    const { width: resizedW, height: resizedH } = this.resizedImageSize!;\n\n    // Scale factors for converting normalized coords to resized image coords\n    const scaleX = resizedW;\n    const scaleY = resizedH;\n\n    const allPoints: number[] = [];\n    const allLabels: number[] = [];\n\n    // Add point prompts\n    for (const point of points) {\n      allPoints.push(\n        point.x * scaleX,\n        point.y * scaleY\n      );\n      allLabels.push(point.label);\n    }\n\n    // Add box prompts (as two corner points)\n    for (const box of boxes) {\n      // Top-left corner (label 2)\n      allPoints.push(box.x1 * scaleX, box.y1 * scaleY);\n      allLabels.push(2);\n      // Bottom-right corner (label 3)\n      allPoints.push(box.x2 * scaleX, box.y2 * scaleY);\n      allLabels.push(3);\n    }\n\n    // Default point if no prompts (center of image)\n    if (allPoints.length === 0) {\n      allPoints.push(resizedW / 2, resizedH / 2);\n      allLabels.push(1);\n    }\n\n    const numPoints = allLabels.length;\n\n    const inputs = new Map<string, EdgeFlowTensor>();\n\n    // input_points: [1, 1, num_points, 2] - SlimSAM format (float32)\n    inputs.set('input_points', new EdgeFlowTensor(\n      new Float32Array(allPoints),\n      [1, 1, numPoints, 2],\n      'float32'\n    ));\n\n    // input_labels: [1, 1, num_points] - SlimSAM format (int64)\n    inputs.set('input_labels', new EdgeFlowTensor(\n      BigInt64Array.from(allLabels.map(l => BigInt(l))),\n      [1, 1, numPoints],\n      'int64'\n    ));\n\n    // Note: image_embeddings and image_positional_embeddings are added in segment()\n    // SlimSAM decoder only needs: image_embeddings, image_positional_embeddings, input_points, input_labels\n\n    return inputs;\n  }\n\n  /**\n   * Post-process masks from decoder output\n   */\n  private postprocessMasks(\n    masks: EdgeFlowTensor,\n    scores: EdgeFlowTensor,\n    threshold: number,\n    returnAllMasks: boolean\n  ): ImageSegmentationResult {\n    const { width, height } = this.currentImageSize!;\n    const scoresData = scores.toFloat32Array();\n    const masksData = masks.toFloat32Array();\n\n    // SAM outputs multiple masks (usually 3)\n    const numMasks = scoresData.length;\n    const maskShape = masks.shape; // [1, num_masks, H, W]\n    const maskH = maskShape[2] ?? height;\n    const maskW = maskShape[3] ?? width;\n\n    // Find best mask by score\n    let bestIdx = 0;\n    let bestScore = scoresData[0] ?? 0;\n\n    for (let i = 1; i < numMasks; i++) {\n      if ((scoresData[i] ?? 0) > bestScore) {\n        bestScore = scoresData[i] ?? 0;\n        bestIdx = i;\n      }\n    }\n\n    // Extract and resize the best mask to original image size\n    const outputMask = this.resizeMask(\n      masksData, \n      bestIdx, \n      maskW, \n      maskH, \n      width, \n      height, \n      threshold\n    );\n\n    const result: ImageSegmentationResult = {\n      mask: outputMask,\n      width,\n      height,\n      score: bestScore,\n    };\n\n    if (returnAllMasks && numMasks > 1) {\n      result.allMasks = [];\n      for (let m = 0; m < numMasks; m++) {\n        const mask = this.resizeMask(\n          masksData, \n          m, \n          maskW, \n          maskH, \n          width, \n          height, \n          threshold\n        );\n        result.allMasks.push({\n          mask,\n          score: scoresData[m] ?? 0,\n        });\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Resize mask from model output size to original image size\n   */\n  private resizeMask(\n    masksData: Float32Array,\n    maskIdx: number,\n    srcW: number,\n    srcH: number,\n    dstW: number,\n    dstH: number,\n    threshold: number\n  ): Uint8Array {\n    const outputMask = new Uint8Array(dstW * dstH);\n    const maskOffset = maskIdx * srcW * srcH;\n\n    // Bilinear interpolation for resizing\n    for (let y = 0; y < dstH; y++) {\n      for (let x = 0; x < dstW; x++) {\n        // Map to source coordinates\n        const srcX = (x / dstW) * srcW;\n        const srcY = (y / dstH) * srcH;\n\n        // Bilinear interpolation\n        const x0 = Math.floor(srcX);\n        const x1 = Math.min(x0 + 1, srcW - 1);\n        const y0 = Math.floor(srcY);\n        const y1 = Math.min(y0 + 1, srcH - 1);\n\n        const xFrac = srcX - x0;\n        const yFrac = srcY - y0;\n\n        const v00 = masksData[maskOffset + y0 * srcW + x0] ?? 0;\n        const v01 = masksData[maskOffset + y0 * srcW + x1] ?? 0;\n        const v10 = masksData[maskOffset + y1 * srcW + x0] ?? 0;\n        const v11 = masksData[maskOffset + y1 * srcW + x1] ?? 0;\n\n        const value = \n          v00 * (1 - xFrac) * (1 - yFrac) +\n          v01 * xFrac * (1 - yFrac) +\n          v10 * (1 - xFrac) * yFrac +\n          v11 * xFrac * yFrac;\n\n        // Apply sigmoid and threshold\n        const sigmoid = 1 / (1 + Math.exp(-value));\n        outputMask[y * dstW + x] = sigmoid > threshold ? 255 : 0;\n      }\n    }\n\n    return outputMask;\n  }\n\n  /**\n   * Clear the current image embedding\n   */\n  clearImage(): void {\n    this.imageEmbedding = null;\n    this.imagePositionalEmbedding = null;\n    this.currentImageSize = null;\n    this.resizedImageSize = null;\n  }\n\n  /**\n   * Preprocess (required by BasePipeline)\n   */\n  protected override async preprocess(input: ImageInput): Promise<EdgeFlowTensor[]> {\n    const imageData = await this.loadImage(input);\n    const { tensor } = this.preprocessImage(imageData);\n    return [tensor];\n  }\n\n  /**\n   * Postprocess (required by BasePipeline)\n   */\n  protected override async postprocess(\n    _outputs: EdgeFlowTensor[],\n    _options?: PipelineOptions\n  ): Promise<ImageSegmentationResult> {\n    // This is handled in segment() method\n    return {\n      mask: new Uint8Array(0),\n      width: 0,\n      height: 0,\n      score: 0,\n    };\n  }\n\n  /**\n   * Dispose resources\n   */\n  override dispose(): void {\n    super.dispose();\n    this.encoderModel?.dispose();\n    this.decoderModel?.dispose();\n    this.imageEmbedding = null;\n    this.imagePositionalEmbedding = null;\n    this.currentImageSize = null;\n    this.resizedImageSize = null;\n    this.modelsLoaded = false;\n  }\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Create image segmentation pipeline\n */\nexport function createImageSegmentationPipeline(\n  config: Partial<PipelineConfig> = {}\n): ImageSegmentationPipeline {\n  return new ImageSegmentationPipeline({\n    task: 'image-segmentation',\n    model: config.model ?? 'slimsam',\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization,\n  });\n}\n\n// Register pipeline\nregisterPipeline('image-segmentation', (config) => new ImageSegmentationPipeline(config));\n"
  },
  {
    "path": "src/pipelines/index.ts",
    "content": "/**\n * edgeFlow.js - Pipeline Exports\n */\n\nimport {\n  PipelineConfig,\n  PipelineTask,\n  RuntimeType,\n  QuantizationType,\n} from '../core/types.js';\n\nimport { getPluginPipeline } from '../core/plugin.js';\nimport { registerAllBackends } from '../backends/index.js';\n\n// Base\nexport {\n  BasePipeline,\n  registerPipeline,\n  getPipelineFactory,\n  SENTIMENT_LABELS,\n  EMOTION_LABELS,\n  IMAGENET_LABELS,\n  type PipelineResult,\n  type TextClassificationResult,\n  type FeatureExtractionResult,\n  type ImageClassificationResult,\n  type ObjectDetectionResult,\n} from './base.js';\n\n// Text Classification\nexport {\n  TextClassificationPipeline,\n  SentimentAnalysisPipeline,\n  createTextClassificationPipeline,\n  createSentimentAnalysisPipeline,\n  type TextClassificationOptions,\n} from './text-classification.js';\n\n// Feature Extraction\nexport {\n  FeatureExtractionPipeline,\n  createFeatureExtractionPipeline,\n  type FeatureExtractionOptions,\n} from './feature-extraction.js';\n\n// Image Classification\nexport {\n  ImageClassificationPipeline,\n  createImageClassificationPipeline,\n  type ImageClassificationOptions,\n  type ImageInput,\n} from './image-classification.js';\n\n// Text Generation\nexport {\n  TextGenerationPipeline,\n  createTextGenerationPipeline,\n  type TextGenerationOptions,\n  type TextGenerationResult,\n  type GenerationStreamEvent,\n  type ChatMessage,\n  type ChatOptions,\n  type ChatTemplateType,\n  type LLMLoadProgress,\n} from './text-generation.js';\n\n// Object Detection\nexport {\n  ObjectDetectionPipeline,\n  createObjectDetectionPipeline,\n  COCO_LABELS,\n  type ObjectDetectionOptions,\n  type Detection,\n  type BoundingBox,\n} from './object-detection.js';\n\n// Automatic Speech Recognition\nexport {\n  AutomaticSpeechRecognitionPipeline,\n  createASRPipeline,\n  type ASROptions,\n  type ASRResult,\n  type WordTimestamp,\n  type ChunkTimestamp,\n} from './automatic-speech-recognition.js';\n\n// Zero-shot Classification\nexport {\n  ZeroShotClassificationPipeline,\n  createZeroShotClassificationPipeline,\n  type ZeroShotClassificationOptions,\n  type ZeroShotClassificationResult,\n} from './zero-shot-classification.js';\n\n// Question Answering\nexport {\n  QuestionAnsweringPipeline,\n  createQuestionAnsweringPipeline,\n  type QuestionAnsweringOptions,\n  type QuestionAnsweringResult,\n  type QAInput,\n} from './question-answering.js';\n\n// Image Segmentation\nexport {\n  ImageSegmentationPipeline,\n  createImageSegmentationPipeline,\n  type ImageSegmentationOptions,\n  type ImageSegmentationResult,\n  type PointPrompt,\n  type BoxPrompt,\n  type ModelLoadProgress,\n} from './image-segmentation.js';\n\n// ============================================================================\n// High-Level Pipeline Factory\n// ============================================================================\n\n/**\n * Pipeline options for the factory function\n */\nexport interface PipelineFactoryOptions {\n  /** Model ID or URL */\n  model?: string;\n  /** Runtime to use */\n  runtime?: RuntimeType;\n  /** Enable caching */\n  cache?: boolean;\n  /** Quantization type */\n  quantization?: QuantizationType;\n  /** Custom labels for classification */\n  labels?: string[];\n}\n\n/**\n * Supported pipeline task mapping\n */\ntype PipelineTaskMap = {\n  'text-classification': TextClassificationPipeline;\n  'sentiment-analysis': SentimentAnalysisPipeline;\n  'feature-extraction': FeatureExtractionPipeline;\n  'image-classification': ImageClassificationPipeline;\n  'text-generation': TextGenerationPipeline;\n  'object-detection': ObjectDetectionPipeline;\n  'automatic-speech-recognition': AutomaticSpeechRecognitionPipeline;\n  'zero-shot-classification': ZeroShotClassificationPipeline;\n  'question-answering': QuestionAnsweringPipeline;\n  'image-segmentation': ImageSegmentationPipeline;\n};\n\n// Import pipeline classes\nimport { TextClassificationPipeline, SentimentAnalysisPipeline } from './text-classification.js';\nimport { FeatureExtractionPipeline } from './feature-extraction.js';\nimport { ImageClassificationPipeline } from './image-classification.js';\nimport { TextGenerationPipeline } from './text-generation.js';\nimport { ObjectDetectionPipeline } from './object-detection.js';\nimport { AutomaticSpeechRecognitionPipeline } from './automatic-speech-recognition.js';\nimport { ZeroShotClassificationPipeline } from './zero-shot-classification.js';\nimport { QuestionAnsweringPipeline } from './question-answering.js';\nimport { ImageSegmentationPipeline } from './image-segmentation.js';\n\n/**\n * Create a pipeline for a specific task\n * \n * @example\n * ```typescript\n * // Create a sentiment analysis pipeline\n * const sentiment = await pipeline('sentiment-analysis');\n * const result = await sentiment.run('I love this product!');\n * \n * // Create an image classifier with custom model\n * const classifier = await pipeline('image-classification', {\n *   model: 'https://example.com/model.bin',\n * });\n * ```\n */\nexport async function pipeline<T extends keyof PipelineTaskMap>(\n  task: T,\n  options?: PipelineFactoryOptions\n): Promise<PipelineTaskMap[T]> {\n  // Guarantee backends are registered before any model loads.\n  // registerAllBackends() is synchronous and idempotent (safe to call repeatedly).\n  registerAllBackends();\n\n  const config: PipelineConfig = {\n    task: task as PipelineTask,\n    model: options?.model ?? 'default',\n    runtime: options?.runtime,\n    cache: options?.cache ?? true,\n    quantization: options?.quantization,\n  };\n\n  type AllPipelines = TextClassificationPipeline | SentimentAnalysisPipeline | FeatureExtractionPipeline | ImageClassificationPipeline | TextGenerationPipeline | ObjectDetectionPipeline | AutomaticSpeechRecognitionPipeline | ZeroShotClassificationPipeline | QuestionAnsweringPipeline | ImageSegmentationPipeline;\n  \n  let pipelineInstance: AllPipelines;\n\n  switch (task) {\n    case 'text-classification':\n      pipelineInstance = new TextClassificationPipeline(config, options?.labels);\n      break;\n    case 'sentiment-analysis':\n      pipelineInstance = new SentimentAnalysisPipeline(config);\n      break;\n    case 'feature-extraction':\n      pipelineInstance = new FeatureExtractionPipeline(config);\n      break;\n    case 'image-classification':\n      pipelineInstance = new ImageClassificationPipeline(config, options?.labels);\n      break;\n    case 'text-generation':\n      pipelineInstance = new TextGenerationPipeline(config);\n      break;\n    case 'object-detection':\n      pipelineInstance = new ObjectDetectionPipeline(config, options?.labels);\n      break;\n    case 'automatic-speech-recognition':\n      pipelineInstance = new AutomaticSpeechRecognitionPipeline(config);\n      break;\n    case 'zero-shot-classification':\n      pipelineInstance = new ZeroShotClassificationPipeline(config);\n      break;\n    case 'question-answering':\n      pipelineInstance = new QuestionAnsweringPipeline(config);\n      break;\n    case 'image-segmentation':\n      pipelineInstance = new ImageSegmentationPipeline(config);\n      break;\n    default: {\n      // Check if a plugin provides this pipeline task\n      const pluginEntry = getPluginPipeline(task);\n      if (pluginEntry) {\n        pipelineInstance = pluginEntry.factory(config);\n        break;\n      }\n      throw new Error(\n        `Unknown pipeline task: \"${task}\". ` +\n        `Register a plugin with registerPlugin() to add custom pipeline tasks.`\n      );\n    }\n  }\n\n  // Initialize the pipeline\n  await pipelineInstance.initialize();\n\n  return pipelineInstance as PipelineTaskMap[T];\n}\n\n/**\n * Create multiple pipelines at once\n */\nexport async function createPipelines<T extends (keyof PipelineTaskMap)[]>(\n  tasks: T,\n  options?: PipelineFactoryOptions\n): Promise<{ [K in T[number]]: PipelineTaskMap[K] }> {\n  const pipelines = await Promise.all(\n    tasks.map(task => pipeline(task, options))\n  );\n\n  const result: Partial<{ [K in T[number]]: PipelineTaskMap[K] }> = {};\n  \n  for (let i = 0; i < tasks.length; i++) {\n    const task = tasks[i]!;\n    result[task as T[number]] = pipelines[i] as PipelineTaskMap[T[number]];\n  }\n\n  return result as { [K in T[number]]: PipelineTaskMap[K] };\n}\n"
  },
  {
    "path": "src/pipelines/object-detection.ts",
    "content": "/**\n * edgeFlow.js - Object Detection Pipeline\n * \n * Detect objects in images with bounding boxes and class labels.\n */\n\nimport { BasePipeline, ObjectDetectionResult, registerPipeline } from './base.js';\nimport { EdgeFlowTensor } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions, LoadedModel } from '../core/types.js';\nimport { ImagePreprocessor, type ImageInput } from '../utils/preprocessor.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInference } from '../core/runtime.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ObjectDetectionOptions extends PipelineOptions {\n  threshold?: number;\n  topK?: number;\n  nms?: boolean;\n  iouThreshold?: number;\n}\n\nexport interface BoundingBox {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nexport interface Detection extends ObjectDetectionResult {\n  classId: number;\n  boxNormalized: BoundingBox;\n}\n\n// ============================================================================\n// Default Model (YOLOS-tiny, quantized)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/yolos-tiny/resolve/main/onnx/model_quantized.onnx',\n};\n\n// ============================================================================\n// COCO Labels\n// ============================================================================\n\nexport const COCO_LABELS = [\n  'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck',\n  'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench',\n  'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra',\n  'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',\n  'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove',\n  'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',\n  'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',\n  'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',\n  'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse',\n  'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink',\n  'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier',\n  'toothbrush'\n];\n\n// ============================================================================\n// Object Detection Pipeline\n// ============================================================================\n\nexport class ObjectDetectionPipeline extends BasePipeline<ImageInput | ImageInput[], Detection[]> {\n  private preprocessor: ImagePreprocessor;\n  private onnxModel: LoadedModel | null = null;\n  private labels: string[];\n  private modelUrl: string;\n\n  constructor(config?: PipelineConfig, labels?: string[]) {\n    super(config ?? {\n      task: 'object-detection',\n      model: 'default',\n    });\n\n    this.labels = labels ?? COCO_LABELS;\n    this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n    this.preprocessor = new ImagePreprocessor({\n      width: 640,\n      height: 640,\n      mean: [0.485, 0.456, 0.406],\n      std: [0.229, 0.224, 0.225],\n      channelFormat: 'CHW',\n    });\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  setLabels(labels: string[]): void {\n    this.labels = labels;\n  }\n\n  override async run(\n    input: ImageInput | ImageInput[],\n    options?: ObjectDetectionOptions\n  ): Promise<Detection[]> {\n    await this.initialize();\n    const tensorInputs = await this.preprocess(input);\n    const outputs = await this.runModelInference(tensorInputs);\n    return this.postprocess(outputs, options);\n  }\n\n  protected async preprocess(input: ImageInput | ImageInput[]): Promise<EdgeFlowTensor[]> {\n    const inputs = Array.isArray(input) ? input : [input];\n\n    if (inputs.length === 1) {\n      const tensor = await this.preprocessor.process(inputs[0]!);\n      return [new EdgeFlowTensor(\n        tensor.toFloat32Array(),\n        [1, ...tensor.shape],\n        'float32'\n      )];\n    }\n\n    return [await this.preprocessor.processBatch(inputs)];\n  }\n\n  private async runModelInference(inputs: EdgeFlowTensor[]): Promise<EdgeFlowTensor[]> {\n    const outputs = await runInference(this.onnxModel!, inputs);\n    return outputs as EdgeFlowTensor[];\n  }\n\n  protected async postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: PipelineOptions\n  ): Promise<Detection[]> {\n    const opts = options as ObjectDetectionOptions ?? {};\n    const threshold = opts.threshold ?? 0.5;\n    const topK = opts.topK ?? 100;\n    const nms = opts.nms ?? true;\n    const iouThreshold = opts.iouThreshold ?? 0.5;\n\n    if (!outputs[0]) {\n      return [];\n    }\n\n    const outputData = outputs[0].toFloat32Array();\n    const shape = [...outputs[0].shape] as number[];\n\n    const detections = this.parseDetections(outputData, shape, threshold);\n\n    let filtered = nms ? this.nonMaxSuppression(detections, iouThreshold) : detections;\n\n    filtered.sort((a, b) => b.score - a.score);\n    filtered = filtered.slice(0, topK);\n\n    return filtered;\n  }\n\n  private parseDetections(\n    data: Float32Array,\n    shape: number[],\n    threshold: number\n  ): Detection[] {\n    const detections: Detection[] = [];\n\n    const numBoxes = shape[1] ?? 0;\n    const boxSize = shape[2] ?? 0;\n\n    if (boxSize >= 5) {\n      const numClasses = boxSize - 5;\n\n      for (let i = 0; i < numBoxes; i++) {\n        const offset = i * boxSize;\n        const objectness = data[offset + 4] ?? 0;\n\n        if (objectness < threshold) continue;\n\n        let maxClassScore = 0;\n        let maxClassIdx = 0;\n\n        for (let c = 0; c < numClasses; c++) {\n          const score = data[offset + 5 + c] ?? 0;\n          if (score > maxClassScore) {\n            maxClassScore = score;\n            maxClassIdx = c;\n          }\n        }\n\n        const confidence = objectness * maxClassScore;\n        if (confidence < threshold) continue;\n\n        const x = data[offset] ?? 0;\n        const y = data[offset + 1] ?? 0;\n        const w = data[offset + 2] ?? 0;\n        const h = data[offset + 3] ?? 0;\n\n        detections.push({\n          label: this.labels[maxClassIdx] ?? `class_${maxClassIdx}`,\n          score: confidence,\n          classId: maxClassIdx,\n          box: {\n            x: Math.max(0, x - w / 2),\n            y: Math.max(0, y - h / 2),\n            width: w,\n            height: h,\n          },\n          boxNormalized: {\n            x: Math.max(0, x - w / 2),\n            y: Math.max(0, y - h / 2),\n            width: w,\n            height: h,\n          },\n        });\n      }\n    } else if (boxSize === 4) {\n      for (let i = 0; i < numBoxes; i++) {\n        const offset = i * boxSize;\n        const x1 = data[offset] ?? 0;\n        const y1 = data[offset + 1] ?? 0;\n        const x2 = data[offset + 2] ?? 0;\n        const y2 = data[offset + 3] ?? 0;\n\n        detections.push({\n          label: this.labels[0] ?? 'object',\n          score: 1.0,\n          classId: 0,\n          box: {\n            x: x1,\n            y: y1,\n            width: x2 - x1,\n            height: y2 - y1,\n          },\n          boxNormalized: {\n            x: x1,\n            y: y1,\n            width: x2 - x1,\n            height: y2 - y1,\n          },\n        });\n      }\n    }\n\n    return detections;\n  }\n\n  private nonMaxSuppression(\n    detections: Detection[],\n    iouThreshold: number\n  ): Detection[] {\n    if (detections.length === 0) return [];\n\n    const sorted = [...detections].sort((a, b) => b.score - a.score);\n    const selected: Detection[] = [];\n    const active = new Array(sorted.length).fill(true);\n\n    for (let i = 0; i < sorted.length; i++) {\n      if (!active[i]) continue;\n\n      const current = sorted[i]!;\n      selected.push(current);\n\n      for (let j = i + 1; j < sorted.length; j++) {\n        if (!active[j]) continue;\n\n        const other = sorted[j]!;\n        if (current.classId !== other.classId) continue;\n\n        const iou = this.computeIoU(current.box, other.box);\n        if (iou > iouThreshold) {\n          active[j] = false;\n        }\n      }\n    }\n\n    return selected;\n  }\n\n  private computeIoU(a: BoundingBox, b: BoundingBox): number {\n    const xOverlap = Math.max(0,\n      Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)\n    );\n    const yOverlap = Math.max(0,\n      Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y)\n    );\n\n    const intersection = xOverlap * yOverlap;\n    const aArea = a.width * a.height;\n    const bArea = b.width * b.height;\n    const union = aArea + bArea - intersection;\n\n    return union > 0 ? intersection / union : 0;\n  }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createObjectDetectionPipeline(\n  config?: PipelineConfig,\n  labels?: string[]\n): ObjectDetectionPipeline {\n  return new ObjectDetectionPipeline(config, labels);\n}\n\nregisterPipeline('object-detection', (config) => new ObjectDetectionPipeline(config));\n"
  },
  {
    "path": "src/pipelines/question-answering.ts",
    "content": "/**\n * edgeFlow.js - Question Answering Pipeline\n * \n * Extract answers from context given a question using real ONNX QA models.\n */\n\nimport { BasePipeline, PipelineResult, registerPipeline } from './base.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions, LoadedModel } from '../core/types.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\n\n// ============================================================================\n// Default Model (DistilBERT fine-tuned on SQuAD)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/onnx/model_quantized.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/distilbert-base-cased-distilled-squad/resolve/main/tokenizer.json',\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface QAInput {\n  question: string;\n  context: string;\n}\n\nexport interface QuestionAnsweringOptions extends PipelineOptions {\n  maxAnswerLength?: number;\n  maxQuestionLength?: number;\n  topK?: number;\n  threshold?: number;\n  handleImpossible?: boolean;\n}\n\nexport interface QuestionAnsweringResult extends PipelineResult {\n  answer: string;\n  score: number;\n  start: number;\n  end: number;\n}\n\n// ============================================================================\n// Question Answering Pipeline\n// ============================================================================\n\nexport class QuestionAnsweringPipeline extends BasePipeline<\n  QAInput | QAInput[],\n  QuestionAnsweringResult | QuestionAnsweringResult[]\n> {\n  private tokenizer: Tokenizer | null = null;\n  private onnxModel: LoadedModel | null = null;\n  private modelUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config?: PipelineConfig) {\n    super(config ?? {\n      task: 'question-answering',\n      model: 'default',\n    });\n    this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  setTokenizer(tokenizer: Tokenizer): void {\n    this.tokenizer = tokenizer;\n  }\n\n  override async run(\n    input: QAInput | QAInput[],\n    options?: QuestionAnsweringOptions\n  ): Promise<QuestionAnsweringResult | QuestionAnsweringResult[]> {\n    await this.initialize();\n\n    const inputs = Array.isArray(input) ? input : [input];\n    const results = await Promise.all(\n      inputs.map(i => this.answerQuestion(i, options ?? {}))\n    );\n\n    return Array.isArray(input) ? results : results[0]!;\n  }\n\n  private async answerQuestion(\n    input: QAInput,\n    options: QuestionAnsweringOptions\n  ): Promise<QuestionAnsweringResult> {\n    const startTime = performance.now();\n    const { question, context } = input;\n    const maxAnswerLength = options.maxAnswerLength ?? 30;\n\n    // No padding — QA runs one example at a time and padding wastes compute\n    const encoded = this.tokenizer!.encode(question, {\n      textPair: context,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      padding: 'do_not_pad',\n      returnAttentionMask: true,\n      returnTokenTypeIds: true,\n    });\n\n    const seqLen = encoded.inputIds.length;\n\n    const inputIds = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, seqLen],\n      'int64'\n    );\n    const attentionMask = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))),\n      [1, seqLen],\n      'int64'\n    );\n\n    const namedInputs = new Map<string, EdgeFlowTensor>();\n    namedInputs.set('input_ids', inputIds);\n    namedInputs.set('attention_mask', attentionMask);\n\n    const outputs = await runInferenceNamed(this.onnxModel!, namedInputs);\n\n    if (outputs.length < 2) {\n      return { answer: '', score: 0, start: 0, end: 0, processingTime: performance.now() - startTime };\n    }\n\n    const startLogits = (outputs[0] as EdgeFlowTensor).toFloat32Array();\n    const endLogits = (outputs[1] as EdgeFlowTensor).toFloat32Array();\n\n    const startProbs = softmax(new EdgeFlowTensor(new Float32Array(startLogits), [seqLen], 'float32')).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(new Float32Array(endLogits), [seqLen], 'float32')).toFloat32Array();\n\n    // Constrain answer span to the context portion only (tokenTypeIds === 1).\n    // tokenTypeIds: 0 = question tokens ([CLS], question, [SEP]), 1 = context tokens.\n    const typeIds = encoded.tokenTypeIds ?? new Array(seqLen).fill(1);\n    // Find where context starts (first index with typeId === 1)\n    const contextStart = typeIds.findIndex(t => t === 1);\n    const spanStart = contextStart >= 0 ? contextStart : 0;\n    const spanEnd = seqLen - 1; // last non-padding position\n\n    let bestStartIdx = spanStart;\n    let bestEndIdx = spanStart;\n    let bestScore = -Infinity;\n\n    for (let s = spanStart; s <= spanEnd; s++) {\n      for (let e = s; e < Math.min(s + maxAnswerLength, spanEnd + 1); e++) {\n        const score = (startProbs[s] ?? 0) * (endProbs[e] ?? 0);\n        if (score > bestScore) {\n          bestScore = score;\n          bestStartIdx = s;\n          bestEndIdx = e;\n        }\n      }\n    }\n\n    // Decode the answer span directly from token IDs in the context portion\n    const answerTokenIds = encoded.inputIds.slice(bestStartIdx, bestEndIdx + 1);\n    const answer = this.tokenizer!.decode(answerTokenIds, true);\n\n    return {\n      answer: answer || '',\n      score: Math.max(0, bestScore),\n      start: bestStartIdx,\n      end: bestEndIdx,\n      processingTime: performance.now() - startTime,\n    };\n  }\n\n  private tokenOffsetToCharOffset(\n    context: string,\n    _question: string,\n    inputIds: number[],\n    tokenIdx: number\n  ): number {\n    // Approximate mapping: decode tokens up to this index and measure length\n    // For a production implementation you'd use the tokenizer's offset mapping.\n    const decoded = this.tokenizer!.decode(inputIds.slice(0, tokenIdx + 1), true);\n    const contextStart = context.indexOf(decoded.trim().split(' ').pop() ?? '');\n    return contextStart >= 0 ? contextStart : 0;\n  }\n\n  protected async preprocess(input: QAInput | QAInput[]): Promise<EdgeFlowTensor[]> {\n    const qaInput = Array.isArray(input) ? input[0]! : input;\n    const encoded = this.tokenizer!.encode(qaInput.question, {\n      textPair: qaInput.context,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      returnAttentionMask: true,\n      returnTokenTypeIds: true,\n    });\n\n    return [\n      new EdgeFlowTensor(\n        BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n        [1, encoded.inputIds.length],\n        'int64'\n      ),\n      new EdgeFlowTensor(\n        BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))),\n        [1, encoded.attentionMask.length],\n        'int64'\n      ),\n    ];\n  }\n\n  protected async postprocess(\n    outputs: EdgeFlowTensor[],\n    _options?: PipelineOptions\n  ): Promise<QuestionAnsweringResult | QuestionAnsweringResult[]> {\n    if (outputs.length < 2) {\n      return { answer: '', score: 0, start: 0, end: 0 };\n    }\n\n    const startLogits = outputs[0]!.toFloat32Array();\n    const endLogits = outputs[1]!.toFloat32Array();\n    const seqLen = startLogits.length;\n\n    const startProbs = softmax(new EdgeFlowTensor(startLogits, [seqLen], 'float32')).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(endLogits, [seqLen], 'float32')).toFloat32Array();\n\n    let bestStart = 0;\n    let bestEnd = 0;\n    let bestScore = 0;\n\n    for (let start = 0; start < seqLen; start++) {\n      for (let end = start; end < Math.min(start + 30, seqLen); end++) {\n        const score = (startProbs[start] ?? 0) * (endProbs[end] ?? 0);\n        if (score > bestScore) {\n          bestScore = score;\n          bestStart = start;\n          bestEnd = end;\n        }\n      }\n    }\n\n    return {\n      answer: '',\n      score: bestScore,\n      start: bestStart,\n      end: bestEnd,\n    };\n  }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createQuestionAnsweringPipeline(\n  config?: PipelineConfig\n): QuestionAnsweringPipeline {\n  return new QuestionAnsweringPipeline(config);\n}\n\nregisterPipeline('question-answering', (config) => new QuestionAnsweringPipeline(config));\n"
  },
  {
    "path": "src/pipelines/text-classification.ts",
    "content": "/**\n * edgeFlow.js - Text Classification Pipeline\n * \n * High-level API for text classification tasks including\n * sentiment analysis, topic classification, etc.\n */\n\nimport {\n  PipelineConfig,\n  PipelineOptions,\n  LoadedModel,\n} from '../core/types.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\nimport {\n  BasePipeline,\n  TextClassificationResult,\n  registerPipeline,\n  SENTIMENT_LABELS,\n} from './base.js';\n\n// ============================================================================\n// Default Model (DistilBERT fine-tuned on SST-2)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/tokenizer.json',\n};\n\nconst DEFAULT_SST2_LABELS = ['NEGATIVE', 'POSITIVE'];\n\n// ============================================================================\n// Text Classification Pipeline\n// ============================================================================\n\nexport interface TextClassificationOptions extends PipelineOptions {\n  returnAllScores?: boolean;\n  labels?: string[];\n  topK?: number;\n}\n\nexport class TextClassificationPipeline extends BasePipeline<\n  string | string[],\n  TextClassificationResult | TextClassificationResult[]\n> {\n  private tokenizer: Tokenizer | null = null;\n  private onnxModel: LoadedModel | null = null;\n  private labels: string[];\n  private modelUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config: PipelineConfig, labels?: string[]) {\n    super(config);\n    this.labels = labels ?? DEFAULT_SST2_LABELS;\n    this.modelUrl = config.model !== 'default' ? config.model : DEFAULT_MODELS.model;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  setLabels(labels: string[]): void {\n    this.labels = labels;\n  }\n\n  override async run(\n    input: string | string[],\n    options?: TextClassificationOptions\n  ): Promise<TextClassificationResult | TextClassificationResult[]> {\n    const isBatch = Array.isArray(input);\n    const inputs = isBatch ? input : [input];\n    \n    await this.initialize();\n    \n    const startTime = performance.now();\n    const results: TextClassificationResult[] = [];\n\n    for (const text of inputs) {\n      const tensorInputs = await this.preprocess(text);\n      const outputs = await this.runInference(tensorInputs);\n      const result = await this.postprocess(outputs, options);\n      results.push(result);\n    }\n\n    const processingTime = performance.now() - startTime;\n    for (const result of results) {\n      result.processingTime = processingTime / results.length;\n    }\n\n    return isBatch ? results : results[0]!;\n  }\n\n  protected override async preprocess(input: string | string[]): Promise<EdgeFlowTensor[]> {\n    const text = Array.isArray(input) ? input[0]! : input;\n    \n    const encoded = this.tokenizer!.encode(text, {\n      maxLength: 128,\n      padding: 'max_length',\n      truncation: true,\n    });\n\n    const inputIds = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, encoded.inputIds.length],\n      'int64'\n    );\n\n    const attentionMask = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))),\n      [1, encoded.attentionMask.length],\n      'int64'\n    );\n\n    return [inputIds, attentionMask];\n  }\n\n  private async runInference(inputs: EdgeFlowTensor[]): Promise<EdgeFlowTensor[]> {\n    const namedInputs = new Map<string, EdgeFlowTensor>();\n    namedInputs.set('input_ids', inputs[0]!);\n    namedInputs.set('attention_mask', inputs[1]!);\n\n    const outputs = await runInferenceNamed(this.onnxModel!, namedInputs);\n    return outputs as EdgeFlowTensor[];\n  }\n\n  protected override async postprocess(\n    outputs: EdgeFlowTensor[],\n    options?: TextClassificationOptions\n  ): Promise<TextClassificationResult> {\n    const logits = outputs[0];\n    if (!logits) {\n      return { label: 'unknown', score: 0 };\n    }\n\n    const probs = softmax(logits, -1) as EdgeFlowTensor;\n    const probsArray = probs.toFloat32Array();\n\n    let maxIdx = 0;\n    let maxScore = probsArray[0] ?? 0;\n    \n    for (let i = 1; i < probsArray.length; i++) {\n      if ((probsArray[i] ?? 0) > maxScore) {\n        maxScore = probsArray[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n\n    const label = options?.labels?.[maxIdx] ?? this.labels[maxIdx] ?? `class_${maxIdx}`;\n\n    return {\n      label,\n      score: maxScore,\n    };\n  }\n}\n\n// ============================================================================\n// Sentiment Analysis Pipeline\n// ============================================================================\n\nexport class SentimentAnalysisPipeline extends TextClassificationPipeline {\n  constructor(config: PipelineConfig) {\n    super(config, SENTIMENT_LABELS);\n  }\n\n  async analyze(\n    text: string | string[],\n    options?: TextClassificationOptions\n  ): Promise<TextClassificationResult | TextClassificationResult[]> {\n    return this.run(text, options);\n  }\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\nexport function createTextClassificationPipeline(\n  config: Partial<PipelineConfig> = {}\n): TextClassificationPipeline {\n  return new TextClassificationPipeline({\n    task: 'text-classification',\n    model: config.model ?? 'default',\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization,\n  });\n}\n\nexport function createSentimentAnalysisPipeline(\n  config: Partial<PipelineConfig> = {}\n): SentimentAnalysisPipeline {\n  return new SentimentAnalysisPipeline({\n    task: 'sentiment-analysis',\n    model: config.model ?? 'default',\n    runtime: config.runtime,\n    cache: config.cache ?? true,\n    quantization: config.quantization,\n  });\n}\n\nregisterPipeline('text-classification', (config) => new TextClassificationPipeline(config));\nregisterPipeline('sentiment-analysis', (config) => new SentimentAnalysisPipeline(config));\n"
  },
  {
    "path": "src/pipelines/text-generation.ts",
    "content": "/**\n * edgeFlow.js - Text Generation Pipeline\n * \n * Autoregressive text generation with streaming support.\n * Supports GPT-2, LLaMA, Mistral, and other causal LM models.\n * Includes chat/conversation support with message history.\n */\n\nimport { BasePipeline, PipelineResult } from './base.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions, LoadedModel } from '../core/types.js';\nimport { runInferenceNamed, loadModelFromBuffer } from '../core/runtime.js';\n\n// ============================================================================\n// Default Model URLs (TinyLlama - quantized for browser)\n// ============================================================================\n\nconst DEFAULT_LLM_MODELS = {\n  model: 'https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/onnx/model_q4f16.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/TinyLlama-1.1B-Chat-v1.0/resolve/main/tokenizer.json',\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * LLM model loading progress callback\n */\nexport interface LLMLoadProgress {\n  /** Stage: 'tokenizer' or 'model' */\n  stage: 'tokenizer' | 'model';\n  /** Bytes loaded */\n  loaded: number;\n  /** Total bytes */\n  total: number;\n  /** Progress percentage (0-100) */\n  progress: number;\n}\n\n/**\n * Chat message\n */\nexport interface ChatMessage {\n  /** Role: 'system', 'user', or 'assistant' */\n  role: 'system' | 'user' | 'assistant';\n  /** Message content */\n  content: string;\n}\n\n/**\n * Chat template type\n */\nexport type ChatTemplateType = 'chatml' | 'llama2' | 'llama3' | 'mistral' | 'phi3' | 'alpaca' | 'vicuna' | 'custom';\n\n/**\n * Text generation options\n */\nexport interface TextGenerationOptions {\n  /** Maximum number of new tokens to generate */\n  maxNewTokens?: number;\n  /** Maximum total length (prompt + generated) */\n  maxLength?: number;\n  /** Minimum number of new tokens to generate */\n  minNewTokens?: number;\n  /** Sampling temperature (higher = more random) */\n  temperature?: number;\n  /** Top-k sampling (0 = disabled) */\n  topK?: number;\n  /** Top-p (nucleus) sampling (1.0 = disabled) */\n  topP?: number;\n  /** Repetition penalty (1.0 = disabled) */\n  repetitionPenalty?: number;\n  /** Stop sequences */\n  stopSequences?: string[];\n  /** Whether to do sampling (false = greedy) */\n  doSample?: boolean;\n  /** Number of sequences to return */\n  numReturnSequences?: number;\n  /** Return full text (including prompt) */\n  returnFullText?: boolean;\n  /** Callback for each generated token */\n  onToken?: (token: string, tokenId: number) => void;\n}\n\n/**\n * Chat generation options\n */\nexport interface ChatOptions extends TextGenerationOptions {\n  /** System prompt */\n  systemPrompt?: string;\n  /** Chat template type */\n  templateType?: ChatTemplateType;\n  /** Custom template (if templateType is 'custom') */\n  customTemplate?: {\n    systemPrefix?: string;\n    systemSuffix?: string;\n    userPrefix?: string;\n    userSuffix?: string;\n    assistantPrefix?: string;\n    assistantSuffix?: string;\n    separator?: string;\n  };\n}\n\n/**\n * Text generation result\n */\nexport interface TextGenerationResult extends PipelineResult {\n  /** Generated text */\n  generatedText: string;\n  /** Full text (prompt + generated) if returnFullText is true */\n  fullText?: string;\n  /** Generated token IDs */\n  tokenIds: number[];\n  /** Number of tokens generated */\n  numTokens: number;\n}\n\n/**\n * Streaming generation event\n */\nexport interface GenerationStreamEvent {\n  /** Current token */\n  token: string;\n  /** Token ID */\n  tokenId: number;\n  /** Generated text so far */\n  generatedText: string;\n  /** Whether generation is complete */\n  done: boolean;\n}\n\n// ============================================================================\n// Text Generation Pipeline\n// ============================================================================\n\n/**\n * TextGenerationPipeline - Autoregressive text generation\n * \n * @example\n * ```typescript\n * const generator = await pipeline('text-generation', 'Xenova/gpt2');\n * \n * // Simple generation\n * const result = await generator.run('Once upon a time');\n * console.log(result.generatedText);\n * \n * // Streaming generation\n * for await (const event of generator.stream('Hello, ')) {\n *   process.stdout.write(event.token);\n * }\n * ```\n */\nexport class TextGenerationPipeline extends BasePipeline<string | string[], TextGenerationResult | TextGenerationResult[]> {\n  private tokenizer: Tokenizer | null = null;\n  private eosTokenId: number = 50256; // GPT-2 default\n  private llmModel: LoadedModel | null = null;\n  private modelsLoaded: boolean = false;\n  \n  // Custom model URLs\n  private modelUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config?: PipelineConfig) {\n    super(config ?? {\n      task: 'text-generation',\n      model: 'default',\n    });\n    this.modelUrl = DEFAULT_LLM_MODELS.model;\n    this.tokenizerUrl = DEFAULT_LLM_MODELS.tokenizer;\n  }\n\n  /**\n   * Check if model is loaded\n   */\n  get isModelLoaded(): boolean {\n    return this.modelsLoaded;\n  }\n\n  /**\n   * Set custom model URLs\n   */\n  setModelUrls(model: string, tokenizer: string): void {\n    this.modelUrl = model;\n    this.tokenizerUrl = tokenizer;\n  }\n\n  /**\n   * Load model and tokenizer with progress callback\n   */\n  async loadModel(\n    onProgress?: (progress: LLMLoadProgress) => void\n  ): Promise<void> {\n    if (this.modelsLoaded) return;\n\n    // Load tokenizer first (small, fast)\n    onProgress?.({ stage: 'tokenizer', loaded: 0, total: 100, progress: 0 });\n    \n    try {\n      const tokenizerResponse = await fetch(this.tokenizerUrl);\n      if (!tokenizerResponse.ok) {\n        throw new Error(`Failed to fetch tokenizer: ${tokenizerResponse.status}`);\n      }\n      const tokenizerJson = await tokenizerResponse.json();\n      this.tokenizer = await Tokenizer.fromJSON(tokenizerJson);\n      \n      const specialIds = this.tokenizer.getSpecialTokenIds();\n      this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 2; // TinyLlama uses 2 as EOS\n      \n      onProgress?.({ stage: 'tokenizer', loaded: 100, total: 100, progress: 100 });\n    } catch (error) {\n      throw new Error(`Failed to load tokenizer: ${error}`);\n    }\n\n    // Load model with progress tracking\n    onProgress?.({ stage: 'model', loaded: 0, total: 100, progress: 0 });\n    \n    const modelData = await this.fetchModelWithProgress(\n      this.modelUrl,\n      (loaded, total) => {\n        onProgress?.({\n          stage: 'model',\n          loaded,\n          total,\n          progress: Math.round((loaded / total) * 100),\n        });\n      }\n    );\n    \n    this.llmModel = await loadModelFromBuffer(modelData, {\n      runtime: 'wasm', // Uses ONNXRuntime which auto-detects WebGPU internally\n    });\n    this.model = this.llmModel;\n\n    this.modelsLoaded = true;\n  }\n\n  /**\n   * Fetch model with progress tracking\n   */\n  private async fetchModelWithProgress(\n    url: string,\n    onProgress: (loaded: number, total: number) => void\n  ): Promise<ArrayBuffer> {\n    const response = await fetch(url);\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);\n    }\n\n    const contentLength = response.headers.get('content-length');\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n\n    if (!response.body) {\n      // Fallback if no streaming support\n      const buffer = await response.arrayBuffer();\n      onProgress(buffer.byteLength, buffer.byteLength);\n      return buffer;\n    }\n\n    const reader = response.body.getReader();\n    const chunks: Uint8Array[] = [];\n    let loaded = 0;\n\n    while (true) {\n      const { done, value } = await reader.read();\n      \n      if (done) break;\n      \n      chunks.push(value);\n      loaded += value.length;\n      onProgress(loaded, total || loaded);\n    }\n\n    // Combine chunks into ArrayBuffer\n    const buffer = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      buffer.set(chunk, offset);\n      offset += chunk.length;\n    }\n\n    return buffer.buffer;\n  }\n\n  /**\n   * Initialize pipeline (override to skip default model loading)\n   */\n  override async initialize(): Promise<void> {\n    if (this.isReady) return;\n    // Don't call super.initialize() - we handle model loading separately\n    this.isReady = true;\n  }\n\n  /**\n   * Set tokenizer\n   */\n  setTokenizer(tokenizer: Tokenizer): void {\n    this.tokenizer = tokenizer;\n    const specialIds = tokenizer.getSpecialTokenIds();\n    this.eosTokenId = specialIds.eosTokenId ?? specialIds.sepTokenId ?? 50256;\n  }\n\n  /**\n   * Preprocess - not used for text generation (handled in generateSingle)\n   */\n  protected async preprocess(input: string | string[]): Promise<EdgeFlowTensor[]> {\n    // For text generation, preprocessing is handled in generateNextToken\n    const text = Array.isArray(input) ? input[0] ?? '' : input;\n    if (!this.tokenizer) {\n      // Return dummy tensor if no tokenizer\n      return [new EdgeFlowTensor(new Float32Array([0]), [1], 'float32')];\n    }\n    const encoded = this.tokenizer.encode(text, {\n      addSpecialTokens: false,\n      padding: 'do_not_pad',\n    });\n    return [new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, encoded.inputIds.length],\n      'int64'\n    )];\n  }\n\n  /**\n   * Postprocess - not used for text generation (handled in generateSingle)\n   */\n  protected async postprocess(\n    _outputs: EdgeFlowTensor[],\n    _options?: PipelineOptions\n  ): Promise<TextGenerationResult | TextGenerationResult[]> {\n    // For text generation, postprocessing is handled in generateSingle\n    return {\n      generatedText: '',\n      tokenIds: [],\n      numTokens: 0,\n      processingTime: 0,\n    };\n  }\n\n  /**\n   * Generate text (non-streaming)\n   */\n  override async run(\n    prompt: string | string[],\n    options?: PipelineOptions & TextGenerationOptions\n  ): Promise<TextGenerationResult | TextGenerationResult[]> {\n    await this.initialize();\n    \n    const prompts = Array.isArray(prompt) ? prompt : [prompt];\n    const results = await Promise.all(\n      prompts.map(p => this.generateSingle(p, options ?? {}))\n    );\n    return Array.isArray(prompt) ? results : results[0]!;\n  }\n\n  /**\n   * Generate text with streaming (async generator)\n   */\n  async *stream(\n    prompt: string,\n    options: TextGenerationOptions = {}\n  ): AsyncGenerator<GenerationStreamEvent> {\n    const startTime = performance.now();\n    \n    if (!this.tokenizer) {\n      throw new Error('Tokenizer not set. Call setTokenizer() first.');\n    }\n\n    const {\n      maxNewTokens = 50,\n      maxLength = 512,\n      temperature = 1.0,\n      topK = 0,\n      topP = 1.0,\n      repetitionPenalty = 1.0,\n      stopSequences = [],\n      doSample = true,\n    } = options;\n\n    // Encode prompt\n    const encoded = this.tokenizer.encode(prompt, {\n      addSpecialTokens: false,\n      padding: 'do_not_pad',\n      truncation: false,\n    });\n\n    let inputIds = [...encoded.inputIds];\n    const generatedIds: number[] = [];\n    let generatedText = '';\n\n    // Generation loop\n    for (let i = 0; i < maxNewTokens; i++) {\n      // Check max length\n      if (inputIds.length >= maxLength) break;\n\n      // Run model forward pass\n      const nextTokenId = await this.generateNextToken(\n        inputIds,\n        temperature,\n        topK,\n        topP,\n        repetitionPenalty,\n        doSample\n      );\n\n      // Check for EOS\n      if (nextTokenId === this.eosTokenId) {\n        yield {\n          token: '',\n          tokenId: nextTokenId,\n          generatedText,\n          done: true,\n        };\n        break;\n      }\n\n      // Decode token\n      const token = this.tokenizer.decode([nextTokenId], true);\n      generatedIds.push(nextTokenId);\n      inputIds.push(nextTokenId);\n      generatedText += token;\n\n      // Call token callback\n      if (options.onToken) {\n        options.onToken(token, nextTokenId);\n      }\n\n      // Check stop sequences\n      let shouldStop = false;\n      for (const stopSeq of stopSequences) {\n        if (generatedText.endsWith(stopSeq)) {\n          generatedText = generatedText.slice(0, -stopSeq.length);\n          shouldStop = true;\n          break;\n        }\n      }\n\n      yield {\n        token,\n        tokenId: nextTokenId,\n        generatedText,\n        done: shouldStop,\n      };\n\n      if (shouldStop) break;\n    }\n\n    // Final event\n    const endTime = performance.now();\n    console.log(`Generation completed in ${(endTime - startTime).toFixed(2)}ms`);\n  }\n\n  /**\n   * Generate a single sequence (non-streaming)\n   */\n  private async generateSingle(\n    prompt: string,\n    options: TextGenerationOptions\n  ): Promise<TextGenerationResult> {\n    const startTime = performance.now();\n    \n    if (!this.tokenizer) {\n      throw new Error('Tokenizer not set. Call setTokenizer() first.');\n    }\n\n    const {\n      maxNewTokens = 50,\n      maxLength = 512,\n      temperature = 1.0,\n      topK = 0,\n      topP = 1.0,\n      repetitionPenalty = 1.0,\n      stopSequences = [],\n      doSample = true,\n      returnFullText = false,\n    } = options;\n\n    // Encode prompt\n    const encoded = this.tokenizer.encode(prompt, {\n      addSpecialTokens: false,\n      padding: 'do_not_pad',\n      truncation: false,\n    });\n\n    let inputIds = [...encoded.inputIds];\n    const generatedIds: number[] = [];\n\n    // Generation loop\n    for (let i = 0; i < maxNewTokens; i++) {\n      // Check max length\n      if (inputIds.length >= maxLength) break;\n\n      // Run model forward pass\n      const nextTokenId = await this.generateNextToken(\n        inputIds,\n        temperature,\n        topK,\n        topP,\n        repetitionPenalty,\n        doSample\n      );\n\n      // Check for EOS\n      if (nextTokenId === this.eosTokenId) break;\n\n      // Add to sequence\n      generatedIds.push(nextTokenId);\n      inputIds.push(nextTokenId);\n\n      // Call token callback\n      if (options.onToken) {\n        const token = this.tokenizer.decode([nextTokenId], true);\n        options.onToken(token, nextTokenId);\n      }\n\n      // Check stop sequences\n      const currentText = this.tokenizer.decode(generatedIds, true);\n      let shouldStop = false;\n      for (const stopSeq of stopSequences) {\n        if (currentText.endsWith(stopSeq)) {\n          shouldStop = true;\n          break;\n        }\n      }\n      if (shouldStop) break;\n    }\n\n    // Decode generated text\n    const generatedText = this.tokenizer.decode(generatedIds, true);\n    const endTime = performance.now();\n\n    return {\n      generatedText,\n      fullText: returnFullText ? prompt + generatedText : undefined,\n      tokenIds: generatedIds,\n      numTokens: generatedIds.length,\n      processingTime: endTime - startTime,\n    };\n  }\n\n  /**\n   * Generate next token using the model\n   */\n  private async generateNextToken(\n    inputIds: number[],\n    temperature: number,\n    topK: number,\n    topP: number,\n    repetitionPenalty: number,\n    doSample: boolean\n  ): Promise<number> {\n    if (!this.model) {\n      throw new Error('Model not loaded');\n    }\n\n    const seqLen = inputIds.length;\n\n    // Prepare named inputs\n    const inputs = new Map<string, EdgeFlowTensor>();\n\n    // input_ids: [1, seq_len]\n    inputs.set('input_ids', new EdgeFlowTensor(\n      BigInt64Array.from(inputIds.map(id => BigInt(id))),\n      [1, seqLen],\n      'int64'\n    ));\n\n    // attention_mask: [1, seq_len]\n    inputs.set('attention_mask', new EdgeFlowTensor(\n      BigInt64Array.from(inputIds.map(() => BigInt(1))),\n      [1, seqLen],\n      'int64'\n    ));\n\n    // position_ids: [1, seq_len] - sequential positions from 0 to seq_len-1\n    inputs.set('position_ids', new EdgeFlowTensor(\n      BigInt64Array.from(Array.from({ length: seqLen }, (_, i) => BigInt(i))),\n      [1, seqLen],\n      'int64'\n    ));\n\n    // TinyLlama has 22 layers with GQA (4 KV heads, head_dim=64)\n    // For first inference without cache, provide empty past_key_values\n    const numLayers = 22;\n    const numKVHeads = 4;\n    const headDim = 64;\n    \n    for (let i = 0; i < numLayers; i++) {\n      // past_key_values.{i}.key: [batch, num_kv_heads, 0, head_dim]\n      inputs.set(`past_key_values.${i}.key`, new EdgeFlowTensor(\n        new Float32Array(0),\n        [1, numKVHeads, 0, headDim],\n        'float32'\n      ));\n      // past_key_values.{i}.value: [batch, num_kv_heads, 0, head_dim]\n      inputs.set(`past_key_values.${i}.value`, new EdgeFlowTensor(\n        new Float32Array(0),\n        [1, numKVHeads, 0, headDim],\n        'float32'\n      ));\n    }\n\n    // Run inference with named inputs\n    const outputs = await runInferenceNamed(this.model, inputs);\n    \n    if (!outputs || outputs.length === 0) {\n      throw new Error('Model returned no outputs');\n    }\n\n    // Get logits for last token\n    const logits = outputs[0]!;\n    const logitsData = logits.toFloat32Array();\n    const vocabSize = logits.shape[logits.shape.length - 1] ?? 50257;\n    \n    // Get logits for the last position\n    const lastPositionLogits = new Float32Array(vocabSize);\n    const offset = (inputIds.length - 1) * vocabSize;\n    \n    for (let i = 0; i < vocabSize; i++) {\n      lastPositionLogits[i] = logitsData[offset + i] ?? 0;\n    }\n\n    // Apply repetition penalty\n    if (repetitionPenalty !== 1.0) {\n      for (const prevId of inputIds) {\n        if (prevId < vocabSize) {\n          const score = lastPositionLogits[prevId] ?? 0;\n          lastPositionLogits[prevId] = score > 0 \n            ? score / repetitionPenalty \n            : score * repetitionPenalty;\n        }\n      }\n    }\n\n    // Apply temperature\n    if (temperature !== 1.0) {\n      for (let i = 0; i < vocabSize; i++) {\n        lastPositionLogits[i] = (lastPositionLogits[i] ?? 0) / temperature;\n      }\n    }\n\n    // Convert to probabilities\n    const logitsTensor = new EdgeFlowTensor(lastPositionLogits, [vocabSize], 'float32');\n    const probs = softmax(logitsTensor).toFloat32Array();\n\n    // Sample or greedy\n    if (doSample) {\n      return this.sample(probs, topK, topP);\n    } else {\n      return this.greedy(probs);\n    }\n  }\n\n  /**\n   * Greedy decoding (argmax)\n   */\n  private greedy(probs: Float32Array): number {\n    let maxIdx = 0;\n    let maxProb = probs[0] ?? 0;\n    \n    for (let i = 1; i < probs.length; i++) {\n      if ((probs[i] ?? 0) > maxProb) {\n        maxProb = probs[i] ?? 0;\n        maxIdx = i;\n      }\n    }\n    \n    return maxIdx;\n  }\n\n  /**\n   * Sample from probability distribution with top-k/top-p filtering\n   */\n  private sample(probs: Float32Array, topK: number, topP: number): number {\n    // Create sorted indices\n    const indices = Array.from({ length: probs.length }, (_, i) => i);\n    indices.sort((a, b) => (probs[b] ?? 0) - (probs[a] ?? 0));\n\n    // Apply top-k filtering\n    let candidateIndices = indices;\n    if (topK > 0 && topK < probs.length) {\n      candidateIndices = indices.slice(0, topK);\n    }\n\n    // Apply top-p (nucleus) filtering\n    if (topP < 1.0) {\n      let cumulativeProb = 0;\n      const filtered: number[] = [];\n      \n      for (const idx of candidateIndices) {\n        filtered.push(idx);\n        cumulativeProb += probs[idx] ?? 0;\n        if (cumulativeProb >= topP) break;\n      }\n      \n      candidateIndices = filtered;\n    }\n\n    // Renormalize probabilities\n    let totalProb = 0;\n    for (const idx of candidateIndices) {\n      totalProb += probs[idx] ?? 0;\n    }\n\n    // Sample\n    const r = Math.random() * totalProb;\n    let cumulative = 0;\n    \n    for (const idx of candidateIndices) {\n      cumulative += probs[idx] ?? 0;\n      if (cumulative >= r) {\n        return idx;\n      }\n    }\n\n    // Fallback\n    return candidateIndices[0] ?? 0;\n  }\n\n  // ==========================================================================\n  // Chat / Conversation Support\n  // ==========================================================================\n\n  private conversationHistory: ChatMessage[] = [];\n  private chatTemplateType: ChatTemplateType = 'chatml';\n\n  /**\n   * Set the chat template type\n   */\n  setChatTemplate(templateType: ChatTemplateType): void {\n    this.chatTemplateType = templateType;\n  }\n\n  /**\n   * Apply chat template to messages\n   */\n  applyChatTemplate(messages: ChatMessage[], options?: ChatOptions): string {\n    const templateType = options?.templateType ?? this.chatTemplateType;\n    \n    switch (templateType) {\n      case 'chatml':\n        return this.applyChatMLTemplate(messages);\n      case 'llama2':\n        return this.applyLlama2Template(messages);\n      case 'llama3':\n        return this.applyLlama3Template(messages);\n      case 'mistral':\n        return this.applyMistralTemplate(messages);\n      case 'phi3':\n        return this.applyPhi3Template(messages);\n      case 'alpaca':\n        return this.applyAlpacaTemplate(messages);\n      case 'vicuna':\n        return this.applyVicunaTemplate(messages);\n      case 'custom':\n        return this.applyCustomTemplate(messages, options?.customTemplate ?? {});\n      default:\n        return this.applyChatMLTemplate(messages);\n    }\n  }\n\n  /**\n   * ChatML template (used by many models including Qwen, Yi)\n   */\n  private applyChatMLTemplate(messages: ChatMessage[]): string {\n    let prompt = '';\n    for (const msg of messages) {\n      prompt += `<|im_start|>${msg.role}\\n${msg.content}<|im_end|>\\n`;\n    }\n    prompt += '<|im_start|>assistant\\n';\n    return prompt;\n  }\n\n  /**\n   * Llama 2 template\n   */\n  private applyLlama2Template(messages: ChatMessage[]): string {\n    let prompt = '';\n    let systemMsg = '';\n    \n    for (const msg of messages) {\n      if (msg.role === 'system') {\n        systemMsg = msg.content;\n      } else if (msg.role === 'user') {\n        if (systemMsg) {\n          prompt += `<s>[INST] <<SYS>>\\n${systemMsg}\\n<</SYS>>\\n\\n${msg.content} [/INST]`;\n          systemMsg = '';\n        } else {\n          prompt += `<s>[INST] ${msg.content} [/INST]`;\n        }\n      } else if (msg.role === 'assistant') {\n        prompt += ` ${msg.content} </s>`;\n      }\n    }\n    \n    return prompt;\n  }\n\n  /**\n   * Llama 3 template\n   */\n  private applyLlama3Template(messages: ChatMessage[]): string {\n    let prompt = '<|begin_of_text|>';\n    \n    for (const msg of messages) {\n      prompt += `<|start_header_id|>${msg.role}<|end_header_id|>\\n\\n${msg.content}<|eot_id|>`;\n    }\n    \n    prompt += '<|start_header_id|>assistant<|end_header_id|>\\n\\n';\n    return prompt;\n  }\n\n  /**\n   * Mistral template\n   */\n  private applyMistralTemplate(messages: ChatMessage[]): string {\n    let prompt = '<s>';\n    \n    for (const msg of messages) {\n      if (msg.role === 'user') {\n        prompt += `[INST] ${msg.content} [/INST]`;\n      } else if (msg.role === 'assistant') {\n        prompt += ` ${msg.content}</s>`;\n      } else if (msg.role === 'system') {\n        prompt += `[INST] ${msg.content}\\n`;\n      }\n    }\n    \n    return prompt;\n  }\n\n  /**\n   * Phi-3 template\n   */\n  private applyPhi3Template(messages: ChatMessage[]): string {\n    let prompt = '';\n    \n    for (const msg of messages) {\n      prompt += `<|${msg.role}|>\\n${msg.content}<|end|>\\n`;\n    }\n    \n    prompt += '<|assistant|>\\n';\n    return prompt;\n  }\n\n  /**\n   * Alpaca template\n   */\n  private applyAlpacaTemplate(messages: ChatMessage[]): string {\n    let prompt = '';\n    let instruction = '';\n    let input = '';\n    \n    for (const msg of messages) {\n      if (msg.role === 'system') {\n        instruction = msg.content;\n      } else if (msg.role === 'user') {\n        input = msg.content;\n      }\n    }\n    \n    if (instruction) {\n      prompt = `### Instruction:\\n${instruction}\\n\\n`;\n    }\n    if (input) {\n      prompt += `### Input:\\n${input}\\n\\n`;\n    }\n    prompt += '### Response:\\n';\n    \n    return prompt;\n  }\n\n  /**\n   * Vicuna template\n   */\n  private applyVicunaTemplate(messages: ChatMessage[]): string {\n    let prompt = '';\n    \n    for (const msg of messages) {\n      if (msg.role === 'system') {\n        prompt += `${msg.content}\\n\\n`;\n      } else if (msg.role === 'user') {\n        prompt += `USER: ${msg.content}\\n`;\n      } else if (msg.role === 'assistant') {\n        prompt += `ASSISTANT: ${msg.content}\\n`;\n      }\n    }\n    \n    prompt += 'ASSISTANT:';\n    return prompt;\n  }\n\n  /**\n   * Custom template\n   */\n  private applyCustomTemplate(\n    messages: ChatMessage[],\n    template: NonNullable<ChatOptions['customTemplate']>\n  ): string {\n    const {\n      systemPrefix = '',\n      systemSuffix = '\\n',\n      userPrefix = 'User: ',\n      userSuffix = '\\n',\n      assistantPrefix = 'Assistant: ',\n      assistantSuffix = '\\n',\n      separator = '',\n    } = template;\n    \n    let prompt = '';\n    \n    for (let i = 0; i < messages.length; i++) {\n      const msg = messages[i]!;\n      if (i > 0) prompt += separator;\n      \n      switch (msg.role) {\n        case 'system':\n          prompt += `${systemPrefix}${msg.content}${systemSuffix}`;\n          break;\n        case 'user':\n          prompt += `${userPrefix}${msg.content}${userSuffix}`;\n          break;\n        case 'assistant':\n          prompt += `${assistantPrefix}${msg.content}${assistantSuffix}`;\n          break;\n      }\n    }\n    \n    prompt += assistantPrefix;\n    return prompt;\n  }\n\n  /**\n   * Chat with the model\n   * \n   * @example\n   * ```typescript\n   * const generator = await pipeline('text-generation', 'model');\n   * \n   * // Single turn\n   * const response = await generator.chat('Hello, how are you?');\n   * \n   * // Multi-turn with history\n   * const response1 = await generator.chat('What is AI?');\n   * const response2 = await generator.chat('Can you give an example?');\n   * \n   * // With system prompt\n   * const response = await generator.chat('Hello', {\n   *   systemPrompt: 'You are a helpful assistant.',\n   * });\n   * ```\n   */\n  async chat(\n    userMessage: string,\n    options?: ChatOptions\n  ): Promise<TextGenerationResult> {\n    // Add system message if provided and not already present\n    if (options?.systemPrompt && \n        (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== 'system')) {\n      this.conversationHistory.unshift({\n        role: 'system',\n        content: options.systemPrompt,\n      });\n    }\n\n    // Add user message\n    this.conversationHistory.push({\n      role: 'user',\n      content: userMessage,\n    });\n\n    // Apply chat template\n    const prompt = this.applyChatTemplate(this.conversationHistory, options);\n\n    // Generate response\n    const result = await this.run(prompt, {\n      ...options,\n      stopSequences: [\n        ...(options?.stopSequences ?? []),\n        '<|im_end|>',\n        '<|end|>',\n        '<|eot_id|>',\n        '</s>',\n        '\\n\\nUser:',\n        '\\n\\nHuman:',\n      ],\n    });\n\n    // Add assistant response to history\n    const response = Array.isArray(result) ? result[0]! : result;\n    this.conversationHistory.push({\n      role: 'assistant',\n      content: response.generatedText.trim(),\n    });\n\n    return response;\n  }\n\n  /**\n   * Stream chat response\n   */\n  async *chatStream(\n    userMessage: string,\n    options?: ChatOptions\n  ): AsyncGenerator<GenerationStreamEvent> {\n    // Add system message if provided\n    if (options?.systemPrompt && \n        (this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== 'system')) {\n      this.conversationHistory.unshift({\n        role: 'system',\n        content: options.systemPrompt,\n      });\n    }\n\n    // Add user message\n    this.conversationHistory.push({\n      role: 'user',\n      content: userMessage,\n    });\n\n    // Apply chat template\n    const prompt = this.applyChatTemplate(this.conversationHistory, options);\n\n    // Stream response\n    let fullResponse = '';\n    for await (const event of this.stream(prompt, {\n      ...options,\n      stopSequences: [\n        ...(options?.stopSequences ?? []),\n        '<|im_end|>',\n        '<|end|>',\n        '<|eot_id|>',\n        '</s>',\n      ],\n    })) {\n      fullResponse = event.generatedText;\n      yield event;\n    }\n\n    // Add assistant response to history\n    this.conversationHistory.push({\n      role: 'assistant',\n      content: fullResponse.trim(),\n    });\n  }\n\n  /**\n   * Get conversation history\n   */\n  getConversationHistory(): ChatMessage[] {\n    return [...this.conversationHistory];\n  }\n\n  /**\n   * Set conversation history\n   */\n  setConversationHistory(messages: ChatMessage[]): void {\n    this.conversationHistory = [...messages];\n  }\n\n  /**\n   * Clear conversation history\n   */\n  clearConversation(): void {\n    this.conversationHistory = [];\n  }\n\n  /**\n   * Remove last exchange (user message + assistant response)\n   */\n  undoLastExchange(): void {\n    // Remove assistant message\n    if (this.conversationHistory.length > 0 && \n        this.conversationHistory[this.conversationHistory.length - 1]?.role === 'assistant') {\n      this.conversationHistory.pop();\n    }\n    // Remove user message\n    if (this.conversationHistory.length > 0 && \n        this.conversationHistory[this.conversationHistory.length - 1]?.role === 'user') {\n      this.conversationHistory.pop();\n    }\n  }\n\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create text generation pipeline\n */\nexport function createTextGenerationPipeline(config?: PipelineConfig): TextGenerationPipeline {\n  return new TextGenerationPipeline(config);\n}\n"
  },
  {
    "path": "src/pipelines/zero-shot-classification.ts",
    "content": "/**\n * edgeFlow.js - Zero-shot Classification Pipeline\n * \n * Classify text into any set of labels without fine-tuning,\n * using a real NLI (Natural Language Inference) model.\n */\n\nimport { BasePipeline, PipelineResult, registerPipeline } from './base.js';\nimport { EdgeFlowTensor, softmax } from '../core/tensor.js';\nimport { PipelineConfig, PipelineOptions, LoadedModel } from '../core/types.js';\nimport { Tokenizer } from '../utils/tokenizer.js';\nimport { loadModelData } from '../utils/model-loader.js';\nimport { loadModelFromBuffer, runInferenceNamed } from '../core/runtime.js';\n\n// ============================================================================\n// Default Model (DistilBART fine-tuned on MNLI)\n// ============================================================================\n\nconst DEFAULT_MODELS = {\n  model: 'https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/onnx/model_quantized.onnx',\n  tokenizer: 'https://huggingface.co/Xenova/nli-deberta-v3-small/resolve/main/tokenizer.json',\n};\n\n// NLI output indices: [contradiction, neutral, entailment]\nconst ENTAILMENT_IDX = 2;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ZeroShotClassificationOptions extends PipelineOptions {\n  multiLabel?: boolean;\n  hypothesisTemplate?: string;\n}\n\nexport interface ZeroShotClassificationResult extends PipelineResult {\n  sequence: string;\n  labels: string[];\n  scores: number[];\n}\n\nexport interface ZeroShotInput {\n  text: string | string[];\n  candidateLabels: string[];\n}\n\n// ============================================================================\n// Zero-shot Classification Pipeline\n// ============================================================================\n\nexport class ZeroShotClassificationPipeline extends BasePipeline<\n  ZeroShotInput,\n  ZeroShotClassificationResult | ZeroShotClassificationResult[]\n> {\n  private tokenizer: Tokenizer | null = null;\n  private onnxModel: LoadedModel | null = null;\n  private hypothesisTemplate: string = 'This text is about {label}.';\n  private modelUrl: string;\n  private tokenizerUrl: string;\n\n  constructor(config?: PipelineConfig) {\n    super(config ?? {\n      task: 'zero-shot-classification',\n      model: 'default',\n    });\n    this.modelUrl = (config?.model && config.model !== 'default') ? config.model : DEFAULT_MODELS.model;\n    this.tokenizerUrl = DEFAULT_MODELS.tokenizer;\n  }\n\n  override async initialize(): Promise<void> {\n    await super.initialize();\n\n    if (!this.tokenizer) {\n      this.tokenizer = await Tokenizer.fromUrl(this.tokenizerUrl);\n    }\n\n    if (!this.onnxModel) {\n      const modelData = await loadModelData(this.modelUrl, { cache: this.config.cache ?? true });\n      this.onnxModel = await loadModelFromBuffer(modelData);\n    }\n  }\n\n  setTokenizer(tokenizer: Tokenizer): void {\n    this.tokenizer = tokenizer;\n  }\n\n  async classify(\n    text: string | string[],\n    candidateLabels: string[],\n    options?: ZeroShotClassificationOptions\n  ): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]> {\n    return this.run({ text, candidateLabels }, options);\n  }\n\n  override async run(\n    input: ZeroShotInput,\n    options?: PipelineOptions\n  ): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]> {\n    await this.initialize();\n\n    const { text, candidateLabels } = input;\n    const opts = options as ZeroShotClassificationOptions ?? {};\n    const texts = Array.isArray(text) ? text : [text];\n    const template = opts.hypothesisTemplate ?? this.hypothesisTemplate;\n    const multiLabel = opts.multiLabel ?? false;\n\n    const results = await Promise.all(\n      texts.map(t => this.classifySingle(t, candidateLabels, template, multiLabel))\n    );\n\n    return Array.isArray(text) ? results : results[0]!;\n  }\n\n  private async classifySingle(\n    text: string,\n    candidateLabels: string[],\n    template: string,\n    multiLabel: boolean\n  ): Promise<ZeroShotClassificationResult> {\n    const startTime = performance.now();\n\n    const hypotheses = candidateLabels.map(label =>\n      template.replace('{label}', label)\n    );\n\n    const scores: number[] = [];\n\n    for (const hypothesis of hypotheses) {\n      const score = await this.scoreHypothesis(text, hypothesis);\n      scores.push(score);\n    }\n\n    let normalizedScores: number[];\n\n    if (multiLabel) {\n      normalizedScores = scores.map(s => 1 / (1 + Math.exp(-s)));\n    } else {\n      const tensor = new EdgeFlowTensor(new Float32Array(scores), [scores.length], 'float32');\n      normalizedScores = Array.from(softmax(tensor).toFloat32Array());\n    }\n\n    const indexed = candidateLabels.map((label, i) => ({\n      label,\n      score: normalizedScores[i] ?? 0,\n    }));\n    indexed.sort((a, b) => b.score - a.score);\n\n    return {\n      sequence: text,\n      labels: indexed.map(i => i.label),\n      scores: indexed.map(i => i.score),\n      processingTime: performance.now() - startTime,\n    };\n  }\n\n  /**\n   * Score a single hypothesis using the real NLI ONNX model.\n   * Returns the entailment logit.\n   */\n  private async scoreHypothesis(premise: string, hypothesis: string): Promise<number> {\n    const encoded = this.tokenizer!.encode(premise, {\n      textPair: hypothesis,\n      addSpecialTokens: true,\n      maxLength: 512,\n      truncation: true,\n      returnAttentionMask: true,\n    });\n\n    const inputIds = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, encoded.inputIds.length],\n      'int64'\n    );\n    const attentionMask = new EdgeFlowTensor(\n      BigInt64Array.from(encoded.attentionMask.map(m => BigInt(m))),\n      [1, encoded.attentionMask.length],\n      'int64'\n    );\n\n    const namedInputs = new Map<string, EdgeFlowTensor>();\n    namedInputs.set('input_ids', inputIds);\n    namedInputs.set('attention_mask', attentionMask);\n\n    const outputs = await runInferenceNamed(this.onnxModel!, namedInputs);\n    const logits = (outputs[0] as EdgeFlowTensor).toFloat32Array();\n\n    // Return entailment logit (index 2 in [contradiction, neutral, entailment])\n    return logits[ENTAILMENT_IDX] ?? 0;\n  }\n\n  protected async preprocess(\n    input: ZeroShotInput\n  ): Promise<EdgeFlowTensor[]> {\n    const { text, candidateLabels } = input;\n    const firstText = Array.isArray(text) ? text[0] ?? '' : text;\n    const firstLabel = candidateLabels[0] ?? '';\n\n    const encoded = this.tokenizer!.encode(firstText, {\n      textPair: this.hypothesisTemplate.replace('{label}', firstLabel),\n      addSpecialTokens: true,\n      maxLength: 512,\n    });\n\n    return [new EdgeFlowTensor(\n      BigInt64Array.from(encoded.inputIds.map(id => BigInt(id))),\n      [1, encoded.inputIds.length],\n      'int64'\n    )];\n  }\n\n  protected async postprocess(\n    _outputs: EdgeFlowTensor[],\n    _options?: PipelineOptions\n  ): Promise<ZeroShotClassificationResult | ZeroShotClassificationResult[]> {\n    return {\n      sequence: '',\n      labels: [],\n      scores: [],\n    };\n  }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createZeroShotClassificationPipeline(\n  config?: PipelineConfig\n): ZeroShotClassificationPipeline {\n  return new ZeroShotClassificationPipeline(config);\n}\n\nregisterPipeline('zero-shot-classification', (config) => new ZeroShotClassificationPipeline(config));\n"
  },
  {
    "path": "src/tools/benchmark.ts",
    "content": "/**\n * edgeFlow.js - Benchmark Utilities\n * \n * Performance testing and comparison tools.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BenchmarkOptions {\n  /** Number of warmup runs (default: 3) */\n  warmupRuns?: number;\n  \n  /** Number of measured runs (default: 10) */\n  runs?: number;\n  \n  /** Whether to log progress (default: true) */\n  verbose?: boolean;\n  \n  /** Timeout per run in ms (default: 30000) */\n  timeout?: number;\n  \n  /** Name for this benchmark */\n  name?: string;\n}\n\nexport interface BenchmarkResult {\n  name: string;\n  \n  /** Average time in ms */\n  avgTime: number;\n  \n  /** Median time in ms */\n  medianTime: number;\n  \n  /** Minimum time in ms */\n  minTime: number;\n  \n  /** Maximum time in ms */\n  maxTime: number;\n  \n  /** Standard deviation in ms */\n  stdDev: number;\n  \n  /** 95th percentile in ms */\n  p95: number;\n  \n  /** 99th percentile in ms */\n  p99: number;\n  \n  /** Throughput (ops/sec) */\n  throughput: number;\n  \n  /** All individual run times */\n  times: number[];\n  \n  /** Number of runs */\n  totalRuns: number;\n  \n  /** Number of failed runs */\n  failedRuns: number;\n}\n\nexport interface CompareBenchmarkResult {\n  baseline: BenchmarkResult;\n  comparison: BenchmarkResult;\n  speedup: number;\n  percentFaster: number;\n  winner: 'baseline' | 'comparison' | 'tie';\n}\n\n// ============================================================================\n// Benchmark Functions\n// ============================================================================\n\n/**\n * Run a benchmark on an async function\n */\nexport async function benchmark(\n  fn: () => Promise<unknown> | unknown,\n  options: BenchmarkOptions = {}\n): Promise<BenchmarkResult> {\n  const {\n    warmupRuns = 3,\n    runs = 10,\n    verbose = false,\n    timeout = 30000,\n    name = 'benchmark',\n  } = options;\n\n  const times: number[] = [];\n  let failedRuns = 0;\n\n  // Warmup\n  if (verbose) console.log(`[${name}] Running ${warmupRuns} warmup iterations...`);\n  for (let i = 0; i < warmupRuns; i++) {\n    try {\n      await Promise.race([\n        Promise.resolve(fn()),\n        new Promise((_, reject) => \n          setTimeout(() => reject(new Error('Timeout')), timeout)\n        ),\n      ]);\n    } catch {\n      // Warmup failures are ignored\n    }\n  }\n\n  // Measured runs\n  if (verbose) console.log(`[${name}] Running ${runs} measured iterations...`);\n  for (let i = 0; i < runs; i++) {\n    try {\n      const start = performance.now();\n      await Promise.race([\n        Promise.resolve(fn()),\n        new Promise((_, reject) => \n          setTimeout(() => reject(new Error('Timeout')), timeout)\n        ),\n      ]);\n      const end = performance.now();\n      times.push(end - start);\n      \n      if (verbose) console.log(`  Run ${i + 1}: ${(end - start).toFixed(2)}ms`);\n    } catch (error) {\n      failedRuns++;\n      if (verbose) console.log(`  Run ${i + 1}: FAILED - ${error}`);\n    }\n  }\n\n  if (times.length === 0) {\n    throw new Error(`All ${runs} runs failed`);\n  }\n\n  // Calculate statistics\n  const sorted = [...times].sort((a, b) => a - b);\n  const sum = times.reduce((a, b) => a + b, 0);\n  const avg = sum / times.length;\n  const variance = times.reduce((sum, t) => sum + Math.pow(t - avg, 2), 0) / times.length;\n  const stdDev = Math.sqrt(variance);\n\n  const result: BenchmarkResult = {\n    name,\n    avgTime: avg,\n    medianTime: sorted[Math.floor(sorted.length / 2)] ?? 0,\n    minTime: sorted[0] ?? 0,\n    maxTime: sorted[sorted.length - 1] ?? 0,\n    stdDev,\n    p95: sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1] ?? 0,\n    p99: sorted[Math.floor(sorted.length * 0.99)] ?? sorted[sorted.length - 1] ?? 0,\n    throughput: 1000 / avg,\n    times,\n    totalRuns: runs,\n    failedRuns,\n  };\n\n  if (verbose) {\n    console.log(`\\n[${name}] Results:`);\n    console.log(`  Avg: ${result.avgTime.toFixed(2)}ms`);\n    console.log(`  Median: ${result.medianTime.toFixed(2)}ms`);\n    console.log(`  Min: ${result.minTime.toFixed(2)}ms`);\n    console.log(`  Max: ${result.maxTime.toFixed(2)}ms`);\n    console.log(`  Std Dev: ${result.stdDev.toFixed(2)}ms`);\n    console.log(`  P95: ${result.p95.toFixed(2)}ms`);\n    console.log(`  Throughput: ${result.throughput.toFixed(2)} ops/sec`);\n  }\n\n  return result;\n}\n\n/**\n * Compare two benchmarks\n */\nexport async function compareBenchmarks(\n  baseline: () => Promise<unknown> | unknown,\n  comparison: () => Promise<unknown> | unknown,\n  options: BenchmarkOptions = {}\n): Promise<CompareBenchmarkResult> {\n  const baselineResult = await benchmark(baseline, { \n    ...options, \n    name: options.name ? `${options.name} (baseline)` : 'baseline' \n  });\n  \n  const comparisonResult = await benchmark(comparison, { \n    ...options, \n    name: options.name ? `${options.name} (comparison)` : 'comparison' \n  });\n\n  const speedup = baselineResult.avgTime / comparisonResult.avgTime;\n  const percentFaster = ((baselineResult.avgTime - comparisonResult.avgTime) / baselineResult.avgTime) * 100;\n\n  let winner: 'baseline' | 'comparison' | 'tie';\n  if (Math.abs(percentFaster) < 5) {\n    winner = 'tie';\n  } else if (percentFaster > 0) {\n    winner = 'comparison';\n  } else {\n    winner = 'baseline';\n  }\n\n  return {\n    baseline: baselineResult,\n    comparison: comparisonResult,\n    speedup,\n    percentFaster,\n    winner,\n  };\n}\n\n/**\n * Run multiple benchmarks in a suite\n */\nexport async function benchmarkSuite(\n  suite: Record<string, () => Promise<unknown> | unknown>,\n  options: BenchmarkOptions = {}\n): Promise<Record<string, BenchmarkResult>> {\n  const results: Record<string, BenchmarkResult> = {};\n\n  for (const [name, fn] of Object.entries(suite)) {\n    console.log(`\\n=== ${name} ===`);\n    results[name] = await benchmark(fn, { ...options, name, verbose: true });\n  }\n\n  return results;\n}\n\n/**\n * Format benchmark result as a table string\n */\nexport function formatBenchmarkResult(result: BenchmarkResult): string {\n  return `\n┌─────────────────────────────────────────┐\n│ ${result.name.padEnd(39)} │\n├─────────────────────────────────────────┤\n│ Avg Time:    ${result.avgTime.toFixed(2).padStart(10)}ms             │\n│ Median:      ${result.medianTime.toFixed(2).padStart(10)}ms             │\n│ Min Time:    ${result.minTime.toFixed(2).padStart(10)}ms             │\n│ Max Time:    ${result.maxTime.toFixed(2).padStart(10)}ms             │\n│ Std Dev:     ${result.stdDev.toFixed(2).padStart(10)}ms             │\n│ P95:         ${result.p95.toFixed(2).padStart(10)}ms             │\n│ P99:         ${result.p99.toFixed(2).padStart(10)}ms             │\n│ Throughput:  ${result.throughput.toFixed(2).padStart(10)} ops/sec     │\n│ Runs:        ${result.totalRuns.toString().padStart(10)} (${result.failedRuns} failed)  │\n└─────────────────────────────────────────┘\n  `.trim();\n}\n\n/**\n * Format comparison result\n */\nexport function formatComparisonResult(result: CompareBenchmarkResult): string {\n  const arrow = result.percentFaster > 0 ? '↑' : result.percentFaster < 0 ? '↓' : '=';\n  const winnerText = result.winner === 'comparison' \n    ? 'Comparison is faster!'\n    : result.winner === 'baseline'\n    ? 'Baseline is faster!'\n    : 'Results are similar';\n\n  return `\n┌─────────────────────────────────────────────────────┐\n│                  BENCHMARK COMPARISON               │\n├─────────────────────────────────────────────────────┤\n│ Baseline:    ${result.baseline.avgTime.toFixed(2).padStart(10)}ms                       │\n│ Comparison:  ${result.comparison.avgTime.toFixed(2).padStart(10)}ms                       │\n├─────────────────────────────────────────────────────┤\n│ Speedup:     ${result.speedup.toFixed(2).padStart(10)}x                        │\n│ Difference:  ${arrow} ${Math.abs(result.percentFaster).toFixed(1).padStart(8)}%                      │\n├─────────────────────────────────────────────────────┤\n│ Winner: ${winnerText.padEnd(42)} │\n└─────────────────────────────────────────────────────┘\n  `.trim();\n}\n\n// ============================================================================\n// Memory Benchmark\n// ============================================================================\n\nexport interface MemoryBenchmarkResult {\n  name: string;\n  peakMemory: number;\n  avgMemory: number;\n  memoryDelta: number;\n}\n\n/**\n * Benchmark memory usage\n */\nexport async function benchmarkMemory(\n  fn: () => Promise<unknown> | unknown,\n  options: { name?: string; runs?: number } = {}\n): Promise<MemoryBenchmarkResult> {\n  const { name = 'memory-benchmark', runs = 5 } = options;\n\n  // Note: Memory APIs are limited in browsers\n  // This is a simplified version that works when performance.memory is available\n  const getMemory = (): number => {\n    if (typeof performance !== 'undefined' && 'memory' in performance) {\n      return (performance as { memory: { usedJSHeapSize: number } }).memory.usedJSHeapSize;\n    }\n    return 0;\n  };\n\n  const memoryReadings: number[] = [];\n  const initialMemory = getMemory();\n\n  for (let i = 0; i < runs; i++) {\n    await fn();\n    memoryReadings.push(getMemory());\n  }\n\n  const peakMemory = Math.max(...memoryReadings);\n  const avgMemory = memoryReadings.reduce((a, b) => a + b, 0) / memoryReadings.length;\n  const memoryDelta = avgMemory - initialMemory;\n\n  return {\n    name,\n    peakMemory,\n    avgMemory,\n    memoryDelta,\n  };\n}\n\n// ============================================================================\n// Export\n// ============================================================================\n\nexport default {\n  benchmark,\n  compareBenchmarks,\n  benchmarkSuite,\n  benchmarkMemory,\n  formatBenchmarkResult,\n  formatComparisonResult,\n};\n"
  },
  {
    "path": "src/tools/debugger.ts",
    "content": "/**\n * edgeFlow.js - Visual Debugging Tools\n * \n * In-browser debugging and visualization utilities for ML models.\n */\n\nimport { EdgeFlowTensor } from '../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Debugger configuration\n */\nexport interface DebuggerConfig {\n  /** Enable logging */\n  logging?: boolean;\n  \n  /** Log level */\n  logLevel?: 'debug' | 'info' | 'warn' | 'error';\n  \n  /** Enable tensor inspection */\n  inspectTensors?: boolean;\n  \n  /** Maximum values to display per tensor */\n  maxDisplayValues?: number;\n  \n  /** Enable performance tracking */\n  trackPerformance?: boolean;\n  \n  /** Custom logger function */\n  logger?: (level: string, message: string, data?: unknown) => void;\n}\n\n/**\n * Tensor inspection result\n */\nexport interface TensorInspection {\n  name: string;\n  shape: number[];\n  dtype: string;\n  size: number;\n  memoryBytes: number;\n  stats: TensorStats;\n  sample: number[];\n  histogram?: HistogramData;\n}\n\n/**\n * Tensor statistics\n */\nexport interface TensorStats {\n  min: number;\n  max: number;\n  mean: number;\n  std: number;\n  zeros: number;\n  nans: number;\n  infinities: number;\n  sparsity: number;\n}\n\n/**\n * Histogram data\n */\nexport interface HistogramData {\n  bins: number[];\n  counts: number[];\n  binEdges: number[];\n}\n\n/**\n * Inference trace\n */\nexport interface InferenceTrace {\n  id: string;\n  modelId: string;\n  timestamp: number;\n  inputs: TensorInspection[];\n  outputs: TensorInspection[];\n  duration: number;\n  memoryUsed: number;\n  operations: OperationTrace[];\n}\n\n/**\n * Operation trace\n */\nexport interface OperationTrace {\n  name: string;\n  type: string;\n  duration: number;\n  inputShapes: number[][];\n  outputShapes: number[][];\n  attributes?: Record<string, unknown>;\n}\n\n/**\n * Debug event\n */\nexport interface DebugEvent {\n  type: 'tensor' | 'inference' | 'error' | 'warning' | 'info' | 'performance';\n  timestamp: number;\n  data: unknown;\n  message: string;\n}\n\n/**\n * Performance metrics\n */\nexport interface PerformanceMetrics {\n  inferenceCount: number;\n  totalInferenceTime: number;\n  averageInferenceTime: number;\n  minInferenceTime: number;\n  maxInferenceTime: number;\n  peakMemoryUsage: number;\n  currentMemoryUsage: number;\n  tensorAllocations: number;\n  tensorDeallocations: number;\n}\n\n// ============================================================================\n// Tensor Inspection\n// ============================================================================\n\n/**\n * Calculate tensor statistics\n */\nfunction calculateTensorStats(data: Float32Array | number[]): TensorStats {\n  const arr = data instanceof Float32Array ? data : new Float32Array(data);\n  \n  let min = Infinity;\n  let max = -Infinity;\n  let sum = 0;\n  let zeros = 0;\n  let nans = 0;\n  let infinities = 0;\n  \n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    \n    if (isNaN(val)) {\n      nans++;\n      continue;\n    }\n    \n    if (!isFinite(val)) {\n      infinities++;\n      continue;\n    }\n    \n    min = Math.min(min, val);\n    max = Math.max(max, val);\n    sum += val;\n    \n    if (val === 0) zeros++;\n  }\n  \n  const validCount = arr.length - nans - infinities;\n  const mean = validCount > 0 ? sum / validCount : 0;\n  \n  // Calculate std\n  let varianceSum = 0;\n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      varianceSum += Math.pow(val - mean, 2);\n    }\n  }\n  const std = validCount > 0 ? Math.sqrt(varianceSum / validCount) : 0;\n  \n  return {\n    min: min === Infinity ? 0 : min,\n    max: max === -Infinity ? 0 : max,\n    mean,\n    std,\n    zeros,\n    nans,\n    infinities,\n    sparsity: zeros / arr.length,\n  };\n}\n\n/**\n * Create histogram from data\n */\nfunction createHistogram(data: Float32Array | number[], bins: number = 50): HistogramData {\n  const arr = data instanceof Float32Array ? data : new Float32Array(data);\n  \n  // Find min/max (excluding NaN/Inf)\n  let min = Infinity;\n  let max = -Infinity;\n  \n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n  }\n  \n  if (min === Infinity || max === -Infinity || min === max) {\n    return { bins: [min || 0], counts: [arr.length], binEdges: [min || 0, max || 0] };\n  }\n  \n  const binWidth = (max - min) / bins;\n  const counts = new Array(bins).fill(0);\n  const binEdges = new Array(bins + 1);\n  \n  for (let i = 0; i <= bins; i++) {\n    binEdges[i] = min + i * binWidth;\n  }\n  \n  for (let i = 0; i < arr.length; i++) {\n    const val = arr[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      const binIndex = Math.min(Math.floor((val - min) / binWidth), bins - 1);\n      counts[binIndex]++;\n    }\n  }\n  \n  return {\n    bins: binEdges.slice(0, -1).map((e, i) => (e + binEdges[i + 1]!) / 2),\n    counts,\n    binEdges,\n  };\n}\n\n/**\n * Inspect a tensor\n */\nexport function inspectTensor(\n  tensor: EdgeFlowTensor,\n  name: string = 'tensor',\n  options: { histogram?: boolean; maxSample?: number } = {}\n): TensorInspection {\n  const { histogram = true, maxSample = 10 } = options;\n  \n  const data = tensor.toFloat32Array();\n  const shape = tensor.shape as number[];\n  const size = tensor.size;\n  \n  // Get sample of values\n  const sampleIndices = [];\n  const step = Math.max(1, Math.floor(size / maxSample));\n  for (let i = 0; i < size && sampleIndices.length < maxSample; i += step) {\n    sampleIndices.push(i);\n  }\n  const sample = sampleIndices.map(i => data[i] ?? 0);\n  \n  // Calculate memory (assuming float32)\n  const bytesPerElement = tensor.dtype === 'float32' ? 4 \n    : tensor.dtype === 'int32' ? 4 \n    : tensor.dtype === 'int64' ? 8 \n    : 4;\n  const memoryBytes = size * bytesPerElement;\n  \n  return {\n    name,\n    shape,\n    dtype: tensor.dtype,\n    size,\n    memoryBytes,\n    stats: calculateTensorStats(data),\n    sample,\n    histogram: histogram ? createHistogram(data) : undefined,\n  };\n}\n\n/**\n * Format tensor inspection for display\n */\nexport function formatTensorInspection(inspection: TensorInspection): string {\n  const { name, shape, dtype, size, memoryBytes, stats, sample } = inspection;\n  \n  const lines = [\n    `┌─ Tensor: ${name} ─────────────────────────────`,\n    `│ Shape: [${shape.join(', ')}]`,\n    `│ Dtype: ${dtype}`,\n    `│ Size: ${size.toLocaleString()} elements`,\n    `│ Memory: ${formatBytes(memoryBytes)}`,\n    `├─ Statistics ─────────────────────────────────`,\n    `│ Min: ${stats.min.toFixed(6)}`,\n    `│ Max: ${stats.max.toFixed(6)}`,\n    `│ Mean: ${stats.mean.toFixed(6)}`,\n    `│ Std: ${stats.std.toFixed(6)}`,\n    `│ Sparsity: ${(stats.sparsity * 100).toFixed(2)}%`,\n  ];\n  \n  if (stats.nans > 0) {\n    lines.push(`│ ⚠️ NaN values: ${stats.nans}`);\n  }\n  if (stats.infinities > 0) {\n    lines.push(`│ ⚠️ Infinity values: ${stats.infinities}`);\n  }\n  \n  lines.push(`├─ Sample Values ──────────────────────────────`);\n  lines.push(`│ [${sample.map(v => v.toFixed(4)).join(', ')}]`);\n  lines.push(`└──────────────────────────────────────────────`);\n  \n  return lines.join('\\n');\n}\n\n/**\n * Format bytes to human readable\n */\nfunction formatBytes(bytes: number): string {\n  if (bytes < 1024) return `${bytes} B`;\n  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;\n  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n  return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\n// ============================================================================\n// Visual Debugger Class\n// ============================================================================\n\n/**\n * Visual debugger for edgeFlow.js\n */\nexport class EdgeFlowDebugger {\n  private config: Required<DebuggerConfig>;\n  private events: DebugEvent[] = [];\n  private traces: InferenceTrace[] = [];\n  private performanceMetrics: PerformanceMetrics;\n  private listeners: Map<string, Array<(event: DebugEvent) => void>> = new Map();\n  private isEnabled: boolean = true;\n  \n  constructor(config: DebuggerConfig = {}) {\n    this.config = {\n      logging: config.logging ?? true,\n      logLevel: config.logLevel ?? 'info',\n      inspectTensors: config.inspectTensors ?? true,\n      maxDisplayValues: config.maxDisplayValues ?? 10,\n      trackPerformance: config.trackPerformance ?? true,\n      logger: config.logger ?? this.defaultLogger.bind(this),\n    };\n    \n    this.performanceMetrics = {\n      inferenceCount: 0,\n      totalInferenceTime: 0,\n      averageInferenceTime: 0,\n      minInferenceTime: Infinity,\n      maxInferenceTime: 0,\n      peakMemoryUsage: 0,\n      currentMemoryUsage: 0,\n      tensorAllocations: 0,\n      tensorDeallocations: 0,\n    };\n  }\n  \n  /**\n   * Default logger\n   */\n  private defaultLogger(level: string, message: string, data?: unknown): void {\n    const timestamp = new Date().toISOString();\n    const prefix = `[edgeFlow.js ${timestamp}] [${level.toUpperCase()}]`;\n    \n    switch (level) {\n      case 'debug':\n        console.debug(prefix, message, data ?? '');\n        break;\n      case 'info':\n        console.info(prefix, message, data ?? '');\n        break;\n      case 'warn':\n        console.warn(prefix, message, data ?? '');\n        break;\n      case 'error':\n        console.error(prefix, message, data ?? '');\n        break;\n      default:\n        console.log(prefix, message, data ?? '');\n    }\n  }\n  \n  /**\n   * Log a message\n   */\n  log(level: string, message: string, data?: unknown): void {\n    if (!this.isEnabled || !this.config.logging) return;\n    \n    const levels = ['debug', 'info', 'warn', 'error'];\n    const configLevel = levels.indexOf(this.config.logLevel);\n    const msgLevel = levels.indexOf(level);\n    \n    if (msgLevel >= configLevel) {\n      this.config.logger(level, message, data);\n    }\n  }\n  \n  /**\n   * Add debug event\n   */\n  private addEvent(event: DebugEvent): void {\n    this.events.push(event);\n    \n    // Notify listeners\n    const listeners = this.listeners.get(event.type) ?? [];\n    for (const listener of listeners) {\n      listener(event);\n    }\n    \n    // Keep only last 1000 events\n    if (this.events.length > 1000) {\n      this.events = this.events.slice(-1000);\n    }\n  }\n  \n  /**\n   * Enable debugger\n   */\n  enable(): void {\n    this.isEnabled = true;\n    this.log('info', 'Debugger enabled');\n  }\n  \n  /**\n   * Disable debugger\n   */\n  disable(): void {\n    this.isEnabled = false;\n  }\n  \n  /**\n   * Subscribe to events\n   */\n  on(type: string, callback: (event: DebugEvent) => void): () => void {\n    const listeners = this.listeners.get(type) ?? [];\n    listeners.push(callback);\n    this.listeners.set(type, listeners);\n    \n    return () => {\n      const idx = listeners.indexOf(callback);\n      if (idx !== -1) listeners.splice(idx, 1);\n    };\n  }\n  \n  /**\n   * Inspect and log a tensor\n   */\n  inspectTensor(tensor: EdgeFlowTensor, name: string = 'tensor'): TensorInspection {\n    const inspection = inspectTensor(tensor, name, {\n      histogram: true,\n      maxSample: this.config.maxDisplayValues,\n    });\n    \n    if (this.config.inspectTensors) {\n      this.log('debug', `Tensor: ${name}`, inspection);\n      \n      this.addEvent({\n        type: 'tensor',\n        timestamp: Date.now(),\n        message: `Inspected tensor: ${name}`,\n        data: inspection,\n      });\n      \n      // Check for issues\n      if (inspection.stats.nans > 0) {\n        this.log('warn', `Tensor \"${name}\" contains ${inspection.stats.nans} NaN values`);\n      }\n      if (inspection.stats.infinities > 0) {\n        this.log('warn', `Tensor \"${name}\" contains ${inspection.stats.infinities} Infinity values`);\n      }\n    }\n    \n    return inspection;\n  }\n  \n  /**\n   * Start tracing an inference\n   */\n  startTrace(modelId: string): string {\n    const id = `trace_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n    \n    const trace: InferenceTrace = {\n      id,\n      modelId,\n      timestamp: Date.now(),\n      inputs: [],\n      outputs: [],\n      duration: 0,\n      memoryUsed: 0,\n      operations: [],\n    };\n    \n    this.traces.push(trace);\n    \n    this.log('debug', `Started trace: ${id} for model: ${modelId}`);\n    \n    return id;\n  }\n  \n  /**\n   * Add input to trace\n   */\n  traceInput(traceId: string, tensor: EdgeFlowTensor, name: string): void {\n    const trace = this.traces.find(t => t.id === traceId);\n    if (!trace) return;\n    \n    trace.inputs.push(inspectTensor(tensor, name));\n  }\n  \n  /**\n   * Add output to trace\n   */\n  traceOutput(traceId: string, tensor: EdgeFlowTensor, name: string): void {\n    const trace = this.traces.find(t => t.id === traceId);\n    if (!trace) return;\n    \n    trace.outputs.push(inspectTensor(tensor, name));\n  }\n  \n  /**\n   * Add operation to trace\n   */\n  traceOperation(traceId: string, operation: OperationTrace): void {\n    const trace = this.traces.find(t => t.id === traceId);\n    if (!trace) return;\n    \n    trace.operations.push(operation);\n  }\n  \n  /**\n   * End trace\n   */\n  endTrace(traceId: string): InferenceTrace | undefined {\n    const trace = this.traces.find(t => t.id === traceId);\n    if (!trace) return;\n    \n    trace.duration = Date.now() - trace.timestamp;\n    \n    // Update performance metrics\n    this.performanceMetrics.inferenceCount++;\n    this.performanceMetrics.totalInferenceTime += trace.duration;\n    this.performanceMetrics.averageInferenceTime = \n      this.performanceMetrics.totalInferenceTime / this.performanceMetrics.inferenceCount;\n    this.performanceMetrics.minInferenceTime = \n      Math.min(this.performanceMetrics.minInferenceTime, trace.duration);\n    this.performanceMetrics.maxInferenceTime = \n      Math.max(this.performanceMetrics.maxInferenceTime, trace.duration);\n    \n    this.log('info', `Trace completed: ${traceId}`, {\n      duration: `${trace.duration}ms`,\n      inputs: trace.inputs.length,\n      outputs: trace.outputs.length,\n      operations: trace.operations.length,\n    });\n    \n    this.addEvent({\n      type: 'inference',\n      timestamp: Date.now(),\n      message: `Inference completed in ${trace.duration}ms`,\n      data: trace,\n    });\n    \n    return trace;\n  }\n  \n  /**\n   * Record tensor allocation\n   */\n  recordAllocation(tensor: EdgeFlowTensor): void {\n    if (!this.config.trackPerformance) return;\n    \n    this.performanceMetrics.tensorAllocations++;\n    const memory = tensor.size * 4; // Assume float32\n    this.performanceMetrics.currentMemoryUsage += memory;\n    this.performanceMetrics.peakMemoryUsage = Math.max(\n      this.performanceMetrics.peakMemoryUsage,\n      this.performanceMetrics.currentMemoryUsage\n    );\n  }\n  \n  /**\n   * Record tensor deallocation\n   */\n  recordDeallocation(tensor: EdgeFlowTensor): void {\n    if (!this.config.trackPerformance) return;\n    \n    this.performanceMetrics.tensorDeallocations++;\n    const memory = tensor.size * 4;\n    this.performanceMetrics.currentMemoryUsage -= memory;\n  }\n  \n  /**\n   * Get performance metrics\n   */\n  getPerformanceMetrics(): PerformanceMetrics {\n    return { ...this.performanceMetrics };\n  }\n  \n  /**\n   * Get all events\n   */\n  getEvents(): DebugEvent[] {\n    return [...this.events];\n  }\n  \n  /**\n   * Get all traces\n   */\n  getTraces(): InferenceTrace[] {\n    return [...this.traces];\n  }\n  \n  /**\n   * Get trace by ID\n   */\n  getTrace(traceId: string): InferenceTrace | undefined {\n    return this.traces.find(t => t.id === traceId);\n  }\n  \n  /**\n   * Clear all data\n   */\n  clear(): void {\n    this.events = [];\n    this.traces = [];\n    this.performanceMetrics = {\n      inferenceCount: 0,\n      totalInferenceTime: 0,\n      averageInferenceTime: 0,\n      minInferenceTime: Infinity,\n      maxInferenceTime: 0,\n      peakMemoryUsage: 0,\n      currentMemoryUsage: 0,\n      tensorAllocations: 0,\n      tensorDeallocations: 0,\n    };\n  }\n  \n  /**\n   * Export debug data\n   */\n  export(): {\n    events: DebugEvent[];\n    traces: InferenceTrace[];\n    metrics: PerformanceMetrics;\n    timestamp: number;\n  } {\n    return {\n      events: this.getEvents(),\n      traces: this.getTraces(),\n      metrics: this.getPerformanceMetrics(),\n      timestamp: Date.now(),\n    };\n  }\n  \n  /**\n   * Generate summary report\n   */\n  generateReport(): string {\n    const metrics = this.getPerformanceMetrics();\n    const traces = this.getTraces();\n    \n    const lines = [\n      '╔══════════════════════════════════════════════════════════════════╗',\n      '║               edgeFlow.js Debug Report                          ║',\n      '╠══════════════════════════════════════════════════════════════════╣',\n      '║ Performance Metrics                                             ║',\n      '╟──────────────────────────────────────────────────────────────────╢',\n      `║ Total Inferences:     ${metrics.inferenceCount.toString().padStart(10)}                          ║`,\n      `║ Average Time:         ${metrics.averageInferenceTime.toFixed(2).padStart(10)}ms                       ║`,\n      `║ Min Time:             ${(metrics.minInferenceTime === Infinity ? 0 : metrics.minInferenceTime).toFixed(2).padStart(10)}ms                       ║`,\n      `║ Max Time:             ${metrics.maxInferenceTime.toFixed(2).padStart(10)}ms                       ║`,\n      `║ Peak Memory:          ${formatBytes(metrics.peakMemoryUsage).padStart(10)}                          ║`,\n      `║ Current Memory:       ${formatBytes(metrics.currentMemoryUsage).padStart(10)}                          ║`,\n      `║ Tensor Allocations:   ${metrics.tensorAllocations.toString().padStart(10)}                          ║`,\n      `║ Tensor Deallocations: ${metrics.tensorDeallocations.toString().padStart(10)}                          ║`,\n      '╟──────────────────────────────────────────────────────────────────╢',\n      '║ Recent Traces                                                   ║',\n      '╟──────────────────────────────────────────────────────────────────╢',\n    ];\n    \n    const recentTraces = traces.slice(-5);\n    for (const trace of recentTraces) {\n      lines.push(`║ ${trace.id.slice(0, 20).padEnd(20)} | ${trace.duration.toFixed(2).padStart(8)}ms | ${trace.modelId.slice(0, 20).padEnd(20)} ║`);\n    }\n    \n    if (recentTraces.length === 0) {\n      lines.push('║ No traces recorded                                              ║');\n    }\n    \n    lines.push('╚══════════════════════════════════════════════════════════════════╝');\n    \n    return lines.join('\\n');\n  }\n}\n\n// ============================================================================\n// Global Debugger Instance\n// ============================================================================\n\nlet globalDebugger: EdgeFlowDebugger | null = null;\n\n/**\n * Get or create the global debugger instance\n */\nexport function getDebugger(config?: DebuggerConfig): EdgeFlowDebugger {\n  if (!globalDebugger || config) {\n    globalDebugger = new EdgeFlowDebugger(config);\n  }\n  return globalDebugger;\n}\n\n/**\n * Enable debugging\n */\nexport function enableDebugging(config?: DebuggerConfig): EdgeFlowDebugger {\n  const debugger_ = getDebugger(config);\n  debugger_.enable();\n  return debugger_;\n}\n\n/**\n * Disable debugging\n */\nexport function disableDebugging(): void {\n  globalDebugger?.disable();\n}\n\n// ============================================================================\n// Visualization Helpers\n// ============================================================================\n\n/**\n * Create ASCII histogram\n */\nexport function createAsciiHistogram(histogram: HistogramData, width: number = 50, height: number = 10): string {\n  const { counts, binEdges } = histogram;\n  const maxCount = Math.max(...counts);\n  \n  if (maxCount === 0) return 'No data to display';\n  \n  const lines: string[] = [];\n  \n  // Scale counts to height\n  const scaled = counts.map(c => Math.round((c / maxCount) * height));\n  \n  // Create rows\n  for (let row = height; row > 0; row--) {\n    let line = row === height ? `${maxCount.toString().padStart(6)} │` : '       │';\n    \n    for (let col = 0; col < width && col < scaled.length; col++) {\n      line += (scaled[col] ?? 0) >= row ? '█' : ' ';\n    }\n    \n    lines.push(line);\n  }\n  \n  // X axis\n  lines.push('       └' + '─'.repeat(Math.min(width, scaled.length)));\n  \n  // Labels\n  const minLabel = (binEdges[0] ?? 0).toFixed(2);\n  const maxLabel = (binEdges[binEdges.length - 1] ?? 0).toFixed(2);\n  lines.push(`        ${minLabel}${' '.repeat(Math.max(0, Math.min(width, scaled.length) - minLabel.length - maxLabel.length))}${maxLabel}`);\n  \n  return lines.join('\\n');\n}\n\n/**\n * Create tensor heatmap (for 2D tensors)\n */\nexport function createTensorHeatmap(tensor: EdgeFlowTensor, width: number = 40): string {\n  const shape = tensor.shape as number[];\n  \n  if (shape.length !== 2) {\n    return 'Heatmap only supports 2D tensors';\n  }\n  \n  const [rows, cols] = shape;\n  if (rows === undefined || cols === undefined) {\n    return 'Invalid tensor shape';\n  }\n  \n  const data = tensor.toFloat32Array();\n  \n  // Find min/max\n  let min = Infinity;\n  let max = -Infinity;\n  for (let i = 0; i < data.length; i++) {\n    const val = data[i] ?? 0;\n    if (!isNaN(val) && isFinite(val)) {\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n  }\n  \n  const range = max - min;\n  const chars = [' ', '░', '▒', '▓', '█'];\n  \n  const lines: string[] = [];\n  const scaleX = Math.max(1, Math.ceil(cols / width));\n  const displayCols = Math.min(cols, width);\n  \n  for (let r = 0; r < rows; r++) {\n    let line = '';\n    for (let c = 0; c < displayCols; c++) {\n      const idx = r * cols + c * scaleX;\n      const val = data[idx] ?? 0;\n      const normalized = range > 0 ? (val - min) / range : 0;\n      const charIdx = Math.floor(normalized * (chars.length - 1));\n      line += chars[charIdx];\n    }\n    lines.push(line);\n  }\n  \n  return lines.join('\\n');\n}\n\n/**\n * Create model architecture visualization\n */\nexport function visualizeModelArchitecture(\n  layers: Array<{ name: string; type: string; inputShape: number[]; outputShape: number[] }>\n): string {\n  const lines: string[] = [];\n  \n  lines.push('┌─────────────────────────────────────────────────────────────────────┐');\n  lines.push('│                        Model Architecture                          │');\n  lines.push('├─────────────────────────────────────────────────────────────────────┤');\n  \n  for (let i = 0; i < layers.length; i++) {\n    const layer = layers[i]!;\n    const inputStr = `[${layer.inputShape.join('×')}]`;\n    const outputStr = `[${layer.outputShape.join('×')}]`;\n    \n    lines.push(`│ ${(i + 1).toString().padStart(2)}. ${layer.name.padEnd(20)} │ ${layer.type.padEnd(15)} │`);\n    lines.push(`│     ${inputStr.padEnd(15)} → ${outputStr.padEnd(15)}                   │`);\n    \n    if (i < layers.length - 1) {\n      lines.push('│                           ↓                                        │');\n    }\n  }\n  \n  lines.push('└─────────────────────────────────────────────────────────────────────┘');\n  \n  return lines.join('\\n');\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport default {\n  EdgeFlowDebugger,\n  getDebugger,\n  enableDebugging,\n  disableDebugging,\n  inspectTensor,\n  formatTensorInspection,\n  createAsciiHistogram,\n  createTensorHeatmap,\n  visualizeModelArchitecture,\n};\n"
  },
  {
    "path": "src/tools/index.ts",
    "content": "/**\n * edgeFlow.js - Tools and Utilities\n * \n * Model optimization, quantization, and analysis tools.\n */\n\nimport { \n  LoadedModel,\n  QuantizationType,\n} from '../core/types.js';\n\n// ============================================================================\n// Quantization Tools\n// ============================================================================\n\n/**\n * Quantization options\n */\nexport interface QuantizationOptions {\n  /** Quantization method */\n  method: QuantizationType;\n  /** Calibration data for calibrated quantization */\n  calibrationData?: Float32Array[];\n  /** Whether to quantize weights only */\n  weightsOnly?: boolean;\n  /** Layers to exclude from quantization */\n  excludeLayers?: string[];\n}\n\n/**\n * Quantization result\n */\nexport interface QuantizationResult {\n  /** Quantized model data */\n  modelData: ArrayBuffer;\n  /** Original size in bytes */\n  originalSize: number;\n  /** Quantized size in bytes */\n  quantizedSize: number;\n  /** Compression ratio */\n  compressionRatio: number;\n  /** Quantization statistics */\n  stats: {\n    layersQuantized: number;\n    layersSkipped: number;\n  };\n}\n\n/**\n * Quantize a model\n * \n * @example\n * ```typescript\n * const quantized = await quantize(model, {\n *   method: 'int8',\n *   calibrationData: samples,\n * });\n * ```\n */\nexport async function quantize(\n  model: LoadedModel | ArrayBuffer,\n  options: QuantizationOptions\n): Promise<QuantizationResult> {\n  // Get model data\n  const modelData = model instanceof ArrayBuffer \n    ? model \n    : await getModelData(model);\n\n  const originalSize = modelData.byteLength;\n\n  // Apply quantization based on method\n  let quantizedData: ArrayBuffer;\n  let layersQuantized = 0;\n  let layersSkipped = 0;\n\n  switch (options.method) {\n    case 'int8':\n      ({ data: quantizedData, layersQuantized, layersSkipped } = \n        quantizeInt8(modelData, options));\n      break;\n    case 'uint8':\n      ({ data: quantizedData, layersQuantized, layersSkipped } = \n        quantizeUint8(modelData, options));\n      break;\n    case 'float16':\n      ({ data: quantizedData, layersQuantized, layersSkipped } = \n        quantizeFloat16(modelData, options));\n      break;\n    case 'int4':\n      ({ data: quantizedData, layersQuantized, layersSkipped } = \n        quantizeInt4(modelData, options));\n      break;\n    default:\n      quantizedData = modelData;\n  }\n\n  return {\n    modelData: quantizedData,\n    originalSize,\n    quantizedSize: quantizedData.byteLength,\n    compressionRatio: originalSize / quantizedData.byteLength,\n    stats: {\n      layersQuantized,\n      layersSkipped,\n    },\n  };\n}\n\n/**\n * Placeholder for getting model data\n */\nasync function getModelData(_model: LoadedModel): Promise<ArrayBuffer> {\n  // In production, this would extract the model weights\n  return new ArrayBuffer(0);\n}\n\n/**\n * INT8 quantization\n */\nfunction quantizeInt8(\n  data: ArrayBuffer,\n  _options: QuantizationOptions\n): { data: ArrayBuffer; layersQuantized: number; layersSkipped: number } {\n  // Simplified INT8 quantization\n  const input = new Float32Array(data);\n  const output = new Int8Array(input.length);\n  \n  // Find scale\n  let max = 0;\n  for (let i = 0; i < input.length; i++) {\n    const abs = Math.abs(input[i] ?? 0);\n    if (abs > max) max = abs;\n  }\n  const scale = max / 127;\n  \n  // Quantize\n  for (let i = 0; i < input.length; i++) {\n    output[i] = Math.round((input[i] ?? 0) / scale);\n  }\n  \n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0,\n  };\n}\n\n/**\n * UINT8 quantization\n */\nfunction quantizeUint8(\n  data: ArrayBuffer,\n  _options: QuantizationOptions\n): { data: ArrayBuffer; layersQuantized: number; layersSkipped: number } {\n  const input = new Float32Array(data);\n  const output = new Uint8Array(input.length);\n  \n  // Find min/max\n  let min = Infinity, max = -Infinity;\n  for (let i = 0; i < input.length; i++) {\n    const val = input[i] ?? 0;\n    if (val < min) min = val;\n    if (val > max) max = val;\n  }\n  const scale = (max - min) / 255;\n  \n  // Quantize\n  for (let i = 0; i < input.length; i++) {\n    output[i] = Math.round(((input[i] ?? 0) - min) / scale);\n  }\n  \n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0,\n  };\n}\n\n/**\n * Float16 quantization\n */\nfunction quantizeFloat16(\n  data: ArrayBuffer,\n  _options: QuantizationOptions\n): { data: ArrayBuffer; layersQuantized: number; layersSkipped: number } {\n  const input = new Float32Array(data);\n  const output = new Uint16Array(input.length);\n  \n  // Convert float32 to float16\n  for (let i = 0; i < input.length; i++) {\n    output[i] = float32ToFloat16(input[i] ?? 0);\n  }\n  \n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0,\n  };\n}\n\n/**\n * INT4 quantization\n */\nfunction quantizeInt4(\n  data: ArrayBuffer,\n  _options: QuantizationOptions\n): { data: ArrayBuffer; layersQuantized: number; layersSkipped: number } {\n  const input = new Float32Array(data);\n  // Pack two INT4 values per byte\n  const output = new Uint8Array(Math.ceil(input.length / 2));\n  \n  // Find scale\n  let max = 0;\n  for (let i = 0; i < input.length; i++) {\n    const abs = Math.abs(input[i] ?? 0);\n    if (abs > max) max = abs;\n  }\n  const scale = max / 7; // INT4 range: -8 to 7\n  \n  // Quantize and pack\n  for (let i = 0; i < input.length; i += 2) {\n    const val1 = Math.round((input[i] ?? 0) / scale) + 8;\n    const val2 = Math.round((input[i + 1] ?? 0) / scale) + 8;\n    output[i / 2] = ((val1 & 0xF) << 4) | (val2 & 0xF);\n  }\n  \n  return {\n    data: output.buffer,\n    layersQuantized: 1,\n    layersSkipped: 0,\n  };\n}\n\n/**\n * Convert float32 to float16\n */\nfunction float32ToFloat16(value: number): number {\n  const floatView = new Float32Array(1);\n  const int32View = new Int32Array(floatView.buffer);\n  \n  floatView[0] = value;\n  const x = int32View[0] ?? 0;\n  \n  let bits = (x >> 16) & 0x8000; // sign\n  let m = (x >> 12) & 0x07ff;    // mantissa\n  const e = (x >> 23) & 0xff;    // exponent\n  \n  if (e < 103) {\n    // Too small, return zero\n    return bits;\n  }\n  \n  if (e > 142) {\n    // Too large, return infinity\n    bits |= 0x7c00;\n    bits |= ((e === 255) ? 0 : 1) && (x & 0x007fffff);\n    return bits;\n  }\n  \n  if (e < 113) {\n    // Denormalized\n    m |= 0x0800;\n    bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);\n    return bits;\n  }\n  \n  bits |= ((e - 112) << 10) | (m >> 1);\n  bits += m & 1;\n  return bits;\n}\n\n// ============================================================================\n// Model Pruning\n// ============================================================================\n\n/**\n * Pruning options\n */\nexport interface PruningOptions {\n  /** Target sparsity (0-1) */\n  sparsity: number;\n  /** Pruning method */\n  method?: 'magnitude' | 'random' | 'structured';\n  /** Layers to exclude */\n  excludeLayers?: string[];\n}\n\n/**\n * Pruning result\n */\nexport interface PruningResult {\n  /** Pruned model data */\n  modelData: ArrayBuffer;\n  /** Achieved sparsity */\n  actualSparsity: number;\n  /** Number of parameters pruned */\n  parametersPruned: number;\n  /** Total parameters */\n  totalParameters: number;\n}\n\n/**\n * Prune model weights\n */\nexport async function prune(\n  model: LoadedModel | ArrayBuffer,\n  options: PruningOptions\n): Promise<PruningResult> {\n  const modelData = model instanceof ArrayBuffer \n    ? model \n    : await getModelData(model);\n\n  const weights = new Float32Array(modelData);\n  const total = weights.length;\n  \n  // Calculate threshold for magnitude pruning\n  const magnitudes = weights.map(Math.abs);\n  const sorted = [...magnitudes].sort((a, b) => a - b);\n  const thresholdIdx = Math.floor(options.sparsity * sorted.length);\n  const threshold = sorted[thresholdIdx] ?? 0;\n  \n  // Prune weights\n  let pruned = 0;\n  for (let i = 0; i < weights.length; i++) {\n    if (Math.abs(weights[i] ?? 0) < threshold) {\n      weights[i] = 0;\n      pruned++;\n    }\n  }\n\n  return {\n    modelData: weights.buffer,\n    actualSparsity: pruned / total,\n    parametersPruned: pruned,\n    totalParameters: total,\n  };\n}\n\n// ============================================================================\n// Model Analysis\n// ============================================================================\n\n/**\n * Model analysis result\n */\nexport interface ModelAnalysis {\n  /** Total number of parameters */\n  totalParameters: number;\n  /** Model size in bytes */\n  sizeBytes: number;\n  /** Layer information */\n  layers: Array<{\n    name: string;\n    type: string;\n    parameters: number;\n    inputShape: number[];\n    outputShape: number[];\n  }>;\n  /** Estimated FLOPs */\n  estimatedFlops: number;\n  /** Memory requirements */\n  memoryRequirements: {\n    weights: number;\n    activations: number;\n    total: number;\n  };\n}\n\n/**\n * Analyze a model\n */\nexport async function analyzeModel(\n  model: LoadedModel | ArrayBuffer\n): Promise<ModelAnalysis> {\n  // Simplified analysis\n  const size = model instanceof ArrayBuffer \n    ? model.byteLength \n    : model.metadata.sizeBytes;\n\n  const estimatedParams = Math.floor(size / 4); // Assume float32\n\n  return {\n    totalParameters: estimatedParams,\n    sizeBytes: size,\n    layers: [],\n    estimatedFlops: estimatedParams * 2, // Rough estimate\n    memoryRequirements: {\n      weights: size,\n      activations: size * 0.1, // Rough estimate\n      total: size * 1.1,\n    },\n  };\n}\n\n// ============================================================================\n// Benchmarking\n// ============================================================================\n\n/**\n * Benchmark options\n */\nexport interface BenchmarkOptions {\n  /** Number of warmup runs */\n  warmupRuns?: number;\n  /** Number of benchmark runs */\n  runs?: number;\n  /** Input shape */\n  inputShape?: number[];\n}\n\n/**\n * Benchmark result\n */\nexport interface BenchmarkResult {\n  /** Average inference time in ms */\n  avgTime: number;\n  /** Minimum inference time in ms */\n  minTime: number;\n  /** Maximum inference time in ms */\n  maxTime: number;\n  /** Standard deviation */\n  stdDev: number;\n  /** Throughput (inferences per second) */\n  throughput: number;\n  /** All run times */\n  times: number[];\n}\n\n/**\n * Benchmark model inference\n */\nexport async function benchmark(\n  runFn: () => Promise<void>,\n  options: BenchmarkOptions = {}\n): Promise<BenchmarkResult> {\n  const {\n    warmupRuns = 3,\n    runs = 10,\n  } = options;\n\n  // Warmup\n  for (let i = 0; i < warmupRuns; i++) {\n    await runFn();\n  }\n\n  // Benchmark\n  const times: number[] = [];\n  for (let i = 0; i < runs; i++) {\n    const start = performance.now();\n    await runFn();\n    times.push(performance.now() - start);\n  }\n\n  // Calculate statistics\n  const sum = times.reduce((a, b) => a + b, 0);\n  const avgTime = sum / times.length;\n  const minTime = Math.min(...times);\n  const maxTime = Math.max(...times);\n  \n  const squaredDiffs = times.map(t => Math.pow(t - avgTime, 2));\n  const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / times.length;\n  const stdDev = Math.sqrt(avgSquaredDiff);\n\n  return {\n    avgTime,\n    minTime,\n    maxTime,\n    stdDev,\n    throughput: 1000 / avgTime,\n    times,\n  };\n}\n\n// ============================================================================\n// Re-export benchmark utilities\n// ============================================================================\n\nexport {\n  benchmark as runBenchmark,\n  compareBenchmarks,\n  benchmarkSuite,\n  benchmarkMemory,\n  formatBenchmarkResult,\n  formatComparisonResult,\n} from './benchmark.js';\n\nexport type {\n  BenchmarkOptions as DetailedBenchmarkOptions,\n  BenchmarkResult as DetailedBenchmarkResult,\n  CompareBenchmarkResult,\n  MemoryBenchmarkResult,\n} from './benchmark.js';\n\n// ============================================================================\n// Re-export advanced quantization tools\n// ============================================================================\n\nexport {\n  quantizeModel,\n  quantizeTensor,\n  dequantizeTensor,\n  pruneModel,\n  pruneTensor,\n  analyzeModel as analyzeModelDetailed,\n  exportModel as exportModelAdvanced,\n  dequantizeInt8,\n  dequantizeUint8,\n  dequantizeFloat16,\n  float16ToFloat32,\n} from './quantization.js';\n\nexport type {\n  QuantizationType as QuantizationMethod,\n  QuantizationOptions as AdvancedQuantizationOptions,\n  QuantizationProgress,\n  QuantizationResult as AdvancedQuantizationResult,\n  LayerQuantizationStats,\n  QuantizationStats,\n  PruningOptions as AdvancedPruningOptions,\n  PruningResult as AdvancedPruningResult,\n  ModelAnalysis as DetailedModelAnalysis,\n  ExportFormat,\n  ExportOptions,\n} from './quantization.js';\n\n// ============================================================================\n// Re-export debugging tools\n// ============================================================================\n\nexport {\n  EdgeFlowDebugger,\n  getDebugger,\n  enableDebugging,\n  disableDebugging,\n  inspectTensor,\n  formatTensorInspection,\n  createAsciiHistogram,\n  createTensorHeatmap,\n  visualizeModelArchitecture,\n} from './debugger.js';\n\nexport type {\n  DebuggerConfig,\n  TensorInspection,\n  TensorStats,\n  HistogramData,\n  InferenceTrace,\n  OperationTrace,\n  DebugEvent,\n  PerformanceMetrics as DebugPerformanceMetrics,\n} from './debugger.js';\n\n// ============================================================================\n// Re-export monitoring tools\n// ============================================================================\n\nexport {\n  PerformanceMonitor,\n  getMonitor,\n  startMonitoring,\n  stopMonitoring,\n  generateDashboardHTML,\n  generateAsciiDashboard,\n} from './monitor.js';\n\nexport type {\n  MonitorConfig,\n  PerformanceSample,\n  InferenceMetrics,\n  MemoryMetrics,\n  SystemMetrics,\n  AlertConfig,\n  AlertEvent,\n  WidgetData,\n} from './monitor.js';\n\n// ============================================================================\n// Export Utilities\n// ============================================================================\n\n/**\n * Export model to different formats\n */\nexport async function exportModel(\n  model: LoadedModel | ArrayBuffer,\n  format: 'onnx' | 'json' | 'binary'\n): Promise<ArrayBuffer | string> {\n  const modelData = model instanceof ArrayBuffer \n    ? model \n    : await getModelData(model);\n\n  switch (format) {\n    case 'json':\n      // Export as JSON (for small models)\n      const array = new Float32Array(modelData);\n      return JSON.stringify(Array.from(array));\n    case 'binary':\n    case 'onnx':\n    default:\n      return modelData;\n  }\n}\n"
  },
  {
    "path": "src/tools/monitor.ts",
    "content": "/**\n * edgeFlow.js - Performance Monitoring Dashboard\n * \n * Real-time performance monitoring and metrics visualization.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Monitor configuration\n */\nexport interface MonitorConfig {\n  /** Enable monitoring (default: true) */\n  enabled?: boolean;\n  \n  /** Sampling interval in ms (default: 1000) */\n  sampleInterval?: number;\n  \n  /** History size (number of samples to keep) */\n  historySize?: number;\n  \n  /** Enable memory monitoring (default: true) */\n  monitorMemory?: boolean;\n  \n  /** Enable FPS monitoring (default: true) */\n  monitorFPS?: boolean;\n  \n  /** Custom metric collectors */\n  collectors?: Array<() => Record<string, number>>;\n}\n\n/**\n * Performance sample\n */\nexport interface PerformanceSample {\n  timestamp: number;\n  inference: InferenceMetrics;\n  memory: MemoryMetrics;\n  system: SystemMetrics;\n  custom: Record<string, number>;\n}\n\n/**\n * Inference metrics\n */\nexport interface InferenceMetrics {\n  /** Inferences in the last interval */\n  count: number;\n  \n  /** Average inference time (ms) */\n  avgTime: number;\n  \n  /** Min inference time (ms) */\n  minTime: number;\n  \n  /** Max inference time (ms) */\n  maxTime: number;\n  \n  /** Throughput (inferences per second) */\n  throughput: number;\n  \n  /** Queue length */\n  queueLength: number;\n  \n  /** Active inferences */\n  activeCount: number;\n}\n\n/**\n * Memory metrics\n */\nexport interface MemoryMetrics {\n  /** Used JS heap size (bytes) */\n  usedHeap: number;\n  \n  /** Total JS heap size (bytes) */\n  totalHeap: number;\n  \n  /** Heap limit (bytes) */\n  heapLimit: number;\n  \n  /** Heap usage percentage */\n  heapUsage: number;\n  \n  /** Tensor memory (bytes) */\n  tensorMemory: number;\n  \n  /** Cache memory (bytes) */\n  cacheMemory: number;\n}\n\n/**\n * System metrics\n */\nexport interface SystemMetrics {\n  /** Frames per second */\n  fps: number;\n  \n  /** CPU usage estimate (0-1) */\n  cpuUsage: number;\n  \n  /** Time since last sample (ms) */\n  deltaTime: number;\n  \n  /** Browser info */\n  userAgent: string;\n  \n  /** WebGPU available */\n  webgpuAvailable: boolean;\n  \n  /** WebNN available */\n  webnnAvailable: boolean;\n}\n\n/**\n * Alert configuration\n */\nexport interface AlertConfig {\n  /** Metric name */\n  metric: string;\n  \n  /** Threshold value */\n  threshold: number;\n  \n  /** Comparison operator */\n  operator: '>' | '<' | '>=' | '<=' | '==' | '!=';\n  \n  /** Alert message */\n  message: string;\n  \n  /** Alert level */\n  level: 'info' | 'warn' | 'error';\n}\n\n/**\n * Alert event\n */\nexport interface AlertEvent {\n  config: AlertConfig;\n  value: number;\n  timestamp: number;\n}\n\n/**\n * Dashboard widget data\n */\nexport interface WidgetData {\n  type: 'chart' | 'gauge' | 'counter' | 'text';\n  title: string;\n  data: unknown;\n}\n\n// ============================================================================\n// Performance Monitor\n// ============================================================================\n\n/**\n * Performance monitor for edgeFlow.js\n */\nexport class PerformanceMonitor {\n  private config: Required<MonitorConfig>;\n  private samples: PerformanceSample[] = [];\n  private isRunning: boolean = false;\n  private intervalId: ReturnType<typeof setInterval> | null = null;\n  private alerts: AlertConfig[] = [];\n  private alertListeners: Array<(alert: AlertEvent) => void> = [];\n  private sampleListeners: Array<(sample: PerformanceSample) => void> = [];\n  \n  // Inference tracking\n  private inferenceCount: number = 0;\n  private inferenceTimes: number[] = [];\n  private queueLength: number = 0;\n  private activeCount: number = 0;\n  \n  // FPS tracking\n  private frameCount: number = 0;\n  private lastFrameTime: number = 0;\n  private fps: number = 0;\n  private rafId: number | null = null;\n  \n  // Memory tracking\n  private tensorMemory: number = 0;\n  private cacheMemory: number = 0;\n  \n  constructor(config: MonitorConfig = {}) {\n    this.config = {\n      enabled: config.enabled ?? true,\n      sampleInterval: config.sampleInterval ?? 1000,\n      historySize: config.historySize ?? 60,\n      monitorMemory: config.monitorMemory ?? true,\n      monitorFPS: config.monitorFPS ?? true,\n      collectors: config.collectors ?? [],\n    };\n  }\n  \n  /**\n   * Start monitoring\n   */\n  start(): void {\n    if (this.isRunning) return;\n    \n    this.isRunning = true;\n    \n    // Start sampling\n    this.intervalId = setInterval(() => {\n      this.collectSample();\n    }, this.config.sampleInterval);\n    \n    // Start FPS monitoring\n    if (this.config.monitorFPS && typeof requestAnimationFrame !== 'undefined') {\n      this.lastFrameTime = performance.now();\n      this.frameCount = 0;\n      this.monitorFPS();\n    }\n  }\n  \n  /**\n   * Stop monitoring\n   */\n  stop(): void {\n    this.isRunning = false;\n    \n    if (this.intervalId) {\n      clearInterval(this.intervalId);\n      this.intervalId = null;\n    }\n    \n    if (this.rafId) {\n      cancelAnimationFrame(this.rafId);\n      this.rafId = null;\n    }\n  }\n  \n  /**\n   * Monitor FPS\n   */\n  private monitorFPS(): void {\n    if (!this.isRunning) return;\n    \n    this.frameCount++;\n    const now = performance.now();\n    const elapsed = now - this.lastFrameTime;\n    \n    if (elapsed >= 1000) {\n      this.fps = Math.round((this.frameCount * 1000) / elapsed);\n      this.frameCount = 0;\n      this.lastFrameTime = now;\n    }\n    \n    this.rafId = requestAnimationFrame(() => this.monitorFPS());\n  }\n  \n  /**\n   * Collect a performance sample\n   */\n  private collectSample(): void {\n    const now = Date.now();\n    \n    // Calculate inference metrics\n    const avgTime = this.inferenceTimes.length > 0\n      ? this.inferenceTimes.reduce((a, b) => a + b, 0) / this.inferenceTimes.length\n      : 0;\n    const minTime = this.inferenceTimes.length > 0\n      ? Math.min(...this.inferenceTimes)\n      : 0;\n    const maxTime = this.inferenceTimes.length > 0\n      ? Math.max(...this.inferenceTimes)\n      : 0;\n    const throughput = this.inferenceCount / (this.config.sampleInterval / 1000);\n    \n    const inference: InferenceMetrics = {\n      count: this.inferenceCount,\n      avgTime,\n      minTime,\n      maxTime,\n      throughput,\n      queueLength: this.queueLength,\n      activeCount: this.activeCount,\n    };\n    \n    // Collect memory metrics\n    const memory = this.collectMemoryMetrics();\n    \n    // Collect system metrics\n    const system = this.collectSystemMetrics();\n    \n    // Collect custom metrics\n    const custom: Record<string, number> = {};\n    for (const collector of this.config.collectors) {\n      try {\n        Object.assign(custom, collector());\n      } catch {\n        // Ignore collector errors\n      }\n    }\n    \n    const sample: PerformanceSample = {\n      timestamp: now,\n      inference,\n      memory,\n      system,\n      custom,\n    };\n    \n    // Add to history\n    this.samples.push(sample);\n    if (this.samples.length > this.config.historySize) {\n      this.samples.shift();\n    }\n    \n    // Check alerts\n    this.checkAlerts(sample);\n    \n    // Notify listeners\n    for (const listener of this.sampleListeners) {\n      listener(sample);\n    }\n    \n    // Reset counters\n    this.inferenceCount = 0;\n    this.inferenceTimes = [];\n  }\n  \n  /**\n   * Collect memory metrics\n   */\n  private collectMemoryMetrics(): MemoryMetrics {\n    let usedHeap = 0;\n    let totalHeap = 0;\n    let heapLimit = 0;\n    \n    if (typeof performance !== 'undefined' && 'memory' in performance) {\n      const memory = (performance as { memory: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number } }).memory;\n      usedHeap = memory.usedJSHeapSize;\n      totalHeap = memory.totalJSHeapSize;\n      heapLimit = memory.jsHeapSizeLimit;\n    }\n    \n    return {\n      usedHeap,\n      totalHeap,\n      heapLimit,\n      heapUsage: heapLimit > 0 ? usedHeap / heapLimit : 0,\n      tensorMemory: this.tensorMemory,\n      cacheMemory: this.cacheMemory,\n    };\n  }\n  \n  /**\n   * Collect system metrics\n   */\n  private collectSystemMetrics(): SystemMetrics {\n    const lastSample = this.samples[this.samples.length - 1];\n    const deltaTime = lastSample \n      ? Date.now() - lastSample.timestamp \n      : this.config.sampleInterval;\n    \n    // Check WebGPU availability\n    let webgpuAvailable = false;\n    if (typeof navigator !== 'undefined' && 'gpu' in navigator) {\n      webgpuAvailable = true;\n    }\n    \n    // Check WebNN availability\n    let webnnAvailable = false;\n    if (typeof navigator !== 'undefined' && 'ml' in navigator) {\n      webnnAvailable = true;\n    }\n    \n    return {\n      fps: this.fps,\n      cpuUsage: this.estimateCPUUsage(),\n      deltaTime,\n      userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',\n      webgpuAvailable,\n      webnnAvailable,\n    };\n  }\n  \n  /**\n   * Estimate CPU usage based on inference times\n   */\n  private estimateCPUUsage(): number {\n    if (this.inferenceTimes.length === 0) return 0;\n    \n    const totalTime = this.inferenceTimes.reduce((a, b) => a + b, 0);\n    return Math.min(1, totalTime / this.config.sampleInterval);\n  }\n  \n  /**\n   * Check alerts\n   */\n  private checkAlerts(sample: PerformanceSample): void {\n    for (const alert of this.alerts) {\n      const value = this.getMetricValue(sample, alert.metric);\n      if (value === undefined) continue;\n      \n      let triggered = false;\n      switch (alert.operator) {\n        case '>': triggered = value > alert.threshold; break;\n        case '<': triggered = value < alert.threshold; break;\n        case '>=': triggered = value >= alert.threshold; break;\n        case '<=': triggered = value <= alert.threshold; break;\n        case '==': triggered = value === alert.threshold; break;\n        case '!=': triggered = value !== alert.threshold; break;\n      }\n      \n      if (triggered) {\n        const event: AlertEvent = {\n          config: alert,\n          value,\n          timestamp: sample.timestamp,\n        };\n        \n        for (const listener of this.alertListeners) {\n          listener(event);\n        }\n      }\n    }\n  }\n  \n  /**\n   * Get metric value from sample\n   */\n  private getMetricValue(sample: PerformanceSample, metric: string): number | undefined {\n    const parts = metric.split('.');\n    let value: unknown = sample;\n    \n    for (const part of parts) {\n      if (value && typeof value === 'object' && part in value) {\n        value = (value as Record<string, unknown>)[part];\n      } else {\n        return undefined;\n      }\n    }\n    \n    return typeof value === 'number' ? value : undefined;\n  }\n  \n  /**\n   * Record an inference\n   */\n  recordInference(duration: number): void {\n    this.inferenceCount++;\n    this.inferenceTimes.push(duration);\n  }\n  \n  /**\n   * Update queue length\n   */\n  updateQueueLength(length: number): void {\n    this.queueLength = length;\n  }\n  \n  /**\n   * Update active count\n   */\n  updateActiveCount(count: number): void {\n    this.activeCount = count;\n  }\n  \n  /**\n   * Update tensor memory\n   */\n  updateTensorMemory(bytes: number): void {\n    this.tensorMemory = bytes;\n  }\n  \n  /**\n   * Update cache memory\n   */\n  updateCacheMemory(bytes: number): void {\n    this.cacheMemory = bytes;\n  }\n  \n  /**\n   * Add an alert\n   */\n  addAlert(config: AlertConfig): void {\n    this.alerts.push(config);\n  }\n  \n  /**\n   * Remove an alert\n   */\n  removeAlert(metric: string): void {\n    this.alerts = this.alerts.filter(a => a.metric !== metric);\n  }\n  \n  /**\n   * Subscribe to alerts\n   */\n  onAlert(callback: (alert: AlertEvent) => void): () => void {\n    this.alertListeners.push(callback);\n    return () => {\n      const idx = this.alertListeners.indexOf(callback);\n      if (idx !== -1) this.alertListeners.splice(idx, 1);\n    };\n  }\n  \n  /**\n   * Subscribe to samples\n   */\n  onSample(callback: (sample: PerformanceSample) => void): () => void {\n    this.sampleListeners.push(callback);\n    return () => {\n      const idx = this.sampleListeners.indexOf(callback);\n      if (idx !== -1) this.sampleListeners.splice(idx, 1);\n    };\n  }\n  \n  /**\n   * Get current sample\n   */\n  getCurrentSample(): PerformanceSample | undefined {\n    return this.samples[this.samples.length - 1];\n  }\n  \n  /**\n   * Get all samples\n   */\n  getSamples(): PerformanceSample[] {\n    return [...this.samples];\n  }\n  \n  /**\n   * Get samples in time range\n   */\n  getSamplesInRange(startTime: number, endTime: number): PerformanceSample[] {\n    return this.samples.filter(s => s.timestamp >= startTime && s.timestamp <= endTime);\n  }\n  \n  /**\n   * Get summary statistics\n   */\n  getSummary(): {\n    avgInferenceTime: number;\n    avgThroughput: number;\n    avgMemoryUsage: number;\n    avgFPS: number;\n    totalInferences: number;\n    uptime: number;\n  } {\n    if (this.samples.length === 0) {\n      return {\n        avgInferenceTime: 0,\n        avgThroughput: 0,\n        avgMemoryUsage: 0,\n        avgFPS: 0,\n        totalInferences: 0,\n        uptime: 0,\n      };\n    }\n    \n    const avgInferenceTime = this.samples.reduce((sum, s) => sum + s.inference.avgTime, 0) / this.samples.length;\n    const avgThroughput = this.samples.reduce((sum, s) => sum + s.inference.throughput, 0) / this.samples.length;\n    const avgMemoryUsage = this.samples.reduce((sum, s) => sum + s.memory.heapUsage, 0) / this.samples.length;\n    const avgFPS = this.samples.reduce((sum, s) => sum + s.system.fps, 0) / this.samples.length;\n    const totalInferences = this.samples.reduce((sum, s) => sum + s.inference.count, 0);\n    \n    const firstSample = this.samples[0]!;\n    const lastSample = this.samples[this.samples.length - 1]!;\n    const uptime = lastSample.timestamp - firstSample.timestamp;\n    \n    return {\n      avgInferenceTime,\n      avgThroughput,\n      avgMemoryUsage,\n      avgFPS,\n      totalInferences,\n      uptime,\n    };\n  }\n  \n  /**\n   * Clear all data\n   */\n  clear(): void {\n    this.samples = [];\n    this.inferenceCount = 0;\n    this.inferenceTimes = [];\n    this.queueLength = 0;\n    this.activeCount = 0;\n    this.tensorMemory = 0;\n    this.cacheMemory = 0;\n  }\n  \n  /**\n   * Export data\n   */\n  export(): {\n    samples: PerformanceSample[];\n    summary: {\n      avgInferenceTime: number;\n      avgThroughput: number;\n      avgMemoryUsage: number;\n      avgFPS: number;\n      totalInferences: number;\n      uptime: number;\n    };\n    config: MonitorConfig;\n    timestamp: number;\n  } {\n    return {\n      samples: this.getSamples(),\n      summary: this.getSummary(),\n      config: this.config,\n      timestamp: Date.now(),\n    };\n  }\n}\n\n// ============================================================================\n// Dashboard Generator\n// ============================================================================\n\n/**\n * Generate HTML dashboard\n */\nexport function generateDashboardHTML(monitor: PerformanceMonitor): string {\n  const summary = monitor.getSummary();\n  const samples = monitor.getSamples();\n  const lastSample = samples[samples.length - 1];\n  \n  const formatBytes = (bytes: number): string => {\n    if (bytes < 1024) return `${bytes} B`;\n    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n    if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n    return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n  };\n  \n  const formatDuration = (ms: number): string => {\n    if (ms < 1000) return `${ms.toFixed(0)}ms`;\n    if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n    return `${(ms / 60000).toFixed(1)}m`;\n  };\n  \n  return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>edgeFlow.js Performance Dashboard</title>\n  <style>\n    :root {\n      --bg-primary: #0d1117;\n      --bg-secondary: #161b22;\n      --bg-tertiary: #21262d;\n      --text-primary: #f0f6fc;\n      --text-secondary: #8b949e;\n      --accent: #58a6ff;\n      --success: #3fb950;\n      --warning: #d29922;\n      --error: #f85149;\n    }\n    \n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n    \n    body {\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n      background: var(--bg-primary);\n      color: var(--text-primary);\n      line-height: 1.6;\n    }\n    \n    .dashboard {\n      max-width: 1400px;\n      margin: 0 auto;\n      padding: 24px;\n    }\n    \n    header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 32px;\n      padding-bottom: 16px;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    h1 {\n      font-size: 24px;\n      font-weight: 600;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n    \n    .status {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      font-size: 14px;\n      color: var(--text-secondary);\n    }\n    \n    .status-dot {\n      width: 8px;\n      height: 8px;\n      border-radius: 50%;\n      background: var(--success);\n    }\n    \n    .grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      gap: 20px;\n      margin-bottom: 32px;\n    }\n    \n    .card {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n    }\n    \n    .card-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .card-title {\n      font-size: 14px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .card-value {\n      font-size: 36px;\n      font-weight: 700;\n      font-variant-numeric: tabular-nums;\n    }\n    \n    .card-value.small {\n      font-size: 24px;\n    }\n    \n    .card-unit {\n      font-size: 14px;\n      color: var(--text-secondary);\n      margin-left: 4px;\n    }\n    \n    .card-change {\n      font-size: 12px;\n      padding: 4px 8px;\n      border-radius: 4px;\n    }\n    \n    .card-change.up {\n      background: rgba(63, 185, 80, 0.2);\n      color: var(--success);\n    }\n    \n    .card-change.down {\n      background: rgba(248, 81, 73, 0.2);\n      color: var(--error);\n    }\n    \n    .progress-bar {\n      height: 8px;\n      background: var(--bg-tertiary);\n      border-radius: 4px;\n      overflow: hidden;\n      margin-top: 12px;\n    }\n    \n    .progress-fill {\n      height: 100%;\n      border-radius: 4px;\n      transition: width 0.3s ease;\n    }\n    \n    .progress-fill.blue { background: var(--accent); }\n    .progress-fill.green { background: var(--success); }\n    .progress-fill.yellow { background: var(--warning); }\n    .progress-fill.red { background: var(--error); }\n    \n    .chart-container {\n      background: var(--bg-secondary);\n      border: 1px solid var(--bg-tertiary);\n      border-radius: 12px;\n      padding: 20px;\n      margin-bottom: 20px;\n    }\n    \n    .chart-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 16px;\n    }\n    \n    .chart-title {\n      font-size: 16px;\n      font-weight: 600;\n    }\n    \n    .chart {\n      height: 200px;\n      position: relative;\n    }\n    \n    .chart-line {\n      stroke: var(--accent);\n      stroke-width: 2;\n      fill: none;\n    }\n    \n    .chart-area {\n      fill: url(#chartGradient);\n      opacity: 0.3;\n    }\n    \n    .chart-grid {\n      stroke: var(--bg-tertiary);\n      stroke-width: 1;\n    }\n    \n    .table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n    \n    .table th,\n    .table td {\n      padding: 12px 16px;\n      text-align: left;\n      border-bottom: 1px solid var(--bg-tertiary);\n    }\n    \n    .table th {\n      font-size: 12px;\n      font-weight: 500;\n      color: var(--text-secondary);\n      text-transform: uppercase;\n      letter-spacing: 0.5px;\n    }\n    \n    .table td {\n      font-variant-numeric: tabular-nums;\n    }\n    \n    footer {\n      text-align: center;\n      padding: 24px;\n      color: var(--text-secondary);\n      font-size: 14px;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"dashboard\">\n    <header>\n      <h1>\n        <svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\">\n          <rect width=\"32\" height=\"32\" rx=\"8\" fill=\"var(--accent)\"/>\n          <path d=\"M8 16L14 10L20 16L26 10\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n          <path d=\"M8 22L14 16L20 22L26 16\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\"/>\n        </svg>\n        edgeFlow.js Performance Dashboard\n      </h1>\n      <div class=\"status\">\n        <div class=\"status-dot\"></div>\n        Running for ${formatDuration(summary.uptime)}\n      </div>\n    </header>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Total Inferences</span>\n        </div>\n        <div class=\"card-value\">${summary.totalInferences.toLocaleString()}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg Inference Time</span>\n        </div>\n        <div class=\"card-value\">${summary.avgInferenceTime.toFixed(1)}<span class=\"card-unit\">ms</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Throughput</span>\n        </div>\n        <div class=\"card-value\">${summary.avgThroughput.toFixed(1)}<span class=\"card-unit\">ops/s</span></div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Avg FPS</span>\n        </div>\n        <div class=\"card-value\">${Math.round(summary.avgFPS)}</div>\n      </div>\n    </div>\n    \n    <div class=\"grid\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Memory Usage</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.usedHeap ?? 0)}</div>\n        <div class=\"progress-bar\">\n          <div class=\"progress-fill ${summary.avgMemoryUsage > 0.8 ? 'red' : summary.avgMemoryUsage > 0.6 ? 'yellow' : 'green'}\" \n               style=\"width: ${(summary.avgMemoryUsage * 100).toFixed(0)}%\"></div>\n        </div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Tensor Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.tensorMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Cache Memory</span>\n        </div>\n        <div class=\"card-value small\">${formatBytes(lastSample?.memory.cacheMemory ?? 0)}</div>\n      </div>\n      \n      <div class=\"card\">\n        <div class=\"card-header\">\n          <span class=\"card-title\">Queue Length</span>\n        </div>\n        <div class=\"card-value small\">${lastSample?.inference.queueLength ?? 0}</div>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Inference Time History</span>\n      </div>\n      <div class=\"chart\">\n        <svg width=\"100%\" height=\"100%\" viewBox=\"0 0 600 200\" preserveAspectRatio=\"none\">\n          <defs>\n            <linearGradient id=\"chartGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stop-color=\"var(--accent)\" stop-opacity=\"0.5\"/>\n              <stop offset=\"100%\" stop-color=\"var(--accent)\" stop-opacity=\"0\"/>\n            </linearGradient>\n          </defs>\n          ${generateChartPath(samples)}\n        </svg>\n      </div>\n    </div>\n    \n    <div class=\"chart-container\">\n      <div class=\"chart-header\">\n        <span class=\"chart-title\">Recent Samples</span>\n      </div>\n      <table class=\"table\">\n        <thead>\n          <tr>\n            <th>Time</th>\n            <th>Inferences</th>\n            <th>Avg Time</th>\n            <th>Throughput</th>\n            <th>Memory</th>\n            <th>FPS</th>\n          </tr>\n        </thead>\n        <tbody>\n          ${samples.slice(-10).reverse().map(s => `\n            <tr>\n              <td>${new Date(s.timestamp).toLocaleTimeString()}</td>\n              <td>${s.inference.count}</td>\n              <td>${s.inference.avgTime.toFixed(2)}ms</td>\n              <td>${s.inference.throughput.toFixed(1)}/s</td>\n              <td>${formatBytes(s.memory.usedHeap)}</td>\n              <td>${s.system.fps}</td>\n            </tr>\n          `).join('')}\n        </tbody>\n      </table>\n    </div>\n    \n    <footer>\n      Generated at ${new Date().toLocaleString()} | edgeFlow.js Performance Monitor\n    </footer>\n  </div>\n</body>\n</html>\n  `.trim();\n}\n\n/**\n * Generate SVG chart path\n */\nfunction generateChartPath(samples: PerformanceSample[]): string {\n  if (samples.length < 2) return '';\n  \n  const width = 600;\n  const height = 180;\n  const padding = 10;\n  \n  const times = samples.map(s => s.inference.avgTime);\n  const maxTime = Math.max(...times, 1);\n  \n  const points = samples.map((s, i) => {\n    const x = padding + (i / (samples.length - 1)) * (width - 2 * padding);\n    const y = height - padding - (s.inference.avgTime / maxTime) * (height - 2 * padding);\n    return `${x},${y}`;\n  });\n  \n  const linePath = `M ${points.join(' L ')}`;\n  const areaPath = `M ${padding},${height - padding} L ${points.join(' L ')} L ${width - padding},${height - padding} Z`;\n  \n  // Grid lines\n  const gridLines = [];\n  for (let i = 0; i <= 4; i++) {\n    const y = padding + (i / 4) * (height - 2 * padding);\n    gridLines.push(`<line class=\"chart-grid\" x1=\"${padding}\" y1=\"${y}\" x2=\"${width - padding}\" y2=\"${y}\"/>`);\n  }\n  \n  return `\n    ${gridLines.join('\\n')}\n    <path class=\"chart-area\" d=\"${areaPath}\"/>\n    <path class=\"chart-line\" d=\"${linePath}\"/>\n  `;\n}\n\n/**\n * Generate ASCII dashboard\n */\nexport function generateAsciiDashboard(monitor: PerformanceMonitor): string {\n  const summary = monitor.getSummary();\n  const samples = monitor.getSamples();\n  const lastSample = samples[samples.length - 1];\n  \n  const formatBytes = (bytes: number): string => {\n    if (bytes < 1024) return `${bytes} B`;\n    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n    if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n    return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n  };\n  \n  const bar = (value: number, max: number, width: number = 20): string => {\n    const filled = Math.round((value / max) * width);\n    return '█'.repeat(filled) + '░'.repeat(width - filled);\n  };\n  \n  const lines = [\n    '╔══════════════════════════════════════════════════════════════════════════╗',\n    '║             edgeFlow.js Performance Monitor Dashboard                   ║',\n    '╠══════════════════════════════════════════════════════════════════════════╣',\n    '║                                                                          ║',\n    `║  Total Inferences:  ${summary.totalInferences.toString().padStart(10)}                                      ║`,\n    `║  Avg Inference:     ${summary.avgInferenceTime.toFixed(2).padStart(10)}ms                                     ║`,\n    `║  Throughput:        ${summary.avgThroughput.toFixed(2).padStart(10)} ops/s                                 ║`,\n    `║  Avg FPS:           ${Math.round(summary.avgFPS).toString().padStart(10)}                                      ║`,\n    '║                                                                          ║',\n    '╟──────────────────────────────────────────────────────────────────────────╢',\n    '║ Memory Usage                                                             ║',\n    `║  Heap:    ${bar(summary.avgMemoryUsage, 1)} ${(summary.avgMemoryUsage * 100).toFixed(0).padStart(3)}%            ║`,\n    `║  Used:    ${formatBytes(lastSample?.memory.usedHeap ?? 0).padStart(10)}                                          ║`,\n    `║  Tensor:  ${formatBytes(lastSample?.memory.tensorMemory ?? 0).padStart(10)}                                          ║`,\n    `║  Cache:   ${formatBytes(lastSample?.memory.cacheMemory ?? 0).padStart(10)}                                          ║`,\n    '║                                                                          ║',\n    '╟──────────────────────────────────────────────────────────────────────────╢',\n    '║ Inference Time History (last 30 samples)                                 ║',\n    '║                                                                          ║',\n  ];\n  \n  // Add mini chart\n  const recentSamples = samples.slice(-30);\n  if (recentSamples.length > 0) {\n    const times = recentSamples.map(s => s.inference.avgTime);\n    const maxTime = Math.max(...times, 1);\n    const chartHeight = 5;\n    \n    for (let row = chartHeight; row > 0; row--) {\n      let line = '║  ';\n      for (const time of times) {\n        const height = Math.ceil((time / maxTime) * chartHeight);\n        line += height >= row ? '▓' : ' ';\n      }\n      lines.push(line.padEnd(76) + '║');\n    }\n    lines.push('║  ' + '─'.repeat(30) + '                                            ║');\n  }\n  \n  lines.push('║                                                                          ║');\n  lines.push(`║  Last updated: ${new Date().toLocaleString().padEnd(40)}             ║`);\n  lines.push('╚══════════════════════════════════════════════════════════════════════════╝');\n  \n  return lines.join('\\n');\n}\n\n// ============================================================================\n// Global Instance\n// ============================================================================\n\nlet globalMonitor: PerformanceMonitor | null = null;\n\n/**\n * Get or create global monitor\n */\nexport function getMonitor(config?: MonitorConfig): PerformanceMonitor {\n  if (!globalMonitor || config) {\n    globalMonitor = new PerformanceMonitor(config);\n  }\n  return globalMonitor;\n}\n\n/**\n * Start monitoring\n */\nexport function startMonitoring(config?: MonitorConfig): PerformanceMonitor {\n  const monitor = getMonitor(config);\n  monitor.start();\n  return monitor;\n}\n\n/**\n * Stop monitoring\n */\nexport function stopMonitoring(): void {\n  globalMonitor?.stop();\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport default {\n  PerformanceMonitor,\n  getMonitor,\n  startMonitoring,\n  stopMonitoring,\n  generateDashboardHTML,\n  generateAsciiDashboard,\n};\n"
  },
  {
    "path": "src/tools/quantization.ts",
    "content": "/**\n * edgeFlow.js - Model Compression & Quantization Tools\n * \n * In-browser model quantization and compression utilities.\n * Supports dynamic quantization (no calibration data needed).\n */\n\nimport { EdgeFlowTensor, DataType } from '../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Quantization type\n */\nexport type QuantizationType = 'int8' | 'uint8' | 'int4' | 'float16' | 'dynamic';\n\n/**\n * Quantization options\n */\nexport interface QuantizationOptions {\n  /** Quantization type */\n  type: QuantizationType;\n  \n  /** Layers/ops to skip quantization (by name pattern) */\n  skipPatterns?: (string | RegExp)[];\n  \n  /** Per-channel quantization (more accurate, larger model) */\n  perChannel?: boolean;\n  \n  /** Symmetric quantization (simpler, slightly less accurate) */\n  symmetric?: boolean;\n  \n  /** Progress callback */\n  onProgress?: (progress: QuantizationProgress) => void;\n  \n  /** Minimum tensor size to quantize (in elements) */\n  minTensorSize?: number;\n  \n  /** Keep original weights for comparison */\n  keepOriginal?: boolean;\n}\n\n/**\n * Quantization progress\n */\nexport interface QuantizationProgress {\n  stage: 'analyzing' | 'quantizing' | 'packing' | 'complete';\n  current: number;\n  total: number;\n  percent: number;\n  layerName?: string;\n}\n\n/**\n * Quantization result\n */\nexport interface QuantizationResult {\n  /** Quantized model data */\n  data: ArrayBuffer;\n  \n  /** Original model size in bytes */\n  originalSize: number;\n  \n  /** Quantized model size in bytes */\n  quantizedSize: number;\n  \n  /** Compression ratio */\n  compressionRatio: number;\n  \n  /** Number of tensors quantized */\n  tensorsQuantized: number;\n  \n  /** Number of tensors skipped */\n  tensorsSkipped: number;\n  \n  /** Quantization statistics per layer */\n  layerStats: LayerQuantizationStats[];\n  \n  /** Overall statistics */\n  stats: QuantizationStats;\n}\n\n/**\n * Layer quantization statistics\n */\nexport interface LayerQuantizationStats {\n  name: string;\n  originalDtype: string;\n  quantizedDtype: string;\n  originalSize: number;\n  quantizedSize: number;\n  scale: number | number[];\n  zeroPoint: number | number[];\n  minValue: number;\n  maxValue: number;\n  skipped: boolean;\n  skipReason?: string;\n}\n\n/**\n * Overall quantization statistics\n */\nexport interface QuantizationStats {\n  totalParameters: number;\n  quantizedParameters: number;\n  averageScale: number;\n  minScale: number;\n  maxScale: number;\n  errorEstimate: number;\n}\n\n/**\n * Quantization parameters for a tensor\n */\ninterface QuantizationParams {\n  scale: number | Float32Array;\n  zeroPoint: number | Int32Array;\n  min: number;\n  max: number;\n}\n\n// ============================================================================\n// Quantization Core\n// ============================================================================\n\n/**\n * Calculate quantization parameters for a tensor\n */\nfunction calculateQuantParams(\n  data: Float32Array,\n  bits: number,\n  symmetric: boolean,\n  perChannel: boolean,\n  channelAxis: number = 0,\n  shape: number[] = []\n): QuantizationParams {\n  const qmin = symmetric ? -(1 << (bits - 1)) : 0;\n  const qmax = symmetric ? (1 << (bits - 1)) - 1 : (1 << bits) - 1;\n  \n  if (perChannel && shape.length > 1) {\n    // Per-channel quantization\n    const numChannels = shape[channelAxis] ?? 1;\n    const scales = new Float32Array(numChannels);\n    const zeroPoints = new Int32Array(numChannels);\n    \n    const channelSize = data.length / numChannels;\n    let globalMin = Infinity;\n    let globalMax = -Infinity;\n    \n    for (let c = 0; c < numChannels; c++) {\n      let min = Infinity;\n      let max = -Infinity;\n      \n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        min = Math.min(min, val);\n        max = Math.max(max, val);\n      }\n      \n      globalMin = Math.min(globalMin, min);\n      globalMax = Math.max(globalMax, max);\n      \n      if (symmetric) {\n        const absMax = Math.max(Math.abs(min), Math.abs(max));\n        scales[c] = absMax / qmax;\n        zeroPoints[c] = 0;\n      } else {\n        scales[c] = (max - min) / (qmax - qmin);\n        zeroPoints[c] = Math.round(qmin - min / (scales[c] || 1));\n      }\n      \n      // Avoid division by zero\n      if (scales[c] === 0) scales[c] = 1;\n    }\n    \n    return { scale: scales, zeroPoint: zeroPoints, min: globalMin, max: globalMax };\n  } else {\n    // Per-tensor quantization\n    let min = Infinity;\n    let max = -Infinity;\n    \n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      min = Math.min(min, val);\n      max = Math.max(max, val);\n    }\n    \n    let scale: number;\n    let zeroPoint: number;\n    \n    if (symmetric) {\n      const absMax = Math.max(Math.abs(min), Math.abs(max));\n      scale = absMax / qmax;\n      zeroPoint = 0;\n    } else {\n      scale = (max - min) / (qmax - qmin);\n      zeroPoint = Math.round(qmin - min / (scale || 1));\n    }\n    \n    // Avoid division by zero\n    if (scale === 0) scale = 1;\n    \n    return { scale, zeroPoint, min, max };\n  }\n}\n\n/**\n * Quantize float32 data to int8\n */\nfunction quantizeToInt8(\n  data: Float32Array,\n  scale: number | Float32Array,\n  zeroPoint: number | Int32Array,\n  perChannel: boolean,\n  channelSize: number = data.length\n): Int8Array {\n  const result = new Int8Array(data.length);\n  \n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = (zeroPoint as Int32Array)[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        result[idx] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n      }\n    }\n  } else {\n    const s = scale as number;\n    const zp = zeroPoint as number;\n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      result[i] = Math.max(-128, Math.min(127, Math.round(val / s + zp)));\n    }\n  }\n  \n  return result;\n}\n\n/**\n * Quantize float32 data to uint8\n */\nfunction quantizeToUint8(\n  data: Float32Array,\n  scale: number | Float32Array,\n  zeroPoint: number | Int32Array,\n  perChannel: boolean,\n  channelSize: number = data.length\n): Uint8Array {\n  const result = new Uint8Array(data.length);\n  \n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = (zeroPoint as Int32Array)[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        const val = data[idx] ?? 0;\n        result[idx] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n      }\n    }\n  } else {\n    const s = scale as number;\n    const zp = zeroPoint as number;\n    for (let i = 0; i < data.length; i++) {\n      const val = data[i] ?? 0;\n      result[i] = Math.max(0, Math.min(255, Math.round(val / s + zp)));\n    }\n  }\n  \n  return result;\n}\n\n/**\n * Quantize float32 data to int4 (packed as uint8, 2 values per byte)\n */\nfunction quantizeToInt4(\n  data: Float32Array,\n  scale: number,\n  zeroPoint: number\n): Uint8Array {\n  const packedLength = Math.ceil(data.length / 2);\n  const result = new Uint8Array(packedLength);\n  \n  for (let i = 0; i < data.length; i += 2) {\n    const val1 = data[i] ?? 0;\n    const val2 = data[i + 1] ?? 0;\n    \n    // Quantize to range [-8, 7] then shift to [0, 15]\n    const q1 = Math.max(0, Math.min(15, Math.round(val1 / scale + zeroPoint + 8)));\n    const q2 = Math.max(0, Math.min(15, Math.round(val2 / scale + zeroPoint + 8)));\n    \n    // Pack two 4-bit values into one byte\n    result[i >> 1] = (q1 << 4) | q2;\n  }\n  \n  return result;\n}\n\n/**\n * Convert float32 to float16 (stored in Uint16Array)\n */\nfunction quantizeToFloat16(data: Float32Array): Uint16Array {\n  const result = new Uint16Array(data.length);\n  \n  for (let i = 0; i < data.length; i++) {\n    result[i] = float32ToFloat16(data[i] ?? 0);\n  }\n  \n  return result;\n}\n\n/**\n * Convert a single float32 value to float16 bits\n */\nfunction float32ToFloat16(value: number): number {\n  const float32View = new Float32Array(1);\n  const int32View = new Int32Array(float32View.buffer);\n  \n  float32View[0] = value;\n  const f = int32View[0]!;\n  \n  const sign = (f >> 16) & 0x8000;\n  const exponent = ((f >> 23) & 0xff) - 127 + 15;\n  const mantissa = f & 0x7fffff;\n  \n  if (exponent <= 0) {\n    // Denormalized or zero\n    if (exponent < -10) {\n      return sign;\n    }\n    const m = (mantissa | 0x800000) >> (1 - exponent);\n    return sign | (m >> 13);\n  } else if (exponent >= 31) {\n    // Overflow to infinity\n    return sign | 0x7c00;\n  }\n  \n  return sign | (exponent << 10) | (mantissa >> 13);\n}\n\n/**\n * Dequantize int8 data back to float32\n */\nexport function dequantizeInt8(\n  data: Int8Array,\n  scale: number | Float32Array,\n  zeroPoint: number | Int32Array,\n  perChannel: boolean = false,\n  channelSize: number = data.length\n): Float32Array {\n  const result = new Float32Array(data.length);\n  \n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = (zeroPoint as Int32Array)[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        result[idx] = ((data[idx] ?? 0) - zp) * s;\n      }\n    }\n  } else {\n    const s = scale as number;\n    const zp = zeroPoint as number;\n    for (let i = 0; i < data.length; i++) {\n      result[i] = ((data[i] ?? 0) - zp) * s;\n    }\n  }\n  \n  return result;\n}\n\n/**\n * Dequantize uint8 data back to float32\n */\nexport function dequantizeUint8(\n  data: Uint8Array,\n  scale: number | Float32Array,\n  zeroPoint: number | Int32Array,\n  perChannel: boolean = false,\n  channelSize: number = data.length\n): Float32Array {\n  const result = new Float32Array(data.length);\n  \n  if (perChannel && scale instanceof Float32Array) {\n    const numChannels = scale.length;\n    for (let c = 0; c < numChannels; c++) {\n      const s = scale[c] ?? 1;\n      const zp = (zeroPoint as Int32Array)[c] ?? 0;\n      for (let i = 0; i < channelSize; i++) {\n        const idx = c * channelSize + i;\n        result[idx] = ((data[idx] ?? 0) - zp) * s;\n      }\n    }\n  } else {\n    const s = scale as number;\n    const zp = zeroPoint as number;\n    for (let i = 0; i < data.length; i++) {\n      result[i] = ((data[i] ?? 0) - zp) * s;\n    }\n  }\n  \n  return result;\n}\n\n/**\n * Convert float16 bits back to float32\n */\nexport function float16ToFloat32(value: number): number {\n  const sign = (value & 0x8000) >> 15;\n  const exponent = (value & 0x7c00) >> 10;\n  const mantissa = value & 0x03ff;\n  \n  if (exponent === 0) {\n    if (mantissa === 0) {\n      return sign === 0 ? 0 : -0;\n    }\n    // Denormalized\n    return (sign === 0 ? 1 : -1) * Math.pow(2, -14) * (mantissa / 1024);\n  } else if (exponent === 31) {\n    if (mantissa === 0) {\n      return sign === 0 ? Infinity : -Infinity;\n    }\n    return NaN;\n  }\n  \n  return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + mantissa / 1024);\n}\n\n/**\n * Dequantize float16 data back to float32\n */\nexport function dequantizeFloat16(data: Uint16Array): Float32Array {\n  const result = new Float32Array(data.length);\n  for (let i = 0; i < data.length; i++) {\n    result[i] = float16ToFloat32(data[i] ?? 0);\n  }\n  return result;\n}\n\n// ============================================================================\n// Model Quantization\n// ============================================================================\n\n/**\n * Simple ONNX-like model representation for quantization\n */\ninterface ModelWeights {\n  name: string;\n  data: Float32Array;\n  shape: number[];\n  dtype: string;\n}\n\n/**\n * Quantized model format\n */\ninterface QuantizedModel {\n  version: number;\n  quantizationType: QuantizationType;\n  originalSize: number;\n  weights: Array<{\n    name: string;\n    data: ArrayBuffer;\n    shape: number[];\n    dtype: string;\n    originalDtype: string;\n    scale?: number | number[];\n    zeroPoint?: number | number[];\n  }>;\n}\n\n/**\n * Parse ONNX model to extract weights\n * Note: This is a simplified parser for demonstration\n */\nfunction parseModelWeights(modelData: ArrayBuffer): ModelWeights[] {\n  // Check if it's an ONNX model by magic number\n  // const view = new DataView(modelData); // Reserved for future ONNX header parsing\n  const weights: ModelWeights[] = [];\n  \n  // Simple heuristic: look for float32 arrays in the buffer\n  // In a real implementation, we'd use proper ONNX parsing\n  const float32Array = new Float32Array(modelData);\n  \n  // Create a single weight tensor from the model data\n  // This is a placeholder - real implementation would parse ONNX properly\n  weights.push({\n    name: 'model_weights',\n    data: float32Array,\n    shape: [float32Array.length],\n    dtype: 'float32',\n  });\n  \n  return weights;\n}\n\n/**\n * Serialize quantized model to ArrayBuffer\n */\nfunction serializeQuantizedModel(model: QuantizedModel): ArrayBuffer {\n  // Create a simple binary format:\n  // Header: version (4 bytes) + type (4 bytes) + originalSize (8 bytes) + numWeights (4 bytes)\n  // For each weight: nameLen (4) + name + shapeLen (4) + shape + dtypeLen (4) + dtype + \n  //                  origDtypeLen (4) + origDtype + hasScale (1) + scale + hasZP (1) + zp + dataLen (8) + data\n  \n  const encoder = new TextEncoder();\n  \n  // Calculate total size\n  let totalSize = 20; // Header\n  \n  for (const weight of model.weights) {\n    const nameBytes = encoder.encode(weight.name);\n    const dtypeBytes = encoder.encode(weight.dtype);\n    const origDtypeBytes = encoder.encode(weight.originalDtype);\n    \n    totalSize += 4 + nameBytes.length; // name\n    totalSize += 4 + weight.shape.length * 4; // shape\n    totalSize += 4 + dtypeBytes.length; // dtype\n    totalSize += 4 + origDtypeBytes.length; // originalDtype\n    totalSize += 1; // hasScale\n    if (weight.scale !== undefined) {\n      totalSize += Array.isArray(weight.scale) ? 4 + weight.scale.length * 4 : 4;\n    }\n    totalSize += 1; // hasZeroPoint\n    if (weight.zeroPoint !== undefined) {\n      totalSize += Array.isArray(weight.zeroPoint) ? 4 + weight.zeroPoint.length * 4 : 4;\n    }\n    totalSize += 8 + weight.data.byteLength; // data\n  }\n  \n  const buffer = new ArrayBuffer(totalSize);\n  const view = new DataView(buffer);\n  const uint8 = new Uint8Array(buffer);\n  let offset = 0;\n  \n  // Write header\n  view.setUint32(offset, model.version, true); offset += 4;\n  view.setUint32(offset, ['int8', 'uint8', 'int4', 'float16', 'dynamic'].indexOf(model.quantizationType), true); offset += 4;\n  // Write originalSize as two 32-bit integers (for 64-bit compatibility)\n  view.setUint32(offset, model.originalSize & 0xFFFFFFFF, true); offset += 4;\n  view.setUint32(offset, (model.originalSize / 0x100000000) >>> 0, true); offset += 4;\n  view.setUint32(offset, model.weights.length, true); offset += 4;\n  \n  // Write weights\n  for (const weight of model.weights) {\n    const nameBytes = encoder.encode(weight.name);\n    const dtypeBytes = encoder.encode(weight.dtype);\n    const origDtypeBytes = encoder.encode(weight.originalDtype);\n    \n    // Name\n    view.setUint32(offset, nameBytes.length, true); offset += 4;\n    uint8.set(nameBytes, offset); offset += nameBytes.length;\n    \n    // Shape\n    view.setUint32(offset, weight.shape.length, true); offset += 4;\n    for (const dim of weight.shape) {\n      view.setInt32(offset, dim, true); offset += 4;\n    }\n    \n    // Dtype\n    view.setUint32(offset, dtypeBytes.length, true); offset += 4;\n    uint8.set(dtypeBytes, offset); offset += dtypeBytes.length;\n    \n    // Original dtype\n    view.setUint32(offset, origDtypeBytes.length, true); offset += 4;\n    uint8.set(origDtypeBytes, offset); offset += origDtypeBytes.length;\n    \n    // Scale\n    if (weight.scale !== undefined) {\n      view.setUint8(offset, 1); offset += 1;\n      if (Array.isArray(weight.scale)) {\n        view.setUint32(offset, weight.scale.length, true); offset += 4;\n        for (const s of weight.scale) {\n          view.setFloat32(offset, s, true); offset += 4;\n        }\n      } else {\n        view.setUint32(offset, 1, true); offset += 4;\n        view.setFloat32(offset, weight.scale, true); offset += 4;\n      }\n    } else {\n      view.setUint8(offset, 0); offset += 1;\n    }\n    \n    // Zero point\n    if (weight.zeroPoint !== undefined) {\n      view.setUint8(offset, 1); offset += 1;\n      if (Array.isArray(weight.zeroPoint)) {\n        view.setUint32(offset, weight.zeroPoint.length, true); offset += 4;\n        for (const zp of weight.zeroPoint) {\n          view.setInt32(offset, zp, true); offset += 4;\n        }\n      } else {\n        view.setUint32(offset, 1, true); offset += 4;\n        view.setInt32(offset, weight.zeroPoint, true); offset += 4;\n      }\n    } else {\n      view.setUint8(offset, 0); offset += 1;\n    }\n    \n    // Data\n    const dataLow = weight.data.byteLength & 0xFFFFFFFF;\n    const dataHigh = (weight.data.byteLength / 0x100000000) >>> 0;\n    view.setUint32(offset, dataLow, true); offset += 4;\n    view.setUint32(offset, dataHigh, true); offset += 4;\n    uint8.set(new Uint8Array(weight.data), offset); offset += weight.data.byteLength;\n  }\n  \n  return buffer;\n}\n\n/**\n * Quantize a model\n */\nexport async function quantizeModel(\n  modelData: ArrayBuffer,\n  options: QuantizationOptions\n): Promise<QuantizationResult> {\n  const {\n    type,\n    skipPatterns = [],\n    perChannel = false,\n    symmetric = true,\n    onProgress,\n    minTensorSize = 100,\n  } = options;\n  \n  const originalSize = modelData.byteLength;\n  const layerStats: LayerQuantizationStats[] = [];\n  let tensorsQuantized = 0;\n  let tensorsSkipped = 0;\n  \n  // Parse model weights\n  onProgress?.({ stage: 'analyzing', current: 0, total: 1, percent: 0 });\n  const weights = parseModelWeights(modelData);\n  \n  const quantizedWeights: QuantizedModel['weights'] = [];\n  let totalParams = 0;\n  let quantizedParams = 0;\n  const scales: number[] = [];\n  \n  // Quantize each weight tensor\n  for (let i = 0; i < weights.length; i++) {\n    const weight = weights[i]!;\n    const percent = ((i + 1) / weights.length) * 100;\n    \n    onProgress?.({\n      stage: 'quantizing',\n      current: i + 1,\n      total: weights.length,\n      percent,\n      layerName: weight.name,\n    });\n    \n    totalParams += weight.data.length;\n    \n    // Check if should skip\n    const shouldSkip = \n      weight.data.length < minTensorSize ||\n      skipPatterns.some(pattern => {\n        if (typeof pattern === 'string') {\n          return weight.name.includes(pattern);\n        }\n        return pattern.test(weight.name);\n      });\n    \n    if (shouldSkip) {\n      tensorsSkipped++;\n      layerStats.push({\n        name: weight.name,\n        originalDtype: weight.dtype,\n        quantizedDtype: weight.dtype,\n        originalSize: weight.data.byteLength,\n        quantizedSize: weight.data.byteLength,\n        scale: 1,\n        zeroPoint: 0,\n        minValue: Math.min(...weight.data),\n        maxValue: Math.max(...weight.data),\n        skipped: true,\n        skipReason: weight.data.length < minTensorSize \n          ? 'Tensor too small' \n          : 'Matched skip pattern',\n      });\n      \n      quantizedWeights.push({\n        name: weight.name,\n        data: weight.data.buffer.slice(0) as ArrayBuffer,\n        shape: weight.shape,\n        dtype: weight.dtype,\n        originalDtype: weight.dtype,\n      });\n      continue;\n    }\n    \n    // Calculate quantization parameters\n    const bits = type === 'int4' ? 4 : 8;\n    const params = calculateQuantParams(\n      weight.data,\n      bits,\n      symmetric,\n      perChannel,\n      0,\n      weight.shape\n    );\n    \n    // Quantize data\n    let quantizedData: ArrayBuffer;\n    let quantizedDtype: string;\n    \n    switch (type) {\n      case 'int8':\n        const int8Data = quantizeToInt8(\n          weight.data,\n          params.scale,\n          params.zeroPoint,\n          perChannel,\n          perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length\n        );\n        quantizedData = int8Data.buffer.slice(0) as ArrayBuffer;\n        quantizedDtype = 'int8';\n        break;\n        \n      case 'uint8':\n        const uint8Data = quantizeToUint8(\n          weight.data,\n          params.scale,\n          params.zeroPoint,\n          perChannel,\n          perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length\n        );\n        quantizedData = uint8Data.buffer.slice(0) as ArrayBuffer;\n        quantizedDtype = 'uint8';\n        break;\n        \n      case 'int4':\n        const int4Data = quantizeToInt4(\n          weight.data,\n          params.scale as number,\n          params.zeroPoint as number\n        );\n        quantizedData = int4Data.buffer.slice(0) as ArrayBuffer;\n        quantizedDtype = 'int4';\n        break;\n        \n      case 'float16':\n        const fp16Data = quantizeToFloat16(weight.data);\n        quantizedData = fp16Data.buffer.slice(0) as ArrayBuffer;\n        quantizedDtype = 'float16';\n        break;\n        \n      case 'dynamic':\n      default:\n        // Dynamic quantization: use int8 for weights\n        const dynData = quantizeToInt8(\n          weight.data,\n          params.scale,\n          params.zeroPoint,\n          perChannel,\n          perChannel ? weight.data.length / (weight.shape[0] ?? 1) : weight.data.length\n        );\n        quantizedData = dynData.buffer.slice(0) as ArrayBuffer;\n        quantizedDtype = 'int8';\n        break;\n    }\n    \n    tensorsQuantized++;\n    quantizedParams += weight.data.length;\n    \n    const scaleValue = params.scale instanceof Float32Array \n      ? Array.from(params.scale)\n      : params.scale;\n    const zpValue = params.zeroPoint instanceof Int32Array\n      ? Array.from(params.zeroPoint)\n      : params.zeroPoint;\n    \n    if (typeof scaleValue === 'number') {\n      scales.push(scaleValue);\n    } else {\n      scales.push(...scaleValue);\n    }\n    \n    layerStats.push({\n      name: weight.name,\n      originalDtype: weight.dtype,\n      quantizedDtype,\n      originalSize: weight.data.byteLength,\n      quantizedSize: quantizedData.byteLength,\n      scale: scaleValue,\n      zeroPoint: zpValue,\n      minValue: params.min,\n      maxValue: params.max,\n      skipped: false,\n    });\n    \n    quantizedWeights.push({\n      name: weight.name,\n      data: quantizedData,\n      shape: weight.shape,\n      dtype: quantizedDtype,\n      originalDtype: weight.dtype,\n      scale: scaleValue,\n      zeroPoint: zpValue,\n    });\n  }\n  \n  // Pack into final format\n  onProgress?.({ stage: 'packing', current: 0, total: 1, percent: 0 });\n  \n  const quantizedModel: QuantizedModel = {\n    version: 1,\n    quantizationType: type,\n    originalSize,\n    weights: quantizedWeights,\n  };\n  \n  const quantizedData = serializeQuantizedModel(quantizedModel);\n  \n  onProgress?.({ stage: 'complete', current: 1, total: 1, percent: 100 });\n  \n  // Calculate statistics\n  const avgScale = scales.length > 0 \n    ? scales.reduce((a, b) => a + b, 0) / scales.length \n    : 1;\n  const minScale = scales.length > 0 ? Math.min(...scales) : 1;\n  const maxScale = scales.length > 0 ? Math.max(...scales) : 1;\n  \n  // Estimate quantization error (very rough approximation)\n  const bitsReduction = type === 'int4' ? 8 : type === 'float16' ? 2 : 4;\n  const errorEstimate = avgScale / bitsReduction;\n  \n  return {\n    data: quantizedData,\n    originalSize,\n    quantizedSize: quantizedData.byteLength,\n    compressionRatio: originalSize / quantizedData.byteLength,\n    tensorsQuantized,\n    tensorsSkipped,\n    layerStats,\n    stats: {\n      totalParameters: totalParams,\n      quantizedParameters: quantizedParams,\n      averageScale: avgScale,\n      minScale,\n      maxScale,\n      errorEstimate,\n    },\n  };\n}\n\n// ============================================================================\n// Tensor Quantization (for individual tensors)\n// ============================================================================\n\n/**\n * Quantize a single EdgeFlowTensor\n */\nexport function quantizeTensor(\n  tensor: EdgeFlowTensor,\n  type: QuantizationType,\n  options: { symmetric?: boolean; perChannel?: boolean } = {}\n): {\n  tensor: EdgeFlowTensor;\n  scale: number | number[];\n  zeroPoint: number | number[];\n} {\n  const { symmetric = true, perChannel = false } = options;\n  const data = tensor.toFloat32Array();\n  const shape = tensor.shape as number[];\n  \n  const bits = type === 'int4' ? 4 : 8;\n  const params = calculateQuantParams(\n    data,\n    bits,\n    symmetric,\n    perChannel,\n    0,\n    shape\n  );\n  \n  let quantizedData: Int8Array | Uint8Array | Uint16Array;\n  let dtype: DataType;\n  \n  switch (type) {\n    case 'int8':\n      quantizedData = quantizeToInt8(\n        data,\n        params.scale,\n        params.zeroPoint,\n        perChannel\n      );\n      dtype = 'int32'; // Store as int32 since we don't have int8 dtype\n      break;\n      \n    case 'uint8':\n      quantizedData = quantizeToUint8(\n        data,\n        params.scale,\n        params.zeroPoint,\n        perChannel\n      );\n      dtype = 'int32';\n      break;\n      \n    case 'float16':\n      quantizedData = quantizeToFloat16(data);\n      dtype = 'float32'; // Will be stored differently\n      break;\n      \n    default:\n      quantizedData = quantizeToInt8(\n        data,\n        params.scale,\n        params.zeroPoint,\n        perChannel\n      );\n      dtype = 'int32';\n  }\n  \n  const scaleValue = params.scale instanceof Float32Array\n    ? Array.from(params.scale)\n    : params.scale;\n  const zpValue = params.zeroPoint instanceof Int32Array\n    ? Array.from(params.zeroPoint)\n    : params.zeroPoint;\n  \n  return {\n    tensor: new EdgeFlowTensor(Array.from(quantizedData), shape, dtype),\n    scale: scaleValue,\n    zeroPoint: zpValue,\n  };\n}\n\n/**\n * Dequantize a tensor back to float32\n */\nexport function dequantizeTensor(\n  tensor: EdgeFlowTensor,\n  scale: number | number[],\n  zeroPoint: number | number[],\n  type: QuantizationType\n): EdgeFlowTensor {\n  const data = tensor.toArray();\n  const shape = tensor.shape as number[];\n  \n  let dequantizedData: Float32Array;\n  \n  const scaleArr = Array.isArray(scale) ? new Float32Array(scale) : scale;\n  const zpArr = Array.isArray(zeroPoint) ? new Int32Array(zeroPoint) : zeroPoint;\n  const perChannel = Array.isArray(scale);\n  \n  switch (type) {\n    case 'int8':\n      dequantizedData = dequantizeInt8(\n        new Int8Array(data.map(Number)),\n        scaleArr,\n        zpArr,\n        perChannel\n      );\n      break;\n      \n    case 'uint8':\n      dequantizedData = dequantizeUint8(\n        new Uint8Array(data.map(Number)),\n        scaleArr,\n        zpArr,\n        perChannel\n      );\n      break;\n      \n    case 'float16':\n      dequantizedData = dequantizeFloat16(new Uint16Array(data.map(Number)));\n      break;\n      \n    default:\n      dequantizedData = dequantizeInt8(\n        new Int8Array(data.map(Number)),\n        scaleArr,\n        zpArr,\n        perChannel\n      );\n  }\n  \n  return new EdgeFlowTensor(Array.from(dequantizedData), shape, 'float32');\n}\n\n// ============================================================================\n// Pruning\n// ============================================================================\n\n/**\n * Pruning options\n */\nexport interface PruningOptions {\n  /** Pruning ratio (0-1, default: 0.5 = 50% sparsity) */\n  ratio?: number;\n  \n  /** Pruning method */\n  method?: 'magnitude' | 'random' | 'structured';\n  \n  /** For structured pruning: dimension to prune along */\n  dim?: number;\n  \n  /** Minimum absolute value to keep */\n  threshold?: number;\n  \n  /** Progress callback */\n  onProgress?: (progress: { current: number; total: number; percent: number }) => void;\n}\n\n/**\n * Pruning result\n */\nexport interface PruningResult {\n  /** Pruned model data */\n  data: ArrayBuffer;\n  \n  /** Original size */\n  originalSize: number;\n  \n  /** Pruned size (sparse representation) */\n  prunedSize: number;\n  \n  /** Sparsity ratio achieved */\n  sparsity: number;\n  \n  /** Number of parameters pruned */\n  parametersPruned: number;\n  \n  /** Total parameters */\n  totalParameters: number;\n}\n\n/**\n * Prune a tensor using magnitude-based pruning\n */\nexport function pruneTensor(\n  tensor: EdgeFlowTensor,\n  options: PruningOptions = {}\n): {\n  tensor: EdgeFlowTensor;\n  mask: EdgeFlowTensor;\n  sparsity: number;\n} {\n  const { ratio = 0.5, method = 'magnitude', threshold } = options;\n  const data = tensor.toFloat32Array();\n  const shape = tensor.shape as number[];\n  \n  const mask = new Float32Array(data.length);\n  const prunedData = new Float32Array(data.length);\n  let prunedCount = 0;\n  \n  if (method === 'magnitude') {\n    // Get threshold based on ratio\n    const absValues = Array.from(data).map(Math.abs).sort((a, b) => a - b);\n    const thresholdIndex = Math.floor(absValues.length * ratio);\n    const computedThreshold = threshold ?? (absValues[thresholdIndex] ?? 0);\n    \n    for (let i = 0; i < data.length; i++) {\n      if (Math.abs(data[i] ?? 0) > computedThreshold) {\n        mask[i] = 1;\n        prunedData[i] = data[i] ?? 0;\n      } else {\n        mask[i] = 0;\n        prunedData[i] = 0;\n        prunedCount++;\n      }\n    }\n  } else if (method === 'random') {\n    for (let i = 0; i < data.length; i++) {\n      if (Math.random() > ratio) {\n        mask[i] = 1;\n        prunedData[i] = data[i] ?? 0;\n      } else {\n        mask[i] = 0;\n        prunedData[i] = 0;\n        prunedCount++;\n      }\n    }\n  }\n  \n  return {\n    tensor: new EdgeFlowTensor(Array.from(prunedData), shape, 'float32'),\n    mask: new EdgeFlowTensor(Array.from(mask), shape, 'float32'),\n    sparsity: prunedCount / data.length,\n  };\n}\n\n/**\n * Prune a model\n */\nexport async function pruneModel(\n  modelData: ArrayBuffer,\n  options: PruningOptions = {}\n): Promise<PruningResult> {\n  const { onProgress } = options;\n  \n  onProgress?.({ current: 0, total: 1, percent: 0 });\n  \n  // This is a simplified implementation\n  // Real implementation would parse the model properly\n  const weights = parseModelWeights(modelData);\n  let totalParams = 0;\n  let prunedParams = 0;\n  \n  for (const weight of weights) {\n    totalParams += weight.data.length;\n    \n    const tensor = new EdgeFlowTensor(\n      Array.from(weight.data),\n      weight.shape,\n      'float32'\n    );\n    \n    const { sparsity } = pruneTensor(tensor, options);\n    prunedParams += Math.floor(weight.data.length * sparsity);\n  }\n  \n  onProgress?.({ current: 1, total: 1, percent: 100 });\n  \n  return {\n    data: modelData, // In a real implementation, we'd create a sparse format\n    originalSize: modelData.byteLength,\n    prunedSize: modelData.byteLength, // Would be smaller with sparse format\n    sparsity: prunedParams / totalParams,\n    parametersPruned: prunedParams,\n    totalParameters: totalParams,\n  };\n}\n\n// ============================================================================\n// Model Analysis\n// ============================================================================\n\n/**\n * Model analysis result\n */\nexport interface ModelAnalysis {\n  /** Total model size in bytes */\n  totalSize: number;\n  \n  /** Number of tensors */\n  tensorCount: number;\n  \n  /** Total number of parameters */\n  totalParameters: number;\n  \n  /** Parameter breakdown by dtype */\n  dtypeBreakdown: Record<string, { count: number; size: number }>;\n  \n  /** Largest tensors */\n  largestTensors: Array<{ name: string; size: number; shape: number[] }>;\n  \n  /** Estimated memory usage at runtime */\n  estimatedMemory: number;\n  \n  /** Recommended quantization type */\n  recommendedQuantization: QuantizationType;\n  \n  /** Estimated size after quantization */\n  estimatedQuantizedSizes: Record<QuantizationType, number>;\n}\n\n/**\n * Analyze a model\n */\nexport async function analyzeModel(modelData: ArrayBuffer): Promise<ModelAnalysis> {\n  const weights = parseModelWeights(modelData);\n  const totalSize = modelData.byteLength;\n  \n  const dtypeBreakdown: Record<string, { count: number; size: number }> = {};\n  let totalParams = 0;\n  \n  const tensorInfos: Array<{ name: string; size: number; shape: number[] }> = [];\n  \n  for (const weight of weights) {\n    totalParams += weight.data.length;\n    \n    const bytesPerElement = weight.dtype === 'float32' ? 4 \n      : weight.dtype === 'float16' ? 2 \n      : weight.dtype === 'int8' ? 1 \n      : 4;\n    const size = weight.data.length * bytesPerElement;\n    \n    if (!dtypeBreakdown[weight.dtype]) {\n      dtypeBreakdown[weight.dtype] = { count: 0, size: 0 };\n    }\n    dtypeBreakdown[weight.dtype]!.count++;\n    dtypeBreakdown[weight.dtype]!.size += size;\n    \n    tensorInfos.push({\n      name: weight.name,\n      size,\n      shape: weight.shape,\n    });\n  }\n  \n  // Sort by size and get top 10\n  tensorInfos.sort((a, b) => b.size - a.size);\n  const largestTensors = tensorInfos.slice(0, 10);\n  \n  // Estimate quantized sizes\n  const estimatedQuantizedSizes: Record<QuantizationType, number> = {\n    int8: Math.ceil(totalSize / 4),\n    uint8: Math.ceil(totalSize / 4),\n    int4: Math.ceil(totalSize / 8),\n    float16: Math.ceil(totalSize / 2),\n    dynamic: Math.ceil(totalSize / 4),\n  };\n  \n  // Recommend quantization based on model size\n  let recommendedQuantization: QuantizationType = 'dynamic';\n  if (totalSize > 500 * 1024 * 1024) {\n    recommendedQuantization = 'int4';\n  } else if (totalSize > 100 * 1024 * 1024) {\n    recommendedQuantization = 'int8';\n  } else if (totalSize > 50 * 1024 * 1024) {\n    recommendedQuantization = 'float16';\n  }\n  \n  return {\n    totalSize,\n    tensorCount: weights.length,\n    totalParameters: totalParams,\n    dtypeBreakdown,\n    largestTensors,\n    estimatedMemory: totalParams * 4, // Assuming float32 at runtime\n    recommendedQuantization,\n    estimatedQuantizedSizes,\n  };\n}\n\n// ============================================================================\n// Export Model\n// ============================================================================\n\n/**\n * Export format\n */\nexport type ExportFormat = 'onnx' | 'tflite' | 'edgeflow';\n\n/**\n * Export options\n */\nexport interface ExportOptions {\n  format: ExportFormat;\n  optimize?: boolean;\n  quantize?: QuantizationType;\n}\n\n/**\n * Export a model to different formats\n * Note: This is a placeholder - real implementation would require proper format conversion\n */\nexport async function exportModel(\n  modelData: ArrayBuffer,\n  options: ExportOptions\n): Promise<ArrayBuffer> {\n  const { format, quantize } = options;\n  \n  // Apply quantization if requested\n  let data = modelData;\n  if (quantize) {\n    const result = await quantizeModel(modelData, { type: quantize });\n    data = result.data;\n  }\n  \n  // Format conversion would happen here\n  // For now, we just return the (possibly quantized) data\n  switch (format) {\n    case 'edgeflow':\n      return data;\n    case 'onnx':\n      // Would convert to ONNX format\n      return data;\n    case 'tflite':\n      // Would convert to TFLite format\n      return data;\n    default:\n      return data;\n  }\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport default {\n  quantizeModel,\n  quantizeTensor,\n  dequantizeTensor,\n  pruneModel,\n  pruneTensor,\n  analyzeModel,\n  exportModel,\n  dequantizeInt8,\n  dequantizeUint8,\n  dequantizeFloat16,\n  float16ToFloat32,\n};\n"
  },
  {
    "path": "src/utils/cache.ts",
    "content": "/**\n * edgeFlow.js - Caching Utilities\n * \n * Smart caching for models, tensors, and inference results.\n */\n\n// ============================================================================\n// Cache Types\n// ============================================================================\n\n/**\n * Cache strategy types\n */\nexport type CacheStrategy = 'lru' | 'lfu' | 'fifo' | 'ttl';\n\n/**\n * Cache entry\n */\ninterface CacheEntry<T> {\n  value: T;\n  size: number;\n  createdAt: number;\n  accessedAt: number;\n  accessCount: number;\n  ttl?: number;\n}\n\n/**\n * Cache options\n */\nexport interface CacheOptions {\n  /** Cache strategy */\n  strategy?: CacheStrategy;\n  /** Maximum cache size in bytes */\n  maxSize?: number;\n  /** Maximum number of entries */\n  maxEntries?: number;\n  /** Default TTL in milliseconds */\n  ttl?: number;\n  /** Enable persistence to IndexedDB */\n  persistent?: boolean;\n  /** Cache name for persistence */\n  name?: string;\n}\n\n/**\n * Cache statistics\n */\nexport interface CacheStats {\n  /** Number of entries */\n  entries: number;\n  /** Total size in bytes */\n  size: number;\n  /** Cache hits */\n  hits: number;\n  /** Cache misses */\n  misses: number;\n  /** Hit rate (0-1) */\n  hitRate: number;\n}\n\n// ============================================================================\n// Cache Implementation\n// ============================================================================\n\n/**\n * Cache - Generic cache implementation\n */\nexport class Cache<T> {\n  private readonly options: Required<CacheOptions>;\n  private readonly cache: Map<string, CacheEntry<T>> = new Map();\n  private currentSize = 0;\n  private hits = 0;\n  private misses = 0;\n\n  constructor(options: CacheOptions = {}) {\n    this.options = {\n      strategy: options.strategy ?? 'lru',\n      maxSize: options.maxSize ?? 100 * 1024 * 1024, // 100MB\n      maxEntries: options.maxEntries ?? 1000,\n      ttl: options.ttl ?? 0, // 0 = no TTL\n      persistent: options.persistent ?? false,\n      name: options.name ?? 'edgeflow-cache',\n    };\n\n    // Load from persistent storage if enabled\n    if (this.options.persistent) {\n      this.loadFromStorage();\n    }\n  }\n\n  /**\n   * Get value from cache\n   */\n  get(key: string): T | undefined {\n    const entry = this.cache.get(key);\n    \n    if (!entry) {\n      this.misses++;\n      return undefined;\n    }\n\n    // Check TTL\n    if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n      this.delete(key);\n      this.misses++;\n      return undefined;\n    }\n\n    // Update access stats\n    entry.accessedAt = Date.now();\n    entry.accessCount++;\n    this.hits++;\n\n    return entry.value;\n  }\n\n  /**\n   * Set value in cache\n   */\n  set(key: string, value: T, size: number, ttl?: number): void {\n    // Remove existing entry if present\n    if (this.cache.has(key)) {\n      this.delete(key);\n    }\n\n    // Evict entries if necessary\n    while (\n      (this.currentSize + size > this.options.maxSize ||\n       this.cache.size >= this.options.maxEntries) &&\n      this.cache.size > 0\n    ) {\n      this.evict();\n    }\n\n    // Determine TTL value\n    const entryTtl = ttl !== undefined ? ttl : (this.options.ttl > 0 ? this.options.ttl : undefined);\n\n    // Add new entry\n    const entry: CacheEntry<T> = {\n      value,\n      size,\n      createdAt: Date.now(),\n      accessedAt: Date.now(),\n      accessCount: 1,\n      ttl: entryTtl,\n    };\n\n    this.cache.set(key, entry);\n    this.currentSize += size;\n\n    // Persist if enabled\n    if (this.options.persistent) {\n      this.saveToStorage();\n    }\n  }\n\n  /**\n   * Check if key exists\n   */\n  has(key: string): boolean {\n    const entry = this.cache.get(key);\n    if (!entry) return false;\n\n    // Check TTL\n    if (entry.ttl && Date.now() - entry.createdAt > entry.ttl) {\n      this.delete(key);\n      return false;\n    }\n\n    return true;\n  }\n\n  /**\n   * Delete entry\n   */\n  delete(key: string): boolean {\n    const entry = this.cache.get(key);\n    if (entry) {\n      this.currentSize -= entry.size;\n      this.cache.delete(key);\n      \n      if (this.options.persistent) {\n        this.saveToStorage();\n      }\n      \n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Clear the cache\n   */\n  clear(): void {\n    this.cache.clear();\n    this.currentSize = 0;\n    this.hits = 0;\n    this.misses = 0;\n\n    if (this.options.persistent) {\n      this.clearStorage();\n    }\n  }\n\n  /**\n   * Get cache statistics\n   */\n  getStats(): CacheStats {\n    const total = this.hits + this.misses;\n    return {\n      entries: this.cache.size,\n      size: this.currentSize,\n      hits: this.hits,\n      misses: this.misses,\n      hitRate: total > 0 ? this.hits / total : 0,\n    };\n  }\n\n  /**\n   * Evict an entry based on strategy\n   */\n  private evict(): void {\n    let keyToEvict: string | null = null;\n\n    switch (this.options.strategy) {\n      case 'lru':\n        keyToEvict = this.findLRU();\n        break;\n      case 'lfu':\n        keyToEvict = this.findLFU();\n        break;\n      case 'fifo':\n        keyToEvict = this.findOldest();\n        break;\n      case 'ttl':\n        keyToEvict = this.findExpired() ?? this.findOldest();\n        break;\n    }\n\n    if (keyToEvict) {\n      this.delete(keyToEvict);\n    }\n  }\n\n  /**\n   * Find least recently used entry\n   */\n  private findLRU(): string | null {\n    let oldest: string | null = null;\n    let oldestTime = Infinity;\n\n    for (const [key, entry] of this.cache) {\n      if (entry.accessedAt < oldestTime) {\n        oldestTime = entry.accessedAt;\n        oldest = key;\n      }\n    }\n\n    return oldest;\n  }\n\n  /**\n   * Find least frequently used entry\n   */\n  private findLFU(): string | null {\n    let lfu: string | null = null;\n    let minCount = Infinity;\n\n    for (const [key, entry] of this.cache) {\n      if (entry.accessCount < minCount) {\n        minCount = entry.accessCount;\n        lfu = key;\n      }\n    }\n\n    return lfu;\n  }\n\n  /**\n   * Find oldest entry (FIFO)\n   */\n  private findOldest(): string | null {\n    let oldest: string | null = null;\n    let oldestTime = Infinity;\n\n    for (const [key, entry] of this.cache) {\n      if (entry.createdAt < oldestTime) {\n        oldestTime = entry.createdAt;\n        oldest = key;\n      }\n    }\n\n    return oldest;\n  }\n\n  /**\n   * Find expired entry\n   */\n  private findExpired(): string | null {\n    const now = Date.now();\n\n    for (const [key, entry] of this.cache) {\n      if (entry.ttl && now - entry.createdAt > entry.ttl) {\n        return key;\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * Load cache from IndexedDB\n   */\n  private async loadFromStorage(): Promise<void> {\n    if (typeof indexedDB === 'undefined') return;\n\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction('cache', 'readonly');\n      const store = tx.objectStore('cache');\n      const request = store.getAll();\n\n      return new Promise((resolve, reject) => {\n        request.onsuccess = () => {\n          const entries = request.result as Array<{ key: string; entry: CacheEntry<T> }>;\n          for (const { key, entry } of entries) {\n            this.cache.set(key, entry);\n            this.currentSize += entry.size;\n          }\n          resolve();\n        };\n        request.onerror = () => reject(request.error);\n      });\n    } catch {\n      // Ignore storage errors\n    }\n  }\n\n  /**\n   * Save cache to IndexedDB\n   */\n  private async saveToStorage(): Promise<void> {\n    if (typeof indexedDB === 'undefined') return;\n\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction('cache', 'readwrite');\n      const store = tx.objectStore('cache');\n\n      // Clear existing entries\n      store.clear();\n\n      // Add current entries\n      for (const [key, entry] of this.cache) {\n        store.put({ key, entry });\n      }\n\n      return new Promise((resolve, reject) => {\n        tx.oncomplete = () => resolve();\n        tx.onerror = () => reject(tx.error);\n      });\n    } catch {\n      // Ignore storage errors\n    }\n  }\n\n  /**\n   * Clear IndexedDB storage\n   */\n  private async clearStorage(): Promise<void> {\n    if (typeof indexedDB === 'undefined') return;\n\n    try {\n      const db = await this.openDB();\n      const tx = db.transaction('cache', 'readwrite');\n      const store = tx.objectStore('cache');\n      store.clear();\n    } catch {\n      // Ignore storage errors\n    }\n  }\n\n  /**\n   * Open IndexedDB database\n   */\n  private openDB(): Promise<IDBDatabase> {\n    return new Promise((resolve, reject) => {\n      const request = indexedDB.open(this.options.name, 1);\n\n      request.onupgradeneeded = () => {\n        const db = request.result;\n        if (!db.objectStoreNames.contains('cache')) {\n          db.createObjectStore('cache', { keyPath: 'key' });\n        }\n      };\n\n      request.onsuccess = () => resolve(request.result);\n      request.onerror = () => reject(request.error);\n    });\n  }\n}\n\n// ============================================================================\n// Inference Result Cache\n// ============================================================================\n\n/**\n * InferenceCache - Cache for inference results\n */\nexport class InferenceCache extends Cache<Float32Array> {\n  /**\n   * Generate cache key from input\n   */\n  generateKey(modelId: string, input: Float32Array | number[]): string {\n    // Create hash from input data\n    const inputArray = Array.isArray(input) ? input : Array.from(input);\n    const hash = this.hashArray(inputArray);\n    return `${modelId}:${hash}`;\n  }\n\n  /**\n   * Simple hash function for arrays\n   */\n  private hashArray(arr: number[]): string {\n    let hash = 0;\n    const sample = arr.length > 100 \n      ? arr.filter((_, i) => i % Math.floor(arr.length / 100) === 0)\n      : arr;\n    \n    for (let i = 0; i < sample.length; i++) {\n      const value = sample[i] ?? 0;\n      hash = ((hash << 5) - hash) + (value * 1000 | 0);\n      hash |= 0;\n    }\n    \n    return hash.toString(36);\n  }\n}\n\n// ============================================================================\n// Model Cache\n// ============================================================================\n\n/**\n * Model download cache using Cache API\n */\nexport class ModelDownloadCache {\n  private readonly cacheName: string;\n  private cache: globalThis.Cache | null = null;\n\n  constructor(cacheName: string = 'edgeflow-models') {\n    this.cacheName = cacheName;\n  }\n\n  /**\n   * Initialize cache\n   */\n  private async ensureCache(): Promise<globalThis.Cache> {\n    if (!this.cache) {\n      if (typeof caches === 'undefined') {\n        throw new Error('Cache API is not available');\n      }\n      this.cache = await caches.open(this.cacheName);\n    }\n    return this.cache;\n  }\n\n  /**\n   * Get cached response\n   */\n  async get(url: string): Promise<Response | undefined> {\n    try {\n      const cache = await this.ensureCache();\n      return await cache.match(url) ?? undefined;\n    } catch {\n      return undefined;\n    }\n  }\n\n  /**\n   * Store response in cache\n   */\n  async put(url: string, response: Response): Promise<void> {\n    try {\n      const cache = await this.ensureCache();\n      await cache.put(url, response.clone());\n    } catch {\n      // Ignore cache errors\n    }\n  }\n\n  /**\n   * Delete cached response\n   */\n  async delete(url: string): Promise<boolean> {\n    try {\n      const cache = await this.ensureCache();\n      return await cache.delete(url);\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Clear all cached models\n   */\n  async clear(): Promise<void> {\n    try {\n      await caches.delete(this.cacheName);\n      this.cache = null;\n    } catch {\n      // Ignore cache errors\n    }\n  }\n\n  /**\n   * Get all cached URLs\n   */\n  async keys(): Promise<string[]> {\n    try {\n      const cache = await this.ensureCache();\n      const requests = await cache.keys();\n      return requests.map(r => r.url);\n    } catch {\n      return [];\n    }\n  }\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create a cache with common presets\n */\nexport function createCache<T>(\n  preset: 'small' | 'medium' | 'large' | 'custom' = 'medium',\n  options: CacheOptions = {}\n): Cache<T> {\n  const presets: Record<string, CacheOptions> = {\n    small: {\n      maxSize: 10 * 1024 * 1024, // 10MB\n      maxEntries: 100,\n    },\n    medium: {\n      maxSize: 100 * 1024 * 1024, // 100MB\n      maxEntries: 500,\n    },\n    large: {\n      maxSize: 500 * 1024 * 1024, // 500MB\n      maxEntries: 2000,\n    },\n    custom: {},\n  };\n\n  return new Cache<T>({ ...presets[preset], ...options });\n}\n"
  },
  {
    "path": "src/utils/hub.ts",
    "content": "/**\n * edgeFlow.js - Hugging Face Hub Integration\n * \n * Automatically download models, tokenizers, and configs from Hugging Face Hub.\n */\n\nimport { loadModelData, isModelCached, type DownloadProgress } from './model-loader.js';\nimport { Tokenizer } from './tokenizer.js';\nimport { EdgeFlowError, ErrorCodes } from '../core/types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Hub options\n */\nexport interface HubOptions {\n  /** HuggingFace API endpoint (default: https://huggingface.co) */\n  endpoint?: string;\n  /** Model revision/branch (default: main) */\n  revision?: string;\n  /** Subfolder within the repo */\n  subfolder?: string;\n  /** Enable caching */\n  cache?: boolean;\n  /** Force re-download */\n  forceDownload?: boolean;\n  /** Progress callback */\n  onProgress?: (progress: HubDownloadProgress) => void;\n  /** HuggingFace API token (for private repos) */\n  token?: string;\n}\n\n/**\n * Download progress for hub\n */\nexport interface HubDownloadProgress {\n  /** Current file being downloaded */\n  file: string;\n  /** File index (1-based) */\n  fileIndex: number;\n  /** Total files */\n  totalFiles: number;\n  /** File download progress */\n  fileProgress: DownloadProgress;\n  /** Overall progress (0-100) */\n  overallProgress: number;\n}\n\n/**\n * Model info from config.json\n */\nexport interface ModelConfig {\n  model_type?: string;\n  architectures?: string[];\n  hidden_size?: number;\n  num_attention_heads?: number;\n  num_hidden_layers?: number;\n  vocab_size?: number;\n  max_position_embeddings?: number;\n  type_vocab_size?: number;\n  id2label?: Record<string, string>;\n  label2id?: Record<string, number>;\n  [key: string]: unknown;\n}\n\n/**\n * Downloaded model bundle\n */\nexport interface ModelBundle {\n  /** Model ID */\n  modelId: string;\n  /** Model data (ArrayBuffer) */\n  modelData: ArrayBuffer;\n  /** Tokenizer instance */\n  tokenizer?: Tokenizer;\n  /** Model config */\n  config?: ModelConfig;\n  /** Model files info */\n  files: {\n    model?: string;\n    tokenizer?: string;\n    config?: string;\n  };\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_ENDPOINT = 'https://huggingface.co';\nconst DEFAULT_REVISION = 'main';\n\n/**\n * Common ONNX model file patterns (in order of preference)\n */\nconst ONNX_MODEL_FILES = [\n  'model.onnx',\n  'model_quantized.onnx',\n  'model_int8.onnx',\n  'model_uint8.onnx',\n  'model_fp16.onnx',\n  'onnx/model.onnx',\n  'onnx/model_quantized.onnx',\n];\n\n// ============================================================================\n// Hub API\n// ============================================================================\n\n/**\n * Build URL for a file in a HuggingFace repo\n */\nfunction buildFileUrl(\n  modelId: string,\n  filename: string,\n  options: HubOptions = {}\n): string {\n  const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;\n  const revision = options.revision ?? DEFAULT_REVISION;\n  const subfolder = options.subfolder ? `${options.subfolder}/` : '';\n  \n  return `${endpoint}/${modelId}/resolve/${revision}/${subfolder}${filename}`;\n}\n\n/**\n * Fetch with optional auth token\n */\nasync function fetchWithAuth(url: string, token?: string): Promise<Response> {\n  const headers: HeadersInit = {};\n  if (token) {\n    headers['Authorization'] = `Bearer ${token}`;\n  }\n  \n  const response = await fetch(url, { headers });\n  return response;\n}\n\n/**\n * Check if a file exists in a repo\n */\nasync function fileExists(\n  modelId: string,\n  filename: string,\n  options: HubOptions = {}\n): Promise<boolean> {\n  const url = buildFileUrl(modelId, filename, options);\n  \n  try {\n    const response = await fetchWithAuth(url, options.token);\n    // HuggingFace returns 302 redirect for existing files\n    return response.ok || response.status === 302;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Find the best ONNX model file in a repo\n */\nasync function findOnnxModel(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<string | null> {\n  // Try common file patterns\n  for (const filename of ONNX_MODEL_FILES) {\n    if (await fileExists(modelId, filename, options)) {\n      return filename;\n    }\n  }\n  \n  return null;\n}\n\n/**\n * Download a file from HuggingFace Hub\n */\nexport async function downloadFile(\n  modelId: string,\n  filename: string,\n  options: HubOptions = {}\n): Promise<ArrayBuffer> {\n  const url = buildFileUrl(modelId, filename, options);\n  \n  // Use model loader for caching and resume support\n  return loadModelData(url, {\n    cache: options.cache ?? true,\n    forceDownload: options.forceDownload ?? false,\n    onProgress: options.onProgress ? (progress) => {\n      options.onProgress!({\n        file: filename,\n        fileIndex: 1,\n        totalFiles: 1,\n        fileProgress: progress,\n        overallProgress: progress.percent,\n      });\n    } : undefined,\n  });\n}\n\n/**\n * Download JSON file from HuggingFace Hub\n */\nexport async function downloadJson<T = unknown>(\n  modelId: string,\n  filename: string,\n  options: HubOptions = {}\n): Promise<T> {\n  const url = buildFileUrl(modelId, filename, options);\n  \n  // Check cache first\n  if (options.cache !== false && !options.forceDownload) {\n    const cached = await isModelCached(url);\n    if (cached) {\n      const data = await loadModelData(url, { cache: true });\n      const text = new TextDecoder().decode(data);\n      return JSON.parse(text) as T;\n    }\n  }\n  \n  // Fetch directly for small JSON files\n  const response = await fetchWithAuth(url, options.token);\n  \n  if (!response.ok) {\n    throw new EdgeFlowError(\n      `Failed to download ${filename} from ${modelId}: ${response.status}`,\n      ErrorCodes.MODEL_NOT_FOUND\n    );\n  }\n  \n  return response.json() as Promise<T>;\n}\n\n/**\n * Download tokenizer from HuggingFace Hub\n */\nexport async function downloadTokenizer(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<Tokenizer> {\n  const url = buildFileUrl(modelId, 'tokenizer.json', options);\n  return Tokenizer.fromUrl(url);\n}\n\n/**\n * Download model config from HuggingFace Hub\n */\nexport async function downloadConfig(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<ModelConfig> {\n  return downloadJson<ModelConfig>(modelId, 'config.json', options);\n}\n\n/**\n * Download complete model bundle (model + tokenizer + config)\n */\nexport async function downloadModel(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<ModelBundle> {\n  const files: ModelBundle['files'] = {};\n  const totalSteps = 3; // model, tokenizer, config\n  let currentStep = 0;\n  \n  const reportProgress = (\n    file: string,\n    progress: DownloadProgress\n  ) => {\n    if (options.onProgress) {\n      const baseProgress = (currentStep / totalSteps) * 100;\n      const stepProgress = (progress.percent / totalSteps);\n      \n      options.onProgress({\n        file,\n        fileIndex: currentStep + 1,\n        totalFiles: totalSteps,\n        fileProgress: progress,\n        overallProgress: baseProgress + stepProgress,\n      });\n    }\n  };\n  \n  // 1. Find and download ONNX model\n  console.log(`🔍 Finding ONNX model in ${modelId}...`);\n  const modelFile = await findOnnxModel(modelId, options);\n  \n  if (!modelFile) {\n    throw new EdgeFlowError(\n      `No ONNX model found in ${modelId}. Please ensure the model has an ONNX file.`,\n      ErrorCodes.MODEL_NOT_FOUND,\n      { modelId, triedFiles: ONNX_MODEL_FILES }\n    );\n  }\n  \n  files.model = modelFile;\n  console.log(`📦 Downloading model: ${modelFile}`);\n  \n  const modelData = await downloadFile(modelId, modelFile, {\n    ...options,\n    onProgress: (p) => reportProgress(modelFile, p.fileProgress),\n  });\n  \n  currentStep = 1;\n  \n  // 2. Download tokenizer (optional)\n  let tokenizer: Tokenizer | undefined;\n  try {\n    console.log(`📝 Downloading tokenizer...`);\n    files.tokenizer = 'tokenizer.json';\n    tokenizer = await downloadTokenizer(modelId, options);\n    console.log(`✓ Tokenizer loaded`);\n  } catch (error) {\n    console.warn(`⚠️ No tokenizer found for ${modelId}`);\n  }\n  \n  currentStep = 2;\n  \n  // 3. Download config (optional)\n  let config: ModelConfig | undefined;\n  try {\n    console.log(`⚙️ Downloading config...`);\n    files.config = 'config.json';\n    config = await downloadConfig(modelId, options);\n    console.log(`✓ Config loaded`);\n  } catch (error) {\n    console.warn(`⚠️ No config found for ${modelId}`);\n  }\n  \n  currentStep = 3;\n  \n  if (options.onProgress) {\n    options.onProgress({\n      file: 'complete',\n      fileIndex: totalSteps,\n      totalFiles: totalSteps,\n      fileProgress: { loaded: 1, total: 1, percent: 100, speed: 0, eta: 0 },\n      overallProgress: 100,\n    });\n  }\n  \n  console.log(`✅ Model bundle downloaded: ${modelId}`);\n  \n  return {\n    modelId,\n    modelData,\n    tokenizer,\n    config,\n    files,\n  };\n}\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\n/**\n * Load a model from HuggingFace Hub\n * \n * @example\n * ```typescript\n * // Load a sentiment analysis model\n * const bundle = await fromHub('Xenova/distilbert-base-uncased-finetuned-sst-2-english');\n * \n * // Use with edgeFlow\n * const model = await loadModelFromBuffer(bundle.modelData);\n * const tokens = bundle.tokenizer.encode('I love this!');\n * ```\n */\nexport async function fromHub(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<ModelBundle> {\n  return downloadModel(modelId, options);\n}\n\n/**\n * Check if a model exists on HuggingFace Hub\n */\nexport async function modelExists(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<boolean> {\n  try {\n    // Try to find an ONNX model\n    const modelFile = await findOnnxModel(modelId, options);\n    return modelFile !== null;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Get model info from HuggingFace Hub\n */\nexport async function getModelInfo(\n  modelId: string,\n  options: HubOptions = {}\n): Promise<{\n  hasOnnx: boolean;\n  onnxFile?: string;\n  hasTokenizer: boolean;\n  hasConfig: boolean;\n  config?: ModelConfig;\n}> {\n  const [onnxFile, hasTokenizer, config] = await Promise.all([\n    findOnnxModel(modelId, options),\n    fileExists(modelId, 'tokenizer.json', options),\n    downloadConfig(modelId, options).catch(() => undefined),\n  ]);\n  \n  return {\n    hasOnnx: onnxFile !== null,\n    onnxFile: onnxFile ?? undefined,\n    hasTokenizer,\n    hasConfig: config !== undefined,\n    config,\n  };\n}\n\n// ============================================================================\n// Popular Models Registry\n// ============================================================================\n\n/**\n * Pre-configured popular models\n */\nexport const POPULAR_MODELS = {\n  // Text Classification / Sentiment\n  'sentiment-analysis': 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n  'text-classification': 'Xenova/distilbert-base-uncased-finetuned-sst-2-english',\n  \n  // Feature Extraction\n  'feature-extraction': 'Xenova/all-MiniLM-L6-v2',\n  'sentence-similarity': 'Xenova/all-MiniLM-L6-v2',\n  \n  // Question Answering\n  'question-answering': 'Xenova/distilbert-base-cased-distilled-squad',\n  \n  // Token Classification\n  'ner': 'Xenova/bert-base-NER',\n  'token-classification': 'Xenova/bert-base-NER',\n  \n  // Text Generation\n  'text-generation': 'Xenova/gpt2',\n  \n  // Translation\n  'translation-en-fr': 'Xenova/t5-small',\n  'translation-en-de': 'Xenova/t5-small',\n  \n  // Summarization\n  'summarization': 'Xenova/distilbart-cnn-6-6',\n  \n  // Fill Mask\n  'fill-mask': 'Xenova/bert-base-uncased',\n  \n  // Image Classification\n  'image-classification': 'Xenova/vit-base-patch16-224',\n  \n  // Object Detection\n  'object-detection': 'Xenova/detr-resnet-50',\n  \n  // Image Segmentation\n  'image-segmentation': 'Xenova/segformer-b0-finetuned-ade-512-512',\n  \n  // Zero-shot Classification\n  'zero-shot-classification': 'Xenova/mobilebert-uncased-mnli',\n  \n  // Speech Recognition\n  'automatic-speech-recognition': 'Xenova/whisper-tiny.en',\n  \n  // Text-to-Speech\n  'text-to-speech': 'Xenova/speecht5_tts',\n} as const;\n\nexport type PopularModelTask = keyof typeof POPULAR_MODELS;\n\n/**\n * Get the default model ID for a task\n */\nexport function getDefaultModel(task: PopularModelTask): string {\n  return POPULAR_MODELS[task];\n}\n\n/**\n * Load a model by task name\n * \n * @example\n * ```typescript\n * const bundle = await fromTask('sentiment-analysis');\n * ```\n */\nexport async function fromTask(\n  task: PopularModelTask,\n  options: HubOptions = {}\n): Promise<ModelBundle> {\n  const modelId = getDefaultModel(task);\n  return downloadModel(modelId, options);\n}\n"
  },
  {
    "path": "src/utils/index.ts",
    "content": "/**\n * edgeFlow.js - Utilities Exports\n */\n\n// Tokenizer\nexport {\n  Tokenizer,\n  createBasicTokenizer,\n  loadTokenizer,\n  loadTokenizerFromHub,\n  type TokenizerModel,\n  type TokenizerOptions,\n} from './tokenizer.js';\n\n// Preprocessor\nexport {\n  ImagePreprocessor,\n  AudioPreprocessor,\n  preprocessText,\n  createImagePreprocessor,\n  createAudioPreprocessor,\n  type ImagePreprocessorOptions,\n  type AudioPreprocessorOptions,\n  type TextPreprocessorOptions,\n} from './preprocessor.js';\n\n// Cache\nexport {\n  Cache,\n  InferenceCache,\n  ModelDownloadCache,\n  createCache,\n  type CacheStrategy,\n  type CacheOptions,\n  type CacheStats,\n} from './cache.js';\n\n// Model Loader (Preloading, Sharding, Resume, Caching)\nexport {\n  loadModelData,\n  preloadModel,\n  preloadModels,\n  isModelCached,\n  getCachedModel,\n  deleteCachedModel,\n  clearModelCache,\n  getModelCacheStats,\n  getPreloadStatus,\n  cancelPreload,\n  getPreloadedModel,\n  type DownloadProgress,\n  type ModelLoaderOptions,\n  type PreloadOptions,\n} from './model-loader.js';\n\n// HuggingFace Hub Integration\nexport {\n  fromHub,\n  fromTask,\n  downloadModel,\n  downloadFile,\n  downloadTokenizer,\n  downloadConfig,\n  modelExists,\n  getModelInfo,\n  getDefaultModel,\n  POPULAR_MODELS,\n  type HubOptions,\n  type HubDownloadProgress,\n  type ModelConfig,\n  type ModelBundle,\n  type PopularModelTask,\n} from './hub.js';\n\n// Offline/PWA Support\nexport {\n  OfflineManager,\n  getOfflineManager,\n  initOffline,\n  isOffline,\n  isPWASupported,\n  generateServiceWorker,\n  generateManifest,\n  type OfflineConfig,\n  type OfflineStatus,\n  type CachedModelInfo,\n} from './offline.js';\n"
  },
  {
    "path": "src/utils/model-loader.ts",
    "content": "/**\n * edgeFlow.js - Advanced Model Loader\n * \n * Features:\n * - Preloading: Background model loading\n * - Sharding: Split large files into chunks for download\n * - Resume Download: Continue download from where it left off\n * - Model Caching: IndexedDB storage for large models\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Download progress information\n */\nexport interface DownloadProgress {\n  /** Downloaded bytes */\n  loaded: number;\n  /** Total bytes (0 if unknown) */\n  total: number;\n  /** Progress percentage (0-100) */\n  percent: number;\n  /** Download speed in bytes/sec */\n  speed: number;\n  /** Estimated time remaining in ms */\n  eta: number;\n  /** Current chunk index (for sharded downloads) */\n  currentChunk?: number;\n  /** Total chunks (for sharded downloads) */\n  totalChunks?: number;\n}\n\n/**\n * Model loader options\n */\nexport interface ModelLoaderOptions {\n  /** Enable caching (default: true) */\n  cache?: boolean;\n  /** Cache name for IndexedDB (default: 'edgeflow-models') */\n  cacheName?: string;\n  /** Enable resume download (default: true) */\n  resumable?: boolean;\n  /** Chunk size for sharded downloads in bytes (default: 5MB) */\n  chunkSize?: number;\n  /** Progress callback */\n  onProgress?: (progress: DownloadProgress) => void;\n  /** Number of parallel download connections (default: 4) */\n  parallelConnections?: number;\n  /** Request timeout in ms (default: 30000) */\n  timeout?: number;\n  /** Force re-download even if cached */\n  forceDownload?: boolean;\n}\n\n/**\n * Preload options\n */\nexport interface PreloadOptions extends ModelLoaderOptions {\n  /** Priority (higher = more important, default: 0) */\n  priority?: number;\n}\n\n/**\n * Cached model metadata\n */\ninterface CachedModelMeta {\n  url: string;\n  size: number;\n  etag?: string;\n  lastModified?: string;\n  cachedAt: number;\n  chunks?: number;\n  complete: boolean;\n}\n\n/**\n * Download state for resume support\n */\ninterface DownloadState {\n  url: string;\n  totalSize: number;\n  downloadedSize: number;\n  chunks: ChunkState[];\n  startedAt: number;\n}\n\n/**\n * Chunk state\n */\ninterface ChunkState {\n  index: number;\n  start: number;\n  end: number;\n  downloaded: boolean;\n}\n\n// ============================================================================\n// IndexedDB Model Cache\n// ============================================================================\n\nconst DB_NAME = 'edgeflow-model-cache';\nconst DB_VERSION = 1;\nconst STORE_META = 'meta';\nconst STORE_CHUNKS = 'chunks';\nconst STORE_STATE = 'download-state';\n\n/**\n * IndexedDB-based model cache for large files\n */\nclass ModelCache {\n  private db: IDBDatabase | null = null;\n  private dbPromise: Promise<IDBDatabase> | null = null;\n\n  /**\n   * Open the database\n   */\n  private async openDB(): Promise<IDBDatabase> {\n    if (this.db) return this.db;\n    if (this.dbPromise) return this.dbPromise;\n\n    this.dbPromise = new Promise((resolve, reject) => {\n      const request = indexedDB.open(DB_NAME, DB_VERSION);\n      \n      request.onupgradeneeded = (event) => {\n        const db = (event.target as IDBOpenDBRequest).result;\n        \n        // Model metadata store\n        if (!db.objectStoreNames.contains(STORE_META)) {\n          db.createObjectStore(STORE_META, { keyPath: 'url' });\n        }\n        \n        // Chunk data store\n        if (!db.objectStoreNames.contains(STORE_CHUNKS)) {\n          const chunkStore = db.createObjectStore(STORE_CHUNKS, { keyPath: ['url', 'index'] });\n          chunkStore.createIndex('url', 'url', { unique: false });\n        }\n        \n        // Download state store (for resume)\n        if (!db.objectStoreNames.contains(STORE_STATE)) {\n          db.createObjectStore(STORE_STATE, { keyPath: 'url' });\n        }\n      };\n\n      request.onsuccess = () => {\n        this.db = request.result;\n        resolve(this.db);\n      };\n\n      request.onerror = () => reject(request.error);\n    });\n\n    return this.dbPromise;\n  }\n\n  /**\n   * Get cached model metadata\n   */\n  async getMeta(url: string): Promise<CachedModelMeta | null> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_META, 'readonly');\n      const store = tx.objectStore(STORE_META);\n      const request = store.get(url);\n      request.onsuccess = () => resolve(request.result ?? null);\n      request.onerror = () => reject(request.error);\n    });\n  }\n\n  /**\n   * Save model metadata (with quota error handling)\n   */\n  async saveMeta(meta: CachedModelMeta): Promise<void> {\n    try {\n      await this.putInStore(STORE_META, meta);\n    } catch (err) {\n      if (this.isQuotaError(err)) {\n        await this.evictOldest(meta.size);\n        try {\n          await this.putInStore(STORE_META, meta);\n        } catch {\n          console.warn('[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache.');\n        }\n      } else {\n        throw err;\n      }\n    }\n  }\n\n  /**\n   * Save a chunk (with quota error handling)\n   */\n  async saveChunk(url: string, index: number, data: ArrayBuffer): Promise<void> {\n    try {\n      await this.putInStore(STORE_CHUNKS, { url, index, data });\n    } catch (err) {\n      if (this.isQuotaError(err)) {\n        await this.evictOldest(data.byteLength);\n        try {\n          await this.putInStore(STORE_CHUNKS, { url, index, data });\n        } catch {\n          console.warn('[edgeFlow.js] IndexedDB quota exceeded even after eviction; skipping cache for chunk.');\n        }\n      } else {\n        throw err;\n      }\n    }\n  }\n\n  /**\n   * Generic put helper\n   */\n  private async putInStore(storeName: string, value: unknown): Promise<void> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(storeName, 'readwrite');\n      const store = tx.objectStore(storeName);\n      store.put(value);\n      tx.oncomplete = () => resolve();\n      tx.onerror = () => reject(tx.error);\n    });\n  }\n\n  /**\n   * Detect IndexedDB quota exceeded errors\n   */\n  private isQuotaError(err: unknown): boolean {\n    if (err instanceof DOMException) {\n      return err.name === 'QuotaExceededError' || err.code === 22;\n    }\n    return false;\n  }\n\n  /**\n   * Evict oldest cached models to free space.\n   * Deletes models by ascending `cachedAt` until at least `bytesNeeded` is freed.\n   */\n  async evictOldest(bytesNeeded: number): Promise<void> {\n    const db = await this.openDB();\n    const allMeta: CachedModelMeta[] = await new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_META, 'readonly');\n      const store = tx.objectStore(STORE_META);\n      const request = store.getAll();\n      request.onsuccess = () => resolve(request.result ?? []);\n      request.onerror = () => reject(request.error);\n    });\n\n    allMeta.sort((a, b) => a.cachedAt - b.cachedAt);\n\n    let freed = 0;\n    for (const meta of allMeta) {\n      if (freed >= bytesNeeded) break;\n      await this.deleteModel(meta.url);\n      freed += meta.size;\n    }\n  }\n\n  /**\n   * Get all chunks for a URL\n   */\n  async getChunks(url: string): Promise<ArrayBuffer[]> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_CHUNKS, 'readonly');\n      const store = tx.objectStore(STORE_CHUNKS);\n      const index = store.index('url');\n      const request = index.getAll(url);\n      \n      request.onsuccess = () => {\n        const results = request.result as Array<{ url: string; index: number; data: ArrayBuffer }>;\n        // Sort by index and extract data\n        results.sort((a, b) => a.index - b.index);\n        resolve(results.map(r => r.data));\n      };\n      request.onerror = () => reject(request.error);\n    });\n  }\n\n  /**\n   * Get complete model data (merged chunks)\n   */\n  async getModel(url: string): Promise<ArrayBuffer | null> {\n    const meta = await this.getMeta(url);\n    if (!meta || !meta.complete) return null;\n\n    const chunks = await this.getChunks(url);\n    if (chunks.length === 0) return null;\n\n    // Merge chunks\n    const totalSize = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n    const result = new Uint8Array(totalSize);\n    let offset = 0;\n    \n    for (const chunk of chunks) {\n      result.set(new Uint8Array(chunk), offset);\n      offset += chunk.byteLength;\n    }\n\n    return result.buffer;\n  }\n\n  /**\n   * Save download state (for resume, with quota handling)\n   */\n  async saveDownloadState(state: DownloadState): Promise<void> {\n    try {\n      await this.putInStore(STORE_STATE, state);\n    } catch (err) {\n      if (this.isQuotaError(err)) {\n        console.warn('[edgeFlow.js] IndexedDB quota exceeded saving download state; resume may not work.');\n      } else {\n        throw err;\n      }\n    }\n  }\n\n  /**\n   * Get download state\n   */\n  async getDownloadState(url: string): Promise<DownloadState | null> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_STATE, 'readonly');\n      const store = tx.objectStore(STORE_STATE);\n      const request = store.get(url);\n      request.onsuccess = () => resolve(request.result ?? null);\n      request.onerror = () => reject(request.error);\n    });\n  }\n\n  /**\n   * Delete download state\n   */\n  async deleteDownloadState(url: string): Promise<void> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_STATE, 'readwrite');\n      const store = tx.objectStore(STORE_STATE);\n      store.delete(url);\n      tx.oncomplete = () => resolve();\n      tx.onerror = () => reject(tx.error);\n    });\n  }\n\n  /**\n   * Delete cached model\n   */\n  async deleteModel(url: string): Promise<void> {\n    const db = await this.openDB();\n    \n    // Delete metadata\n    await new Promise<void>((resolve, reject) => {\n      const tx = db.transaction(STORE_META, 'readwrite');\n      const store = tx.objectStore(STORE_META);\n      store.delete(url);\n      tx.oncomplete = () => resolve();\n      tx.onerror = () => reject(tx.error);\n    });\n\n    // Delete chunks\n    const chunks = await this.getChunks(url);\n    if (chunks.length > 0) {\n      await new Promise<void>((resolve, reject) => {\n        const tx = db.transaction(STORE_CHUNKS, 'readwrite');\n        const store = tx.objectStore(STORE_CHUNKS);\n        const index = store.index('url');\n        const request = index.openCursor(IDBKeyRange.only(url));\n        \n        request.onsuccess = (event) => {\n          const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n          if (cursor) {\n            cursor.delete();\n            cursor.continue();\n          }\n        };\n        \n        tx.oncomplete = () => resolve();\n        tx.onerror = () => reject(tx.error);\n      });\n    }\n\n    // Delete download state\n    await this.deleteDownloadState(url);\n  }\n\n  /**\n   * Clear all cached models\n   */\n  async clear(): Promise<void> {\n    const db = await this.openDB();\n    \n    const stores = [STORE_META, STORE_CHUNKS, STORE_STATE];\n    for (const storeName of stores) {\n      await new Promise<void>((resolve, reject) => {\n        const tx = db.transaction(storeName, 'readwrite');\n        const store = tx.objectStore(storeName);\n        store.clear();\n        tx.oncomplete = () => resolve();\n        tx.onerror = () => reject(tx.error);\n      });\n    }\n  }\n\n  /**\n   * Get cache statistics\n   */\n  async getStats(): Promise<{ models: number; totalSize: number }> {\n    const db = await this.openDB();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction(STORE_META, 'readonly');\n      const store = tx.objectStore(STORE_META);\n      const request = store.getAll();\n      \n      request.onsuccess = () => {\n        const metas = request.result as CachedModelMeta[];\n        resolve({\n          models: metas.filter(m => m.complete).length,\n          totalSize: metas.reduce((sum, m) => sum + (m.complete ? m.size : 0), 0),\n        });\n      };\n      request.onerror = () => reject(request.error);\n    });\n  }\n}\n\n// Global cache instance\nconst modelCache = new ModelCache();\n\n// ============================================================================\n// Advanced Model Loader\n// ============================================================================\n\n/**\n * Check if server supports Range requests\n */\nasync function supportsRangeRequests(url: string): Promise<{ supports: boolean; size: number; etag?: string }> {\n  try {\n    const response = await fetch(url, { method: 'HEAD' });\n    const acceptRanges = response.headers.get('Accept-Ranges');\n    const contentLength = response.headers.get('Content-Length');\n    const etag = response.headers.get('ETag') ?? undefined;\n    \n    return {\n      supports: acceptRanges === 'bytes',\n      size: contentLength ? parseInt(contentLength, 10) : 0,\n      etag,\n    };\n  } catch {\n    return { supports: false, size: 0 };\n  }\n}\n\n/**\n * Download a single chunk using Range request\n */\nasync function downloadChunk(\n  url: string,\n  start: number,\n  end: number,\n  timeout: number\n): Promise<ArrayBuffer> {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n  try {\n    const response = await fetch(url, {\n      headers: { Range: `bytes=${start}-${end}` },\n      signal: controller.signal,\n    });\n\n    if (response.status !== 206 && response.status !== 200) {\n      throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n    }\n\n    return await response.arrayBuffer();\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Download model with sharding and resume support\n */\nasync function downloadWithResume(\n  url: string,\n  options: ModelLoaderOptions\n): Promise<ArrayBuffer> {\n  const {\n    chunkSize = 5 * 1024 * 1024, // 5MB\n    parallelConnections = 4,\n    timeout = 30000,\n    onProgress,\n  } = options;\n\n  // Check server capabilities\n  const { supports: supportsRange, size: totalSize, etag } = await supportsRangeRequests(url);\n\n  // If no Range support or small file, download normally\n  if (!supportsRange || totalSize < chunkSize * 2) {\n    return downloadSimple(url, timeout, onProgress);\n  }\n\n  // Check for existing download state\n  let state = await modelCache.getDownloadState(url);\n  \n  // Initialize or reset state if needed\n  if (!state || (etag && state.totalSize !== totalSize)) {\n    const numChunks = Math.ceil(totalSize / chunkSize);\n    const chunks: ChunkState[] = [];\n    \n    for (let i = 0; i < numChunks; i++) {\n      const start = i * chunkSize;\n      const end = Math.min(start + chunkSize - 1, totalSize - 1);\n      chunks.push({ index: i, start, end, downloaded: false });\n    }\n    \n    state = {\n      url,\n      totalSize,\n      downloadedSize: 0,\n      chunks,\n      startedAt: Date.now(),\n    };\n    \n    // Clear any existing chunks\n    await modelCache.deleteModel(url);\n  }\n\n  // Download remaining chunks\n  const pendingChunks = state.chunks.filter(c => !c.downloaded);\n  let downloadedSize = state.downloadedSize;\n  const startTime = Date.now();\n  let lastProgressTime = startTime;\n  let lastDownloadedSize = downloadedSize;\n\n  // Progress tracking\n  const reportProgress = () => {\n    if (!onProgress) return;\n    \n    const now = Date.now();\n    const elapsed = (now - lastProgressTime) / 1000;\n    const bytesDownloaded = downloadedSize - lastDownloadedSize;\n    const speed = elapsed > 0 ? bytesDownloaded / elapsed : 0;\n    const remaining = totalSize - downloadedSize;\n    const eta = speed > 0 ? (remaining / speed) * 1000 : 0;\n\n    onProgress({\n      loaded: downloadedSize,\n      total: totalSize,\n      percent: (downloadedSize / totalSize) * 100,\n      speed,\n      eta,\n      currentChunk: state!.chunks.filter(c => c.downloaded).length,\n      totalChunks: state!.chunks.length,\n    });\n\n    lastProgressTime = now;\n    lastDownloadedSize = downloadedSize;\n  };\n\n  // Download chunks in parallel\n  const downloadQueue = [...pendingChunks];\n  const inProgress = new Map<number, Promise<void>>();\n\n  while (downloadQueue.length > 0 || inProgress.size > 0) {\n    // Start new downloads up to parallelConnections limit\n    while (downloadQueue.length > 0 && inProgress.size < parallelConnections) {\n      const chunk = downloadQueue.shift()!;\n      \n      const downloadPromise = (async () => {\n        try {\n          const data = await downloadChunk(url, chunk.start, chunk.end, timeout);\n          await modelCache.saveChunk(url, chunk.index, data);\n          \n          chunk.downloaded = true;\n          downloadedSize += data.byteLength;\n          \n          // Update state periodically\n          state!.downloadedSize = downloadedSize;\n          await modelCache.saveDownloadState(state!);\n          \n          reportProgress();\n        } finally {\n          inProgress.delete(chunk.index);\n        }\n      })();\n      \n      inProgress.set(chunk.index, downloadPromise);\n    }\n\n    // Wait for at least one to complete\n    if (inProgress.size > 0) {\n      await Promise.race(inProgress.values());\n    }\n  }\n\n  // All chunks downloaded, merge them\n  const chunks = await modelCache.getChunks(url);\n  const result = new Uint8Array(totalSize);\n  let offset = 0;\n  \n  for (const chunk of chunks) {\n    result.set(new Uint8Array(chunk), offset);\n    offset += chunk.byteLength;\n  }\n\n  // Save metadata and cleanup state\n  await modelCache.saveMeta({\n    url,\n    size: totalSize,\n    etag,\n    cachedAt: Date.now(),\n    chunks: chunks.length,\n    complete: true,\n  });\n  await modelCache.deleteDownloadState(url);\n\n  return result.buffer;\n}\n\n/**\n * Simple download without sharding\n */\nasync function downloadSimple(\n  url: string,\n  timeout: number,\n  onProgress?: (progress: DownloadProgress) => void\n): Promise<ArrayBuffer> {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n  try {\n    const response = await fetch(url, { signal: controller.signal });\n    \n    if (!response.ok) {\n      throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n    }\n\n    const contentLength = response.headers.get('Content-Length');\n    const total = contentLength ? parseInt(contentLength, 10) : 0;\n\n    if (!response.body || !onProgress || total === 0) {\n      return await response.arrayBuffer();\n    }\n\n    // Stream with progress\n    const reader = response.body.getReader();\n    const chunks: Uint8Array[] = [];\n    let loaded = 0;\n    const startTime = Date.now();\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n\n      chunks.push(value);\n      loaded += value.length;\n\n      const elapsed = (Date.now() - startTime) / 1000;\n      const speed = elapsed > 0 ? loaded / elapsed : 0;\n      const remaining = total - loaded;\n      const eta = speed > 0 ? (remaining / speed) * 1000 : 0;\n\n      onProgress({\n        loaded,\n        total,\n        percent: (loaded / total) * 100,\n        speed,\n        eta,\n      });\n    }\n\n    // Merge chunks\n    const result = new Uint8Array(loaded);\n    let offset = 0;\n    for (const chunk of chunks) {\n      result.set(chunk, offset);\n      offset += chunk.length;\n    }\n\n    return result.buffer;\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\n// ============================================================================\n// Preload Manager\n// ============================================================================\n\ninterface PreloadTask {\n  url: string;\n  priority: number;\n  options: ModelLoaderOptions;\n  promise: Promise<ArrayBuffer>;\n  resolve: (data: ArrayBuffer) => void;\n  reject: (error: Error) => void;\n  status: 'pending' | 'loading' | 'complete' | 'error';\n}\n\n/**\n * Preload manager for background model loading\n */\nclass PreloadManager {\n  private tasks: Map<string, PreloadTask> = new Map();\n  private queue: string[] = [];\n  private maxConcurrent = 2;\n  private activeCount = 0;\n\n  /**\n   * Preload a model in the background\n   */\n  preload(url: string, options: PreloadOptions = {}): Promise<ArrayBuffer> {\n    // Check if already preloading\n    const existing = this.tasks.get(url);\n    if (existing) {\n      return existing.promise;\n    }\n\n    // Create task\n    let resolve!: (data: ArrayBuffer) => void;\n    let reject!: (error: Error) => void;\n    \n    const promise = new Promise<ArrayBuffer>((res, rej) => {\n      resolve = res;\n      reject = rej;\n    });\n\n    const task: PreloadTask = {\n      url,\n      priority: options.priority ?? 0,\n      options,\n      promise,\n      resolve,\n      reject,\n      status: 'pending',\n    };\n\n    this.tasks.set(url, task);\n    \n    // Insert into queue based on priority\n    const insertIndex = this.queue.findIndex(u => {\n      const t = this.tasks.get(u);\n      return t && t.priority < task.priority;\n    });\n    \n    if (insertIndex === -1) {\n      this.queue.push(url);\n    } else {\n      this.queue.splice(insertIndex, 0, url);\n    }\n\n    // Process queue\n    this.processQueue();\n\n    return promise;\n  }\n\n  /**\n   * Process the preload queue\n   */\n  private async processQueue(): Promise<void> {\n    while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {\n      const url = this.queue.shift();\n      if (!url) break;\n\n      const task = this.tasks.get(url);\n      if (!task || task.status !== 'pending') continue;\n\n      this.activeCount++;\n      task.status = 'loading';\n\n      this.downloadTask(task).finally(() => {\n        this.activeCount--;\n        this.processQueue();\n      });\n    }\n  }\n\n  /**\n   * Download a preload task\n   */\n  private async downloadTask(task: PreloadTask): Promise<void> {\n    try {\n      const data = await loadModelData(task.url, task.options);\n      task.status = 'complete';\n      task.resolve(data);\n    } catch (error) {\n      task.status = 'error';\n      task.reject(error instanceof Error ? error : new Error(String(error)));\n    }\n  }\n\n  /**\n   * Check if a model is preloaded\n   */\n  isPreloaded(url: string): boolean {\n    const task = this.tasks.get(url);\n    return task?.status === 'complete';\n  }\n\n  /**\n   * Get preload status\n   */\n  getStatus(url: string): 'pending' | 'loading' | 'complete' | 'error' | 'not_found' {\n    const task = this.tasks.get(url);\n    return task?.status ?? 'not_found';\n  }\n\n  /**\n   * Get preloaded model data\n   */\n  async get(url: string): Promise<ArrayBuffer | null> {\n    const task = this.tasks.get(url);\n    if (!task) return null;\n    \n    if (task.status === 'complete' || task.status === 'loading') {\n      return task.promise;\n    }\n    \n    return null;\n  }\n\n  /**\n   * Cancel preload\n   */\n  cancel(url: string): void {\n    const task = this.tasks.get(url);\n    if (task && task.status === 'pending') {\n      this.tasks.delete(url);\n      this.queue = this.queue.filter(u => u !== url);\n      task.reject(new Error('Preload cancelled'));\n    }\n  }\n\n  /**\n   * Clear all preloads\n   */\n  clear(): void {\n    for (const [, task] of this.tasks) {\n      if (task.status === 'pending') {\n        task.reject(new Error('Preload cleared'));\n      }\n    }\n    this.tasks.clear();\n    this.queue = [];\n  }\n}\n\n// Global preload manager\nconst preloadManager = new PreloadManager();\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Load model data with caching, sharding, and resume support\n */\nexport async function loadModelData(\n  url: string,\n  options: ModelLoaderOptions = {}\n): Promise<ArrayBuffer> {\n  const {\n    cache = true,\n    forceDownload = false,\n    resumable = true,\n  } = options;\n\n  // Check cache first\n  if (cache && !forceDownload) {\n    const cached = await modelCache.getModel(url);\n    if (cached) {\n      // Validate: reject cached content that is clearly an HTTP error page\n      // (HTML starts with '<', JSON error starts with '{').  Valid ONNX\n      // protobuf binaries always have high-bit or control bytes first.\n      const firstByte = new Uint8Array(cached)[0];\n      const isHtmlOrText = firstByte === 0x3c /* '<' */ || firstByte === 0x7b /* '{' */;\n      if (isHtmlOrText || cached.byteLength < 1024) {\n        console.warn(`[edgeFlow.js] Cached model for ${url} appears corrupt (${cached.byteLength} bytes, first byte 0x${firstByte?.toString(16)}). Evicting and re-downloading.`);\n        await modelCache.deleteModel(url);\n      } else {\n        console.log(`✓ Model loaded from cache: ${url}`);\n        options.onProgress?.({\n          loaded: cached.byteLength,\n          total: cached.byteLength,\n          percent: 100,\n          speed: 0,\n          eta: 0,\n        });\n        return cached;\n      }\n    }\n  }\n\n  // Download with resume support\n  let data: ArrayBuffer;\n  \n  if (resumable) {\n    data = await downloadWithResume(url, options);\n  } else {\n    data = await downloadSimple(url, options.timeout ?? 30000, options.onProgress);\n  }\n\n  // Cache the result\n  if (cache) {\n    // For simple downloads, save as single chunk\n    if (!resumable) {\n      await modelCache.saveChunk(url, 0, data);\n      await modelCache.saveMeta({\n        url,\n        size: data.byteLength,\n        cachedAt: Date.now(),\n        chunks: 1,\n        complete: true,\n      });\n    }\n  }\n\n  return data;\n}\n\n/**\n * Preload a model in the background\n */\nexport function preloadModel(url: string, options: PreloadOptions = {}): Promise<ArrayBuffer> {\n  return preloadManager.preload(url, options);\n}\n\n/**\n * Preload multiple models\n */\nexport function preloadModels(\n  urls: Array<{ url: string; priority?: number }>,\n  options: Omit<PreloadOptions, 'priority'> = {}\n): Promise<ArrayBuffer[]> {\n  return Promise.all(\n    urls.map(({ url, priority }) => preloadManager.preload(url, { ...options, priority }))\n  );\n}\n\n/**\n * Check if a model is cached\n */\nexport async function isModelCached(url: string): Promise<boolean> {\n  const meta = await modelCache.getMeta(url);\n  return meta?.complete ?? false;\n}\n\n/**\n * Get cached model data\n */\nexport async function getCachedModel(url: string): Promise<ArrayBuffer | null> {\n  return modelCache.getModel(url);\n}\n\n/**\n * Delete a cached model\n */\nexport async function deleteCachedModel(url: string): Promise<void> {\n  return modelCache.deleteModel(url);\n}\n\n/**\n * Clear all cached models\n */\nexport async function clearModelCache(): Promise<void> {\n  return modelCache.clear();\n}\n\n/**\n * Get model cache statistics\n */\nexport async function getModelCacheStats(): Promise<{ models: number; totalSize: number }> {\n  return modelCache.getStats();\n}\n\n/**\n * Get preload status\n */\nexport function getPreloadStatus(url: string): 'pending' | 'loading' | 'complete' | 'error' | 'not_found' {\n  return preloadManager.getStatus(url);\n}\n\n/**\n * Cancel a preload\n */\nexport function cancelPreload(url: string): void {\n  preloadManager.cancel(url);\n}\n\n/**\n * Get preloaded model (or wait for preload to complete)\n */\nexport async function getPreloadedModel(url: string): Promise<ArrayBuffer | null> {\n  return preloadManager.get(url);\n}\n"
  },
  {
    "path": "src/utils/offline.ts",
    "content": "/**\n * edgeFlow.js - Offline/PWA Support\n * \n * Utilities for offline-first ML inference.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OfflineConfig {\n  /** Enable offline mode (default: true) */\n  enabled?: boolean;\n  \n  /** Cache models for offline use (default: true) */\n  cacheModels?: boolean;\n  \n  /** Cache model config/tokenizer (default: true) */\n  cacheConfig?: boolean;\n  \n  /** Maximum cache size in bytes (default: 500MB) */\n  maxCacheSize?: number;\n  \n  /** Models to preload for offline use */\n  preloadModels?: string[];\n  \n  /** Service worker path (if using custom SW) */\n  serviceWorkerPath?: string;\n}\n\nexport interface OfflineStatus {\n  /** Whether the browser is online */\n  isOnline: boolean;\n  \n  /** Whether offline mode is available */\n  offlineReady: boolean;\n  \n  /** Number of cached models */\n  cachedModels: number;\n  \n  /** Total cache size in bytes */\n  cacheSize: number;\n  \n  /** Service worker status */\n  serviceWorker: 'active' | 'installing' | 'waiting' | 'none';\n}\n\nexport interface CachedModelInfo {\n  url: string;\n  size: number;\n  cachedAt: Date;\n  lastAccessed: Date;\n  modelId?: string;\n}\n\n// ============================================================================\n// Offline Manager\n// ============================================================================\n\n/**\n * Offline manager for PWA support\n */\nexport class OfflineManager {\n  private config: Required<OfflineConfig>;\n  private onlineListeners: Set<(online: boolean) => void> = new Set();\n  private isInitialized = false;\n  \n  constructor(config: OfflineConfig = {}) {\n    this.config = {\n      enabled: config.enabled ?? true,\n      cacheModels: config.cacheModels ?? true,\n      cacheConfig: config.cacheConfig ?? true,\n      maxCacheSize: config.maxCacheSize ?? 500 * 1024 * 1024, // 500MB\n      preloadModels: config.preloadModels ?? [],\n      serviceWorkerPath: config.serviceWorkerPath ?? '/edgeflow-sw.js',\n    };\n  }\n\n  /**\n   * Initialize offline support\n   */\n  async initialize(): Promise<void> {\n    if (this.isInitialized) return;\n    \n    // Listen for online/offline events\n    if (typeof window !== 'undefined') {\n      window.addEventListener('online', () => this.notifyOnlineStatus(true));\n      window.addEventListener('offline', () => this.notifyOnlineStatus(false));\n    }\n    \n    // Register service worker if available\n    if (this.config.enabled && 'serviceWorker' in navigator) {\n      try {\n        await this.registerServiceWorker();\n      } catch (error) {\n        console.warn('Service worker registration failed:', error);\n      }\n    }\n    \n    // Preload models for offline use\n    if (this.config.preloadModels.length > 0) {\n      await this.preloadForOffline(this.config.preloadModels);\n    }\n    \n    this.isInitialized = true;\n  }\n\n  /**\n   * Register service worker\n   */\n  private async registerServiceWorker(): Promise<void> {\n    if (!('serviceWorker' in navigator)) {\n      throw new Error('Service workers not supported');\n    }\n    \n    try {\n      const registration = await navigator.serviceWorker.register(\n        this.config.serviceWorkerPath,\n        { scope: '/' }\n      );\n      \n      console.log('edgeFlow.js service worker registered:', registration.scope);\n      \n      // Handle updates\n      registration.onupdatefound = () => {\n        const newWorker = registration.installing;\n        if (newWorker) {\n          newWorker.onstatechange = () => {\n            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {\n              console.log('New edgeFlow.js service worker available');\n            }\n          };\n        }\n      };\n    } catch (error) {\n      throw new Error(`Service worker registration failed: ${error}`);\n    }\n  }\n\n  /**\n   * Preload models for offline use\n   */\n  async preloadForOffline(modelUrls: string[]): Promise<void> {\n    const { loadModelData } = await import('./model-loader.js');\n    \n    for (const url of modelUrls) {\n      try {\n        console.log(`Preloading for offline: ${url}`);\n        await loadModelData(url, { cache: true });\n        console.log(`✓ Cached: ${url}`);\n      } catch (error) {\n        console.warn(`Failed to cache ${url}:`, error);\n      }\n    }\n  }\n\n  /**\n   * Get offline status\n   */\n  async getStatus(): Promise<OfflineStatus> {\n    const { getModelCacheStats } = await import('./model-loader.js');\n    const stats = await getModelCacheStats();\n    \n    let swStatus: OfflineStatus['serviceWorker'] = 'none';\n    if ('serviceWorker' in navigator) {\n      const registration = await navigator.serviceWorker.getRegistration();\n      if (registration) {\n        if (registration.active) swStatus = 'active';\n        else if (registration.installing) swStatus = 'installing';\n        else if (registration.waiting) swStatus = 'waiting';\n      }\n    }\n    \n    return {\n      isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n      offlineReady: stats.models > 0,\n      cachedModels: stats.models,\n      cacheSize: stats.totalSize,\n      serviceWorker: swStatus,\n    };\n  }\n\n  /**\n   * Get list of cached models\n   */\n  async getCachedModels(): Promise<CachedModelInfo[]> {\n    // Query IndexedDB for cached model metadata\n    const db = await this.openDatabase();\n    return new Promise((resolve, reject) => {\n      const tx = db.transaction('meta', 'readonly');\n      const store = tx.objectStore('meta');\n      const request = store.getAll();\n      \n      request.onsuccess = () => {\n        const models = (request.result || []).map((meta: Record<string, unknown>) => ({\n          url: meta['url'] as string,\n          size: meta['size'] as number,\n          cachedAt: new Date(meta['cachedAt'] as number),\n          lastAccessed: new Date((meta['lastAccessed'] as number) || (meta['cachedAt'] as number)),\n          modelId: meta['modelId'] as string | undefined,\n        }));\n        resolve(models);\n      };\n      request.onerror = () => reject(request.error);\n    });\n  }\n\n  /**\n   * Check if a model is available offline\n   */\n  async isModelAvailableOffline(url: string): Promise<boolean> {\n    const { isModelCached } = await import('./model-loader.js');\n    return isModelCached(url);\n  }\n\n  /**\n   * Remove model from offline cache\n   */\n  async removeFromOffline(url: string): Promise<void> {\n    const { deleteCachedModel } = await import('./model-loader.js');\n    await deleteCachedModel(url);\n  }\n\n  /**\n   * Clear all offline data\n   */\n  async clearOfflineData(): Promise<void> {\n    const { clearModelCache } = await import('./model-loader.js');\n    await clearModelCache();\n  }\n\n  /**\n   * Check available storage\n   */\n  async getStorageInfo(): Promise<{ quota: number; usage: number; available: number }> {\n    if ('storage' in navigator && 'estimate' in navigator.storage) {\n      const estimate = await navigator.storage.estimate();\n      return {\n        quota: estimate.quota ?? 0,\n        usage: estimate.usage ?? 0,\n        available: (estimate.quota ?? 0) - (estimate.usage ?? 0),\n      };\n    }\n    return { quota: 0, usage: 0, available: 0 };\n  }\n\n  /**\n   * Request persistent storage\n   */\n  async requestPersistentStorage(): Promise<boolean> {\n    if ('storage' in navigator && 'persist' in navigator.storage) {\n      return await navigator.storage.persist();\n    }\n    return false;\n  }\n\n  /**\n   * Add online status listener\n   */\n  onOnlineStatusChange(listener: (online: boolean) => void): () => void {\n    this.onlineListeners.add(listener);\n    return () => this.onlineListeners.delete(listener);\n  }\n\n  /**\n   * Check if currently online\n   */\n  isOnline(): boolean {\n    return typeof navigator !== 'undefined' ? navigator.onLine : true;\n  }\n\n  /**\n   * Notify listeners of online status change\n   */\n  private notifyOnlineStatus(online: boolean): void {\n    this.onlineListeners.forEach(listener => listener(online));\n  }\n\n  /**\n   * Open IndexedDB\n   */\n  private async openDatabase(): Promise<IDBDatabase> {\n    return new Promise((resolve, reject) => {\n      const request = indexedDB.open('edgeflow-model-cache', 1);\n      request.onsuccess = () => resolve(request.result);\n      request.onerror = () => reject(request.error);\n    });\n  }\n}\n\n// ============================================================================\n// Service Worker Template\n// ============================================================================\n\n/**\n * Generate service worker code\n */\nexport function generateServiceWorker(options: {\n  cacheName?: string;\n  modelUrls?: string[];\n  cacheFirst?: boolean;\n} = {}): string {\n  const {\n    cacheName = 'edgeflow-v1',\n    modelUrls = [],\n    cacheFirst = true,\n  } = options;\n\n  return `\n// edgeFlow.js Service Worker\n// Auto-generated - customize as needed\n\nconst CACHE_NAME = '${cacheName}';\nconst MODEL_URLS = ${JSON.stringify(modelUrls)};\n\n// Install event - cache core files\nself.addEventListener('install', (event) => {\n  event.waitUntil(\n    caches.open(CACHE_NAME)\n      .then((cache) => {\n        console.log('[edgeFlow SW] Caching core files');\n        return cache.addAll([\n          '/',\n          '/edgeflow.browser.min.js',\n          ...MODEL_URLS,\n        ]);\n      })\n      .then(() => self.skipWaiting())\n  );\n});\n\n// Activate event - cleanup old caches\nself.addEventListener('activate', (event) => {\n  event.waitUntil(\n    caches.keys()\n      .then((cacheNames) => {\n        return Promise.all(\n          cacheNames\n            .filter((name) => name !== CACHE_NAME)\n            .map((name) => caches.delete(name))\n        );\n      })\n      .then(() => self.clients.claim())\n  );\n});\n\n// Fetch event - ${cacheFirst ? 'cache first' : 'network first'} strategy\nself.addEventListener('fetch', (event) => {\n  const url = new URL(event.request.url);\n  \n  // Only handle same-origin and model requests\n  if (url.origin !== location.origin && !isModelRequest(url)) {\n    return;\n  }\n  \n  ${cacheFirst ? `\n  // Cache first strategy\n  event.respondWith(\n    caches.match(event.request)\n      .then((cached) => {\n        if (cached) {\n          return cached;\n        }\n        return fetch(event.request)\n          .then((response) => {\n            if (response.ok && shouldCache(event.request)) {\n              const clone = response.clone();\n              caches.open(CACHE_NAME)\n                .then((cache) => cache.put(event.request, clone));\n            }\n            return response;\n          });\n      })\n  );\n  ` : `\n  // Network first strategy\n  event.respondWith(\n    fetch(event.request)\n      .then((response) => {\n        if (response.ok && shouldCache(event.request)) {\n          const clone = response.clone();\n          caches.open(CACHE_NAME)\n            .then((cache) => cache.put(event.request, clone));\n        }\n        return response;\n      })\n      .catch(() => caches.match(event.request))\n  );\n  `}\n});\n\n// Check if request is for a model file\nfunction isModelRequest(url) {\n  return url.pathname.endsWith('.onnx') ||\n         url.pathname.endsWith('.bin') ||\n         url.hostname.includes('huggingface.co');\n}\n\n// Check if response should be cached\nfunction shouldCache(request) {\n  const url = new URL(request.url);\n  return request.method === 'GET' && (\n    url.pathname.endsWith('.js') ||\n    url.pathname.endsWith('.onnx') ||\n    url.pathname.endsWith('.bin') ||\n    url.pathname.endsWith('.json')\n  );\n}\n\n// Handle messages from main thread\nself.addEventListener('message', (event) => {\n  if (event.data.type === 'SKIP_WAITING') {\n    self.skipWaiting();\n  }\n  if (event.data.type === 'CACHE_MODEL') {\n    cacheModel(event.data.url);\n  }\n});\n\n// Cache a model URL\nasync function cacheModel(url) {\n  const cache = await caches.open(CACHE_NAME);\n  try {\n    const response = await fetch(url);\n    if (response.ok) {\n      await cache.put(url, response);\n      console.log('[edgeFlow SW] Cached model:', url);\n    }\n  } catch (error) {\n    console.error('[edgeFlow SW] Failed to cache model:', url, error);\n  }\n}\n  `.trim();\n}\n\n/**\n * Generate PWA manifest\n */\nexport function generateManifest(options: {\n  name: string;\n  shortName?: string;\n  description?: string;\n  themeColor?: string;\n  backgroundColor?: string;\n  icons?: Array<{ src: string; sizes: string; type: string }>;\n} = { name: 'edgeFlow.js App' }): object {\n  return {\n    name: options.name,\n    short_name: options.shortName ?? options.name,\n    description: options.description ?? 'ML-powered application built with edgeFlow.js',\n    start_url: '/',\n    display: 'standalone',\n    theme_color: options.themeColor ?? '#4F46E5',\n    background_color: options.backgroundColor ?? '#FFFFFF',\n    icons: options.icons ?? [\n      { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },\n      { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },\n    ],\n    categories: ['utilities', 'productivity'],\n  };\n}\n\n// ============================================================================\n// Singleton Instance\n// ============================================================================\n\nlet offlineManager: OfflineManager | null = null;\n\n/**\n * Get the global offline manager instance\n */\nexport function getOfflineManager(config?: OfflineConfig): OfflineManager {\n  if (!offlineManager) {\n    offlineManager = new OfflineManager(config);\n  }\n  return offlineManager;\n}\n\n/**\n * Initialize offline support\n */\nexport async function initOffline(config?: OfflineConfig): Promise<OfflineStatus> {\n  const manager = getOfflineManager(config);\n  await manager.initialize();\n  return manager.getStatus();\n}\n\n/**\n * Check if running in offline mode\n */\nexport function isOffline(): boolean {\n  return typeof navigator !== 'undefined' ? !navigator.onLine : false;\n}\n\n/**\n * Check if PWA features are supported\n */\nexport function isPWASupported(): boolean {\n  return typeof window !== 'undefined' && \n         'serviceWorker' in navigator && \n         'caches' in window;\n}\n"
  },
  {
    "path": "src/utils/preprocessor.ts",
    "content": "/**\n * edgeFlow.js - Preprocessor\n * \n * Data preprocessing utilities for images, audio, and other data types.\n * Supports HuggingFace preprocessor_config.json format.\n */\n\nimport { EdgeFlowTensor } from '../core/tensor.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Image input types\n */\nexport type ImageInput = \n  | HTMLImageElement \n  | HTMLCanvasElement \n  | ImageBitmap \n  | ImageData \n  | Blob \n  | File \n  | string;\n\n/**\n * Audio input types\n */\nexport type AudioInput = \n  | AudioBuffer \n  | Float32Array \n  | ArrayBuffer \n  | Blob \n  | File \n  | string;\n\n// ============================================================================\n// Image Preprocessing\n// ============================================================================\n\n/**\n * Image preprocessing options\n */\nexport interface ImagePreprocessorOptions {\n  /** Target width (or size for square) */\n  width?: number;\n  /** Target height */\n  height?: number;\n  /** Single size for square output (sets both width and height) */\n  size?: number;\n  /** Resize mode */\n  resizeMode?: 'stretch' | 'contain' | 'cover' | 'pad' | 'shortest_edge' | 'longest_edge';\n  /** Normalization mean */\n  mean?: [number, number, number];\n  /** Normalization std */\n  std?: [number, number, number];\n  /** Rescale factor (applied before normalization) */\n  rescaleFactor?: number;\n  /** Convert to grayscale */\n  grayscale?: boolean;\n  /** Channel format */\n  channelFormat?: 'CHW' | 'HWC';\n  /** Output data type */\n  dtype?: 'float32' | 'uint8';\n  /** Do resize */\n  doResize?: boolean;\n  /** Do rescale */\n  doRescale?: boolean;\n  /** Do normalize */\n  doNormalize?: boolean;\n  /** Do center crop */\n  doCenterCrop?: boolean;\n  /** Center crop size */\n  cropSize?: number | { width: number; height: number };\n  /** Padding color for 'pad' mode (RGB 0-255) */\n  paddingColor?: [number, number, number];\n}\n\n/**\n * Default image preprocessing options (ImageNet style)\n */\nconst DEFAULT_IMAGE_OPTIONS: ImagePreprocessorOptions = {\n  width: 224,\n  height: 224,\n  resizeMode: 'cover',\n  mean: [0.485, 0.456, 0.406],\n  std: [0.229, 0.224, 0.225],\n  rescaleFactor: 1 / 255,\n  grayscale: false,\n  channelFormat: 'CHW',\n  dtype: 'float32',\n  doResize: true,\n  doRescale: true,\n  doNormalize: true,\n  doCenterCrop: false,\n  paddingColor: [0, 0, 0],\n};\n\n/**\n * ImagePreprocessor - Process images for model input\n * \n * Supports HuggingFace preprocessor_config.json format.\n */\nexport class ImagePreprocessor {\n  private readonly options: Required<ImagePreprocessorOptions>;\n  private canvas: HTMLCanvasElement | null = null;\n  private ctx: CanvasRenderingContext2D | null = null;\n\n  constructor(options: ImagePreprocessorOptions = {}) {\n    // Handle size option\n    const size = options.size;\n    const width = options.width ?? size ?? DEFAULT_IMAGE_OPTIONS.width!;\n    const height = options.height ?? size ?? DEFAULT_IMAGE_OPTIONS.height!;\n    \n    this.options = {\n      ...DEFAULT_IMAGE_OPTIONS,\n      ...options,\n      width,\n      height,\n      size: size ?? width,\n      cropSize: options.cropSize ?? options.size ?? width,\n    } as Required<ImagePreprocessorOptions>;\n  }\n\n  /**\n   * Load from HuggingFace preprocessor_config.json\n   */\n  static fromConfig(config: Record<string, unknown>): ImagePreprocessor {\n    const options: ImagePreprocessorOptions = {};\n    \n    // Map HuggingFace config to our options\n    const size = config['size'];\n    if (size !== undefined) {\n      if (typeof size === 'number') {\n        options.size = size;\n      } else if (typeof size === 'object' && size !== null) {\n        const sizeObj = size as { width?: number; height?: number; shortest_edge?: number };\n        options.width = sizeObj.width ?? sizeObj.shortest_edge;\n        options.height = sizeObj.height ?? sizeObj.shortest_edge;\n      }\n    }\n    \n    const cropSize = config['crop_size'];\n    if (cropSize !== undefined) {\n      if (typeof cropSize === 'number') {\n        options.cropSize = cropSize;\n      } else if (typeof cropSize === 'object' && cropSize !== null) {\n        const cropObj = cropSize as { width?: number; height?: number };\n        options.cropSize = { width: cropObj.width ?? 224, height: cropObj.height ?? 224 };\n      }\n    }\n    \n    const imageMean = config['image_mean'];\n    if (Array.isArray(imageMean)) {\n      options.mean = imageMean as [number, number, number];\n    }\n    \n    const imageStd = config['image_std'];\n    if (Array.isArray(imageStd)) {\n      options.std = imageStd as [number, number, number];\n    }\n    \n    const rescaleFactor = config['rescale_factor'];\n    if (typeof rescaleFactor === 'number') {\n      options.rescaleFactor = rescaleFactor;\n    }\n    \n    const doResize = config['do_resize'];\n    if (typeof doResize === 'boolean') {\n      options.doResize = doResize;\n    }\n    \n    const doRescale = config['do_rescale'];\n    if (typeof doRescale === 'boolean') {\n      options.doRescale = doRescale;\n    }\n    \n    const doNormalize = config['do_normalize'];\n    if (typeof doNormalize === 'boolean') {\n      options.doNormalize = doNormalize;\n    }\n    \n    const doCenterCrop = config['do_center_crop'];\n    if (typeof doCenterCrop === 'boolean') {\n      options.doCenterCrop = doCenterCrop;\n    }\n    \n    if (config['resample'] !== undefined) {\n      // Map HuggingFace resample to our resize mode\n      options.resizeMode = 'cover';\n    }\n    \n    return new ImagePreprocessor(options);\n  }\n\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromUrl(url: string): Promise<ImagePreprocessor> {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load preprocessor config from ${url}`);\n    }\n    const config = await response.json() as Record<string, unknown>;\n    return ImagePreprocessor.fromConfig(config);\n  }\n\n  /**\n   * Load from HuggingFace Hub by model ID\n   */\n  static async fromHuggingFace(\n    modelId: string,\n    options?: { revision?: string }\n  ): Promise<ImagePreprocessor> {\n    const revision = options?.revision ?? 'main';\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n    return ImagePreprocessor.fromUrl(url);\n  }\n\n  /**\n   * Initialize canvas (lazy)\n   */\n  private ensureCanvas(): void {\n    if (!this.canvas) {\n      if (typeof document !== 'undefined') {\n        this.canvas = document.createElement('canvas');\n        this.ctx = this.canvas.getContext('2d');\n      } else {\n        throw new Error('ImagePreprocessor requires a browser environment');\n      }\n    }\n  }\n\n  /**\n   * Process an image\n   */\n  async process(input: ImageInput): Promise<EdgeFlowTensor> {\n    let imageData: ImageData;\n\n    if (typeof input === 'string') {\n      // Load from URL or base64\n      imageData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      imageData = await this.loadFromBlob(input);\n    } else if (input instanceof ImageData) {\n      imageData = input;\n    } else {\n      // HTMLImageElement, HTMLCanvasElement, ImageBitmap\n      imageData = this.toImageData(input);\n    }\n\n    // Apply preprocessing pipeline\n    let processed = imageData;\n\n    // 1. Resize\n    if (this.options.doResize) {\n      processed = this.resize(processed);\n    }\n\n    // 2. Center crop\n    if (this.options.doCenterCrop) {\n      processed = this.centerCrop(processed);\n    }\n\n    // 3. Convert to tensor (with rescale and normalize)\n    return this.toTensor(processed);\n  }\n\n  /**\n   * Process multiple images (batch)\n   */\n  async processBatch(inputs: ImageInput[]): Promise<EdgeFlowTensor> {\n    const tensors = await Promise.all(inputs.map(input => this.process(input)));\n    \n    // Stack tensors into batch\n    const batchSize = tensors.length;\n    const firstTensor = tensors[0];\n    if (!firstTensor) {\n      return new EdgeFlowTensor(new Float32Array(0), [0], 'float32');\n    }\n    \n    const channels = firstTensor.shape[0] ?? 3;\n    const height = firstTensor.shape[1] ?? this.options.height;\n    const width = firstTensor.shape[2] ?? this.options.width;\n    \n    const batchData = new Float32Array(batchSize * channels * height * width);\n    \n    for (let i = 0; i < tensors.length; i++) {\n      const t = tensors[i];\n      if (t) {\n        batchData.set(t.toFloat32Array(), i * channels * height * width);\n      }\n    }\n\n    return new EdgeFlowTensor(\n      batchData,\n      [batchSize, channels, height, width],\n      'float32'\n    );\n  }\n\n  /**\n   * Load image from URL or base64\n   */\n  private async loadFromUrl(url: string): Promise<ImageData> {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.crossOrigin = 'anonymous';\n      \n      img.onload = () => {\n        resolve(this.toImageData(img));\n      };\n      \n      img.onerror = () => {\n        reject(new Error(`Failed to load image from ${url}`));\n      };\n      \n      img.src = url;\n    });\n  }\n\n  /**\n   * Load image from Blob/File\n   */\n  private async loadFromBlob(blob: Blob): Promise<ImageData> {\n    const url = URL.createObjectURL(blob);\n    try {\n      return await this.loadFromUrl(url);\n    } finally {\n      URL.revokeObjectURL(url);\n    }\n  }\n\n  /**\n   * Center crop image\n   */\n  private centerCrop(imageData: ImageData): ImageData {\n    const cropSize = this.options.cropSize;\n    let cropWidth: number;\n    let cropHeight: number;\n    \n    if (typeof cropSize === 'number') {\n      cropWidth = cropSize;\n      cropHeight = cropSize;\n    } else {\n      cropWidth = cropSize.width;\n      cropHeight = cropSize.height;\n    }\n    \n    const srcX = Math.max(0, Math.floor((imageData.width - cropWidth) / 2));\n    const srcY = Math.max(0, Math.floor((imageData.height - cropHeight) / 2));\n    \n    this.ensureCanvas();\n    \n    // Draw source image\n    const srcCanvas = document.createElement('canvas');\n    srcCanvas.width = imageData.width;\n    srcCanvas.height = imageData.height;\n    const srcCtx = srcCanvas.getContext('2d')!;\n    srcCtx.putImageData(imageData, 0, 0);\n    \n    // Crop\n    this.canvas!.width = cropWidth;\n    this.canvas!.height = cropHeight;\n    this.ctx!.drawImage(srcCanvas, srcX, srcY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);\n    \n    return this.ctx!.getImageData(0, 0, cropWidth, cropHeight);\n  }\n\n  /**\n   * Convert image element to ImageData\n   */\n  private toImageData(\n    source: HTMLImageElement | HTMLCanvasElement | ImageBitmap\n  ): ImageData {\n    this.ensureCanvas();\n    \n    const { width, height } = source;\n    this.canvas!.width = width;\n    this.canvas!.height = height;\n    \n    this.ctx!.drawImage(source, 0, 0);\n    return this.ctx!.getImageData(0, 0, width, height);\n  }\n\n  /**\n   * Resize image data\n   */\n  private resize(imageData: ImageData): ImageData {\n    const { width, height, resizeMode } = this.options;\n    \n    this.ensureCanvas();\n    \n    // Calculate resize dimensions\n    let srcX = 0, srcY = 0, srcW = imageData.width, srcH = imageData.height;\n    let dstX = 0, dstY = 0, dstW = width, dstH = height;\n\n    if (resizeMode === 'contain') {\n      const scale = Math.min(width / imageData.width, height / imageData.height);\n      dstW = Math.round(imageData.width * scale);\n      dstH = Math.round(imageData.height * scale);\n      dstX = Math.round((width - dstW) / 2);\n      dstY = Math.round((height - dstH) / 2);\n    } else if (resizeMode === 'cover') {\n      const scale = Math.max(width / imageData.width, height / imageData.height);\n      srcW = Math.round(width / scale);\n      srcH = Math.round(height / scale);\n      srcX = Math.round((imageData.width - srcW) / 2);\n      srcY = Math.round((imageData.height - srcH) / 2);\n    }\n\n    // Create temp canvas for source\n    const srcCanvas = document.createElement('canvas');\n    srcCanvas.width = imageData.width;\n    srcCanvas.height = imageData.height;\n    const srcCtx = srcCanvas.getContext('2d')!;\n    srcCtx.putImageData(imageData, 0, 0);\n\n    // Draw to output canvas\n    this.canvas!.width = width;\n    this.canvas!.height = height;\n    \n    // Fill with black for padding modes\n    if (resizeMode === 'contain' || resizeMode === 'pad') {\n      this.ctx!.fillStyle = 'black';\n      this.ctx!.fillRect(0, 0, width, height);\n    }\n    \n    this.ctx!.drawImage(srcCanvas, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH);\n    \n    return this.ctx!.getImageData(0, 0, width, height);\n  }\n\n  /**\n   * Convert ImageData to tensor\n   */\n  private toTensor(imageData: ImageData): EdgeFlowTensor {\n    const { \n      mean, std, grayscale, channelFormat, dtype,\n      doRescale, rescaleFactor, doNormalize\n    } = this.options;\n    \n    const height = imageData.height;\n    const width = imageData.width;\n    const channels = grayscale ? 1 : 3;\n    \n    const data = new Float32Array(channels * height * width);\n    const pixels = imageData.data;\n\n    for (let y = 0; y < height; y++) {\n      for (let x = 0; x < width; x++) {\n        const pixelIdx = (y * width + x) * 4;\n        \n        if (grayscale) {\n          // Convert to grayscale\n          let gray = (\n            0.299 * (pixels[pixelIdx] ?? 0) +\n            0.587 * (pixels[pixelIdx + 1] ?? 0) +\n            0.114 * (pixels[pixelIdx + 2] ?? 0)\n          );\n          \n          if (doRescale) {\n            gray *= rescaleFactor;\n          }\n          \n          if (doNormalize) {\n            gray = (gray - (mean[0] ?? 0)) / (std[0] ?? 1);\n          }\n          \n          const idx = y * width + x;\n          data[idx] = gray;\n        } else if (channelFormat === 'CHW') {\n          // Channel-first format (used by most PyTorch models)\n          for (let c = 0; c < 3; c++) {\n            let value = pixels[pixelIdx + c] ?? 0;\n            \n            if (doRescale) {\n              value *= rescaleFactor;\n            }\n            \n            if (doNormalize) {\n              value = (value - (mean[c] ?? 0)) / (std[c] ?? 1);\n            }\n            \n            const idx = c * height * width + y * width + x;\n            data[idx] = value;\n          }\n        } else {\n          // HWC format (used by TensorFlow models)\n          for (let c = 0; c < 3; c++) {\n            let value = pixels[pixelIdx + c] ?? 0;\n            \n            if (doRescale) {\n              value *= rescaleFactor;\n            }\n            \n            if (doNormalize) {\n              value = (value - (mean[c] ?? 0)) / (std[c] ?? 1);\n            }\n            \n            const idx = y * width * 3 + x * 3 + c;\n            data[idx] = value;\n          }\n        }\n      }\n    }\n\n    const shape = channelFormat === 'CHW'\n      ? [channels, height, width]\n      : [height, width, channels];\n\n    return new EdgeFlowTensor(data, shape, dtype);\n  }\n\n  /**\n   * Get current options\n   */\n  getOptions(): ImagePreprocessorOptions {\n    return { ...this.options };\n  }\n}\n\n// ============================================================================\n// Audio Preprocessing\n// ============================================================================\n\n/**\n * Audio preprocessing options\n */\nexport interface AudioPreprocessorOptions {\n  /** Target sample rate */\n  sampleRate?: number;\n  /** Number of mel bins */\n  nMels?: number;\n  /** FFT size */\n  nFft?: number;\n  /** Hop length */\n  hopLength?: number;\n  /** Whether to normalize */\n  normalize?: boolean;\n  /** Maximum duration in seconds */\n  maxDuration?: number;\n}\n\n/**\n * Default audio options\n */\nconst DEFAULT_AUDIO_OPTIONS: Required<AudioPreprocessorOptions> = {\n  sampleRate: 16000,\n  nMels: 80,\n  nFft: 400,\n  hopLength: 160,\n  normalize: true,\n  maxDuration: 30,\n};\n\n/**\n * AudioPreprocessor - Process audio for model input\n * \n * Supports Whisper and other audio model preprocessing.\n */\nexport class AudioPreprocessor {\n  private readonly options: Required<AudioPreprocessorOptions>;\n  private audioContext: AudioContext | null = null;\n\n  constructor(options: AudioPreprocessorOptions = {}) {\n    this.options = { ...DEFAULT_AUDIO_OPTIONS, ...options };\n  }\n\n  /**\n   * Load from HuggingFace feature_extractor config\n   */\n  static fromConfig(config: Record<string, unknown>): AudioPreprocessor {\n    const options: AudioPreprocessorOptions = {};\n    \n    const samplingRate = config['sampling_rate'];\n    if (typeof samplingRate === 'number') {\n      options.sampleRate = samplingRate;\n    }\n    \n    const featureSize = config['feature_size'];\n    if (typeof featureSize === 'number') {\n      options.nMels = featureSize;\n    }\n    \n    const nFft = config['n_fft'];\n    if (typeof nFft === 'number') {\n      options.nFft = nFft;\n    }\n    \n    const hopLength = config['hop_length'];\n    if (typeof hopLength === 'number') {\n      options.hopLength = hopLength;\n    }\n    \n    return new AudioPreprocessor(options);\n  }\n\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromHuggingFace(\n    modelId: string,\n    options?: { revision?: string }\n  ): Promise<AudioPreprocessor> {\n    const revision = options?.revision ?? 'main';\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/preprocessor_config.json`;\n    \n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load audio config from ${url}`);\n    }\n    const config = await response.json() as Record<string, unknown>;\n    return AudioPreprocessor.fromConfig(config);\n  }\n\n  /**\n   * Initialize audio context (lazy)\n   */\n  private ensureAudioContext(): void {\n    if (!this.audioContext) {\n      if (typeof AudioContext !== 'undefined') {\n        this.audioContext = new AudioContext({ sampleRate: this.options.sampleRate });\n      } else {\n        throw new Error('AudioPreprocessor requires Web Audio API support');\n      }\n    }\n  }\n\n  /**\n   * Process audio data\n   */\n  async process(input: AudioInput): Promise<EdgeFlowTensor> {\n    let audioData: Float32Array;\n\n    if (typeof input === 'string') {\n      // Load from URL\n      audioData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      // Load from Blob/File\n      audioData = await this.loadFromBlob(input);\n    } else if (input instanceof AudioBuffer) {\n      audioData = this.audioBufferToFloat32(input);\n    } else if (input instanceof Float32Array) {\n      audioData = input;\n    } else {\n      // ArrayBuffer - decode\n      audioData = await this.decodeAudioData(input);\n    }\n\n    // Resample if needed\n    // For now, assume input is at target sample rate\n\n    // Normalize\n    if (this.options.normalize) {\n      audioData = this.normalizeAudio(audioData);\n    }\n\n    // Truncate if needed\n    const maxSamples = this.options.maxDuration * this.options.sampleRate;\n    if (audioData.length > maxSamples) {\n      audioData = audioData.slice(0, maxSamples);\n    }\n\n    // Compute mel spectrogram (simplified)\n    const melSpec = this.computeMelSpectrogram(audioData);\n\n    return melSpec;\n  }\n\n  /**\n   * Process raw waveform (for models that don't need mel spectrogram)\n   */\n  async processRaw(input: AudioInput): Promise<EdgeFlowTensor> {\n    let audioData: Float32Array;\n\n    if (typeof input === 'string') {\n      audioData = await this.loadFromUrl(input);\n    } else if (input instanceof Blob || input instanceof File) {\n      audioData = await this.loadFromBlob(input);\n    } else if (input instanceof AudioBuffer) {\n      audioData = this.audioBufferToFloat32(input);\n    } else if (input instanceof Float32Array) {\n      audioData = input;\n    } else {\n      audioData = await this.decodeAudioData(input);\n    }\n\n    // Normalize\n    if (this.options.normalize) {\n      audioData = this.normalizeAudio(audioData);\n    }\n\n    // Truncate/pad\n    const maxSamples = this.options.maxDuration * this.options.sampleRate;\n    if (audioData.length > maxSamples) {\n      audioData = audioData.slice(0, maxSamples);\n    }\n\n    return new EdgeFlowTensor(audioData, [1, audioData.length], 'float32');\n  }\n\n  /**\n   * Load audio from URL\n   */\n  private async loadFromUrl(url: string): Promise<Float32Array> {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to load audio from ${url}`);\n    }\n    \n    const arrayBuffer = await response.arrayBuffer();\n    return this.decodeAudioData(arrayBuffer);\n  }\n\n  /**\n   * Load audio from Blob/File\n   */\n  private async loadFromBlob(blob: Blob): Promise<Float32Array> {\n    const arrayBuffer = await blob.arrayBuffer();\n    return this.decodeAudioData(arrayBuffer);\n  }\n\n  /**\n   * Decode audio data\n   */\n  private async decodeAudioData(data: ArrayBuffer): Promise<Float32Array> {\n    this.ensureAudioContext();\n    const audioBuffer = await this.audioContext!.decodeAudioData(data.slice(0)); // Clone to avoid detached buffer\n    return this.audioBufferToFloat32(audioBuffer);\n  }\n\n  /**\n   * Convert AudioBuffer to Float32Array\n   */\n  private audioBufferToFloat32(buffer: AudioBuffer): Float32Array {\n    // Get first channel\n    const channelData = buffer.getChannelData(0);\n    return new Float32Array(channelData);\n  }\n\n  /**\n   * Normalize audio\n   */\n  private normalizeAudio(data: Float32Array): Float32Array {\n    let max = 0;\n    for (let i = 0; i < data.length; i++) {\n      const abs = Math.abs(data[i] ?? 0);\n      if (abs > max) max = abs;\n    }\n\n    if (max > 0) {\n      const result = new Float32Array(data.length);\n      for (let i = 0; i < data.length; i++) {\n        result[i] = (data[i] ?? 0) / max;\n      }\n      return result;\n    }\n\n    return data;\n  }\n\n  /**\n   * Compute mel spectrogram (simplified implementation)\n   */\n  private computeMelSpectrogram(audio: Float32Array): EdgeFlowTensor {\n    const { nMels, nFft, hopLength } = this.options;\n    \n    // Calculate number of frames\n    const numFrames = Math.floor((audio.length - nFft) / hopLength) + 1;\n    \n    if (numFrames <= 0) {\n      // Return empty spectrogram for very short audio\n      return new EdgeFlowTensor(new Float32Array(nMels), [1, nMels], 'float32');\n    }\n\n    const melSpec = new Float32Array(numFrames * nMels);\n\n    // Simplified mel spectrogram computation\n    // In production, use proper FFT and mel filterbank\n    for (let frame = 0; frame < numFrames; frame++) {\n      const start = frame * hopLength;\n      \n      // Compute frame energy (simplified - not real FFT)\n      for (let mel = 0; mel < nMels; mel++) {\n        let energy = 0;\n        const freqStart = Math.floor((mel / nMels) * (nFft / 2));\n        const freqEnd = Math.floor(((mel + 1) / nMels) * (nFft / 2));\n        \n        for (let i = freqStart; i < Math.min(freqEnd, nFft); i++) {\n          const sample = audio[start + i] ?? 0;\n          energy += sample * sample;\n        }\n        \n        // Convert to log scale\n        melSpec[frame * nMels + mel] = Math.log(energy + 1e-10);\n      }\n    }\n\n    return new EdgeFlowTensor(melSpec, [numFrames, nMels], 'float32');\n  }\n\n  /**\n   * Dispose resources\n   */\n  dispose(): void {\n    if (this.audioContext) {\n      this.audioContext.close();\n      this.audioContext = null;\n    }\n  }\n}\n\n// ============================================================================\n// Text Preprocessing\n// ============================================================================\n\n/**\n * Text preprocessing options\n */\nexport interface TextPreprocessorOptions {\n  /** Convert to lowercase */\n  lowercase?: boolean;\n  /** Remove punctuation */\n  removePunctuation?: boolean;\n  /** Remove extra whitespace */\n  normalizeWhitespace?: boolean;\n  /** Maximum length in characters */\n  maxLength?: number;\n}\n\n/**\n * Preprocess text\n */\nexport function preprocessText(\n  text: string,\n  options: TextPreprocessorOptions = {}\n): string {\n  const {\n    lowercase = true,\n    removePunctuation = false,\n    normalizeWhitespace = true,\n    maxLength,\n  } = options;\n\n  let result = text;\n\n  if (lowercase) {\n    result = result.toLowerCase();\n  }\n\n  if (removePunctuation) {\n    result = result.replace(/[^\\w\\s]/g, '');\n  }\n\n  if (normalizeWhitespace) {\n    result = result.replace(/\\s+/g, ' ').trim();\n  }\n\n  if (maxLength && result.length > maxLength) {\n    result = result.slice(0, maxLength);\n  }\n\n  return result;\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create image preprocessor with common presets\n */\nexport function createImagePreprocessor(\n  preset: 'imagenet' | 'clip' | 'vit' | 'custom' = 'imagenet',\n  options: ImagePreprocessorOptions = {}\n): ImagePreprocessor {\n  const presets: Record<string, ImagePreprocessorOptions> = {\n    imagenet: {\n      width: 224,\n      height: 224,\n      mean: [0.485, 0.456, 0.406],\n      std: [0.229, 0.224, 0.225],\n    },\n    clip: {\n      width: 224,\n      height: 224,\n      mean: [0.48145466, 0.4578275, 0.40821073],\n      std: [0.26862954, 0.26130258, 0.27577711],\n    },\n    vit: {\n      width: 224,\n      height: 224,\n      mean: [0.5, 0.5, 0.5],\n      std: [0.5, 0.5, 0.5],\n    },\n    custom: {},\n  };\n\n  return new ImagePreprocessor({ ...presets[preset], ...options });\n}\n\n/**\n * Create audio preprocessor with common presets\n */\nexport function createAudioPreprocessor(\n  preset: 'whisper' | 'wav2vec' | 'custom' = 'whisper',\n  options: AudioPreprocessorOptions = {}\n): AudioPreprocessor {\n  const presets: Record<string, AudioPreprocessorOptions> = {\n    whisper: {\n      sampleRate: 16000,\n      nMels: 80,\n      nFft: 400,\n      hopLength: 160,\n    },\n    wav2vec: {\n      sampleRate: 16000,\n      normalize: true,\n    },\n    custom: {},\n  };\n\n  return new AudioPreprocessor({ ...presets[preset], ...options });\n}\n"
  },
  {
    "path": "src/utils/tokenizer.ts",
    "content": "/**\n * edgeFlow.js - Tokenizer\n * \n * Full-featured tokenizer supporting HuggingFace tokenizer.json format.\n * Supports BPE, WordPiece, and Unigram tokenization.\n */\n\nimport {\n  TokenizerConfig,\n  TokenizedOutput,\n  EdgeFlowError,\n  ErrorCodes,\n} from '../core/types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TokenizerModel = 'BPE' | 'WordPiece' | 'Unigram' | 'basic';\n\nexport interface TokenizerOptions {\n  addSpecialTokens?: boolean;\n  maxLength?: number;\n  padding?: 'max_length' | 'longest' | 'do_not_pad';\n  truncation?: boolean;\n  returnAttentionMask?: boolean;\n  returnTokenTypeIds?: boolean;\n  textPair?: string;\n}\n\n/**\n * HuggingFace tokenizer.json format\n */\ninterface HFTokenizerJSON {\n  version?: string;\n  truncation?: {\n    max_length: number;\n    strategy: string;\n  };\n  padding?: {\n    strategy: string;\n    pad_id: number;\n    pad_token: string;\n  };\n  added_tokens?: Array<{\n    id: number;\n    content: string;\n    single_word: boolean;\n    lstrip: boolean;\n    rstrip: boolean;\n    normalized: boolean;\n    special: boolean;\n  }>;\n  normalizer?: {\n    type: string;\n    lowercase?: boolean;\n    strip_accents?: boolean;\n    [key: string]: unknown;\n  };\n  pre_tokenizer?: {\n    type: string;\n    [key: string]: unknown;\n  };\n  post_processor?: {\n    type: string;\n    single?: Array<{ id: string; type_id: number } | { SpecialToken: { id: string; type_id: number } } | { Sequence: { id: string; type_id: number } }>;\n    pair?: Array<{ id: string; type_id: number } | { SpecialToken: { id: string; type_id: number } } | { Sequence: { id: string; type_id: number } }>;\n    special_tokens?: Record<string, { id: string; ids: number[]; tokens: string[] }>;\n    [key: string]: unknown;\n  };\n  decoder?: {\n    type: string;\n    [key: string]: unknown;\n  };\n  model: {\n    type: string;\n    vocab?: Record<string, number>;\n    merges?: string[];\n    unk_token?: string;\n    continuing_subword_prefix?: string;\n    end_of_word_suffix?: string;\n    fuse_unk?: boolean;\n    byte_fallback?: boolean;\n    [key: string]: unknown;\n  };\n}\n\n\n// ============================================================================\n// Tokenizer Implementation\n// ============================================================================\n\n/**\n * Tokenizer - Full-featured tokenizer supporting HuggingFace format\n */\nexport class Tokenizer {\n  private vocab: Map<string, number> = new Map();\n  private reverseVocab: Map<number, string> = new Map();\n  private merges: Map<string, number> = new Map();\n  private addedTokens: Map<string, number> = new Map();\n  private specialTokens: Set<string> = new Set();\n  \n  private modelType: TokenizerModel = 'BPE';\n  private unkToken: string = '[UNK]';\n  private continuingSubwordPrefix: string = '##';\n  \n  // Special token IDs\n  private padTokenId: number = 0;\n  private unkTokenId: number = 0;\n  private clsTokenId?: number;\n  private sepTokenId?: number;\n  private maskTokenId?: number;\n  private bosTokenId?: number;\n  private eosTokenId?: number;\n  \n  // Config\n  private maxLength: number = 512;\n  private doLowerCase: boolean = false;\n  private stripAccents: boolean = false;\n  \n  // Post-processor config\n  private postProcessor?: HFTokenizerJSON['post_processor'];\n  \n  // Byte encoder for BPE\n  private byteEncoder: Map<number, string> = new Map();\n  private byteDecoder: Map<string, number> = new Map();\n\n  constructor() {\n    this.initByteEncoder();\n  }\n\n  /**\n   * Initialize byte encoder/decoder for BPE\n   */\n  private initByteEncoder(): void {\n    const bytes: number[] = [];\n    \n    // Printable ASCII\n    for (let i = 33; i <= 126; i++) bytes.push(i);\n    for (let i = 161; i <= 172; i++) bytes.push(i);\n    for (let i = 174; i <= 255; i++) bytes.push(i);\n    \n    const chars = [...bytes];\n    let n = 0;\n    \n    for (let i = 0; i < 256; i++) {\n      if (!bytes.includes(i)) {\n        bytes.push(i);\n        chars.push(256 + n);\n        n++;\n      }\n    }\n    \n    for (let i = 0; i < bytes.length; i++) {\n      const byte = bytes[i]!;\n      const char = String.fromCharCode(chars[i]!);\n      this.byteEncoder.set(byte, char);\n      this.byteDecoder.set(char, byte);\n    }\n  }\n\n  /**\n   * Load from HuggingFace tokenizer.json\n   */\n  static async fromJSON(json: HFTokenizerJSON | string): Promise<Tokenizer> {\n    const tokenizer = new Tokenizer();\n    const data = typeof json === 'string' ? JSON.parse(json) as HFTokenizerJSON : json;\n    \n    // Load model config\n    if (data.model) {\n      tokenizer.modelType = data.model.type as TokenizerModel;\n      \n      // Load vocabulary.\n      // BPE/WordPiece: vocab is an object { token: id }.\n      // Unigram (SentencePiece): vocab is an array of [token, score] pairs\n      // where the array *index* is the token ID.\n      if (data.model.vocab) {\n        if (Array.isArray(data.model.vocab)) {\n          // Unigram format\n          const unigramVocab = data.model.vocab as Array<[string, number]>;\n          for (let i = 0; i < unigramVocab.length; i++) {\n            const entry = unigramVocab[i]!;\n            const token = Array.isArray(entry) ? entry[0] : (entry as unknown as string);\n            tokenizer.vocab.set(token, i);\n            tokenizer.reverseVocab.set(i, token);\n          }\n        } else {\n          for (const [token, id] of Object.entries(data.model.vocab)) {\n            tokenizer.vocab.set(token, id as number);\n            tokenizer.reverseVocab.set(id as number, token);\n          }\n        }\n      }\n      \n      // Load merges for BPE\n      if (data.model.merges) {\n        for (let i = 0; i < data.model.merges.length; i++) {\n          tokenizer.merges.set(data.model.merges[i]!, i);\n        }\n      }\n      \n      // Model-specific config\n      tokenizer.unkToken = data.model.unk_token ?? '[UNK]';\n      tokenizer.continuingSubwordPrefix = data.model.continuing_subword_prefix ?? '##';\n    }\n    \n    // Load added tokens\n    if (data.added_tokens) {\n      for (const token of data.added_tokens) {\n        tokenizer.addedTokens.set(token.content, token.id);\n        tokenizer.reverseVocab.set(token.id, token.content);\n        if (token.special) {\n          tokenizer.specialTokens.add(token.content);\n        }\n        \n        // Detect special token types\n        const content = token.content.toLowerCase();\n        if (content.includes('pad')) tokenizer.padTokenId = token.id;\n        if (content.includes('unk')) tokenizer.unkTokenId = token.id;\n        if (content.includes('cls') || content === '[cls]') tokenizer.clsTokenId = token.id;\n        if (content.includes('sep') || content === '[sep]') tokenizer.sepTokenId = token.id;\n        if (content.includes('mask')) tokenizer.maskTokenId = token.id;\n        if (content.includes('bos') || content === '<s>') tokenizer.bosTokenId = token.id;\n        if (content.includes('eos') || content === '</s>') tokenizer.eosTokenId = token.id;\n      }\n    }\n    \n    // Load normalizer config\n    if (data.normalizer) {\n      tokenizer.doLowerCase = data.normalizer.lowercase ?? false;\n      tokenizer.stripAccents = data.normalizer.strip_accents ?? false;\n    }\n    \n    // Load truncation config\n    if (data.truncation) {\n      tokenizer.maxLength = data.truncation.max_length;\n    }\n    \n    // Load post-processor\n    if (data.post_processor) {\n      tokenizer.postProcessor = data.post_processor;\n    }\n    \n    return tokenizer;\n  }\n\n  /**\n   * Load from URL (tokenizer.json)\n   */\n  static async fromUrl(url: string): Promise<Tokenizer> {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new EdgeFlowError(\n        `Failed to load tokenizer from ${url}: ${response.status}`,\n        ErrorCodes.MODEL_NOT_FOUND\n      );\n    }\n    const json = await response.json() as HFTokenizerJSON;\n    return Tokenizer.fromJSON(json);\n  }\n\n  /**\n   * Load from HuggingFace Hub\n   */\n  static async fromHuggingFace(modelId: string, options?: { revision?: string }): Promise<Tokenizer> {\n    const revision = options?.revision ?? 'main';\n    const url = `https://huggingface.co/${modelId}/resolve/${revision}/tokenizer.json`;\n    return Tokenizer.fromUrl(url);\n  }\n\n  /**\n   * Normalize text\n   */\n  private normalize(text: string): string {\n    let result = text;\n    \n    if (this.doLowerCase) {\n      result = result.toLowerCase();\n    }\n    \n    if (this.stripAccents) {\n      result = result.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n    }\n    \n    // Normalize whitespace\n    result = result.replace(/\\s+/g, ' ').trim();\n    \n    return result;\n  }\n\n  /**\n   * Pre-tokenize text (split into words)\n   */\n  private preTokenize(text: string): string[] {\n    // GPT-2 style: split on whitespace and punctuation, keeping them\n    const pattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+/gu;\n    const matches = text.match(pattern);\n    return matches ?? [text];\n  }\n\n  /**\n   * Encode text to bytes (for BPE)\n   */\n  private textToBytes(text: string): string {\n    const encoder = new TextEncoder();\n    const bytes = encoder.encode(text);\n    return Array.from(bytes).map(b => this.byteEncoder.get(b) ?? '').join('');\n  }\n\n  /**\n   * Decode bytes to text (for BPE)\n   */\n  private bytesToText(text: string): string {\n    const bytes = new Uint8Array(\n      text.split('').map(c => this.byteDecoder.get(c) ?? 0)\n    );\n    const decoder = new TextDecoder('utf-8', { fatal: false });\n    return decoder.decode(bytes);\n  }\n\n  /**\n   * Get BPE pairs from word\n   */\n  private getPairs(word: string[]): Set<string> {\n    const pairs = new Set<string>();\n    for (let i = 0; i < word.length - 1; i++) {\n      pairs.add(`${word[i]} ${word[i + 1]}`);\n    }\n    return pairs;\n  }\n\n  /**\n   * Apply BPE to a word\n   */\n  private bpe(token: string): string[] {\n    if (this.vocab.has(token)) {\n      return [token];\n    }\n    \n    let word = token.split('');\n    let pairs = this.getPairs(word);\n    \n    if (pairs.size === 0) {\n      return [token];\n    }\n    \n    while (true) {\n      // Find the pair with lowest merge rank\n      let minPair: string | null = null;\n      let minRank = Infinity;\n      \n      for (const pair of pairs) {\n        const rank = this.merges.get(pair);\n        if (rank !== undefined && rank < minRank) {\n          minRank = rank;\n          minPair = pair;\n        }\n      }\n      \n      if (minPair === null) break;\n      \n      const parts = minPair.split(' ');\n      const first = parts[0];\n      const second = parts[1];\n      if (!first || !second) break;\n      \n      const newWord: string[] = [];\n      let i = 0;\n      \n      while (i < word.length) {\n        const j = word.indexOf(first, i);\n        if (j === -1) {\n          newWord.push(...word.slice(i));\n          break;\n        }\n        \n        newWord.push(...word.slice(i, j));\n        \n        if (word[j] === first && j < word.length - 1 && word[j + 1] === second) {\n          newWord.push(first + second);\n          i = j + 2;\n        } else {\n          newWord.push(word[j]!);\n          i = j + 1;\n        }\n      }\n      \n      word = newWord;\n      \n      if (word.length === 1) break;\n      \n      pairs = this.getPairs(word);\n    }\n    \n    return word;\n  }\n\n  /**\n   * WordPiece tokenization\n   */\n  private wordPiece(word: string): string[] {\n    if (this.vocab.has(word)) {\n      return [word];\n    }\n    \n    const tokens: string[] = [];\n    let start = 0;\n    \n    while (start < word.length) {\n      let end = word.length;\n      let curSubstr: string | null = null;\n      \n      while (start < end) {\n        let substr = word.slice(start, end);\n        if (start > 0) {\n          substr = this.continuingSubwordPrefix + substr;\n        }\n        \n        if (this.vocab.has(substr)) {\n          curSubstr = substr;\n          break;\n        }\n        end--;\n      }\n      \n      if (curSubstr === null) {\n        tokens.push(this.unkToken);\n        start++;\n      } else {\n        tokens.push(curSubstr);\n        start = end;\n      }\n    }\n    \n    return tokens;\n  }\n\n  /**\n   * Tokenize a single word\n   */\n  private tokenizeWord(word: string): string[] {\n    // Check added tokens first\n    if (this.addedTokens.has(word)) {\n      return [word];\n    }\n    \n    switch (this.modelType) {\n      case 'BPE': {\n        // Convert to byte representation\n        const byteStr = this.textToBytes(word);\n        return this.bpe(byteStr);\n      }\n      case 'WordPiece':\n        return this.wordPiece(word);\n      case 'Unigram':\n        return this.unigramTokenize(word);\n      default:\n        return this.vocab.has(word) ? [word] : [this.unkToken];\n    }\n  }\n\n  /**\n   * Greedy longest-match tokenizer for SentencePiece Unigram models.\n   * Adds the U+2581 (▁) word-start prefix expected by SPM-based models.\n   */\n  private unigramTokenize(word: string): string[] {\n    // SentencePiece prepends ▁ to words that follow a space (i.e. the\n    // tokenizer receives individual words, so all of them get the prefix).\n    const prefixedWord = '\\u2581' + word;\n    const tokens: string[] = [];\n    let start = 0;\n    const text = prefixedWord;\n\n    while (start < text.length) {\n      let end = text.length;\n      let found = false;\n      // Greedy longest-match scan\n      while (end > start) {\n        const sub = text.slice(start, end);\n        if (this.vocab.has(sub)) {\n          tokens.push(sub);\n          start = end;\n          found = true;\n          break;\n        }\n        end--;\n      }\n      if (!found) {\n        // Emit the single character (or unk if it's not in vocab either)\n        const ch = text[start]!;\n        tokens.push(this.vocab.has(ch) ? ch : this.unkToken);\n        start++;\n      }\n    }\n\n    return tokens.length > 0 ? tokens : [this.unkToken];\n  }\n\n  /**\n   * Main tokenization\n   */\n  private tokenize(text: string): string[] {\n    // Normalize\n    const normalized = this.normalize(text);\n    \n    // Check for added tokens (special tokens)\n    const tokens: string[] = [];\n    let remaining = normalized;\n    \n    // Sort added tokens by length (longest first) for greedy matching\n    const sortedAddedTokens = Array.from(this.addedTokens.keys())\n      .sort((a, b) => b.length - a.length);\n    \n    // Split by added tokens\n    for (const addedToken of sortedAddedTokens) {\n      if (remaining.includes(addedToken)) {\n        const parts = remaining.split(addedToken);\n        const newRemaining: string[] = [];\n        \n        for (let i = 0; i < parts.length; i++) {\n          if (parts[i]) {\n            newRemaining.push(parts[i]!);\n          }\n          if (i < parts.length - 1) {\n            tokens.push(addedToken);\n          }\n        }\n        \n        remaining = newRemaining.join(' ');\n      }\n    }\n    \n    // Pre-tokenize remaining text\n    if (remaining.trim()) {\n      const words = this.preTokenize(remaining);\n      \n      for (const word of words) {\n        if (!word) continue;\n        const wordTokens = this.tokenizeWord(word);\n        tokens.push(...wordTokens);\n      }\n    }\n    \n    return tokens;\n  }\n\n  /**\n   * Convert tokens to IDs\n   */\n  private convertTokensToIds(tokens: string[]): number[] {\n    return tokens.map(token => {\n      // Check added tokens first\n      const addedId = this.addedTokens.get(token);\n      if (addedId !== undefined) return addedId;\n      \n      // Check vocabulary\n      const vocabId = this.vocab.get(token);\n      if (vocabId !== undefined) return vocabId;\n      \n      // Return UNK\n      return this.unkTokenId;\n    });\n  }\n\n  /**\n   * Convert IDs to tokens\n   */\n  private convertIdsToTokens(ids: number[]): string[] {\n    return ids.map(id => this.reverseVocab.get(id) ?? this.unkToken);\n  }\n\n  /**\n   * Apply post-processing (add special tokens)\n   */\n  private postProcess(\n    ids: number[],\n    pairIds?: number[]\n  ): { ids: number[]; typeIds: number[] } {\n    if (!this.postProcessor) {\n      // Default: [CLS] tokens [SEP] or [CLS] tokens [SEP] pair [SEP]\n      const result: number[] = [];\n      const typeIds: number[] = [];\n      \n      if (this.clsTokenId !== undefined) {\n        result.push(this.clsTokenId);\n        typeIds.push(0);\n      }\n      \n      result.push(...ids);\n      typeIds.push(...ids.map(() => 0));\n      \n      if (this.sepTokenId !== undefined) {\n        result.push(this.sepTokenId);\n        typeIds.push(0);\n      }\n      \n      if (pairIds) {\n        result.push(...pairIds);\n        typeIds.push(...pairIds.map(() => 1));\n        \n        if (this.sepTokenId !== undefined) {\n          result.push(this.sepTokenId);\n          typeIds.push(1);\n        }\n      }\n      \n      return { ids: result, typeIds };\n    }\n    \n    // Use post-processor config\n    const template = pairIds ? this.postProcessor.pair : this.postProcessor.single;\n    if (!template) {\n      return { ids, typeIds: ids.map(() => 0) };\n    }\n    \n    const result: number[] = [];\n    const typeIds: number[] = [];\n    \n    for (const item of template) {\n      if ('SpecialToken' in item) {\n        const specialToken = this.postProcessor.special_tokens?.[item.SpecialToken.id];\n        if (specialToken) {\n          result.push(...specialToken.ids);\n          typeIds.push(...specialToken.ids.map(() => item.SpecialToken.type_id));\n        }\n      } else if ('Sequence' in item) {\n        const seqIds = item.Sequence.id === 'A' ? ids : pairIds ?? [];\n        result.push(...seqIds);\n        typeIds.push(...seqIds.map(() => item.Sequence.type_id));\n      }\n    }\n    \n    return { ids: result, typeIds };\n  }\n\n  /**\n   * Encode text\n   */\n  encode(text: string, options: TokenizerOptions = {}): TokenizedOutput {\n    const {\n      addSpecialTokens = true,\n      maxLength = this.maxLength,\n      padding = 'max_length',\n      truncation = true,\n      returnAttentionMask = true,\n      returnTokenTypeIds = false,\n      textPair,\n    } = options;\n    \n    // Tokenize\n    const tokens = this.tokenize(text);\n    let inputIds = this.convertTokensToIds(tokens);\n    \n    // Tokenize pair if provided\n    let pairIds: number[] | undefined;\n    if (textPair) {\n      const pairTokens = this.tokenize(textPair);\n      pairIds = this.convertTokensToIds(pairTokens);\n    }\n    \n    // Post-process (add special tokens)\n    let tokenTypeIds: number[] | undefined;\n    if (addSpecialTokens) {\n      const processed = this.postProcess(inputIds, pairIds);\n      inputIds = processed.ids;\n      if (returnTokenTypeIds) {\n        tokenTypeIds = processed.typeIds;\n      }\n    } else if (pairIds) {\n      inputIds = [...inputIds, ...pairIds];\n      if (returnTokenTypeIds) {\n        tokenTypeIds = [...inputIds.map(() => 0), ...pairIds.map(() => 1)];\n      }\n    }\n    \n    // Truncate\n    if (truncation && inputIds.length > maxLength) {\n      inputIds = inputIds.slice(0, maxLength);\n      if (tokenTypeIds) {\n        tokenTypeIds = tokenTypeIds.slice(0, maxLength);\n      }\n    }\n    \n    // Create attention mask\n    let attentionMask: number[] = [];\n    if (returnAttentionMask) {\n      attentionMask = inputIds.map(() => 1);\n    }\n    \n    // Padding\n    if (padding === 'max_length' && inputIds.length < maxLength) {\n      const padLength = maxLength - inputIds.length;\n      inputIds = [...inputIds, ...new Array(padLength).fill(this.padTokenId) as number[]];\n      if (returnAttentionMask) {\n        attentionMask = [...attentionMask, ...new Array(padLength).fill(0) as number[]];\n      }\n      if (tokenTypeIds) {\n        tokenTypeIds = [...tokenTypeIds, ...new Array(padLength).fill(0) as number[]];\n      }\n    }\n    \n    const result: TokenizedOutput = {\n      inputIds,\n      attentionMask,\n    };\n    \n    if (returnTokenTypeIds && tokenTypeIds) {\n      result.tokenTypeIds = tokenTypeIds;\n    }\n    \n    return result;\n  }\n\n  /**\n   * Batch encode\n   */\n  encodeBatch(texts: string[], options: TokenizerOptions = {}): TokenizedOutput[] {\n    // For 'longest' padding, first encode all without padding\n    if (options.padding === 'longest') {\n      const encodings = texts.map(t => this.encode(t, { ...options, padding: 'do_not_pad' }));\n      const maxLen = Math.max(...encodings.map(e => e.inputIds.length));\n      return texts.map(t => this.encode(t, { ...options, maxLength: maxLen, padding: 'max_length' }));\n    }\n    \n    return texts.map(t => this.encode(t, options));\n  }\n\n  /**\n   * Decode IDs to text\n   */\n  decode(ids: number[], skipSpecialTokens = true): string {\n    let tokens = this.convertIdsToTokens(ids);\n\n    if (skipSpecialTokens) {\n      tokens = tokens.filter(t => !this.specialTokens.has(t));\n    }\n\n    if (this.modelType === 'BPE') {\n      // BPE: byte-level encoding, join raw and decode bytes\n      return this.bytesToText(tokens.join('')).replace(/\\s+/g, ' ').trim();\n    }\n\n    if (this.modelType === 'WordPiece') {\n      // WordPiece: tokens starting with continuingSubwordPrefix (##) are\n      // subword continuations and must be appended to the previous word\n      // WITHOUT a space. All other tokens are word-starts and get a space.\n      const prefix = this.continuingSubwordPrefix; // '##'\n      const words: string[] = [];\n      for (const token of tokens) {\n        if (token.startsWith(prefix)) {\n          if (words.length > 0) {\n            words[words.length - 1] += token.slice(prefix.length);\n          } else {\n            words.push(token.slice(prefix.length));\n          }\n        } else {\n          words.push(token);\n        }\n      }\n      return words.join(' ').replace(/\\s+/g, ' ').trim();\n    }\n\n    if (this.modelType === 'Unigram') {\n      // SentencePiece: ▁ marks word boundaries (replaces the leading space)\n      return tokens\n        .join('')\n        .replace(/\\u2581/g, ' ')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    }\n\n    // Default: space-join\n    return tokens.join(' ').replace(/\\s+/g, ' ').trim();\n  }\n\n  /**\n   * Decode batch\n   */\n  decodeBatch(batchIds: number[][], skipSpecialTokens = true): string[] {\n    return batchIds.map(ids => this.decode(ids, skipSpecialTokens));\n  }\n\n  /**\n   * Get vocabulary size\n   */\n  get vocabSize(): number {\n    return this.vocab.size + this.addedTokens.size;\n  }\n\n  /**\n   * Get special token IDs\n   */\n  getSpecialTokenIds(): {\n    padTokenId: number;\n    unkTokenId: number;\n    clsTokenId?: number;\n    sepTokenId?: number;\n    maskTokenId?: number;\n    bosTokenId?: number;\n    eosTokenId?: number;\n  } {\n    return {\n      padTokenId: this.padTokenId,\n      unkTokenId: this.unkTokenId,\n      clsTokenId: this.clsTokenId,\n      sepTokenId: this.sepTokenId,\n      maskTokenId: this.maskTokenId,\n      bosTokenId: this.bosTokenId,\n      eosTokenId: this.eosTokenId,\n    };\n  }\n\n  /**\n   * Get config\n   */\n  getConfig(): TokenizerConfig {\n    return {\n      vocabSize: this.vocabSize,\n      maxLength: this.maxLength,\n      padTokenId: this.padTokenId,\n      unkTokenId: this.unkTokenId,\n      clsTokenId: this.clsTokenId,\n      sepTokenId: this.sepTokenId,\n      maskTokenId: this.maskTokenId,\n      bosTokenId: this.bosTokenId,\n      eosTokenId: this.eosTokenId,\n    };\n  }\n\n  /**\n   * Check if token is special\n   */\n  isSpecialToken(token: string): boolean {\n    return this.specialTokens.has(token);\n  }\n\n  /**\n   * Get token ID\n   */\n  getTokenId(token: string): number | undefined {\n    return this.addedTokens.get(token) ?? this.vocab.get(token);\n  }\n\n  /**\n   * Get token from ID\n   */\n  getToken(id: number): string | undefined {\n    return this.reverseVocab.get(id);\n  }\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create a basic English tokenizer (for testing)\n */\nexport function createBasicTokenizer(): Tokenizer {\n  const tokenizer = new Tokenizer();\n  return tokenizer;\n}\n\n/**\n * Load tokenizer from URL\n */\nexport async function loadTokenizer(url: string): Promise<Tokenizer> {\n  return Tokenizer.fromUrl(url);\n}\n\n/**\n * Load tokenizer from HuggingFace Hub\n */\nexport async function loadTokenizerFromHub(\n  modelId: string,\n  options?: { revision?: string }\n): Promise<Tokenizer> {\n  return Tokenizer.fromHuggingFace(modelId, options);\n}\n\n"
  },
  {
    "path": "tests/e2e/browser.spec.ts",
    "content": "/**\n * Playwright E2E tests for edgeFlow.js in a real browser environment.\n *\n * Requires the demo server to be running (handled by playwright.config.ts webServer).\n * Run with: npm run test:e2e\n */\nimport { test, expect } from '@playwright/test';\n\ntest.describe('edgeFlow.js Browser E2E', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('/');\n    // Wait for the edgeFlow global to be ready\n    await page.waitForFunction(() => typeof (window as any).edgeFlow !== 'undefined', {\n      timeout: 10_000,\n    }).catch(() => {\n      // If the global isn't exposed, tests below will fail with clear messages\n    });\n  });\n\n  test('page loads successfully', async ({ page }) => {\n    await expect(page).toHaveTitle(/.*/);\n  });\n\n  test('edgeFlow global is exposed', async ({ page }) => {\n    const hasGlobal = await page.evaluate(() => typeof (window as any).edgeFlow !== 'undefined');\n    expect(hasGlobal).toBe(true);\n  });\n\n  test('tensor creation works in browser', async ({ page }) => {\n    const shape = await page.evaluate(() => {\n      const ef = (window as any).edgeFlow;\n      if (!ef?.tensor) return null;\n      const t = ef.tensor([1, 2, 3, 4], [2, 2]);\n      return t.shape;\n    });\n\n    if (shape !== null) {\n      expect(shape).toEqual([2, 2]);\n    }\n  });\n\n  test('memory stats are accessible', async ({ page }) => {\n    const stats = await page.evaluate(() => {\n      const ef = (window as any).edgeFlow;\n      if (!ef?.getMemoryStats) return null;\n      return ef.getMemoryStats();\n    });\n\n    if (stats !== null) {\n      expect(stats).toHaveProperty('allocated');\n      expect(stats).toHaveProperty('tensorCount');\n    }\n  });\n\n  test('pipeline factory is callable', async ({ page }) => {\n    const hasPipeline = await page.evaluate(() => {\n      const ef = (window as any).edgeFlow;\n      return typeof ef?.pipeline === 'function';\n    });\n\n    expect(hasPipeline).toBe(true);\n  });\n});\n"
  },
  {
    "path": "tests/e2e/browser.test.ts",
    "content": "/**\n * E2E Browser Tests\n * \n * These tests verify that edgeFlow.js works correctly in a browser environment.\n * Run with: npm run test:e2e\n * \n * Note: These tests require a browser environment (Playwright)\n * For now, they serve as documentation for browser behavior\n */\nimport { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';\n\n// Skip these tests in non-browser environment\nconst isBrowser = typeof window !== 'undefined';\nconst describeIf = isBrowser ? describe : describe.skip;\n\ndescribeIf('Browser E2E Tests', () => {\n  describe('Global API', () => {\n    it('should expose edgeFlow global', () => {\n      // @ts-ignore\n      expect(window.edgeFlow).toBeDefined();\n    });\n\n    it('should have pipeline function', () => {\n      // @ts-ignore\n      expect(typeof window.edgeFlow.pipeline).toBe('function');\n    });\n\n    it('should have tensor function', () => {\n      // @ts-ignore\n      expect(typeof window.edgeFlow.tensor).toBe('function');\n    });\n  });\n\n  describe('Tensor Operations in Browser', () => {\n    it('should create tensors', () => {\n      // @ts-ignore\n      const tensor = window.edgeFlow.tensor([1, 2, 3, 4], [2, 2]);\n      expect(tensor.shape).toEqual([2, 2]);\n    });\n\n    it('should perform math operations', () => {\n      // @ts-ignore\n      const a = window.edgeFlow.tensor([1, 2], [2]);\n      // @ts-ignore\n      const b = window.edgeFlow.tensor([3, 4], [2]);\n      const result = a.add(b);\n      expect(result.toArray()).toEqual([4, 6]);\n    });\n\n    it('should compute softmax', () => {\n      // @ts-ignore\n      const tensor = window.edgeFlow.tensor([1, 2, 3], [3]);\n      const result = tensor.softmax();\n      const sum = result.sum();\n      expect(Math.abs(sum - 1)).toBeLessThan(0.001);\n    });\n  });\n\n  describe('Runtime Detection', () => {\n    it('should detect available runtimes', async () => {\n      // @ts-ignore\n      const capabilities = await window.edgeFlow.detectCapabilities();\n      \n      expect(capabilities).toHaveProperty('webgpu');\n      expect(capabilities).toHaveProperty('webnn');\n      expect(capabilities).toHaveProperty('wasm');\n    });\n\n    it('should have WASM support', async () => {\n      // @ts-ignore\n      const capabilities = await window.edgeFlow.detectCapabilities();\n      expect(capabilities.wasm).toBe(true);\n    });\n  });\n\n  describe('Memory Management', () => {\n    it('should track memory usage', () => {\n      // @ts-ignore\n      const stats = window.edgeFlow.getMemoryStats();\n      \n      expect(stats).toHaveProperty('allocated');\n      expect(stats).toHaveProperty('tensorCount');\n    });\n\n    it('should dispose tensors', () => {\n      // @ts-ignore\n      const tensor = window.edgeFlow.tensor([1, 2, 3], [3]);\n      tensor.dispose();\n      \n      expect(tensor.isDisposed).toBe(true);\n    });\n  });\n\n  describe('IndexedDB Caching', () => {\n    it('should cache models in IndexedDB', async () => {\n      // @ts-ignore\n      const stats = await window.edgeFlow.getModelCacheStats();\n      \n      expect(stats).toHaveProperty('models');\n      expect(stats).toHaveProperty('totalSize');\n    });\n\n    it('should check if model is cached', async () => {\n      // @ts-ignore\n      const isCached = await window.edgeFlow.isModelCached('https://example.com/model.onnx');\n      \n      expect(typeof isCached).toBe('boolean');\n    });\n  });\n});\n\n/**\n * Tests that require actual model loading\n * These should be run manually or in CI with proper setup\n */\ndescribe.skip('Model Loading E2E', () => {\n  const MODEL_URL = 'https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/onnx/model_quantized.onnx';\n\n  it('should load model from HuggingFace', async () => {\n    // @ts-ignore\n    const model = await window.edgeFlow.loadModel(MODEL_URL);\n    \n    expect(model).toHaveProperty('id');\n    expect(model).toHaveProperty('metadata');\n    \n    model.dispose();\n  }, 60000);\n\n  it('should run inference', async () => {\n    // @ts-ignore\n    const pipeline = await window.edgeFlow.pipeline('text-classification');\n    \n    const result = await pipeline.run('I love this product!');\n    \n    expect(result).toHaveProperty('label');\n    expect(result).toHaveProperty('score');\n    \n    pipeline.dispose();\n  }, 60000);\n\n  it('should handle batch processing', async () => {\n    // @ts-ignore\n    const pipeline = await window.edgeFlow.pipeline('text-classification');\n    \n    const results = await pipeline.run([\n      'Great product!',\n      'Terrible service.',\n      'Just okay.',\n    ]);\n    \n    expect(results.length).toBe(3);\n    \n    pipeline.dispose();\n  }, 60000);\n});\n\n/**\n * Performance Tests\n */\ndescribe.skip('Performance E2E', () => {\n  it('should complete inference within time limit', async () => {\n    // @ts-ignore\n    const pipeline = await window.edgeFlow.pipeline('text-classification');\n    \n    const start = performance.now();\n    await pipeline.run('Test text');\n    const duration = performance.now() - start;\n    \n    // Should complete within 1 second after warm-up\n    expect(duration).toBeLessThan(1000);\n    \n    pipeline.dispose();\n  }, 60000);\n\n  it('should handle concurrent inference', async () => {\n    // @ts-ignore\n    const pipeline = await window.edgeFlow.pipeline('text-classification');\n    \n    const start = performance.now();\n    await Promise.all([\n      pipeline.run('Text 1'),\n      pipeline.run('Text 2'),\n      pipeline.run('Text 3'),\n      pipeline.run('Text 4'),\n    ]);\n    const duration = performance.now() - start;\n    \n    // Concurrent should be faster than 4x serial\n    console.log(`Concurrent inference: ${duration}ms`);\n    \n    pipeline.dispose();\n  }, 60000);\n});\n"
  },
  {
    "path": "tests/e2e/localai-10s-check.spec.ts",
    "content": "/**\n * E2E test: Check model dot status at 10s and 60s after Load Models\n * Run with: npx playwright test tests/e2e/localai-10s-check.spec.ts --config=playwright.localai.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-10s-screenshots';\n\ntest.describe('LocalAI - 10s Dot Status Check', () => {\n  test('check model dots at 10s and 60s', async ({ page }) => {\n    const consoleErrors: string[] = [];\n\n    page.on('console', (msg) => {\n      if (msg.type() === 'error') {\n        consoleErrors.push(msg.text());\n        console.log(`[CONSOLE ERROR] ${msg.text()}`);\n      }\n    });\n\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // 1. Initial screenshot\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/00-initial.png`, fullPage: true });\n\n    // 2. Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // 3. Wait exactly 10 seconds, screenshot, report dot status\n    await page.waitForTimeout(10_000);\n    const statusAt10s = await page.evaluate(() => {\n      const results: Record<string, string> = {};\n      const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n      const allText = document.body.innerText;\n      for (const name of modelNames) {\n        const nameIdx = allText.indexOf(name);\n        if (nameIdx >= 0) {\n          const snippet = allText.slice(nameIdx, nameIdx + 150);\n          if (snippet.includes('READY')) results[name] = 'READY';\n          else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n          else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n          else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n          else results[name] = 'UNKNOWN';\n        } else results[name] = 'NOT_FOUND';\n      }\n      return results;\n    });\n\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/01-at-10s.png`, fullPage: true });\n\n    console.log('\\n=== DOT STATUS AT 10 SECONDS ===');\n    console.log('all-MiniLM-L6-v2:', statusAt10s['all-MiniLM-L6-v2'], '(grey=IDLE, orange-amber pulsing=LOADING, green=READY, red=ERROR)');\n    console.log('distilbart-mnli:', statusAt10s['distilbart-mnli']);\n    console.log('distilbert-squad:', statusAt10s['distilbert-squad']);\n\n    // 4. Wait until 60 seconds total (50 more seconds)\n    await page.waitForTimeout(50_000);\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/02-at-60s.png`, fullPage: true });\n\n    // 5. Console errors\n    console.log('\\n=== CONSOLE ERRORS ===');\n    consoleErrors.forEach((e, i) => console.log(`${i + 1}. ${e}`));\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-clear-cache-load.spec.ts",
    "content": "/**\n * E2E test: Clear IndexedDB cache, then Load Models and check for LOADING state\n * Run with: npx playwright test tests/e2e/localai-clear-cache-load.spec.ts --config=playwright.network.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-clear-cache-screenshots';\n\ntest.describe('LocalAI - Clear Cache Then Load', () => {\n  test('clear IndexedDB, load models, check LOADING state and HuggingFace requests', async ({\n    page,\n  }) => {\n    const consoleMessages: Array<{ type: string; text: string }> = [];\n    const allRequests: Array<{ url: string; status: number }> = [];\n\n    page.on('console', (msg) => {\n      consoleMessages.push({ type: msg.type(), text: msg.text() });\n    });\n\n    page.on('response', (response) => {\n      allRequests.push({ url: response.url(), status: response.status() });\n    });\n\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // Clear IndexedDB - delete common cache database names\n    await page.evaluate(() => {\n      const names = ['edgeflow-cache', 'model-cache', 'huggingface-cache', 'cache', 'opfs', 'ort-cache', 'default'];\n      for (const name of names) {\n        try {\n          indexedDB.deleteDatabase(name);\n        } catch (_) {}\n      }\n    });\n    await page.waitForTimeout(1000);\n\n    // Clear request/console capture for fresh Load Models run\n    allRequests.length = 0;\n    consoleMessages.length = 0;\n\n    // Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // After 5 seconds - screenshot and report dot status\n    await page.waitForTimeout(5_000);\n    const statusAt5s = await page.evaluate(() => {\n      const results: Record<string, string> = {};\n      const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n      const allText = document.body.innerText;\n      for (const name of modelNames) {\n        const nameIdx = allText.indexOf(name);\n        if (nameIdx >= 0) {\n          const snippet = allText.slice(nameIdx, nameIdx + 150);\n          if (snippet.includes('READY')) results[name] = 'READY';\n          else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n          else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n          else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n          else results[name] = 'UNKNOWN';\n        } else results[name] = 'NOT_FOUND';\n      }\n      return results;\n    });\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/01-at-5s.png`, fullPage: true });\n\n    console.log('\\n=== DOT STATUS AT 5 SECONDS ===');\n    console.log('all-MiniLM-L6-v2:', statusAt5s['all-MiniLM-L6-v2'], '(grey=IDLE, orange=LOADING, green=READY, red=ERROR)');\n    console.log('distilbart-mnli:', statusAt5s['distilbart-mnli']);\n    console.log('distilbert-squad:', statusAt5s['distilbert-squad']);\n\n    // After 30 seconds total - another screenshot\n    await page.waitForTimeout(25_000);\n    const statusAt30s = await page.evaluate(() => {\n      const results: Record<string, string> = {};\n      const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n      const allText = document.body.innerText;\n      for (const name of modelNames) {\n        const nameIdx = allText.indexOf(name);\n        if (nameIdx >= 0) {\n          const snippet = allText.slice(nameIdx, nameIdx + 150);\n          if (snippet.includes('READY')) results[name] = 'READY';\n          else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n          else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n          else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n          else results[name] = 'UNKNOWN';\n        } else results[name] = 'NOT_FOUND';\n      }\n      return results;\n    });\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/02-at-30s.png`, fullPage: true });\n\n    console.log('\\n=== DOT STATUS AT 30 SECONDS ===');\n    console.log('all-MiniLM-L6-v2:', statusAt30s['all-MiniLM-L6-v2']);\n    console.log('distilbart-mnli:', statusAt30s['distilbart-mnli']);\n    console.log('distilbert-squad:', statusAt30s['distilbert-squad']);\n\n    // Check for huggingface.co requests\n    const hfRequests = allRequests.filter((r) =>\n      r.url.toLowerCase().includes('huggingface.co')\n    );\n\n    console.log('\\n=== REQUESTS TO HUGGINGFACE.CO ===');\n    if (hfRequests.length === 0) {\n      console.log('NONE - No requests were made to huggingface.co');\n    } else {\n      hfRequests.forEach((r, i) => console.log(`${i + 1}. [${r.status}] ${r.url}`));\n    }\n\n    // Check console for download/loading messages\n    const downloadMessages = consoleMessages.filter(\n      (m) =>\n        m.text.toLowerCase().includes('download') ||\n        m.text.toLowerCase().includes('loading') ||\n        m.text.toLowerCase().includes('fetch') ||\n        m.text.toLowerCase().includes('huggingface')\n    );\n\n    console.log('\\n=== CONSOLE: download/loading/fetch/huggingface messages ===');\n    if (downloadMessages.length === 0) {\n      console.log('None');\n    } else {\n      downloadMessages.forEach((m, i) => console.log(`${i + 1}. [${m.type}] ${m.text}`));\n    }\n\n    // Console errors\n    const errors = consoleMessages.filter((m) => m.type === 'error');\n    console.log('\\n=== CONSOLE ERRORS ===');\n    errors.forEach((e, i) => console.log(`${i + 1}. ${e.text}`));\n\n    console.log('\\n=== KEY QUESTION ===');\n    const anyLoading = Object.values(statusAt5s).includes('LOADING') || Object.values(statusAt30s).includes('LOADING');\n    console.log('Do models enter LOADING state (orange/amber pulsing)?', anyLoading ? 'YES' : 'NO');\n    console.log('Are there requests to huggingface.co?', hfRequests.length > 0 ? 'YES' : 'NO');\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-knowledge-base.spec.ts",
    "content": "/**\n * E2E test for LocalAI Knowledge Base app at http://localhost:5174/\n * Run with: npx playwright test tests/e2e/localai-knowledge-base.spec.ts\n * Ensure the app is running on port 5174 before running.\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5174/';\nconst SCREENSHOT_DIR = 'test-results/localai-screenshots';\n\ntest.describe('LocalAI Knowledge Base App', () => {\n  test.beforeEach(async ({ page }) => {\n    // Capture console messages for error reporting\n    page.on('console', (msg) => {\n      const type = msg.type();\n      const text = msg.text();\n      if (type === 'error') {\n        console.log(`[CONSOLE ERROR] ${text}`);\n      }\n    });\n  });\n\n  test('initial page load and UI elements', async ({ page }) => {\n    // Navigate to the app\n    const response = await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n    expect(response?.status()).toBe(200);\n\n    // Take initial screenshot\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/01-initial-page.png`, fullPage: true });\n\n    // Check for sidebar\n    const sidebar = page.locator('[class*=\"sidebar\"], aside, nav, [data-testid*=\"sidebar\"]').first();\n    const sidebarVisible = await sidebar.isVisible().catch(() => false);\n\n    // Check for model status panel\n    const modelStatus = page.locator('text=/model|Model/i').first();\n    const modelStatusVisible = await modelStatus.isVisible().catch(() => false);\n\n    // Check for upload zone\n    const uploadZone = page.locator('input[type=\"file\"], [class*=\"upload\"], [class*=\"dropzone\"], [role=\"button\"]:has-text(\"upload\"), [role=\"button\"]:has-text(\"Upload\")').first();\n    const uploadZoneVisible = await uploadZone.isVisible().catch(() => false);\n\n    // Check for search bar\n    const searchBar = page.locator('input[type=\"search\"], input[placeholder*=\"search\" i], input[placeholder*=\"Search\" i], [class*=\"search\"] input').first();\n    const searchBarVisible = await searchBar.isVisible().catch(() => false);\n\n    // Check for Load Models button\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\"), [role=\"button\"]:has-text(\"Load Models\")').first();\n    const loadModelsVisible = await loadModelsBtn.isVisible().catch(() => false);\n\n    // Log findings\n    console.log('UI Elements found:');\n    console.log('- Sidebar visible:', sidebarVisible);\n    console.log('- Model status panel visible:', modelStatusVisible);\n    console.log('- Upload zone visible:', uploadZoneVisible);\n    console.log('- Search bar visible:', searchBarVisible);\n    console.log('- Load Models button visible:', loadModelsVisible);\n\n    // Click Load Models if present\n    if (loadModelsVisible) {\n      await loadModelsBtn.click();\n      await page.waitForTimeout(2000); // Wait for models to load\n      await page.screenshot({ path: `${SCREENSHOT_DIR}/02-after-load-models.png`, fullPage: true });\n    } else {\n      await page.screenshot({ path: `${SCREENSHOT_DIR}/02-no-load-models-btn.png`, fullPage: true });\n    }\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-load-models.spec.ts",
    "content": "/**\n * E2E test for LocalAI Knowledge Base - model loading flow\n * Run with: npx playwright test tests/e2e/localai-load-models.spec.ts --config=playwright.localai.config.ts\n * Uses headed mode for WebGPU/WebNN support. Ensure app is running on port 5173.\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-load-screenshots';\nconst WAIT_SECONDS = 150;\nconst SCREENSHOT_INTERVAL_SEC = 30;\n\ntest.describe('LocalAI Knowledge Base - Model Loading', () => {\n  test('load models and capture loading progress', async ({ page }) => {\n    test.setTimeout(180_000); // 150s wait + buffer\n    const consoleErrors: string[] = [];\n\n    page.on('console', (msg) => {\n      const type = msg.type();\n      const text = msg.text();\n      if (type === 'error') {\n        consoleErrors.push(text);\n        console.log(`[CONSOLE ERROR] ${text}`);\n      } else if (type === 'warning' && (text.includes('WASM') || text.includes('404'))) {\n        consoleErrors.push(`[WARNING] ${text}`);\n        console.log(`[CONSOLE WARNING] ${text}`);\n      }\n    });\n\n    // Navigate to the app\n    const response = await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n    expect(response?.status()).toBe(200);\n\n    // 1. Initial screenshot\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/00-initial.png`, fullPage: true });\n\n    // 2. Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // 3 & 4. Wait up to 150 seconds, screenshot every 30 seconds, report state at each checkpoint\n    const startTime = Date.now();\n    let screenshotCount = 1;\n    while (Date.now() - startTime < WAIT_SECONDS * 1000) {\n      await page.waitForTimeout(SCREENSHOT_INTERVAL_SEC * 1000);\n      const elapsed = screenshotCount * SCREENSHOT_INTERVAL_SEC;\n      const checkpointStatuses = await page.evaluate(() => {\n        const results: Record<string, string> = {};\n        const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n        const allText = document.body.innerText;\n        for (const name of modelNames) {\n          const nameIdx = allText.indexOf(name);\n          if (nameIdx >= 0) {\n            const snippet = allText.slice(nameIdx, nameIdx + 150);\n            if (snippet.includes('READY')) results[name] = 'READY';\n            else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n            else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n            else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n            else results[name] = 'UNKNOWN';\n          } else results[name] = 'NOT_FOUND';\n        }\n        return results;\n      });\n      console.log(`\\n--- Checkpoint at ${elapsed}s ---`);\n      console.log(`all-MiniLM-L6-v2: ${checkpointStatuses['all-MiniLM-L6-v2']}`);\n      console.log(`distilbart-mnli: ${checkpointStatuses['distilbart-mnli']}`);\n      console.log(`distilbert-squad: ${checkpointStatuses['distilbert-squad']}`);\n      await page.screenshot({\n        path: `${SCREENSHOT_DIR}/${String(screenshotCount).padStart(2, '0')}-at-${elapsed}s.png`,\n        fullPage: true,\n      });\n      screenshotCount++;\n      if (screenshotCount * SCREENSHOT_INTERVAL_SEC >= WAIT_SECONDS) break;\n    }\n\n    // 5. Extract final model statuses from page text\n    const finalStatuses = await page.evaluate(() => {\n      const results: Record<string, string> = {};\n      const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n      const allText = document.body.innerText;\n      for (const name of modelNames) {\n        const nameIdx = allText.indexOf(name);\n        if (nameIdx >= 0) {\n          const snippet = allText.slice(nameIdx, nameIdx + 150);\n          if (snippet.includes('READY')) results[name] = 'READY';\n          else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n          else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n          else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n          else results[name] = 'UNKNOWN';\n        } else {\n          results[name] = 'NOT_FOUND';\n        }\n      }\n      return results;\n    });\n\n    // Final screenshot\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/99-final.png`, fullPage: true });\n\n    // Log results for report\n    console.log('\\n=== MODEL STATUS REPORT ===');\n    console.log('all-MiniLM-L6-v2:', finalStatuses['all-MiniLM-L6-v2'] || 'UNKNOWN');\n    console.log('distilbart-mnli:', finalStatuses['distilbart-mnli'] || 'UNKNOWN');\n    console.log('distilbert-squad:', finalStatuses['distilbert-squad'] || 'UNKNOWN');\n    console.log('\\n=== CONSOLE ERRORS ===');\n    consoleErrors.forEach((e, i) => console.log(`${i + 1}. ${e}`));\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-loading-check.spec.ts",
    "content": "/**\n * E2E test: Check for LOADING state and capture all console messages\n * Run with: npx playwright test tests/e2e/localai-loading-check.spec.ts --config=playwright.network.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-loading-screenshots';\n\ntest.describe('LocalAI - Loading State Check', () => {\n  test('check for LOADING state and console messages', async ({ page }) => {\n    const consoleMessages: Array<{ type: string; text: string }> = [];\n\n    page.on('console', (msg) => {\n      const type = msg.type();\n      const text = msg.text();\n      consoleMessages.push({ type, text });\n      if (type === 'error') console.log(`[ERROR] ${text}`);\n      else if (text.includes('cache') || text.includes('Evicting') || text.includes('download') || text.includes('progress') || text.includes('loaded'))\n        console.log(`[${type}] ${text}`);\n    });\n\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // 1. Initial screenshot\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/00-initial.png`, fullPage: true });\n\n    // 2. Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // 3. Within 5 seconds - screenshot and report\n    await page.waitForTimeout(5_000);\n    const statusAt5s = await page.evaluate(() => {\n      const results: Record<string, string> = {};\n      const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n      const allText = document.body.innerText;\n      for (const name of modelNames) {\n        const nameIdx = allText.indexOf(name);\n        if (nameIdx >= 0) {\n          const snippet = allText.slice(nameIdx, nameIdx + 150);\n          if (snippet.includes('READY')) results[name] = 'READY';\n          else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n          else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n          else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n          else results[name] = 'UNKNOWN';\n        } else results[name] = 'NOT_FOUND';\n      }\n      return results;\n    });\n    await page.screenshot({ path: `${SCREENSHOT_DIR}/01-at-5s.png`, fullPage: true });\n    console.log('\\n=== DOT STATUS AT 5 SECONDS ===');\n    console.log('all-MiniLM-L6-v2:', statusAt5s['all-MiniLM-L6-v2']);\n    console.log('distilbart-mnli:', statusAt5s['distilbart-mnli']);\n    console.log('distilbert-squad:', statusAt5s['distilbert-squad']);\n\n    // 4. Screenshots at 30s, 60s, 90s, 120s (wait from previous checkpoint)\n    const checkpoints = [30, 60, 90, 120];\n    let prevSec = 5;\n    for (const sec of checkpoints) {\n      await page.waitForTimeout((sec - prevSec) * 1000);\n      prevSec = sec;\n      const status = await page.evaluate(() => {\n        const results: Record<string, string> = {};\n        const modelNames = ['all-MiniLM-L6-v2', 'distilbart-mnli', 'distilbert-squad'];\n        const allText = document.body.innerText;\n        for (const name of modelNames) {\n          const nameIdx = allText.indexOf(name);\n          if (nameIdx >= 0) {\n            const snippet = allText.slice(nameIdx, nameIdx + 150);\n            if (snippet.includes('READY')) results[name] = 'READY';\n            else if (snippet.includes('LOADING')) results[name] = 'LOADING';\n            else if (snippet.includes('ERROR')) results[name] = 'ERROR';\n            else if (snippet.includes('IDLE')) results[name] = 'IDLE';\n            else results[name] = 'UNKNOWN';\n          } else results[name] = 'NOT_FOUND';\n        }\n        return results;\n      });\n      console.log(`\\n=== DOT STATUS AT ${sec}s ===`);\n      console.log('all-MiniLM-L6-v2:', status['all-MiniLM-L6-v2']);\n      console.log('distilbart-mnli:', status['distilbart-mnli']);\n      console.log('distilbert-squad:', status['distilbert-squad']);\n      await page.screenshot({ path: `${SCREENSHOT_DIR}/02-at-${sec}s.png`, fullPage: true });\n    }\n\n    // Report console messages of interest\n    const errors = consoleMessages.filter((m) => m.type === 'error');\n    const cacheEvictProgress = consoleMessages.filter(\n      (m) =>\n        m.text.toLowerCase().includes('cache') ||\n        m.text.toLowerCase().includes('evicting') ||\n        m.text.toLowerCase().includes('download') ||\n        m.text.toLowerCase().includes('progress') ||\n        m.text.toLowerCase().includes('loaded from')\n    );\n\n    console.log('\\n=== CONSOLE ERRORS ===');\n    errors.forEach((e, i) => console.log(`${i + 1}. ${e.text}`));\n    console.log('\\n=== CONSOLE: cache/Evicting/download/progress/loaded ===');\n    cacheEvictProgress.forEach((m, i) => console.log(`${i + 1}. [${m.type}] ${m.text}`));\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-network-audit.spec.ts",
    "content": "/**\n * E2E test: Audit ALL network requests when Load Models is clicked\n * Captures requests to HuggingFace, xethub, model URLs\n * Run with: npx playwright test tests/e2e/localai-network-audit.spec.ts --config=playwright.network.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-network-audit';\n\ninterface RequestRecord {\n  url: string;\n  method: string;\n  status?: number;\n  statusText?: string;\n  size?: number;\n  headers?: Record<string, string>;\n  error?: string;\n}\n\ntest.describe('LocalAI - Network Audit', () => {\n  test('capture all network requests when Load Models clicked', async ({ page }) => {\n    const allRequests: RequestRecord[] = [];\n    const requestMap = new Map<string, RequestRecord>();\n\n    // Capture all requests\n    page.on('request', (request) => {\n      const url = request.url();\n      if (!requestMap.has(url)) {\n        requestMap.set(url, {\n          url,\n          method: request.method(),\n        });\n      }\n    });\n\n    // Capture all responses\n    page.on('response', async (response) => {\n      const url = response.url();\n      const req = requestMap.get(url) || { url, method: 'GET' };\n      req.status = response.status();\n      req.statusText = response.statusText();\n      const cl = response.headers()['content-length'];\n      if (cl) req.size = parseInt(cl, 10);\n      else {\n        try {\n          const body = await response.body();\n          req.size = body?.length ?? 0;\n        } catch {\n          req.size = 0;\n        }\n      }\n      requestMap.set(url, req);\n    });\n\n    // Capture request failures\n    page.on('requestfailed', (request) => {\n      const url = request.url();\n      const req = requestMap.get(url) || { url, method: request.method() };\n      req.error = request.failure()?.errorText ?? 'unknown';\n      requestMap.set(url, req);\n    });\n\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // Open DevTools and Network tab BEFORE clicking\n    await page.keyboard.press('F12');\n    await page.waitForTimeout(500);\n    await page.keyboard.press(process.platform === 'darwin' ? 'Meta+Shift+E' : 'Control+Shift+E');\n    await page.waitForTimeout(500);\n\n    // Clear request map for fresh capture\n    requestMap.clear();\n\n    // Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // Wait 15 seconds\n    await page.waitForTimeout(15_000);\n\n    // Collect all requests\n    const requests = Array.from(requestMap.values());\n\n    // Filter for HuggingFace, xethub, model-related\n    const hfRequests = requests.filter(\n      (r) =>\n        r.url.includes('huggingface.co') ||\n        r.url.includes('xethub.hf.co') ||\n        r.url.includes('hf.co') ||\n        r.url.includes('models') ||\n        r.url.includes('.onnx') ||\n        r.url.includes('.bin') ||\n        r.url.includes('config.json') ||\n        r.url.includes('tokenizer')\n    );\n\n    // Screenshot Network tab\n    await page.screenshot({\n      path: `${SCREENSHOT_DIR}/network-all-requests.png`,\n      fullPage: true,\n    });\n\n    // Report\n    console.log('\\n=== ALL REQUESTS (HuggingFace/xethub/model-related) ===');\n    if (hfRequests.length === 0) {\n      console.log('NONE - No requests to HuggingFace, xethub.hf.co, or model URLs were made.');\n    } else {\n      hfRequests.forEach((r, i) => {\n        console.log(`\\n${i + 1}. ${r.url}`);\n        console.log(`   Method: ${r.method}`);\n        console.log(`   Status: ${r.status ?? 'N/A'} ${r.statusText ?? ''}`);\n        console.log(`   Size: ${r.size ?? 0} bytes`);\n        if (r.error) console.log(`   Error: ${r.error}`);\n      });\n    }\n\n    // Also list ALL requests for context\n    console.log('\\n=== ALL REQUEST URLS (first 50) ===');\n    requests.slice(0, 50).forEach((r, i) => {\n      const err = r.error ? ` [${r.error}]` : '';\n      const status = r.status ? ` [${r.status}]` : '';\n      const size = r.size !== undefined ? ` ${r.size}B` : '';\n      console.log(`${i + 1}. ${r.url}${status}${size}${err}`);\n    });\n\n    // Failed requests\n    const failed = requests.filter((r) => r.status && r.status >= 400);\n    console.log('\\n=== FAILED REQUESTS (4xx/5xx) ===');\n    failed.forEach((r, i) => {\n      console.log(`${i + 1}. [${r.status}] ${r.url} - ${r.error ?? ''}`);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-network-failures.spec.ts",
    "content": "/**\n * E2E test: Capture failed network requests when Load Models is clicked\n * Run with: npx playwright test tests/e2e/localai-network-failures.spec.ts --config=playwright.localai.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-network-screenshots';\n\ntest.describe('LocalAI - Failed Network Requests', () => {\n  test('capture failed requests when Load Models is clicked', async ({ page }) => {\n    const failedRequests: Array<{ url: string; status: number }> = [];\n\n    // Capture failed responses (4xx, 5xx)\n    page.on('response', (response) => {\n      const status = response.status();\n      if (status >= 400) {\n        const url = response.url();\n        failedRequests.push({ url, status });\n        console.log(`[FAILED] ${status} ${url}`);\n      }\n    });\n\n    // Navigate to the app\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // Open DevTools (F12) - Network tab\n    await page.keyboard.press('F12');\n    await page.waitForTimeout(500);\n\n    // Switch to Network tab: Cmd+Shift+E (Mac) or Ctrl+Shift+E (Win/Linux)\n    await page.keyboard.press(process.platform === 'darwin' ? 'Meta+Shift+E' : 'Control+Shift+E');\n    await page.waitForTimeout(500);\n\n    // Clear captured failures from initial page load - we want only Load Models failures\n    failedRequests.length = 0;\n\n    // Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // Wait 15 seconds\n    await page.waitForTimeout(15_000);\n\n    // Screenshot - may capture DevTools if visible\n    await page.screenshot({\n      path: `${SCREENSHOT_DIR}/network-failed-requests.png`,\n      fullPage: true,\n    });\n\n    // Log all failed request URLs\n    console.log('\\n=== FAILED REQUEST URLS (exact as captured) ===');\n    const uniqueUrls = [...new Map(failedRequests.map((f) => [f.url, f])).values()];\n    uniqueUrls.forEach(({ url, status }, i) => {\n      console.log(`${i + 1}. [${status}] ${url}`);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/e2e/localai-network-full.spec.ts",
    "content": "/**\n * E2E test: Capture ALL network requests when Load Models is clicked\n * Run with: npx playwright test tests/e2e/localai-network-full.spec.ts --config=playwright.network.config.ts\n */\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = 'http://localhost:5173/';\nconst SCREENSHOT_DIR = 'test-results/localai-network-full-screenshots';\n\ninterface NetworkEntry {\n  url: string;\n  status?: number;\n  statusText?: string;\n  size?: string;\n  contentLength?: number;\n  error?: string;\n  failed?: boolean;\n}\n\ntest.describe('LocalAI - Full Network Capture', () => {\n  test('capture all network requests when Load Models clicked', async ({ page }) => {\n    const allRequests: NetworkEntry[] = [];\n    const failedRequests: NetworkEntry[] = [];\n\n    // Capture all responses\n    page.on('response', async (response) => {\n      const url = response.url();\n      const status = response.status();\n      const headers = response.headers();\n      const contentLength = headers['content-length'];\n      let size: string | undefined;\n      if (contentLength) {\n        const bytes = parseInt(contentLength, 10);\n        size = bytes >= 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${bytes} B`;\n      } else {\n        size = '(no content-length)';\n      }\n      allRequests.push({\n        url,\n        status,\n        statusText: response.statusText(),\n        size,\n        contentLength: contentLength ? parseInt(contentLength, 10) : undefined,\n      });\n    });\n\n    // Capture failed requests\n    page.on('requestfailed', (request) => {\n      const failure = request.failure();\n      failedRequests.push({\n        url: request.url(),\n        error: failure?.errorText || 'Unknown',\n        failed: true,\n      });\n    });\n\n    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15_000 });\n\n    // Open DevTools, Network tab\n    await page.keyboard.press('F12');\n    await page.waitForTimeout(500);\n    await page.keyboard.press(process.platform === 'darwin' ? 'Meta+Shift+E' : 'Control+Shift+E');\n    await page.waitForTimeout(500);\n\n    // Clear previous captures - we want only Load Models requests\n    allRequests.length = 0;\n    failedRequests.length = 0;\n\n    // Click Load Models\n    const loadModelsBtn = page.locator('button:has-text(\"Load Models\")').first();\n    await expect(loadModelsBtn).toBeVisible({ timeout: 5000 });\n    await loadModelsBtn.click();\n\n    // Wait 15 seconds\n    await page.waitForTimeout(15_000);\n\n    // Screenshot Network tab\n    await page.screenshot({\n      path: `${SCREENSHOT_DIR}/network-tab-all-requests.png`,\n      fullPage: true,\n    });\n\n    // Filter for HuggingFace / model URLs\n    const hfUrls = ['huggingface.co', 'hf.co', 'xethub.hf.co', 'cdn-lfs', 'huggingface'];\n    const modelRequests = allRequests.filter((r) =>\n      hfUrls.some((h) => r.url.toLowerCase().includes(h))\n    );\n    const otherRequests = allRequests.filter(\n      (r) => !hfUrls.some((h) => r.url.toLowerCase().includes(h))\n    );\n\n    // Combine with failed\n    const allFailed = failedRequests;\n\n    console.log('\\n=== REQUESTS TO HUGGINGFACE / MODEL URLs ===');\n    if (modelRequests.length === 0) {\n      console.log('NONE - No requests were made to HuggingFace or model download URLs');\n    } else {\n      modelRequests.forEach((r, i) => {\n        console.log(`${i + 1}. URL: ${r.url}`);\n        console.log(`   Status: ${r.status || 'N/A'} ${r.statusText || ''}`);\n        console.log(`   Size: ${r.size || 'N/A'}`);\n      });\n    }\n\n    console.log('\\n=== FAILED REQUESTS (blocked/CORS/net::ERR_*) ===');\n    if (allFailed.length === 0) {\n      console.log('None');\n    } else {\n      allFailed.forEach((r, i) => {\n        console.log(`${i + 1}. URL: ${r.url}`);\n        console.log(`   Error: ${r.error}`);\n      });\n    }\n\n    console.log('\\n=== ALL REQUEST URLS (first 50) ===');\n    [...allRequests, ...allFailed.map((f) => ({ ...f, status: 0 }))].slice(0, 50).forEach((r, i) => {\n      const status = r.failed ? `FAILED: ${r.error}` : `${r.status}`;\n      const size = r.size || r.error || '';\n      console.log(`${i + 1}. [${status}] ${r.size || ''} ${r.url}`);\n    });\n\n    console.log(`\\nTotal requests captured: ${allRequests.length}`);\n    console.log(`Failed requests: ${allFailed.length}`);\n    console.log(`HuggingFace/model requests: ${modelRequests.length}`);\n  });\n});\n"
  },
  {
    "path": "tests/integration/pipeline.test.ts",
    "content": "/**\n * Integration tests for Pipelines\n * \n * These tests mock the ONNX runtime to return realistic tensor shapes\n * without requiring actual model files.\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EdgeFlowTensor, softmax } from '../../src/core/tensor';\n\n// ============================================================================\n// Mock ONNX Runtime helpers\n// ============================================================================\n\n/**\n * Create a mock ONNX session that returns tensors with the given output shapes\n */\nfunction createMockOutputs(shapes: { shape: number[]; fill?: number }[]): EdgeFlowTensor[] {\n  return shapes.map(({ shape, fill }) => {\n    const size = shape.reduce((a, b) => a * b, 1);\n    const data = new Float32Array(size).fill(fill ?? 0);\n    // Add some variation so softmax/argmax produce deterministic results\n    for (let i = 0; i < data.length; i++) {\n      data[i] = (data[i] ?? 0) + Math.sin(i * 0.7) * 2;\n    }\n    return new EdgeFlowTensor(data, shape, 'float32');\n  });\n}\n\n// ============================================================================\n// Text Classification Pipeline\n// ============================================================================\n\ndescribe('TextClassificationPipeline', () => {\n  it('should postprocess logits into label + score', () => {\n    // Simulate [batch=1, num_labels=2] logits (SST-2)\n    const logits = new EdgeFlowTensor(new Float32Array([1.5, -0.5]), [1, 2], 'float32');\n    const probs = softmax(logits, -1) as EdgeFlowTensor;\n    const probsArray = probs.toFloat32Array();\n\n    expect(probsArray.length).toBe(2);\n    // logit 1.5 should have higher probability than -0.5\n    expect(probsArray[0]).toBeGreaterThan(probsArray[1]!);\n    // Sum should be ~1\n    expect((probsArray[0] ?? 0) + (probsArray[1] ?? 0)).toBeCloseTo(1.0, 4);\n  });\n\n  it('should handle batch of logits', () => {\n    const labels = ['NEGATIVE', 'POSITIVE'];\n\n    // Batch of 3 texts: positive, negative, neutral\n    const texts = [\n      new EdgeFlowTensor(new Float32Array([-2.0, 3.0]), [1, 2], 'float32'), // POSITIVE\n      new EdgeFlowTensor(new Float32Array([3.0, -2.0]), [1, 2], 'float32'), // NEGATIVE\n      new EdgeFlowTensor(new Float32Array([0.1, 0.1]), [1, 2], 'float32'),  // ~neutral\n    ];\n\n    for (const logits of texts) {\n      const probs = softmax(logits, -1) as EdgeFlowTensor;\n      const arr = probs.toFloat32Array();\n      let maxIdx = 0;\n      if ((arr[1] ?? 0) > (arr[0] ?? 0)) maxIdx = 1;\n      expect(labels[maxIdx]).toBeDefined();\n    }\n\n    // Verify first is POSITIVE\n    const p0 = softmax(texts[0]!, -1).toFloat32Array();\n    expect(p0[1]).toBeGreaterThan(p0[0]!);\n\n    // Verify second is NEGATIVE\n    const p1 = softmax(texts[1]!, -1).toFloat32Array();\n    expect(p1[0]).toBeGreaterThan(p1[1]!);\n  });\n\n  it('should handle multi-class logits', () => {\n    const logits = new EdgeFlowTensor(\n      new Float32Array([0.1, 0.2, 5.0, 0.3, 0.1]),\n      [1, 5],\n      'float32'\n    );\n    const probs = softmax(logits, -1) as EdgeFlowTensor;\n    const arr = probs.toFloat32Array();\n\n    // Index 2 should dominate\n    let maxIdx = 0;\n    for (let i = 1; i < arr.length; i++) {\n      if ((arr[i] ?? 0) > (arr[maxIdx] ?? 0)) maxIdx = i;\n    }\n    expect(maxIdx).toBe(2);\n  });\n});\n\n// ============================================================================\n// Feature Extraction Pipeline\n// ============================================================================\n\ndescribe('FeatureExtractionPipeline', () => {\n  it('should produce embeddings of correct dimension', () => {\n    // Simulate [batch=1, seq_len=10, hidden=384] (MiniLM)\n    const seqLen = 10;\n    const hiddenDim = 384;\n    const data = new Float32Array(seqLen * hiddenDim);\n    for (let i = 0; i < data.length; i++) data[i] = Math.sin(i * 0.01);\n\n    const hiddenStates = new EdgeFlowTensor(data, [1, seqLen, hiddenDim], 'float32');\n\n    // Mean pooling\n    const flat = hiddenStates.toFloat32Array();\n    const result = new Float32Array(hiddenDim);\n    for (let i = 0; i < seqLen; i++) {\n      for (let j = 0; j < hiddenDim; j++) {\n        result[j] = (result[j] ?? 0) + (flat[i * hiddenDim + j] ?? 0) / seqLen;\n      }\n    }\n\n    expect(result.length).toBe(384);\n    // Verify non-zero\n    expect(result.some(v => v !== 0)).toBe(true);\n  });\n\n  it('should normalize embeddings to unit length', () => {\n    const vec = [0.3, 0.4, 0.5, 0.6];\n    let norm = 0;\n    for (const v of vec) norm += v * v;\n    norm = Math.sqrt(norm);\n    const normalized = vec.map(v => v / norm);\n\n    let normAfter = 0;\n    for (const v of normalized) normAfter += v * v;\n    expect(Math.sqrt(normAfter)).toBeCloseTo(1.0, 5);\n  });\n\n  it('should handle CLS pooling', () => {\n    const seqLen = 5;\n    const hiddenDim = 384;\n    const data = new Float32Array(seqLen * hiddenDim);\n    for (let i = 0; i < data.length; i++) data[i] = i * 0.001;\n\n    // CLS = first token\n    const cls = Array.from(data.slice(0, hiddenDim));\n    expect(cls.length).toBe(384);\n    expect(cls[0]).toBeCloseTo(0);\n    expect(cls[1]).toBeCloseTo(0.001, 4);\n  });\n});\n\n// ============================================================================\n// Image Classification Pipeline\n// ============================================================================\n\ndescribe('ImageClassificationPipeline', () => {\n  it('should postprocess logits to label', () => {\n    // Simulate [1, 1000] ImageNet logits\n    const logits = new Float32Array(1000);\n    logits[282] = 10.0; // tiger cat\n    const tensor = new EdgeFlowTensor(logits, [1, 1000], 'float32');\n    const probs = softmax(tensor, -1) as EdgeFlowTensor;\n    const arr = probs.toFloat32Array();\n\n    let maxIdx = 0;\n    for (let i = 1; i < arr.length; i++) {\n      if ((arr[i] ?? 0) > (arr[maxIdx] ?? 0)) maxIdx = i;\n    }\n    expect(maxIdx).toBe(282);\n  });\n});\n\n// ============================================================================\n// Object Detection Pipeline\n// ============================================================================\n\ndescribe('ObjectDetectionPipeline', () => {\n  it('should parse YOLO-style output', () => {\n    // Simulate [1, 3, 85] (3 boxes, 80 COCO classes + 5)\n    const numBoxes = 3;\n    const boxSize = 85;\n    const data = new Float32Array(numBoxes * boxSize);\n\n    // Box 0: high confidence person\n    data[0] = 0.5; data[1] = 0.5; data[2] = 0.2; data[3] = 0.3;\n    data[4] = 0.9; // objectness\n    data[5] = 0.95; // class 0 (person)\n\n    // Box 1: low objectness\n    const offset1 = boxSize;\n    data[offset1 + 4] = 0.1;\n\n    const threshold = 0.5;\n    const detections: Array<{ classId: number; score: number }> = [];\n\n    for (let i = 0; i < numBoxes; i++) {\n      const off = i * boxSize;\n      const objectness = data[off + 4] ?? 0;\n      if (objectness < threshold) continue;\n\n      let maxClass = 0;\n      let maxScore = 0;\n      for (let c = 0; c < 80; c++) {\n        const s = data[off + 5 + c] ?? 0;\n        if (s > maxScore) { maxScore = s; maxClass = c; }\n      }\n\n      detections.push({ classId: maxClass, score: objectness * maxScore });\n    }\n\n    expect(detections.length).toBe(1);\n    expect(detections[0]!.classId).toBe(0);\n    expect(detections[0]!.score).toBeCloseTo(0.9 * 0.95);\n  });\n\n  it('should filter by confidence threshold', () => {\n    const boxes = [\n      { score: 0.9, label: 'cat' },\n      { score: 0.3, label: 'dog' },\n      { score: 0.7, label: 'bird' },\n    ];\n\n    const filtered = boxes.filter(b => b.score >= 0.5);\n    expect(filtered.length).toBe(2);\n    expect(filtered.map(b => b.label)).toEqual(['cat', 'bird']);\n  });\n});\n\n// ============================================================================\n// Question Answering Pipeline\n// ============================================================================\n\ndescribe('QuestionAnsweringPipeline', () => {\n  it('should find best span from start/end logits', () => {\n    const seqLen = 10;\n    const startLogits = new Float32Array(seqLen);\n    const endLogits = new Float32Array(seqLen);\n\n    // Best answer at positions 3-5\n    startLogits[3] = 5.0;\n    endLogits[5] = 5.0;\n\n    const startProbs = softmax(new EdgeFlowTensor(startLogits, [seqLen], 'float32')).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(endLogits, [seqLen], 'float32')).toFloat32Array();\n\n    let bestStart = 0;\n    let bestEnd = 0;\n    let bestScore = 0;\n\n    for (let s = 0; s < seqLen; s++) {\n      for (let e = s; e < Math.min(s + 8, seqLen); e++) {\n        const score = (startProbs[s] ?? 0) * (endProbs[e] ?? 0);\n        if (score > bestScore) {\n          bestScore = score;\n          bestStart = s;\n          bestEnd = e;\n        }\n      }\n    }\n\n    expect(bestStart).toBe(3);\n    expect(bestEnd).toBe(5);\n    expect(bestScore).toBeGreaterThan(0);\n  });\n\n  it('should handle no-answer case (all low scores)', () => {\n    const seqLen = 10;\n    const startLogits = new Float32Array(seqLen).fill(0.01);\n    const endLogits = new Float32Array(seqLen).fill(0.01);\n\n    const startProbs = softmax(new EdgeFlowTensor(startLogits, [seqLen], 'float32')).toFloat32Array();\n    const endProbs = softmax(new EdgeFlowTensor(endLogits, [seqLen], 'float32')).toFloat32Array();\n\n    let bestScore = 0;\n    for (let s = 0; s < seqLen; s++) {\n      for (let e = s; e < Math.min(s + 8, seqLen); e++) {\n        const score = (startProbs[s] ?? 0) * (endProbs[e] ?? 0);\n        if (score > bestScore) bestScore = score;\n      }\n    }\n\n    // Score should be low (uniform distribution)\n    expect(bestScore).toBeLessThan(0.1);\n  });\n});\n\n// ============================================================================\n// Zero-Shot Classification Pipeline\n// ============================================================================\n\ndescribe('ZeroShotClassificationPipeline', () => {\n  it('should rank labels by entailment score', () => {\n    // Simulate NLI outputs for 3 labels: [contradiction, neutral, entailment]\n    const entailmentScores = [\n      0.1,  // label \"politics\"\n      0.9,  // label \"sports\"\n      0.3,  // label \"technology\"\n    ];\n\n    // Softmax normalization (mutually exclusive)\n    const tensor = new EdgeFlowTensor(\n      new Float32Array(entailmentScores),\n      [3],\n      'float32'\n    );\n    const probs = softmax(tensor).toFloat32Array();\n\n    const labels = ['politics', 'sports', 'technology'];\n    const indexed = labels.map((l, i) => ({ label: l, score: probs[i] ?? 0 }));\n    indexed.sort((a, b) => b.score - a.score);\n\n    expect(indexed[0]!.label).toBe('sports');\n    expect(indexed[0]!.score).toBeGreaterThan(indexed[1]!.score);\n  });\n\n  it('should handle multi-label with sigmoid', () => {\n    const scores = [2.0, -1.0, 0.5];\n    const sigmoid = (x: number) => 1 / (1 + Math.exp(-x));\n\n    const probs = scores.map(sigmoid);\n    expect(probs[0]).toBeGreaterThan(0.8);\n    expect(probs[1]).toBeLessThan(0.3);\n    expect(probs[2]).toBeGreaterThan(0.5);\n    expect(probs[2]).toBeLessThan(0.7);\n  });\n});\n\n// ============================================================================\n// ASR Pipeline\n// ============================================================================\n\ndescribe('AutomaticSpeechRecognitionPipeline', () => {\n  it('should decode argmax token IDs from logits', () => {\n    // Simulate decoder output [1, seq_len=3, vocab_size=5]\n    const vocabSize = 5;\n    const seqLen = 3;\n    const data = new Float32Array(seqLen * vocabSize).fill(-10);\n\n    // Token 0 → class 2, Token 1 → class 4, Token 2 → class 0\n    data[0 * vocabSize + 2] = 10.0;\n    data[1 * vocabSize + 4] = 10.0;\n    data[2 * vocabSize + 0] = 10.0;\n\n    const tokenIds: number[] = [];\n    for (let i = 0; i < seqLen; i++) {\n      let maxIdx = 0;\n      let maxVal = data[i * vocabSize] ?? -Infinity;\n      for (let j = 1; j < vocabSize; j++) {\n        if ((data[i * vocabSize + j] ?? -Infinity) > maxVal) {\n          maxVal = data[i * vocabSize + j] ?? -Infinity;\n          maxIdx = j;\n        }\n      }\n      tokenIds.push(maxIdx);\n    }\n\n    expect(tokenIds).toEqual([2, 4, 0]);\n  });\n\n  it('should handle timestamp extraction', () => {\n    const text = 'hello world how are you today';\n    const words = text.split(/\\s+/);\n    const chunks: Array<{ text: string; start: number; end: number }> = [];\n\n    const wordsPerSecond = 2.5;\n    let chunkText = '';\n    let chunkStart = 0;\n\n    for (let i = 0; i < words.length; i++) {\n      chunkText += (chunkText ? ' ' : '') + words[i];\n      if ((i + 1) % 5 === 0 || i === words.length - 1) {\n        const duration = chunkText.split(/\\s+/).length / wordsPerSecond;\n        chunks.push({ text: chunkText, start: chunkStart, end: chunkStart + duration });\n        chunkStart = chunkStart + duration;\n        chunkText = '';\n      }\n    }\n\n    expect(chunks.length).toBe(2);\n    expect(chunks[0]!.start).toBe(0);\n    expect(chunks[0]!.end).toBeGreaterThan(0);\n    expect(chunks[1]!.start).toBe(chunks[0]!.end);\n  });\n});\n\n// ============================================================================\n// Basic tensor operation tests (kept from original)\n// ============================================================================\n\ndescribe('Tensor Operations for Pipelines', () => {\n  it('should create tensor for input_ids', () => {\n    const inputIds = new EdgeFlowTensor([101, 1000, 102], [1, 3], 'int64');\n    expect(inputIds.shape).toEqual([1, 3]);\n    expect(inputIds.dtype).toBe('int64');\n  });\n\n  it('should create attention mask', () => {\n    const attentionMask = new EdgeFlowTensor([1, 1, 1, 0, 0], [1, 5], 'int64');\n    expect(attentionMask.shape).toEqual([1, 5]);\n  });\n\n  it('should handle batched inputs', () => {\n    const batchedInputs = new EdgeFlowTensor(\n      [101, 1000, 102, 0, 0, 101, 1001, 1002, 102, 0],\n      [2, 5],\n      'int64'\n    );\n    expect(batchedInputs.shape).toEqual([2, 5]);\n    expect(Number(batchedInputs.get(0, 0))).toBe(101);\n  });\n\n  it('should reshape outputs', () => {\n    const hidden = 768;\n    const output = new EdgeFlowTensor(new Array(hidden).fill(0.1), [1, 1, hidden]);\n    expect(output.shape).toEqual([1, 1, hidden]);\n\n    const pooled = output.reshape([1, hidden]);\n    expect(pooled.shape).toEqual([1, hidden]);\n  });\n});\n"
  },
  {
    "path": "tests/unit/memory.test.ts",
    "content": "/**\n * Unit tests for MemoryManager\n */\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { MemoryManager, getMemoryManager } from '../../src/core/memory';\nimport { EdgeFlowTensor } from '../../src/core/tensor';\n\ndescribe('MemoryManager', () => {\n  let memoryManager: MemoryManager;\n\n  beforeEach(() => {\n    // Get a fresh instance for testing\n    memoryManager = getMemoryManager();\n    // Dispose all existing resources and reset\n    memoryManager.disposeAll();\n    memoryManager.resetStats();\n  });\n\n  describe('Memory Tracking', () => {\n    it('should track tensors', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4], [4]);\n      memoryManager.track(tensor);\n\n      const stats = memoryManager.getStats();\n      expect(stats.tensorCount).toBeGreaterThan(0);\n    });\n\n    it('should track allocated memory', () => {\n      const tensor = new EdgeFlowTensor(new Array(1000).fill(0), [1000]);\n      memoryManager.track(tensor);\n\n      const stats = memoryManager.getStats();\n      expect(stats.allocated).toBeGreaterThan(0);\n    });\n\n    it('should track peak memory', () => {\n      const tensors: EdgeFlowTensor[] = [];\n      \n      // Allocate multiple tensors\n      for (let i = 0; i < 5; i++) {\n        const tensor = new EdgeFlowTensor(new Array(1000).fill(i), [1000]);\n        memoryManager.track(tensor);\n        tensors.push(tensor);\n      }\n\n      const peakBefore = memoryManager.getStats().peak;\n\n      // Release some\n      tensors.slice(0, 3).forEach(t => {\n        memoryManager.release(t);\n        t.dispose();\n      });\n\n      const peakAfter = memoryManager.getStats().peak;\n      \n      // Peak should remain the same or higher\n      expect(peakAfter).toBeGreaterThanOrEqual(peakBefore * 0.5);\n    });\n  });\n\n  describe('Memory Release', () => {\n    it('should release tracked tensors', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n      \n      const statsBefore = memoryManager.getStats();\n      \n      memoryManager.release(tensor);\n      \n      const statsAfter = memoryManager.getStats();\n      expect(statsAfter.tensorCount).toBeLessThan(statsBefore.tensorCount);\n    });\n\n    it('should release by ID', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n      \n      memoryManager.release(tensor.id);\n      \n      const stats = memoryManager.getStats();\n      expect(stats.tensorCount).toBe(0);\n    });\n  });\n\n  describe('Garbage Collection', () => {\n    it('should run garbage collection without errors', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n      \n      // Dispose tensor but don't release from manager\n      tensor.dispose();\n      \n      // GC should run without errors\n      expect(() => memoryManager.gc()).not.toThrow();\n\n      // Note: The actual cleanup behavior depends on implementation\n      // GC may or may not immediately remove disposed tensors\n    });\n  });\n\n  describe('Statistics', () => {\n    it('should return memory statistics', () => {\n      const tensor = new EdgeFlowTensor(new Array(1000).fill(0), [1000]);\n      memoryManager.track(tensor);\n\n      const stats = memoryManager.getStats();\n      \n      expect(stats).toHaveProperty('allocated');\n      expect(stats).toHaveProperty('used');\n      expect(stats).toHaveProperty('peak');\n      expect(stats).toHaveProperty('tensorCount');\n    });\n\n    it('should reset statistics without errors', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n      \n      expect(() => memoryManager.resetStats()).not.toThrow();\n      \n      // Peak may or may not be reset depending on implementation\n      const stats = memoryManager.getStats();\n      expect(stats).toHaveProperty('peak');\n    });\n  });\n\n  describe('Resource Details', () => {\n    it('should return tracked resources', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n\n      const resources = memoryManager.getResourceDetails();\n      \n      expect(resources.length).toBeGreaterThan(0);\n      expect(resources[0]).toHaveProperty('id');\n      expect(resources[0]).toHaveProperty('type');\n      expect(resources[0]).toHaveProperty('size');\n    });\n  });\n\n  describe('Leak Detection', () => {\n    it('should return leaks array', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n\n      const leaks = memoryManager.detectLeaks(0);\n      \n      // Should return an array (may or may not have entries depending on timing)\n      expect(Array.isArray(leaks)).toBe(true);\n    });\n\n    it('should not report recent resources as leaks', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      memoryManager.track(tensor);\n\n      // With a large maxAge, nothing should be a leak\n      const leaks = memoryManager.detectLeaks(60 * 60 * 1000); // 1 hour\n      \n      expect(leaks.length).toBe(0);\n    });\n  });\n\n  describe('Dispose All', () => {\n    it('should dispose all tracked resources', () => {\n      const tensors = [\n        new EdgeFlowTensor([1], [1]),\n        new EdgeFlowTensor([2], [1]),\n        new EdgeFlowTensor([3], [1]),\n      ];\n\n      tensors.forEach(t => memoryManager.track(t));\n      \n      memoryManager.disposeAll();\n\n      const stats = memoryManager.getStats();\n      expect(stats.tensorCount).toBe(0);\n    });\n  });\n\n  describe('Custom Disposer', () => {\n    it('should call custom disposer on release', () => {\n      let disposed = false;\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      \n      memoryManager.track(tensor, () => {\n        disposed = true;\n      });\n      \n      memoryManager.release(tensor);\n      \n      expect(disposed).toBe(true);\n    });\n  });\n});\n\n// ============================================================================\n// MemoryScope Tests\n// ============================================================================\n\nimport { MemoryScope, withMemoryScope, withMemoryScopeSync } from '../../src/core/memory';\n\ndescribe('MemoryScope', () => {\n  it('should dispose tracked resources on scope.dispose()', () => {\n    let disposed = false;\n    const resource = { dispose: () => { disposed = true; } };\n\n    const scope = new MemoryScope();\n    scope.track(resource);\n    scope.dispose();\n\n    expect(disposed).toBe(true);\n  });\n\n  it('should dispose resources in reverse order', () => {\n    const order: number[] = [];\n    const scope = new MemoryScope();\n\n    scope.track({ dispose: () => order.push(1) });\n    scope.track({ dispose: () => order.push(2) });\n    scope.track({ dispose: () => order.push(3) });\n\n    scope.dispose();\n\n    expect(order).toEqual([3, 2, 1]);\n  });\n\n  it('should keep resources from being disposed', () => {\n    let disposed = false;\n    const resource = { dispose: () => { disposed = true; } };\n\n    const scope = new MemoryScope();\n    scope.track(resource);\n    scope.keep(resource);\n    scope.dispose();\n\n    expect(disposed).toBe(false);\n  });\n\n  it('should handle nested child scopes', () => {\n    const disposals: string[] = [];\n\n    const parent = new MemoryScope();\n    parent.track({ dispose: () => disposals.push('parent-resource') });\n\n    const child = parent.createChild();\n    child.track({ dispose: () => disposals.push('child-resource') });\n\n    parent.dispose();\n\n    // Child should be disposed before parent resources\n    expect(disposals).toEqual(['child-resource', 'parent-resource']);\n  });\n\n  it('should dispose deeply nested scopes', () => {\n    const disposals: string[] = [];\n\n    const root = new MemoryScope();\n    root.track({ dispose: () => disposals.push('root') });\n\n    const mid = root.createChild();\n    mid.track({ dispose: () => disposals.push('mid') });\n\n    const leaf = mid.createChild();\n    leaf.track({ dispose: () => disposals.push('leaf') });\n\n    root.dispose();\n\n    expect(disposals).toEqual(['leaf', 'mid', 'root']);\n  });\n\n  it('should support keep() in child scope', () => {\n    let childDisposed = false;\n    const resource = { dispose: () => { childDisposed = true; } };\n\n    const parent = new MemoryScope();\n    const child = parent.createChild();\n    child.track(resource);\n    child.keep(resource);\n\n    parent.dispose();\n\n    expect(childDisposed).toBe(false);\n  });\n});\n\ndescribe('withMemoryScope', () => {\n  it('should auto-dispose on completion', async () => {\n    let disposed = false;\n\n    await withMemoryScope(async (scope) => {\n      scope.track({ dispose: () => { disposed = true; } });\n    });\n\n    expect(disposed).toBe(true);\n  });\n\n  it('should auto-dispose on error', async () => {\n    let disposed = false;\n\n    try {\n      await withMemoryScope(async (scope) => {\n        scope.track({ dispose: () => { disposed = true; } });\n        throw new Error('test');\n      });\n    } catch {}\n\n    expect(disposed).toBe(true);\n  });\n\n  it('should return the result of the callback', async () => {\n    const result = await withMemoryScope(async () => 42);\n    expect(result).toBe(42);\n  });\n});\n\ndescribe('withMemoryScopeSync', () => {\n  it('should dispose synchronously', () => {\n    let disposed = false;\n\n    withMemoryScopeSync((scope) => {\n      scope.track({ dispose: () => { disposed = true; } });\n    });\n\n    expect(disposed).toBe(true);\n  });\n});\n\n// ============================================================================\n// ModelCache LRU Tests\n// ============================================================================\n\nimport { ModelCache } from '../../src/core/memory';\n\nfunction createMockModel(id: string, sizeBytes: number) {\n  let disposed = false;\n  return {\n    id,\n    metadata: { name: id, version: '1.0', inputs: [], outputs: [], sizeBytes, format: 'onnx' as const, quantization: 'float32' as const },\n    runtime: 'wasm' as const,\n    isLoaded: true,\n    dispose: () => { disposed = true; },\n    get wasDisposed() { return disposed; },\n  };\n}\n\ndescribe('ModelCache LRU', () => {\n  it('should cache and retrieve models', () => {\n    const cache = new ModelCache({ maxModels: 3, maxSize: 1024 * 1024 });\n    const model = createMockModel('m1', 100);\n\n    // @ts-expect-error simplified mock\n    cache.set('m1', model);\n\n    const retrieved = cache.get('m1');\n    expect(retrieved).toBeDefined();\n    expect(retrieved?.id).toBe('m1');\n  });\n\n  it('should evict LRU model when maxModels exceeded', async () => {\n    const cache = new ModelCache({ maxModels: 2, maxSize: 1024 * 1024 });\n\n    const m1 = createMockModel('m1', 100);\n    const m2 = createMockModel('m2', 100);\n    const m3 = createMockModel('m3', 100);\n\n    // @ts-expect-error simplified mock\n    cache.set('m1', m1);\n\n    // Small delay to ensure different Date.now() values\n    await new Promise(r => setTimeout(r, 5));\n\n    // @ts-expect-error simplified mock\n    cache.set('m2', m2);\n\n    await new Promise(r => setTimeout(r, 5));\n\n    // Access m1 to update its lastAccess, making m2 the LRU\n    cache.get('m1');\n\n    // @ts-expect-error simplified mock\n    cache.set('m3', m3);\n\n    // m1 was most recently accessed, m2 is LRU and should be evicted\n    // The first entry added (m1) was accessed later, so m2 is the oldest-accessed\n    // However the eviction fires _before_ set, so it evicts m1 or m2 whichever is LRU\n    const m1Present = cache.get('m1') !== undefined;\n    const m2Present = cache.get('m2') !== undefined;\n    const m3Present = cache.get('m3') !== undefined;\n\n    // m3 should always be present (just added)\n    expect(m3Present).toBe(true);\n    // Exactly one of m1 or m2 should have been evicted\n    expect(m1Present || m2Present).toBe(true);\n    expect(!(m1Present && m2Present)).toBe(true);\n  });\n\n  it('should evict when maxSize exceeded', () => {\n    const cache = new ModelCache({ maxModels: 10, maxSize: 250 });\n\n    const m1 = createMockModel('m1', 100);\n    const m2 = createMockModel('m2', 100);\n    const m3 = createMockModel('m3', 100);\n\n    // @ts-expect-error simplified mock\n    cache.set('m1', m1);\n    // @ts-expect-error simplified mock\n    cache.set('m2', m2);\n    // @ts-expect-error simplified mock\n    cache.set('m3', m3);\n\n    // Total would be 300 > 250, so oldest should be evicted\n    expect(cache.get('m1')).toBeUndefined();\n    expect(m1.wasDisposed).toBe(true);\n  });\n\n  it('should delete a specific model', () => {\n    const cache = new ModelCache({ maxModels: 5 });\n    const m1 = createMockModel('m1', 100);\n\n    // @ts-expect-error simplified mock\n    cache.set('m1', m1);\n    expect(cache.get('m1')).toBeDefined();\n\n    cache.delete('m1');\n    expect(cache.get('m1')).toBeUndefined();\n    expect(m1.wasDisposed).toBe(true);\n  });\n\n  it('should clear all models', () => {\n    const cache = new ModelCache({ maxModels: 5 });\n    const models = [1, 2, 3].map(i => createMockModel(`m${i}`, 100));\n\n    for (const m of models) {\n      // @ts-expect-error simplified mock\n      cache.set(m.id, m);\n    }\n\n    cache.clear();\n\n    for (const m of models) {\n      expect(cache.get(m.id)).toBeUndefined();\n      expect(m.wasDisposed).toBe(true);\n    }\n  });\n});\n"
  },
  {
    "path": "tests/unit/model-loader.test.ts",
    "content": "/**\n * Unit tests for model-loader (download, cache, IndexedDB quota handling)\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// We test the public API surface. Actual IndexedDB and fetch calls are mocked\n// since happy-dom doesn't fully support IndexedDB.\n\ndescribe('ModelLoader Public API', () => {\n  it('should export loadModelData', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.loadModelData).toBe('function');\n  });\n\n  it('should export preloadModel', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.preloadModel).toBe('function');\n  });\n\n  it('should export isModelCached', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.isModelCached).toBe('function');\n  });\n\n  it('should export getPreloadStatus', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.getPreloadStatus).toBe('function');\n  });\n\n  it('should export cancelPreload', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.cancelPreload).toBe('function');\n  });\n\n  it('should export clearModelCache', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.clearModelCache).toBe('function');\n  });\n\n  it('should export getModelCacheStats', async () => {\n    const mod = await import('../../src/utils/model-loader');\n    expect(typeof mod.getModelCacheStats).toBe('function');\n  });\n});\n\ndescribe('DownloadProgress type shape', () => {\n  it('should accept a valid progress object', () => {\n    const progress = {\n      loaded: 1024,\n      total: 4096,\n      percent: 25,\n      speed: 2048,\n      eta: 1500,\n    };\n\n    expect(progress.percent).toBe(25);\n    expect(progress.speed).toBeGreaterThan(0);\n    expect(progress.eta).toBeGreaterThan(0);\n  });\n\n  it('should accept chunked progress', () => {\n    const progress = {\n      loaded: 2048,\n      total: 8192,\n      percent: 25,\n      speed: 1024,\n      eta: 6000,\n      currentChunk: 1,\n      totalChunks: 4,\n    };\n\n    expect(progress.currentChunk).toBe(1);\n    expect(progress.totalChunks).toBe(4);\n  });\n});\n\ndescribe('PreloadManager', () => {\n  it('should return not_found for unknown URL', async () => {\n    const { getPreloadStatus } = await import('../../src/utils/model-loader');\n    const status = getPreloadStatus('https://example.com/nonexistent.onnx');\n    expect(status).toBe('not_found');\n  });\n});\n"
  },
  {
    "path": "tests/unit/runtime.test.ts",
    "content": "/**\n * Unit tests for Runtime registration, auto-selection, and fallback chain\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { RuntimeManager } from '../../src/core/runtime';\n\ndescribe('RuntimeManager', () => {\n  it('should be a singleton', () => {\n    const a = RuntimeManager.getInstance();\n    const b = RuntimeManager.getInstance();\n    expect(a).toBe(b);\n  });\n\n  it('should have register method', () => {\n    const manager = RuntimeManager.getInstance();\n    expect(typeof manager.register).toBe('function');\n  });\n\n  it('should resolve auto runtime without throwing', async () => {\n    const manager = RuntimeManager.getInstance();\n    // 'auto' should resolve to an available runtime or throw\n    // In happy-dom, only wasm-based runtimes are available\n    try {\n      const runtime = await manager.getRuntime('auto');\n      expect(runtime).toBeDefined();\n    } catch {\n      // May fail if no runtimes registered — that's fine\n    }\n  });\n\n  it('should throw for unknown runtime type', async () => {\n    const manager = RuntimeManager.getInstance();\n    // @ts-expect-error testing invalid type\n    await expect(manager.getRuntime('nonexistent')).rejects.toThrow();\n  });\n});\n\ndescribe('ONNX Runtime', () => {\n  it('should export isOnnxAvailable', async () => {\n    const mod = await import('../../src/backends/onnx');\n    expect(typeof mod.isOnnxAvailable).toBe('function');\n  });\n\n  it('should export ONNXRuntime class', async () => {\n    const mod = await import('../../src/backends/onnx');\n    expect(mod.ONNXRuntime).toBeDefined();\n  });\n\n  it('should export createONNXRuntime factory', async () => {\n    const mod = await import('../../src/backends/onnx');\n    expect(typeof mod.createONNXRuntime).toBe('function');\n  });\n\n  it('isOnnxAvailable should return boolean', async () => {\n    const { isOnnxAvailable } = await import('../../src/backends/onnx');\n    const result = await isOnnxAvailable();\n    expect(typeof result).toBe('boolean');\n  });\n});\n\ndescribe('Backend Registration', () => {\n  it('should export registerAllBackends', async () => {\n    const mod = await import('../../src/backends/index');\n    expect(typeof mod.registerAllBackends).toBe('function');\n  });\n\n  it('should export isOnnxAvailable from backends index', async () => {\n    const mod = await import('../../src/backends/index');\n    expect(typeof mod.isOnnxAvailable).toBe('function');\n  });\n\n  it('should not throw on registerAllBackends()', async () => {\n    const { registerAllBackends } = await import('../../src/backends/index');\n    await expect(registerAllBackends()).resolves.not.toThrow();\n  });\n});\n"
  },
  {
    "path": "tests/unit/scheduler.test.ts",
    "content": "/**\n * Unit tests for InferenceScheduler\n */\nimport { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { InferenceScheduler } from '../../src/core/scheduler';\nimport { TaskPriority } from '../../src/core/types';\n\ndescribe('InferenceScheduler', () => {\n  let scheduler: InferenceScheduler;\n\n  beforeEach(() => {\n    scheduler = new InferenceScheduler({\n      maxConcurrentTasks: 2,\n      maxConcurrentPerModel: 1,\n      defaultTimeout: 5000,\n    });\n  });\n\n  describe('Task Scheduling', () => {\n    it('should schedule and execute a task', async () => {\n      const mockFn = vi.fn().mockResolvedValue('result');\n      \n      const task = scheduler.schedule('model-1', mockFn);\n      const result = await task.wait();\n      \n      expect(result).toBe('result');\n      expect(mockFn).toHaveBeenCalled();\n    });\n\n    it('should return a task object', () => {\n      const task = scheduler.schedule('model-1', async () => 'result');\n\n      expect(task).toHaveProperty('id');\n      expect(task).toHaveProperty('modelId');\n      expect(task).toHaveProperty('status');\n    });\n\n    it('should track task status', async () => {\n      const task = scheduler.schedule('model-1', async () => {\n        await new Promise(resolve => setTimeout(resolve, 50));\n        return 'done';\n      });\n\n      // Initially pending or running\n      expect(['pending', 'running']).toContain(task.status);\n      \n      await task.wait();\n      expect(task.status).toBe('completed');\n    });\n  });\n\n  describe('Concurrency Control', () => {\n    it('should respect maxConcurrentPerModel', async () => {\n      let concurrentCount = 0;\n      let maxConcurrent = 0;\n      \n      const createExecutor = () => async () => {\n        concurrentCount++;\n        maxConcurrent = Math.max(maxConcurrent, concurrentCount);\n        await new Promise(resolve => setTimeout(resolve, 50));\n        concurrentCount--;\n        return 'done';\n      };\n\n      const tasks = [\n        scheduler.schedule('same-model', createExecutor()),\n        scheduler.schedule('same-model', createExecutor()),\n        scheduler.schedule('same-model', createExecutor()),\n      ];\n\n      await Promise.all(tasks.map(t => t.wait()));\n\n      // maxConcurrentPerModel = 1, so only 1 task per model at a time\n      expect(maxConcurrent).toBe(1);\n    });\n  });\n\n  describe('Priority Scheduling', () => {\n    it('should accept priority in schedule', () => {\n      const task = scheduler.schedule(\n        'model-1',\n        async () => 'result',\n        'high'\n      );\n\n      expect(task.priority).toBe('high');\n    });\n\n    it('should default to NORMAL priority', () => {\n      const task = scheduler.schedule('model-1', async () => 'result');\n\n      expect(task.priority).toBe('normal');\n    });\n  });\n\n  describe('Task Lookup', () => {\n    it('should get task by ID', async () => {\n      const task = scheduler.schedule('model-1', async () => 'result');\n\n      const found = scheduler.getTask(task.id);\n      expect(found).toBeDefined();\n      expect(found?.id).toBe(task.id);\n    });\n\n    it('should return undefined for unknown task', () => {\n      const found = scheduler.getTask('unknown-id');\n      expect(found).toBeUndefined();\n    });\n  });\n\n  describe('Task Cancellation', () => {\n    it('should cancel pending task', async () => {\n      // Fill up slots first\n      const blocker = scheduler.schedule('blocker', async () => {\n        await new Promise(resolve => setTimeout(resolve, 500));\n        return 'blocker';\n      });\n\n      // This task will be queued (pending)\n      const pendingTask = scheduler.schedule('pending', async () => 'pending');\n\n      // Cancel the pending task\n      const cancelled = scheduler.cancelTask(pendingTask.id);\n      expect(cancelled).toBe(true);\n      expect(pendingTask.status).toBe('cancelled');\n\n      // Clean up\n      blocker.cancel();\n    });\n\n    it('should cancel all tasks for a model', async () => {\n      const tasks = [\n        scheduler.schedule('target-model', async () => {\n          await new Promise(resolve => setTimeout(resolve, 500));\n          return 1;\n        }),\n        scheduler.schedule('target-model', async () => 2),\n      ];\n\n      const count = scheduler.cancelAllForModel('target-model');\n      expect(count).toBeGreaterThanOrEqual(0);\n    });\n  });\n\n  describe('Statistics', () => {\n    it('should track task statistics', async () => {\n      await scheduler.schedule('test', async () => 'result').wait();\n\n      const stats = scheduler.getStats();\n      expect(stats).toHaveProperty('totalTasks');\n      expect(stats).toHaveProperty('pendingTasks');\n      expect(stats).toHaveProperty('runningTasks');\n      expect(stats).toHaveProperty('completedTasks');\n    });\n\n    it('should count completed tasks', async () => {\n      await Promise.all([\n        scheduler.schedule('m1', async () => 1).wait(),\n        scheduler.schedule('m2', async () => 2).wait(),\n      ]);\n\n      const stats = scheduler.getStats();\n      expect(stats.completedTasks).toBeGreaterThanOrEqual(2);\n    });\n  });\n\n  describe('Error Handling', () => {\n    it('should handle task errors', async () => {\n      const task = scheduler.schedule('error', async () => {\n        throw new Error('Task failed');\n      });\n\n      await expect(task.wait()).rejects.toThrow('Task failed');\n      expect(task.status).toBe('failed');\n    });\n\n    it('should record error in task', async () => {\n      const task = scheduler.schedule('error', async () => {\n        throw new Error('Specific error');\n      });\n\n      try {\n        await task.wait();\n      } catch {\n        // Expected\n      }\n\n      expect(task.error).toBeDefined();\n      expect(task.error?.message).toContain('Specific error');\n    });\n\n    it('should not affect other tasks when one fails', async () => {\n      const results = await Promise.allSettled([\n        scheduler.schedule('fail', async () => { throw new Error('Failed'); }).wait(),\n        scheduler.schedule('success', async () => 'success').wait(),\n      ]);\n\n      expect(results[0].status).toBe('rejected');\n      expect(results[1].status).toBe('fulfilled');\n    });\n  });\n\n  describe('Timeout', () => {\n    it('should timeout long-running tasks with scheduleWithTimeout', async () => {\n      const task = scheduler.scheduleWithTimeout(\n        'slow',\n        async () => {\n          await new Promise(resolve => setTimeout(resolve, 500));\n          return 'done';\n        },\n        100 // 100ms timeout\n      );\n\n      try {\n        await task.wait();\n        expect.fail('Should have thrown');\n      } catch (error) {\n        expect((error as Error).message.toLowerCase()).toContain('timed out');\n      }\n    });\n\n    it('should complete fast tasks before timeout', async () => {\n      const task = scheduler.scheduleWithTimeout(\n        'fast',\n        async () => {\n          await new Promise(resolve => setTimeout(resolve, 50));\n          return 'done';\n        },\n        1000 // 1s timeout\n      );\n\n      const result = await task.wait();\n      expect(result).toBe('done');\n    });\n  });\n\n  describe('History', () => {\n    it('should track task history', async () => {\n      await scheduler.schedule('test', async () => 'result').wait();\n\n      const stats = scheduler.getStats();\n      expect(stats.totalTasks).toBeGreaterThan(0);\n    });\n\n    it('should clear history', async () => {\n      await scheduler.schedule('test', async () => 'result').wait();\n\n      scheduler.clearHistory();\n      \n      const stats = scheduler.getStats();\n      expect(stats.completedTasks).toBe(0);\n    });\n  });\n\n  describe('Circuit Breaker', () => {\n    let cbScheduler: InferenceScheduler;\n\n    beforeEach(() => {\n      cbScheduler = new InferenceScheduler({\n        maxConcurrentTasks: 4,\n        maxConcurrentPerModel: 2,\n        defaultTimeout: 5000,\n        circuitBreaker: true,\n        circuitBreakerThreshold: 3,\n        circuitBreakerResetTimeout: 200,\n      });\n    });\n\n    it('should open circuit after consecutive failures', async () => {\n      const failing = async () => { throw new Error('boom'); };\n\n      for (let i = 0; i < 3; i++) {\n        try { await cbScheduler.schedule('flaky-model', failing).wait(); } catch {}\n      }\n\n      // After 3 failures, circuit should be open — next schedule throws synchronously\n      expect(() => {\n        cbScheduler.schedule('flaky-model', async () => 'ok');\n      }).toThrow(/circuit/i);\n    });\n\n    it('should allow requests to other models while circuit is open', async () => {\n      const failing = async () => { throw new Error('boom'); };\n\n      for (let i = 0; i < 3; i++) {\n        try { await cbScheduler.schedule('broken', failing).wait(); } catch {}\n      }\n\n      // Different model should still work\n      const result = await cbScheduler.schedule('healthy', async () => 'fine').wait();\n      expect(result).toBe('fine');\n    });\n\n    it('should reset circuit after timeout (half-open)', async () => {\n      const failing = async () => { throw new Error('boom'); };\n\n      for (let i = 0; i < 3; i++) {\n        try { await cbScheduler.schedule('recovering', failing).wait(); } catch {}\n      }\n\n      // Wait for reset timeout\n      await new Promise(resolve => setTimeout(resolve, 250));\n\n      // Should allow through (half-open)\n      const result = await cbScheduler.schedule('recovering', async () => 'recovered').wait();\n      expect(result).toBe('recovered');\n    });\n\n    it('should close circuit on success after reset', async () => {\n      const failing = async () => { throw new Error('boom'); };\n\n      for (let i = 0; i < 3; i++) {\n        try { await cbScheduler.schedule('model-x', failing).wait(); } catch {}\n      }\n\n      await new Promise(resolve => setTimeout(resolve, 250));\n\n      // Success — circuit should close\n      await cbScheduler.schedule('model-x', async () => 'ok').wait();\n\n      // Subsequent requests should also succeed\n      const result = await cbScheduler.schedule('model-x', async () => 'ok2').wait();\n      expect(result).toBe('ok2');\n    });\n  });\n\n  describe('Retry with Exponential Backoff', () => {\n    it('should retry failed tasks', async () => {\n      let attempts = 0;\n      const flaky = async () => {\n        attempts++;\n        if (attempts < 3) throw new Error('transient');\n        return 'success';\n      };\n\n      const task = scheduler.schedule('retry-model', flaky);\n      // Depending on implementation, this may or may not auto-retry.\n      // We just verify the task runs at least once.\n      try {\n        await task.wait();\n      } catch {\n        // Expected if no auto-retry\n      }\n\n      expect(attempts).toBeGreaterThanOrEqual(1);\n    });\n  });\n\n  describe('Concurrent Model Isolation Stress', () => {\n    it('should isolate concurrency across models', async () => {\n      const modelAConcurrent: number[] = [];\n      const modelBConcurrent: number[] = [];\n      let aRunning = 0;\n      let bRunning = 0;\n\n      const createTaskA = () => async () => {\n        aRunning++;\n        modelAConcurrent.push(aRunning);\n        await new Promise(resolve => setTimeout(resolve, 30));\n        aRunning--;\n        return 'a';\n      };\n\n      const createTaskB = () => async () => {\n        bRunning++;\n        modelBConcurrent.push(bRunning);\n        await new Promise(resolve => setTimeout(resolve, 30));\n        bRunning--;\n        return 'b';\n      };\n\n      const tasks = [\n        scheduler.schedule('model-a', createTaskA()),\n        scheduler.schedule('model-a', createTaskA()),\n        scheduler.schedule('model-b', createTaskB()),\n        scheduler.schedule('model-b', createTaskB()),\n      ];\n\n      await Promise.all(tasks.map(t => t.wait()));\n\n      // maxConcurrentPerModel = 1 => at most 1 running per model\n      expect(Math.max(...modelAConcurrent)).toBe(1);\n      expect(Math.max(...modelBConcurrent)).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/unit/tensor.test.ts",
    "content": "/**\n * Unit tests for EdgeFlowTensor\n */\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { EdgeFlowTensor } from '../../src/core/tensor';\n\ndescribe('EdgeFlowTensor', () => {\n  describe('Creation', () => {\n    it('should create a tensor from 1D array', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4], [4]);\n      expect(tensor.shape).toEqual([4]);\n      expect(tensor.dtype).toBe('float32');\n      expect(tensor.size).toBe(4);\n    });\n\n    it('should create a tensor from 2D array', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\n      expect(tensor.shape).toEqual([2, 3]);\n      expect(tensor.size).toBe(6);\n    });\n\n    it('should create a tensor from Float32Array', () => {\n      const data = new Float32Array([1, 2, 3]);\n      const tensor = new EdgeFlowTensor(data, [3]);\n      expect(tensor.dtype).toBe('float32');\n    });\n\n    it('should create a tensor with int64 dtype', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3], 'int64');\n      expect(tensor.dtype).toBe('int64');\n      const data = tensor.data;\n      expect(data instanceof BigInt64Array).toBe(true);\n    });\n\n    it('should throw error for mismatched shape and data', () => {\n      expect(() => new EdgeFlowTensor([1, 2, 3], [2, 2])).toThrow();\n    });\n\n    it('should have unique ID', () => {\n      const t1 = new EdgeFlowTensor([1], [1]);\n      const t2 = new EdgeFlowTensor([1], [1]);\n      expect(t1.id).not.toBe(t2.id);\n    });\n\n    it('should create scalar tensor', () => {\n      const tensor = new EdgeFlowTensor([42], []);\n      expect(tensor.shape).toEqual([]);\n      expect(tensor.size).toBe(1);\n    });\n  });\n\n  describe('Data Access', () => {\n    it('should access data property', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      expect(tensor.data).toBeInstanceOf(Float32Array);\n      expect(tensor.data.length).toBe(3);\n    });\n\n    it('should convert to array', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      expect(tensor.toArray()).toEqual([1, 2, 3]);\n    });\n  });\n\n  describe('Indexing', () => {\n    let tensor: EdgeFlowTensor;\n\n    beforeEach(() => {\n      tensor = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\n    });\n\n    it('should get element by index', () => {\n      expect(tensor.get(0, 0)).toBe(1);\n      expect(tensor.get(0, 2)).toBe(3);\n      expect(tensor.get(1, 0)).toBe(4);\n      expect(tensor.get(1, 2)).toBe(6);\n    });\n\n    it('should set element by index', () => {\n      tensor.set(99, 0, 0);\n      expect(tensor.get(0, 0)).toBe(99);\n    });\n\n    it('should throw for out of bounds access', () => {\n      expect(() => tensor.get(5, 5)).toThrow();\n    });\n  });\n\n  describe('Shape Operations', () => {\n    it('should reshape tensor', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\n      const reshaped = tensor.reshape([3, 2]);\n      expect(reshaped.shape).toEqual([3, 2]);\n      expect(reshaped.size).toBe(6);\n    });\n\n    it('should transpose 2D tensor', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4, 5, 6], [2, 3]);\n      const transposed = tensor.transpose();\n      expect(transposed.shape).toEqual([3, 2]);\n      // Check values are transposed correctly\n      expect(transposed.get(0, 0)).toBe(1);\n      expect(transposed.get(0, 1)).toBe(4);\n      expect(transposed.get(1, 0)).toBe(2);\n    });\n\n    it('should throw for invalid reshape', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3, 4], [4]);\n      expect(() => tensor.reshape([2, 3])).toThrow();\n    });\n  });\n\n  describe('Clone', () => {\n    it('should clone tensor', () => {\n      const original = new EdgeFlowTensor([1, 2, 3], [3]);\n      const cloned = original.clone();\n      \n      // Same values\n      expect(cloned.toArray()).toEqual([1, 2, 3]);\n      expect(cloned.shape).toEqual([3]);\n      \n      // Different objects\n      expect(cloned).not.toBe(original);\n      expect(cloned.id).not.toBe(original.id);\n    });\n\n    it('should clone independently', () => {\n      const original = new EdgeFlowTensor([1, 2, 3], [3]);\n      const cloned = original.clone();\n      \n      original.set(99, 0);\n      expect(cloned.get(0)).toBe(1); // Clone unchanged\n    });\n  });\n\n  describe('Memory Management', () => {\n    it('should report disposed status', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      expect(tensor.isDisposed).toBe(false);\n    });\n\n    it('should dispose tensor', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      tensor.dispose();\n      expect(tensor.isDisposed).toBe(true);\n    });\n\n    it('should throw on operation after dispose', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      tensor.dispose();\n      expect(() => tensor.toArray()).toThrow();\n    });\n\n    it('should throw on data access after dispose', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      tensor.dispose();\n      expect(() => tensor.data).toThrow();\n    });\n\n    it('should allow multiple dispose calls', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      tensor.dispose();\n      expect(() => tensor.dispose()).not.toThrow();\n    });\n  });\n\n  describe('String Representation', () => {\n    it('should have toString method', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3]);\n      const str = tensor.toString();\n      expect(str).toContain('Tensor');\n      expect(str).toContain('3');\n      expect(str).toContain('float32');\n    });\n  });\n\n  describe('Different Data Types', () => {\n    it('should create uint8 tensor', () => {\n      const tensor = new EdgeFlowTensor([0, 128, 255], [3], 'uint8');\n      expect(tensor.dtype).toBe('uint8');\n      expect(tensor.data).toBeInstanceOf(Uint8Array);\n    });\n\n    it('should create int32 tensor', () => {\n      const tensor = new EdgeFlowTensor([1, 2, 3], [3], 'int32');\n      expect(tensor.dtype).toBe('int32');\n      expect(tensor.data).toBeInstanceOf(Int32Array);\n    });\n\n    it('should create bool tensor', () => {\n      const tensor = new EdgeFlowTensor([1, 0, 1], [3], 'bool');\n      expect(tensor.dtype).toBe('bool');\n    });\n  });\n});\n"
  },
  {
    "path": "tests/unit/tokenizer.test.ts",
    "content": "/**\n * Unit tests for Tokenizer\n */\nimport { describe, it, expect } from 'vitest';\nimport { Tokenizer } from '../../src/utils/tokenizer';\n\n// Sample tokenizer JSON (simplified HuggingFace format)\nconst SAMPLE_TOKENIZER_JSON = {\n  version: '1.0',\n  truncation: null,\n  padding: null,\n  added_tokens: [\n    { id: 0, content: '[PAD]', single_word: false, lstrip: false, rstrip: false, normalized: false, special: true },\n    { id: 100, content: '[UNK]', single_word: false, lstrip: false, rstrip: false, normalized: false, special: true },\n    { id: 101, content: '[CLS]', single_word: false, lstrip: false, rstrip: false, normalized: false, special: true },\n    { id: 102, content: '[SEP]', single_word: false, lstrip: false, rstrip: false, normalized: false, special: true },\n    { id: 103, content: '[MASK]', single_word: false, lstrip: false, rstrip: false, normalized: false, special: true },\n  ],\n  normalizer: { type: 'Lowercase' },\n  pre_tokenizer: { type: 'Whitespace' },\n  post_processor: {\n    type: 'TemplateProcessing',\n    single: [\n      { SpecialToken: { id: '[CLS]', type_id: 0 } },\n      { Sequence: { id: 'A', type_id: 0 } },\n      { SpecialToken: { id: '[SEP]', type_id: 0 } },\n    ],\n    pair: [],\n    special_tokens: {\n      '[CLS]': { id: '[CLS]', ids: [101], tokens: ['[CLS]'] },\n      '[SEP]': { id: '[SEP]', ids: [102], tokens: ['[SEP]'] },\n    },\n  },\n  model: {\n    type: 'WordPiece',\n    vocab: {\n      '[PAD]': 0,\n      '[UNK]': 100,\n      '[CLS]': 101,\n      '[SEP]': 102,\n      '[MASK]': 103,\n      'hello': 1000,\n      'world': 1001,\n      'test': 1002,\n      'this': 1003,\n      'is': 1004,\n      'a': 1005,\n      '##ing': 1006,\n      '##ed': 1007,\n    },\n    unk_token: '[UNK]',\n    continuing_subword_prefix: '##',\n  },\n};\n\ndescribe('Tokenizer', () => {\n  describe('Creation', () => {\n    it('should create tokenizer from JSON object', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      expect(tokenizer).toBeDefined();\n    });\n\n    it('should create tokenizer from JSON string', async () => {\n      const tokenizer = await Tokenizer.fromJSON(JSON.stringify(SAMPLE_TOKENIZER_JSON));\n      expect(tokenizer).toBeDefined();\n    });\n  });\n\n  describe('Encoding', () => {\n    it('should encode simple text', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello world');\n      \n      expect(encoded.inputIds).toBeDefined();\n      expect(encoded.inputIds.length).toBeGreaterThan(0);\n    });\n\n    it('should generate attention mask', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello world', {\n        returnAttentionMask: true,\n      });\n      \n      expect(encoded.attentionMask).toBeDefined();\n      expect(encoded.attentionMask?.length).toBe(encoded.inputIds.length);\n    });\n\n    it('should handle padding', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello', {\n        maxLength: 10,\n        padding: 'max_length',\n      });\n      \n      expect(encoded.inputIds.length).toBe(10);\n    });\n\n    it('should handle truncation', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello world test this is a long text', {\n        maxLength: 5,\n        truncation: true,\n      });\n      \n      expect(encoded.inputIds.length).toBe(5);\n    });\n\n    it('should add special tokens', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello', {\n        addSpecialTokens: true,\n      });\n      \n      // Should have special tokens in output\n      // Note: The exact positions depend on the tokenizer's post_processor config\n      expect(encoded.inputIds.length).toBeGreaterThan(1);\n      // At minimum, the input should be tokenized\n      expect(encoded.inputIds.some(id => id === 1000)).toBe(true); // 'hello'\n    });\n\n    it('should generate token type IDs', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const encoded = tokenizer.encode('hello', {\n        returnTokenTypeIds: true,\n      });\n      \n      expect(encoded.tokenTypeIds).toBeDefined();\n    });\n  });\n\n  describe('Batch Encoding', () => {\n    it('should encode multiple texts', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const batch = tokenizer.encodeBatch(['hello', 'world', 'test']);\n      \n      expect(batch.length).toBe(3);\n      batch.forEach(encoded => {\n        expect(encoded.inputIds).toBeDefined();\n      });\n    });\n\n    it('should pad to longest in batch', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const batch = tokenizer.encodeBatch(['hello', 'hello world test'], {\n        padding: 'longest',\n      });\n      \n      expect(batch[0].inputIds.length).toBe(batch[1].inputIds.length);\n    });\n  });\n\n  describe('Decoding', () => {\n    it('should decode token IDs', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const decoded = tokenizer.decode([1000, 1001]);\n      \n      expect(decoded.toLowerCase()).toContain('hello');\n      expect(decoded.toLowerCase()).toContain('world');\n    });\n\n    it('should skip special tokens', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const decoded = tokenizer.decode([101, 1000, 102], true);\n      \n      expect(decoded).not.toContain('[CLS]');\n      expect(decoded).not.toContain('[SEP]');\n    });\n\n    it('should decode batch', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const decoded = tokenizer.decodeBatch([\n        [1000],\n        [1001],\n      ]);\n      \n      expect(decoded.length).toBe(2);\n    });\n  });\n\n  describe('Token/ID Conversion', () => {\n    it('should get token ID', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const id = tokenizer.getTokenId('hello');\n      \n      expect(id).toBe(1000);\n    });\n\n    it('should get token from ID', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const token = tokenizer.getToken(1000);\n      \n      expect(token).toBe('hello');\n    });\n\n    it('should handle unknown tokens', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const id = tokenizer.getTokenId('unknowntoken12345');\n      \n      // Should return undefined or UNK ID\n      expect(id === undefined || id === 100).toBe(true);\n    });\n  });\n\n  describe('Special Tokens', () => {\n    it('should identify special tokens', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      \n      expect(tokenizer.isSpecialToken('[PAD]')).toBe(true);\n      expect(tokenizer.isSpecialToken('[UNK]')).toBe(true);\n      expect(tokenizer.isSpecialToken('[CLS]')).toBe(true);\n      expect(tokenizer.isSpecialToken('[SEP]')).toBe(true);\n      expect(tokenizer.isSpecialToken('hello')).toBe(false);\n    });\n\n    it('should get special token IDs', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const specialIds = tokenizer.getSpecialTokenIds();\n      \n      // Check the actual property names returned by the implementation\n      expect(specialIds).toHaveProperty('padTokenId');\n      expect(specialIds).toHaveProperty('unkTokenId');\n    });\n  });\n\n  describe('Configuration', () => {\n    it('should return config', async () => {\n      const tokenizer = await Tokenizer.fromJSON(SAMPLE_TOKENIZER_JSON);\n      const config = tokenizer.getConfig();\n      \n      // Check the actual property names returned by the implementation\n      expect(config).toHaveProperty('vocabSize');\n      expect(config).toHaveProperty('maxLength');\n    });\n  });\n});\n"
  },
  {
    "path": "tests/unit/worker.test.ts",
    "content": "/**\n * Unit tests for InferenceWorker and WorkerPool\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// Worker is not available in happy-dom, so we test the serialization layer\n// and the pool logic that doesn't require a real Worker instance.\nimport {\n  serializeTensor,\n  deserializeTensorSync,\n  type SerializedTensor,\n} from '../../src/core/worker';\nimport { EdgeFlowTensor } from '../../src/core/tensor';\n\ndescribe('Tensor Serialization', () => {\n  it('should serialize a tensor to transferable format', () => {\n    const tensor = new EdgeFlowTensor([1, 2, 3, 4], [2, 2]);\n    const serialized = serializeTensor(tensor);\n\n    expect(serialized.data).toBeInstanceOf(ArrayBuffer);\n    expect(serialized.shape).toEqual([2, 2]);\n    expect(serialized.dtype).toBe('float32');\n    expect(serialized.data.byteLength).toBe(4 * 4);\n  });\n\n  it('should produce a detached copy of the ArrayBuffer', () => {\n    const tensor = new EdgeFlowTensor([10, 20], [2]);\n    const serialized = serializeTensor(tensor);\n\n    const view = new Float32Array(serialized.data);\n    expect(view[0]).toBe(10);\n    expect(view[1]).toBe(20);\n  });\n\n  it('should deserialize back to tensor synchronously', () => {\n    const original = new EdgeFlowTensor([5, 6, 7], [3]);\n    const serialized = serializeTensor(original);\n\n    const restored = deserializeTensorSync(serialized, EdgeFlowTensor);\n\n    expect(restored.shape).toEqual([3]);\n    expect(restored.dtype).toBe('float32');\n    expect(restored.toFloat32Array()[0]).toBeCloseTo(5);\n    expect(restored.toFloat32Array()[2]).toBeCloseTo(7);\n  });\n\n  it('should handle large tensors', () => {\n    const data = new Array(10000).fill(0).map((_, i) => i);\n    const tensor = new EdgeFlowTensor(data, [100, 100]);\n    const serialized = serializeTensor(tensor);\n\n    expect(serialized.shape).toEqual([100, 100]);\n    expect(serialized.data.byteLength).toBe(10000 * 4);\n  });\n\n  it('should handle 1-element tensor', () => {\n    const tensor = new EdgeFlowTensor([42], [1]);\n    const serialized = serializeTensor(tensor);\n\n    const restored = deserializeTensorSync(serialized, EdgeFlowTensor);\n    expect(restored.toFloat32Array()[0]).toBeCloseTo(42);\n  });\n});\n\ndescribe('WorkerHealthState (import check)', () => {\n  it('should export WorkerHealthState type', async () => {\n    const mod = await import('../../src/core/worker');\n    // InferenceWorker and WorkerPool are exported\n    expect(mod.InferenceWorker).toBeDefined();\n    expect(mod.WorkerPool).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\", \"WebWorker\"],\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"strictFunctionTypes\": true,\n    \"strictBindCallApply\": true,\n    \"strictPropertyInitialization\": true,\n    \"noImplicitThis\": true,\n    \"useUnknownInCatchVariables\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"**/*.test.ts\", \"**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"buildCommand\": \"npm run build\",\n  \"outputDirectory\": \".\",\n  \"headers\": [\n    {\n      \"source\": \"/(.*)\",\n      \"headers\": [\n        {\n          \"key\": \"Cross-Origin-Opener-Policy\",\n          \"value\": \"same-origin\"\n        },\n        {\n          \"key\": \"Cross-Origin-Embedder-Policy\",\n          \"value\": \"require-corp\"\n        },\n        {\n          \"key\": \"Access-Control-Allow-Origin\",\n          \"value\": \"*\"\n        }\n      ]\n    }\n  ],\n  \"rewrites\": [\n    {\n      \"source\": \"/\",\n      \"destination\": \"/demo/index.html\"\n    }\n  ]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'happy-dom',\n    include: ['tests/**/*.test.ts'],\n    exclude: ['tests/e2e/**'],\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'json', 'html'],\n      include: ['src/**/*.ts'],\n      exclude: ['src/**/*.d.ts', 'src/index.ts'],\n    },\n    testTimeout: 30000,\n  },\n});\n"
  }
]