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

@alia.onl/sdk

v11.1.0

Published

Alia AI chat SDK — reusable chat UI, voice, and streaming components for Oxy apps

Readme

@alia.onl/sdk

Alia's chat SDK for React Native and Expo apps built on Oxy: the chat sheet and screen, the streaming hook, speech in and out, and the primitives they are made of. It renders inside the one OxyProvider your app already mounts and sends the signed-in user's Oxy session with every request; it never holds a token of its own.

The package ships raw TypeScript source — main, types and every exports entry point at src/. Your bundler compiles it with the rest of your app, which is why the section on endpoints below matters more than it would for a prebuilt library.

Install

bun add @alia.onl/sdk

react, react-native, @oxy.so/core, @oxy.so/services and the Expo modules listed under peerDependencies in package.json are yours to install. Metro must resolve package exports (resolver.unstable_enablePackageExports = true, the default in Expo SDK 53+), because every icon import is a subpath.

The chat is built from Bloom (@oxy.so/bloom, a peer dependency): mount Bloom's BloomProvider above it, with the one KeyboardProvider your app already has.

Alia's avatar is Bloom's 3D character (AgentAvatar), which draws through a runtime Bloom ships as static files (about 11 MB). Serve the package's assets/character-runtime/ directory unchanged and give the URL of runtime.mjs to Bloom's provider, once, near the root:

import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';

<AgentAvatarProvider runtimeUrl="/bloom-character/runtime.mjs">…</AgentAvatarProvider>

Without it the avatar shows Bloom's "unavailable" state. Native avatars run in a WebView, so there the URL must be absolute and reachable from the device.

Failures (a voice call that cannot start, a dictation error) are shown with Bloom's toast, so the app needs a ToastOutlet (OxyProvider mounts one), or pass onError to handle them.

The chat's own copy is English. Pass labels to AliaChatSheet or AliaChatScreen to translate it (AliaChatLabels; anything omitted stays English). Bloom's own strings follow BloomProvider's locale.

Two entry points:

  • @alia.onl/sdk — text chat, dictation, read-aloud, catalogue, primitives.
  • @alia.onl/sdk/voice — VoiceSession and useVoiceRoom, the voice call. Pass VoiceSession to the shell as voiceSession to offer calls; leave it out for text-only chat and the call is never compiled.

Speech

Dictation (useSpeechToText) and the voice call (useVoiceRoom) recognize speech on the device: the Web Speech API in browsers that have it (Chromium and Safari — not Firefox, where useSpeechToText().isSupported is false), and expo-speech-recognition on iOS and Android. No audio is sent to Alia; whether the platform's recognizer uses a network service of its own is the platform's (Chrome's does).

On native, add expo-speech-recognition (a peer dependency, like expo-audio: the root entry's mic button reaches it) and its config plugin to your app, then make a new native build:

["expo-speech-recognition", {
  "microphonePermission": "Allow $(PRODUCT_NAME) to hear what you dictate and say in voice calls.",
  "speechRecognitionPermission": "Allow $(PRODUCT_NAME) to turn your speech into text."
}]

A voice call is a turn loop, not a realtime session: it listens until the person pauses, sends what they said through the ordinary chat path (POST /v1/chat/completions with responseMode: "voice", or your own sendTurn), and speaks the answer sentence by sentence through POST /v1/audio/speech in the product voice (male / female). Talking over the answer interrupts it. Pass lang to both hooks to follow your app's language.

Use

import { AliaChatScreen } from '@alia.onl/sdk';

export function Assistant() {
  return <AliaChatScreen />;
}

AliaChatScreen is a full-screen route: it pads itself by the top and bottom safe-area insets, so render it where no tab bar or other bottom chrome is drawn. On iOS and Android its composer rises with the keyboard through react-native-keyboard-controller, which needs the app wrapped in its KeyboardProvider (re-exported here).

Or the hook on its own, with your own UI:

import { useAliaChat } from '@alia.onl/sdk';

const { messages, send, isStreaming, stop, error } = useAliaChat({
  clientContext: 'The user is looking at their order history.',
});

useAliaChat streams a turn as Server-Sent Events and switches on Alia's product events — alia.reasoning, alia.tool_result, alia.research_progress, alia.plan_preview, delegated-agent answers — into ChatMessage fields your components render. Those events are the product runtime's, not part of the Oxy generic inference contract, and this package is written against them on purpose: it is a product client.

Which endpoint the SDK calls, and why

The hook sends its turns to POST {apiUrl}/v1/chat/completions, with apiUrl defaulting to EXPO_PUBLIC_ALIA_API_URL or https://api.alia.onl. It also reads GET {apiUrl}/catalogue once per base URL when a model is passed, to check that publisher/model against the real models the server offers. With no model, the request carries none and the server's default model answers — the SDK ships no model identifier of its own.

That is the compatibility surface, not the product route. POST /alia/chat is the same handler with the same authentication, and it is where Alia's own clients go — but the two mounts answer browsers differently:

| Preflight | access-control-allow-origin | | --- | --- | | OPTIONS /alia/chat from https://alia.onl or https://console.alia.onl | that origin, with credentials | | OPTIONS /alia/chat from an origin Alia does not enumerate | absent | | OPTIONS /v1/chat/completions from anywhere | * |

/alia/chat takes Alia's exact-origin allowlist (packages/api/src/lib/cors-origins.ts); /v1 is public CORS. Because this package compiles into your app on your origin, defaulting the hook to /alia/chat would fail every consumer's preflight on web. So it stays on /v1/chat/completions, which is Alia's permanent product API — it does not sunset (ADR 0010). Generic model access is a different product, Kaana through Oxy; this package is written against Alia's, and the credential a consumer presents is an Oxy one — the signed-in user's session today. A change of default to /alia/chat would be a semver-major of this package, would reach only consumers who upgrade and rebuild, and is not needed to escape a sunset; it is taken only if the per-application origin policy below makes it worthwhile (#244, closed on that decision).

Native apps are not subject to CORS at all — React Native sends no Origin header — so the table above is about web builds and the backend path below.

Using the SDK from your own product: the consumer-backend path

The supported way for an application outside Alia's origins to use this SDK today is to put your own backend between the SDK and Alia. Your backend receives the SDK's request, calls Alia's product route server-to-server — a server sends no Origin header, so /alia/chat answers it on its credential alone — and streams the SSE body back unchanged. Then point the hook at that backend:

useAliaChat({ apiUrl: 'https://your-backend.example' });

The hook keeps its paths, so your backend answers POST /v1/chat/completions and GET /catalogue under whatever base URL you give it. Nothing about the stream changes: the SDK's parser reads the bytes Alia wrote.

Which credential. The SDK attaches the signed-in user's Oxy access token as Authorization: Bearer … on the request your backend receives. Forward that header as it is. POST /alia/chat authenticates with authenticateTokenOrApiKey (packages/api/src/routes/chat.ts, packages/api/src/middleware/auth.ts), which accepts an Oxy session token — verified with Oxy, the same way it would be if the browser had called Alia directly — and the turn is metered to that user's Alia entitlement, the same as a direct call. The other credentials the middleware recognises are Alia-internal. There is no consumer-application credential for this route yet — that is Oxy Applications' to issue (OxyHQ/oxy#972).

A minimal relay, Node 18+ and Express:

import express from 'express';

const ALIA = 'https://api.alia.onl';
const app = express();
app.use(express.json({ limit: '1mb' }));

/** Relay one request to Alia and stream the answer back unchanged. */
async function relay(
  req: express.Request,
  res: express.Response,
  method: 'GET' | 'POST',
  path: string,
): Promise<void> {
  const controller = new AbortController();
  req.on('close', () => controller.abort());

  const authorization = req.get('authorization');
  const upstream = await fetch(`${ALIA}${path}`, {
    method,
    headers: {
      ...(authorization ? { Authorization: authorization } : {}),
      Accept: req.get('accept') ?? 'application/json',
      ...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),
    },
    body: method === 'POST' ? JSON.stringify(req.body) : undefined,
    signal: controller.signal,
  });

  res.status(upstream.status);
  res.setHeader('Content-Type', upstream.headers.get('content-type') ?? 'application/json');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('X-Accel-Buffering', 'no'); // keep proxies from buffering the SSE stream
  res.flushHeaders();

  if (upstream.body === null) {
    res.end();
    return;
  }
  try {
    for await (const chunk of upstream.body) res.write(chunk);
  } catch (error) {
    if (!controller.signal.aborted) throw error;
  } finally {
    res.end();
  }
}

// The SDK's turn: answered by Alia's PRODUCT route, server-to-server.
app.post('/v1/chat/completions', (req, res) => relay(req, res, 'POST', '/alia/chat'));

// The SDK's model check. Without this the hook still works — an unreadable
// catalogue leaves the requested identifier alone — but it never learns what
// the server offers.
app.get('/catalogue', (req, res) => relay(req, res, 'GET', '/catalogue'));

app.listen(3000);

The request body the SDK sends is { model?, messages, stream: true } and the response Alia writes is text/event-stream; the relay forwards both without reading them. CORS between your web app and your backend is yours to configure, as it would be for any of your own routes.

The trade-off. This is one more hop, and one more process in the path of a stream. Some consumers will not want it, which is why it is documented as the path that works today rather than chosen as the only one. The other two shapes — a CORS policy on /alia/chat for registered consumer origins, and the SDK default moving to /alia/chat in a major — are recorded in #244: the first is blocked on Oxy Applications carrying an origin registry (OxyHQ/oxy#972), and the second is an adoption window rather than a switch, for the raw-source reason above.

Scripts

bun run typecheck       # tsc --noEmit
bun run test            # vitest
bun run check:entries   # the root entry must not reach the voice call

Licence

Apache-2.0. See LICENSE and NOTICE.