
Security News
Feross on TBPN: How North Korea Hijacked Axios
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.
@syncorix/ai-chat-sdk
Advanced tools
Type-safe Socket.IO SDK for building real-time AI chat UIs in the browser (streaming tokens, typing indicators, presence, reconnects).
Type‑safe, frontend‑first utilities for building real‑time AI chat UIs:
TypingObserver for focus/typing/pause/stop (IME‑aware).Works in the browser. Bring any Socket.IO backend that speaks your
ChatEventscontract.
New: Dynamic event-name remapping, wildcard subscriptions, optional topic discovery, arbitrary connect params, optional/no-room joins, and per‑emit meta stamping — adapt to any backend naming scheme without code changes.
# pick one
pnpm add @syncorix/ai-chat-sdk
npm i @syncorix/ai-chat-sdk
yarn add @syncorix/ai-chat-sdk
Requirements
ChatEvents types (or provide a mapping; see below).chatId is optional and you can skip joins (joinEvent: null).This is the simplest path: create the socket, create the SDK, listen to events, and send a message.
import { ChatSDK, AIChatSocket } from "@syncorix/ai-chat-sdk";
// 1) Your socket client (connects to your Socket.IO backend)
const socket = new AIChatSocket({
url: import.meta.env.VITE_SOCKET_URL, // e.g. "http://localhost:4000"
chatId: "room-1", // keep if your server uses rooms
autoConnect: true,
});
// 2) High-level SDK that wires socket → conversation graph → UI events
const sdk = new ChatSDK({
socket,
chatId: "room-1",
userId: "user-123",
typing: { target: "#message", autoEmit: true }, // optional: emits typingStart/typingStop
});
// 3) Subscribe to key events for your UI
sdk.on("conversation:update", ({ conversation }) => {
// Render bubbles from conversation.nodes (USER → SYSTEM pairs via conversation.paths)
});
sdk.on("status:change", ({ to }) => {
// to = "queued" | "running" | "done" | "error" → show spinners/progress
});
sdk.on("ai:token", ({ cumulative }) => {
// streaming text for the current assistant message
});
sdk.on("system:update", ({ message, options }) => {
// assistant bubble (final or mid‑stream); render optional quick‑reply options[] if provided
});
sdk.on("error", ({ error }) => console.warn(error));
// 4) Send a user message
sdk.sendText("Hello!");
// (Optional) Abort the current assistant turn
sdk.abort("user canceled");
// (Optional) Mark messages as read
sdk.markRead(["msg-1", "msg-2"]);
No‑room backend variant (new):
const socket = new AIChatSocket({
url: import.meta.env.VITE_SOCKET_URL,
// Pass arbitrary connect params (any names) via query/auth
ioOptions: {
transports: ["websocket"],
query: { consultationId: "abc-123", tenant: "acme" },
// or: auth: { token: "..." }
},
joinEvent: null, // ← skip room join entirely
meta: { consultationId: "abc-123" }, // ← merged into ALL client→server emits
});
const sdk = new ChatSDK({ socket, chatId: "ui-thread-1", userId: "user-123" });
React tip: Put sdk in a context or a store (e.g., Zustand/Redux), and update your UI from conversation:update / system:update events.
Your backend doesn’t need to use our default topic names. Remap them per app or per tenant:
const socket = new AIChatSocket({
url: import.meta.env.VITE_SOCKET_URL,
chatId: "room-1",
eventNames: {
JOIN: "room:enter",
USER_MESSAGE: "chat/user_message",
AI_TOKEN: "llm:delta",
AI_MESSAGE: "llm:final",
},
// or compute dynamically:
// eventResolver: (key, def) => tenantTopicMap[key] ?? def,
});
Extras:
socket.onAny((event, ...args) => { /* debug/telemetry */ })socket.emitRaw(name, payload), socket.onRaw(name, cb), socket.offRaw(name, cb)const s = new AIChatSocket({
url: import.meta.env.VITE_SOCKET_URL,
chatId: "room-1",
discoverEvents: true,
discoveryRequestEvent: "meta:events:request",
discoveryResponseEvent: "meta:events:response",
});
If your backend expects custom connect params (e.g., consultationId) or doesn’t use rooms, you don’t need to change your server:
const chat = new AIChatSocket({
url: "https://your-socket-host",
ioOptions: {
transports: ["websocket"],
query: { consultationId: sessionId }, // any arbitrary key/value
// or: auth: { token }
},
joinEvent: null, // skip emitting a join event entirely (no rooms)
meta: { consultationId: sessionId }, // merged into every client→server emit
});
// You can also listen to raw backend topics without remapping:
const off = chat.onRaw("consultation-result", (data) => { /* handle */ });
// later: off();
You control what gets sent. Build your prompt locally (system/guard/user), optionally moderate it, then call sdk.sendText().
function composePrompt(userText: string) {
const system = "You are a helpful assistant.";
const guard = "Avoid PII.";
// final string the model will see
const composed = [system, guard, userText].join("\n\n");
return composed;
}
async function onSend(userText: string) {
const composed = composePrompt(userText);
// optional: run your own moderation pipeline here
// if blocked → show UI and return
await sdk.sendText(composed);
}
The SDK will optimistically append a USER node, open the paired SYSTEM node, and stream tokens/status as events arrive from your server.
Observe typing/focus with IME support.
import { TypingObserver, TypingObserverEvent } from "@syncorix/ai-chat-sdk/typing-observer";
const ob = new TypingObserver("#message", { pauseDelay: 700, stopDelay: 1500 });
ob.on(TypingObserverEvent.TypingStart, () => console.log("start"));
ob.on(TypingObserverEvent.Typing, (e) => console.log("value:", e.value));
ob.on(TypingObserverEvent.TypingPause, () => console.log("pause"));
ob.on(TypingObserverEvent.TypingStop, () => console.log("stop"));
When you pass
typing: { target, autoEmit: true }toChatSDK, it will automatically callsocket.typingStart/typingStopand emit a unifiedtypingevent for your UI.
If you persist a simple array of rows, you can rebuild the conversation graph on load:
import { rebuildConversationFromShape } from "@syncorix/ai-chat-sdk";
type Msg = { message: string; options?: string[]; timestamp?: number };
type Row = { user?: Msg; system?: Msg; status?: "queued"|"running"|"done"|"error" };
const rows: Row[] = JSON.parse(localStorage.getItem("chat-shape") || "[]");
const convo = rebuildConversationFromShape(rows);
Use convo.nodes and convo.paths to render. New traffic from the SDK continues on top of the rebuilt graph.
import {
ChatSDK, // orchestrates socket → conversation → UI events
AIChatSocket, // typed Socket.IO client wrapper (with dynamic topics)
TypingObserver, // typing/focus observer (also available via subpath)
TypingObserverEvent,
rebuildConversationFromShape, // hydrate from a simple shape
Conversation, // low-level graph (optional direct use)
} from "@syncorix/ai-chat-sdk";
new ChatSDK(options)type ChatSDKOptions = {
socket: AIChatSocket;
chatId: string | number;
userId: string | number;
typing?: { target: HTMLElement | string; options?: { pauseDelay?: number; stopDelay?: number; trackSelection?: boolean }; autoEmit?: boolean };
mapStatus?: (serverStatus: any) => "queued" | "running" | "done" | "error"; // optional mapper
};
Common methods
sendText(text: string, extra?) → creates a USER→SYSTEM pair, sends to server.abort(reason?) → asks server to stop the current assistant turn.markRead(messageIds: string[], readAtISO?) → acknowledge message reads.on(event, handler) / off(event, handler) → subscribe/unsubscribe.Events you’ll likely handle
conversation:update → render from conversation.status:change → "queued" | "running" | "done" | "error".ai:token → { token, index, cumulative } for streaming.system:update → { message, options? } for the assistant bubble.ai:message → final assistant message (with optional usage).typing → { kind: "start"|"tick"|"pause"|"stop" }.error → any surfaced error object.const socket = new AIChatSocket({
url: "http://localhost:4000",
chatId: "room-1",
autoConnect: true,
eventNames: { AI_MESSAGE: "llm:final" }, // example remap
callbacks: {
onAIMessage: (e) => console.log(e.text),
onAIToken: (e) => console.log(e.token),
},
});
socket.sendMessage({ messageId: crypto.randomUUID(), userId: "user-1", text: "Hello" });
We ship a small Vite playground and a mock Socket.IO server to help you try the SDK end‑to‑end while you integrate your own backend.
pnpm dev:all # runs mock server (4000) + playground (5173)
# or start just one
pnpm mock:dev
pnpm playground:dev
Create playground/.env:
VITE_SOCKET_URL=http://localhost:4000
VITE_CHAT_ID=room-1
VITE_USER_ID=user-123
eventNames map or enable discovery so the client knows what to listen to. Also check CORS and transports.joinEvent: null and omit chatId; pass required params via ioOptions.query/auth, and (optionally) set meta to stamp all emits.VITE_SOCKET_URL, and that transports include websocket on both sides if you disabled polling.@syncorix/ai-chat-sdk.useEffect in Next.js).MIT © Syncorix Global
pnpm i
pnpm test
pnpm build
pnpm dev:all
NPM_TOKEN):
pnpm version patch|minor|major
git push && git push --tags
docs/ (VitePress): pnpm docs:devRepository: https://github.com/Syncorix-Global/AI-Chat
Docs: https://docs.syncorixglobal.ai
FAQs
Type-safe Socket.IO SDK for building real-time AI chat UIs in the browser (streaming tokens, typing indicators, presence, reconnects).
We found that @syncorix/ai-chat-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.

Security News
OpenSSF has issued a high-severity advisory warning open source developers of an active Slack-based campaign using impersonation to deliver malware.

Research
/Security News
Malicious packages published to npm, PyPI, Go Modules, crates.io, and Packagist impersonate developer tooling to fetch staged malware, steal credentials and wallets, and enable remote access.