Skip to content

Repository files navigation

@dioschub/client

The official client SDK for the Diosc AI Assistant Kit — a typed, namespaced API for embedding and driving the Diosc chat assistant on your web app.

Zero runtime dependencies. Framework-agnostic (React, Vue, Angular, vanilla).


Mental model (read this first)

A few things shape the entire API:

  • The kit owns the chat UI. When you embed Diosc, the assistant-kit web components are loaded from DioscHub at runtime and render the whole chat experience. You don't build a chat UI; you configure one and wire it to your app.
  • Two display layouts, one widget. The same <diosc-chat> renders either as a FAB (floating bubble, default) or embed (inline panel). There is no headless mode — the SDK never asks you to render messages, sessions, or approvals yourself.
  • Two planes of integration:
    • Control — drive the widget (connect, send, open/close).
    • Extend — give the AI host capabilities (tools, mentions, page context, custom approval UI, navigation).
  • Identity is BYOA (Bring Your Own Auth). Diosc never sees your credentials. Your app authenticates the user and binds that identity to the connection server-to-server. There is no auth-header / token API in this SDK by design.

This package gives you two entry points:

Entry point Use when
createDiosc() The supported, fully-typed instance API. Use this.
loadDiosc() Lower-level script injector + raw command queue. Use only if you need the gtag-style global directly.

Installation

npm install @dioschub/client

Quick start

import { createDiosc } from '@dioschub/client';

const diosc = createDiosc({
  apiKey: 'ak_xxx',                       // public embed key (like a Maps key)
  backendUrl: 'https://hub.example.com',
});

await diosc.ready;                        // optional — resolves when the kit loads

diosc.on('stream:chunk', (c) => console.log(c));
diosc.send('Hello!');

Then render the widget anywhere in your page:

<diosc-chat></diosc-chat>

That's the whole happy path. Everything below is opt-in.


The widget element

The kit auto-mounts <diosc-chat>. You can configure it via createDiosc(...) (recommended) or with attributes:

<!-- FAB (default) -->
<diosc-chat></diosc-chat>

<!-- Inline embed panel -->
<diosc-chat mode="embed"></diosc-chat>

<!-- Fully attribute-driven (no JS) -->
<diosc-chat
  api-key="ak_xxx"
  backend-url="https://hub.example.com"
  bind-endpoint="/api/diosc/bind">
</diosc-chat>
Attribute Maps to
api-key apiKey
backend-url backendUrl
assistant-id assistantId (auto-detected if omitted)
bind-endpoint bindEndpoint (see Identity)
mode "embed" for inline; omit for FAB

The instance API

createDiosc(options) returns a DioscInstance with a small core plus three namespaces: ui, extend, identity.

createDiosc(options)

options is your DioscConfig plus two loader controls:

Option Type Default Notes
apiKey string Public embed key.
backendUrl string DioscHub URL.
assistantId string auto Resolved from config fetch if omitted.
bindEndpoint string Your server endpoint for BYOA bind.
autoConnect boolean true Connect on init.
verbose boolean false Verbose logging.
reconnectAttempts / reconnectDelay / reconnectDelayMax / connectionTimeout number Transport tuning.
scriptUrl string derived Custom kit script URL.
autoLoad boolean true Set false if the loader <script> is already on the page.

Core

await diosc.ready;                         // Promise<void> — kit script loaded

await diosc.connect();                     // open the connection (no-op if autoConnect)
diosc.disconnect();

await diosc.send('Summarize my order', { pageContext: { orderId } });
diosc.cancelStream();                      // drain the in-flight response

const off = diosc.on('stream:chunk', (chunk) => render(chunk));
off();                                     // unsubscribe

diosc.onAny((event, payload) => log(event, payload));

await diosc.fetchAssistantConfig();        // re-fetch public assistant config

Attachments caveat: send(text, { attachments }) accepts an attachments field for forward-compatibility, but it is not wired end-to-end yet — today it is a no-op (file sends go through the composer UI). See Known gaps.

ui — drive the widget shell

diosc.ui.open();
diosc.ui.close();
diosc.ui.toggle();
diosc.ui.setPosition('bottom-left');       // 'bottom-left' | 'bottom-right'

// Change listeners (fire immediately with the current value, then on change):
const offOpen = diosc.ui.onOpenChange((isOpen) => syncMyButton(isOpen));
const offPos  = diosc.ui.onPositionChange((pos) => layout(pos));

extend — give the AI host capabilities

This is the high-value plane. Everything here is optional.

Tools — let the AI call your code

diosc.extend.tool('navigate', async (params) => {
  router.push(params.path);
  return { navigatedTo: params.path };
});

Mentions — populate the composer @-popover

The host filter is authoritative — return exactly what the popover should show. Selecting an item serializes to the wire format @[Name](kind:id), where id is what the LLM sees.

const people = [{ id: 'u_1', name: 'Ada', kind: 'user' }];

diosc.extend.mentions((needle) =>
  people.filter((p) => p.name.toLowerCase().includes(needle.toLowerCase())),
);

diosc.extend.mentions(null);               // clear

Browser adapter — expose page state + actionable intents

The adapter gives the AI a fresh page snapshot each turn plus a set of intents it may invoke. Each intent can declare a client-local approval gate that the AI cannot bypass.

diosc.extend.browser({
  read: async () => ({ url: location.href, title: document.title, data: getState() }),
  intents: [
    {
      name: 'apply_coupon',
      description: 'Apply a discount coupon to the cart',
      schema: { type: 'object', properties: { code: { type: 'string' } }, required: ['code'] },
      handler: async ({ code }) => {
        await cart.applyCoupon(code);
        return { success: true, data: { code } };
      },
      // Optional: gate behind a local approval dialog before running
      approval: {
        severity: 'medium',
        summary: ({ code }) => `Apply coupon ${code}?`,
      },
    },
  ],
});

Custom consensus view — restyle the approval body

The kit ships the consensus (approval) dialog and always owns the decision. What you can replace is the dialog's detail body, per tool, so a gated call reads in your domain's terms instead of a generic schema form:

const off = diosc.extend.consensusView(/acme-helpdesk_/, {
  mount(el, ctx) {
    // `el` is a light-DOM anchor — your stylesheets apply. Portal into it from
    // your own component tree to keep context/providers.
    render(<TicketBody ctx={ctx} />, el);
  },
  update(ctx) { /* operator edited a field, or switched request */ },
  unmount() { /* teardown */ },
});

ctx.fields is the kit's computed diff (render from it, don't re-derive), and ctx.setField(key, value) reports an edit, which becomes modifiedArgs on submit. The descriptor has no approve/reject — see below.

There is intentionally no diosc.approve() / reject(), and no host-rendered approval surface at all. Approval decisions resolve only from the built-in consensus dialog. This preserves the human-in-the-loop (Responsibility-First) guarantee.

Navigation observer — push SPA route changes into chat

diosc.extend.observeNavigation((notify) => {
  const stop = router.afterEach((route) => notify({ path: route.path }));
  return stop;                             // return a cleanup fn (optional)
});

identity — BYOA

Diosc never receives your credentials. Your app authenticates the user and binds that identity to the live connection server-to-server.

Flow:

  1. You set bindEndpoint (a route on your server).
  2. When the connection needs identity, the kit POSTs { wsId } to your bindEndpoint with credentials: 'include' (so your auth cookie rides along).
  3. Your endpoint authenticates the user and forwards the identity to DioscHub's POST /auth/bind. The bound identity (and feature flags) flow back to the kit.
const diosc = createDiosc({
  apiKey: 'ak_xxx',
  backendUrl: 'https://hub.example.com',
  bindEndpoint: '/api/diosc/bind',
});

// If your auth lives in JS (e.g. a Bearer token) rather than a cookie,
// supply extra headers for the bind request:
diosc.identity.setBindHeaders(() => ({ Authorization: `Bearer ${getToken()}` }));

// After the user signs in (e.g. anonymous → authenticated), rebind in place:
diosc.identity.reauth();

A minimal bindEndpoint on your server:

// POST /api/diosc/bind   body: { wsId: string }
app.post('/api/diosc/bind', requireAuth, async (req, res) => {
  const { wsId } = req.body;
  // Forward the authenticated identity to DioscHub.
  await fetch(`${HUB_URL}/auth/bind`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      wsId,
      identity: { userId: req.user.id, username: req.user.name, role: req.user.role },
    }),
  });
  res.sendStatus(204);
});

See the DioscHub docs for the authoritative bind protocol, including feature flags and REST-token rotation.

Events

Subscribe with diosc.on(event, handler) (typed) or diosc.onAny(handler).

Event Payload (shape) Meaning
stream:start { … } Assistant began responding
stream:chunk { … } Streamed content delta
stream:end { … } Response complete
tool:started { toolCallId, toolName, toolParameters } A tool call started
tool:completed { toolCallId, toolName, durationMs, resultSummary } Tool call finished
tool:failed { toolCallId, toolName, errorMessage } Tool call failed
approval:request { toolCalls, … } A human decision is required
content_blocked { reason, message } Output guardrail tripped
session:started / joined / loaded / restored { session, … } Session lifecycle
session:renamed / pinned { sessionId, … } Session list change
auth:refreshed / auth:failed { … } Identity bind lifecycle
files:updated { fileId?, files? } File set changed
browser:read_page { … } Kit requested a page snapshot

ProtocolEventName is the full typed union; on() also accepts any string for forward-compat.

Short-lived AI triggers (one-shot, non-chat calls like generate a description from the selected items) are out of scope for this SDK. Today they belong on your backend. If introduced later, they will ship as UI components, not as a bare client method. See the DioscHub docs.


Framework examples

React

import { useEffect, useRef } from 'react';
import { createDiosc, type DioscInstance } from '@dioschub/client';

export function Assistant() {
  const ref = useRef<DioscInstance>();

  useEffect(() => {
    const diosc = createDiosc({
      apiKey: import.meta.env.VITE_DIOSC_KEY,
      backendUrl: import.meta.env.VITE_DIOSC_URL,
      bindEndpoint: '/api/diosc/bind',
    });
    ref.current = diosc;

    diosc.extend.tool('navigate', async ({ path }) => {
      window.history.pushState({}, '', path);
      return { navigatedTo: path };
    });

    const off = diosc.on('approval:request', () => toast('Action needs approval'));
    return () => off();
  }, []);

  return <diosc-chat />;
}

Vue

<template><diosc-chat /></template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { createDiosc } from '@dioschub/client';

onMounted(() => {
  const diosc = createDiosc({
    apiKey: import.meta.env.VITE_DIOSC_KEY,
    backendUrl: import.meta.env.VITE_DIOSC_URL,
  });
  diosc.extend.observeNavigation((notify) =>
    router.afterEach((r) => notify({ path: r.path })),
  );
});
</script>

Angular

import { Injectable } from '@angular/core';
import { createDiosc, type DioscInstance } from '@dioschub/client';

@Injectable({ providedIn: 'root' })
export class DioscService {
  readonly diosc: DioscInstance = createDiosc({
    apiKey: environment.dioscKey,
    backendUrl: environment.dioscUrl,
    bindEndpoint: '/api/diosc/bind',
  });

  constructor() {
    this.diosc.identity.setBindHeaders(() => ({
      Authorization: `Bearer ${this.auth.token}`,
    }));
  }
}

Vanilla JS

<script type="module">
  import { createDiosc } from 'https://esm.sh/@dioschub/client';

  const diosc = createDiosc({ apiKey: 'ak_xxx', backendUrl: 'https://hub.example.com' });
  diosc.ui.setPosition('bottom-left');
</script>

<diosc-chat></diosc-chat>

Lower-level: loadDiosc + the raw command doorway

If you need the gtag-style global directly (or are migrating older code), loadDiosc() injects the script and returns the raw command function and a ready promise. The raw doorway is also reachable via instance.raw.

import { loadDiosc } from '@dioschub/client';

const { diosc, ready } = loadDiosc({ backendUrl: 'https://hub.example.com', apiKey: 'ak_xxx' });
diosc('config', { autoConnect: true });    // commands buffered until the script loads
await ready;

createDiosc() is built on top of loadDiosc() — prefer the instance API; reach for the raw doorway only as an escape hatch.


What this SDK intentionally does not have

These omissions are deliberate design decisions, not gaps:

  • No headless mode. The kit renders the chat UI (FAB/embed). You never build message lists, session pickers, or the approval dialog yourself.
  • No host session API. loadSession / startNewSession / renameSession / etc. are driven by the kit's own session-history panel, not host code.
  • No approve / reject global, and no host-rendered approval surface. Decisions resolve only from the kit's own consensus dialog. You may restyle its body via extend.consensusView — that carries no verdict channel — Responsibility-First.
  • No auth-header / token API. Identity is bound server-to-server via bindEndpoint. The credential-blind principle means Diosc never sees tokens.

See the DioscHub docs for the full rationale.


Known gaps

Gap Status
send(text, { attachments }) attachments is accepted but a no-op (engine invoke doesn't forward it yet).

Exported types

DioscInstance, CreateDioscOptions, DioscUi, DioscExtend, DioscIdentity, SendOptions, WidgetPosition, DioscConfig, BoundIdentity, BoundRole, NavigationData, NavigationObserverCallback, BrowserToolHandler, BrowserAdapter, PageSnapshot, IntentDefinition, IntentApproval, IntentResult, JsonSchema, MentionItem, MentionQuery, ApprovalRequest, ApprovalToolCall, ApprovalActions, ApprovalValidationError, ApprovalHandler, SessionEventName, ServerEventName, ProtocolEventName, EventHandler, WildcardEventHandler, Unsubscribe, DioscCommand, DioscFunction, DioscEngineHandle, LoadDioscOptions, LoadDioscResult.


License

MIT

About

Client for dioschub assistant kit.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages