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

@aquantinc/acp-web-sdk

v0.1.3

Published

Browser SDK for Aquant Conversation Platform — chat, voice, and session management

Downloads

52

Readme

@aquantinc/acp-web-sdk

Browser SDK for the Aquant Conversation Platform — voice calls, chat messaging, and session management from a single, framework-agnostic package.

Works with any JavaScript environment that runs in a browser: plain <script> tags, React, Vue, Angular, Svelte, or any bundler.

Install

With a package manager (React, Vue, Angular, etc.):

npm install @aquantinc/acp-web-sdk

Without a bundler (plain HTML/JS):

No install needed. Serve the SDK's dist/ folder or copy dist/index.js into your project and import it directly:

<script type="module">
  import { AquantClient } from "./path-to/dist/index.js";
  // ready to use
</script>

The SDK handles all voice dependencies automatically at runtime — no extra <script> tags or installs required.

Quick Start

With a bundler (React, Vue, etc.)

import { AquantClient } from "@aquantinc/acp-web-sdk";

const client = new AquantClient({
  apiKey: "your-api-key",
  apiSecret: "your-api-secret",
  agentId: "your-agent-id",
});

await client.initialize();

const agent = client.voice.agentInfo;
console.log(`Agent: ${agent?.agentName} (${agent?.agentPhone})`);

await client.voice.startCall();

// Later…
await client.voice.endCall();
await client.destroy();

Without a bundler (plain HTML)

<!DOCTYPE html>
<html>
<body>
  <button id="call">Start Call</button>
  <button id="mute" disabled>Mute</button>
  <button id="end" disabled>End Call</button>

  <script type="module">
    import { AquantClient } from "./dist/index.js";

    const client = new AquantClient({
      apiKey: "your-api-key",
      apiSecret: "your-api-secret",
      agentId: "your-agent-id",
    });

    document.getElementById("call").onclick = async () => {
      if (!client.isInitialized) await client.initialize();
      await client.voice.startCall();
      document.getElementById("mute").disabled = false;
      document.getElementById("end").disabled = false;
    };

    document.getElementById("mute").onclick = () => {
      if (client.voice.isMuted) client.voice.unmute();
      else client.voice.mute();
    };

    document.getElementById("end").onclick = async () => {
      await client.voice.endCall();
    };
  </script>
</body>
</html>

Browser requirement: The browser will prompt the user for microphone access when a call starts. The SDK requires a modern browser with WebRTC support (Chrome, Edge, Firefox, Safari 14.1+).

Configuration

const client = new AquantClient({
  apiKey: string;       // API key (required)
  apiSecret: string;    // API secret (required)
  agentId: string;      // Agent identifier (required)
  baseUrl?: string;     // VoiceAI server URL (default: "https://voiceai-api.aquant.ai")
  callerId?: string;    // Caller identifier (default: "acp-sdk-user")
  voiceAdapter?: VoiceAdapter;  // Optional — swap VoIP backend (default: Twilio browser SDK)
  chatAdapter?: ChatAdapter;    // Optional — swap chat transport (default: VoiceAI POST /vss/web-chat SSE)
  debug?: boolean;      // Enable debug logging (default: false)
});

| Property | Required | Default | Description | |-----------------|----------|--------------------------------------|--------------------------------------| | apiKey | Yes | — | Your API key | | apiSecret | Yes | — | Your API secret | | agentId | Yes | — | AI agent identifier | | baseUrl | No | https://voiceai-api.aquant.ai | Server URL (override for dev/staging)| | callerId | No | acp-sdk-user | Identifies the caller to the system | | voiceAdapter | No | Twilio-based adapter | Custom VoiceAdapter implementation | | chatAdapter | No | SSE web-chat adapter | Custom ChatAdapter implementation | | debug | No | false | Enables verbose console logging |

Authentication

The SDK uses API key / secret authentication. During client.initialize(), the SDK exchanges your credentials via POST /acp/token to obtain an access token and agent metadata (name, phone number). Tokens are valid for 1 hour — to refresh, call destroy() and re-initialize.

Client Lifecycle

new AquantClient(config)  →  client.initialize()  →  use voice/chat/sessions  →  client.destroy()
        ↑                          ↑                                                     ↑
   Creates the client.     Fetches token, registers      Cleans up voice device,
   No network calls yet.   voice device; initializes     chat adapter, and internal
                           chat adapter. Required        state.
                           before voice/chat APIs.

You can check client.isInitialized and client.isDestroyed at any time.

Usage with React

The SDK is framework-agnostic, but here's the recommended React pattern:

import { useCallback, useEffect, useRef, useState } from "react";
import { AquantClient, type VoiceCallState } from "@aquantinc/acp-web-sdk";

export default function VoiceCall() {
  const clientRef = useRef<AquantClient | null>(null);
  const [callState, setCallState] = useState<VoiceCallState | null>(null);
  const [calling, setCalling] = useState(false);

  // Create client once, destroy on unmount
  useEffect(() => {
    const client = new AquantClient({
      apiKey: "your-api-key",
      apiSecret: "your-api-secret",
      agentId: "your-agent-id",
    });
    clientRef.current = client;

    client.on("voice.call.ended", () => {
      setCalling(false);
      setCallState(null);
    });

    return () => { client.destroy(); };
  }, []);

  const handleStartCall = async () => {
    const client = clientRef.current!;
    setCalling(true);
    try {
      if (!client.isInitialized) await client.initialize();
      const state = await client.voice.startCall();
      setCallState(state);
      client.voice.onStateChange(setCallState);
    } catch (e: any) {
      setCalling(false);
      console.error(e.message);
    }
  };

  const handleEndCall = async () => {
    await clientRef.current!.voice.endCall();
  };

  const handleToggleMute = () => {
    const voice = clientRef.current!.voice;
    if (voice.isMuted) voice.unmute(); else voice.mute();
    setCallState({ ...voice.callState });
  };

  return (
    <div>
      <button onClick={handleStartCall} disabled={calling}>Start Call</button>
      <button onClick={handleToggleMute} disabled={!callState}>
        {callState?.muted ? "Unmute" : "Mute"}
      </button>
      <button onClick={handleEndCall} disabled={!callState}>End Call</button>
      {callState && <p>Status: {callState.status}</p>}
    </div>
  );
}

Key React tips:

  • Store the client in a useRef — it should not trigger re-renders.
  • Create the client in useEffect and call client.destroy() in the cleanup function.
  • Use client.isInitialized to lazy-initialize on first action (avoids a separate "Initialize" button).
  • Use onStateChange to push call state updates into React state.

Imports

The SDK exposes a main entry point and subpath imports:

// Full SDK
import { AquantClient, AquantError } from "@aquantinc/acp-web-sdk";

// Chat module only
import { ChatClient } from "@aquantinc/acp-web-sdk/chat";

// Voice module only
import { VoiceClient } from "@aquantinc/acp-web-sdk/voice";

Voice

How it works

  1. client.initialize() exchanges your credentials for an access token and retrieves agent metadata (name, phone number).
  2. The SDK sets up the underlying voice device automatically.
  3. startCall() connects to the agent with optimized WebRTC audio (echo cancellation, noise suppression, auto gain control).
  4. Audio flows: Browser ↔ VoiceAI ↔ AI Agent.

API

// Initialize (fetches token, registers voice device)
await client.initialize();

// Agent info is available after initialization
const agent = client.voice.agentInfo;
// { agentId: string, agentName: string, agentPhone: string }

// Start a call
const callState = await client.voice.startCall();

// Mute / unmute
client.voice.mute();
client.voice.unmute();
client.voice.isMuted; // boolean

// Listen for state changes
client.voice.onStateChange((state) => {
  console.log(state.status); // "idle" | "connecting" | "ringing" | "connected" | "disconnected" | "failed"
  console.log(state.muted);  // boolean
});

// End the call
await client.voice.endCall();

// Full call state at any time
client.voice.callState;
// { status, muted, agentInfo? }

Call Status Flow

idle → connecting → ringing → connected → disconnected
                                  ↓
                               failed

Advanced: Custom Voice Adapter

The voice module is provider-agnostic. You can swap the built-in provider for any VoIP backend by implementing the VoiceAdapter interface:

import type { VoiceAdapter } from "@aquantinc/acp-web-sdk/voice";

class MyCustomAdapter implements VoiceAdapter {
  readonly name = "my-provider";
  // Implement: initialize, startCall, endCall, mute, unmute,
  //            onStatusChange, destroy, status, isMuted, agentInfo
}

const client = new AquantClient({
  apiKey: "...",
  apiSecret: "...",
  agentId: "...",
  voiceAdapter: new MyCustomAdapter(),
});

Sessions

const session = await client.sessions.create({
  metadata: { source: "web-app" },
});

const existing = await client.sessions.get("sess_123");

await client.sessions.updateMetadata("sess_123", { resolved: true });

await client.sessions.end("sess_123", { reason: "resolved" });

Chat

Chat uses POST {baseUrl}/vss/web-chat with JSON { agent_id, sender_id, message }. The server streams the assistant reply as Server-Sent Events (text/event-stream): each data: line carries JSON with a text delta, then { "done": true }. There is no WebSocket — the SDK consumes the stream with fetch and ReadableStream.

After initialize(), call connect({ sessionId }) once per conversation thread. The sessionId is sent as sender_id (stable id for that user/thread — often the id from sessions.create()).

await client.initialize();

client.chat.connect({ sessionId: session.id });

const unsubscribe = client.chat.onMessage((message) => {
  // User message once; assistant messages update as deltas arrive (same id, growing `text`).
  console.log(`[${message.role}]: ${message.text}`);
});

const reply = await client.chat.sendMessage({
  sessionId: session.id,
  text: "Can you help me?",
});
// `reply` is the final assistant message (full accumulated text).

client.chat.onConnectionStatusChange((status) => {
  console.log("Chat:", status); // "disconnected" | "connected"
});

client.chat.disconnect();

Advanced: Custom Chat Adapter

Implement ChatAdapter and pass chatAdapter if you need a non-default backend (the default streams VoiceAI web-chat SSE).

import type { ChatAdapter } from "@aquantinc/acp-web-sdk";

class MyChatAdapter implements ChatAdapter {
  readonly name = "custom";
  // initialize, connect, disconnect, streamAssistantReply, destroy, isConnected
}

const client = new AquantClient({
  apiKey: "...",
  apiSecret: "...",
  agentId: "...",
  chatAdapter: new MyChatAdapter(),
});

Events

Subscribe to typed events across all modules:

// Voice
client.on("voice.call.started",   (state) => { /* ... */ });
client.on("voice.call.connected", (state) => { /* ... */ });
client.on("voice.call.ended",     (state) => { /* ... */ });

// Chat
client.on("chat.message.received", (msg) => { /* ... */ });
client.on("chat.message.sent",     (msg) => { /* ... */ });

// Sessions
client.on("session.created", (session) => { /* ... */ });
client.on("session.ended",   (session) => { /* ... */ });

// Errors
client.on("error", (error) => { /* ... */ });

// One-time listener
client.once("voice.call.connected", (state) => { /* ... */ });

// Unsubscribe
const unsub = client.on("error", handler);
unsub();

Error Handling

All SDK errors extend AquantError with a typed error code:

import { AquantError, isAquantError } from "@aquantinc/acp-web-sdk";

try {
  await client.initialize();
} catch (error) {
  if (isAquantError(error)) {
    console.error(error.code);    // e.g. "AUTH_INVALID_CREDENTIALS"
    console.error(error.message); // human-readable description
    console.error(error.details); // optional extra context
  }
}

| Error Code | When | |-----------------------------|---------------------------------------------------| | AUTH_INVALID_CREDENTIALS | Invalid API key or secret (HTTP 401) | | AGENT_NOT_FOUND | No agent found for the given agentId (HTTP 404) | | AGENT_NOT_ACTIVE | Agent exists but is not active (HTTP 400) | | VOICE_CONNECTION_FAILED | Voice device or call connection failed | | VOICE_NOT_CONNECTED | Action requires an active call but none exists | | CLIENT_NOT_INITIALIZED | Method called before initialize() | | CLIENT_DESTROYED | Method called after destroy() | | NETWORK_UNAVAILABLE | Network request failed | | SESSION_NOT_FOUND | Session ID not found | | SESSION_CREATE_FAILED | Session creation failed | | CHAT_SEND_FAILED | Chat request failed (network, HTTP error, or SSE error payload) | | CHAT_CONNECTION_FAILED | sendMessage without connect(), or sessionId mismatch |

Development

npm install       # install dependencies
npm run build     # build ESM + CJS + types
npm test          # run tests (Vitest)
npm run lint      # type-check with tsc

Project Structure

src/
  index.ts                # Root entry point & public exports
  core/
    client.ts             # AquantClient — main entry point
    config.ts             # Config validation, defaults, headers
    events.ts             # Typed EventBus
    errors.ts             # AquantError + error codes
    sessions.ts           # SessionManager
    types.ts              # Shared type definitions
  chat/
    index.ts              # Chat subpath entry
    chatClient.ts         # ChatClient (public API)
    chatAdapter.ts        # ChatAdapter interface + WebChatAdapter (SSE) + default stub
    types.ts              # Chat-specific types
  voice/
    index.ts              # Voice subpath entry
    voiceClient.ts        # VoiceClient (public API)
    voiceAdapter.ts       # VoiceAdapter interface + DefaultVoiceAdapter (stub)
    twilioVoiceAdapter.ts # Built-in Twilio browser SDK adapter
    types.ts              # Voice-specific types
  utils/
    logger.ts             # Leveled logger
    guards.ts             # Runtime assertion helpers

License

MIT