npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

despia-intelligence

v1.1.4

Published

Local AI models and on-device LLM inference for hybrid mobile apps on iOS and Android, powered by the Despia Native Runtime. Private, offline, zero per-token cost.

Readme

Despia Intelligence

JavaScript SDK for Local AI in Despia Native apps. Use the same web code you already ship, while Despia handles model downloads, on-device inference, and native acceleration on iOS and Android.

npm license source

Beta / experimental: This SDK is currently experimental. APIs and behavior may change before the stable public release. The final stable version will ship with Despia V4.

Capabilities

  • Run text generation on-device from JavaScript.
  • Stream the full accumulated response into your UI.
  • List available and installed local models.
  • Download and remove models with progress callbacks.
  • Resume the active inference job after a soft app close.
  • Safely import in browser previews and SSR paths.

Current public support is type: 'text'.

Install

npm install despia-intelligence
import intelligence from 'despia-intelligence';

Core Principle

despia-intelligence is the public JavaScript API for Despia Local AI. App code should treat it like a normal SDK: import it, check availability, load models, start one generation, and render the callback results.

The mental model is:

your app -> intelligence.run(...) -> Despia Local AI -> handler.stream(...)

The SDK owns the Despia integration details. Application code should not create a second Local AI integration or manually wire callback plumbing. Doing that is the most common cause of model lists not resolving or streaming callbacks disappearing in React apps.

Correct integration:

const call = intelligence.run({
  type: 'text',
  model: 'qwen3-0.6b',
  prompt: 'Write a short welcome message.',
}, {
  stream: (chunk) => {
    // chunk is a string with the full response so far.
    // Example: "Hello" then "Hello there" then "Hello there!"
    // Use the value directly; replace your displayed text with chunk.
  },
  complete: (text) => {
    // text is the final response string.
    // Example: "Hello there!"
  },
  error: (error) => {
    // error is an object: { code: number, message: string }
    // Access the message with error.message.
  },
});

Keep app code at this level. The only supported surface is the exported intelligence object and its methods.

Check Availability

Local AI is only available inside a Despia app with Local AI support. Always gate feature entry points with intelligence.runtime.ok.

if (!intelligence.runtime.ok) {
  // Local AI is not available in this environment.
  return;
}
intelligence.runtime.ok       // boolean
intelligence.runtime.status   // 'ready' | 'outdated' | 'unavailable'
intelligence.runtime.message  // string | null

| Status | Meaning | | --- | --- | | ready | Local AI is available. | | outdated | The app should be updated before Local AI can be used. | | unavailable | Local AI is not available in the current environment. |

The package is safe to import outside Despia. In that case, runtime.ok is false and model/inference APIs return not-ready values instead of throwing.

First Inference

import intelligence from 'despia-intelligence';

if (!intelligence.runtime.ok) {
  // Local AI is not available in this environment.
  return;
}

const call = intelligence.run({
  type: 'text',
  model: 'qwen3-0.6b',
  system: 'Answer in three sentences or fewer.',
  prompt: 'How does TCP handle packet loss?',
  stream: true,
}, {
  stream: (chunk) => {
    // chunk is a string with the full accumulated response so far.
    // Example chunks: "TCP", "TCP handles", "TCP handles packet loss..."
    // Replace rendered text with chunk; do not append chunks together.
  },
  complete: (text) => {
    // text is the final response string.
    // Example: "TCP handles packet loss by retransmitting missing data..."
  },
  error: (error) => {
    // error is an object: { code: number, message: string }
    // Access the message with error.message.
  },
});

if (!call.ok) {
  // Show call.message in your UI.
}

Example run() params:

{
  "type": "text",
  "model": "qwen3-0.6b",
  "system": "Answer in three sentences or fewer.",
  "prompt": "How does TCP handle packet loss?",
  "stream": true
}

Example callback payloads:

// stream receives strings like:
"TCP"
"TCP handles"
"TCP handles packet loss by retransmitting missing data."

// complete receives the final string:
"TCP handles packet loss by retransmitting missing data."

// error receives an object like:
{
  code: 7,
  message: "Invalid model id"
}

Access values like normal JavaScript:

const handler = {
  stream: (chunk) => {
    // chunk is the text string.
  },
  complete: (text) => {
    // text is the final text string.
  },
  error: (error) => {
    // error.code is the numeric error code.
    // error.message is the readable error message.
  },
};

Model Selection

Models are installed per device. Do not assume a model id is usable until models.installed() confirms it.

The SDK only requires model.id. Any other model properties are native-provided metadata and should be treated as optional.

async function getModelId() {
  if (!intelligence.runtime.ok) return null;

  const installed = await intelligence.models.installed();
  if (Array.isArray(installed) && installed.length > 0) {
    return installed[0].id;
  }

  const available = await intelligence.models.available();
  const model = available[0];
  if (!model) return null;

  await new Promise((resolve, reject) => {
    intelligence.models.download(model.id, {
      onStart: () => {
        // Show download UI for model.
      },
      onProgress: (percent) => {
        // Update download progress with percent.
      },
      onEnd: resolve,
      onError: reject,
    });
  });

  return model.id;
}

If inference reports an invalid or unknown model id, reload models.installed() and choose from the installed list before calling run() again.

One Generation At A Time

Despia Local AI intentionally supports one active inference job at a time. Running multiple local generations in parallel can overload the device, so the SDK rejects a second run() while another job is active or pending soft-close resume.

const first = intelligence.run({
  type: 'text',
  model: 'qwen3-0.6b',
  prompt: 'First prompt',
});

const second = intelligence.run({
  type: 'text',
  model: 'qwen3-0.6b',
  prompt: 'Second prompt',
});

if (!second.ok && second.status === 'busy') {
  // Wait for the first job to complete/error, or cancel it, then try again.
}

If a second call provides handler.error, it receives:

{ code: 409, message: 'Another inference job is already running...' }

JSON shape:

{
  "code": 409,
  "message": "Another inference job is already running. Wait for it to finish or cancel it before starting a new one."
}

For user interfaces, disable the send button while generation is active. Re-enable it after complete, error, or an explicit cancel().

API Reference

intelligence.run(params, handler?)

Starts one text inference job.

type Params = {
  type: 'text'
  model?: string
  prompt?: string
  system?: string
  stream?: boolean
  [key: string]: unknown
}

type Handler = {
  stream?: (chunk: string) => void
  complete?: (text: string) => void
  error?: (error: { code: number; message: string }) => void
}

Successful return:

type CallHandle = {
  ok: true
  intent: Params
  cancel(): void
}

Rejected return:

type RunRejected = {
  ok: false
  status: 'outdated' | 'unavailable' | 'busy'
  message: string | null
  intent: null
  cancel(): void
}

cancel() removes the job from SDK routing. It prevents future callbacks from reaching your handler. It does not guarantee that an already-started generation stops immediately.

Example rejected return:

{
  "ok": false,
  "status": "busy",
  "message": "Another inference job is already running. Wait for it to finish or cancel it before starting a new one.",
  "intent": null
}

The actual return object also includes cancel(). Function fields are omitted from JSON examples because JSON cannot represent functions.

Common handler.error payloads:

{
  "code": 7,
  "message": "Invalid model id"
}
{
  "code": 409,
  "message": "Another inference job is already running. Wait for it to finish or cancel it before starting a new one."
}

intelligence.models.available()

Loads the installable model catalogue.

const models = await intelligence.models.available();

Returns Promise<Model[]>. If a cached catalogue is already available, the SDK returns it immediately. If Local AI is unavailable, or the model list does not load within 10 seconds, it resolves to [].

Example response:

[
  {
    "id": "qwen3-0.6b",
    "name": "Qwen 3 0.6B"
  },
  {
    "id": "gemma-3n-e2b-it",
    "name": "Gemma 3n E2B"
  }
]

Native may include extra metadata. Treat everything except id as optional:

[
  {
    "id": "qwen3-0.6b",
    "name": "Qwen 3 0.6B",
    "sizeBytes": 420000000,
    "quantization": "int4"
  }
]

intelligence.models.installed()

Loads models installed on the current device.

const installed = await intelligence.models.installed();

Returns Promise<Model[]>. If a cached installed-model list is already available, the SDK returns it immediately. If Local AI is unavailable, or the installed list does not load within 10 seconds, it resolves to [].

Example response:

[
  {
    "id": "qwen3-0.6b",
    "name": "Qwen 3 0.6B"
  }
]

No models installed:

[]

Not-ready response:

{
  "ok": false,
  "status": "unavailable",
  "message": null,
  "intent": null
}

The actual not-ready object also includes cancel(). Function fields are omitted from JSON examples because JSON cannot represent functions.

Model

type Model = {
  id: string
  name?: string
  [key: string]: unknown
}

Only id is required by the SDK.

intelligence.models.download(modelId, callbacks?)

Starts a model download.

intelligence.models.download('qwen3-0.6b', {
  onStart: () => {
    // Show download UI.
  },
  onProgress: (percent) => {
    // percent is an integer from 0 to 100.
  },
  onEnd: () => {
    // Mark the model as installed in your UI.
  },
  onError: (message) => {
    // Show message in your UI.
  },
});

Downloads are handled by Despia. They can continue while your app is backgrounded. The SDK does not restart downloads on foreground, because doing so could create duplicate transfers.

Callback meaning:

// onStart: the download started.
// onProgress: percent is an integer from 0 to 100.
// onEnd: the download finished.
// onError: message explains why the download failed.

intelligence.models.remove(modelId)

Removes one installed model.

await intelligence.models.remove('qwen3-0.6b');

intelligence.models.removeAll()

Removes all installed models.

await intelligence.models.removeAll();

intelligence.on(event, fn)

Registers a global download listener.

const off = intelligence.on('downloadProgress', (modelId, percent) => {
  // Update shared download state for modelId.
});

off();

Supported events:

  • downloadStart(modelId)
  • downloadProgress(modelId, percent)
  • downloadEnd(modelId)
  • downloadError(modelId, message)

Use off(event, fn) to remove a listener, or once(event, fn) to run a listener once.

Soft-Close Resume

Soft close means the user leaves the app and returns while the page is still alive. JavaScript memory survives, but the active generation may need to be restarted.

The SDK handles this case automatically for the single active inference job:

  1. The active job is snapshotted before suspension.
  2. When the app returns, the SDK starts that job again with the same params and handler.
  3. The restarted job uses the same params and continues delivering results to the same handler.

Important details:

  • Resume restarts generation. It does not continue from the exact token where the previous attempt stopped.
  • The same handler receives stream, complete, and error callbacks after resume.
  • Jobs that completed, errored, or were cancelled do not resume.
  • A new run() while a job is active or pending resume returns status: 'busy'.
  • If the OS fully closes the page, JavaScript memory is gone and the SDK cannot resume. Restore from your app state and call run() again.
  • Downloads are separate. Despia owns downloads, so the SDK does not restart them.

Production Guidance

  • Gate all Local AI UI with intelligence.runtime.ok.
  • Use models.installed() before inference.
  • Disable generation controls while a job is active.
  • Treat stream(chunk) as a full snapshot, not a delta.
  • Handle busy returns from run().
  • Handle empty arrays from model APIs.
  • Keep your own app state if you need recovery after the OS fully closes the page.

Guidance For AI-Generated Apps

When asking an AI app builder to use Despia Local AI, give it these constraints:

  • Import despia-intelligence and use intelligence.run, intelligence.models.available, intelligence.models.installed, intelligence.models.download, and intelligence.models.remove.
  • Do not create another Local AI integration. The SDK is the integration.
  • Do not use globals, browser navigation, network requests, workers, or custom callback plumbing for local inference.
  • Do not start multiple generations at once. Wait for complete, error, or call cancel() before starting another run().
  • Use only model ids returned by await intelligence.models.installed() for inference.
  • Treat stream(chunk) as the full text so far and replace the UI text with it.

Prompt snippet:

Use the despia-intelligence npm package only. Gate UI with
intelligence.runtime.ok. Load installed models with
intelligence.models.installed(). Start at most one intelligence.run(...) at a
time. In stream(chunk), replace the displayed text; do not append chunks.

Demo And Tests

This repository includes demo.html, a standalone page for testing the SDK inside Despia.

Run tests:

npm test

License

MIT