React Native SDK reference
Every export of @graineai/inapp-react-native 0.44.1 and @graineai/inapp-react-native-calls 0.5.1, grouped by what you are building — components and their props, hooks, the client, functions, constants and types: signatures, parameters, what each returns, the errors it raises and what to expect.
Every export of the React Native SDK, @graineai/inapp-react-native 0.44.1, and of its calls package, @graineai/inapp-react-native-calls 0.5.1 (which carries the call engine of Graine's iOS and Android SDKs 0.10.1), read from their source. The page is grouped by what you are building — installing, identity and memory, the chat, the voice bar, several agents, screen context and actions, calls, booking, the scheduled-calls list, design, going live, events, errors — and every component, hook, function, constant and type sits with the use it serves. Each entry opens with what it is for, then its signature, parameters or props, what it returns, the errors it can raise and an example; each type lists every field with what to expect. The same API across every platform, topic by topic, is the SDK reference; a step-by-step set-up is React Native.
Requirements, install, permissions and initialisation
Use this section to add the packages, declare what the operating systems ask for, and mount the agent once at the root of your app.
| What | Details |
|---|---|
| Packages | @graineai/inapp-react-native 0.44.1 — the agent: chat, the voice bar, screen context, actions, booking. @graineai/inapp-react-native-calls 0.5.1 — optional: calls that ring like a phone call (CallKit on iOS, a full-screen ring on Android), with the call engine of the native SDKs 0.10.1. |
| Requires | The SDK: React Native 0.68+ and React 17+ (its peer dependencies), and react-native-webview for voice — the voice engine runs in a WebView you pass in, so a chat-only app does not need it. The calls package: React Native 0.71+, React 18+, a native build (not Expo Go), iOS 15+ (its pod) and Android 7 / API 24+ (its Gradle module, Java 17). Node 18+ for development. |
| Recommended | @react-native-async-storage/async-storage, passed as storage: it keeps this install's id between launches, which memory, conversations that continue and booked calls need (the calls package keeps one itself when it is linked). expo-font, passed as loadFont: the SDK loads the design's typefaces. react-native-svg, passed as svg: the SDK's icons are drawn in the design's icon style. Without the last two: the system font and plain glyphs. |
| Optional | WebRTC voice for the bar (RtcVoiceSessionController, or transport="rtc"): react-native-webrtc and the WebRTC peer dependency the package declares as optional — a native module, so it needs a store rebuild. |
| WebRTC calls | Optional, per app build: the calls package's calls — the customer calling the agent, and the customer answering a call Graine rang — carry their audio over WebRTC. One line in ios/Podfile and one in android/gradle.properties, nothing to install from npm, then a rebuild — see WebRTC calls. |
| Expo | A development build (SDK 50+). Expo Go cannot load react-native-webview or the calls package. The calls package ships an Expo config plugin that writes a call's iOS setup at prebuild; the SDK package has none — put its microphone settings in app.json (below). |
Install
Permissions and capabilities
Use this list to declare what the operating systems ask for before the first conversation.
- The SDK package carries no manifest. Declare the microphone yourself: Android
RECORD_AUDIOandMODIFY_AUDIO_SETTINGS; iOSNSMicrophoneUsageDescription(iOS ends an app that opens the microphone without it). - The calls package's Android manifest merges everything a call needs — the microphone, notifications, the full-screen ring, the microphone foreground service, the call screen, the screen it asks for permissions from, its service and its receiver. On iOS, add the capabilities yourself — Push Notifications, and Background Modes → Audio and Voice over IP, plus the usage string above — or, with Expo, let the calls package's config plugin add them.
- At run time: call
requestMicrophonePermission()(below) to choose when Android asks for the bar's voice; otherwise it asks when the first call starts, and iOS asks the first time the microphone is used. For calls,GraineCalls.requestCallPermissions()asks for the microphone and, on Android 13+, notifications at a moment you choose; the calls package also asks for the microphone before it places or answers a call without it. Without notifications a ring cannot show while your app is closed.GraineCalls.callSetupProblems()names whatever is still missing (Permissions and the setup check). - iPhone ringer switch: the SDK leaves your app's audio session to you for the bar's voice — while a conversation is live, set your audio library's equivalents of
playsInSilentModeIOS: trueandallowsRecordingIOS: true, or the agent is silent when the ringer is off. Calls from the calls package set their own audio session.
JSON — app.json (Expo), with the calls package
The plugin writes, at expo prebuild: NSMicrophoneUsageDescription (microphonePermission, else the one already in your config, else a plain default), UIBackgroundModes audio and voip beside any you have, and the Push Notifications entitlement (aps-environment development unless you set it; Xcode signs TestFlight and App Store builds with production). "webrtc": true also turns on WebRTC calls — ENV['GRAINE_WEBRTC'] = '1' in the Podfile and graineWebRtc=true in android/gradle.properties; left out, both are taken away again. A wrong option stops the prebuild with its name. Android needs nothing from it: the package's manifest merges every call entry.
JSON — app.json (Expo), the SDK package alone
aps-environment is the Push Notifications capability: development for development builds, production for TestFlight and App Store builds. With the calls package installed, its manifest adds the Android entries at build time anyway.
requestMicrophonePermission
Call it before the first voice conversation — on an onboarding screen, say — so the microphone prompt is not the first thing a customer sees after tapping the agent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
rationale.title | string | "Microphone" | The title of Android's explanation, shown before the system prompt when Android wants one. |
rationale.message | string | "Allow the microphone so you can talk to the assistant." | Why your app wants the microphone, in your words. |
rationale.buttonPositive | string | "Allow" | The label of the explanation's button. |
Returns — Promise<boolean>: true when the microphone may be used (already allowed, or allowed now), false when the customer refused. On iOS it resolves true at once — iOS asks the first time the microphone is used, and a refusal then reaches you as the mic_denied event and the launcher's onMicDenied. Example value: true.
Errors — none: it never rejects. A device whose permission service throws resolves true, so a call is still attempted.
Initialise
Use this as the starting point: one provider at the root, the voice engine mounted early, your own launcher button, and the calls package handed what the app tells the agent.
React Native · TypeScript — App.tsx
GraineVoiceLauncher owns the voice engine and the call; mounted early with active={false} it loads the engine before the tap, and active turning true with autoStart places the call. GraineVoiceBar draws the bar, the chat window (where the agent offers chat), the components the agent draws and the booked-calls card, in the design chosen in the dashboard. RootNavigator stands for your own navigator.
WebRTC calls
Add this when the calls package's calls should carry their audio over WebRTC instead of the WebSocket — both directions: the customer calling the agent (startCall), and the customer answering a call Graine rang (a booked call, or Ring now). It is one switch per app build: your code, the ring, the call screen and the outcomes stay the same.
Ruby — ios/Podfile, at the top
Properties — android/gradle.properties
Then run pod install in ios/ and rebuild the app. It is native code: it reaches customers in a store build, never in an over-the-air update. On a build server (EAS Build, CI), GRAINE_WEBRTC=1 in the build's environment turns it on for both platforms. Expo: a development build — with prebuild writing ios/ and android/ afresh, set "webrtc": true in the calls package's config plugin (see Permissions and capabilities), or put GRAINE_WEBRTC=1 in the build profile's environment (env in eas.json), rather than editing the two files; Expo Go cannot load the calls package.
| What | Details |
|---|---|
| What changes | Calls carry their audio over WebRTC (with callTransport at its default, "auto"): a jitter buffer, loss concealment and the platform's echo canceller, so on a weak or changing network the agent's voice holds up instead of stalling and catching up. The call is prepared at the tap — or while a ring is showing — so it connects as fast as before. |
| What does not | Your code, the ring, the call screen, "Connecting…", the states and the outcomes. The voice bar (GraineVoiceLauncher) is untouched: this is the calls package only. |
| Sound | Over WebRTC the agent's voice arrives at telephone bandwidth today: steadier through a weak network, narrower than the WebSocket's on a perfect one. |
| When WebRTC cannot set a call up | Before any audio flows — a network that blocks WebRTC, a refusal, no answer within about 12 seconds in all — the call continues on the WebSocket inside the same call: no second ring, no new call screen, no error; "Connecting…" shows a moment longer. A call the agent cannot take at all (no credit left, the agent switched off) ends with that reason, exactly as it does without WebRTC. Once audio flows, a call never moves to the WebSocket: a dropped network gets one retry, then the call ends as dropped. |
| Choosing | GraineCalls.configure({ callTransport }): "auto" (the default), "websocket" or "webrtc" — see GraineCallTransport. Which one carried a call: transport and transportFallbackReason on GraineCallSnapshot (useGraineCall()). |
| The dashboard | The agent's In-app call audio setting (agent → Calls & scheduling): WebRTC (recommended) or Standard. Standard moves every install's calls back to the WebSocket, whatever the app chose, from each app's next start — no app release needed. |
| Size | A prebuilt WebRTC library (Google's WebRTC, BSD 3-Clause, its symbols renamed so it never clashes with another copy in the app, such as react-native-webrtc). iOS: about 12 MB more installed on an arm64 iPhone, about 6 MB more to download. Android, per phone (an app bundle delivers one of these): about 7.4 MB installed on 64-bit ARM (about 2.5–3.3 MB to download), 4.3 MB on 32-bit ARM, 8.2–8.4 MB on x86 — and about 0.5 MB of classes. |
| Permissions | Android: ACCESS_NETWORK_STATE joins your manifest — a normal permission, granted at install: nothing is asked. Its R8 rules come with the package. iOS: nothing new — no Info.plist entry, no capability. |
| Licences | WebRTC is BSD 3-Clause, with Google's patent grant: list it in your app's open-source licences. |
| One WebRTC build per app (iOS) | CocoaPods installs one version of the WebRTC build the calls use. If another pod in your app pins that same build at a different version, the two cannot be installed together: leave GRAINE_WEBRTC off (calls stay on the WebSocket) or align the versions. |
Install and the first answer
Use these to mount the agent, build its client, load its settings and know when it is ready to answer.
GraineProvider
Wrap the root of your app in it, once per agent: it builds the agent's client (or uses yours), loads the agent's settings from the dashboard, reports the screen the customer is on, connects the chat, and gives every hook and SDK view below it the agent.
Props — GraineProviderProps, which also takes every GraineInAppOptions field (baseUrl, publishableKey, variables, storage, chatSize…) when you do not pass client.
Returns — the provider element. It renders children (inside tap capture when fab or captureTaps asks for it), the floating button when fab is set, and the booking sheet an openBooking() asked for.
Errors — building the client throws Error("[Graine] publishableKey is required.") or Error("[Graine] baseUrl is required.") during render when one is missing and no client is passed. Nothing else throws: a key or an agent that cannot be loaded shows as error in useGraineAgent() and in the console ([Graine] …), never as a crash.
What to expect — On mount it loads the agent's settings once (sessionReady turns true), registers its client as the default for hooks outside it (the provider at the root only), answers highlight_element for you, and — with autoConnect on — opens the chat connection, unless a GraineVoiceLauncher is mounted and the customer is on the call (the launcher holds that conversation). On unmount it closes the connection. A provider inside another provider's tree is that part of the app's agent; see Several agents in one app.
GraineProviderProps
Read it to configure the provider: which client, when to connect, how screens are tracked, where the launcher may show, and how the design is drawn.
What to expect — Every prop is read on mount; appVersion, friction and navigationRef are applied again when they change. With client passed, the GraineInAppOptions fields are ignored — the client already has its own.
| Prop | Type | Default | Meaning |
|---|---|---|---|
children | React.ReactNode | required | Your app — everything that should reach the agent through hooks. |
client | GraineInAppClient | built from the props | Use this client instead of building one: pass the instance your app already uses outside React (an API layer calling track, a login flow calling identify), so the provider and your code share one client. |
autoConnect | boolean | true | Open the chat connection as soon as the provider mounts, so the agent can speak first. Set false to connect only when your code calls connect(). A launcher holding the call keeps the provider from opening a second conversation either way. |
surface | AgentSurface | the dashboard's start | Which of the call ("voice") or the chat ("chat") opens first, when the dashboard allows both. A surface the dashboard does not allow is ignored. |
onSurfaceChange | (surface: AgentSurface) => void | none | Hear the customer move between the call and the chat — for analytics, or to change your own UI. Not called for the first surface. |
onProactive | (text: string) => void | none | Hear the agent speak first (a turn nobody asked for), with its first words — to draw a nudge in your own UI. |
endOnBackground | boolean | true | End the conversation when the app leaves the foreground (a call ends two seconds after it goes to the background) and reconnect when it returns. Set false only if a conversation must survive a switch to another app. |
navigationRef | { current: any } | null | none | Pass the ref you give React Navigation's NavigationContainer: every screen the customer opens is reported to the agent by its route name, and the launcher visibility rules apply per route. |
appVersion | string | none | Your app's version, so each conversation's record says which build it happened in. |
friction | FrictionConfig | none | Your app's friction rules, layered over the dashboard's — for screens only your app knows about. See FrictionConfig. |
fab | Omit<GraineFabProps, "webView"> & { webView: React.ComponentType<any> } | none | Mount the SDK's floating button for you: pass { webView: WebView } and the button, the voice engine and the bar come with it. See GraineFabProps. |
captureTaps | boolean | true with fab, else false | Track short, still touches anywhere in your app as tap:screen (with no wrapping of each button) while a screen has been reported and the launcher may show — they feed the rage-tap rule and the conversation's timeline. |
includeScreens | string[] | every screen | Route names where the launcher may appear. |
launcherDelayMs | number | 0 | Wait this long after the customer lands on a screen before the launcher appears. |
visibility | LauncherVisibility | none | Per-flow rules for the launcher: groups of screens, delays, continuity and insets. See LauncherVisibility. |
fonts | Record<string, string> | none | Typefaces your app already registered, by the family name the dashboard uses — { Inter: "Inter-Regular" } draws the design's "Inter" with your fontFamily. A family named here wins over loadFont. |
loadFont | FontLoader | none | Let the SDK load the design's typefaces itself: pass expo-font's Font.loadAsync. Only the faces the design draws with are fetched; text redraws when they arrive. See FontLoader. |
svg | SvgKit | none | Let the SDK draw its icons (microphone, hang up, send, chat, call) as vectors in the design's icon style: pass { Svg, Path } from react-native-svg. Without it, plain glyphs. |
GraineInAppClient
Use it when code outside React needs the agent — a login flow, an API layer, a screen controller — and pass the instance to GraineProvider as client so both share it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
opts | GraineInAppOptions | required | The agent's publishable key, where Graine is, and how the client behaves — see GraineInAppOptions. |
Returns — a new client, already sharing this device with the app's other clients (who the customer is, the screen, one conversation at a time).
Errors — throws Error("[Graine] publishableKey is required.") or Error("[Graine] baseUrl is required.") when one is missing. Nothing is sent until you (or the provider) ask for something.
What to expect — Every method is safe to call at any time, including before the agent's settings have loaded; a request that needs them waits for them. Its members, by use:
- Install and the first answer:
opts,config,session,connect,isConnected,transport,close. - Identity and memory:
identify,setMemory,memoryEnabled,getIdentity,getRawTraits,installId,getInstallId,identityChannel,memories,forgetMemory,newConversation. - Chat:
openChat,takeChatOpen,sendText,queuedTexts,say,chatFailed,retry,clearChat,surface,setSurface,inTextChat. - The voice bar:
setMuted,isMuted,isAgentSpeaking,connectionQuality,networkState,reportQuality,reportNetwork,sendAudio,endUtterance,setPlayoutClock,hasVoiceHost,setVoiceHost,setHostCallLive. - Several agents:
conversation,dispose,joinDevice,leaveDevice,yieldLine. - Screen context and actions:
setScreen,clearScreen,updateField,removeField,getScreen,buildContextFrame,track,getRecentEvents,reportEvent,noteInteraction,setVariables,getVariables,allVariables,registerAction,executeAction,checkActions,availableActions,getAvailableActions,registerHighlightable,getHighlightables,highlight,submitWidget,pendingWidget,clearPendingWidget,presentWidget,presentPendingWidget,configureFriction,friction. - Calls:
nativeCallLive,setNativeCallLive. - Booking:
bookingAvailable,bookingBlocker,openBooking,takeBooking,settleBooking. - The scheduled-calls list:
schedulingRules,scheduleCall,scheduledCalls,cancelScheduledCall. - Going live:
setAppVersion. Events:on,emit.
GraineInAppOptions
Read it to build a client (or a provider without client): where Graine is, which agent, and how conversations behave on this device.
What to expect — Read once, when the client is built; to change one, build a new client. Nothing here is sent to Graine except the key, the variables and the time zone, as each row says.
| Field | Type | Default | Meaning |
|---|---|---|---|
baseUrl | string | required | Where Graine is: "https://www.graine.ai", or your own deployment's address. A trailing slash is ignored. |
publishableKey | string | required | The agent's publishable key (pk_live_…, Agent → Embed & Widgets → Install). Public by design: it identifies the agent and is safe in an app bundle. |
variables | Record<string, string> | none | Prompt variables your app knows at launch — the customer's first name, their plan — for the placeholders in the agent's prompt. Merged over the agent's dashboard defaults and under setVariables() and identify(). |
stallAfterMs | number | 45000 | How long a customer may sit untouched on one screen before the agent is told they look stuck; 0 turns it off. The older single setting for the idle friction rule — prefer friction. |
friction | FrictionConfig | none | Your app's friction rules, over the built-in defaults and the dashboard's. See FrictionConfig. |
voice | boolean | false | Open this client's own connection as a spoken conversation rather than a typed chat — for an app that brings its own audio (useGraineVoice). Decided when the connection opens. Not needed for GraineVoiceLauncher. |
storage | GraineStorage | null | none | Where this install's id and each customer's memory choice are kept between launches: pass AsyncStorage as it is. Without it (and without the calls package) there is no install id, so memory, conversations that continue and booked calls refuse with invalid_request. |
installIdSource | () => string | null | undefined | Promise<string | null | undefined> | the calls package's id, when linked | An install id your platform already keeps, asked first. Rarely needed: with the calls package linked, the SDK uses the call engine's id, so a call and a chat are one device. |
timezone | string | the device's | The customer's IANA time zone ("Asia/Kolkata"), sent with bookings and conversations so times are read in their zone. |
chatSize | ChatSize | the design's | How big the chat window opens — "compact", "large" or "full" — over the agent's Window size in the dashboard. An openChat({ size }) wins for that open. |
reconnectDelaysMs | number[] | [1000, 2000, 4000, 8000, 15000, 30000] | How long a chat that dropped waits before each try to reconnect (each jittered by a quarter either way); the last delay repeats while the chat is wanted. |
WebSocketImpl | any | the global WebSocket | Replace the WebSocket implementation — for tests. |
fetchImpl | typeof fetch | the global fetch | Replace fetch for every request the client makes — for tests, or a network layer of your own. |
GraineStorage
Implement it only when you do not pass AsyncStorage: the client keeps this install's id and each customer's memory choice through it.
What to expect — Synchronous or asynchronous both work. The client writes two keys, graine.installId and graine.prefs; the memory choices are stored under a one-way hash, never the customer's identifier. A storage that throws is treated as empty: the install id is then not kept.
| Member | Type | Default | Meaning |
|---|---|---|---|
getItem | (key: string) => string | null | undefined | Promise<string | null | undefined> | required | Read a value back: the install id at the first request, the memory choices once per storage. |
setItem | (key: string, value: string) => unknown | required | Keep a value: a fresh install id on first launch, and the memory choices when they change. |
removeItem | (key: string) => unknown | optional | Delete a value. Accepted for compatibility with AsyncStorage; this release does not call it. |
GraineInAppClient.opts
Read it to see the options a client was built with — its key, its address, its time zone.
Returns — the options as given, with stallAfterMs filled (45000) when you left it out. Example value: { baseUrl: "https://www.graine.ai", publishableKey: "pk_live_…", stallAfterMs: 45000 }.
Errors — none.
GraineInAppClient.session
Call it to load (or reload) the agent's settings from the dashboard — its look, its declared actions, its memory and booking rules; the provider does it once on mount.
Parameters — none.
Returns — Promise<SessionConfig>: the settings, also kept as config and announced as the session event. Every call asks Graine again. Example value: { agentId: "a_1f2e…", appearance: { accent: "#3740B8" }, calls: { hosted: true, scheduling: { usersCanBook: true } } }.
Errors — rejects with a GraineError:
origin_refused(403): "This agent does not accept connections from a mobile app yet. Add "app://" to its Allowed domains in the dashboard, under Embed & Widgets. (server said: …)" — addapp://to the agent's Allowed domains.invalid_key(401): "Publishable key was rejected. Check it against Embed & Widgets in the dashboard."- any other refusal: Graine's code (
rate_limited,unavailable, …) with Graine's message.
With no network at all, the platform's own fetch error (a TypeError such as "Network request failed") passes through unchanged.
SessionConfig
Read it to see the agent's settings as the dashboard sets them; most apps never need to — the SDK's views and hooks read it for you.
What to expect — Loaded by session(); null on the client until then. A setting an older server does not send means its default: the user_id trait, two devices per customer, booking off. Unknown fields are kept but not read.
| Field | Type | Default | Meaning |
|---|---|---|---|
agentId | string | required | The agent's id, to tell agents apart in your own logs. |
webcallAgentId | string | optional | The id of the agent's voice counterpart, when it has one; voice conversations use it. |
wsHost | string | optional | The host a direct conversation connects to, when Graine sends one. |
appearance | Record<string, any> | optional | The agent's Appearance settings exactly as saved — accent, heading, logo, radius, light or dark, loader. Read it through resolveNativeAppearance() rather than raw. |
design | ResolvedDesign | optional | The agent's design and its tokens for light and dark, as Graine resolved them. Absent from an older server; read it through sessionDesign(), which fills anything missing. |
variables | Record<string, string> | optional | The agent's default prompt variables from the dashboard; your own variables are merged over them. |
components | any[] | optional | The components the agent may draw, as configured in the dashboard. The SDK's views draw them; you rarely read this. |
appActions | Array<{ tool_name?: string; toolName?: string; name?: string }> | optional | The in-app actions the agent is allowed to call. Only actions declared here and registered in your app work — see checkActions(). |
friction | FrictionConfig | optional | The friction rules set in the dashboard, applied under your app's own. |
pageVersion | string | optional | Which version of the voice engine Graine serves, so the launcher reloads it only when it changes. |
identity | { field?: string; devicesPerUser?: number } | optional | Who the customer is, as the agent's Memory section says: the trait to key on (user_id when absent) and how many devices one customer may use (2 when absent). |
memory | { enabled?: boolean } | optional | Says whether this agent keeps any memory of customers. |
calls | { hosted?: boolean; platforms?: string[]; callName?: string; ringTimeoutSeconds?: number; scheduling?: { usersCanBook?: boolean; maxDaysAhead?: number; minLeadMinutes?: number; maxPendingPerUser?: number } } | optional | Calls Graine rings and what customers may book: whether Graine rings this workspace's apps (hosted), on which platforms, the name the ring shows (callName, also the booking sheet's "… will call you"), how long it rings, and the booking limits. Read it through schedulingRules and bookingAvailable. |
GraineInAppClient.config
Read it to see the agent's settings once they have loaded, without asking Graine again.
Returns — the SessionConfig the last session() loaded, or null before one has. Example value: null at launch, then { agentId: "a_1f2e…", … }.
Errors — none.
GraineInAppClient.connect
Call it to open this client's conversation connection — a typed chat, or a spoken one with voice: true — when autoConnect is off or after you closed it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options.automatic | boolean | false | true for a connection nobody asked for this moment (launch, back from the background): it ends another agent's chat on this device but never its call — it waits for the call to end instead, resolving at once without connecting. |
Returns — Promise<void>, resolved once the connection is open (or at once when it is already open, opening, or waiting for another agent's call). The connection is announced as the open event; the agent's welcome follows.
Errors — rejects when the agent's settings cannot load (a GraineError as session() raises), with Error("Socket did not open in time") after 15 seconds, or with the connection's own error. A first connection that fails is not retried — fix what it says; the chat then says it gave up (chatFailed, the chat_failed event), and retry() tries again. A chat that had opened and then dropped comes back by itself (the reconnecting event).
What to expect — One conversation at a time on the device: connecting ends a chat another agent's client holds, and (unless automatic) its call. Variables and the identity are fixed when the connection opens.
GraineInAppClient.isConnected
Read it to know whether this client holds an open conversation connection right now.
Returns — true while this client's own connection is open. A call the voice launcher holds is not this client's connection: during it, this is usually false. Example value: true.
Errors — none.
GraineInAppClient.transport
Read it to see how the current conversation connected, when you are diagnosing a connection.
Returns — "gateway": the usual connection, opened with a single-use ticket Graine issued for it — your variables travel in the ticket (see TICKET_VARIABLE_LIMITS); "direct": no ticket could be had (a deployment that issues none), so the client connected directly and the variables travel in the connection's address (see contextToken); null before the first connection. Example value: "gateway".
Errors — none.
GraineInAppClient.close
Call it to hang up this client's conversation and stop it reconnecting — when the customer leaves the agent, or your app is done with it.
Parameters — none.
Returns — nothing. The agent is told the customer hung up (so the conversation is recorded as ended, not dropped), then the connection closes a moment later; the close event follows with byClient: true.
Errors — none: closing a connection that is already gone does nothing.
useGraineAgent
Call it in any component under the provider to draw the conversation: its state, the transcript, the controls, the design, and whether booking is available.
Parameters — none.
Returns — the provider's GraineContextValue, re-rendering as it changes.
Errors — throws Error("[Graine] useGraine* must be used inside <GraineProvider>.") outside a provider.
GraineContextValue
Read it from useGraineAgent() to draw your own conversation UI; the type is not exported by name — write ReturnType<typeof useGraineAgent>.
What to expect — Updated as the conversation moves; every field is state, so draw from it. For things you do once when they happen (log, analytics), subscribe with useGraineEvents.
| Field | Type | Default | Meaning |
|---|---|---|---|
client | GraineInAppClient | required | The provider's client, for anything the hooks do not cover. |
connected | boolean | required | Whether the chat connection is open — draw "online" from it. |
connecting | boolean | required | Whether a connection is being opened — draw a spinner from it. |
error | string | null | required | Why the automatic connection failed (a rejected key, an agent not open to apps), else null — show it to your team, not the customer. |
open | boolean | required | Whether your agent panel should be open; the provider opens it when the agent speaks first. |
setOpen | (open: boolean) => void | required | Open or close your agent panel. |
messages | Turn[] | required | The transcript, oldest first — one entry per message, the agent's turn growing in place while it streams. See Turn. |
widgets | any[] | required | Every component the agent has drawn in this provider's lifetime, oldest first. To draw the current one, use useGraineWidget(). |
send | (text: string) => void | required | Send what the customer typed. Never lost: with no connection it shows as waiting (pending) and goes the moment there is one. |
typing | boolean | required | The agent has the message but not one word of its reply has come back yet — show a typing indicator meanwhile. Ends with the first word, a component, the connection closing, or after 12 seconds. |
reconnecting | boolean | required | The chat dropped and is coming back by itself — say "Reconnecting…" rather than "Offline". |
failed | boolean | required | The chat gave up: its first connect failed, or Graine ended or refused the conversation — it will not come back by itself. Say so and offer retry(); a message typed now waits (pending), and sending it tries again too. A dropped network is never failed: it stays reconnecting. See chatFailed. |
retry | () => void | required | Connect the chat again now — after it gave up, or to cut a reconnect's wait short; connecting is true meanwhile. See retry. |
clearChat | () => void | required | Empty the chat on screen. Only the screen: the conversation goes on, a message still waiting is still sent, and nothing Graine keeps is touched. See clearChat. |
muted | boolean | required | Whether this client's own voice path (useGraineVoice) is holding the customer's voice back. A call the voice launcher holds has its own muted on GraineVoiceApi. |
setMuted | (muted: boolean) => void | required | Mute or unmute the customer on this client's own voice path; for the voice launcher's call, use its api.setMuted. |
agentSpeaking | boolean | required | Whether the agent is mid-reply — drive a speaking animation from it. |
caption | Caption | null | required | The line being said right now, by either side — for live captions. |
launcherVisible | boolean | required | Whether your launcher may be on screen now, after the route rules and delays. |
launcherInset | LauncherInset | required | Where your launcher should sit on this screen, clear of tab bars and sheets. |
currentScreen | string | null | required | The route the SDK believes the customer is on (with navigationRef), else null. |
appearance | Record<string, any> | null | required | The agent's Appearance settings as saved in the dashboard; null until they load. Read through resolveNativeAppearance(). |
design | ResolvedDesign | required | The agent's design for light and dark — always complete (the default design until the settings load). To draw with it, use useGraineTheme(): it chooses the right scheme for you. |
fonts | Record<string, string> | null | required | The provider's fonts, or null. |
faces | LoadedFaces | required | The design's typefaces the SDK has loaded through loadFont, as "Family|weight" → the fontFamily registered. Empty until one loads. |
svg | SvgKit | null | required | The provider's svg, or null. |
endOnBackground | boolean | required | The provider's endOnBackground, which the voice launcher follows too. |
sessionReady | boolean | required | Whether the agent's settings have loaded — show a splash until it is true. |
onEvent | (fn: (e: GraineEvent) => void) => () => void | required | Subscribe to the event stream; returns the unsubscribe. useGraineEvents wraps it. |
track | (name: string, data?: Record<string, unknown>) => void | required | Record a product event the agent should know about, without making it speak. |
identify | (user: Record<string, unknown> | null, options?: { memory?: boolean }) => void | required | Tell the agent who the customer is; null signs out. See identify. |
newConversation | () => Promise<void> | required | Start a fresh conversation. See newConversation. |
surface | AgentSurface | required | Which conversation the customer is in: the call ("voice") or the chat ("chat"). |
surfaces | AgentSurface[] | required | Each surface the dashboard permits, listed in the order you should offer them. |
canSwitch | boolean | required | Whether to draw a call/chat switch at all. |
setSurface | (surface: AgentSurface) => void | required | Move the customer between the call and the chat; ends the one they leave. A surface the dashboard does not allow is refused. |
chatSize | ChatSize | required | How big the chat window opens now: the last openChat() size, else the provider's chatSize, else the design's. |
openChat | (options?: { size?: ChatSize }) => void | required | Move to the chat and open it. See openChat. |
endChatOpen | () => void | required | For the SDK's own bar: the chat window closed, so an open's size no longer applies. |
chatScreenShown | boolean | required | Whether a GraineChatScreen is on screen (the bar's own chat steps aside while it is). |
noteChatScreen | (delta: number) => void | required | For GraineChatScreen only: counts chat screens appearing (+1) and going (−1). |
connectionQuality | ConnectionQuality | required | The connection in one word — excellent, good, poor, lost, or unknown outside a call. |
network | "online" | "offline" | "unknown" | required | The SDK's most recent reading of whether the device is online. |
setVariables | (vars: Record<string, unknown> | null) => void | required | Set prompt variables while the app runs. See setVariables. |
bookingAvailable | boolean | required | 0.42.0. Whether this customer can book a call from here — show or hide your own "Call me" button with it. Re-read when the settings load and when the customer changes. See bookingAvailable. |
openBooking | (options?: { reason?: string }) => Promise<GraineScheduledCall | null> | required | 0.42.0. Open the SDK's booking sheet, which this provider draws; resolves with the booked call, or null when it closed without one. See openBooking. |
useGraineClient
Call it in a component to get the agent's client — the provider's, or the default one registered with setDefaultClient when no provider is above.
Parameters — none.
Returns — the GraineInAppClient for this render.
Errors — throws Error("[Graine] No client. Either render inside <GraineProvider>, or call setDefaultClient(client) once at startup.") when there is neither.
setDefaultClient
Call it once at startup when some of your screens sit outside the provider (a provider that wraps only your bar), so the SDK's hooks on those screens still reach the agent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
client | GraineInAppClient | null | required | The client hooks use where no GraineProvider is above them; null clears it. |
Returns — nothing.
Errors — none.
What to expect — A provider at the root registers its own client this way on mount, and hands the default back when it unmounts; a provider around one screen does not.
useGraineReady
Call it to show a splash, or a set-up error, until the agent's settings have loaded.
Parameters — none.
Returns — ready: true once the agent's settings have loaded; error: why the automatic connection failed (a rejected key, an app not allowed), else null. Example value: { ready: true, error: null }.
Errors — throws outside a provider, as useGraineAgent does.
useGraineInit
Call it as useGraineReady, when you prefer the isInitialized name.
Parameters — none.
Returns — isInitialized: true once the agent's settings have loaded; error: as useGraineReady. Example value: { isInitialized: false, error: null }.
Errors — throws outside a provider, as useGraineAgent does.
Identity, memory and conversations that continue
Use these to tell the agent who the customer is, let them choose whether they are remembered, show and forget what is remembered, and start a fresh conversation.
GraineInAppClient.identify
Call it after sign-in and on every account switch — and with null on sign-out — so the agent greets the customer by name, remembers them, and rings their devices for calls they book.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
user | Record<string, unknown> | null | required | The customer's traits. The trait your agent's Who is the user? setting names (user_id unless you chose another) identifies them for memory, their devices and booked calls, and never reaches the prompt. Every other trait becomes a prompt variable, masked on the device. null signs out. |
options.memory | boolean | their last choice (true at first) | false keeps this customer's conversations out of memory — neither remembered nor recalled; true turns it back on. Kept for this customer on this install, so you need not repeat it. |
Returns — nothing, at once; the sign-out of a previous customer goes on in the background.
Errors — none: it never throws. A sign-out that cannot reach Graine is logged, and the device is released anyway after 30 days without use.
What to expect —
- Values are turned into strings (an object becomes its JSON);
nullandundefinedtraits are skipped. - The identifying value counts as nobody when it is empty or a placeholder —
guest,anonymous,unknown,null,none,0,-,n/a,test, or a masked token such as[EMAIL]. Anemailtrait is compared lowercased and must look like an address; aphonetrait keeps its last 10 digits (8 to 15 digits in all); any other trait is exact, at most 256 characters. - The identity applies from the next conversation: a conversation's customer is fixed when it starts. Prompt variables reach a live call at once.
- Signing in someone else signs the previous customer out first and clears the chat on screen.
identify(null)releases this device from the customer — it stops ringing for them and stops counting as one of their devices — and clears the chat. - Every app on React Native shares one device across its agents: one
identify()reaches every agent's client.
useGraineIdentify
Call it in a component to get identify bound to the agent's client — sign-in and sign-out effects are the usual place.
Parameters — none.
Returns — a function with the same parameters and behaviour as GraineInAppClient.identify.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineInAppClient.setMemory
Call it from a privacy setting — "Let the assistant remember our conversations" — to change the current customer's memory choice without identifying them again.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | boolean | required | true to remember this customer's conversations; false to keep them out of memory. |
Returns — nothing. The choice is kept for this customer on this install (under a one-way hash, never their identifier) and announced as the identity event.
Errors — none.
What to expect — Applies to every agent on the device, from the next conversation. Call it while a customer is identified: with nobody identified, the choice lasts only until the next identify().
GraineInAppClient.memoryEnabled
Read it to draw the current customer's memory switch in its true state.
Returns — true unless setMemory(false) or identify(…, { memory: false }) said otherwise for this customer on this install. Example value: true.
Errors — none.
GraineInAppClient.getIdentity
Read it to see the traits the agent will receive as prompt variables — masked, and without the identifying trait.
Parameters — none.
Returns — a copy of the masked traits from the last identify(), minus the trait that identifies the customer. Example value: { name: "Asha", plan: "Plus" }.
Errors — none.
GraineInAppClient.getRawTraits
Read it only when you hand the customer to an engine that masks traits itself — the calls package's attach() does this for you.
Parameters — none.
Returns — a copy of the traits exactly as identify() was given them, unmasked. This client never sends them anywhere. Example value: { user_id: "u_8812", name: "Asha", plan: "Plus" }.
Errors — none.
GraineInAppClient.installId
Read it to see this install's id — the id that makes this phone one of the customer's devices — once it has been read.
Returns — a lowercase UUID made on first launch and kept, or null until it has been read (any request reads it; so does getInstallId()), and null for good when it cannot be kept — pass storage. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none.
GraineInAppClient.getInstallId
Call it to read this install's id, making it on first launch when it has to.
Parameters — none.
Returns — Promise<string | null>: the calls package's id when it is linked (so a call and a chat are one device), else the id kept in storage (made and kept on first launch), else null. With neither, the console warns once that the device has no install id — "so the agent cannot remember this customer or call them back" — and names the fixes: storage={AsyncStorage} on the provider or the client, or the calls package. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none: a storage that fails resolves null.
GraineInAppClient.identityChannel
Call it when you carry the conversation yourself and must tell Graine who it is with: the customer's identifying value, this install, the memory choice and the time zone.
Parameters — none.
Returns — Promise<IdentityChannel>, its fields in IdentityChannel. It changes with identify(), setMemory() and the agent's settings, and each change is announced as the identity event. Example value: { value: "u_8812", installId: "9b2e7c4a-…", memory: true, timezone: "Asia/Kolkata" }.
Errors — none.
IdentityChannel
Read it to see who a conversation is with, as the SDK hands it to whatever carries the conversation.
What to expect — value is null until the agent's settings have loaded (the identifying trait is never guessed) and whenever nobody is identified.
| Field | Type | Default | Meaning |
|---|---|---|---|
value | string | null | required | The raw value of the trait that identifies the customer, or null when nobody is identified — what memory, devices and booked calls are keyed on. |
installId | string | null | required | This install's id, or null when none can be kept. |
memory | boolean | required | Whether this customer's conversations may be remembered (true unless they said no). |
timezone | string | null | required | The customer's IANA time zone (opts.timezone, else the device's), or null when the device cannot say. |
DEFAULT_IDENTITY_FIELD
Read it to know which trait identifies the customer when the agent's Who is the user? setting names none.
What to expect — Always "user_id". Pass the customer's id under this name in identify() unless the agent names another trait (email, phone, …).
GraineInAppClient.memories
Call it on a "What the assistant remembers" screen to show the customer what the agent keeps about them.
Parameters — none.
Returns — Promise<GraineMemory[]>: newest first, at most 100, already redacted. See GraineMemory. Example value: [{ id: "m_41", text: "Prefers a call after 6 pm", createdAt: new Date("2026-10-02T09:14:00Z"), updatedAt: new Date("2026-10-12T17:40:00Z") }].
Errors — rejects with a GraineError:
identity_required— "Nobody is identified. Call identify() with the customer's id first."invalid_request— the device has no install id (the message namesstorageand the calls package).device_limit— this install is not one of the customer's devices.rate_limited,unavailable,invalid_key,origin_refused— from Graine;network— "Could not reach Graine. Check the connection and try again."
useGraineMemories
Call it in a component that lists what the agent remembers about the customer — it loads, reloads and reports errors as state.
Parameters — none.
Returns — memories: the list, newest first (empty on error); loading: a read is in progress; error: the last read's GraineError — identity_required before sign-in, device_limit on a device that is not one of theirs — else null; refresh(): read again (it never rejects; failures land in error). It reads on mount, when the customer changes, and after forgetMemory(). Example value: { memories: [], loading: false, error: null, refresh: [Function] }.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineMemory
Read it to show one thing the agent remembers about the customer.
What to expect — From memories(): newest first, at most 100, already redacted, only for the identified customer on one of their devices. The dates are moments in time — show them in the device's time zone; null when Graine did not send one.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | The memory's id, as a list key. |
text | string | required | What the agent remembers, in one sentence, already redacted — show it as it is. |
createdAt | Date | null | required | When the agent first remembered it. |
updatedAt | Date | null | required | When it last changed. |
GraineInAppClient.forgetMemory
Call it from a "Forget everything" control to delete what this agent remembers about the current customer.
Parameters — none.
Returns — Promise<number>: how many memories were deleted. The chat on screen clears too (the conversation it held is part of what was forgotten), messages still waiting are dropped, and memories_changed is announced so lists reload. Example value: 12.
Errors — rejects with a GraineError, as memories does: identity_required, invalid_request (no install id), device_limit, rate_limited, network, unavailable, invalid_key, origin_refused. On a failure nothing is forgotten and the chat stays.
GraineInAppClient.newConversation
Call it from a "New conversation" control to start a fresh thread, as a support chat offers one; the chat window's header does this for you.
Parameters — none.
Returns — Promise<void>, once the fresh conversation has started. The chat hangs up; Graine lets go of the conversation it kept for this agent and customer, so the next chat starts afresh; the chat on screen clears; and — when the customer was in the chat — a new chat opens with the welcome. What the agent remembers (memories) is untouched. With nobody identified there was nothing kept, so only the screen clears.
Errors — rejects with a GraineError when Graine refused (device_limit, invalid_request for a missing install id, network, rate_limited, unavailable); the chat is then reconnected as it was. A call in progress is not a chat and is left alone.
RestoredTurn
Read it to understand the earlier messages a continuing conversation opens with; the SDK's chat folds them in for you.
What to expect — For an identified customer, on an agent with memory on, on one of their devices, the chat opens with the earlier conversation — up to 50 messages from the last 7 days, oldest first — before any new message, and with no welcome. They arrive as history, become ChatTurns with restored: true, and are never live.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | Graine's id for the message — "h<index>" when the frame had none or repeated one — so a list keeps its place. |
role | "agent" | "customer" | required | Who said it — draw the bubble on that side. |
text | string | required | What was said, never blank. |
at | number | required | When it was said, in milliseconds since 1970 (UTC) — for the day separators. |
channel | "chat" | "voice" | required | "voice" for something said on a call — a chat window draws a run of these as one "Voice call" row — else "chat". |
Chat
Use these to open the typed conversation, send what the customer types, move between the call and the chat, and draw a chat window of your own from the same rules the SDK's windows use.
GraineInAppClient.openChat
Call it from your own "Message us" button to move the customer to the chat and open it — at a size of your choosing for this open.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options.size | ChatSize | the app's chatSize, else the design's Window size | How big the chat opens this time: "compact", "large" or "full". A word this release does not know is passed over. |
Returns — nothing. A GraineProvider's bar moves to the chat (where the agent offers one), draws it at that size and turns open on; a provider mounted later still answers it. Announced as the open_chat event ({ size }).
Errors — none.
GraineInAppClient.takeChatOpen
The provider calls it to answer an openChat() exactly once; your app does not need it.
Parameters — none.
Returns — the open still waiting to be answered — { size }, size null when the open named none — and clears it; null when there is none. Example value: { size: "large" }.
Errors — none.
GraineChatScreen
Use it when the chat should be a screen of your own app — a Help route — rather than a window over it: the header with a back chevron, the conversation, the components the agent draws, the booked calls and the composer, in the agent's design.
Props — GraineChatScreenProps.
Returns — the chat, filling its parent.
Errors — throws outside a GraineProvider, as useGraineAgent does.
What to expect — While it is on screen the customer is in the chat (where the agent offers one) and the bar's own chat steps aside; when it goes, what was open before comes back. Since 0.42.0 it shows the customer's booked calls — an Upcoming calls card with up to three calls, each with Cancel — and Book a call where booking is available, which opens the booking sheet over it (see Booking with the SDK's sheet). The header offers New conversation while an earlier conversation is on screen.
GraineChatScreenProps
Read it to wire GraineChatScreen into your navigation.
| Prop | Type | Default | Meaning |
|---|---|---|---|
onClose | () => void | none | What the header's back chevron does — navigation.goBack, say. Without it no chevron is drawn. |
safeArea | { top?: number; bottom?: number } | a typical phone's | The phone's safe areas, so the header and the composer clear the notch and the home indicator — pass react-native-safe-area-context's useSafeAreaInsets(). |
GraineInAppClient.sendText
Call it with what the customer typed in a chat you draw yourself: it is sent now when there is a connection, and kept in order until there is one when there is not.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | string | required | The customer's message, as they typed it. |
Returns — id: this message's id ("t1", "t2", …), to match it to its text_sent event; queued: false when it went now — on this client's connection, or to the call the voice launcher holds (the agent takes a typed turn on a call) — and true when it is waiting for a connection. Example value: { id: "t3", queued: true }.
Errors — none: a message is never dropped in silence. At most 20 wait; beyond that the oldest is dropped.
GraineInAppClient.queuedTexts
Read it to show the messages still waiting for a connection.
Returns — a copy of the waiting messages, oldest first; empty when everything has gone. Example value: [{ id: "t3", text: "Where is my order?" }].
Errors — none.
GraineInAppClient.say
Use it only to send text on this client's own open connection right now — for what the customer typed, use sendText, which never loses a message.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | string | required | The text to send as the customer's turn. |
Returns — true when it went on this client's connection; false when there is none open — nothing is kept. Replies arrive as chunk events. Example value: false.
Errors — none.
GraineInAppClient.chatFailed
Read it to know the chat gave up — so you can say so and offer to try again; useGraineAgent().failed is the same, as state.
Returns — true while the chat is still wanted (connect() and no close() since) yet is not connected, not connecting and not waiting to reconnect: its first connect failed — a setting to fix, never retried by itself — or Graine ended or refused the conversation. false again the moment someone asks again (retry(), connect(), or the customer's next message through useGraineAgent().send), when the chat opens, and when it is closed. Announced as chat_failed, with true and then false. Example value: false.
Errors — none.
What to expect — A dropped network is never chatFailed: the chat keeps coming back by itself (reconnecting) for as long as the customer is in it. A message typed meanwhile waits, pending, and goes the moment the chat connects. A spoken connection (voice: true) never reports it.
GraineInAppClient.retry
Call it from a "Try again" button once the chat gave up (chatFailed) — or to cut a reconnect's wait short.
Parameters — none.
Returns — Promise<void>, as connect returns it: resolved once the chat is open. The wait between tries starts over from the first of reconnectDelaysMs, and what the customer typed meanwhile goes the moment the chat connects. A person asked, so — as connect() does — it ends another agent's conversation on this device. useGraineAgent().retry() does the same and turns connecting on meanwhile.
Errors — rejects as connect() does. A chat that never opened is then chatFailed again; one that had opened keeps trying by itself.
GraineInAppClient.clearChat
Call it from a "Clear" control to empty the chat on screen — a new topic, a tidy screen — without ending the conversation.
Parameters — none.
Returns — nothing. Announced as chat_cleared with reason: "app", which empties useGraineAgent().messages and the SDK's chat windows (foldChat folds it as cleared). useGraineAgent().clearChat() is the same.
Errors — none.
What to expect — Only the screen: the conversation goes on (connected stays connected), a message still waiting for the connection is still sent, and nothing Graine keeps is touched — the conversation that continues comes back with the next history, and the agent's memories stay. newConversation starts over; forgetMemory forgets.
GraineInAppClient.inTextChat
Read it to know whether the conversation right now is a typed chat — to word a component's prompt as "tap" rather than "say".
Returns — true when the customer is on the chat surface, or this client's own connection is open as a typed chat. Example value: true.
Errors — none.
useGraineSurface
Call it to draw a call/chat switch, or to know which of the two the customer is in — from a component that is not the transcript.
Parameters — none.
Returns — surface: the conversation the customer is in ("voice" or "chat"); surfaces: what the dashboard allows, in the order to offer them; canSwitch: whether to draw a switch at all; setSurface: move the customer — it ends the conversation they leave, and refuses (and logs) a surface the dashboard did not allow. Example value: { surface: "chat", surfaces: ["voice", "chat"], canSwitch: true, setSurface: [Function] }.
Errors — throws outside a GraineProvider.
GraineInAppClient.surface
Read it outside React to know whether the customer is in the call or the chat — an analytics wrapper, a screen controller.
Returns — "voice" or "chat". The provider keeps it in step with the dashboard's policy and the customer's choice; a client with no provider stays "voice". Example value: "voice".
Errors — none.
GraineInAppClient.setSurface
The provider calls it to record which conversation the customer is in; to move the customer, use setSurface from useGraineSurface, which also ends the conversation they leave.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
next | AgentSurface | required | "voice" or "chat"; anything else is ignored. |
Returns — nothing. A change is announced as the surface event ({ surface }).
Errors — none.
AgentSurface
Read it to tell the two conversations apart: the call or the chat — one at a time, never both.
| Value | Meaning |
|---|---|
"voice" | The call: the customer talks and the agent speaks, in the voice bar. |
"chat" | The typed chat: silent, turn by turn. |
SurfacePolicy
Read it to see which conversations the dashboard allows and which one opens first (Embed & Widgets → What visitors get).
| Field | Type | Default | Meaning |
|---|---|---|---|
allowed | AgentSurface[] | required | The surfaces this agent may offer, in the order to offer them. |
start | AgentSurface | required | The surface that opens first. |
canSwitch | boolean | required | Whether switching between them is open to the customer; false if just one is allowed. |
resolveSurface
Use it to read the dashboard's call/chat policy yourself — the provider already does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | Record<string, any> | null | none | The agent's Appearance settings (useGraineAgent().appearance): its mode ("voice", "chat" or both) and start. |
prefer | AgentSurface | null | none | Your app's choice of what opens first. It wins only among the surfaces the dashboard allows — it can never add one. |
Returns — a SurfacePolicy. An absent or unreadable setting allows both, voice first. Example value: { allowed: ["voice", "chat"], start: "chat", canSwitch: true }.
Errors — none.
Turn
Read it to draw one message of the provider's transcript (useGraineAgent().messages).
What to expect — One per message, oldest first. The agent's turn grows in place while it streams (live), then stops. A message typed with no connection is pending until it goes. Earlier messages of a conversation that continues arrive once, restored, before anything new. Consecutive voice turns are drawn as one "Voice call" row. The list empties on sign-out, when another customer signs in, after forgetMemory() and on newConversation(); at most 200 are kept.
| Field | Type | Default | Meaning |
|---|---|---|---|
role | "agent" | "customer" | required | Who said it — draw the bubble on that side. |
text | string | required | What was said so far (the agent's text grows while live). |
live | boolean | optional | true while the agent is still streaming this turn — draw it as provisional. |
pending | boolean | optional | true while the customer's message waits for a connection — draw it as "sending". |
id | string | optional | Matches a waiting message to its text_sent; a restored turn's id is Graine's. |
proactive | boolean | optional | true when the agent started this turn unasked — the welcome, or the agent speaking up after an app event. |
at | number | optional | When it was said, in milliseconds since 1970 — for timestamps and day separators. |
restored | true | optional | From an earlier conversation, delivered before this one — not said just now. |
channel | "voice" | optional | Said on a call; absent for the chat. |
ChatTurn
Read it to draw one message when you assemble the chat yourself with foldChat; it is the same shape as Turn.
What to expect — As for Turn: one per message, oldest first, the agent's growing in place while live; at most CHAT_HISTORY_CAP (200) are kept.
| Field | Type | Default | Meaning |
|---|---|---|---|
role | "agent" | "customer" | required | Who said it. |
text | string | required | What was said so far. |
live | boolean | optional | The agent is still streaming this turn. |
pending | boolean | optional | Set while the customer's message waits for a connection; it is sent as soon as one is available. |
id | string | optional | Ties a waiting message to its customer_sent event; a restored turn carries Graine's id. |
proactive | boolean | optional | The agent started this turn without being asked. |
at | number | optional | When it was said, in milliseconds since 1970, when the event that began it carried a time. |
restored | true | optional | Restored from an earlier conversation — delivered, not said just now. |
channel | "voice" | optional | Said on a call; a chat window draws a run of these as one "Voice call" row. |
ChatState
Read it to draw a chat you assemble yourself: the turns, and whether to show "typing".
| Field | Type | Default | Meaning |
|---|---|---|---|
turns | ChatTurn[] | required | The messages, oldest first. |
typing | boolean | required | A message reached the agent and no word of the reply has arrived — draw a typing indicator. |
awaitingReply | boolean | required | A message of the customer's reached the agent and no agent turn has started since; the next agent turn is then not proactive. |
settled | ReadonlySet<ChatTurn> | optional | The turns already delivered when the connection last opened — what a history frame replaces. Absent until a connection has opened. |
ChatEvent
Read it to feed foldChat: each event is one thing that happened to the chat, branched on type.
type | Fields | Meaning |
|---|---|---|
agent_text | text, at? | Words of the agent's current turn: appended to it, or the start of a new turn when none is open. |
agent_turn_start | none | An agent turn begins; whatever was open is finished. |
agent_turn_end | none | The agent's turn is finished. |
customer | text, id?, queued?, at? | The customer sent a message; queued: true when it is waiting for a connection. |
customer_sent | id | A waiting message went out; its turn stops being pending. |
widget | none | The agent answered with a component; "typing" ends. |
closed | none | The connection went away; nothing is open any more. |
typing_timeout | none | The reply took too long (CHAT_TYPING_TIMEOUT_MS, 12 seconds) to keep saying it is coming; "typing" ends. |
opened | none | The connection opened; what is delivered by now is what a history may replace. |
history | turns | An earlier conversation, restored — see RestoredTurn. |
cleared | none | The chat on screen goes: memory forgotten, sign-out, another person, or a new conversation. |
foldChat
Use it to assemble a chat of your own from the client's events, exactly as the SDK's chat does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | ChatState | required | The chat so far — start from EMPTY_CHAT. |
e | ChatEvent | required | What just happened. |
Returns — the next ChatState; the same object when nothing changed, so a React state update can be skipped. A new agent turn is proactive when no message of the customer's was waiting for a reply.
Errors — none.
chatEventFromClient
Use it to turn a client event (chunk, turn_end, text_sent…) into the ChatEvent that foldChat takes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
event | string | required | The client event's name. |
payload | unknown | none | What the event carried. |
Returns — chunk → agent_text; turn_start → agent_turn_start; turn_end → agent_turn_end; text_sent → customer_sent; widget and widget_pending → widget; close → closed; open → opened; history → history; chat_cleared → cleared; anything else, or a payload that cannot be read, → null. The customer's own messages are yours to fold ({ type: "customer", … }) when you call sendText. Example value: { type: "agent_text", text: "Sure — " }.
Errors — none.
EMPTY_CHAT
Use it as the starting state of a chat you assemble with foldChat.
What to expect — A fixed value; foldChat returns it again on cleared.
CHAT_TYPING_TIMEOUT_MS
Use it to time out your typing indicator as the SDK does: after this long with nothing arriving, fold a typing_timeout.
What to expect — 12 seconds.
CHAT_HISTORY_CAP
Read it to know how many messages a chat keeps: older ones are dropped beyond it.
What to expect — 200 turns.
ChatRow
Read it to draw a chat window's rows in order: a bubble per message, one row per voice call, and day separators.
What to expect — A day separator comes before the first row when it is not from today, and wherever the device's day changes. A conversation that is all today has no separators. A voice call is one row however many turns it had — the window shows that a call happened, never its transcript.
kind | Fields | Meaning |
|---|---|---|
day | key, day, label | A day separator: day is "today", "yesterday" or the local date "YYYY-MM-DD"; label is "Today", "Yesterday" or the date in the device's words. |
voice | key, at, first, last, label | One row for a run of voice turns on one day: at is when the call's first turn was said, first and last are the indexes of its first and last turns, label reads "Voice call · 6:05 PM". |
turn | key, index, item | One message: item is your item, index its position in the list you passed. |
Every row's key is stable — use it as the list key.
ChatRowOptions
Read it to fix the clock, the time zone or the wording chatRows uses — tests and previews, mostly.
| Field | Type | Default | Meaning |
|---|---|---|---|
now | number | now | The moment "Today" and "Yesterday" are measured from, in milliseconds since 1970. |
utcOffsetMinutes | number | the device's zone | A fixed offset east of UTC, in minutes, to read days and times in. |
clock | (at: number) => string | chatClock | The words for a time, as in "6:05 PM". |
date | (at: number, sameYear: boolean) => string | chatDate | The words for a day that is neither today nor yesterday. |
chatRows
Use it to lay out a chat window of your own from any list of items — with the day separators and the voice-call rows the SDK's windows draw.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
items | readonly T[] | required | Your messages, oldest first. |
read | (item: T) => { at?: number | null; voice?: boolean } | required | Says when an item was said (milliseconds since 1970) and whether on a call. An item with no time — a component — joins the day before it. |
options | ChatRowOptions | {} | The clock, the zone and the wording — see ChatRowOptions. |
Returns — the rows to draw, in order; see ChatRow. Example value: [{ kind: "day", key: "day-2026-10-14", day: "yesterday", label: "Yesterday" }, { kind: "turn", key: "turn-0", index: 0, item: … }].
Errors — none.
chatTurnRows
Use it to lay out the SDK's own turns (ChatTurn[]) without writing read yourself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | readonly ChatTurn[] | required | The chat's turns, oldest first. |
options | ChatRowOptions | {} | As for chatRows. |
Returns — the rows, as chatRows returns them.
Errors — none.
chatClock
Use it to print a message's time the way the SDK's chat does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | number | required | The moment, in milliseconds since 1970. |
utcOffsetMinutes | number | the device's zone | A fixed offset east of UTC, in minutes. |
Returns — the time in the device's own words and 12- or 24-hour setting. Example value: "6:05 PM".
Errors — none.
chatDate
Use it to print a day that is neither today nor yesterday, the way the SDK's chat does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | number | required | The moment, in milliseconds since 1970. |
sameYear | boolean | required | true to leave the year out (the day is in the current year). |
utcOffsetMinutes | number | the device's zone | A fixed offset east of UTC, in minutes. |
Returns — the weekday, day and month in the device's own words — "Tue 14 Oct" on an English (UK) phone, "Tue, Oct 14" on an English (US) one — with the year when sameYear is false. Example value: "Tue 14 Oct".
Errors — none.
voiceCallLabel
Use it to word the row that stands for an earlier call in a chat window.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | number | required | When the call's first turn was said. |
clock | (at: number) => string | chatClock | The words for that time. |
Returns — "Voice call · " and the time. Example value: "Voice call · 6:05 PM".
Errors — none.
chatIsNew
Use it to decide whether to show the brand's conversation starters: only on a new chat.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | readonly { role?: string; restored?: boolean }[] | required | The turns on screen. |
Returns — true while the customer has said nothing and no earlier conversation is on screen. Example value: true.
Errors — none.
chatPieces
Use it to decide what a chat window of your own draws: which turns, the starters, the "New conversation" control, the booked-calls card and — since 0.42.0 — "Book a call".
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | readonly T[] | required | Everything in the thread, oldest first; items with restored: true are earlier messages. |
chat | ChatPieceTokens | null | undefined | required | The design's chat settings — useGraineTheme().chat. |
upcomingCount | number | 0 | How many booked calls the customer has (upcomingCalls(…).length). |
bookingAvailable | boolean | false | 0.42.0. Whether booking is available here (client.bookingAvailable). |
Returns — a ChatPieces. Example value: { turns: [ … ], starters: false, newConversation: true, upcoming: true, upcomingTitle: "Upcoming call", book: true }.
Errors — none.
ChatPieces
Read it to draw exactly the pieces the brand's design keeps.
What to expect — With earlier messages hidden by the design, the window opens like a new chat: the restored turns are not drawn (the agent still has them), the starters may show, and "New conversation" does not.
| Field | Type | Default | Meaning |
|---|---|---|---|
turns | T[] | required | The turns to draw, oldest first — without the restored ones while the design hides earlier messages. |
starters | boolean | required | Whether the brand's conversation starters may show: nothing restored on screen, nothing said by the customer. |
newConversation | boolean | required | Whether the header shows "New conversation": the design allows it and earlier messages are on screen. |
upcoming | boolean | required | Whether to draw the booked-calls card: the design keeps it and at least one call is booked. |
upcomingTitle | string | required | The card's title: the brand's words, else "Upcoming call" or "Upcoming calls". |
book | boolean | required | 0.42.0. Whether to offer "Book a call" — at the foot of the booked-calls card, or as a row of its own in the card's place when nothing is booked: the design keeps the booked-calls card and booking is available here. |
ChatPieceTokens
Read it to see the design settings chatPieces reads: a value that is not a boolean (or, for the title, not a string) counts as the default.
| Field | Type | Default | Meaning |
|---|---|---|---|
showUpcomingCalls | unknown | shown | false hides the booked-calls card — and with it "Book a call". |
upcomingTitle | unknown | "Upcoming call" / "Upcoming calls" | The card's title, when the brand wrote one. |
showHistory | unknown | shown | false keeps earlier messages off screen (the agent still has them). |
newConversation | unknown | shown | false hides the header's "New conversation" control (newConversation() still works). |
DEFAULT_UPCOMING_TITLE
Use it to title a booked-calls card where the brand wrote no title.
What to expect — one for a single booked call, many for more.
ChatSize
Read it to choose how big the chat window opens.
What to expect — Which size wins: the size passed to openChat(), then the app's chatSize, then the agent's Window size in the dashboard, else compact. An open's size lasts until the chat closes. How each size is drawn on a phone and on a tablet is chatPresentation.
| Value | Meaning |
|---|---|
"compact" | A window above the voice bar. |
"large" | A sheet over most of a phone's screen; a panel on a tablet. |
"full" | The whole screen, inside the safe areas. |
CHAT_SIZES
Use it to list the sizes in a settings screen of your own.
What to expect — A fixed list, smallest first.
chatSizeOf
Use it to settle which size a chat opens at, as the SDK does: the first real size among the candidates.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candidates | unknown[] | none | The sizes in order of precedence — the open's own, the app's chatSize, the design's. |
Returns — the first candidate that is a size this release knows, else "compact". Example value: "large".
Errors — none.
The voice bar
Use these to put the agent's voice in your app: the engine that holds the call, the bar and buttons drawn in the agent's design, loaders and captions, the connection's quality, and — for an app with its own audio — the lower-level voice pieces.
GraineVoiceLauncher
Mount it once, near the root and inside the provider, to own the voice call: it loads Graine's voice engine in a hidden WebView, places and ends the call, and hands your UI the call's state through its render prop.
Props — GraineVoiceLauncherProps.
Returns — the hidden engine and whatever children(api) renders; api is a GraineVoiceApi.
Errors — throws outside a GraineProvider. Nothing else throws: a call that cannot be set up reaches onError and api.error; a refused microphone reaches onMicDenied and api.micDenied.
What to expect — Mounted with active={false} it loads the engine idle — nothing is registered or started — so the customer's tap later is one quick step. A page that fails to load is retried on the timing.retryDelaysMs ladder; after the last try api.connectivity.state is "failed" and onError says "The assistant could not load. Check your connection and try again." The call ends two seconds after the app leaves the foreground (unless the provider's endOnBackground is off). While a call from the calls package rings or is live, a start is refused with "A call is already in progress." One conversation at a time: starting the call closes an open chat.
GraineVoiceLauncherProps
Read it to wire the launcher: the WebView, when the engine is active, how audio travels, and the callbacks your UI needs.
| Prop | Type | Default | Meaning |
|---|---|---|---|
transport | "ws" | "rtc" | the agent's setting | How the call's audio travels. "ws": over the same connection as the screen context and actions. "rtc": WebRTC — packet-loss concealment, a jitter buffer and the phone's own echo cancellation — falling back to "ws" when WebRTC is unavailable (see onTransport). Leave it out and the agent's Embed & Widgets setting decides, so it can change without a release. |
onTransport | (info: { transport: "ws" | "rtc"; requested: "ws" | "rtc"; reason?: "not_configured" | "unreachable" | "screen_channel"; message?: string }) => void | none | Hear how the call actually travelled, including after a fallback: reason "not_configured" is a setting to change in the dashboard (it never succeeds on retry), "unreachable" is the network blocking WebRTC, "screen_channel" means the audio connected but the screen context could not, so the call restarted the other way rather than run blind; message is Graine's sentence naming the setting. |
webView | React.ComponentType<any> | required | WebView from react-native-webview, passed in so the SDK does not depend on it. |
autoStart | boolean | false | Place the call as soon as the engine is ready and active is true — for a bar that opens straight into a call. |
active | boolean | true | false loads the engine idle before the customer needs it; true makes it ready (with autoStart, it places the call; without, it warms up so the tap is instant). Turning it back to false ends a live call, as unmounting does. |
onCaption | (c: { role: string; text: string; live: boolean }) => void | none | Hear each caption line as it is said — role "agent" or "customer", live while it is still being said. api.captions keeps the transcript for you. |
onCallState | (s: { connected: boolean; connecting: boolean }) => void | none | Hear the call start connecting, connect, or end — connected and connecting as they now stand. |
onEnded | () => void | none | Hear the call end — once, on the way out of a live call — to close your UI. Give the goodbye a moment to finish before you unmount. |
onMicDenied | (reason: string) => void | none | Hear that the microphone was refused ("NotAllowedError") or that the device has none usable ("NotFoundError") — show how to allow it. |
onError | (message: string) => void | none | Hear that voice cannot work until something changes: the key was rejected, the agent is not open to apps, the engine could not load after every retry, or a call from the calls package holds the line ("A call is already in progress."). The same sentence reaches useGraineEvents as an error event. |
timing | { readyTimeoutMs?: number; retryDelaysMs?: number[]; stallMs?: number; bootMs?: number; hardCapMs?: number; endWatchdogMs?: number; slowConnectMs?: number } | see below | Tune how long the engine may take to load and how often it retries: readyTimeoutMs 15000 (load to ready); retryDelaysMs [1000, 2000, 4000, 8000, 15000, 15000] (its length is the number of reloads before "failed"); stallMs (nothing arriving) and bootMs (loaded but not ready), both readyTimeoutMs; hardCapMs 120000 (one attempt, however steadily it loads); endWatchdogMs 4000 (how long a hang-up may go unconfirmed before the call is ended on the device); slowConnectMs 4000 (when api.slowConnect turns on). |
children | (api: GraineVoiceApi) => React.ReactNode | none | Draw your UI from the call — usually <GraineVoiceBar api={api} />. |
GraineVoiceApi
Read it in GraineVoiceLauncher's render prop to draw a call UI: where the call is, the controls, the captions and the connection.
What to expect — Re-rendered as the call changes. captions is cleared when a call starts and when the launcher becomes inactive.
| Field | Type | Default | Meaning |
|---|---|---|---|
phase | "idle" | "connecting" | "reconnecting" | "thinking" | "speaking" | "listening" | required | Where the call is, for a bar that shows one thing at a time: idle (no call, ready), connecting (loading or placing), reconnecting (the engine is reloading), thinking (the customer finished; the agent has not answered), speaking (the agent is talking), listening (the agent waits for the customer). Pair it with <GraineLoader moment="thinking" />. |
connectivity | { state: "loading" | "ready" | "retrying" | "failed"; attempt: number; reason?: string } | required | Whether the voice engine is there: loading (first load), ready, retrying (a load failed; a reload is scheduled), failed (every retry used — say so and offer retry()); attempt counts reloads (0 on the first load); reason says why the last attempt was abandoned. |
retry | () => void | required | Reload the engine and start the retry ladder again — for a "Try again" button. |
connected | boolean | required | Whether the call is connected — the agent can hear the customer. |
connecting | boolean | required | Whether a call is being placed. |
muted | boolean | required | Whether the customer's microphone is muted on this call — draw your mute button from it. |
ready | boolean | required | Whether a call can start now; false while the agent's settings are still loading, when a tap would do nothing — draw a waiting state instead. |
start | () => void | required | Place the call. Pressed before the engine is ready, it waits and places it the moment it is. |
end | () => void | required | Hang up. |
setMuted | (m: boolean) => void | required | Mute or unmute the customer on this call. |
captions | Caption[] | required | What has been said so far, oldest first — both sides, a live line replaced in place as words arrive; at most CAPTION_HISTORY lines. |
caption | Caption | null | required | The line being said now, or the last one said; null before any. |
captionsOn | boolean | required | The captions switch — true by default — so every screen draws captions the same way. |
setCaptionsOn | (on: boolean) => void | required | Turn captions on or off. |
clearCaptions | () => void | required | Clear the transcript and keep the call going. |
quality | ConnectionQuality | required | The call's connection in one word — excellent, good, poor, lost, or unknown outside a call; show "weak connection" from it. |
network | "online" | "offline" | "unknown" | required | Whether the device is online, as the engine sees it. |
reconnecting | boolean | required | A call interrupted by the network is being re-established; the transcript is kept and phase reads reconnecting. |
error | string | null | required | Why voice cannot work until a setting is fixed (a key, an agent not set up for apps), else null. Also sent to onError. |
micDenied | boolean | required | true if the customer turned down microphone access at the most recent start. Also sent to onMicDenied. |
connectStage | "registering" | "dialling" | "retrying" | "switching" | null | required | Where a call being placed is: registering (setting up, on a cold start), dialling (on its way to the agent), retrying (the first attempt went unanswered; trying a fresh connection — on mobile data, usually one the network dropped while idle), switching (moving to the other way of carrying audio); null outside a connect. Say "Trying again…" across a retry. |
slowConnect | boolean | required | The connect has run past timing.slowConnectMs (4 seconds) — say "Still connecting…". |
callFailed | boolean | required | The last start could not connect at all. Unlike error, nothing needs fixing: start() simply tries again. |
CAPTION_HISTORY
Read it to know how many lines api.captions keeps.
What to expect — 12 lines, oldest dropped first. A caption box usually shows the last two or three.
Caption
Read it to draw one caption line.
| Field | Type | Default | Meaning |
|---|---|---|---|
role | "agent" | "customer" | required | Who is speaking — style the line by it. |
text | string | required | What has been said so far in this line. |
live | boolean | required | true while the line is still being said or recognised — draw it as provisional. |
GraineVoiceBar
Use it as the call's UI: the bar, the chat window (where the agent offers chat), the components the agent draws and the booked-calls card, drawn in the design chosen in the dashboard.
Props — GraineVoiceBarProps.
Returns — the bar, positioned over your app at the bottom.
Errors — throws outside a GraineProvider.
What to expect — It follows the conversation by itself: the call's controls, captions, the switch to the chat, a chat window at the size the design (or openChat) asks for, the components the agent draws (unless your app mounts its own GraineWidget), and — since 0.42.0 — the customer's booked calls with Cancel and Book a call where booking is available. It rises above the keyboard and the customer can drag it.
GraineVoiceBarProps
Read it to place the bar and connect it to the call.
| Prop | Type | Default | Meaning |
|---|---|---|---|
api | GraineVoiceApi | required | The call — pass what GraineVoiceLauncher's render prop hands you. |
onClose | () => void | none | Hear the bar close — after a hang-up or the close button, once the goodbye has finished playing — to unmount it. |
bottomInset | number | the provider's inset for this screen, else 96 | How far above the bottom edge the bar sits, clear of your tab bar. A value you pass wins. |
draggable | boolean | true | Let the customer drag the bar up and down. |
placement | { bottomOffset?: number } | null | none | A position you saved, to restore: pixels above the bar's resting place. |
onPlacementChange | (placement: { bottomOffset: number }) => void | none | Hear where the customer let go — save bottomOffset to restore it next time. |
name | string | the dashboard's heading | What spoken labels call the agent — "Start talking to Acme". The name drawn on the bar is always the dashboard's heading. |
accessory | React.ReactNode | none | Something to ride on the bar, just above it — a GraineBarAction that moves the conversation to a phone call, say. It moves with the bar and nothing of the bar's styling reaches it. |
safeArea | { top?: number; bottom?: number } | a typical phone's | The phone's safe areas, for a chat that opens as a sheet or the whole screen — pass useSafeAreaInsets(). |
GraineBarAction
Use it for a button that rides on the bar (its accessory) and must look like part of it — in the design's accent, corners, type, shadow and icons.
Props — GraineBarActionProps.
Returns — the button.
Errors — none.
GraineBarActionProps
Read it to say what the button says and does — its look is the design's.
| Prop | Type | Default | Meaning |
|---|---|---|---|
label | string | required | The button's text, which screen readers also read aloud. |
icon | "call" | "chat" | "call" | The SDK icon before the label, in the design's icon style. |
onPress | () => void | required | What a tap does. |
GraineLauncherButton
Use it as your launcher when you show and hide GraineVoiceBar yourself: the design's launcher, drawn as the floating button draws it.
Props — GraineLauncherButtonProps.
Returns — the button; it shows the brand's logo or glyph in the design's launcher shape.
Errors — throws outside a GraineProvider, as useGraineAgent does.
GraineLauncherButtonProps
Read it to place the launcher and say what a tap does — its look is the design's.
| Prop | Type | Default | Meaning |
|---|---|---|---|
onPress | () => void | required | What a tap does — usually opening your bar. |
style | ViewStyle | none | Its position on screen — for example { position: "absolute", right: 16, bottom: 24 }. |
label | string | "Talk to " and the dashboard's heading | What a screen reader says. |
size | number | 56 | Its diameter, in points. |
GraineFab
Use it when you want the whole flow done for you: a floating button that opens the conversation, folds back into a button when the call ends, and follows the launcher visibility rules. The provider mounts it for you with fab={{ webView: WebView }}.
Props — GraineFabProps.
Returns — the button (hidden where the visibility rules say, or when the agent cannot start), then the bar while a conversation is open; null when hidden.
Errors — throws outside a GraineProvider.
GraineFabProps
Read it to tune the floating button: the corner, the size, and when the voice engine loads.
| Prop | Type | Default | Meaning |
|---|---|---|---|
webView | ComponentType<any> | required | WebView from react-native-webview: the voice engine runs in it. |
position | "bottom-right" | "bottom-left" | "bottom-right" | Which bottom corner; the visibility rules' insets still apply. |
size | number | 56 | The button's diameter, in points. |
accent | string | the dashboard's accent | A colour for this button only. |
label | string | "Talk to " and the dashboard's heading | What a screen reader says. |
overlay | ComponentType<{ children?: React.ReactNode }> | none | A wrapper to draw the button in, above native screens and modals — pass react-native-screens' FullWindowOverlay on iOS. |
onOpen | () => void | none | Hear the customer open the conversation. |
onClose | () => void | none | Hear the conversation end and the button come back. |
prewarm | boolean | true | Load the voice engine before the customer taps, so the call connects in well under a second; false loads it on the tap and costs the customer a few seconds of "Connecting…". |
GraineAgentBar
Use it for a ready-made bar over your app — status, transcript, captions and a composer — styled from the dashboard's Appearance (accent, heading, logo) with props to override; the floating button opens it. For the brand's full design from the Garage, use GraineVoiceBar. Mount it at your root, not inside a screen, so a navigation never unmounts the conversation.
Props — GraineAgentBarProps.
Returns — the bar, or null while hidden, while the visibility rules hide the launcher, or when the agent could not start (useGraineAgent().error).
Errors — throws outside a GraineProvider.
GraineAgentBarProps
Read it to style and place the classic bar; the dashboard's appearance is the default and each prop overrides it.
| Prop | Type | Default | Meaning |
|---|---|---|---|
accent | string | the dashboard's accent | The bar's accent colour. |
name | string | the dashboard's heading | The agent's name beside the status — the name your customers hear. |
avatarUrl | string | the dashboard's logo | A picture of the agent. |
bottomInset | number | 96 | How far above the bottom edge it sits, clear of your tab bar and primary buttons. |
hidden | boolean | false | Hide it without unmounting the provider — on sign-in or payment screens, say. |
captionsOn | boolean | false | Start with captions on; the customer can switch them from the bar. |
scheme | "dark" | "light" | "auto" | "auto" | Which palette to draw; "auto" follows the device. |
theme | Partial<GraineBarTheme> | the scheme's palette | Override single colours of the palette — see GraineBarTheme. |
draggable | boolean | true | Let the customer drag the bar up and down; a tap still works. |
placement | { bottomOffset?: number } | null | none | A position you saved, to restore: pixels above the resting place. |
onPlacementChange | (placement: { bottomOffset: number }) => void | none | Hear where the customer let go — save bottomOffset. |
GraineBarTheme
Read it to override the classic bar's colours one at a time; the accent comes from the dashboard.
| Field | Type | Default | Meaning |
|---|---|---|---|
surface | string | the scheme's | The bar's and panel's background. |
border | string | the scheme's | The hairline around them. |
text | string | the scheme's | Main text: the agent's name, replies, what the customer types. |
sub | string | the scheme's | Text with less emphasis — the status line, placeholders and the empty state. |
chip | string | the scheme's | Used for the agent's message bubbles and for the understated round buttons. |
onAccent | string | the scheme's | Text and icons drawn on the accent, and on danger. |
danger | string | the scheme's | The muted state — the one state with a colour of its own. |
GraineLauncher
Keep it only in an integration that already uses it: it is GraineAgentBar under its earlier name, deprecated.
Props — GraineLauncherProps, the same as GraineAgentBarProps.
Returns, Errors — as GraineAgentBar.
GraineLauncherProps
Read it as GraineAgentBarProps — the same props under the deprecated component's name.
What to expect — Every prop of GraineAgentBarProps, with the same defaults.
GraineLoader
Use it wherever the customer waits — connecting, reconnecting, the agent thinking, a component loading — so waiting looks as the dashboard's Appearance says (kind, size, speed, colour and words).
Props — GraineLoaderProps.
Returns — the loader: the mark, and the words for the moment beside or under it. It holds still when the phone asks for reduced motion, and reads as a progress bar to screen readers.
Errors — none: outside a provider it draws the defaults.
GraineLoaderProps
Read it to choose the moment the loader is for; everything else defaults to the dashboard's choice.
| Prop | Type | Default | Meaning |
|---|---|---|---|
moment | LoaderMoment | none | Which of the dashboard's words to show: "connecting", "reconnecting", "thinking" or "component". Leave it out for no words. |
label | string | the moment's words | Words of your own, over the moment's. |
kind | LoaderKind | the dashboard's ("dots") | The animation: "dots", "pulse", "ring", "bars" or "mark" (the brand's logo, breathing). |
color | string | the dashboard's, else the accent | The mark's colour. |
size | number | the dashboard's size (20) | The mark's size, in points. |
stacked | boolean | false | Put the words under the mark instead of beside it. |
useGraineLoader
Call it when you draw your own loader but want the dashboard's choice: the kind, size, speed, colour and words.
Parameters — none.
Returns — the dashboard's loader resolved into numbers — see LoaderLook — plus accent, the colour to draw it in (the loader's own colour, else the design's accent). Example value: { kind: "dots", color: null, px: 20, periodMs: 1100, text: { connecting: "Connecting…", … }, skeleton: false, accent: "#3740B8" }.
Errors — none: outside a provider it returns the defaults.
GraineInAppClient.setMuted
Call it to mute or unmute the customer on this client's own voice path (useGraineVoice); the voice launcher's call has its own api.setMuted.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
muted | boolean | required | true to stop sending the customer's voice; false to send it again. |
Returns — nothing. The microphone keeps running and nothing leaves the device while muted, so unmuting is instant. A change is announced as the muted event.
Errors — none.
GraineInAppClient.isMuted
Read it to draw the mute button of a voice UI built on this client.
Returns — true while this client is holding the customer's voice back. Example value: false.
Errors — none.
GraineInAppClient.isAgentSpeaking
Read it to know whether the agent is mid-reply on this client's connection.
Returns — on a typed chat, true from the first word of a reply until it ends; on a voice connection, true while the agent's voice is still playing. Example value: true.
Errors — none.
useGraineConnection
Call it in a voice UI to say "Weak connection" or "Offline" when the network is the problem, so the agent does not take the blame.
Parameters — none.
Returns — quality: the call's connection in one word (see ConnectionQuality); online: true or false once the SDK knows, else null; network: the same as a word. Example value: { quality: "poor", online: true, network: "online" }.
Errors — throws outside a GraineProvider.
GraineInAppClient.connectionQuality
Read it outside React to know how good the call's connection is.
Returns — the last measured quality; "unknown" outside a call, "lost" while the device is offline. Example value: "good".
Errors — none.
GraineInAppClient.networkState
Read it outside React to know whether the device is online, as the voice engine last reported.
Returns — "unknown" until the engine reports. Example value: "online".
Errors — none.
GraineInAppClient.reportQuality
The voice engine calls it with each connection measurement; call it yourself only when you carry the call's audio and can measure it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sample | QualitySample | null | required | Round-trip time, jitter and loss as measured; null when there is no call. |
Returns — the quality the sample amounts to ("unknown" for null). The quality event fires only when the word changes. Example value: "good".
Errors — none.
GraineInAppClient.reportNetwork
The voice engine calls it when the device goes offline or comes back; call it yourself only when you carry the call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
online | boolean | required | true when the device is online again, false when it went offline. |
Returns — nothing. Going offline also sets the quality to "lost". A change is announced as the network event.
Errors — none.
ConnectionQuality
Read it to show the connection as one word.
What to expect — Measured every couple of seconds while a call is up and changed only when the word changes. The worst of the three measurements decides.
| Value | Meaning |
|---|---|
"excellent" | Round trip under 150 ms, jitter under 30 ms, loss under 2%. |
"good" | Any of: round trip from 150 ms, jitter from 30 ms, or loss from 2% — still a clear conversation. |
"poor" | Any of: round trip from 400 ms, jitter from 80 ms, or loss from 8% — say "Weak connection". |
"lost" | The device went offline. |
"unknown" | No call, or nothing measured yet. |
QualitySample
Read it to describe one measurement of a call's connection, for reportQuality and classifyQuality.
| Field | Type | Default | Meaning |
|---|---|---|---|
rtt | number | null | optional | Round-trip time to the media server, in milliseconds. |
jitter | number | null | optional | How much arrival times vary, in milliseconds. |
loss | number | null | optional | The share of packets lost, from 0 to 1. |
classifyQuality
Use it to turn a measurement into the word the SDK would show, with the same thresholds as the app and the website.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | QualitySample | null | undefined | required | The measurement. |
Returns — "excellent", "good" or "poor" by the thresholds in ConnectionQuality; "unknown" when nothing was measured. It never returns "lost". Example value: "poor".
Errors — none.
GraineInAppClient.hasVoiceHost
Read it to know whether a GraineVoiceLauncher is mounted for this client — the provider then leaves the call to it.
Returns — true while a launcher is mounted. Example value: true.
Errors — none.
GraineInAppClient.setVoiceHost
GraineVoiceLauncher calls it as it mounts and unmounts; your app does not need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | boolean | required | true as a launcher mounts, false as it unmounts. |
Returns — nothing. A change is announced as the voice_host event ({ present }).
Errors — none.
GraineInAppClient.setHostCallLive
GraineVoiceLauncher calls it as its call connects and ends; your app does not need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
live | boolean | required | true while the launcher's call is connected; false when it ends. |
Returns — nothing. Messages typed before the call connected are handed to it now (each announced as text_sent).
Errors — none.
useGraineVoice
Call it only when your app brings its own audio module instead of GraineVoiceLauncher: it runs a spoken conversation over the client's connection with your adapter. Pass voice to the provider as well, or the agent answers in text.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
adapter | AudioAdapter | null | required | Your microphone and speaker — see AudioAdapter; null until it is ready. |
options | VoiceSessionOptions | {} | Callbacks for the customer's recognised speech and the agent speaking — see VoiceSessionOptions. |
Returns — listening: the microphone is being captured; agentSpeaking: the agent's voice is playing; start(): begin capturing and playing; stop(): stop both. Example value: { listening: false, agentSpeaking: false, start: [Function], stop: [Function] }.
Errors — throws outside a GraineProvider. start() rejects with Error("[Graine] useGraineVoice needs an audio adapter.") while adapter is null, and with whatever your adapter's startCapture throws. Microphone permission must be granted before start() — the hook does not ask.
VoiceSession
Use it outside React to wire your audio adapter to a client's spoken conversation — useGraineVoice wraps it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
client | GraineInAppClient | required | The client whose connection carries the conversation (built with voice: true). |
adapter | AudioAdapter | required | Your microphone and speaker. |
options | VoiceSessionOptions | {} | Callbacks — see VoiceSessionOptions. |
| Member | Type | Meaning |
|---|---|---|
constructor | (client, adapter, options?) | Prepares a session; nothing starts until start(). |
active | boolean | true between start() and stop() — the microphone is being captured. |
start | () => Promise<void> | Plays the agent's audio through your adapter (working out its format once per call) and sends the customer's, holding back the agent's own echo; tells the client how much audio is still queued so turn-taking is timed at playout. Rejects with what your adapter's startCapture throws. Calling it again while active does nothing. |
stop | () => Promise<void> | Stops capture, drops queued playback and listens no more. Calling it while stopped does nothing. |
Errors — start() rejects with your adapter's errors; nothing else throws.
VoiceSessionOptions
Read it to hear what the customer says and when the agent speaks, during a session you run.
| Field | Type | Default | Meaning |
|---|---|---|---|
onTranscript | (text: string, final: boolean) => void | none | Hear the customer's speech as it is recognised — final true for the settled sentence. |
onAgentSpeaking | (speaking: boolean) => void | none | Hear the agent start and stop speaking — drive your own speaking indicator. |
AudioAdapter
Implement it to connect your audio module to useGraineVoice or VoiceSession: capture the microphone, play the agent.
What to expect — Capture must be 16 kHz mono 16-bit audio (CAPTURE_SAMPLE_RATE), base64-encoded, with permission already granted; a different rate is not refused — it produces a confident, wrong transcript.
| Member | Type | Default | Meaning |
|---|---|---|---|
startCapture | (onChunk: (base64: string) => void) => Promise<void> | void | required | Start the microphone and call onChunk with each chunk. |
stopCapture | () => Promise<void> | void | required | Stop the microphone. |
play | (pcm16: Int16Array, sampleRate: number) => void | required | Queue a chunk of the agent's voice for playback. Queue it with a small cushion rather than playing at once, or the voice breaks up. |
clear | () => void | required | Drop everything queued and unplayed — the customer interrupted. |
bufferedSeconds | () => number | optional | Seconds of the agent's voice still waiting to be heard. With it, turn-taking is timed to what the customer actually hears; without it, the timing is optimistic. |
liveAudioStreamAdapter
Use it to build an AudioAdapter from react-native-live-audio-stream for capture and any player you pass for playback.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
LiveAudioStream | any | required | The react-native-live-audio-stream module, passed in. |
sink | Pick<AudioAdapter, "play" | "clear"> | required | Your player: queue the agent's audio, and drop it on an interruption. |
Returns — an AudioAdapter that captures 16 kHz mono in chunks of about 40 ms (with Android's voice-recognition source, which turns on echo cancellation and noise suppression) and plays through sink.
Errors — none.
CAPTURE_SAMPLE_RATE
Read it to capture the microphone at the rate the agent expects.
What to expect — 16 kHz mono; anything else must be resampled before it reaches the SDK.
AGENT_PCM_RATE
Read it to know the rate of the agent's voice on a spoken connection when it arrives as raw samples with no format declared.
What to expect — 24 kHz. Played at any other rate the agent sounds too slow and too low (or too fast).
GraineInAppClient.sendAudio
VoiceSession calls it with each chunk of the customer's voice; call it yourself only when you carry the audio without VoiceSession.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
base64 | string | required | One chunk of 16-bit mono audio, base64-encoded. |
sampleRate | number | 16000 | The chunk's sample rate, in hertz — keep it 16000. |
Returns — true when the chunk was sent; false while muted (the chunk is dropped on the device) or with no open connection. The first chunk while the agent is speaking tells the agent the customer is talking over it. Example value: true.
Errors — none.
GraineInAppClient.endUtterance
Call it when your audio module detects that the customer stopped talking, so the agent answers promptly.
Parameters — none.
Returns — true when the signal was sent; false with no open connection. Example value: true.
Errors — none.
GraineInAppClient.setPlayoutClock
VoiceSession calls it so the client knows how much of the agent's voice is still waiting to be heard; call it yourself only when you play the agent's audio without VoiceSession.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
fn | (() => number) | null | required | Returns the seconds of the agent's voice still queued for playback; null to stop. |
Returns — nothing. With it, the client tells the agent each piece was heard when it actually played (capped at 10 seconds); without it, at once.
Errors — none.
decodeAgentAudio
Use it to turn a frame of the agent's voice into playable samples when you play the agent's audio yourself — it works out whether the frame is µ-law or 16-bit PCM.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
bytes | Uint8Array | required | The frame as it arrived (decode base64 with base64ToBytes). |
declared | { format?: string | null; sampleRate?: number | null } | null | required | What Graine said about the frame, if anything — the audio event's format and sampleRate. A declared format is believed. |
latched | AudioFormat | null | required | What this call's audio was already found to be (the last result's latch), or null at the start of a call. |
fallbackPcmRate | number | 16000 | The rate to assume for PCM nobody declared — pass AGENT_PCM_RATE on a spoken connection. |
Returns — a DecodedAudio, or null for an empty frame. Keep its latch and pass it back as latched: once a frame loud enough to tell has settled the format, the rest of the call uses it. Example value: { pcm16: Int16Array(4800), sampleRate: 24000, kind: "pcm", latch: { kind: "pcm", sampleRate: 24000 } }.
Errors — none.
AudioFormat
Read it as what a call's audio turned out to be; keep it and pass it back to decodeAgentAudio.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | "mulaw" | "pcm" | required | µ-law (8-bit telephone audio) or 16-bit PCM. |
sampleRate | number | required | Samples per second, in hertz. |
DecodedAudio
Read it to play one decoded frame of the agent's voice.
| Field | Type | Default | Meaning |
|---|---|---|---|
pcm16 | Int16Array | required | The samples, ready for a 16-bit player. |
sampleRate | number | required | The rate to play them at, in hertz. |
kind | "mulaw" | "pcm" | required | What the frame was read as. |
latch | AudioFormat | null | required | The format, once a frame settled it — keep it and pass it back as latched; null while still undecided. |
decodeMuLaw
Use it to turn µ-law bytes into 16-bit samples (ITU-T G.711).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
bytes | Uint8Array | required | µ-law bytes, one per sample. |
Returns — one 16-bit sample per byte.
Errors — none.
decodePcm16
Use it to read little-endian 16-bit PCM bytes as samples.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
bytes | Uint8Array | required | Two bytes per sample, low byte first. |
Returns — the samples; an odd trailing byte (half a sample) is dropped rather than played as a click.
Errors — none.
base64ToBytes
Use it to decode base64 on any React Native engine, without depending on atob or Buffer.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
b64 | string | required | Base64 text. |
Returns — the bytes.
Errors — none.
base64ToPcm16
Use it when you already know a base64 chunk is 16-bit PCM — for the agent's audio, prefer decodeAgentAudio, which does not assume.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
b64 | string | required | Base64-encoded little-endian 16-bit PCM. |
Returns — the samples.
Errors — none.
EchoGuard
Use it when you send the microphone yourself on a phone's loudspeaker: it holds back the agent's own voice coming back through the microphone, and lets the customer through when they really talk over it. VoiceSession uses one for you.
| Member | Type | Meaning |
|---|---|---|
constructor | (opts?) | Tune it — every option is optional: overEcho (2.2) how far above the measured echo a level must be, overNoise (4) how far above the room's noise, minLevel (0.012) an absolute floor, sustainMs (240) how long it must last to count as the customer, preRollFrames (12) how much held audio to send ahead of a confirmed interruption, warmupFrames (10) how many frames at the start of each agent utterance are spent measuring its echo. |
threshold | number | How loud a frame has to be, at this moment, to be taken as the customer speaking. |
interrupting | boolean | true once the customer has been loud enough for long enough to be talking over the agent. |
reset | () => void | Forget what the echo sounded like — at the start of each call. |
filter | (base64, pcm, agentAudible, frameMs) => string[] | Decide what to send for one captured frame: base64 the frame as captured, pcm the same frame decoded, agentAudible whether the agent's voice is playing now, frameMs the frame's length. Returns the frames to send, oldest first — empty while it is only echo. |
Errors — none.
RtcVoiceSessionController
Use it for WebRTC voice of your own, outside GraineVoiceLauncher: it places a WebRTC call to the agent with your publishable key. It needs react-native-webrtc and the optional WebRTC peer dependency the package declares — a native module, so a store rebuild. Screen context and actions still travel on the client's connection.
| Member | Type | Meaning |
|---|---|---|
constructor | (opts: RtcVoiceOptions) | Prepares a controller; nothing connects until start(). See RtcVoiceOptions. |
sessionId | string | This call's id (yours from opts.sessionId, else a fresh one) — the same id ties the call to the client's connection, so the agent sees the screen during it. |
getState | () => RtcVoiceState | Where the call is now — see RtcVoiceState. |
start | () => Promise<void> | Get a single-use call credential with your key (and the customer's identity, when opts.client is set), connect, and place the call; resolves once it is ringing. Rejects with an RtcVoiceError: mint_failed (no key, Graine unreachable — "Could not reach the call service." — or refused, with Graine's message), microphone_denied ("The microphone was not allowed." — with opts.client set, its events hear mic_denied too), connect_failed (the connection failed), already_active ("A call is already in progress." — a second start while one runs). |
setMuted | (muted: boolean) => void | Mute or unmute the customer without ending the call; a mute that fails never ends it. |
stop | () => Promise<void> | Hang up and disconnect; an unfinished start() is cancelled. Does nothing when idle or ended. |
Errors — only start() rejects, as above.
RtcVoiceOptions
Read it to configure a WebRTC call: normally only your publishable key and the client.
| Field | Type | Default | Meaning |
|---|---|---|---|
publishableKey | string | none | The agent's publishable key — the call is set up with it. |
baseUrl | string | "https://www.graine.ai" | Where Graine is. |
getSession | () => Promise<RtcVoiceSession> | none | Only for an app that must get the call credential through its own backend: return Graine's answer unread. Without it, publishableKey is required. |
onState | (s: RtcVoiceState) => void | none | Hear the call's state change — drive your call UI from it. |
onEnded | (reason: string) => void | none | Hear the call end, with the reason the connection gave ("ended" or "failed" when it gave none). |
onLog | (line: string) => void | none | Hear diagnostic lines — for your debug log. |
sessionId | string | a fresh id | Ties this call to the client's connection, so the agent sees the screen during the call. |
client | { identityChannel(): Promise<IdentityChannel> } | null | none | Pass your GraineInAppClient so the call carries who the customer is, this install, the memory choice and the time zone — remembered as the chat is. Without it the call is anonymous. |
RtcVoiceSession
Return it, unread, from getSession — it is the call credential Graine issues, and its fields may change.
| Field | Type | Default | Meaning |
|---|---|---|---|
server | string | required | Where the call connects. |
username | string | required | The single-use credential's name. |
password | string | required | The single-use credential's secret — short-lived; never store it. |
realm | string | required | The account the credential belongs to. |
application_sid | string | required | What the call is placed to. |
agent_id | string | optional | The agent the call is with. |
expires_at | string | optional | When the credential stops working (ISO-8601). |
RtcVoiceState
Read it to draw a WebRTC call's state.
| Value | Meaning |
|---|---|
"idle" | No call yet. |
"connecting" | Getting the credential and connecting. |
"ringing" | The call is placed and waiting to be answered. |
"active" | The call is answered: audio is flowing. |
"ended" | The call ended or failed; start() may be called again. |
useKeyboardLift
Call it in a bottom-anchored view of your own (a bar, a composer) to keep it above the keyboard on both platforms.
Parameters — none.
Returns — the points to add to the view's bottom while the keyboard is up: the keyboard's height minus however much the window already shrank for it (Android's resize mode), so the view is never lifted twice. 0 while the keyboard is hidden. Example value: 291.
Errors — none.
liftFor
Use it to compute the keyboard lift yourself, from the keyboard's height and how much the window shrank.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
keyboardHeight | number | required | Height of the keyboard according to the platform; 0 while it is hidden. |
windowShrink | number | required | How much shorter the window is now than before the keyboard showed. |
Returns — keyboardHeight − windowShrink, rounded, never below 0; 0 when the keyboard is hidden. Example value: 0 for a window that already resized.
Errors — none.
useDraggablePlacement
Call it in a bar of your own to let the customer drag it up and down — the gesture, the limits and the arithmetic, with a tap still working.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
opts.baseBottom | number | required | Where the bar rests with no offset, in points from the bottom. |
opts.enabled | boolean | true | false turns the gesture off; the offset is then always 0. |
opts.initialOffset | number | 0 | A position you saved, to restore; applied when it changes. |
opts.onChange | (offset: number) => void | none | Hear where the customer let go — save it. |
opts.topMargin | number | 48 | Space to keep clear at the top of the screen. |
Returns — offset: points to add to the bar's bottom; panHandlers: spread on the bar's outermost view; onLayout: attach to the same view so the limits know the bar's height; dragging: true while a finger moves it (do not open the panel then); reset(): put it back. Example value: { offset: 120, dragging: false, … }.
Errors — none.
clampOffset
Use it to keep a bar's offset where the whole bar stays on screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
offset | number | required | The proposed offset, in points above the resting place. |
b | { windowHeight: number; baseBottom: number; barHeight: number; topMargin?: number } | required | The screen's height, where the bar rests, the bar's height (0 before it is measured — a modest bar is assumed) and the space to keep clear at the top (48). |
Returns — the offset, rounded, between 0 and the highest it may go. A number that is not finite becomes 0. Example value: 540.
Errors — none.
offsetDuringDrag
Use it to work out a bar's offset while a finger drags it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
startOffset | number | required | The offset when the drag began. |
dy | number | required | How far the finger moved down since then (a gesture's own dy; moving up is negative). |
b | PlacementBounds | required | The limits, as for clampOffset. |
Returns — startOffset − dy, kept on screen. Example value: 140.
Errors — none.
isTap
Use it to tell a tap from a drag on something the customer can drag.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
dx | number | required | How far the finger moved across. |
dy | number | required | How far it moved up or down. |
Returns — true when both moved less than DRAG_SLOP_PX. Example value: true.
Errors — none.
DRAG_SLOP_PX
Read it to know how far a finger may move and still count as a tap.
What to expect — 8 points, in each direction.
Several agents in one app
Use these when one app runs more than one agent — a support agent and a sales agent, each with its own publishable key, design and conversations.
One provider per agent
Use one GraineProvider per agent: the provider at the root is the app's agent, and a provider inside another's tree is the agent for that part of the app (a sales screen, say).
What to expect — The agents share one device: who the customer is (identify reaches every agent, each reading its own Who is the user? setting), this install's id, the screen the customer is on, and the line — one conversation at a time on the device, whichever agent. Starting a conversation with one agent ends a chat another holds; a call is ended only when the customer starts another conversation themselves (an automatic connection waits for the call instead). Each agent keeps its own key, design, settings, memory and booked calls. Only the provider at the root registers its client as the default for hooks outside every provider.
For calls that ring, configure every agent in the calls package too — see GraineCalls.configure and GraineCalls.agents.
GraineInAppClient.conversation
Read it to know which conversation this agent's client holds right now, if any — one at a time on the device.
Returns — "call": the voice launcher's call, or this client's spoken connection; "chat": this client's typed chat (open, opening, or coming back after a drop); null: none. Example value: "chat".
Errors — none.
GraineInAppClient.dispose
Call it when you are done with a client you built yourself — its conversation ends and it stops sharing the device with the app's other clients.
Parameters — none.
Returns — nothing. The conversation is hung up as close() does, the client leaves the device, and a booking sheet still open for it resolves null.
Errors — none.
GraineInAppClient.joinDevice
The provider calls it as it mounts; a client joins the device when it is built, so your app does not need it.
Parameters — none.
Returns — nothing. The client shares the device again and takes on the customer the app has identified.
Errors — none.
GraineInAppClient.leaveDevice
The provider calls it as it unmounts a client it built; to finish with a client of your own, call dispose().
Parameters — none.
Returns — nothing. The client stops sharing the device; one still bound to a customer keeps hearing who the customer is until it has signed them out.
Errors — none.
GraineInAppClient.yieldLine
The SDK calls it when another agent takes the line; your app does not need it.
Parameters — none.
Returns — nothing. A call this client's launcher holds ends as a hang-up does (announced as line_taken, { reason: "another_agent" }), and this client's connection closes (the close event carries reason: "another_agent").
Errors — none.
GraineCalls.agents
Read it to see which agents the calls package has been configured with.
Returns — "default", then the default agent's own name when it was configured under one, then every other agent's name; [] before configure(). Example value: ["default", "support", "sales"].
Errors — none.
DEFAULT_AGENT
Read it as the name the calls package gives the agent configure(options) sets up — the one a call uses when you name none.
What to expect — Always "default". startCall({ agent: "default" }) is the same as startCall(); a call's agent reads "default" unless you named the default agent yourself.
GraineCallsAgentOptions
Read it to add one more agent to the calls package: its own publishable key, and its own address when it has one.
| Field | Type | Default | Meaning |
|---|---|---|---|
publishableKey | string | required | The agent's publishable key — calls placed to it and rings from it are matched by it. A missing key rejects configure with publishableKey is required for the agent "<name>". |
baseUrl | string | the default agent's | Where this agent's Graine is, when it is not the default agent's. |
What to expect — The name on the ring, the ringtone and the call screen's design come from that agent's own dashboard settings (the name falls back to displayName); every other option is the default agent's.
GraineCallsAgentsOptions
Read it to configure several agents in one call without a default key: the first agent listed becomes the default.
| Field | Type | Default | Meaning |
|---|---|---|---|
publishableKey | undefined | — | Left out: there is no separate default key. The first agent in agents is the default — the one startCall() uses with no agent named, and the one a ring for an agent this app did not configure comes in as. |
agents | Record<string, GraineCallsAgentOptions> | required | Every agent by your name for it, each with its own key. |
What to expect — Every other field is a GraineCallsOptions field (displayName is still required) and applies to every agent.
Screen context, actions and components
Use these to tell the agent what the customer can see, record what they do, let the agent act in your app and point at things, draw the components the agent sends, notice a customer who is stuck, mask what must not leave the phone, and decide where the launcher appears.
useGraineScreen
Call it in each screen component to tell the agent what that screen shows — for as long as it is mounted.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | ScreenContext | null | required | What the customer can see on this screen — see ScreenContext; null while there is nothing to say. |
Returns — nothing. The screen is re-sent whenever its content changes (compared by value, so an object rebuilt on every render does not resend), and withdrawn on unmount — only if it is still the screen being described, so a screen leaving during a navigation never erases the next one.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
What to expect — With navigationRef on the provider every route is already reported by name; this hook adds the detail a route name cannot carry — the step, the fields, the error showing. Values are masked on the device; an unchanged screen is not sent again; the first change goes at once and later ones at most every 400 ms.
ScreenContext
Read it to describe a screen to the agent: what it is, where it sits in a flow, its fields and what may be done on it.
| Field | Type | Default | Meaning |
|---|---|---|---|
screen | string | required | A stable id for the screen ("checkout_address") — the per-screen friction rules and the conversation's timeline key on it, so keep it constant. |
title | string | optional | The screen's title as the customer sees it — the agent says it out loud. |
journey | { name: string; step?: number; of?: number; next?: string } | optional | Where the screen sits in a flow: its name, the step and how many there are, and what comes next — so the agent can answer "what happens after this?" instead of guessing. |
fields | ScreenField[] | optional | The inputs on the screen and how each is going — see ScreenField. |
error | string | optional | An error banner for the whole screen, apart from any field's own error. |
data | Record<string, unknown> | optional | Anything else the agent should know about the screen — short, shallow values. |
availableActions | string[] | optional | The actions that work on this screen. Leave it out to allow every action the agent declares; it can only narrow that list, never add to it ([] means none here). |
ScreenField
Read it to describe one input on the screen — its status is what lets the agent name the field that is wrong.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The field's id, unique on the screen — updateField and removeField find it by this. |
label | string | optional | The label the customer sees — the agent uses it to name the field. |
value | string | number | null | optional | What is in it. Masked before it leaves the device; never send a raw identifier. |
status | "empty" | "filled" | "invalid" | "pending" | "locked" | optional | How the field is going: empty, filled, invalid (rejected), pending (being checked) or locked (cannot be changed). |
error | string | optional | The validation message actually showing — the most useful thing the agent can offer to fix. |
optional | boolean | optional | true when leaving it blank is fine: it stays on the screen for the agent but is not counted as work left. A field with an error is outstanding either way. |
useGraineField
Call it next to one input's own state so the agent sees its value, its status and the error the moment it shows — without re-describing the whole screen on every keystroke.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The field's id on the screen. |
field | Omit<ScreenField, "name"> | required | Its label, value, status, error and whether it is optional — see ScreenField. |
Returns — nothing. The field is merged into the current screen when something about it changes, and removed when the input unmounts. It needs a screen to belong to: while none is reported the field is dropped until it next changes — report the screen first (useGraineScreen, or navigationRef on the provider), or include the field in it.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineInAppClient.setScreen
Call it outside React (a screen controller, a navigation listener) to tell the agent what the customer is looking at; in components use useGraineScreen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | ScreenContext | null | required | The screen as the customer sees it — see ScreenContext; null forgets it (prefer clearScreen). |
Returns — nothing. Every agent on the device is told. The screen event fires at once for a host carrying its own conversation; the frame to Graine goes at once the first time and at most every 400 ms after. A different screen id is a new visit for friction detection and restarts the stuck clock.
Errors — none.
GraineInAppClient.clearScreen
Call it when a screen goes away — in its cleanup — to withdraw its description, but only if it is still the one being described.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screenId | string | null | undefined | required | The id of the screen leaving. When a different screen is described now, nothing changes; with no id, the current screen is cleared. |
Returns — nothing.
Errors — none.
GraineInAppClient.updateField
Call it when one input changes, to report just that field instead of the whole screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
field | ScreenField | required | The field — merged by name into the current screen's fields, added when new. |
Returns — nothing. Goes out like any screen report (masked, coalesced), and friction detection sees it — a field rejecting the customer twice is noticed here. With no screen reported yet, nothing happens — the field is not kept for later.
Errors — none.
GraineInAppClient.removeField
Call it when an input leaves the screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The field's id. A field that was never reported is ignored. |
Returns — nothing.
Errors — none.
GraineInAppClient.getScreen
Read it to see the screen as last reported — to forward it, or to check what the agent was told.
Parameters — none.
Returns — the last ScreenContext exactly as reported (unmasked), or null when none is. Example value: { screen: "checkout_address", title: "Delivery address" }.
Errors — none.
GraineInAppClient.buildContextFrame
Call it only when you carry the conversation yourself: it builds the context the agent should have right now — the screen, the customer, recent events — masked.
Parameters — none.
Returns — the frame, or null when there is no screen, no identified customer and no recent event to describe. context holds the screen's fields plus idle_ms (how long since the customer last did anything on it), user (the masked traits), app_version, recent_events and highlightable (what the agent may point at), all masked; available_actions lists the actions usable here — absent means every declared action, [] means none; seq counts frames. Example value: { type: "app_context", context: { screen: "checkout_address", idle_ms: 4200, user: { name: "Asha" } }, seq: 7 }.
Errors — none.
useGraineTrack
Call it to record something that happened in your app — a payment declined, an upload that failed — so the agent knows it when the customer next asks, without making it speak.
Parameters — none.
Returns — a track(name, data?) function; see GraineInAppClient.track.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineInAppClient.track
Call it outside React to record a product event the agent should know about, without making it speak.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The event's name — "payment_declined". Names starting tap: are taps (see useGraineTap). An empty name is ignored. |
data | Record<string, unknown> | none | Details of the event; masked on the device. |
opts.coalesce | boolean | false | true for a burst you would rather send once — automatic tap capture uses it: friction still sees each event at once, and the context goes out at the end of the 400 ms window. |
Returns — nothing. The last 10 events ride with the screen context (oldest dropped), every agent on the device records them, and the same event recorded twice within a moment by two providers counts once. Friction detection reads taps here (rage taps, a submit with fields still to fill).
Errors — none.
GraineInAppClient.getRecentEvents
Read it to see the events the agent will be given, oldest first.
Parameters — none.
Returns — a copy of the last 10 tracked events: name, at (milliseconds since 1970) and the data given. Example value: [{ name: "tap:pay", at: 1760500000000 }].
Errors — none.
useGraineTap
Call it to wrap a button's onPress so the agent knows the button was tapped — your handler runs exactly as before, even if tracking fails.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | What was tapped, in words the agent can use — tracked as tap:<name> ("pay" → tap:pay). |
handler | T | none | Your own handler; it runs after the tap is recorded and its result is returned. |
data | Record<string, unknown> | none | Details to record with the tap; masked on the device. |
Returns — the wrapped handler, stable across renders. Example value: [Function].
Errors — none: a tracking failure never stops the button.
UNNAMED_TAP
Read it as the name an automatically captured tap is recorded under.
What to expect — Named taps are tap:<name>; a tap GraineTouchCapture saw with no name for the control is tap:screen — "tapped the screen" — so the timeline, the rage-tap rule and the agent all read it sensibly.
GraineTouchCapture
Use it to record every tap in a part of your app with no per-button wrapping — around screens that sit outside the provider (the provider wraps its own children when captureTaps or fab is set).
Props — GraineTouchCaptureProps.
Returns — a view filling its parent, wrapping children. It never takes a touch away from your app.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client; nothing inside it can crash a touch.
What to expect — A short, still touch — one finger, moving at most TAP_MAX_MOVE_PX, lasting at most TAP_MAX_MS — is recorded as tap:screen with where it landed, only while a screen has been reported and (inside a provider) the launcher may show.
GraineTouchCaptureProps
Read it to wrap the part of your app whose taps should be recorded.
| Prop | Type | Default | Meaning |
|---|---|---|---|
children | React.ReactNode | required | The part of your app to watch. |
enabled | boolean | true | false stops recording without unmounting the wrapper — on a payment keypad, say. |
style | StyleProp<ViewStyle> | { flex: 1 } | The wrapper view's style. |
TAP_MAX_MOVE_PX
Read it to know how far a touch may move and still count as a tap.
What to expect — 12 points; further is a scroll or a drag.
TAP_MAX_MS
Read it to know how long a touch may last and still count as a tap.
What to expect — 400 milliseconds; longer is a long-press.
GraineInAppClient.reportEvent
Call it when something happens that the agent may want to speak up about — an upload failed, an application was rejected — and let the agent decide whether to.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
event | AppEvent | required | What happened and how the agent should treat it — see AppEvent. |
Returns — nothing. The agent holding the conversation on the device hears it; the app_event event fires as well, for a host carrying the conversation itself.
Errors — none.
AppEvent
Read it to describe something worth the agent speaking up about, unprompted — or, with quiet, something it should simply know.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The event's name — "upload_failed", "stalled_on_step". |
detail | string | optional | The situation in plain words; the agent is given it as it is. |
cooldown_seconds | number | 20 | Seconds before the agent may volunteer again. |
max_per_session | number | 3 | The most times the agent may volunteer in one conversation. |
quiet | boolean | false | true to record it in the agent's context without making it speak — a state, not a nudge. |
data | Record<string, unknown> | optional | Structured details — short, shallow values. |
GraineInAppClient.noteInteraction
Call it when the customer does something your screen reports no other way — they are not stuck — to restart the stuck clock.
Parameters — none.
Returns — nothing. Every agent on the device restarts its idle timer for the current screen. The SDK calls it for you when the customer speaks.
Errors — none.
useGraineVariables
Call it to give the agent's prompt values from your app's own data while it runs — the plan just upgraded to, the cart total.
Parameters — none.
Returns — a setVariables(vars) function; see GraineInAppClient.setVariables.
Errors — throws outside a GraineProvider.
GraineInAppClient.setVariables
Call it outside React to set prompt variables while the app runs.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
vars | Record<string, unknown> | null | required | Variables by name. Merged into what you set before; a key set to null is removed; null clears them all. Numbers and booleans become strings; objects are ignored. |
Returns — nothing. Values are masked on the device. The set reaches the next conversation on every path — the chat, the voice launcher's next call, the calls package — and a live voice call at once. The variables event fires with the merged set.
Errors — none.
What to expect — Precedence, last wins: the agent's defaults in the dashboard, the provider's variables, these, then the customer's traits from identify(). On the usual ticketed connection the agent receives at most 12 variables, about 1 KB, within the TICKET_VARIABLE_LIMITS — past that, the biggest are left off first, who_called and call_reason last.
GraineInAppClient.getVariables
Read it to see every variable the agent will receive — to check what a conversation will carry, or when you carry the conversation yourself.
Parameters — none.
Returns — every variable the agent will receive, merged in precedence order — the dashboard's defaults, the provider's variables, setVariables(), then identify() (without the identifying trait) — as the next conversation carries them: the other SDKs' currentVariables(). A copy: changing it changes nothing. The same set as allVariables(). Example value: { first_name: "Asha", plan: "Plus" }.
Errors — none.
GraineInAppClient.allVariables
Read it to see the merged variables in their order of precedence — the same set getVariables() returns.
Parameters — none.
Returns — the agent's dashboard defaults, then the provider's variables, then setVariables(), then the customer's masked traits (without the identifying trait) — later ones winning. Example value: { greeting_name: "Asha", plan: "Plus" }.
Errors — none.
useGraineAction
Call it in the component that can do the thing — "open the cart", "switch language" — to let the agent do it while that component is mounted.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The action's name, exactly as declared on the agent (Agent → Embed & Widgets → Actions). |
handler | ActionHandler | required | What to do. It receives the agent's arguments and returns ok, refused or error — see ActionHandler. |
Returns — nothing. The action is available while the component is mounted and withdrawn when it unmounts. A handler that closes over fresh state is used without registering again.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineInAppClient.registerAction
Call it outside React to teach the agent one thing it can do in your app.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The action's name, exactly as declared on the agent. Registering a name again replaces its handler. |
handler | ActionHandler | required | What to do — see ActionHandler. |
Returns — a function that unregisters it. The change reaches the agent with the screen context at once.
Errors — none. An action the agent does not declare is never called — checkActions() lists such mismatches.
ActionHandler
Read it to write an action handler: it gets the agent's arguments and answers what happened.
What to expect — Answer within about 5.5 seconds: after that the agent is told ""<name>" did not finish in time.", and after 6 seconds it would decide the action did not happen. For slow work, return at once and report the result with reportEvent or the screen when it is done. Returning nothing counts as { status: "ok" }; a throw is answered error with ""<name>" failed: <message>".
AppActionResult
Read it to answer an action honestly: ok makes the agent tell the customer it is done.
| Field | Type | Default | Meaning |
|---|---|---|---|
status | "ok" | "error" | "refused" | required | ok — it happened; refused — the customer or the app declined (with a reason, the most useful answer there is); error — it failed. |
data | Record<string, unknown> | optional | Structured results the agent can reason about. |
message | string | optional | A sentence for the agent, not the customer — it decides how to put it. |
GraineInAppClient.executeAction
Call it to run one of your registered actions yourself and get its answer — the calls package uses it to run the agent's actions during a call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The registered action's name. |
args | Record<string, unknown> | {} | Its arguments. |
Returns — the handler's AppActionResult, within the same deadline as an action the agent asks for. An action this build never registered answers refused, with the message This app build does not implement "open_cart". for an action named open_cart. The action_result event fires either way. Example value: { status: "ok", message: "The app is now in Hindi." }.
Errors — none: a throw or a timeout is answered error, never rejected.
AppActionRequest
Read it to see what the agent sends when it asks your app to act — the action event carries it.
| Field | Type | Default | Meaning |
|---|---|---|---|
action_id | string | required | The request's id; the answer is matched to it. |
name | string | required | The action's name — matches a handler you registered. |
kind | string | optional | The kind of action, when the agent says. |
arguments | Record<string, unknown> | required | The agent's arguments for the action. |
GraineInAppClient.checkActions
Call it while developing to see which of your actions the agent can really call — an action must be both registered in the app and declared on the agent.
Parameters — none.
Returns — an ActionDiagnosis. Before the agent's settings load, every registered action reads as undeclared. Example value: { usable: ["open_cart"], undeclared: ["set_theme"], unhandled: ["open_orders"] }.
Errors — none. When the settings load with a mismatch, the console says so and the diagnostic event fires.
ActionDiagnosis
Read it to fix your actions: what works, what the agent cannot call, and what your app does not handle.
| Field | Type | Default | Meaning |
|---|---|---|---|
usable | string[] | required | Registered here and declared on the agent — these work. |
undeclared | string[] | required | Registered here but not declared on the agent — the agent cannot call them; declare them in Embed & Widgets. |
unhandled | string[] | required | Declared on the agent with no handler here — the agent will be told this build cannot do them. |
GraineInAppClient.availableActions
Read it to see which actions this build can perform right now.
Returns — the names of the registered actions. Example value: ["open_cart", "set_language"].
Errors — none.
GraineInAppClient.getAvailableActions
Read it for the same list as a fresh copy — the calls package reads it.
Parameters — none.
Returns — a copy of the registered actions' names. Example value: ["open_cart"].
Errors — none.
NAVIGATE_ACTION
Register a handler under this name once, and every screen a component opens can be set in the dashboard with no app release.
What to expect — A component's tap with a path runs this action with { path }; an app that has not registered it refuses, and the agent is told.
useGraineHighlight
Call it on an element the agent may point at — "tap Continue" is no help if the customer cannot find Continue — and style it while highlighted is true.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The name the agent points at it by — "continue_button". |
Returns — highlighted: true while the agent is pointing at it (6 seconds by default). The SDK draws nothing: style it as your app would — a ring, a pulse. Example value: { highlighted: false }.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
What to expect — Declare the highlight_element action on the agent once; the SDK answers it itself, refusing names that are not on screen and naming the ones that are.
GraineInAppClient.registerHighlightable
Call it outside React to make an element something the agent may point at.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The element's name; an empty name is ignored. |
Returns — a function that withdraws it. The agent is offered it with the screen context.
Errors — none.
GraineInAppClient.getHighlightables
Read it to see what the agent can point at right now.
Parameters — none.
Returns — the registered names. Example value: ["continue_button", "plan_card"].
Errors — none.
GraineInAppClient.highlight
Call it to point at an element yourself — the agent's highlight_element comes through here too.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string | null | required | The element to point at; null to stop. |
ttlMs | number | 6000 | How long the highlight lasts, in milliseconds. |
Returns — nothing. The highlight event fires with { name, ttlMs }; useGraineHighlight follows it.
Errors — none.
useGraineWidget
Call it to draw the component the agent just sent — a product list, choices, time slots, a card — yourself, and to answer it; GraineWidget does all of this for you.
Parameters — none.
Returns — the component's state and controls:
| Field | Type | Meaning |
|---|---|---|
widget | GraineWidget | null | The component on screen now (see the object below), or null. A component sent while nothing was listening is kept until it is answered. |
answered | { widget: GraineWidget; data: Record<string, unknown>; summary: string } | null | What the customer just chose, so it can stay on screen as one line; cleared when the next component arrives or on dismiss. |
loading | { id: string; kind: string } | null | A component is on its way — draw a skeleton for kind; cleared when it arrives, or after PENDING_WIDGET_MS. |
awaitingReply | boolean | The customer answered and the agent has not spoken since — draw the loader beside their choice; cleared when the agent starts, or after AWAITING_REPLY_MS. |
closed | GraineWidget | null | The most recent component the customer dismissed without picking anything. |
reopen | () => void | Put closed back on screen, exactly as it was. |
submit | (data: Record<string, unknown>, summary: string) => void | Answer the component: data is the structured answer, summary the sentence the agent hears (the customer's own bubble in a chat). When the component names an in-app action or path, that runs first (a failure is told to the agent) and its say replaces the summary. |
dismiss | () => void | Close it without choosing. The agent is told quietly, so it does not refer to options the customer can no longer see. |
The component object — what widget, closed and answered.widget hold:
| Field | Type | Meaning |
|---|---|---|
id | string | The component's id — submitWidget answers it by this. |
kind | string | What to draw — "product_list", "choices", "calendar_slots", "info_card", or whatever your agent declares. Draw fallbackText for a kind you cannot draw. |
data | Record<string, unknown> | The content to draw it from; image paths are already absolute addresses. |
fallbackText | string | What the agent would have said instead — show it when you cannot draw kind. |
style | WidgetStyle | How the dashboard asked for it to look, already checked — see WidgetStyle. |
onSelect | WidgetSelectAction | What your app should do on a tap, beyond telling the agent — see WidgetSelectAction. |
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineWidget
Mount it where you want the agent's components drawn by the SDK — a sheet, a full screen or a card above the bar, in the agent's design — and the SDK's own bars leave components to it.
Props — GraineWidgetProps.
Returns — the current component (or its loader, or the customer's answered choice), or null when there is none.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client. A component that fails to draw draws nothing; the conversation goes on.
What to expect — Where it draws follows widgetSurface(kind) unless the dashboard chose: product lists take the screen; comparisons, choices, quick actions and time slots slide up as a sheet; an information card, or a kind this release cannot draw, sits as a card. Spoken prompts ("just say it") show only on a call. Light or dark follows the dashboard, and "auto" follows the phone.
GraineWidgetProps
Read it only to override the dashboard's look for components in your app — every prop is optional.
| Prop | Type | Default | Meaning |
|---|---|---|---|
theme | Partial<GraineWidgetTheme> | the dashboard's light or dark palette | Colours to use instead, one at a time — see GraineWidgetTheme. |
accent | string | the component's, else the dashboard's accent | The colour of buttons and badges. |
radius | number | the component's, else the dashboard's | The corner rounding, in points. |
GraineWidgetTheme
Read it to override the colours a component is drawn in.
| Field | Type | Default | Meaning |
|---|---|---|---|
surface | string | the palette's | The component's background. |
border | string | the palette's | Hairlines and chip fills. |
text | string | the palette's | Titles and prices. |
muted | string | the palette's | Subtitles, facts and secondary labels. |
onAccent | string | chosen for contrast | Text on the accent — button labels. Chosen by brightness, so a pale accent still reads. |
GraineInAppClient.submitWidget
Call it to answer a component the agent drew, when you draw components yourself without useGraineWidget.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widgetId | string | required | The component's id. |
data | Record<string, unknown> | required | The structured answer — what was chosen. |
summary | string | required | The sentence the agent hears as the customer's turn — "I'll take the Plus plan". Without it the agent sees the answer but the conversation does not move. |
Returns — nothing. The widget_result event fires for a host carrying the conversation itself.
Errors — none.
GraineInAppClient.pendingWidget
Read it to find a component the agent sent while your UI was not mounted — so the customer never opens your panel to nothing.
Returns — the component as the agent sent it (with widget_id, kind, data, fallback_text), kept until it is answered or dismissed; null when there is none. useGraineWidget reads it for you. Example value: null.
Errors — none.
GraineInAppClient.clearPendingWidget
Call it when a component you drew yourself has been answered or dismissed, so it is not shown again.
Parameters — none.
Returns — nothing.
Errors — none.
GraineInAppClient.presentWidget
Use it to draw a component yourself — a preview, a test, a screen of your own — as if the agent had just sent it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
frame | Record<string, unknown> | required | The component as the agent sends it: widget_id (required — a frame without it is not drawn), kind, data, and optionally fallback_text, style and on_select. |
Returns — nothing. It becomes the pending component (pendingWidget) and every component view draws it — useGraineWidget(), GraineWidget, the bar — announced as the widget event.
Errors — none.
What to expect — It is read exactly as the agent's would be: image paths in data become absolute addresses, and style and on_select are checked. An answer (submit, submitWidget) goes to the conversation that is live, as for a component the agent sent.
GraineInAppClient.presentPendingWidget
Use it to show the loader a component on its way draws — to preview the dashboard's loader, or in a test — as if the agent had asked for one.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hint | Record<string, unknown> | {} | What is coming: kind, for its skeleton, and optionally widget_id. |
Returns — nothing. Announced as widget_pending: useGraineWidget().loading and GraineWidget draw the loader until a component arrives (presentWidget) or PENDING_WIDGET_MS passes.
Errors — none.
PENDING_WIDGET_MS
Read it to know how long a "component coming" skeleton may stand with nothing arriving.
What to expect — 12 seconds; useGraineWidget().loading clears itself after it.
AWAITING_REPLY_MS
Read it to know how long the loader beside an answered component waits for the agent.
What to expect — 12 seconds; useGraineWidget().awaitingReply clears itself after it, so a reply that never comes never leaves a spinner.
widgetSurface
Use it to decide where a component of a kind is drawn, by the same rule as the SDK and the website.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
kind | string | null | undefined | required | The component's kind. |
Returns — "screen" for product_list and products; "sheet" for plan_comparison, choices, quick_actions and calendar_slots; "card" for info_card and every kind it does not know (what is drawn for those is the agent's sentence, which should not take over the screen). Example value: "sheet".
Errors — none.
WidgetSurface
Read it as where a component goes on screen.
| Value | Meaning |
|---|---|
"screen" | The component fills the whole screen — for pictures or several items to compare. |
"sheet" | It slides over the app — a choice to make, then back. |
"card" | It sits above the bar — a fact, or a kind that cannot be drawn. |
WIDGET_SURFACE_CHOICES
Use it to list the surface choices the dashboard offers for a component.
What to expect — "auto" keeps the per-kind rule of widgetSurface.
WidgetStyle
Read it to see how the dashboard asked for a component to look — the preset and the adjustments on it.
What to expect — Already checked when it reaches you: unknown keys and values outside these lists are dropped, so an older SDK draws the default for a preset it does not know.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | WidgetPreset | "cards" | The whole look in one word — see WidgetPreset. |
imageSize | WidgetImageSize | the preset's | How big pictures are drawn. |
density | WidgetDensity | "comfortable" | How much room around each item. |
button | WidgetButton | the preset's | How the action buttons are drawn. |
facts | boolean | true | Show the fact chips. |
columns | 1 | 2 | 1 | Columns, for the gallery and cards presets only. |
surface | "auto" | WidgetSurface | "auto" | Where it is drawn; "auto" follows widgetSurface. |
radius | number | the Appearance radius | Corner rounding, 0–24. |
accent | string | the Appearance accent | A #RRGGBB colour for this component. |
platform | WidgetPlatform | "auto" | Whose design conventions to follow — see WidgetPlatform. |
select | WidgetSelect | "one" | With "many", the customer can choose several options and send a single answer. |
maxSelect | number | 3 | With select: "many", the most that may be picked, 2–8. |
WidgetLook
Read it as the look to draw: the preset's defaults with the adjustments on top, every field settled.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | WidgetPreset | required | The preset in force. |
imageSize | WidgetImageSize | required | The picture size in force (gallery and spotlight large, cards medium, list and compact small, unless chosen). |
density | WidgetDensity | required | The spacing in force. |
button | WidgetButton | required | The button style in force (list row, compact outline, the others filled, unless chosen). |
facts | boolean | required | Whether to show fact chips. |
columns | 1 | 2 | required | Columns in force — 2 only for gallery or cards with two chosen. |
surface | WidgetSurface | required | Where to draw it, with "auto" resolved for the kind. |
radius | number | optional | The component's own corner rounding, when it has one. |
accent | string | optional | The component's own accent, when it has one. |
platform | WidgetPlatform | required | As chosen; "auto" is for the renderer to resolve with resolvePlatform. |
select | WidgetSelect | required | One answer, or several. |
maxSelect | number | required | The most that may be picked — meaningful only with select: "many". |
normaliseWidgetStyle
Use it to check a style that came from anywhere: only the keys and values a renderer reads survive.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | unknown | required | The style as it arrived. |
Returns — a WidgetStyle with unknown keys dropped, words lowercased and checked against their lists, radius clamped to 0–24, accent kept only as #RRGGBB (uppercased), maxSelect clamped to 2–8; {} for anything that is not an object. Example value: { preset: "gallery", columns: 2 }.
Errors — none.
resolveWidgetLook
Use it to settle how a component of a kind should look, from its style.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
style | WidgetStyle | null | undefined | required | The component's style (checked again here). |
kind | string | null | undefined | required | Its kind, for the surface when the style says "auto". |
Returns — a WidgetLook. Example value: { preset: "cards", imageSize: "medium", density: "comfortable", button: "filled", facts: true, columns: 1, surface: "sheet", platform: "auto", select: "one", maxSelect: 3 }.
Errors — none.
WidgetPreset
Read it as the whole look of a component in one word, chosen on the component in the dashboard.
| Value | Meaning |
|---|---|
"gallery" | Large pictures, filled buttons; one or two columns. |
"cards" | Medium pictures in cards, filled buttons — the default. |
"list" | Small pictures in rows, each row a button. |
"spotlight" | One large item at a time, paged. |
"compact" | Small pictures, outline buttons — the least room. |
WIDGET_PRESETS
Use it to list the presets.
What to expect — A fixed list.
DEFAULT_WIDGET_PRESET
Read it as the preset a component without one is drawn in.
What to expect — "cards".
WidgetImageSize
Read it as how big a component's pictures are drawn.
| Value | Meaning |
|---|---|
"small" | Thumbnails beside the text. |
"medium" | Pictures that share the card with the text. |
"large" | Pictures that lead the card. |
WIDGET_IMAGE_SIZES
Use it to list the picture sizes.
What to expect — A fixed list.
WidgetDensity
Read it as how much room a component leaves around each item.
| Value | Meaning |
|---|---|
"comfortable" | The default spacing. |
"compact" | Tighter, to fit more on screen. |
WIDGET_DENSITIES
Use it to list the densities.
What to expect — A fixed list.
WidgetButton
Read it as how a component's action buttons are drawn.
| Value | Meaning |
|---|---|
"filled" | A solid button in the accent. |
"outline" | An outlined button. |
"row" | The whole row is the button. |
WIDGET_BUTTONS
Use it to list the button styles.
What to expect — A fixed list.
WidgetPlatform
Read it as whose design conventions a component follows.
| Value | Meaning |
|---|---|
"auto" | Follow the phone: Apple's on an iPhone, Material on Android, One UI on a Samsung. |
"ios" | Apple's conventions everywhere. |
"material" | Google's Material conventions everywhere. |
"oneui" | Samsung's One UI conventions everywhere. |
WIDGET_PLATFORMS
Use it to list the platform choices.
What to expect — A fixed list.
PlatformGrammar
Read it as the shapes a platform's conventions change — not the colours, which stay the brand's.
| Field | Type | Default | Meaning |
|---|---|---|---|
radius | number | required | The card corner rounding the convention would choose, in points — used only when the platform is chosen explicitly (under "auto" the brand's radius wins). |
chipRadius | number | required | Chip corner rounding (999 is a full pill). |
buttonRadius | number | required | Button corner rounding (999 is a full pill). |
pillButtons | boolean | required | Whether buttons are full pills. |
separatorInset | number | required | How far list dividers are indented, in points (0 runs edge to edge). |
grabber | { width: number; height: number } | required | The size of a sheet's drag handle, in points. |
titleWeight | "600" | "700" | "500" | required | The weight of titles. |
padBoost | number | required | Extra padding, in points. |
chipOutlined | boolean | required | Whether chips are outlined rather than filled. |
PLATFORM_GRAMMAR
Use it to read each platform's conventions as numbers.
| Key | Value | Meaning |
|---|---|---|
ios | { radius: 12, chipRadius: 8, buttonRadius: 12, pillButtons: false, separatorInset: 16, grabber: { width: 36, height: 5 }, titleWeight: "600", padBoost: 0, chipOutlined: false } | Apple's: 12-point cards and buttons, 8-point tags, inset dividers, semibold titles. |
material | { radius: 16, chipRadius: 8, buttonRadius: 20, pillButtons: true, separatorInset: 0, grabber: { width: 32, height: 4 }, titleWeight: "500", padBoost: 0, chipOutlined: true } | Material 3: 16-point cards, outlined chips, pill buttons, full-width dividers, medium titles. |
oneui | { radius: 26, chipRadius: 999, buttonRadius: 999, pillButtons: true, separatorInset: 0, grabber: { width: 40, height: 4 }, titleWeight: "700", padBoost: 2, chipOutlined: false } | One UI: 26-point cards, pill chips and buttons, roomier padding, bold titles. |
resolvePlatform
Use it to turn "auto" into the phone's own conventions.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
platform | WidgetPlatform | undefined | required | The chosen platform; anything but "auto" is returned as it is. |
os | string | undefined | required | Platform.OS. |
manufacturer | string | null | none | The phone's maker (Android) — a Samsung follows One UI. |
Returns — "material" on Android ("oneui" on a Samsung), "ios" everywhere else. Example value: "material".
Errors — none.
WidgetSelect
Read it as whether the customer picks one item or several.
| Value | Meaning |
|---|---|
"one" | A tap answers at once. |
"many" | Each row gets a tick and one button answers with everything picked — for "compare these". |
WIDGET_SELECTS
Use it to list the selection choices.
What to expect — A fixed list.
MAX_SELECT_DEFAULT
Read it as how many items may be picked when the component says none.
What to expect — 3.
manySummary
Use it to word the answer when several items were picked — the sentence the agent hears and, in a chat, the customer's bubble.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
titles | string[] | required | The names of the items picked. |
label | string | "Compare" | The button's label, used as it is; an empty label leaves just the names. |
Returns — the label, a colon, then the names joined with commas and "and"; "" when nothing was picked. Example value: "Compare: Basic, Plus and Pro".
Errors — none.
WidgetSelectAction
Read it as what your app does when the customer taps a component's item — beyond telling the agent: open a screen, or run one of your actions.
| Field | Type | Default | Meaning |
|---|---|---|---|
path | string | optional | Where to go, as your app understands it — "/product/{id}" — run through your NAVIGATE_ACTION handler, so new screens are set in the dashboard with no release. |
action | string | optional | One of your registered actions to run instead ([a-z0-9_]). It wins over path. |
args | Record<string, string> | optional | Its arguments. Values may use {id}, {title}, {price}, {subtitle} and {summary}, filled from the tapped item. |
say | string | optional | What the agent is told instead of the usual sentence; the same placeholders. |
normaliseSelectAction
Use it to check a tap action that came from anywhere.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | unknown | required | The action as it arrived. |
Returns — a WidgetSelectAction: action kept only when it is a valid action name (lowercased); path and say trimmed to SELECT_VALUE_MAX characters; at most SELECT_ARG_MAX arguments, each value cut to SELECT_VALUE_MAX; {} when there is neither an action nor a path. Example value: { path: "/product/{id}" }.
Errors — none.
fillSelectArgs
Use it to fill a tap action's arguments from the tapped item.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
args | Record<string, string> | undefined | required | The arguments, with placeholders. |
item | Record<string, unknown> | required | The tapped item's values. |
Returns — each argument with its placeholders filled by fillSelectTemplate. Example value: { sku: "P-1001" }.
Errors — none.
fillSelectTemplate
Use it to fill {id}, {title} and the other placeholders of one string from the tapped item.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
template | string | required | The text with placeholders. |
item | Record<string, unknown> | required | The tapped item's values. |
Returns — the text with each placeholder replaced; a placeholder the item lacks becomes empty, never left as written. Example value: "/product/P-1001".
Errors — none.
selectCall
Use it to work out the action to run, and its arguments, for one tap on a component's item.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
onSelect | WidgetSelectAction | undefined | null | required | The component's tap action. |
item | Record<string, unknown> | required | The tapped item. |
Returns — { name, args }: the explicit action, else NAVIGATE_ACTION; the filled arguments, plus path (filled) whenever there is one. null when the component names neither. Example value: { name: "open_screen", args: { path: "/product/P-1001" } }.
Errors — none.
SELECT_ARG_MAX
Read it as the most arguments a tap action keeps.
What to expect — 10; more are dropped.
SELECT_VALUE_MAX
Read it as the longest a tap action's path, say or argument value may be.
What to expect — 200 characters; longer is cut.
resolveWidgetImages
Use it to turn a component's image paths into addresses a phone can load, when you draw components yourself (useGraineWidget already does).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
data | Record<string, unknown> | required | The component's data. |
baseUrl | string | required | The agent's address — client.opts.baseUrl. |
Returns — a copy in which every image field (image, imageUrl, icon, logo, photo, thumbnail and their Url/_url forms), at the top level and one level into lists, that starts with a single / is prefixed with baseUrl. Absolute addresses and data URIs are left alone. Example value: { image: "https://www.graine.ai/uploads/plan-a.png" }.
Errors — none.
resolveWidgetPalette
Use it to draw something of your own beside a component in the same colours — light, dark, or the phone's choice, as the dashboard says.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | Record<string, any> | null | undefined | required | The agent's appearance (useGraineAgent().appearance). |
opts.scheme | "light" | "dark" | null | none | The phone's colour scheme (useColorScheme()), for the dashboard's "auto". |
opts.accent | string | none | The accent, so onAccent is chosen against it. |
Returns — a WidgetPalette. Example value: { surface: "#FFFFFF", border: "#E5E4E0", text: "#101318", muted: "#5F6672", onAccent: "#FFFFFF", mode: "light" }.
Errors — none.
WidgetPalette
Read it as the colours a component is drawn in, for the light or dark mode in force.
| Field | Type | Default | Meaning |
|---|---|---|---|
surface | string | required | The background (#FFFFFF light, #17181B dark). |
border | string | required | Hairlines and chip fills. |
text | string | required | Titles and prices. |
muted | string | required | Subtitles, facts, secondary labels. |
onAccent | string | required | Text on the accent — dark on a bright accent, white otherwise. |
mode | WidgetMode | required | Which set this is, with "auto" resolved against the phone. |
WidgetMode
Read it as which colour set a palette is.
| Value | Meaning |
|---|---|
"light" | The light set. |
"dark" | The dark set. |
LoaderKind
Read it as how waiting looks — chosen in Embed & Widgets → Appearance and drawn by GraineLoader.
| Value | Meaning |
|---|---|
"dots" | Three dots — the default. |
"pulse" | A pulsing disc. |
"ring" | A spinning ring on a faint track. |
"bars" | Rising and falling bars. |
"mark" | The brand's logo, breathing. |
LOADER_KINDS
Use it to list the loader kinds.
What to expect — A fixed list.
DEFAULT_LOADER_KIND
Read it as the loader drawn when the dashboard chose none.
What to expect — "dots".
LoaderSize
Read it as the loader's size setting.
| Value | Meaning |
|---|---|
"small" | 14 points. |
"medium" | 20 points — the default. |
"large" | 28 points. |
LOADER_SIZES
Use it to list the loader sizes.
What to expect — A fixed list.
LoaderSpeed
Read it as the loader's pace.
| Value | Meaning |
|---|---|
"calm" | One cycle every 1.6 seconds. |
"normal" | One cycle every 1.1 seconds — the default. |
"quick" | One cycle every 0.7 seconds. |
LOADER_SPEEDS
Use it to list the loader speeds.
What to expect — A fixed list.
LoaderMoment
Read it as the moments a customer waits, each with its own words.
| Value | Meaning |
|---|---|
"connecting" | The conversation is being set up. |
"reconnecting" | The voice engine is reloading after a failure. |
"thinking" | The customer has stopped talking and the agent's reply has not begun. |
"component" | The agent requested a component, and its content is still loading. |
LOADER_MOMENTS
Use it to list the moments.
What to expect — A fixed list.
LoaderText
Read it as the words shown for each moment.
| Field | Type | Default | Meaning |
|---|---|---|---|
connecting | string | "Connecting…" | Shown while the conversation is set up. |
reconnecting | string | "Reconnecting…" | Shown while the voice engine reloads. |
thinking | string | "Thinking…" | Shown while the agent works out its answer. |
component | string | "Loading…" | Shown while a component's content loads. |
What to expect — At most LOADER_TEXT_MAX characters each; an empty value means the default.
DEFAULT_LOADER_TEXT
Read it as the words for each moment when the dashboard wrote none.
| Key | Value | Meaning |
|---|---|---|
connecting | "Connecting…" | While the conversation is set up. |
reconnecting | "Reconnecting…" | While the voice engine reloads. |
thinking | "Thinking…" | While the agent works out its answer. |
component | "Loading…" | While a component's content loads. |
LOADER_TEXT_MAX
Read it as the longest a loader's words may be.
What to expect — 40 characters; longer words are cut.
LoaderStyle
Read it as the loader the dashboard chose, as it arrives with the agent's appearance (appearance.loader).
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | LoaderKind | "dots" | The animation. |
color | string | "accent" | "accent", or a #RRGGBB colour. |
size | LoaderSize | "medium" | The size. |
speed | LoaderSpeed | "normal" | The pace. |
text | LoaderText | the defaults | The words for each moment. |
skeleton | boolean | false | Also draw the outline of the coming component under the loader — for a list that always looks the same. |
LoaderLook
Read it as the loader to draw, as numbers a renderer uses directly.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | LoaderKind | required | The animation to draw. |
color | string | null | required | The colour; null means the accent. |
px | number | required | The mark's size, in points (14, 20 or 28). |
periodMs | number | required | One animation cycle, in milliseconds (1600, 1100 or 700). |
text | Required<LoaderText> | required | The words for every moment, defaults filled. |
skeleton | boolean | required | Whether to draw the coming component's outline too. |
normaliseLoaderStyle
Use it to check a loader style that came from anywhere.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | unknown | required | The style as it arrived. |
Returns — a LoaderStyle keeping only known kinds, sizes and speeds, a colour that is "accent" or #RRGGBB (uppercased), a boolean skeleton, and words trimmed to LOADER_TEXT_MAX; {} for anything that is not an object. Example value: { kind: "ring", color: "#3740B8" }.
Errors — none.
resolveLoader
Use it to turn a loader style into the numbers to draw.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
style | LoaderStyle | null | undefined | required | The style (checked again here). |
Returns — a LoaderLook with every default filled. Example value: { kind: "dots", color: null, px: 20, periodMs: 1100, text: { connecting: "Connecting…", reconnecting: "Reconnecting…", thinking: "Thinking…", component: "Loading…" }, skeleton: false }.
Errors — none.
MIN_LOADER_MS
Read it as how long a loader, once shown, stays — so a quick answer reads as "waiting, then the answer" rather than a flicker.
What to expect — 450 milliseconds; a component that arrives sooner waits that long behind its loader.
FrictionConfig
Read it to tune when the agent is told the customer looks stuck — idle on a step, a field rejecting them, going back and forth, tapping in frustration, pressing on with fields left, an error on screen.
What to expect — Layered: the built-in defaults (DEFAULT_FRICTION), then the dashboard's rules for the agent, then your app's (the provider's friction, or configureFriction). Each layer changes only what it names; false turns a rule off; unknown keys and bad numbers are ignored. A signal is told to the agent (which decides whether to speak) and to your app as the friction_detected event.
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled | boolean | true | Turns friction detection on or off everywhere. |
cooldownMs | number | 60000 | The least time between two signals on the same screen. |
nudge | FrictionNudge | see FrictionNudge | How often the agent may volunteer, and whether a signal should open your launcher. |
screens | Record<string, FrictionScreenRules> | {} | Rules for single screens, keyed by the screen id you report. |
ignore, idle, repeatedErrors, backAndForth, rageTaps, incompleteSubmit, screenError, message | — | as FrictionScreenRules | The rules themselves, for every screen. |
FrictionScreenRules
Read it to change the rules for one screen (or, at the top level of FrictionConfig, for all of them); every field is optional.
| Field | Type | Default | Meaning |
|---|---|---|---|
ignore | boolean | false | No friction detection on this screen at all. |
idle | { afterMs: number } | false | { afterMs: 45000 } | Untouched on a step this long → stalled_on_step. Fires once per visit to the screen. |
repeatedErrors | { count: number; windowMs: number } | false | { count: 2, windowMs: 60000 } | The same field showing an error this many times in the window → repeated_field_error. |
backAndForth | { visits: number; windowMs: number } | false | { visits: 3, windowMs: 45000 } | Arriving on the same screen this many times in the window → looping_between_screens. |
rageTaps | { taps: number; windowMs: number } | false | { taps: 4, windowMs: 1500 } | The same tap this many times in the window → rage_taps. |
incompleteSubmit | { taps: string[] } | false | submit, next, continue, save, proceed, apply, confirm, done, pay, verify | A tap whose name contains one of these words (any case), while fields are still empty or invalid → incomplete_submit. Set false where pressing on with blanks is fine. |
screenError | boolean | true | An error banner on the screen (ScreenContext.error) is itself a signal → screen_error. |
message | string | the built-in sentence | What the agent is told instead, with {title}, {screen}, {field}, {error}, {count}, {seconds} and {tap} filled in. |
FrictionNudge
Read it to limit how often the agent volunteers after a signal.
| Field | Type | Default | Meaning |
|---|---|---|---|
cooldownSeconds | number | 20 | Seconds the agent waits between two unprompted offers of help. |
maxPerSession | number | 3 | The most unprompted offers in one conversation. |
openLauncher | boolean | false | For a launcher you draw yourself: whether a signal should open it when no conversation is running. The SDK only carries the flag; your app decides what opening means. |
FrictionSignal
Read it as one moment the customer looked stuck — what the agent is told, and what your app hears.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | FrictionKind | required | Which rule fired. |
screen | string | required | The screen id it fired on. |
detail | string | required | The sentence the agent is told — the built-in one, or your message filled in. |
data | Record<string, unknown> | required | The numbers behind it — count, seconds, field, error, tap. |
cooldown_seconds | number | required | How long the agent waits before volunteering again (nudge.cooldownSeconds). |
max_per_session | number | required | The most unprompted offers in the conversation (nudge.maxPerSession). |
FrictionKind
Read it as the names of the friction signals.
| Value | Meaning |
|---|---|
"stalled_on_step" | Untouched on a step for idle.afterMs. |
"repeated_field_error" | A field rejected the customer repeatedErrors.count times. |
"looping_between_screens" | The customer keeps coming back to a screen without getting past it. |
"rage_taps" | The same control (or the screen) tapped again and again in a moment. |
"incomplete_submit" | "Next" or "Submit" pressed with fields still to fill. |
"screen_error" | The screen is showing an error banner. |
DEFAULT_FRICTION
Read it as the built-in friction rules every layer starts from.
| Key | Value | Meaning |
|---|---|---|
enabled | true | Friction detection is on. |
cooldownMs | 60000 | A minute between two signals on one screen. |
idle | { afterMs: 45000 } | 45 seconds untouched. |
repeatedErrors | { count: 2, windowMs: 60000 } | Twice in a minute. |
backAndForth | { visits: 3, windowMs: 45000 } | Three arrivals in 45 seconds. |
rageTaps | { taps: 4, windowMs: 1500 } | Four taps in 1.5 seconds. |
incompleteSubmit | { taps: ["submit", "next", "continue", "save", "proceed", "apply", "confirm", "done", "pay", "verify"] } | The words that mean "move me on". |
screenError | true | An error banner counts. |
nudge | { cooldownSeconds: 20, maxPerSession: 3, openLauncher: false } | The agent's pacing. |
screens | {} | No per-screen rules. |
resolveFriction
Use it to combine friction layers the way the SDK does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
layers | Array<FrictionConfig | null | undefined> | none | The layers, lowest first — the dashboard's, then the app's. Empty layers are skipped. |
Returns — the defaults with each layer applied in turn, every field filled; unknown keys are dropped and numbers that are not positive fall back. Example value: { enabled: true, cooldownMs: 60000, idle: { afterMs: 20000 }, … }.
Errors — none.
rulesFor
Use it to see the rules that apply on one screen: the top level with that screen's own rules over it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
config | Required<FrictionConfig> | required | Resolved rules — client.friction. |
screenId | string | null | undefined | required | The screen's id. |
Returns — every rule for that screen. Example value: { ignore: false, idle: { afterMs: 20000 }, repeatedErrors: { count: 2, windowMs: 60000 }, … }.
Errors — none.
FrictionDetector
Use it only to run friction detection on your own timers; the client runs one for you.
| Member | Type | Meaning |
|---|---|---|
constructor | (config?) | Starts with resolveFriction(config). |
configure | (config: Required<FrictionConfig>) => void | Replaces the rules (pass resolved rules). |
settings | Required<FrictionConfig> | The rules in force. |
onScreen | (ctx, now) => FrictionSignal[] | Report a screen at time now (milliseconds); returns what can be known at once — a repeated field error, an error banner, a loop between screens. A new screen id is a new visit; the same id is an update. |
onInteraction | (now) => void | The customer did something: the idle clock restarts. |
onTrack | (name, now) => FrictionSignal[] | Report a tracked event; only taps (tap:…) are read — for rage taps and pressing on with fields left. |
idleDueIn | (now) => number | null | Milliseconds until the idle rule should fire, or null when it should not. |
onIdle | (now) => FrictionSignal | null | The idle timer went off: the stalled_on_step signal, at most once per visit. |
Errors — none. Every signal respects cooldownMs per screen.
GraineInAppClient.configureFriction
Call it to replace your app's layer of friction rules at any time — after a remote setting changes, say.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
override | FrictionConfig | null | undefined | required | Your app's rules; null removes your layer, leaving the defaults and the dashboard's. |
Returns — nothing. The rules apply at once and the idle timer restarts.
Errors — none.
GraineInAppClient.friction
Read it to see the friction rules in force, every layer applied.
Returns — the resolved rules. Example value: { enabled: true, cooldownMs: 60000, idle: { afterMs: 45000 }, … }.
Errors — none.
addMaskRule
Call it once at startup to mask an identifier only your business knows — an account number format — before anything leaves the device.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
pattern | RegExp | required | What to mask. It is made global, so every match is replaced. |
token | string | "[REDACTED]" | What replaces each match. |
Returns — nothing. The rule applies everywhere the SDK masks — screens, fields, traits, variables, events — after the built-in rules, and in your own maskString and maskDeep calls.
Errors — none.
maskDeep
Use it to mask a whole payload as the SDK does before sending it: every string inside it, at any depth.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | T | required | Any value — a string, an object, a list. |
Returns — a copy with every string masked by the built-in rules and then yours; numbers and booleans are left as they are (so amounts stay numbers). A Date becomes its ISO string, as JSON sends it (null for an invalid date), masked like any string. Pass plain data otherwise: another class instance comes back as a plain object of its own fields. Example value: { email: "[EMAIL]", amount: 1200 }.
Errors — none.
maskString
Use it to mask one string as the SDK does: the built-in rules, then yours.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input | string | required | The text. |
Returns — the text with email addresses → [EMAIL], card numbers (13–19 digits) → [CARD], mobile numbers → [PHONE], and the built-in national identifier formats → [PAN] and [AADHAAR] — then every rule added with addMaskRule, the same pass maskDeep gives each string. Example value: "Write to [EMAIL]".
Errors — none.
LauncherVisibility
Read it to say where and when the launcher may appear, per flow — the provider's visibility prop.
What to expect — A screen may show the launcher when any rule names it: includeScreens or a group's screens. With no rules at all, every screen does.
| Field | Type | Default | Meaning |
|---|---|---|---|
defaultDelayMs | number | the provider's launcherDelayMs | The wait before the launcher appears, for screens whose group sets none. |
defaultInset | LauncherInset | { right: 16, bottom: 20 } | Where the launcher sits, for screens whose group sets none. |
groups | LauncherGroup[] | none | Flows of screens, each with its own rules — see LauncherGroup. |
LauncherGroup
Read it to treat several screens as one flow for the launcher.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | The group's id, unique — the delay policies remember it. |
screens | string[] | required | Route names in the flow, matched exactly (case matters). |
continuity | LauncherContinuity | required | Whether the launcher stays put while the customer moves inside the flow. |
inset | LauncherInset | defaultInset | Where the launcher sits on these screens. |
delayMs | number | defaultDelayMs | The wait before it appears. |
delayPolicy | LauncherDelayPolicy | "perScreen" | When that wait is spent. |
LauncherInset
Read it as where the launcher sits: its distance from each edge of the screen, in points.
| Field | Type | Default | Meaning |
|---|---|---|---|
top | number | none | Distance from the top edge. |
right | number | none | Distance from the right edge (from the left edge for a left-hand launcher). |
bottom | number | none | Distance from the bottom edge — clear your tab bar with it. |
left | number | none | Distance from the left edge. |
LauncherContinuity
Read it as whether the launcher stays put while the customer moves inside one flow.
| Value | Meaning |
|---|---|
"continuous" | The flow is one place: moving between its screens keeps the launcher where it is, with no new delay. |
"perScreen" | Each screen decides afresh. |
LauncherDelayPolicy
Read it as when a group's delay is spent.
| Value | Meaning |
|---|---|
"perScreen" | Every time the customer lands on a screen. |
"oncePerGroupEntry" | Once on entering the flow; free inside it. |
"oncePerAppSession" | Once per app run — an offer, not a follow. |
LauncherVisibilityTracker
Use it only to apply the launcher visibility rules to your own navigation; the provider runs one for you with navigationRef.
| Member | Type | Meaning |
|---|---|---|
constructor | (includeScreens, visibility, defaultDelayMs = 0) | The provider's includeScreens, visibility and launcherDelayMs. |
reset | () => void | Forget the delays already spent — on a new call or a new app session. |
onRoute | (route: string) => VisibilityDecision | Call it on every route change, in order: returns whether the launcher may show on this screen, after how long and where. |
Errors — none.
VisibilityDecision
Read it as the launcher's verdict for one screen.
| Field | Type | Default | Meaning |
|---|---|---|---|
eligible | boolean | required | Whether the launcher may be on this screen at all. |
delayMs | number | required | How long to wait before showing it, with the delay policy already applied. |
inset | LauncherInset | required | Where it sits on this screen. |
groupId | string | null | required | The group that decided — for debugging a rule that surprises you. |
continuous | boolean | required | true when the launcher should stay where it is rather than appear again. |
activeRouteName
Use it to read the screen the customer is actually on from a React Navigation state — the deepest active route, not a navigator's name.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | any | required | A navigation state — navigationRef.getRootState(). |
Returns — the name of the deepest focused route, or null for a state it cannot read. Example value: "OrderDetail".
Errors — none.
Calls the user places
Use @graineai/inapp-react-native-calls for calls that behave like a phone call — CallKit on iOS, a full-screen call on Android, going on with the screen off — when the customer calls the agent from your app. Version 0.3.0 runs the call engine of Graine's iOS and Android SDKs 0.8.0.
GraineCalls
Use it as the calls package's one entry point: configure the engine, place and control calls, hand the call what your app tells the agent, and draw your own call screen from its state.
What to expect — One engine for the whole app, one call at a time on the device. Configure it once at launch, before a push can arrive; its options are kept on the device, so a call that wakes a closed app rings and connects before any JavaScript runs. A method that needs the native code throws Error("@graineai/inapp-react-native-calls is not linked: rebuild the app (pod install on iOS) — it carries native code, so an over-the-air update cannot add it.") in a build without it — check GraineCalls.isAvailable first.
Placing calls: configure, startCall, end, setMuted, setSpeaker, attach, getSnapshot, subscribe. Calls Graine rings: enableVoIPPushes, handlePush, registerPushToken, installId, answer, decline, onIncomingCall, onOutcome. Permissions and the setup check: callPermissions, requestCallPermissions, openCallSettings, callSetupProblems. Several agents: agents. Going live: isAvailable.
GraineCalls.configure
Call it once at launch — in index.js, before a push can arrive — to give the call engine your agent's key and the name its calls show; call it again by name to add more agents.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options | GraineCallsOptions | required | The default agent and how calls behave — see GraineCallsOptions. With agents, more agents by name; without a top-level publishableKey (GraineCallsAgentsOptions) the first agent listed is the default. |
name | string | — | One agent by your name for it: its publishableKey (and its own baseUrl). The first agent configured becomes the default when there is none yet; "default", or the default agent's own name, updates the default agent. |
Returns — Promise<void>, once the engine has the options; the call state is read then, so getSnapshot() is current.
Errors — rejects, and keeps the last configuration (every agent is checked before anything changes):
Error("publishableKey is required")orError("displayName is required")for the default agent;Error('publishableKey is required for the agent "sales"')for a named agent without a key;Error("An agent's name must be a non-empty string.");Error('"default" is the agent with the top-level publishableKey; name the others')whenagentslists"default"beside a top-level key;- code
invalid_options, with a message naming the option, when the engine refuses a value — for examplecallTransport must be "auto", "websocket" or "webrtc"; - the not-linked error above.
GraineCallsOptions
Read it to configure the call engine: the default agent's key, the name calls show, and how calls ring and look.
| Field | Type | Default | Meaning |
|---|---|---|---|
publishableKey | string | required | The default agent's publishable key (Agent → Embed & Widgets). Public by design. |
baseUrl | string | "https://www.graine.ai" | Where Graine is. |
displayName | string | required | The name on the ring and the call screen — "Acme assistant" — for agents whose dashboard settings name none. |
ringtoneSound | string | the phone's ringtone | iOS: a sound file of 30 seconds or less in your app bundle. Android: a raw resource name. |
ringTimeoutMs | number | 30000 | How long a ring lasts before it counts as missed, in milliseconds. |
appVersion | string | none | Your app's version, so each call's record says which build placed it. |
callTransport | GraineCallTransport | "auto" | What carries the calls' audio, in both directions — calls the customer places and calls Graine rings: "auto" (WebRTC in a build with WebRTC calls, else the WebSocket), "websocket" or "webrtc". The agent's In-app call audio setting can still move every call to the WebSocket. See GraineCallTransport. |
callScreen | "sdk" | "app" | "sdk" | Who draws the call while your app is open: "sdk" — the SDK's call screen (a full-screen activity on Android, a full-screen view over your app on iOS); "app" — your own screen, drawn from useGraineCall(). On a locked iPhone, the system's call screen appears whatever this is set to. |
debug | boolean | false | The native call engine's debug log in Xcode's console or logcat — how a call connected and what its audio did. Turn it on in debug builds only; it is read at launch, so a change applies at the next launch. |
android | { smallIcon?: string } | none | Android only: smallIcon, the drawable resource name for the call notifications. |
ios | { iconTemplate?: string; includesCallsInRecents?: boolean } | none | iOS only: iconTemplate, a template image in your bundle shown on the system call screen; includesCallsInRecents (false), whether calls appear in the Phone app's Recents. |
agents | Record<string, GraineCallsAgentOptions> | none | More agents, by your name for each — see GraineCallsAgentOptions. When Graine rings a call, it arrives as the agent that booked it. |
GraineCallTransport
Read it to choose what carries the calls' audio — GraineCallsOptions.callTransport — for every call: the ones the customer places and the ones Graine rings.
What to expect — It matters only in a build with WebRTC calls; without them every call runs on the WebSocket, whatever it says. The agent's In-app call audio setting decides last: Standard keeps every call on the WebSocket, whatever the app chose. The words are case-sensitive: any other makes configure() reject with code invalid_options. Which one carried a call: transport and transportFallbackReason on GraineCallSnapshot.
| Value | Meaning |
|---|---|
"auto" | The default. WebRTC when the build includes it and the dashboard allows it; otherwise the WebSocket. |
"websocket" | Always the WebSocket. |
"webrtc" | WebRTC wherever it can carry the call; the WebSocket only when the build does not include it or WebRTC cannot set this call up. On Android, a build without it says so: transportFallbackReason "unavailable". |
GraineCalls.startCall
Call it from your "Call us" button: the customer calls the agent — the default one, or one you name — on a real call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options | GraineStartCallOptions | {} | Why the customer is calling, prompt variables for this call, and which agent — see GraineStartCallOptions. |
Returns — Promise<string | null>: the call's id — every outcome carries it — or null when no call was placed: one is already live (one at a time), or, on Android, the customer refused the microphone (then getSnapshot().error reads "The microphone permission was denied."). Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — rejects with Error('No agent named "sales" is configured. Call GraineCalls.configure("sales", { publishableKey, … }) first.') for an agent you have not configured, and when a named agent meets an engine from before several agents (rebuild the app); throws the not-linked error in a build without the package.
What to expect — The package asks for the microphone first. The agent hears who_called = "customer" and call_reason = your reason as prompt variables, with your variables and the attached client's. Since 0.3.0 a call starts at once on the configuration already kept on the device and refreshes it behind the call. In a build with WebRTC calls it is prepared at the tap and carries its audio over WebRTC, unless callTransport or the dashboard says otherwise — transport on the snapshot says which. Then the call reports through the snapshot and onOutcome: ended (with who ended it) once connected, or failed with why it never connected — see GraineCallOutcome for the 0.3.0 failure lines.
GraineStartCallOptions
Read it to place a call: why, with what variables, and with which agent.
| Field | Type | Default | Meaning |
|---|---|---|---|
reason | string | none | Why the customer is calling — the agent hears it as the call_reason prompt variable. |
variables | Record<string, unknown> | none | Prompt variables for this call only. |
agent | string | the default agent | The configured agent to call, by your name for it. |
GraineCalls.end
Call it from your call screen's hang-up button — while a call rings, it declines it.
Parameters — none.
Returns — Promise<void>. A connected call reports ended with reason: "local"; a ring reports declined. On Android 0.8.0's engine, hanging up lets go of the microphone at once.
Errors — throws the not-linked error in a build without the package.
GraineCalls.setMuted
Call it from your call screen's mute button: the customer's voice stops reaching the agent and the call goes on.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
muted | boolean | required | true to mute the customer; false to unmute. |
Returns — nothing; useGraineCall().muted follows.
Errors — throws the not-linked error in a build without the package.
GraineCalls.setSpeaker
Call it from your call screen's speaker button to move the call between the loudspeaker and the earpiece (or a headset).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | boolean | required | true for the loudspeaker; false for the earpiece, or a connected headset. Calls start on the earpiece, like a phone call. |
Returns — nothing; useGraineCall().speakerOn follows.
Errors — throws the not-linked error in a build without the package.
GraineCalls.attach
Call it once, inside the provider, so a call knows what your app tells the agent — who the customer is, your variables, the screen, recent events — and the agent's actions run through your handlers during a call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
client | GraineAttachableClient | required | Your agent's client — useGraineClient(). |
options.agent | string | matched by the client's key | Which configured agent this client is; needed only for a client without opts.publishableKey. |
Returns — a function that detaches the client.
Errors — throws the not-linked error in a build without the package.
What to expect — Kept in step as things change: the customer's traits (handed over raw — the engine masks them and takes out the identifying value), the memory choice, your variables, the screen and the full context (at most every 400 ms), and the actions you registered (the engine asks your client to run them, and answers refused for one this build lacks). While a call rings, connects or is connected, the client is told, so its voice bar ends its conversation and refuses to start one ("A call is already in progress."). With several agents, attach each agent's client: the one for the agent on the call feeds the engine and runs that agent's actions, else the default agent's.
GraineAttachableClient
Read it as what attach() needs from a client — GraineInAppClient from @graineai/inapp-react-native 0.40.1 or later has all of it.
| Member | Type | Default | Meaning |
|---|---|---|---|
on | (event: string, fn: (payload: any) => void) => () => void | required | How the package hears the client's variables, screen and identity changes, to pass them to a live call. |
getIdentity | () => Record<string, string> | required | The customer's masked traits — used when getRawTraits is missing. |
getRawTraits | () => Record<string, unknown> | optional | The customer's traits as given, so the engine can take out the identifying value itself (0.41+). |
memoryEnabled | boolean | optional | The customer's memory choice, so a call is remembered as the chat is (0.41+). |
buildContextFrame | () => object | null | optional | The full context — screen, customer, recent events and taps — sent to the call (0.41+); without it the engine builds one from getScreen(). |
getVariables | () => Record<string, string> | required | Your prompt variables, for the call. |
getScreen | () => object | null | required | The screen the customer is on, for the call. |
executeAction | (name: string, args?: Record<string, unknown>) => Promise<GraineActionResult> | required | Runs one of your actions when the agent asks for it during a call. |
getAvailableActions | () => string[] | optional | The actions you registered, so the engine offers them. |
setNativeCallLive | (live: boolean) => void | optional | Lets the voice bar step aside while a call is live (0.40.2+); without it the bar beside a call is yours to manage. |
opts | { readonly publishableKey?: string } | optional | The client's options — its key matches it to the configured agent with the same key. |
GraineActionResult
Read it as the answer an action gives during a call — the same three outcomes as AppActionResult.
| Field | Type | Default | Meaning |
|---|---|---|---|
status | "ok" | "refused" | "error" | required | ok — it happened; refused — declined (or not in this build); error — it failed. |
message | string | optional | A sentence for the agent about what happened. |
data | Record<string, unknown> | optional | Structured results for the agent. |
GraineInAppClient.nativeCallLive
Read it to know whether a call from the calls package is ringing, connecting or connected — while it is, the voice bar does not start.
Returns — true from the moment such a call rings or is placed until it is over (told by GraineCalls.attach()). Example value: false.
Errors — none.
GraineInAppClient.setNativeCallLive
GraineCalls.attach() calls it as a call goes live and ends; your app does not need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
live | boolean | required | true while a call rings, connects or is connected; false when it is over. |
Returns — nothing. Every agent's client on the device is told; each announces native_call ({ live }), and its voice launcher ends its conversation and refuses to start while it is true.
Errors — none.
GraineCalls.getSnapshot
Read it for the call as it stands right now, outside React.
Parameters — none.
Returns — the current GraineCallSnapshot; idle before anything has happened. Example value: { state: "active", callId: "call_6f1c…", direction: "outgoing", muted: false, speakerOn: false, agentSpeaking: true, connectedAt: 1760500000000, error: null, agent: "default", transport: "webrtc", transportFallbackReason: null, … }.
Errors — none.
GraineCalls.subscribe
Call it to be told every time the call changes, outside React.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
fn | () => void | required | Called on every change; read the new state with getSnapshot(). |
Returns — a function that unsubscribes.
Errors — throws the not-linked error in a build without the package (it starts listening to the engine).
useGraineCall
Call it in your own call screen to draw the call — it re-renders as the call changes.
Parameters — none.
Returns — the current GraineCallSnapshot.
Errors — throws the not-linked error in a build without the package.
GraineCallSnapshot
Read it to draw a call: its state, who and why, the controls' state, the live caption, what carries its audio and, when it failed, why.
What to expect — idle → ringing (a call coming in) or connecting (a call placed, or answered) → active → ended, then idle again once reported. One call at a time: a ring during a live call is reported missed without disturbing it. transport and transportFallbackReason describe calls in both directions — placed or answered — and are final once the call is active.
| Field | Type | Default | Meaning |
|---|---|---|---|
state | GraineCallState | required | Where the call is — see GraineCallState. |
callId | string | null | required | The call's id: your server's for a call it rang, a generated one for a call the customer placed; null while idle. |
direction | "incoming" | "outgoing" | null | required | Whether the call came in (a ring) or was placed; null while idle. |
reason | string | null | required | Why the call is happening — the reason it was placed or rung with. |
callerName | string | null | required | The name the call shows. |
muted | boolean | required | Whether the customer is muted. |
speakerOn | boolean | required | Whether the loudspeaker is on. |
agentSpeaking | boolean | required | Whether the agent's voice is playing now — for a speaking animation. |
caption | GraineCaption | null | required | The line being said now, by either side. |
connectedAt | number | null | required | When the conversation opened, in milliseconds since 1970 — for a call timer. |
error | string | null | required | Why the last call could not connect, when it could not — the failed outcome's line (see GraineCallOutcome), or, on Android, "The microphone permission was denied." Cleared when the next call starts; a call that connected and then ended leaves none. |
agent | string | null | required | Which configured agent the call is with — "default" or your name for it; null while idle, and from an engine that predates several agents. |
transport | "webrtc" | "websocket" | null | required | What carries this call's audio: "webrtc" or "websocket", once decided — at the tap, or when the ring arrives; a call that falls back reads "websocket" before any audio flows. null with no call, and from an engine that predates WebRTC. See WebRTC calls. |
transportFallbackReason | string | null | required | Why a call that tried WebRTC runs on the WebSocket — one of the values below; null when it did not fall back, and before a transport is decided. Read it once the call is active. |
Why a call fell back to the WebSocket — the values of transportFallbackReason:
| Value | Meaning |
|---|---|
"mint_failed" | The call's WebRTC session could not be set up — refused, rate-limited, or unreachable. |
"register_failed" | Graine's media service did not accept the connection, or never answered it. |
"no_response" | The call got no reply at all. |
"no_answer" | Replies came; the answer did not. |
"ice_timeout" | Answered, but the audio path never connected — a network that blocks WebRTC. |
"error" | Anything else before the audio flowed. |
"dashboard" | Android only: the agent's In-app call audio setting is Standard. iOS leaves the reason null here. |
"unavailable" | Android only: callTransport: "webrtc" in a build that cannot run WebRTC calls — built without them, or their library could not load. iOS leaves the reason null here. |
GraineCallState
Read it as where a call is.
| Value | Meaning |
|---|---|
"idle" | No call. |
"ringing" | A call is coming in and ringing. |
"connecting" | A call was placed, or answered, and is being connected. |
"active" | The conversation is open — the agent can hear the customer. |
"ended" | The call just ended; idle follows once the outcome is reported. |
GraineCaption
Read it to draw the live caption of a call.
| Field | Type | Default | Meaning |
|---|---|---|---|
role | "agent" | "customer" | required | Who is speaking. |
text | string | required | What has been said so far in this line. |
live | boolean | required | true while the line is still being said. |
GraineCallOutcome
Read it to report what became of each call to your server — onOutcome hands it over once per change, with the call's id.
What to expect — Every outcome carries callId. Do not rely on its order relative to the snapshot's state changes. A value newer than this package arrives as its word.
outcome | Fields | Meaning |
|---|---|---|
ringing | callId, hosted | A call Graine rang is ringing on this device (hosted: true; Graine is told too). |
answered | callId | The customer answered a ring; ended or failed follows. |
declined | callId | The customer declined a ring. |
missed | callId | A ring nobody answered in time — or one that came while another call was live. |
ended | callId, reason, durationSeconds | A connected call ended: reason "local" (this app hung up), "remote" (the agent ended it) or "failed" (the connection broke mid-call); durationSeconds when the engine measured it. |
failed | callId, error | The call never connected; error says why, in a sentence. |
Every outcome may also carry hosted (true for a call Graine rang — Graine has its outcome already) and agent (the configured agent the call was with).
The failure lines (0.3.0) — what error reads, and when:
| Platform | error, or what you receive | When |
|---|---|---|
| iOS | "The call could not connect within 20 seconds." | One deadline now covers everything before the conversation opens — the agent's settings, the call's ticket and the connection — so a call never sits on "Connecting" for most of a minute. |
| iOS | "The conversation did not open within 15 seconds." | The connection's own open limit: it starts once the call's ticket is in hand, so it can end the call before the 20 seconds are up. |
| iOS | "The call's audio could not start." | The conversation opened but the system never handed the call its audio: the engine waits 3 seconds, starts the audio itself once, and fails the call 3 seconds later if it still does not run — instead of a call nobody can hear. |
| iOS | "The microphone permission was not granted." | The customer refused the microphone for a call they placed or answered. |
| Android | "The call could not connect within 15 seconds." | The connection did not open in time (unchanged). |
| Android | outcome: "ended", reason: "failed" | New: a connected call where nothing at all arrived for 50 seconds (two keep-alive periods) — a network that died silently — ends instead of staying open and silent. The engine words it "Nothing arrived for 50 seconds: the connection was lost."; the outcome here carries the reason only. |
| Either | another sentence naming what failed | For example "The agent's configuration could not be loaded." on iOS; "The call was refused (close …)." or "The call could not connect: " and the detail on Android. |
EVENTS
Read it only if you listen to the native module yourself — the names its events carry; onOutcome, onIncomingCall, subscribe and enableVoIPPushes listen for you.
| Key | Value | Meaning |
|---|---|---|
state | "GraineCallState" | A new call snapshot. |
outcome | "GraineCallOutcome" | A call's outcome. |
incoming | "GraineIncomingCall" | A ring started. |
voipToken | "GraineVoIPToken" | iOS: the VoIP push token, or null when it was invalidated. |
action | "GraineActionRequest" | The agent asked for one of your actions during a call. |
Calls Graine rings
Use these so a call can ring the customer's phone — on the lock screen, with the app closed — when Graine rings them for a call they or the agent booked, or when your own server rings them.
What to expect — Graine rings an identified customer's devices for booked calls when your workspace has In-app calling set up; on Android, with Firebase in the app (Expo: android.googleServicesFile and the package's plugin; bare: graineFirebaseMessaging=true in android/gradle.properties), the package registers for pushes itself — an app whose own messaging service receives the pushes (@react-native-firebase/messaging) forwards them with handlePush and registerPushToken — and on iOS the VoIP token is registered for you. Your own server can ring too, with one push of string values: type: "agent_call", call_id (your id — every outcome carries it back) and optionally reason, caller_name and variables (a JSON object, as a string) — on iOS a VoIP push to APNs (apns-push-type: voip, topic <bundle id>.voip) to the token from enableVoIPPushes; on Android a data-only FCM message at high priority. The push carries nothing personal. A call Graine rang tells the agent who_called = "scheduled" for a booked time (else "agent"). A call that rings carries its audio exactly as a call the customer places: over WebRTC in a build with WebRTC calls — prepared while the ring shows, so answering connects as fast as before — else over the WebSocket; callTransport, transport and transportFallbackReason apply to both directions.
GraineCalls.enableVoIPPushes
Call it on iOS when your own server rings customers: it hands you the VoIP push token to send the call push to, now and whenever it changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
onToken | (token: string | null) => void | required | Called with the token — at once when the device already has one, and on every change — and with null when it was invalidated (stop sending to it). |
Returns — a function that stops listening for token changes. On Android it does nothing and returns a function that does nothing: Android has no VoIP pushes — use handlePush.
Errors — throws the not-linked error on iOS in a build without the package.
What to expect — Every VoIP push must ring: iOS requires it, and the engine does. For calls Graine rings, you do not need this — the token is registered with Graine for you.
GraineCalls.handlePush
Call it on Android from your Firebase message handlers — background and foreground — with every push's data, first, when your app's own messaging service receives the pushes (@react-native-firebase/messaging): a Graine call rings at once, even when the app was not running. Without one, the package's own messaging service hands every push to the engine natively.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
data | Record<string, string> | required | The push's data exactly as the messaging library hands it over. |
Returns — Promise<boolean>: true when the push was a Graine call (it is ringing), false when it is not yours to ignore — handle your other pushes then. On iOS it resolves false: VoIP pushes arrive natively. Example value: true.
Errors — throws the not-linked error in a build without the package.
GraineCalls.registerPushToken
Call it on Android only when your app's own messaging service receives the tokens (@react-native-firebase/messaging), on every refresh (onTokenRefresh). With Firebase in the app the engine reads the token itself at launch, and without a messaging service of your own the package's receives every new one.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
push.token | string | required | The push token. |
push.kind | "fcm" | "voip" | "fcm" on Android, "voip" on iOS | What kind of token it is. |
push.environment | "production" | "sandbox" | none | For an APNs token you register yourself: which push environment it belongs to. |
Returns — Promise<boolean>: true when the engine took the token — it keeps it and registers this device with Graine for every configured agent; on iOS, false for a VoIP token (iOS registers its own). Example value: true.
Errors — rejects with Error("registerPushToken needs the token") for an empty token, and with "@graineai/inapp-react-native-calls cannot register push tokens in this build: rebuild the app (pod install on iOS) — it carries native code, so an over-the-air update cannot add it." when the app's native module predates it.
GraineCalls.installId
Read it to see the id that makes this phone one of the customer's devices — the calls package and the in-app SDK share it, so a call and a chat are one device.
Parameters — none.
Returns — Promise<string | null>: the id, or null before configure() or with a native module from before it. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none: a failure resolves null.
GraineCalls.answer
Call it from your own ringing screen (callScreen: "app") to answer the ringing call; the system ring answers by itself.
Parameters — none.
Returns — Promise<void>. The package asks for the microphone first; answered is reported and the call connects — over WebRTC in a build with WebRTC calls, unless callTransport or the dashboard turned it off (it was prepared while the call rang), else over the WebSocket; useGraineCall().transport says which. A refused microphone fails the call (iOS: "The microphone permission was not granted."; Android: the snapshot's error reads "The microphone permission was denied.").
Errors — throws the not-linked error in a build without the package.
GraineCalls.decline
Call it from your own ringing screen to decline the ringing call.
Parameters — none.
Returns — Promise<void>; declined is reported. For a call Graine rang, nothing rings that customer again for 6 hours.
Errors — throws the not-linked error in a build without the package.
GraineCalls.onIncomingCall
Call it to hear a call start ringing — to draw your own ringing screen, or to log it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
fn | (c: GraineIncomingCall) => void | required | Called with each ring — see GraineIncomingCall. |
Returns — a function that stops listening.
Errors — throws the not-linked error in a build without the package.
What to expect — Rings that happened before anything listened (a call rung while the app's JavaScript was not running) reach the first listener, oldest first — up to 20 are kept.
GraineIncomingCall
Read it to show a ring: who is calling and why — it carries only what the ring shows, nothing personal.
| Field | Type | Default | Meaning |
|---|---|---|---|
callId | string | required | The call's id — Graine's for a call it rang, your server's for one you rang. |
reason | string | null | required | Why the call is happening, from the push. |
callerName | string | null | required | The name to show on the ring. |
agent | string | null | optional | The configured agent calling, by your name for it, when the engine says. |
hosted | boolean | optional | true when Graine rang this call itself — a call booked for this customer, or Ring now from the dashboard — rather than a push from your own server: its outcomes are reported to Graine for you, so your server has nothing to record. Absent on any other ring, as on the outcome. |
GraineCalls.onOutcome
Call it once at launch to report every call's outcome to your server — answered, declined, missed, ended or failed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
fn | (o: GraineCallOutcome) => void | required | Called with each outcome — see GraineCallOutcome. |
Returns — a function that stops listening.
Errors — throws the not-linked error in a build without the package.
What to expect — Outcomes from before anything listened (a call that rang and ended while the app's JavaScript was not running) reach the first listener, oldest first — up to 20 are kept. A call Graine rang (hosted: true) is reported to Graine by the engine as well.
Permissions and the setup check
What a call needs from the phone, and from your app's setup, in one place. The calls package never asks on its own at launch: it asks for the microphone when a call starts without it, and requestCallPermissions() asks earlier, when your app chooses — on Android from a transparent screen of the SDK's own, so nothing forwards a permission result. callSetupProblems() says what your setup still lacks; the native SDK logs each problem once from configure on (Xcode's console, logcat), and a development build also warns each once in Metro's console. A problem is never an exception: calls still work where they can. With Expo, the package's config plugin writes the iOS setup at prebuild (Permissions and capabilities).
GraineCalls.callPermissions
Read it to see which call permissions the phone holds now.
The microphone, and on Android the ring's notifications (13+ asks) and the full-screen ring (14+, Settings only); on iOS those two are "notNeeded" — CallKit rings and draws its own screen. Readable before configure.
Returns — a GraineCallPermissions. Read it again when the app returns to the foreground (AppState "active"): the customer may have changed Settings.
Errors — throws in a build without the package's native code, or with a native build older than this package: rebuild the app.
GraineCalls.requestCallPermissions
Call it at a moment you choose — onboarding, a "Call me" screen — to ask for what a call needs.
Asks for the call permissions the system's dialog can still ask for — the microphone, and notifications on Android 13+ — and resolves them as they stand after. Nothing to ask: it resolves at once. The full-screen ring has no dialog: openCallSettings("fullScreen") opens its page. The package never asks on its own at launch; a call that starts without the microphone still asks then.
Returns — a GraineCallPermissions, after the customer answers.
Errors — as callPermissions.
GraineCalls.openCallSettings
Call it to send the customer to the Settings page that changes a call permission.
Opens, on Android, the full-screen ring's own page (14+) or the app's notification settings; otherwise the app's page, where the microphone is.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
item | GraineCallPermission | required | "microphone", "notifications" or "fullScreen". |
Returns — true when a page opened.
Errors — throws Error('openCallSettings takes "microphone", "notifications" or "fullScreen", …') for any other word, and as callPermissions in a build without the native code.
GraineCalls.callSetupProblems
Read it to learn what your app's setup still lacks for calls, in plain words.
What the app's setup still lacks for calls, read now — each a stable code and a message with the exact fix. Empty when nothing is missing, and before configure.
| Code | Platform | When |
|---|---|---|
microphone_usage_missing | iOS | No NSMicrophoneUsageDescription: iOS closes the app when a call asks for the microphone. The Expo plugin adds it. |
background_audio_missing | iOS | UIBackgroundModes lacks audio: a call goes silent when the screen locks. |
background_voip_missing | iOS | UIBackgroundModes lacks voip: iOS does not wake the app for a ring. |
push_entitlement_missing | iOS | Signed without Push Notifications (read from development and ad hoc builds). |
voip_pushes_not_enabled | iOS | enableVoIPPushes not called 15 seconds after configure. |
no_voip_token | iOS | No VoIP token 15 seconds after enableVoIPPushes (never on the simulator). |
manifest_entry_missing | Android | The merged manifest lost a call entry (look for tools:node="remove"). |
no_push_token | Android | No push token 15 seconds after configure: Firebase is missing or not set up in the Android app (the message says which), or an app whose own messaging service receives the tokens never called registerPushToken. Calls can't ring. |
notifications_not_allowed | Android | A call can't ring while the app is closed or in the background. |
full_screen_not_allowed | Android | A locked phone rings with a heads-up notification instead. |
microphone_blocked | Both | Only Settings can let calls hear the customer. |
hosted_ringing_off | Both | No push credentials uploaded for this platform: Graine can't ring this phone. |
Returns — GraineCallSetupProblem[].
Errors — as callPermissions.
GraineCallPermission
Use it to name a call permission.
| Value | Meaning |
|---|---|
"microphone" | The customer's voice. |
"notifications" | A call that rings while the app is closed or in the background — Android (13+ asks). iOS rings through CallKit, which needs none. |
"fullScreen" | A ring that fills a locked screen — Android 14+, allowed in Settings. iOS: CallKit's own screen. |
GraineCallPermissionStatus
Read it to tell whether a call permission is allowed, can still be asked for, or only Settings can change it.
| Value | Meaning |
|---|---|
"granted" | Allowed. |
"denied" | Not allowed yet: requestCallPermissions() can ask for it. A word the engine sends that this package does not know reads "denied", never granted. |
"blocked" | Refused for good, or only allowed in Settings: offer openCallSettings. |
"notNeeded" | This phone needs no such permission. |
GraineCallPermissions
Read it to see every call permission at once.
| Field | Type | Default | Meaning |
|---|---|---|---|
microphone | GraineCallPermissionStatus | required | The microphone. |
notifications | GraineCallPermissionStatus | required | The ring's notifications — "notNeeded" on iOS. |
fullScreen | GraineCallPermissionStatus | required | The full-screen ring — "notNeeded" on iOS. |
allGranted | boolean | required | Every permission is granted or not needed: calls can ring and connect. |
GraineCallSetupProblem
Read it to show or log what your app's setup still lacks.
| Field | Type | Default | Meaning |
|---|---|---|---|
code | string | required | Stable, the same on every SDK: act on it, or count it. The list is under GraineCalls.callSetupProblems. |
message | string | required | What is missing and how to fix it, in plain words. |
Booking with the SDK's sheet
Use these to let a customer book a call — "call me at…" — with no screen of your own: the SDK draws the booking sheet in the agent's design, books the call, and tells you what was booked. The same rules are exported for an app that draws its own booking screen. New in 0.42.0.
What the customer sees. The sheet's header reads Book a call, with "call name will call you" under it — the agent's call name from the dashboard, else the chat's title, else the heading, else "Assistant" — and a close button. Under Day, a chip per day from today to the last day the agent allows: "Today", "Tomorrow", then the weekday and date in the phone's words ("Thu 16 Oct"); a day with no time left is not offered. Under Time, a grid of times every 15 minutes, four to a row, only between the earliest time the agent allows (now plus its notice, rounded up to 5 minutes) and the latest (now plus its days ahead, rounded down to 5 minutes); the day's first time is chosen. Under the grid, the phone's time zone and the time chosen — "Asia/Kolkata · Tue 14 Oct, 6:00 PM". Then What's it about? (optional), one line of up to 120 characters (line breaks become spaces), filled with your reason when you pass one. Then Book call, which shows a spinner while it books.
A refusal stays in the sheet as one line under Book call, worded from the agent's own numbers:
| Code | The line |
|---|---|
too_soon | "Pick a time at least 5 minutes from now" — the agent's notice; the sheet then moves to the new earliest time. |
too_far | "Pick a time within 14 days" — the agent's limit. |
outside_window | "That time is outside the hours we can call — pick another" |
too_many_pending | "You already have 3 calls booked — cancel one first" — the agent's limit. |
scheduling_off | "Booking a call isn't available right now" |
identity_required | "Sign in to book a call" |
rate_limited, unavailable, network, and any other code | "Couldn't book just now — try again" |
Opened where booking is not available, the sheet opens, shows the scheduling_off or identity_required line and does not book. Once booked, it says "Booked — call name will call you on Wed 14 Oct, 6:00 PM" (always the date, never "Today") with Done. It closes with Done, the close button, a tap beside it, or Android's back button; closed without booking, it books nothing. On a phone it is a sheet from the bottom with a grab handle; on a tablet (shorter side 600 points or more), a centred panel 420 points wide. The times move on while it is open: what is offered is checked again every 30 seconds and when the app returns to the foreground.
In the SDK's chat windows (GraineVoiceBar's chat and GraineChatScreen) — new in 0.42.0, with the Upcoming calls card itself — where booking is available and the design keeps the booked-calls card: Book a call at the foot of the card under the customer's booked calls, or — when nothing is booked — a compact row of its own in the card's place reading Book a call and "call name will call you". Either opens the sheet over the chat, and the card reads the calls again after a booking.
GraineBookingSheet
Use it when your own "Call me" button should open the booking sheet and you want to hold its open state yourself; otherwise call openBooking() and the provider draws it.
Props — GraineBookingSheetProps.
Returns — the sheet while visible is true (a fresh one each time it opens — no day, time or error left from the last), else null.
Errors — none thrown: with no client at all (no provider, no default client, none passed) it shows "Booking a call isn't available right now" and does not book. A booking's refusal is shown in the sheet, never thrown.
What to expect — Until the agent's settings have loaded it asks for them, and Book call stays disabled; then it offers what the agent's rules allow and shows the refusal line when booking is not available here. It is redrawn when the settings arrive and when the customer signs in or out.
GraineBookingSheetProps
Read it to show the sheet, hear what it booked, and fill in what the call is about.
| Prop | Type | Default | Meaning |
|---|---|---|---|
visible | boolean | required | true shows the sheet; false hides it. |
onClose | () => void | required | Hear the sheet close — Done, the close button, a tap beside it, Android's back — and set visible to false. |
onBooked | (call: GraineScheduledCall) => void | none | Hear the call that was booked, the moment it is; the sheet stays open saying so until Done. See GraineScheduledCall. |
reason | string | none | Fills "What's it about? (optional)"; the customer may change it. Trimmed and cut to 120 characters. |
safeArea | { top?: number; bottom?: number } | a typical phone's | The phone's safe areas, so the sheet clears the notch and the home indicator — useSafeAreaInsets(). |
client | GraineInAppClient | the provider's, else the default client | The agent to book with — for an app with several agents. |
GraineInAppClient.openBooking
Call it from your own "Call me" button to open the SDK's booking sheet — the provider draws it — and get back the call the customer booked.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options.reason | string | none | Fills "What's it about? (optional)"; trimmed and cut to 120 characters; the customer may change it. |
Returns — Promise<GraineScheduledCall | null>: when the sheet closes, the call the customer booked — see GraineScheduledCall — or null when they closed it without booking. Example value: { id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: new Date("2026-10-16T12:30:00Z"), status: "scheduled", source: "app", reason: "Talk through my order", isUpcoming: true, canCancel: true }.
Errors — none: it never rejects. Refusals are shown in the sheet.
What to expect — A GraineProvider with this client draws the sheet — one mounted later still does, and only one draws it. A second openBooking() while one is open replaces it; the first resolves null. dispose() resolves an open one null. Opened where booking is not available, the sheet explains why and resolves null when closed — check bookingAvailable to hide your button instead. Announced as the open_booking event ({ id, reason }). The same function is openBooking in useGraineAgent() and useGraineScheduling().
GraineInAppClient.bookingAvailable
Read it to show or hide your own "Call me" button: it is true only when this customer can book a call from here.
Returns — true when all of these hold:
- the agent lets customers book (
schedulingRules.usersCanBook); - Graine can ring this phone: your workspace has In-app calling set up (
calls.hosted) and its platforms are empty or name this one (iosorandroid); - the customer is identified —
identify()with a real value for the trait the agent's Who is the user? setting names.
false until the agent's settings have loaded. Example value: true.
Errors — none.
What to expect — It changes when the settings load (session event) and when the customer signs in or out (identity event); useGraineAgent() and useGraineScheduling() carry it as state, so a component redraws by itself. Each agent has its own.
GraineInAppClient.bookingBlocker
Read it to say why booking is not available — the same reason the sheet shows.
Returns — "scheduling_off" (the agent takes no bookings, or Graine cannot ring this phone), "identity_required" (nobody is identified), or null when bookingAvailable is true. The agent's settings come first: signing in would not help while booking is off. Example value: "identity_required".
Errors — none.
GraineInAppClient.takeBooking
The provider calls it to take the sheet an openBooking() asked for, once — so two providers of one client draw one sheet. Your app does not need it.
Parameters — none.
Returns — the open sheet's id and reason, the first time it is asked; null when there is none or it is already taken. Example value: { id: 3, reason: "Talk through my order" }.
Errors — none.
GraineInAppClient.settleBooking
The provider calls it when the sheet it drew closes, to resolve that openBooking(). Your app does not need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | number | required | The sheet's id, from takeBooking(). A sheet that is no longer the open one is ignored. |
call | GraineScheduledCall | null | required | The call booked, or null. |
Returns — nothing.
Errors — none.
bookingAvailableFrom
Use it to apply the booking rule yourself, from the agent's settings — what bookingAvailable computes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
calls | unknown | required | The agent's calls settings — client.config?.calls. |
platform | string | null | undefined | required | "ios", "android" or "web" — see BookingPlatform. |
identified | boolean | required | Whether the customer is identified with the agent's identifying trait. |
Returns — true when bookingBlocker(…) is null. Example value: false.
Errors — none.
bookingBlocker
Use it to find why booking is not offered, from the agent's settings.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
calls | unknown | required | The agent's calls settings. |
platform | string | null | undefined | required | Where the customer books from. A web page cannot be rung, so on "web" the ringing rule does not apply — the booking rings the customer's app. Any other value than "ios", "android" or "web" is not bookable. |
identified | boolean | required | Whether the customer is identified. |
Returns — "scheduling_off" when calls.scheduling.usersCanBook is not true, or (on a phone) calls.hosted is not true or calls.platforms names other platforms only; then "identity_required" when identified is false; else null. Example value: "scheduling_off".
Errors — none.
BookingBlocker
Read it as why booking is not offered here — the code whose line the sheet shows.
| Value | Meaning |
|---|---|
"scheduling_off" | The agent takes no bookings from the app, or Graine cannot ring this phone. The sheet says "Booking a call isn't available right now". |
"identity_required" | Nobody is identified. The sheet says "Sign in to book a call". |
BookingPlatform
Read it as where a customer books from.
| Value | Meaning |
|---|---|
"ios" | An iPhone or iPad Graine can ring. |
"android" | An Android phone Graine can ring. |
"web" | A web page: it cannot be rung, so the booking rings the customer's app. |
bookingBounds
Use it to find the earliest and latest times a booking may be made for, rounded as the sheet offers them.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
rules | { minLeadMinutes: number; maxDaysAhead: number } | required | The agent's notice and horizon — client.schedulingRules. A number that is not positive counts as 0. |
now | Date | now | The moment to count from. |
Returns — a BookingBounds. Example value: { earliest: new Date("2026-10-15T10:10:00Z"), latest: new Date("2026-10-29T10:05:00Z") }.
Errors — none.
BookingBounds
Read it as the window every time the sheet offers lies in, both ends on the 5-minute grid.
| Field | Type | Default | Meaning |
|---|---|---|---|
earliest | Date | required | Now plus the agent's notice (minLeadMinutes), rounded up to 5 minutes — the first time that may be booked. |
latest | Date | required | Now plus the agent's days ahead (maxDaysAhead), rounded down to 5 minutes — the last time that may be booked. |
clampBooking
Use it to keep a time a customer chose in your own picker inside the bounds.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | Date | required | The time chosen. |
bounds | BookingBounds | required | From bookingBounds. |
Returns — at when it lies inside; earliest for a time before it or a date that cannot be read; latest for a time after it (never before earliest). Example value: new Date("2026-10-15T10:10:00Z").
Errors — none.
bookingDays
Use it to list the days a booking screen of your own should offer, each with its first and last time.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
bounds | BookingBounds | required | From bookingBounds. |
now | Date | now | The moment "Today" and "Tomorrow" are measured from. |
options.stepMinutes | number | 5 | The grid the times sit on: 5 for a time picker, BOOKING_SLOT_MINUTES (15) for a grid of times. |
options.utcOffsetMinutes | number | the phone's zone | A fixed offset east of UTC, in minutes, to read days in. |
Returns — one BookingDay per day from the day of the earliest time to the last bookable day, in order; a day with nothing left on the grid is left out. Example value: [{ key: "2026-10-15", label: "Today", first: …, last: … }, { key: "2026-10-16", label: "Tomorrow", … }].
Errors — none.
BookingDay
Read it as one day the booking screen offers.
| Field | Type | Default | Meaning |
|---|---|---|---|
key | string | required | The day on the phone, YYYY-MM-DD — a stable key for the chip. |
label | string | required | The chip's words: "Today", "Tomorrow", then the weekday and date ("Thu 16 Oct"). |
first | Date | required | The first time the day offers — on the grid, inside the bounds. |
last | Date | required | The last time the day offers. |
bookingSlots
Use it to list the times of one day for a grid of times.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
day | BookingDay | required | The day, from bookingDays. |
stepMinutes | number | 15 | The gap between times, on the clock's grid (:00, :15, :30, :45). |
Returns — the times from the day's first to its last, every stepMinutes. Example value: [new Date("…T10:15:00"), new Date("…T10:30:00"), …].
Errors — none.
bookingDayLabel
Use it to word a day's chip: "Today", "Tomorrow", else the date.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | Date | required | A moment on the day. |
now | Date | now | The moment "Today" is measured from. |
utcOffsetMinutes | number | the phone's zone | A fixed offset east of UTC, in minutes. |
Returns — "Today", "Tomorrow", or the weekday and date in the phone's words (with the year when it is not this year). Example value: "Thu 16 Oct".
Errors — none.
bookingCallName
Use it to choose the name in "… will call you", as every platform's sheet does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
names | Array<string | null | undefined> | none | Candidate names in order — the agent's call name (schedulingRules.callName), the chat's title, the heading. |
Returns — the first that is not blank, trimmed; "Assistant" when none is. Example value: "Acme".
Errors — none.
bookingSubtitle
Use it to word the line under the sheet's title.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
callName | string | required | From bookingCallName. |
Returns — the name followed by " will call you". Example value: "Acme will call you".
Errors — none.
bookingZoneLine
Use it to word the time-zone line under the times: the zone, then the time chosen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | Date | required | The time chosen. |
zone | string | null | the phone's IANA zone | The zone to name; null leaves it out. |
now | Date | now | Decides whether the year is shown. |
utcOffsetMinutes | number | the phone's zone | A fixed offset east of UTC, in minutes. |
Returns — the zone, " · ", then the date and time in the phone's words. Example value: "Asia/Kolkata · Tue 14 Oct, 6:00 PM".
Errors — none.
bookingDoneLine
Use it to word the confirmation after a booking.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
callName | string | required | From bookingCallName. |
at | Date | null | required | When the call rings — the booked call's at. |
now | Date | now | Decides whether the year is shown. |
utcOffsetMinutes | number | the phone's zone | A fixed offset east of UTC, in minutes. |
Returns — "Booked — " and the name, " will call you on " and the date and time (always the date, never "Today"); without a time, "Booked — name will call you". Example value: "Booked — Acme will call you on Wed 14 Oct, 6:00 PM".
Errors — none.
bookingErrorLine
Use it to word a booking refusal in one plain line, from the error's code and the agent's numbers.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
code | unknown | required | The GraineError's code. |
rules | { minLeadMinutes: number; maxDaysAhead: number; maxPendingPerUser: number } | required | The agent's numbers — client.schedulingRules. |
Returns — the line from the refusal table at the top of this section, with the agent's numbers ("1 minute" or "5 minutes"); "Couldn't book just now — try again" for any other code. Example value: "Pick a time at least 5 minutes from now".
Errors — none.
BOOKING_TEXT
Use it to word your own booking screen exactly as the SDK's sheet does.
| Key | Value | Where it shows |
|---|---|---|
title | "Book a call" | The sheet's title, and the chat's "Book a call" button and row. |
about | "What's it about? (optional)" | The label of the line for what the call is about. |
book | "Book call" | The button that books. |
done | "Done" | The button that closes the sheet after a booking. |
web | "We'll ring you in the app" | On a website, under the times: the booking rings the customer's app, not the browser. |
BOOKING_ROUND_MINUTES
Read it as the grid booking times sit on: the earliest time is rounded up to it, the latest down.
What to expect — 5 minutes.
BOOKING_SLOT_MINUTES
Read it as the gap between the times in React Native's grid of times (and the website's).
What to expect — 15 minutes: :00, :15, :30 and :45.
BOOKING_REASON_MAX
Read it as the longest "what's it about" a booking keeps.
What to expect — 120 characters; the sheet stops typing there, and scheduleCall and openBooking cut a longer reason.
The scheduled-calls list
Use these to read what a customer may book, book a call from a screen of your own, list the customer's calls and cancel one. How booked calls ring — who may book, when they ring, every status — is Scheduled calls.
The SDK lists them for you, too. Since 0.42.0 the SDK's chat windows (GraineVoiceBar's chat and GraineChatScreen) open with an Upcoming calls card: the customer's booked calls still to ring, soonest first, three at most — each with its time ("Tomorrow, 9:00 AM"), the call's name and Cancel ("Cancelling…", then gone; "Couldn't cancel · tap to retry" when it fails). The card is read when the chat opens, after a booking or a cancellation through the SDK, when the customer changes, when the app returns to the foreground, and after the agent's turns at most every 30 seconds; a read that fails leaves it as it was. The design's chat.showUpcomingCalls hides it and chat.upcomingTitle titles it ("Upcoming call" / "Upcoming calls" by default).
GraineInAppClient.schedulingRules
Read it to build a booking screen within the agent's rules: whether customers may book, how soon and how far ahead, and how many calls may wait.
Returns — a GraineSchedulingRules, read from the agent's settings. Before they load, booking is off and the numbers are the defaults. Example value: { usersCanBook: true, minLeadMinutes: 5, maxDaysAhead: 14, maxPendingPerUser: 3, ringTimeoutSeconds: 45, callName: "Acme", hostedRinging: true, platforms: ["ios", "android"], earliest: [Function], latest: [Function] }.
Errors — none.
GraineSchedulingRules
Read it as what a customer may book, as the agent's Calls & scheduling settings say.
What to expect — A property, not a request: read from the agent's settings when they load; until then booking is off. scheduleCall checks the first three rules on the device before anything is sent; Graine then checks the calling hours and the calls already waiting.
| Field | Type | Default | Meaning |
|---|---|---|---|
usersCanBook | boolean | false | Whether customers may book calls from the app at all. |
minLeadMinutes | number | 5 | The least notice a booking needs, in minutes. |
maxDaysAhead | number | 14 | How far ahead a call may be booked, in days. |
maxPendingPerUser | number | 3 | How many booked calls one customer may have waiting. |
ringTimeoutSeconds | number | 45 | How long Graine rings a booked call, in seconds (read in the dashboard's range, 20–60). |
callName | string | null | null | The name the ring shows — and the booking sheet's "… will call you". |
hostedRinging | boolean | false | Whether Graine rings the customer's devices itself — your workspace has In-app calling set up. |
platforms | Array<"ios" | "android"> | [] | The platforms Graine rings on; empty means every platform. |
earliest | (now?: Date) => Date | — | The earliest time a call may be booked for: now plus minLeadMinutes. |
latest | (now?: Date) => Date | — | The latest: now plus maxDaysAhead days. |
GraineInAppClient.scheduleCall
Call it from a booking screen of your own to book a call from the agent for a chosen time; the SDK's own sheet uses it too.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | Date | string | number | required | When the call should ring: a Date, an ISO-8601 time, or milliseconds since 1970. Sent as a moment with the customer's time zone; it must lie between schedulingRules.earliest() and latest(). |
reason | string | none | What the call is about — kept with the call for the agent (it hears it as call_reason when it rings), never in the push. Trimmed and cut to 120 characters. |
request | { at: Date | string | number; reason?: string } | — | The same two, as one object. |
Returns — Promise<GraineScheduledCall>: the booked call, status "scheduled" — see GraineScheduledCall. It rings the identified customer's devices (or, for someone not identified, this install) when it is due, inside the agent's calling hours. Announced as scheduled_calls_changed ({ booked: id }). Example value: { id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: new Date("2026-10-16T12:30:00Z"), status: "scheduled", source: "app", reason: "Talk through my order", isUpcoming: true, canCancel: true }.
Errors — rejects with a GraineError. Checked on the device first, before anything is sent (status 0) — the refusals in the booking sheet's own words, as on every SDK and the website:
invalid_request— "atmust be a Date, an ISO-8601 time or a timestamp."scheduling_off— "Booking a call isn't available right now"too_soon— "Pick a time at least 5 minutes from now" (the agent's notice — "1 minute" when it is one; 30 seconds of grace are allowed)too_far— "Pick a time within 14 days"invalid_request— the device has no install id (the message namesstorageand the calls package).
Then from Graine: outside_window (outside the agent's calling hours in the customer's zone — the message names a time that works), too_many_pending, identity_required, device_limit, invalid_request, rate_limited, unavailable, invalid_key, origin_refused; network — "Could not reach Graine. Check the connection and try again."; unknown — "The call was booked, but its details did not come back. Read them with scheduledCalls()."
GraineInAppClient.scheduledCalls
Call it to list the customer's booked calls — for a "Your calls" screen.
Parameters — none.
Returns — Promise<GraineScheduledCall[]>: this customer's calls (or this install's, for someone not identified), upcoming first. Example value: [{ id: "6f1c2a9e-…", at: new Date("2026-10-16T12:30:00Z"), status: "scheduled", source: "agent", reason: null, isUpcoming: true, canCancel: true }].
Errors — rejects with a GraineError: invalid_request (no install id), network, rate_limited, unavailable, invalid_key, origin_refused.
GraineInAppClient.cancelScheduledCall
Call it from a booked call's Cancel button; only a call that has not started ringing (canCancel) can be cancelled.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | string | required | The booked call's id. |
Returns — Promise<GraineCallStatus>: the call's status afterwards — "cancelled". Announced as scheduled_calls_changed ({ cancelled: id }). Example value: "cancelled".
Errors — rejects with a GraineError: invalid_request — "Pass the id of the call to cancel." for an empty id, or no install id; not_found — not this customer's or install's, or no such call; network, rate_limited, unavailable.
useGraineScheduling
Call it in a screen that books and lists calls: the rules, the customer's calls, and booking and cancelling, as state — plus the SDK's own sheet.
Parameters — none.
Returns — rules: the agent's rules, following its settings as they load; calls: the customer's calls, upcoming first — read on mount, after every booking or cancellation, and when the customer changes; schedule and cancel: as scheduleCall and cancelScheduledCall — they reject with a GraineError and also set error; refresh(): read the calls again (it never rejects — a failure lands in error); loading: a read is in progress; error: the last failure, else null; 0.42.0: bookingAvailable and openBooking — see Booking with the SDK's sheet. Example value: { rules: { usersCanBook: true, … }, calls: [], loading: false, error: null, bookingAvailable: true, … }.
Errors — throws, like useGraineClient, when there is neither a provider above nor a default client.
GraineScheduledCall
Read it to show a booked call: when it rings, where it is, and whether it can still be cancelled.
What to expect — From scheduleCall (status scheduled), scheduledCalls() (upcoming first) and the chat's booked-calls card. at is a moment in time — show it in the phone's time zone. A status or source newer than this SDK arrives as its own word: give every switch a default.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | The booked call's id (a UUID) — pass it to cancelScheduledCall. |
at | Date | null | required | When it rings; null only when Graine's time could not be read. |
status | GraineCallStatus | required | Where the call is — see GraineCallStatus. |
source | GraineCallSource | null | required | Who booked it — see GraineCallSource; null when Graine did not say. |
reason | string | null | required | What the call is about, at most 120 characters; null when none was given. |
isUpcoming | boolean | required | true while it has not rung yet, or is ringing now (scheduled or ringing). |
canCancel | boolean | required | true while it can still be cancelled — it has not started ringing (scheduled). |
GraineCallStatus
Read it as where a booked call is.
What to expect — scheduled → ringing → answered → completed, or one of the endings below. A status newer than this SDK arrives as its own word.
| Value | Meaning |
|---|---|
"scheduled" | Booked, waiting for its time — it can be cancelled. |
"ringing" | Ringing the customer's devices now. |
"answered" | The customer answered; the conversation is under way. |
"completed" | The call happened and ended. |
"declined" | The customer declined the ring; nothing rings that customer again for 6 hours. |
"missed" | It rang out unanswered. |
"failed" | It could not be placed. |
"cancelled" | Called off before it rang. |
"expired" | Its time passed while it could not ring — more than 10 minutes late, or no opening left in the calling hours. |
"noDevice" | No device of the customer's has been seen in the last 30 days (a browser cannot be rung). |
GraineCallSource
Read it as who booked a call.
| Value | Meaning |
|---|---|
"agent" | The agent booked it, in a conversation. |
"app" | Your app booked it — scheduleCall or the booking sheet. |
"dashboard" | Your team booked it in the dashboard. |
"api" | It was booked through the API. |
A source newer than this SDK arrives as its own word.
upcomingCalls
Use it to pick the calls a booked-calls card lists: those still to ring, soonest first.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
calls | readonly GraineScheduledCall[] | null | undefined | required | The customer's calls — scheduledCalls(). |
Returns — the scheduled calls only, soonest first, a call with no readable time last. The SDK's card draws the first three. Example value: [{ id: "6f1c…", status: "scheduled", … }].
Errors — none.
upcomingTimeLabel
Use it to word a booked call's time on a card.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | Date | null | required | When the call rings. |
now | Date | now | The moment "Today" is measured from. |
utcOffsetMinutes | number | the phone's zone | A fixed offset east of UTC, in minutes. |
Returns — "Today, 5:30 PM", "Tomorrow, 9:00 AM", else the date and time ("Tue 14 Oct, 10:00 AM"), in the phone's words; "Booked" when there is no time. Example value: "Tomorrow, 9:00 AM".
Errors — none.
Design
Use these to draw your own UI beside the agent's in the same design the brand chose in the dashboard (the Garage), and to read the design system the SDK draws with: tokens, presets, icons, logos, typefaces, appearance and the chat window's sizes.
useGraineTheme
Call it in any component to draw in the agent's design — the same colours, type, corners and depth the SDK draws with.
Parameters — none.
Returns — the ThemeTokens for now: the brand's fixed light or dark, else the phone's, picked again the moment the phone switches appearance. Outside a provider, the default design. Example value: { v: 1, preset: "signature", scheme: "light", colors: { accent: "#3740B8", … }, … }.
Errors — none.
ThemeTokens
Read it as everything a renderer needs for one scheme, as plain numbers and #RRGGBB colours.
What to expect — Resolved by Graine from the brand's design and carried with the agent's settings; a token missing from an older server is filled with the default. Every text colour has already been checked against what it sits on.
| Field | Type | Default | Meaning |
|---|---|---|---|
v | 1 | required | The token format's version. |
preset | string | required | The design preset (philosophy) these tokens come from — see DESIGN_PRESETS. |
scheme | "light" | "dark" | required | Which scheme these tokens are for. |
colors | ThemeColors | required | The colours — see ThemeColors. |
font | { body: string; heading: string; scale: { caption: number; body: number; title: number; display: number }; weights: { body: number; strong: number; title: number }; letterSpacing: { body: number; title: number }; lineHeight: { body: number; title: number } } | required | Type: the body and heading families ("system" is the phone's own font), sizes in points, weights, letter spacing and line height. |
shape | { radius: { sm: number; md: number; lg: number; xl: number }; borderWidth: number; launcher: LauncherShape } | required | Corner radii in points (at most 48), the hairline width, and the launcher's shape. |
spacing | { unit: number; cardPadding: number; gap: number } | required | The spacing unit, a card's padding and the gap between items, in points. |
elevation | { card: ThemeShadow | null; floating: ThemeShadow | null; hardOffset: { x: number; y: number } | null } | required | Shadows for cards and floating surfaces; hardOffset is a solid offset shadow drawn in colors.text (a bold, outlined look). |
motion | { durationMs: number; easing: MotionEasing; bounce: number } | required | How long transitions take, their easing, and how much they overshoot. |
icons | { style: IconStyle; stroke: number; launcher: { id: string; path: string; stroke: boolean } | null } | required | The icon style and stroke width, and the launcher's glyph (null when the launcher shows the brand's logo). |
glass | { blur: number; surfaceOpacity: number } | null | required | Translucent, blurred surfaces, or null for solid ones. |
bubbles | DesignBubbles | required | How message bubbles are drawn. |
chrome | DesignChrome | required | "window" frames the chat, the caption and components as desktop windows; "none" is a plain surface. |
chat | ThemeChat | required | The chat window, resolved: its words (title, subtitle, placeholder — empty for the defaults), avatar, timestamps, conversation starters, background and pattern, header and composer colours, size, and the pieces it may leave out — showUpcomingCalls, upcomingTitle, showHistory, newConversation (all on by default). |
call | ThemeCall | required | The call screen, resolved: its background (and gradient), text, avatar, button colours, end-button shape, whether captions and a timer show, the in-call controls, the ringtone and the status words. |
ThemeColors
Read it as the design's colours for one scheme, each a #RRGGBB string.
| Field | Type | Default | Meaning |
|---|---|---|---|
background | string | required | The page behind everything. |
surface | string | required | Panels, sheets and the bar. |
surfaceRaised | string | required | Cards and raised controls on a surface. |
text | string | required | Main text. |
textMuted | string | required | Secondary text — captions, hints, timestamps. |
border | string | required | Hairlines and outlines. |
accent | string | required | The brand's accent — primary buttons, the launcher, selection. |
onAccent | string | required | Text and icons on the accent. |
accentSoft | string | required | The accent at low strength — chips, selected rows, the focus halo. |
success | string | required | Success states. |
warning | string | required | Warnings. |
danger | string | required | Errors, hanging up, refusal lines. |
userBubble | string | required | The customer's message bubbles. |
userBubbleText | string | required | Text in the customer's bubbles. |
agentBubble | string | required | The agent's message bubbles. |
agentBubbleText | string | required | Text in the agent's bubbles. |
scrim | string | required | The dimming colour behind a sheet (drawn at reduced opacity). |
ThemeShadow
Read it as one shadow of the design.
| Field | Type | Default | Meaning |
|---|---|---|---|
y | number | required | How far down the shadow falls, in points. |
blur | number | required | How soft it is, in points. |
spread | number | required | How far it grows beyond the shape, in points. |
opacity | number | required | How strong it is, 0 to 1, applied to color. |
color | string | required | The shadow's colour. |
ResolvedDesign
Read it as a whole design: the brand's choices and the tokens they resolve to in both schemes.
| Field | Type | Default | Meaning |
|---|---|---|---|
config | DesignConfig | required | What the brand chose — see DesignConfig. |
light | ThemeTokens | required | The tokens for light mode. |
dark | ThemeTokens | required | The tokens for dark mode. |
DesignConfig
Read it as the brand's design choices, as the dashboard saves them: a preset and a handful of adjustments.
What to expect — Every field but preset is optional — absent means the preset's own answer. Unknown values are dropped, so an older SDK draws something sensible from a newer design.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | string | "graine-classic" | The design philosophy — see DESIGN_PRESETS. An unknown one is the default. |
accent | string | the preset's | A #RRGGBB accent replacing the preset's in both schemes. |
scheme | "auto" | "light" | "dark" | "auto" | Fix the scheme, or follow the phone. |
roundness | number | 1 | 0 (square) to 1 (the preset's own corners) to 2 (twice as round). |
font | string | the preset's | The body typeface: "system" or a family from DESIGN_FONTS. |
headingFont | string | the preset's | The heading typeface, as font. |
density | "compact" | "comfortable" | "spacious" | "comfortable" | How much room around things. |
shadow | "preset" | "none" | "soft" | "medium" | "strong" | "hard" | "preset" | The shadows. |
motion | "preset" | "none" | "subtle" | "lively" | "preset" | The transitions. |
neutral | "preset" | "cool" | "neutral" | "warm" | "preset" | The tint of the greys. |
glass | boolean | the preset's | Translucent, blurred surfaces. |
bubbles | DesignBubbles | the preset's | How message bubbles are drawn. |
launcherIcon | string | the preset's | A glyph id from LAUNCHER_ICONS, or "logo" for the brand's logo. |
launcherShape | LauncherShape | the preset's | The launcher's shape. |
iconStyle | IconStyle | the preset's | How the SDK's icons are drawn. |
chrome | DesignChrome | the preset's | Frame the chat, the caption and components as desktop windows. |
chat | DesignChatConfig | none | The chat window's choices: title, subtitle, avatar ("logo", "glyph", "none"), placeholder, timestamps, background ("plain", "tint", "pattern"), up to four conversation starters, header ("surface", "accent"), size, and showUpcomingCalls, upcomingTitle, showHistory, newConversation. |
call | DesignCallConfig | none | The call screen's choices: background, avatar and its shape, captions, timer, the in-call controls, the end button's shape, the ringtone and the status words. |
DesignPreset
Read it as one design philosophy — a complete look before any adjustment.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | The preset's id, as DesignConfig.preset names it. |
name | string | required | Its name in the dashboard. |
tagline | string | required | A one-line description, as the dashboard shows it. |
family | "graine" | "native" | "modern" | "enterprise" | "expressive" | required | The group it is listed under. |
inspiredBy | string[] | required | The well-known looks it draws on. |
font | ThemeTokens["font"] | required | Its type. |
shape | ThemeTokens["shape"] | required | Its corners, hairline and launcher shape. |
spacing | ThemeTokens["spacing"] | required | Its spacing. |
elevation | ThemeTokens["elevation"] | required | Its shadows. |
motion | ThemeTokens["motion"] | required | Its transitions. |
icons | { style: IconStyle; stroke: number; launcher: string } | required | Its icon style, stroke and launcher glyph id. |
glass | ThemeTokens["glass"] | required | Its translucency, or null. |
bubbles | DesignBubbles | required | Its message bubbles. |
chrome | DesignChrome | "none" | Its framing. |
colors | { light: Omit<ThemeColors, "accentSoft">; dark: Omit<ThemeColors, "accentSoft"> } | required | Its colours for light and dark (every ThemeColors field but accentSoft, which is derived from the accent). |
DESIGN_PRESETS
Use it to list the design philosophies a brand can choose.
What to expect — 23 presets, in the dashboard's order, by id: graine-classic, signature, chat-neutral, material-3, ios-native, minimal-mono, fluent-2, carbon, fintech-clear, liquid-glass, soft-playful, desktop-buddy, swiss-grid, precision, japandi-calm, neo-brutalism, editorial, luxury, retro-terminal, warm-earthy, bauhaus, gamified, high-contrast. A newer server may know more; the SDK draws an unknown one as the default.
DEFAULT_DESIGN_PRESET
Read it as the design an agent that never chose one is drawn in.
What to expect — "graine-classic".
designPreset
Use it to look up a preset by id.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | unknown | required | The preset's id (any case); a former id is mapped to its current one. |
Returns — the DesignPreset, or the default (graine-classic) for an id it does not know. Example value: { id: "signature", name: "Signature", … }.
Errors — none.
normaliseDesignConfig
Use it to check a design config that came from anywhere: known keys, allowed values, nothing else.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | unknown | required | The config as it arrived. |
Returns — a DesignConfig: an unknown preset becomes the default, colours become #RRGGBB, roundness is clamped to 0–2, values outside their lists are dropped. Example value: { preset: "graine-classic", accent: "#3740B8" }.
Errors — none.
designFromAppearance
Use it to read the design an agent's appearance describes — the chosen design, or the older appearance settings as one.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | unknown | required | The agent's appearance (useGraineAgent().appearance). |
Returns — appearance.design when the brand chose one; otherwise Graine Classic with the older settings' accent, corners and light or dark. Example value: { preset: "graine-classic", accent: "#318CE7", scheme: "auto", roundness: 1 }.
Errors — none.
resolveTokens
Use it to work out the tokens of one scheme from a design config.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
configInput | DesignConfig | required | The design (checked again here). |
scheme | "light" | "dark" | required | The scheme to resolve. |
Returns — the ThemeTokens.
Errors — none.
resolveDesign
Use it to work out a whole design — both schemes at once — from a config.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
configInput | unknown | required | The design config, as it arrived. |
Returns — a ResolvedDesign.
Errors — none.
pickTokens
Use it to pick the scheme to draw: the brand's fixed one, else the phone's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
design | ResolvedDesign | required | The design. |
deviceScheme | "light" | "dark" | null | undefined | required | The phone's scheme — useColorScheme(). |
Returns — design.light or design.dark: the brand's scheme when it fixed one; otherwise dark only when the phone is dark.
Errors — none.
sessionDesign
Use it to get the design to draw from the agent's settings — always a complete one, even before they load.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
session | { design?: unknown; appearance?: unknown } | null | undefined | required | The agent's settings — client.config — or null before they load. |
Returns — Graine's design with any missing or malformed token filled, or — when the settings carry none — the design the appearance implies; the default design for null.
Errors — none.
contrastRatio
Use it to check that a text colour reads on a background (WCAG contrast).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
a | string | required | One colour, #RGB or #RRGGBB. |
b | string | required | The other colour. |
Returns — the contrast ratio, from 1 (none) to 21 (black on white); 4.5 is the usual minimum for body text. Example value: 4.83.
Errors — none.
readableOn
Use it to choose the text colour that reads best on a background.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
bg | string | required | The background colour. |
candidates | ReadonlyArray<string> | ["#FFFFFF", "#101318"] | The colours to choose from. |
Returns — the candidate with the highest contrast on bg. Example value: "#FFFFFF".
Errors — none.
DesignBubbles
Read it as how message bubbles are drawn.
| Value | Meaning |
|---|---|
"filled" | Solid bubbles. |
"outline" | Outlined bubbles. |
"minimal" | No bubble — text on the page. |
DesignChrome
Read it as how a surface holding content is framed.
| Value | Meaning |
|---|---|
"none" | A plain surface. |
"window" | A desktop window, with three dots and a title bar — the playful-desktop look. |
DESIGN_CHROMES
Use it to list the framings.
What to expect — A fixed list.
LauncherShape
Read it as the launcher's shape.
| Value | Meaning |
|---|---|
"circle" | Round. |
"squircle" | A rounded square with continuous curves. |
"rounded-square" | A square with rounded corners. |
"square" | Square corners. |
"pill" | A wide pill. |
MotionEasing
Read it as how the design's transitions ease.
| Value | Meaning |
|---|---|
"standard" | An ordinary ease. |
"emphasized" | A fast start and a long, soft settle. |
"spring" | An overshoot that settles, scaled by the token's bounce. |
"linear" | An even pace. |
"none" | No animation. |
IconStyle
Read it as how the SDK's icons are drawn.
| Value | Meaning |
|---|---|
"outline" | Stroked, with round ends and joins. |
"filled" | The shape filled, lightly stroked. |
"duotone" | The shape filled at low strength, then stroked. |
"sharp" | Stroked, with square ends and mitred joins. |
SdkIconName
Read it as the names of the SDK's own icons, drawn the same on every surface.
| Value | Meaning |
|---|---|
"mic" | The microphone. |
"micOff" | The microphone, muted. |
"end" | Hang up. |
"send" | Send a message. |
"chat" | The chat. |
"call" | A call — the booking sheet and the booked-calls card use it. |
"chevron" | Back, or collapse. |
"close" | Close. |
"compose" | New conversation. |
SDK_ICONS
Use it to draw the SDK's icons yourself, as 24×24 paths, in the design's icon style.
What to expect — Each icon is a closed body that may be filled and lines that are only ever stroked, both SVG path data on a 24×24 grid. Draw them in icons.style at icons.stroke.
| Key | Value | Meaning |
|---|---|---|
mic | { body, lines } | The microphone. |
micOff | { body, lines } | The microphone with a strike. |
end | { body } | The hang-up handset. |
send | { body, lines } | The send arrow. |
chat | { body } | The chat bubble. |
call | { lines } | The phone handset. |
chevron | { lines } | The chevron. |
close | { lines } | The cross. |
compose | { lines } | A page and a pen — "New conversation". |
speaker | { body, lines } | The loudspeaker, for a call screen. |
LAUNCHER_ICONS
Use it to list the launcher glyphs a brand can choose, as 24×24 SVG paths.
What to expect — 17 glyphs: waveform, audio-lines, sparkles, microphone, message-circle, headset, bot, phone, zap, star, orbit, sparkle-filled, buddy, chat-dots-filled, graphic-eq, grain, grain-voice. Each has an id, a label for the dashboard, its path, and stroke — true to stroke it at icons.stroke, false to fill it.
glyphUrl
Use it to fetch a launcher glyph as a PNG image instead of drawing the path.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
origin | string | required | Graine's address — client.opts.baseUrl. |
id | string | required | The glyph's id from LAUNCHER_ICONS. |
color | string | required | #RRGGBB (white when it cannot be read). |
sizePx | number | required | The size in physical pixels (points × screen scale), so it is sharp — 16 to 512. |
stroke | number | 2 | The stroke width, 0.5 to 4. |
Returns — the image's address on Graine. Example value: "https://www.graine.ai/api/embed/glyph?id=waveform&c=FFFFFF&s=168&w=2&v=2".
Errors — none.
LogoPlate
Read it as what the brand's logo sits on, chosen in Appearance → Logo.
| Value | Meaning |
|---|---|
"accent" | The shape's own accent fill — for a white or light logo made for a coloured background. The default. |
"white" | A white plate with a hairline — for a coloured or dark logo, or a logo on an opaque image. |
LOGO_PLATES
Use it to list the logo plates.
What to expect — A fixed list.
LOGO_PLATE_WHITE
Read it as the white plate's colour.
What to expect — "#FFFFFF".
normaliseLogoPlate
Use it to read a logo plate setting safely.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | unknown | required | The setting as it arrived. |
Returns — "white" for "white", else "accent". Example value: "accent".
Errors — none.
logoBox
Use it to size the brand's logo inside the launcher or an avatar — whole, centred, never cropped.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
shape | string | required | The shape's LauncherShape. |
size | number | required | The shape's size across (a pill: its height), in points. |
Returns — the side of the square the logo fits in: 80% of size for a square or rounded square, 70% for a circle, squircle or pill, rounded down, at least 1. Example value: 39 for a 56-point circle.
Errors — none.
logoPlateLook
Use it to draw what the logo sits on: the fill, and the outline when there is one.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
plate | LogoPlate | required | The plate. |
colors | Pick<ThemeColors, "accent" | "border"> | required | The design's accent and border colours. |
borderWidth | number | 1 | The outline's width, at least 1. |
Returns — for "white": { fill: "#FFFFFF", outline: { width, color: colors.border } }; for "accent": { fill: colors.accent, outline: null }. Example value: { fill: "#3740B8", outline: null }.
Errors — none.
DESIGN_FONTS
Use it to list the typefaces the dashboard offers, with the scripts each covers.
What to expect — 25 entries: system (the phone's own font, every script it covers), and families such as Inter, Roboto, Geist, IBM Plex Sans, DM Sans, Noto Sans, Mukta, Hind, Poppins, Nunito, Atkinson Hyperlegible Next, Source Serif 4, Newsreader, Fraunces, Playfair Display, Martel and JetBrains Mono. category is "sans", "serif", "mono" or "display"; scripts lists what each can write (latin, devanagari, tamil, …) — a Latin-only face needs a partner for other scripts.
FontLoader
Pass expo-font's Font.loadAsync as the provider's loadFont: it is the shape the SDK needs to register the design's typefaces.
What to expect — Called with { "Graine-DMSans-700": "https://…" }-style maps — the registered name and the file's address on Graine; a face that fails to load is drawn in the system font.
FontFace
Read it as one face of one family, as the SDK registers it.
| Field | Type | Default | Meaning |
|---|---|---|---|
family | string | required | The family, as the design names it. |
weight | number | required | The weight this face draws (the nearest the family has). |
name | string | required | The fontFamily it is registered under — "Graine-DMSans-700". |
url | string | required | Where Graine serves the file. |
faceFor
Use it to find the face to load for a family at a weight.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
family | string | null | undefined | required | The family (any case). |
weight | number | required | The weight wanted. |
origin | string | "https://www.graine.ai" | Graine's address. |
Returns — the FontFace at the nearest weight the family has (ties go heavier), or null for "system" or a family without files. Example value: { family: "DM Sans", weight: 700, name: "Graine-DMSans-700", url: "https://www.graine.ai/api/embed/font?f=DM%20Sans&w=700&v=1" }.
Errors — none.
facesForDesign
Use it to list every face a design draws with, in either scheme — to load them ahead of time.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
design | ResolvedDesign | required | The design. |
origin | string | "https://www.graine.ai" | Graine's address. |
Returns — the body family at its body and strong weights and the heading family at its title and strong weights, for both schemes, each once.
Errors — none.
fontUrl
Use it to build the address of one typeface file on Graine.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
origin | string | required | Graine's address. |
family | string | required | The family. |
weight | number | required | The weight. |
Returns — the file's address, including the file version. Example value: "https://www.graine.ai/api/embed/font?f=Inter&w=400&v=1".
Errors — none.
FONT_ASSET_VERSION
Read it as the version of the typeface files, part of their address so no cache keeps an old file.
What to expect — 1.
SvgKit
Pass react-native-svg's two components as the provider's svg, so the SDK can draw its icons as vectors without depending on the library.
| Field | Type | Default | Meaning |
|---|---|---|---|
Svg | React.ComponentType<any> | required | react-native-svg's Svg. |
Path | React.ComponentType<any> | required | react-native-svg's Path. |
resolveNativeAppearance
Use it to read the agent's appearance safely for a bar you draw yourself: a valid accent, the heading, the logo's address and the corner radius.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | Record<string, any> | null | undefined | required | The agent's appearance (useGraineAgent().appearance). |
fallback | NativeAppearanceFallback | required | What to use where the dashboard has nothing — see NativeAppearanceFallback. |
Returns — a NativeAppearance. Example value: { accent: "#3740B8", heading: "Acme assistant", logoUrl: "https://www.graine.ai/api/embed/logo?k=…", logoPlate: "accent", radius: 18 }.
Errors — none.
NativeAppearance
Read it as the agent's appearance, ready to draw.
| Field | Type | Default | Meaning |
|---|---|---|---|
accent | string | required | A valid #RGB or #RRGGBB accent. |
heading | string | required | The panel's heading as written in the dashboard — a title, not necessarily the agent's name. |
logoUrl | string | null | required | The brand's logo as an absolute address, ready for an Image; null when there is none. |
logoPlate | LogoPlate | required | The backing behind the logo when the logo replaces the launcher glyph. |
radius | number | required | Corner rounding in points, within what your bar allows. |
NativeAppearanceFallback
Read it as what to draw where the dashboard has nothing.
| Field | Type | Default | Meaning |
|---|---|---|---|
accent | string | required | The accent when the dashboard's is missing or invalid. |
heading | string | required | The heading when the dashboard has none. |
radius | number | 18 | The rounding when the dashboard has none. |
maxRadius | number | 48 | The most rounding your bar will draw (never above 48). |
origin | string | "https://www.graine.ai" | Graine's address, for the logo. |
chatPresentation
Use it to decide how a chat window of a size is drawn on a screen this big — the rule the SDK's windows follow.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
size | unknown | required | The size to open at — see ChatSize; an unknown word is compact. |
width | number | required | The window's width, in points. |
height | number | required | The window's height, in points. |
platform | "app" | "web" | "app" | An app's window, or a web page's. |
Returns — "window" for compact; for large, "sheet" on a phone and "panel" on a tablet (shorter side 600 points or more); for full, "screen" in an app (a web page on a computer gets "panel"). Example value: "sheet".
Errors — none.
ChatPresentation
Read it as where a chat window of a size goes.
| Value | Meaning |
|---|---|
"window" | Above the voice bar. |
"sheet" | Up from the bottom over about 92% of a phone's screen. |
"panel" | A panel 420 points wide in the bottom corner of a tablet (or a computer). |
"screen" | The whole screen. |
chatPanelSize
Use it to size a chat panel on a screen this tall.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
height | number | required | The screen's height, in points. |
Returns — width 420; height 90% of the screen, at most 760. Example value: { width: 420, height: 760 }.
Errors — none.
CHAT_WINDOW
Read it as the numbers a large or full chat window is drawn with.
| Key | Value | Meaning |
|---|---|---|
sheetTop | 0.08 | How far below the top safe area a sheet's top edge starts, as a fraction of the screen's height. |
panelWidth | 420 | A panel's width, in points. |
panelMaxHeight | 760 | A panel's greatest height, in points. |
panelHeight | 0.9 | A panel's height as a share of the screen's, up to panelMaxHeight. |
tabletMinSide | 600 | An app's window counts as a tablet's when its shorter side is at least this, in points. |
webPhoneBelow | 640 | A website draws a phone's sheet and full screen only while the page is narrower than this. |
Going live
Use this before a release build: the versions to ship, the settings a release needs, and the checks that catch a build without the calls package.
Release checklist
Work through it once per release.
- Versions.
@graineai/inapp-react-native0.42.0 is JavaScript only: it ships in an over-the-air update.@graineai/inapp-react-native-calls0.3.0 carries native code — the call engine of Graine's iOS and Android SDKs 0.8.0 — so adding or upgrading it needs a new store build (pod installon iOS); readGraineCalls.isAvailablebefore calling it from code an over-the-air update may run on an older build. - The agent accepts your app. The agent's Allowed domains (Agent → Embed & Widgets) include
app://; without it every request is refused withorigin_refused. - The key. The provider and
GraineCalls.configureuse the agent'spk_live_…key. - Install id. Pass
storage(AsyncStorage) to the provider, or link the calls package — memory, conversations that continue and booked calls need an install id. - Debug logs off.
GraineCalls.configure({ debug: false })in release builds (it is off unless you turn it on). - Your app's version. Pass
appVersionto the provider and toGraineCalls.configure, so each conversation and call says which build it came from. - iOS.
aps-environmentisproductionfor TestFlight and App Store builds; Push Notifications and Background Modes → Audio and Voice over IP are on; test calls on a real iPhone — CallKit and VoIP pushes do not run in the simulator. - Android. Ask for notifications (
POST_NOTIFICATIONS) on Android 13+; Firebase in the release build (google-services.json, and Firebase Cloud Messaging — the package's Expo plugin orgraineFirebaseMessaging=truebrings it), so the package registers for pushes itself. With@react-native-firebase/messaging, hand every Firebase message toGraineCalls.handlePushfirst, fromindex.js, and refreshed tokens toregisterPushToken. - Expo. A development or store build, never Expo Go.
- WebRTC calls, if you ship them.
ENV["GRAINE_WEBRTC"] = "1"inios/PodfileandgraineWebRtc=trueinandroid/gradle.properties— orGRAINE_WEBRTC=1in the build server's environment — in the release build itself; list WebRTC (BSD 3-Clause) in your app's open-source licences. The agent's In-app call audio setting can move every install back to the WebSocket without a release. See WebRTC calls.
GraineCalls.isAvailable
Read it to know whether this build carries the calls package's native code — before you show a "Call" button from code an over-the-air update may run on an older build.
Returns — true when the native module is linked into this build. Example value: true.
Errors — none: it is the one member that never throws without the native code.
GraineInAppClient.setAppVersion
Call it with your app's version so each conversation's record says which build it happened in — the provider does it from its appVersion prop.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
version | string | null | undefined | required | Your app's version — "4.2.1 (310)"; empty or null clears it. |
Returns — nothing. It travels with the screen context from then on.
Errors — none.
Events and listeners
Use these for what you do once, when something happens — log it, count it, show a toast. For drawing, read state from the hooks instead.
useGraineEvents
Call it in a component to hear the conversation's events as they happen, in order; it unsubscribes on unmount.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
handler | (e: GraineEvent) => void | required | Called with each event — see GraineEvent. An inline function is fine: it is not re-subscribed on every render. |
Returns — nothing.
Errors — throws outside a GraineProvider.
What to expect — Events fire once, at the moment, on the JavaScript thread; nothing is replayed to a handler that subscribes later.
GraineEvent
Read it to react to the conversation once, when something happens; branch on type.
type | Fields | When it fires, and what to do with it |
|---|---|---|
conversation_started | none | The provider's chat connection opened — count conversations. |
conversation_ended | reason | It closed: reason tells the customer hanging up (or your app closing it) from a connection that died — keep them apart in your funnel. |
agent_speaking | speaking | The agent started (true) or stopped (false) speaking. |
agent_volunteered | text | The agent spoke first — a turn nobody asked for — with its first words; worth measuring on its own. The provider also opens its panel. |
action_requested | name | The agent asked your app to run one of your actions. |
action_completed | name, status, message | Your app answered it: status ok, refused or error, and the handler's own message when it gave one — count failures. |
muted | muted | The customer was muted or unmuted on the provider's client. |
error | message | Loud for you, invisible to the customer: the agent could not be started, the chat could not open, or the voice launcher reported something to fix — an engine page that will not load, a transport the agent has no setup for, a call refused while another was live. message is the sentence useGraineAgent().error or the launcher's onError also gets — log it. |
mic_denied | reason | The microphone was refused ("NotAllowedError") or unusable ("NotFoundError") — a call that could have happened and did not; a WebRTC call of your own (RtcVoiceSessionController with client) reports it too. |
launcher_shown | screen | The launcher became visible on a screen (the route's name, or null) — the moment a nudge is seen. |
friction_detected | name, screen, detail | The customer looks stuck — a FrictionKind, the screen, and the sentence the agent was told. Open your launcher or badge a control. |
connection_quality | quality, rtt, jitter, loss | The call's connection changed word (ConnectionQuality), with the measurement when there is one. Fires only on a change. |
network | online | The device went offline (false) or came back (true). |
reconnecting | attempt, delayMs | The chat dropped; try number attempt comes in delayMs milliseconds. |
GraineInAppClient.on
Call it outside React to hear one of the client's events; it returns the function that stops listening.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
event | string | required | The event's name — see the table below. |
fn | (payload: any) => void | required | Called with the event's payload. A listener that throws is logged and never stops the others. |
Returns — a function that removes the listener.
Errors — none.
What to expect — Events fire as they happen; nothing is replayed to a listener added later (the last component sent is kept as pendingWidget).
| Event | Payload | When it fires |
|---|---|---|
session | SessionConfig | The agent's settings loaded. |
identity | IdentityChannel | Who the customer is, this install, the memory choice or the time zone changed. |
variables | Record<string, string> | The merged prompt variables changed (setVariables, identify). |
screen | { screen: ScreenContext | null; actions: string[] } | The screen or the registered actions changed — at once, for a host carrying its own conversation. |
open | { transport: "gateway" | "direct" } | This client's connection opened. |
close | { code?: number; reason?: string; byClient?: true } | It closed: byClient when your app (or the SDK) closed it; reason "another_agent" when another agent took the line. |
reconnecting | { attempt: number; delayMs: number } | A dropped chat tries again in delayMs. |
chunk | string | Words of the agent's reply. |
turn_start | none | An agent turn begins. |
turn_end | none | The agent's words for this turn are complete. |
agent_speaking | boolean | The agent started or stopped speaking. |
caption | { role: string; text: string; live: boolean } | A caption line from the voice launcher's call. |
transcript | { text: string; final: boolean } | The customer's speech, recognised, on this client's spoken connection. |
text_sent | { id: string; text: string } | A message that was waiting went out. |
relay_text | { id: string; text: string } | For the voice launcher: a typed message to hand to the call it holds. |
history | RestoredTurn[] | An earlier conversation was restored. |
chat_cleared | { reason: "memory_forgotten" | "signed_out" | "another_user" | "new_conversation" | "app" } | The chat on screen should empty — "app" when your app called clearChat(). |
chat_failed | boolean | The chat gave up (true) — its first connect failed, or Graine ended or refused the conversation — or someone asked again or it opened (false). See chatFailed. |
widget | the component as sent | The agent drew a component (widget_id, kind, data, fallback_text, style, on_select). |
widget_pending | { widget_id?: string; kind?: string } | A component is on its way. |
widget_result | { widget_id: string; data: Record<string, unknown>; summary: string } | Your app answered a component. |
action | AppActionRequest | The agent asked for one of your actions; your handler runs next. |
action_result | { name: string; status: string; message?: string } | An action finished, or was refused. |
app_event | AppEvent | reportEvent (or a friction signal) told the agent something. |
friction | FrictionSignal | The customer looks stuck. |
highlight | { name: string | null; ttlMs: number } | Point at an element (or stop). |
muted | boolean | Muted or unmuted. |
quality | { quality: ConnectionQuality; rtt?: number; jitter?: number; loss?: number } | The connection's word changed. |
network | { online: boolean } | The device went offline or came back. |
mic_denied | { reason: string } | The microphone was refused — for the voice launcher's call, or a WebRTC call of your own started with client. |
voice_error | { message: string } | The voice launcher reported something to fix, as its onError does; useGraineEvents hears it as error. |
call_state | { connected: boolean; connecting: boolean } | The voice launcher's call is connecting, connected, or over. |
audio | { data: string; format: string | null; sampleRate: number | null } | A frame of the agent's voice on this client's spoken connection (base64). |
clear | none | The customer talked over the agent: drop the agent's queued audio. |
memories_changed | { forgotten: true; deleted: number } | forgetMemory() deleted the customer's memories. |
scheduled_calls_changed | { booked: string } | { cancelled: string } | A call was booked or cancelled through the SDK. |
open_chat | { size: ChatSize | null } | openChat() asked for the chat. |
open_booking | { id: number; reason: string | null } | openBooking() asked for the booking sheet. |
surface | { surface: AgentSurface } | The customer switched from the call to the chat, or back. |
voice_host | { present: boolean } | A voice launcher mounted or unmounted. |
native_call | { live: boolean } | A call from the calls package went live, or ended. |
line_taken | { reason: "another_agent" } | Another agent took the line from this client's call. |
diagnostic | { type: "actions"; usable: string[]; undeclared: string[]; unhandled: string[] } | Your registered actions and the agent's declared ones do not match. |
frame | the message as sent | Graine sent something this release does not handle — for diagnostics. |
GraineInAppClient.emit
The SDK's own views call it to pass events between the pieces that share one client; your app does not need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
event | string | required | The event's name. |
payload | any | none | What it carries. |
Returns — nothing. Every listener of event is called; one that throws is logged and the others still run.
Errors — none.
Errors
Use these to handle what can go wrong: the error a request to Graine rejects with and every code it carries, what stops voice from starting, the WebRTC controller's errors, the calls package's, and the limits that apply.
GraineError
Catch it from any method that asks Graine on the customer's behalf — memory, booking, a new conversation, the agent's settings: branch on code, and show message, which is written for the customer.
| Member | Type | Meaning |
|---|---|---|
constructor | (code, message, status = 0, retryAfterSeconds = null) | Builds one — for tests, or to reject the same way from your own wrappers. |
code | GraineErrorCode | What went wrong, as a code to branch on — see GraineErrorCode. |
status | number | The HTTP status of Graine's answer; 0 when the SDK refused on the device or no answer came back. |
retryAfterSeconds | number | null | With rate_limited: how long Graine asked you to wait, in seconds, when it said; else null. |
message | string | A sentence fit for the customer. It never carries the identifier, traits or a push token. |
name | string | Always "GraineError". |
GraineErrorCode
Read it to know every code a GraineError can carry, when it happens and how to fix it.
What to expect — A code newer than this SDK arrives as its own word: give every switch a default.
| Code | HTTP | Means | Raised by | Fix |
|---|---|---|---|---|
invalid_key | 401 | The publishable key is not one, or belongs to no agent. | Every request to Graine | Copy the key again from Agent → Embed & Widgets → Install. |
origin_refused | 403 | The app is not allowed: app:// is missing from the agent's Allowed domains. | Every request | Add app:// to the agent's Allowed domains. |
rate_limited | 429 | Too many requests from this install, key or address. | Every request | Wait retryAfterSeconds when Graine said how long, then retry. |
invalid_request | 400 or 422 (0 on the device) | A field is missing or malformed — a time that cannot be read, an empty call id, no install id. | scheduleCall, scheduledCalls, cancelScheduledCall, memories, forgetMemory, newConversation | Fix the request; for the install id, pass storage or link the calls package. |
identity_required | 400 (0 on the device) | No customer is identified, or the identifier is a placeholder. | memories, forgetMemory, scheduleCall | Call identify() with the customer's identifier first. |
device_limit | 403 | This install is not one of the customer's allowed devices. | memories, forgetMemory, newConversation, scheduleCall | Sign out on another device, raise the agent's Devices per user, or Reset devices for the customer. |
scheduling_off | 403 (0 on the device) | The agent does not take bookings from the app. | scheduleCall | Turn on booking for the agent, or hide booking when bookingAvailable is false. |
too_soon | 400 (0 on the device) | Earlier than schedulingRules.earliest(now). | scheduleCall | Offer times from earliest(now). |
too_far | 400 (0 on the device) | Later than schedulingRules.latest(now). | scheduleCall | Offer times up to latest(now). |
outside_window | 400 | Outside the agent's calling hours, in the customer's time zone. | scheduleCall | Show the message — it names a time that works — and offer another. |
too_many_pending | 409 | The customer already has as many booked calls waiting as the agent allows. | scheduleCall | Cancel one first. |
not_found | 404 | No such booked call for this customer or install. | cancelScheduledCall | Refresh the list with scheduledCalls(). |
unavailable | 503 (504 on a timeout) | Graine could not answer just now — including a booking refused because the agent is switched off or the workspace is out of credit. | Every request | Retry with backoff; check the agent is on and the workspace has credit. |
network | — (0) | No answer came back: offline, or the connection dropped. "Could not reach Graine. Check the connection and try again." | Every request | Retry when online. |
unknown | other | An answer the SDK could not read, or a booking whose details did not come back. | Every request | Log the code and message; show the message. |
When voice cannot start
Read this when the voice bar will not start: the reason reaches onError and api.error (with onTransport for a WebRTC fallback), not a GraineError.
| Reason | HTTP | Means | Fix |
|---|---|---|---|
key_missing | 401 | No key reached Graine. | Pass publishableKey to the provider. |
key_malformed | 401 | Not a publishable key. | Use the pk_live_… key from Agent → Embed & Widgets. |
key_unknown | 401 | No agent has that key. | Copy the key again. |
no_origin | 403 | The app is not allowed — the usual one for apps. | Add the literal entry app:// to the agent's Allowed domains. |
no_domains_configured | 403 | The agent's Allowed domains list is empty, which allows nothing. | Add app://. |
origin_not_allowed | 403 | This origin is not on the list. | Add it to Allowed domains. |
not_configured | 503 | WebRTC is not enabled for your workspace. The call still works the other way. | Ask Graine support; it cannot be fixed from the app. |
A refused microphone arrives as onMicDenied("NotAllowedError") ("NotFoundError" for a phone with no usable microphone). An engine that could not load after every retry says "The assistant could not load. Check your connection and try again."; a start while a call from the calls package holds the line says "A call is already in progress." Everything that reaches onError also reaches useGraineEvents as an error event, so analytics can hear it without wiring onError.
RtcVoiceError
Catch it from RtcVoiceSessionController.start(): branch on code, show message.
| Member | Type | Meaning |
|---|---|---|
constructor | (code, message, cause?) | Builds one — for tests. |
code | RtcVoiceErrorCode | What went wrong — see RtcVoiceErrorCode. |
cause | unknown | The underlying error, when there was one — for your logs. |
message | string | What went wrong, in a sentence. |
RtcVoiceErrorCode
Read it to know why a WebRTC call could not start.
| Value | Meaning |
|---|---|
"mint_failed" | The call could not be set up: no publishableKey ("Pass publishableKey (or getSession for a custom backend)."), Graine unreachable ("Could not reach the call service."), or refused — the message is Graine's own, or "Could not start a call (status)." |
"microphone_denied" | The customer refused the microphone — "The microphone was not allowed." They can allow it in Settings; trying again will not help until they do. With opts.client set, the client's events hear mic_denied too. |
"connect_failed" | The connection or the call failed after it was set up. |
"already_active" | A call is already starting or live on this controller — "A call is already in progress." |
Calls package errors
Read this for what the calls package throws or rejects with — plain Errors with these messages, or the engine's invalid_options code.
| Where | Message | Fix |
|---|---|---|
| Any method that needs the native code | "@graineai/inapp-react-native-calls is not linked: rebuild the app (pod install on iOS) — it carries native code, so an over-the-air update cannot add it." | Ship a build with the package; check GraineCalls.isAvailable. |
configure | "publishableKey is required", "displayName is required", publishableKey is required for the agent "sales", "An agent's name must be a non-empty string.", "default" is the agent with the top-level publishableKey; name the others | Fix the options; the last good configuration stays in force. |
configure | code invalid_options, a message naming the option | Fix that option's value. |
startCall | No agent named "sales" is configured. Call GraineCalls.configure("sales", { publishableKey, … }) first. | Configure the agent first. |
registerPushToken | "registerPushToken needs the token", or "…cannot register push tokens in this build: rebuild the app…" | Pass the token; rebuild with a current package. |
A call that fails reports through onOutcome and the snapshot's error, not by throwing — see GraineCallOutcome.
Rate limits and quotas
Read this to know the limits that apply to every app, and what the agent's settings let you change.
| Limit | Value | Where it applies |
|---|---|---|
| Bookings from one install | 10 an hour | scheduleCall, and the agent's bookings in that install's conversations |
| Bookings in a workspace | 1,000 an hour | All in-app bookings |
| Calls waiting per customer | 3 by default (1–10) | At most this many upcoming calls per user |
| Devices per customer | 2 by default (1–5) | Devices per user; idle devices free their place after 30 days |
| Rings per customer per day | 3 by default (1–10) | Ring each user at most, in the customer's own day |
| Quiet period after a decline | 6 hours | No ring for that customer |
| Notice and horizon | 5 minutes (2–1440) · 14 days (1–30) | With at least this much notice · Booked at most this far ahead |
| Ring time · ring again | 45 s (20–60) · once after 10 min (0–120) | Ring for · After a missed call, ring once more after |
| Lateness | 10 minutes | A booked call more than 10 minutes late is not rung |
| Booking reason · variables | 120 characters · 12 variables, 1 KB in all | Bookings, and the variables a conversation's ticket carries |
| Memories listed | At most 100, newest first | memories() |
| Conversation kept | 50 messages, 7 days after the last | Conversations that continue |
| Requests per key and address, per minute | Starting a conversation 30 (300 per key); voice sessions 10 (120 per key); booking, listing, cancelling, device registration, memory list and sign-out 30 (300 per key); call outcomes 60 (600 per key); forgetting memory 10 (120 per key); listing memories also 30 per install | Answered rate_limited, with how long to wait |
| An action's answer · a component's content | About 5.5 seconds · 8 seconds | After that, the agent is told the action did not finish; a component shows what it was drawn with |
Lower-level building blocks
Use these only when you carry a conversation yourself — your own connection to Graine — instead of the client: how variables travel, how the agent's text marks its turns, and how an earlier conversation arrives.
TICKET_VARIABLE_LIMITS
Read it to know how many prompt variables a conversation's ticket carries, and how long they may be — the usual connection takes your variables from the ticket.
| Key | Value | Meaning |
|---|---|---|
keys | 12 | At most 12 variables: who_called and call_reason first, then the rest in their order — the first 12 with a usable value are kept. |
keyLength | 40 | A name is cut to 40 characters. |
valueLength | 200 | A value is cut to 200 characters. |
totalBytes | 1024 | The set is trimmed to fit 1 KB (names and values in UTF-8, plus a few bytes each): the biggest values are left off first, one at a time, until the rest fit — who_called and call_reason last. |
What to expect — Every conversation opened by the client asks Graine for a single-use ticket carrying your variables within these limits; the agent's context comes from the ticket. A direct connection (no ticket) carries them in its address instead — see contextToken.
variablesForTicket
Use it to trim variables to what a ticket carries, exactly as the client does.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
vars | Record<string, unknown> | null | undefined | required | The variables — client.allVariables(). |
Returns — at most TICKET_VARIABLE_LIMITS.keys variables as strings — who_called and call_reason first — names and values cut to their limits; null, undefined and object values, and empty names or values, are skipped. Over 1 KB, the biggest values are left off one at a time until the rest fit (who_called and call_reason only when nothing else is left), so keep the ones that matter short. undefined when nothing is left. Example value: { first_name: "Asha", plan: "Plus" }.
Errors — none.
contextToken
Use it to encode variables for a direct connection's address, the way the client does when no ticket can be had.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
variables | Record<string, unknown> | null | undefined | required | The variables; null, undefined and object values are dropped, numbers and booleans become strings. |
callType | string | "webcall" | The conversation's kind: "webcall" for a spoken one, "text_chat" for a typed one. |
Returns — base64 (UTF-8, so any script is safe) of the variables together with the conversation's kind — for the address's context_token parameter; null when no variable is left or they cannot be encoded. Example value: "eyJmaXJzdF9uYW1lIjoiQXNoYSIs…".
Errors — none.
readAgentChunk
Use it to read one text message of the agent's reply: the words to show, and whether it opens or closes a turn.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | unknown | required | The text as it arrived. |
Returns — an AgentChunk: the turn markers (STREAM_START, STREAM_END, in any case) taken out and nothing else touched — no trimming, since the pieces are joined into a sentence. Example value: { text: "Sure — ", startsTurn: true, endsTurn: false }.
Errors — none.
AgentChunk
Read it as one piece of the agent's reply.
| Field | Type | Default | Meaning |
|---|---|---|---|
text | string | required | The words to show — often empty, when the piece is only a marker. |
startsTurn | boolean | required | This piece opened an agent turn: start a new bubble. |
endsTurn | boolean | required | This piece closed the turn. |
STREAM_START
Read it as the marker that opens an agent turn inside its text — never words to show.
What to expect — Taken out by readAgentChunk, which matches it in any case.
STREAM_END
Read it as the marker that closes an agent turn inside its text — never words to show.
What to expect — Taken out by readAgentChunk. On a spoken conversation it means the turn's text is complete; the voice may play on for some seconds.
readHistoryFrame
Use it to read the earlier conversation Graine sends when an identified customer's chat continues.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
frame | unknown | required | The message as it arrived (its turns list). |
Returns — the readable turns, oldest first — see RestoredTurn; null when the message has no turns list (then ignore it). A turn that cannot be read is skipped: one that is not an object, has a role other than the customer's or the agent's, blank text, or a time that is not ISO-8601 with a zone. Example value: [{ id: "t_81", role: "customer", text: "Where is my order?", at: 1760400000000, channel: "chat" }].
Errors — none: it never throws.
What's new in 0.42.0 and calls 0.3.0
@graineai/inapp-react-native 0.42.0:
- Booking with the SDK's own sheet.
client.openBooking({ reason })opens a booking sheet the provider draws in the agent's design — the day, a grid of times every 15 minutes, the phone's time zone, "What's it about? (optional)" and Book call — and resolves with the booked call, ornullwhen the customer closes it.GraineBookingSheetis the same sheet for an app that holds its open state itself. - Whether to offer booking.
client.bookingAvailableandclient.bookingBlocker, andbookingAvailable/openBookinginuseGraineAgent()anduseGraineScheduling(): true only when the agent lets customers book, Graine can ring this phone, and the customer is identified. - Refusals in plain words. A refused booking stays in the sheet as one line — "Pick a time at least 5 minutes from now", "Sign in to book a call", … — worded from the agent's own numbers.
- Booked calls and "Book a call" in the chat. The SDK's chat windows (
GraineVoiceBar's andGraineChatScreen) now show the customer's booked calls — an Upcoming calls card, as the design allows, each call with Cancel — and Book a call under them, or in their place when none are booked, wherever booking is available.chatPiecestakesbookingAvailableand returnsbook. - The booking rules, for your own screen.
bookingBounds,bookingDays,bookingSlots,clampBooking,bookingDayLabel,bookingCallName,bookingSubtitle,bookingZoneLine,bookingDoneLine,bookingErrorLine,upcomingCalls,upcomingTimeLabel,BOOKING_TEXTand theBOOKING_*numbers — the same rules and words as every platform's sheet.
@graineai/inapp-react-native-calls 0.3.0 — the call engine of the iOS and Android SDKs 0.8.0:
- Calls start at once on the configuration already kept on the device; it is refreshed behind the call.
- iOS: one 20-second deadline covers everything before the conversation opens — "The call could not connect within 20 seconds." — instead of steps that could add up to most of a minute; a call whose audio the system never hands over now fails with "The call's audio could not start." instead of staying silent.
- Android: a connected call where nothing arrives for 50 seconds now ends (
ended,reason: "failed") instead of staying open and silent; a slow network never holds the call up (at most 300 ms of the customer's voice waits to be sent, the oldest dropped); hanging up lets go of the microphone at once.
Every release: Changelog.
Every symbol, A to Z
Every public symbol of the React Native SDK — 1259 in all — by name, each linked to where this page documents it: a type or function first, then its members.
ActionDiagnosis—undeclared·unhandled·usableActionHandleractiveRouteNameaddMaskRuleAGENT_PCM_RATEAgentChunk—endsTurn·startsTurn·textAgentSurface—chat·voiceAppActionRequest—action_id·arguments·kind·nameAppActionResult—data·message·statusAppEvent—cooldown_seconds·data·detail·max_per_session·name·quietAudioAdapter—bufferedSeconds·clear·play·startCapture·stopCaptureAudioFormat—kind·sampleRateAWAITING_REPLY_MSbase64ToBytesbase64ToPcm16BOOKING_REASON_MAXBOOKING_ROUND_MINUTESBOOKING_SLOT_MINUTESBOOKING_TEXT—about·book·done·title·webbookingAvailableFrombookingBlockerBookingBlocker—identity_required·scheduling_offbookingBoundsBookingBounds—earliest·latestbookingCallNameBookingDay—first·key·label·lastbookingDayLabelbookingDaysbookingDoneLinebookingErrorLineBookingPlatform—android·ios·webbookingSlotsbookingSubtitlebookingZoneLineCaption—live·role·textCAPTION_HISTORYCAPTURE_SAMPLE_RATECHAT_HISTORY_CAPCHAT_SIZESCHAT_TYPING_TIMEOUT_MSCHAT_WINDOW—panelHeight·panelMaxHeight·panelWidth·sheetTop·tabletMinSide·webPhoneBelowchatClockchatDateChatEvent—agent_text·agent_turn_end·agent_turn_start·at·cleared·closed·customer·customer_sent·history·id·opened·queued·text·turns·type·typing_timeout·widgetchatEventFromClientchatIsNewchatPanelSizechatPiecesChatPieces—book·newConversation·starters·turns·upcoming·upcomingTitleChatPieceTokens—newConversation·showHistory·showUpcomingCalls·upcomingTitlechatPresentationChatPresentation—panel·screen·sheet·windowChatRow—at·day·first·index·item·key·kind·label·last·turn·voiceChatRowOptions—clock·date·now·utcOffsetMinuteschatRowsChatSize—compact·full·largechatSizeOfChatState—awaitingReply·settled·turns·typingChatTurn—at·channel·id·live·pending·proactive·restored·role·textchatTurnRowsclampBookingclampOffsetclassifyQualityConnectionQuality—excellent·good·lost·poor·unknowncontextTokencontrastRatiodecodeAgentAudioDecodedAudio—kind·latch·pcm16·sampleRatedecodeMuLawdecodePcm16DEFAULT_AGENTDEFAULT_DESIGN_PRESETDEFAULT_FRICTIONDEFAULT_IDENTITY_FIELDDEFAULT_LOADER_KINDDEFAULT_LOADER_TEXT—component·connecting·reconnecting·thinkingDEFAULT_UPCOMING_TITLE—many·oneDEFAULT_WIDGET_PRESETDESIGN_CHROMESDESIGN_FONTSDESIGN_PRESETSDesignBubbles—filled·minimal·outlineDesignChrome—none·windowDesignConfig—accent·bubbles·call·chat·chrome·density·font·glass·headingFont·iconStyle·launcherIcon·launcherShape·motion·neutral·preset·roundness·scheme·shadowdesignFromAppearancedesignPresetDesignPreset—bubbles·chrome·colors·elevation·family·font·glass·icons·id·inspiredBy·motion·name·shape·spacing·taglineDRAG_SLOP_PXEchoGuard—constructor·filter·interrupting·reset·thresholdEMPTY_CHAT—awaitingReply·turns·typingEVENTS—action·incoming·outcome·state·voipTokenfaceForfacesForDesignfillSelectArgsfillSelectTemplatefoldChatFONT_ASSET_VERSIONFontFace—family·name·url·weightFontLoaderfontUrlFrictionConfig—cooldownMs·enabled·nudge·screensFrictionDetector—configure·constructor·idleDueIn·onIdle·onInteraction·onScreen·onTrack·settingsFrictionKind—incomplete_submit·looping_between_screens·rage_taps·repeated_field_error·screen_error·stalled_on_stepFrictionNudge—cooldownSeconds·maxPerSession·openLauncherFrictionScreenRules—backAndForth·idle·ignore·incompleteSubmit·message·rageTaps·repeatedErrors·screenErrorFrictionSignal—cooldown_seconds·data·detail·max_per_session·name·screenglyphUrlGraineActionResult—data·message·statusGraineAgentBarGraineAgentBarProps—accent·avatarUrl·bottomInset·captionsOn·draggable·hidden·name·onPlacementChange·placement·scheme·themeGraineAttachableClient—buildContextFrame·executeAction·getAvailableActions·getIdentity·getRawTraits·getScreen·getVariables·memoryEnabled·on·opts·setNativeCallLiveGraineBarActionGraineBarActionProps—icon·label·onPressGraineBarTheme—border·chip·danger·onAccent·sub·surface·textGraineBookingSheetGraineBookingSheetProps—client·onBooked·onClose·reason·safeArea·visibleGraineCallOutcome—agent·answered·callId·declined·durationSeconds·ended·error·failed·hosted·missed·outcome·reason·ringingGraineCallPermission—fullScreen·microphone·notificationsGraineCallPermissions—allGranted·fullScreen·microphone·notificationsGraineCallPermissionStatus—blocked·denied·granted·notNeededGraineCalls—agents·answer·attach·callPermissions·callSetupProblems·configure·decline·enableVoIPPushes·end·getSnapshot·handlePush·installId·isAvailable·onIncomingCall·onOutcome·openCallSettings·registerPushToken·requestCallPermissions·setMuted·setSpeaker·startCall·subscribeGraineCallsAgentOptions—baseUrl·publishableKeyGraineCallsAgentsOptions—agents·publishableKeyGraineCallSetupProblem—code·messageGraineCallSnapshot—agent·agentSpeaking·callerName·callId·caption·connectedAt·direction·error·muted·reason·speakerOn·state·transport·transportFallbackReasonGraineCallsOptions—agents·android·appVersion·baseUrl·callScreen·callTransport·debug·displayName·ios·publishableKey·ringTimeoutMs·ringtoneSoundGraineCallSource—agent·api·app·dashboardGraineCallState—active·connecting·ended·idle·ringingGraineCallStatus—answered·cancelled·completed·declined·expired·failed·missed·noDevice·ringing·scheduledGraineCallTransport—auto·webrtc·websocketGraineCaption—live·role·textGraineChatScreenGraineChatScreenProps—onClose·safeAreaGraineError—code·constructor·retryAfterSeconds·statusGraineErrorCode—device_limit·identity_required·invalid_key·invalid_request·network·not_found·origin_refused·outside_window·rate_limited·scheduling_off·too_far·too_many_pending·too_soon·unavailable·unknownGraineEvent—action_completed·action_requested·agent_speaking·agent_volunteered·attempt·connection_quality·conversation_ended·conversation_started·delayMs·detail·error·friction_detected·jitter·launcher_shown·loss·message·mic_denied·muted·name·network·online·quality·reason·reconnecting·rtt·screen·speaking·status·text·typeGraineFabGraineFabProps—accent·label·onClose·onOpen·overlay·position·prewarm·size·webViewGraineInAppClient—allVariables·availableActions·bookingAvailable·bookingBlocker·buildContextFrame·cancelScheduledCall·chatFailed·checkActions·clearChat·clearPendingWidget·clearScreen·close·config·configureFriction·connect·connectionQuality·constructor·conversation·dispose·emit·endUtterance·executeAction·forgetMemory·friction·getAvailableActions·getHighlightables·getIdentity·getInstallId·getRawTraits·getRecentEvents·getScreen·getVariables·hasVoiceHost·highlight·identify·identityChannel·installId·inTextChat·isAgentSpeaking·isConnected·isMuted·joinDevice·leaveDevice·memories·memoryEnabled·nativeCallLive·networkState·newConversation·noteInteraction·on·openBooking·openChat·opts·pendingWidget·presentPendingWidget·presentWidget·queuedTexts·registerAction·registerHighlightable·removeField·reportEvent·reportNetwork·reportQuality·retry·say·scheduleCall·scheduledCalls·schedulingRules·sendAudio·sendText·session·setAppVersion·setHostCallLive·setMemory·setMuted·setNativeCallLive·setPlayoutClock·setScreen·setSurface·settleBooking·setVariables·setVoiceHost·submitWidget·surface·takeBooking·takeChatOpen·track·transport·updateField·yieldLineGraineInAppOptions—baseUrl·chatSize·fetchImpl·friction·installIdSource·publishableKey·reconnectDelaysMs·stallAfterMs·storage·timezone·variables·voice·WebSocketImplGraineIncomingCall—agent·callerName·callId·hosted·reasonGraineLauncherGraineLauncherButtonGraineLauncherButtonProps—label·onPress·size·styleGraineLauncherPropsGraineLoaderGraineLoaderProps—color·kind·label·moment·size·stackedGraineMemory—createdAt·id·text·updatedAtGraineProviderGraineProviderProps—appVersion·autoConnect·captureTaps·children·client·endOnBackground·fab·fonts·friction·includeScreens·launcherDelayMs·loadFont·navigationRef·onProactive·onSurfaceChange·surface·svg·visibilityGraineScheduledCall—at·canCancel·id·isUpcoming·reason·source·statusGraineSchedulingRules—callName·earliest·hostedRinging·latest·maxDaysAhead·maxPendingPerUser·minLeadMinutes·platforms·ringTimeoutSeconds·usersCanBookGraineStartCallOptions—agent·reason·variablesGraineStorage—getItem·removeItem·setItemGraineTouchCaptureGraineTouchCaptureProps—children·enabled·styleGraineVoiceApi—callFailed·caption·captions·captionsOn·clearCaptions·connected·connecting·connectivity·connectStage·end·error·micDenied·muted·network·phase·quality·ready·reconnecting·retry·setCaptionsOn·setMuted·slowConnect·startGraineVoiceBarGraineVoiceBarProps—accessory·api·bottomInset·draggable·name·onClose·onPlacementChange·placement·safeAreaGraineVoiceLauncherGraineVoiceLauncherProps—active·autoStart·children·onCallState·onCaption·onEnded·onError·onMicDenied·onTransport·timing·transport·webViewGraineWidgetGraineWidgetProps—accent·radius·themeGraineWidgetTheme—border·muted·onAccent·surface·textIconStyle—duotone·filled·outline·sharpIdentityChannel—installId·memory·timezone·valueisTapLAUNCHER_ICONSLauncherContinuity—continuous·perScreenLauncherDelayPolicy—oncePerAppSession·oncePerGroupEntry·perScreenLauncherGroup—continuity·delayMs·delayPolicy·id·inset·screensLauncherInset—bottom·left·right·topLauncherShape—circle·pill·rounded-square·square·squircleLauncherVisibility—defaultDelayMs·defaultInset·groupsLauncherVisibilityTracker—constructor·onRoute·resetliftForliveAudioStreamAdapterLOADER_KINDSLOADER_MOMENTSLOADER_SIZESLOADER_SPEEDSLOADER_TEXT_MAXLoaderKind—bars·dots·mark·pulse·ringLoaderLook—color·kind·periodMs·px·skeleton·textLoaderMoment—component·connecting·reconnecting·thinkingLoaderSize—large·medium·smallLoaderSpeed—calm·normal·quickLoaderStyle—color·kind·size·skeleton·speed·textLoaderText—component·connecting·reconnecting·thinkingLOGO_PLATE_WHITELOGO_PLATESlogoBoxLogoPlate—accent·whitelogoPlateLookmanySummarymaskDeepmaskStringMAX_SELECT_DEFAULTMIN_LOADER_MSMotionEasing—emphasized·linear·none·spring·standardNativeAppearance—accent·heading·logoPlate·logoUrl·radiusNativeAppearanceFallback—accent·heading·maxRadius·origin·radiusNAVIGATE_ACTIONnormaliseDesignConfignormaliseLoaderStylenormaliseLogoPlatenormaliseSelectActionnormaliseWidgetStyleoffsetDuringDragPENDING_WIDGET_MSpickTokensPLATFORM_GRAMMAR—ios·material·oneuiPlatformGrammar—buttonRadius·chipOutlined·chipRadius·grabber·padBoost·pillButtons·radius·separatorInset·titleWeightQualitySample—jitter·loss·rttreadableOnreadAgentChunkreadHistoryFramerequestMicrophonePermissionResolvedDesign—config·dark·lightresolveDesignresolveFrictionresolveLoaderresolveNativeAppearanceresolvePlatformresolveSurfaceresolveTokensresolveWidgetImagesresolveWidgetLookresolveWidgetPaletteRestoredTurn—at·channel·id·role·textRtcVoiceError—cause·code·constructorRtcVoiceErrorCode—already_active·connect_failed·microphone_denied·mint_failedRtcVoiceOptions—baseUrl·client·getSession·onEnded·onLog·onState·publishableKey·sessionIdRtcVoiceSession—agent_id·application_sid·expires_at·password·realm·server·usernameRtcVoiceSessionController—constructor·getState·sessionId·setMuted·start·stopRtcVoiceState—active·connecting·ended·idle·ringingrulesForScreenContext—availableActions·data·error·fields·journey·screen·titleScreenField—error·label·name·optional·status·valueSDK_ICONS—call·chat·chevron·close·compose·end·mic·micOff·send·speakerSdkIconName—call·chat·chevron·close·compose·end·mic·micOff·sendSELECT_ARG_MAXSELECT_VALUE_MAXselectCallSessionConfig—agentId·appActions·appearance·calls·components·design·friction·identity·memory·pageVersion·variables·webcallAgentId·wsHostsessionDesignsetDefaultClientSTREAM_ENDSTREAM_STARTSurfacePolicy—allowed·canSwitch·startSvgKit—Path·SvgTAP_MAX_MOVE_PXTAP_MAX_MSThemeColors—accent·accentSoft·agentBubble·agentBubbleText·background·border·danger·onAccent·scrim·success·surface·surfaceRaised·text·textMuted·userBubble·userBubbleText·warningThemeShadow—blur·color·opacity·spread·yThemeTokens—bubbles·call·chat·chrome·colors·elevation·font·glass·icons·motion·preset·scheme·shape·spacing·vTICKET_VARIABLE_LIMITS—keyLength·keys·totalBytes·valueLengthTurn—at·channel·id·live·pending·proactive·restored·role·textUNNAMED_TAPupcomingCallsupcomingTimeLabeluseDraggablePlacementuseGraineActionuseGraineAgentuseGraineCalluseGraineClientuseGraineConnectionuseGraineEventsuseGraineFielduseGraineHighlightuseGraineIdentifyuseGraineInituseGraineLoaderuseGraineMemoriesuseGraineReadyuseGraineSchedulinguseGraineScreenuseGraineSurfaceuseGraineTapuseGraineThemeuseGraineTrackuseGraineVariablesuseGraineVoiceuseGraineWidgetuseKeyboardLiftvariablesForTicketVisibilityDecision—continuous·delayMs·eligible·groupId·insetvoiceCallLabelVoiceSession—active·constructor·start·stopVoiceSessionOptions—onAgentSpeaking·onTranscriptWIDGET_BUTTONSWIDGET_DENSITIESWIDGET_IMAGE_SIZESWIDGET_PLATFORMSWIDGET_PRESETSWIDGET_SELECTSWIDGET_SURFACE_CHOICESWidgetButton—filled·outline·rowWidgetDensity—comfortable·compactWidgetImageSize—large·medium·smallWidgetLook—accent·button·columns·density·facts·imageSize·maxSelect·platform·preset·radius·select·surfaceWidgetMode—dark·lightWidgetPalette—border·mode·muted·onAccent·surface·textWidgetPlatform—auto·ios·material·oneuiWidgetPreset—cards·compact·gallery·list·spotlightWidgetSelect—many·oneWidgetSelectAction—action·args·path·sayWidgetStyle—accent·button·columns·density·facts·imageSize·maxSelect·platform·preset·radius·select·surfacewidgetSurfaceWidgetSurface—card·screen·sheet

