@bymos/agentkit-sdk
v0.8.0
Published
AgentKit embeddable widget — CDN build and npm package
Downloads
1,208
Readme
@bymos/agentkit-sdk
Embeddable AI chat widget for KlicForge. Add a fully-featured, themeable chat widget to any website or app — via a <script> tag or npm.
Installation
npm install @bymos/agentkit-sdk
# or
pnpm add @bymos/agentkit-sdkPeer dependency: preact@^10.0.0
Quick start
Both integration paths take the same single-argument config.
CDN (no build step)
<script src="https://unpkg.com/@bymos/[email protected]/agentkit-widget.iife.js"></script>
<script>
window.AgentKit.init({
agentId: 'your-agent-id',
tenantId: 'your-tenant-id',
apiBaseUrl: 'https://api.klicforge.ai',
title: 'Support',
});
</script>The @0.7 pin tracks the latest patch of that minor line. Pin an exact version
(@0.7.0) if you need byte-identical builds, and add
SRI
(integrity + crossorigin="anonymous") when you do — an SRI hash only holds
against an exact version.
npm (ESM / CJS)
import { init } from '@bymos/agentkit-sdk';
const widget = init({
agentId: 'your-agent-id',
tenantId: 'your-tenant-id',
apiBaseUrl: 'https://api.klicforge.ai',
});
widget.on('message:received', (msg) => console.log(msg.content));init and mount default their renderer and stylesheet to the bundled UI. To
supply your own UI layer, pass them explicitly —
init(config, myRenderer, myCss) — which is also the pre-0.6 call shape and
still works unchanged.
Before it will run: allow your domain
The API rejects widget traffic from any origin that is not on the agent's
allowlist. In the KlicForge dashboard open your agent → Channels → Widget
and add every origin the widget loads from (https://example.com,
https://www.example.com — scheme and host must match exactly; there are no
wildcards). Without this the widget renders an access-restricted panel instead of
a chat.
Configuration
type AgentKitWidgetConfig = {
// Required
agentId: string; // Agent ID or slug from your KlicForge dashboard
apiBaseUrl: string; // Base URL of your KlicForge API
// Strongly recommended — required for conversation tracking and validation
tenantId?: string;
// Identity of the end user in your app (all optional; omit for anonymous)
user?: {
externalId?: string; // Your own identifier — DB ID, UUID, email, anything
authId?: string; // ID from your auth system (JWT sub, Clerk userId, …)
name?: string;
email?: string;
};
sessionId?: string; // Resume a conversation across tabs/page loads
// Display (optional)
mode?: 'floating' | 'inline'; // Default: 'floating'
title?: string; // Default: 'AI Assistant'
subtitle?: string; // Shown under the agent name in the header
description?: string; // Shown in the empty state
avatarUrl?: string;
// Behaviour (optional)
streaming?: boolean; // Default: true
metadata?: Record<string, unknown>; // Sent with every message
// Theming (optional)
theme?: {
mode?: 'light' | 'dark' | 'system'; // Default: 'light'
accentColor?: string; // Default: '#6366f1'
fontFamily?: string; // Default: system UI stack
borderRadius?: string; // Default: '12px'
};
};Config is validated at init — an invalid value throws a ZodError, so wrap
init() in a try/catch if the values are user-supplied.
At init time the widget fetches the widget config set for your agent in the KlicForge dashboard (title, avatar, suggested questions, attachment limits) and uses it as defaults. Anything you pass here overrides it.
Suggested questions, photo/file upload and attachment limits are server-only — configure them on the agent in the dashboard; they cannot be set from the host page.
Your agent always serves its published version. preview, skipServerConfigFetch
and configChannel are dashboard-internal: they only do anything for a caller
holding a KlicForge session cookie for the owning tenant, so setting them on a
public site has no effect.
Widget instance API
init(), mount() and mountFloating() all return a WidgetInstance:
widget.open();
widget.close();
widget.toggle();
widget.sendMessage('Hello!');
widget.sendMessage('See this', [{ file, kind: 'image' }]);
widget.reset(); // Clear the conversation and start over
widget.destroy(); // Tears down the DOM and closes the event stream
widget.getState(); // → WidgetState
widget.on(event, fn);
widget.off(event, fn);Call destroy() when unmounting in an SPA — it closes the operator-reply event
stream, which otherwise reconnects indefinitely.
Events
| Event | Payload |
| ---------------------- | ------------------------ |
| ready | WidgetState |
| open / close | — |
| message:sent | WidgetMessage |
| message:received | WidgetMessage |
| message:error | { error, message } |
| conversation:started | { sessionId } |
| stream:start | — |
| stream:delta | { delta, accumulated } |
| stream:end | { content } |
| control_mode_changed | { mode } |
| reset | — |
| destroy | — |
control_mode_changed fires when a human operator takes over the conversation
or hands it back to the agent.
CDN global API (window.AgentKit)
| Method | Description |
| ------------------------ | ---------------------------------------------------- |
| init(config) | Create a floating widget anchored to document.body |
| mount(element, config) | Mount a widget inline inside a DOM element |
| destroy(id?) | Destroy widget by ID, or all widgets if no ID given |
Inline mount example
<div id="chat"></div>
<script src="https://unpkg.com/@bymos/[email protected]/agentkit-widget.iife.js"></script>
<script>
window.AgentKit.mount(document.getElementById('chat'), {
agentId: 'your-agent-id',
tenantId: 'your-tenant-id',
apiBaseUrl: 'https://api.klicforge.ai',
});
</script>mount() always renders inline — there is no need to pass mode.
npm exports
// Lifecycle
import { init, mount, mountFloating, destroy } from '@bymos/agentkit-sdk';
import { destroyWidget, listWidgets, createWidget } from '@bymos/agentkit-sdk';
// Config
import { normalizeConfig } from '@bymos/agentkit-sdk';
// Session
import { getSessionId, setSessionId, clearSession } from '@bymos/agentkit-sdk';
// UI (requires the preact peer dep)
import { renderWidget, buildWidgetCSS, buildThemeVars } from '@bymos/agentkit-sdk';
// Lower-level
import { ApiClient, streamChat, EventEmitter } from '@bymos/agentkit-sdk';
import {
createShadowHost,
createFloatingContainer,
prepareInlineContainer,
} from '@bymos/agentkit-sdk';
// Types
import type {
AgentKitWidgetConfig,
NormalizedWidgetConfig,
WidgetInstance,
WidgetState,
WidgetMessage,
WidgetEventMap,
RenderFn,
} from '@bymos/agentkit-sdk';mountFloating(element, config) anchors the launcher inside a specific element
rather than the page — useful for previews and bounded layouts.
Session management
Within a browser tab, sessions are automatic — the SDK reads and writes
sessionStorage keyed by agentId with no configuration needed. Sessions are
cleared when the tab closes, and storage access is failure-tolerant: in Safari
private mode or a partitioned third-party context the widget simply starts a new
conversation on each load rather than erroring.
To persist a conversation across tabs or page reloads, save and restore the session ID yourself:
widget.on('conversation:started', ({ sessionId }) => {
localStorage.setItem('chat-session', sessionId);
});
// Later, on the next load:
const widget = window.AgentKit.init({
agentId: 'your-agent-id',
tenantId: 'your-tenant-id',
apiBaseUrl: 'https://api.klicforge.ai',
sessionId: localStorage.getItem('chat-session') ?? undefined,
});Theming
The widget renders inside a Shadow DOM to prevent CSS conflicts with your
page. Use the theme config to customise it:
window.AgentKit.init({
agentId: 'your-agent-id',
tenantId: 'your-tenant-id',
apiBaseUrl: 'https://api.klicforge.ai',
theme: {
mode: 'dark',
accentColor: '#ec4899',
fontFamily: "'Inter', sans-serif",
borderRadius: '8px',
},
});Theme values become CSS custom properties inside the shadow root —
--ak-accent, --ak-accent-hover, --ak-font, --ak-radius. Setting those
variables on your own page has no effect; pass theme instead.
Content Security Policy
If your site sends a CSP, the widget needs:
| Directive | Value |
| ------------- | ------------------------------------------------------------ |
| script-src | https://unpkg.com (CDN install only) |
| connect-src | your apiBaseUrl |
| img-src | your apiBaseUrl and any avatar host, plus data: |
| style-src | 'unsafe-inline' — styles are injected into the shadow root |
Shadow DOM does not exempt inline styles from CSP, so style-src 'unsafe-inline'
is currently required.
Requirements
- Node.js ≥ 18 (for npm usage)
- Modern browser with Shadow DOM support
