@athenaintel/react
v0.14.0
Published
Athena React SDK — Build AI-powered chat applications with the Athena platform
Readme
@athenaintel/react
React SDK for building AI-powered chat applications with the Athena platform.
Installation
npm install @athenaintel/reactIf you use the PropelAuth-specific provider from @athenaintel/react/auth, also install:
npm install @propelauth/reactQuick Start
import { AthenaProvider, AthenaChat, Toolkits } from '@athenaintel/react';
import '@athenaintel/react/styles.css';
function App() {
return (
<AthenaProvider
config={{ apiKey: 'your-api-key' }}
tools={[Toolkits.DOCUMENT, Toolkits.WEB_SEARCH]}
>
<AthenaChat />
</AthenaProvider>
);
}Provider Config
<AthenaProvider config={{ environment: 'staging' }}>
<AthenaChat />
</AthenaProvider>
<AthenaProvider
config={{
apiUrl: 'https://sync.example.com/api/chat',
backendUrl: 'https://api.example.com',
appUrl: 'https://app.example.com',
}}
>
<AthenaChat />
</AthenaProvider>config accepts apiKey, token, apiUrl, backendUrl, appUrl, trustedParentOrigins, environment, transport, and statewireSyncUrl. Top-level apiKey, token, apiUrl, backendUrl, appUrl, and environment props remain supported as compatibility aliases, but config is now the preferred API.
If config.appUrl is omitted, the provider resolves it from the parent bridge or from the matching Athena environment defaults. Known Athena staging and production apiUrl and backendUrl values automatically fall back to the corresponding frontend origin.
Use trustedParentOrigins when the SDK runs inside an iframe on a private-cloud or custom parent domain and you want to explicitly allow postMessage auth/config from that parent.
Statewire Transport (default as of 0.12.0)
Statewire is the default chat transport: the SDK attaches to the deep-agent
statewire sync host, so the thread is hosted server-side and replicated live —
history, reconnect, run scheduling, mid-run queueing, HITL approvals, and
client tools (for recognized SDK hosts, e.g. computer-asset app_ids) all
come from the server snapshot. Pass transport: 'legacy' to opt a deployment
back onto the legacy Iris /api/chat stream:
<AthenaProvider config={{ environment: 'staging', transport: 'legacy' }}>
<AthenaChat />
</AthenaProvider>Migrating from ≤0.11.x (legacy default):
- Threads are transport-scoped: conversations created on the legacy transport are read-only from statewire (and vice versa). Users keep their history but continue in new threads.
- The statewire runtime applies the deep-agent model default when no
modelprop is set; pinmodelexplicitly if you depended on the legacy default. - The
agentprop's legacy agent names are not honored on statewire; usecollab_agent:<id>refs (withtransport: 'statewire'semantics) or the default agent.
The sync endpoint defaults to the effective apiUrl host + /v2/threads
(e.g. staging resolves to https://iris.stg.athenaintel.com/v2/threads, the
same statewire base the mobile app and Chrome extension ship); pass
statewireSyncUrl to override it for custom hosts whose sync mount lives on
a different origin. If neither yields a URL (e.g. an unparseable custom
apiUrl), the provider throws instead of falling back to an Athena default —
credentials are never sent to a host you didn't configure. Auth reuses the
provider's existing credential plumbing: tokens ride Authorization: Bearer
and API keys ride X-API-KEY.
What the statewire transport wires up in AthenaChat:
- Approvals (HITL): when the backend parks a run on an approval-gated
tool call, an approval card renders above the composer with Approve/Reject
(or Continue for a generic pause) and resumes the run through the statewire
run/inputcommand. Hosts that build their own thread UI should mount the exportedStatewireApprovalCardthemselves — without it an approval-gated run stays parked with no way to answer it. - Frontend tools: tools in
frontendToolswith a localexecuteare declared to the backend asclient_toolsand executed by the mounted client-tool bridge, which resumes the run with each tool's result. - Mid-run sends and the queue: a message sent while a run is active queues on the server. The queue panel above the composer lists queued entries with remove and "Send now" (steer), plus Continue for a stopped run's steer lane.
- Errors and connection state: transport errors (send rejections,
paywall refusals) render as a dismissible banner; a degraded connection
shows a reconnect strip; a read-only pre-cutover thread shows a read-only
notice. Custom hosts can read all of it via
useAthenaStatewireLifecycle. - Thread list:
enableThreadListworks with statewire. The list is the same persisted Agora session list; switching threads re-attaches the statewire session to the selected thread.ThreadListanduseAthenaThreadManagerwork in both modes.
Remaining limitations: on statewire the agent prop only accepts
collab_agent:<asset_id> refs (see below) — any other value is ignored,
because the statewire host always runs the Athena deep agent. model
defaults to the deep-agent default model unless a collab agent supplies one.
Collab Agents
A collab agent is an agent configuration authored in Athena (prompt,
model, toolkits, behavior) and addressed as collab_agent:<asset_id>. Point
the provider at one and the chat runs as that agent:
<AthenaProvider
transport="statewire"
agent="collab_agent:asset_432af46f-293d-480b-a518-30b1f42a9ef7"
channel="askbob_web"
>
<AthenaChat />
</AthenaProvider>Copy the snippet with real ids from the agent's Channels tab in Athena.
channel is optional. It selects a channel override layer — a built-in
channel (email, sms, …) or a custom channel defined on that agent — so one
agent can present different prompts, models, and tools per surface. Omit it to
run the agent's base configuration.
Requirements
transport="statewire". The legacy transport ignorescollab_agent:refs.- Publish the agent. Resolution reads the published snapshot, not the
live draft, so a channel that exists only in the editor is rejected with
collab_agent_channel_unknown. - The acting user needs VIEW access to the agent asset (admins bypass). Running as an agent exposes its prompt and tool policy, so the same gate that governs opening the asset governs running it.
Do not also pass model, systemPrompt, or tools
Request keys override the agent definition, so anything you pass here silently replaces what the agent's author configured — the run succeeds and returns a plausible answer using your config instead of theirs.
// ❌ the agent's prompt, model, and tools are all discarded
<AthenaProvider
transport="statewire"
agent="collab_agent:asset_1234"
model="claude-opus-4-6"
systemPrompt="You are a helpful assistant."
tools={['web_search_browse_toolkit']}
/>
// ✅ the agent's own configuration wins
<AthenaProvider transport="statewire" agent="collab_agent:asset_1234" />Pass them only when you deliberately want to override the agent — an explicit
model is honoured as a caller override, with the agent's model as the default
beneath it.
Failure codes
A refused selection surfaces as an AthenaSdkError with
code: 'collab_agent_rejected'; the specific reason below rides in the error's
detail. Subscribe with onError (see
Debugging) rather than guessing from messages:
| Code | Meaning |
|---|---|
| collab_agent_not_found | No such asset, or it is not a collab_agent |
| collab_agent_forbidden | The acting user lacks VIEW on the asset |
| collab_agent_channel_unknown | No such channel (the message lists the known ones) |
| collab_agent_channel_disabled | The channel exists but is toggled off |
| collab_agent_channel_kind_mismatch | A voice channel was selected for a text run |
| collab_agent_runs_disabled | The deployment has the SDK seam turned off |
Authentication
Same-origin SSO defaults
import { AthenaSSOProvider } from '@athenaintel/react/auth';
import { AthenaChat, AthenaProvider } from '@athenaintel/react';
function App() {
return (
<AthenaSSOProvider>
<AthenaProvider enableThreadList>
<AthenaChat />
</AthenaProvider>
</AthenaSSOProvider>
);
}By default, AthenaSSOProvider uses same-origin SSO endpoints:
/api/sso/userinfo/api/sso/initiate/api/sso/logout
This makes the SSO URLs optional when your frontend is reverse-proxied with the backend.
Cross-origin SSO backend
import { AthenaSSOProvider } from '@athenaintel/react/auth';
<AthenaSSOProvider ssoBaseUrl="https://api.example.com">
<AthenaProvider
config={{
backendUrl: 'https://api.example.com/api/assistant-ui',
apiUrl: 'https://sync.example.com/api/chat',
trustedParentOrigins: ['https://app.example.com'],
}}
enableThreadList
>
<AthenaChat />
</AthenaProvider>
</AthenaSSOProvider>If you need full control, ssoUserInfoUrl, ssoLoginUrl, and ssoLogoutUrl remain available as explicit overrides.
Citation Links
When you render chat inside <AthenaLayout>, Athena citation links (app.athenaintel.com/dashboard/spaces?...) now open the referenced asset in the SDK asset pane by default instead of navigating away.
If your host app wants different behavior, configure linkClicks on <AthenaProvider> or use useAthenaLinkClickHandler() inside a custom message renderer. The hook runs for every rendered link; only Athena citation links are intercepted by default.
import { useMemo } from 'react';
import { AthenaChat, AthenaLayout, AthenaProvider } from '@athenaintel/react';
function App() {
const linkClicks = useMemo(
() => ({
onClick: (link) => {
if (link.kind !== 'athena-citation' || !link.openInAssetPanel) {
return false;
}
console.log('Citation clicked:', link.citation?.assetId);
link.openInAssetPanel();
return true;
},
}),
[],
);
return (
<AthenaProvider linkClicks={linkClicks}>
<AthenaLayout>
<AthenaChat />
</AthenaLayout>
</AthenaProvider>
);
}Set linkClicks={{ interceptAthenaCitations: false }} to keep Athena citation links opening in the browser while still letting your app observe all link clicks.
Standalone Asset Embeds
Use AthenaAssetEmbed to render a native Athena asset without chat, thread history, tabs, or the SDK split-pane layout. It defaults to the editable, full native asset experience (readOnly={false} and displayMode="full"). The embed token and the current user must still have edit permission; otherwise Athena renders the asset read-only.
import { useState } from 'react';
import {
AthenaAssetEmbed,
AthenaProvider,
type AthenaAssetCitationReference,
} from '@athenaintel/react';
function AssetView() {
const [linkedReference, setLinkedReference] =
useState<AthenaAssetCitationReference | null>(null);
return (
<AthenaProvider config={{ token: accessToken }}>
<div style={{ height: 800 }}>
<AthenaAssetEmbed
assetId="asset_123"
displayMode="full"
readOnly={false}
onCitationClick={({ reference, sourceAssetId }) => {
console.log('Citation clicked', { reference, sourceAssetId });
}}
onCitationAction={({ action, reference }) => {
if (action === 'open-in-spaces') {
setLinkedReference(reference);
}
}}
/>
{linkedReference && (
<AthenaAssetEmbed
assetId={linkedReference.id}
reference={linkedReference}
displayMode="full"
readOnly={false}
/>
)}
</div>
</AthenaProvider>
);
}When onCitationClick is provided, citation navigation is delegated to the host by default. Set citationBehavior="browser" to observe the event while preserving Athena's default new-tab navigation. Host mode requires onCitationClick; without a handler, the SDK safely falls back to browser navigation.
onCitationAction handles actions initiated from a citation popover. It currently receives action: 'open-in-spaces' when the user clicks the (+) button. Providing the handler delegates that action to the host by default. Set citationActionBehavior="browser" to observe the event while preserving Athena's default navigation; requesting host mode without a handler safely falls back to browser behavior.
Pass the callback's complete reference to the linked AthenaAssetEmbed, rather than storing only
reference.id. The embed uses the reference anchor to navigate PDFs to the cited page and highlight
the cited excerpt or rectangular region. Updating the reference also navigates when the host opens
another citation in the same asset.
If the host starts with an Athena citation URL/string instead of an
onCitationAction event, parse it and pass the returned reference directly to the embed:
import {
AthenaAssetEmbed,
parseAthenaCitationLink,
} from '@athenaintel/react';
const citation = parseAthenaCitationLink({
href: citationString,
appUrl: 'https://app.athenaintel.com',
});
return citation ? (
<AthenaAssetEmbed
assetId={citation.assetId}
reference={citation.reference}
displayMode="full"
readOnly={false}
/>
) : null;For example, this citation opens the PDF directly at page 34:
https://app.athenaintel.com/dashboard/spaces/?asset_ids=asset_pdf&anchor=page&page=34&excerpt=Term%20debtThe important query parameters are asset_ids, anchor=page, page, and excerpt. Page ranges,
text anchors, and positioned citations are also preserved by parseAthenaCitationLink. A
page_rect may include unit=percent (the default) or unit=point; retain the unit so the region
maps to the correct PDF coordinates.
Styling
styles.css is a self-contained stylesheet compiled at package build time. One import styles every SDK component — the host app does not need Tailwind:
import '@athenaintel/react/styles.css';It contains the design tokens, the Tailwind utilities the SDK components use, and an element reset (preflight) scoped to the SDK's own chrome (.aui-root trees and .athena-sdk-chrome roots, including portaled tooltips/popups). It never restyles the host page — not even host components you nest inside AthenaProvider to use hooks like useSendMessage: the reset only reaches SDK-rendered roots, design tokens live on the provider's .athena-sdk wrapper (inert custom properties) rather than :root, and everything ships in CSS cascade layers so any unlayered host rule wins on conflict.
Hosts that already run Tailwind v4 (like the athena-app template) may instead — or additionally — compile the SDK's classes themselves:
@source "../node_modules/@athenaintel/react/dist";Importing styles.css alongside a host Tailwind build is harmless (utilities are identical and layered). One caveat for themed Tailwind hosts: token values set on the .athena-sdk element shadow values inherited from :root, so define your app's token overrides on .athena-sdk too (:root, .athena-sdk { --primary: …; }) — or use the theme prop below, which always wins.
Theming
import { themes } from '@athenaintel/react';
<AthenaProvider theme={themes.dark}>
<AthenaProvider theme={{ ...themes.dark, primary: '#8b5cf6' }}>
<AthenaProvider theme={{ primary: '#e11d48', radius: '1rem' }}>Preset themes: light, dark, midnight, warm, purple, green.
For CSS-level theming without the theme prop, set the design-token custom properties on .athena-sdk (unlayered rules override the SDK's layered defaults):
.athena-sdk {
--primary: oklch(0.4 0.15 260);
--radius: 1rem;
}Key Components
<AthenaProvider>— Runtime, auth, theming, and configuration<AthenaChat>— Full chat UI with composer, messages, and tool rendering<AthenaLayout>— Split-pane layout with asset panel<ThreadList>— Conversation history sidebarToolkits— Constants for all available backend toolkits
Composer Hooks
Use useAppendToComposer() when you want to prefill a draft without sending it.
Use useSendMessage() for workflow buttons, sidebar shortcuts, and other UI that lives outside <AthenaChat> but still needs to submit a prompt.
import { useSendMessage } from '@athenaintel/react';
function WorkflowButton() {
const sendMessage = useSendMessage();
return (
<button onClick={() => void sendMessage('Run the quarterly workflow')}>
Run workflow
</button>
);
}Debugging and Diagnostics
The SDK emits structured diagnostic events — auth handshake, thread list,
statewire attach, sends, errors — with timings. Nothing is logged by default;
turn it on with the debug prop:
<AthenaProvider
debug // or 'debug' | 'info' | { console: 'debug', posthog: true }
onDiagnostic={(event) => console.log(event.name, event.durationMs)}
onError={(error) => reportToSentry(error)}
>onError receives an AthenaSdkError with a stable code, a human hint,
and the originating status/detail — match on code, never on message text:
import { ATHENA_SDK_ERROR_CODES } from '@athenaintel/react';
onError={(error) => {
if (error.code === ATHENA_SDK_ERROR_CODES.collab_agent_rejected) {
// the backend's granular reason rides in `detail`, e.g.
// 'collab_agent_channel_unknown' — whose message lists the known channels
console.warn(error.detail, error.hint);
}
}}Each provider registers its own consumer, so multiple mounted providers all receive every event; a callback that throws is caught and cannot break the chat.
Without a rebuild, from the browser console:
localStorage.setItem('athena:debug', 'debug'); // console echo on, survives reload
__ATHENA_SDK__.diagnostics.snapshot(); // recent events + timings
__ATHENA_SDK__.diagnostics.export(); // JSON, for attaching to a bug reportCredentials are redacted everywhere (buffer, console, PostHog): any key
matching token, secret, api[-_]?key, authorization, cookie, or
password is stripped before an event is recorded. Spans also emit
performance.mark/measure entries prefixed athena-sdk:, so they show up on
the browser Performance timeline.
License
Proprietary. For licensed enterprise customers only.
