@synerise/ai-assistant-core
v1.15.0
Published
Core component and API helpers for embedding the Synerise AI Assistant in Preact applications.
Maintainers
Readme
@synerise/ai-assistant-core
Core component and API helpers for embedding the Synerise AI Assistant in Preact applications.
Requirements
This package is designed for the Synerise platform. To use it you need:
- An active Synerise tenant and a tracker key
- Network access to
https://api.synerise.com(or your custom Synerise API endpoint) - Preact 10 in your application
If you don't have a Synerise account, visit synerise.com.
Which package do I need?
| Your stack | Package |
| --- | --- |
| Vanilla JS / no bundler / <script> tag | @synerise/ai-assistant-sdk |
| React 18 | @synerise/ai-assistant-react |
| Preact | @synerise/ai-assistant-core (this package) |
| Build a custom chat UI from primitives | @synerise/ai-assistant-ui |
How packages relate
@synerise/ai-assistant-core ──> @synerise/ai-assistant-uicore provides the high-level AIAssistant component with chat lifecycle, API integration, error handling, streaming, etc. ui provides only visual primitives.
Installation
npm install @synerise/ai-assistant-core preact
# or
pnpm add @synerise/ai-assistant-core preact
# or
yarn add @synerise/ai-assistant-core preactPeer dependencies
preact^10.26.9
What this package provides
AIAssistant— Preact component with built-in chat lifecycle handling, exposing an imperative ref (AIAssistantRef) for thread loadingassistantApi— direct API helpers (initChat,sendChatMessage,getChatMessages,getConversations)- Constants — message types, chat states, error types, operation types
- Errors — typed
ApiErrorandNetworkErrorclasses - Re-exports from
@synerise/ai-assistant-ui—BaseChat,Icon, action/message constants, theme types
Quick start
import { AIAssistant } from "@synerise/ai-assistant-core";
export function SupportChat() {
return (
<AIAssistant
apiUrl="https://api.synerise.com/agents/v1/ai-assistant"
context={{ profileId: "user-123" }}
additionalContextValues={{ segment: "premium" }}
displayMode="bordered"
onPromptSuccess={(response) => {
console.log("threadId:", response.meta.threadId);
}}
onCustomAction={({ actionName, params }) => {
console.log("custom action:", actionName, params);
}}
/>
);
}Important props
| Prop | Type | Description |
| --- | --- | --- |
| apiUrl | string | Base assistant API URL |
| context | Context \| null | Per-request context object. Optional — pass null or omit when unused. |
| additionalContextValues | AdditionalContextValues \| null | Per-request metadata. Optional — pass null or omit when unused. |
| displayMode | "drawer" \| "bordered" | UI layout |
| onPromptSuccess | (response) => void | Required. Called after init/message response |
| onCustomAction | ({ actionName, params }) => void | Called when the assistant emits a custom action |
| onConversationTitle | ({ threadId, title }) => void | Called when the backend emits the conversation's auto-generated title (conversation_title SSE event, stream mode only) |
| onStreamError | ({ message, type, stage }) => void | Called when the backend refuses a streamed exchange (error SSE event, stream mode only) — see Refused responses |
| threadId | string | If provided, loads an existing conversation |
| stream | boolean | Enables SSE mode (text/event-stream) |
| fastMode | boolean | Appends fastMode=true query param |
| assistantId | string | Optional assistant configuration ID (UUID). Sent as assistantId query param; falls back to backend default when omitted. |
| authParams | { code, clientUUID } | Auth payload (alternative to XSRF cookie auth) |
| disableXSRFToken | boolean | Skip XSRF token header in fetch calls |
Imperative API — AIAssistantRef
AIAssistant is a forwardRef component. Attaching a ref gives you a
small imperative surface for interacting with the running chat from
outside the component tree — useful for "history" pickers, "resume
conversation" affordances, deep-link handlers, etc.
import { useRef } from "preact/compat";
import { AIAssistant, type AIAssistantRef } from "@synerise/ai-assistant-core";
function SupportChat() {
const chatRef = useRef<AIAssistantRef>(null);
return (
<>
<button
onClick={async () => {
const conversations = await chatRef.current?.getConversations();
// …render a list, then on click:
// await chatRef.current?.loadThread(conv.threadId);
}}
>
History
</button>
<AIAssistant
ref={chatRef}
apiUrl="https://api.synerise.com/gen-ai/v3/ai-assistant"
displayMode="bordered"
onPromptSuccess={(response) => console.log(response.meta.threadId)}
/>
</>
);
}| Method | Returns | Description |
| ---------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| loadThread(threadId) | Promise<void> | Replace the active conversation with the messages of an existing thread. 404 → THREAD_NOT_FOUND state. |
| getConversations() | Promise<AIAssistantConversation[]> | List the current user's historical threads. |
Pass threadId as a prop to mount the component with an existing thread
already loaded instead of calling loadThread after mount.
Conversation flow
┌──────────┐ initChat() ┌─────────────┐
│ init ├───────────────>│ threadId │
└──────────┘ └──────┬──────┘
│
┌──────────┐ sendChatMessage() │
│ user ├───────────────────────┤
│ message │ │
└──────────┘ │
v
┌─────────────────┐
│ assistant │
│ response │
│ (POST or SSE) │
└─────────┬───────┘
v
onPromptSuccess(response)AIAssistant orchestrates the entire flow internally. Use assistantApi directly only if you need custom UX (e.g. headless integration).
Direct API usage
import { assistantApi } from "@synerise/ai-assistant-core";
const initResponse = await assistantApi.initChat({
apiUrl: "https://api.synerise.com/agents/v1/ai-assistant",
context: { profileId: "user-123" },
additionalContextValues: { segment: "premium" },
message: null,
});
const threadId = initResponse?.meta.threadId;
if (threadId) {
const response = await assistantApi.sendChatMessage({
apiUrl: "https://api.synerise.com/agents/v1/ai-assistant",
threadId,
message: "Show me products for trail running",
context: { profileId: "user-123" },
additionalContextValues: { segment: "premium" },
});
console.log(response.data.messages);
}Listing historical conversations
import { assistantApi, type AIAssistantConversation } from "@synerise/ai-assistant-core";
const conversations: AIAssistantConversation[] = await assistantApi.getConversations({
apiUrl: "https://api.synerise.com/gen-ai/v3/ai-assistant",
});
// Each entry: { threadId, agentType, realm, businessProfileId, createdAt, summary?, … }To then load a specific thread's messages, use either assistantApi.getChatMessages({ apiUrl, threadId }) (headless) or ref.loadThread(threadId) on a mounted <AIAssistant> (component).
Error handling
import { ApiError, NetworkError, AI_ASSISTANT_ERROR_TYPE } from "@synerise/ai-assistant-core";
try {
await assistantApi.initChat({ /* ... */ });
} catch (error) {
if (error instanceof ApiError) {
console.error("API error:", error.body.errorCode, error.body.message);
console.error("trace id:", error.body.traceId);
} else if (error instanceof NetworkError) {
console.error("Network error — check connectivity");
}
}AI_ASSISTANT_ERROR_TYPE enumerates the error types surfaced by the chat component callbacks.
Refused responses (guardrails)
In stream mode the backend can refuse an exchange with an error SSE event
emitted instead of the response (no message event follows, only [DONE]):
event: error
data: {"message":"Nie mogę pomóc w tej sprawie. Czy mogę pomóc w czymś innym?","type":"validation_failed","stage":"input"}- With
message— the copy is user-facing and already prepared by the backend, so it is appended as the assistant's reply (the way guardrails behaved when they arrived as ordinary messages). No error state, chat stays usable,onErrordoes not fire, and the turn is reported throughonPromptSuccesslike any other response. A prompt refused atstage: "input"was never echoed by the backend, so the human bubble is re-added client-side — that turn is not part of the server-side history.- The exception is an
initrefused this way: the refusal replaces the response, so nothreadIdwas ever received and nothing can be sent afterwards. The copy is still shown, but the chat ends in the error state below it (errorType: "STREAM_ERROR") so the user can retry — and because that turn ends as an error rather than a response,onPromptSuccessdoes not fire for it (onErrordoes).
- The exception is an
- Without
message— raised as aStreamError, which unwinds into the regular error path:errorType: "STREAM_ERROR", error state with "try again",onErrorfires. Customise the copy viatexts.errorMessage.STREAM_ERROR. The prompt is kept in the transcript, so the error is shown under the question it belongs to.
onStreamError receives the raw payload in both cases; a callback that throws
is contained, so the refusal still reaches the user.
Exports
AIAssistant,AIAssistantRef(imperative handle exposingloadThread,getConversations)assistantApi(initChat,sendChatMessage,getChatMessages,getConversations)BaseChat,Icon(re-exported from@synerise/ai-assistant-ui)AI_ASSISTANT_MESSAGE_ELEMENT_TYPE,AI_ASSISTANT_MESSAGE_TYPE,AI_ASSISTANT_ERROR_TYPE(includesTHREAD_NOT_FOUND,STREAM_ERROR),AI_ASSISTANT_OPERATION(includesLOAD_THREAD),CHAT_STATE,CHAT_ACTION_TYPE,CHAT_MESSAGE_TYPEApiError,NetworkError,StreamError- TypeScript types from
AIAssistant.types(includingAIAssistantConversation) - Storefront page context (PDP) primitives —
StorefrontPageContext,detectPageContextFromMeta(),observePageContext({ onChange }). Pass detected values to<AIAssistant>underadditionalContextValues.page_context(snake_case outer key required by the backend; inner object stays camelCase). See the SDK or React package README for end-to-end usage.
Troubleshooting
document is not defined— this package is browser-only (uses cookies, fetch, DOM). Render only on the client in SSR frameworks.- 401 / 403 from API — verify your tracker key, that
apiUrlmatches your tenant, and thatdisableXSRFTokenmatches your tenant's auth flow. - CORS errors — your tenant must allow your origin. Contact Synerise support.
- Using from React — install
@synerise/ai-assistant-reactinstead, which provides a typed React wrapper.
Support
For technical and licensing inquiries: [email protected].
License
Proprietary. See LICENSE.
