@mobilabsolutions/axium-agent-sdk
v1.2.0
Published
https://mobilabsolutions.com/axium/
Keywords
Readme
@mobilabsolutions/axium-agent-sdk
https://mobilabsolutions.com/axium/
Installation
npm install @mobilabsolutions/axium-agent-sdkStyles
The SDK ships two stylesheet variants:
Tailwindcss
If your host app already uses Tailwind:
import '@mobilabsolutions/axium-agent-sdk/styles.css';This variant uses CSS @layer's and does not include Tailwind's preflight, since the host app should already be providing it.
Unlayered styles
If your host app uses a vanilla CSS reset or no CSS framework:
import '@mobilabsolutions/axium-agent-sdk/styles-unlayered.css';This variant has no @layers defined, and it assumes the host app already has its own reset/preflight in place. It's important that the styles be imported next to the host styles in order for the host to take precedence.
More styling control
| Prop | Type | Default | Description |
| ------- | ------- | ------- | ----------------------------------------------------------------- |
| theme | Theme | — | Optional theme object (colors, typography, logos, favicon, fonts) |
Theme object
All fields are optional. Only the fields you provide will be applied.
import type { Theme } from '@mobilabsolutions/axium-agent-sdk';
const theme: Theme = {
title: 'My App',
tokens: {
colors: {
// Overrides CSS variables
'primary-A400': '#0066cc',
'primary-A500': '#004d99',
},
typography: {
primary: 'Roboto',
secondary: 'Lato',
},
},
};Usage
import {
AxiumAgentProvider,
Chat,
BlankChat,
useChats,
useChatLoader,
useCreateChat,
useDeleteChat,
} from '@mobilabsolutions/axium-agent-sdk';
import '@mobilabsolutions/axium-agent-sdk/styles.css';
function App() {
return (
<AxiumAgentProvider
baseUrl="https://agent.axium.com"
msalInstance={msal}
scopes={['api://<agent-client-id>/.default']}
theme={theme}
>
<ChatPane />
</AxiumAgentProvider>
);
}
function ChatPane() {
const { data: chats } = useChats();
const { chat, isLoading } = useChatLoader(chatId);
const { create: createChat, creating } = useCreateChat({
done: (chatId) => {
/* navigate to chat */
},
});
const { mutate: deleteChat } = useDeleteChat();
if (isLoading) return <div>Loading...</div>;
if (!chat) return <BlankChat onSubmit={handleSubmit} hideConnectors />;
return <Chat chat={chat} hideConnectors />;
}Props
AxiumAgentProvider
| Prop | Type | Default | Description |
| -------------- | -------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
| baseUrl | string | required | Base URL for API calls (e.g. "https://agent.axium.com") |
| msalInstance | IPublicClientApplication | required | MSAL instance for Bearer token auth |
| scopes | string[] | required | Token scopes (e.g. ["api://<agent-client-id>/.default"]) |
| theme | Theme | — | Optional theme object (colors, typography, logos) |
| onAuthError | (error: unknown) => void | — | Invoked when token acquisition fails (e.g. no signed-in account). Use it to present a login UI. |
| tools | ClientTool[] | — | Client-side tools the model can call, executed in the browser (see Client tools). |
Chat
The parent container must use display: flex with a defined height (e.g. height: 100% or height: 100vh) for the chat to fill the available space correctly.
| Prop | Type | Default | Description |
| ---------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| chat | ChatDetailResponse | required | Chat data from useChatLoader |
| hideConnectors | boolean | false | Hide the connectors dropdown in chat input |
| useFullWidth | boolean | false | Remove the default max-width constraints on conversation, input, and error/no-LLM messages so they fill the parent |
| run | ChatRun | — | A run the host already owns (see Headless runs). Omit it and Chat owns the run itself |
ChatView
The same UI as Chat, but it never owns a run: pass one from useChatRun. Use it when the host drives the conversation itself.
| Prop | Type | Default | Description |
| ---------------- | -------------------- | -------- | ---------------------------------------- |
| chat | ChatDetailResponse | required | Chat data from useChatLoader |
| run | ChatRun | required | The run to render, from useChatRun |
| hideConnectors | boolean | false | Hide the connectors dropdown |
| useFullWidth | boolean | false | Remove the default max-width constraints |
BlankChat
| Prop | Type | Default | Description |
| ------------------ | ----------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| onSubmit | (input: string, files?: FileUIPart[]) => Promise<boolean> | required | Message submit handler |
| initialPrompt | string | — | Pre-fill the input |
| hideIllustration | boolean | false | Hide the welcome illustration |
| hideConnectors | boolean | false | Hide the connectors dropdown in chat input |
| useFullWidth | boolean | false | Remove the default max-width constraints on illustration, input, and no-LLM message so they fill the parent |
Client tools
Pass tools to AxiumAgentProvider to let the model call functions that run in
the browser. Each tool's definition (name, description, inputSchema) is
sent to the API so the model knows about it; when the model calls the tool, its
execute handler runs client-side and the return value is sent back to the model
as the tool output. The chat then continues automatically.
import {
AxiumAgentProvider,
type ClientTool,
} from '@mobilabsolutions/axium-agent-sdk';
const tools: ClientTool[] = [
{
name: 'get_current_location',
description: "Get the user's current geographic coordinates.",
// JSON Schema for the tool input.
inputSchema: { type: 'object', properties: {}, required: [] },
async execute() {
const pos = await new Promise<GeolocationPosition>((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject),
);
return { lat: pos.coords.latitude, lng: pos.coords.longitude };
},
},
];
<AxiumAgentProvider
baseUrl={baseUrl}
msalInstance={msal}
scopes={scopes}
tools={tools}
>
{/* … */}
</AxiumAgentProvider>;ClientTool fields:
| Field | Type | Description |
| ------------- | ------------------------------------------- | ------------------------------------------------------------------------------ |
| name | string | Unique tool name matching ^[a-zA-Z0-9_-]{1,64}$. |
| description | string | What the tool does; shown to the model (1–2048 chars). |
| inputSchema | JSONSchema7 | JSON Schema for the tool input (must be serializable). |
| execute | (input, ctx) => Output \| Promise<Output> | Runs in the browser; the return value is sent back to the model as the output. |
Notes:
- Names must be unique and are validated on the client; invalid, duplicate, or
reserved names are dropped with a
console.error. - Reserved names (
web_search_preview,visualize,visualize_read_me) and names that collide with a connector tool cannot be overridden. - Up to 32 client tools are accepted.
- Throwing inside
executereturns an error output to the model rather than crashing the chat.
Headless runs
useChatRun owns one chat's run: the request to the API, the message stream,
client-tool dispatch, and the auto-generated title. It renders nothing, so a host
can start a conversation from a button anywhere in its own UI, keep the user on
the page they were on, and show progress however it likes.
import {
useChatRun,
useCreateChat,
useChatLoader,
ChatView,
} from '@mobilabsolutions/axium-agent-sdk';
// 1. Create the chat with its first message. Nothing runs yet.
const { create } = useCreateChat({ done: (id) => setTaskChatId(id) });
// 2. Mount a runner for it. `autoStart` triggers the assistant response.
function BackgroundTask({ chatId }: { chatId: string }) {
const { chat } = useChatLoader(chatId);
if (!chat) return null;
return <Runner chat={chat} />;
}
function Runner({ chat }: { chat: ChatDetailResponse }) {
const run = useChatRun({
chat,
autoStart: true,
onFinish: () => toast('Task finished'),
});
// Nothing rendered: drive a status badge from `run.status` instead.
return <StatusBadge status={run.status} onCancel={run.stop} />;
}To let the user watch a run that started this way, hand the same run to the chat UI instead of starting a second one:
const run = useChatRun({ chat, autoStart: true });
return open ? (
<ChatView chat={chat} run={run} />
) : (
<StatusBadge status={run.status} />
);Notes:
- Keep the hook mounted for the whole run. The model loop continues server-side if the browser goes away, but a client tool call ends the step waiting for a browser-supplied output, so a run that reaches one with no runner mounted stalls. Mount the runner above whatever unmounts (a route, a drawer), not inside it.
- One run per chat. Two mounts for the same
chat.ideach hold their own message state and both post to the API. Hoist the hook and share the returned run. autoStartversususeActiveChat.autoStartdefaults to the sharedisNewChatflag, which suits a single visible chat. Pass it explicitly when more than one run can be in flight, so two runners cannot race for the flag.- Streams are not resumable yet: a reload during a run loses the live view. The transcript is persisted server-side when the response completes.
ChatRun
| Field | Type | Description |
| ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------- |
| messages | UIMessage[] | The live conversation, including the streaming response |
| status | 'submitted' \| 'streaming' \| 'ready' \| 'error' | Run state, for progress indicators |
| error | Error \| undefined | The last run error |
| usage | LanguageModelUsage \| undefined | Token usage of the latest response, when the model reported it |
| send | (input: string, files?: FileUIPart[]) => Promise<boolean> | Sends a user message. Resolves false while a response is streaming |
| stop | () => void | Aborts the in-flight response |
Hooks
useChatRun
Runs a chat headlessly. Takes { chat, autoStart?, onFinish? } and returns a
ChatRun. See Headless runs.
useCreateChat
Creates a new chat on the server. Returns { create, creating }.
When the chat is created, the done callback fires with the new chatId. You should call setIsNewChat(true) (from useActiveChat) before navigating to the chat view — this signals the Chat component to auto-submit the first message to the LLM.
const { setIsNewChat } = useActiveChat();
const { create, creating } = useCreateChat({
done: (chatId) => {
setIsNewChat(true);
navigate(`/chat/${chatId}`);
},
});useActiveChat
Returns { isNewChat, setIsNewChat }. The Chat component watches isNewChat — when true and messages are loaded, it automatically triggers the LLM response and resets the flag. This bridges the gap between creating a chat and starting the AI generation.
The flag is shared by every mounted run. If your app can have more than one run in flight, use useChatRun's autoStart option instead of this flag.
useChatLoader
Loads a chat by ID. Returns { chat, isLoading }.
useChats / useDeleteChat
useChats() returns the list of conversations. useDeleteChat() returns a mutation to delete a chat by ID.
License
See LICENSE.md.
