Hooks

useDocyrusAgentChat

The complete streaming chat runtime for a Docyrus AI agent — AI SDK transport, threads, attachment uploads and client-side tool execution — ready to drive DocyrusAgent.

iOSAndroidExpo Go

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-agent-chat
Required Packages(4 packages)
pnpm add ai @ai-sdk/react zod @tanstack/react-query

Native-first hook (platform-neutral name, so web can adopt it later) that extracts the transport and tool loop the web EditorAgent builds inline. It wires @ai-sdk/react useChat + ai DefaultChatTransport to ${apiBaseUrl}/ai/agents/${agentId}/chat with a per-request bearer token, embeds useDocyrusAgentThreads and useDocyrusAgentAttachments, sends the last maxHistoryLength messages (file parts stripped) with threadId, modelId, files, supportFiles, featureFlags, pageDataSourceId, additionalContext and your extraBody, and auto-executes registered client tools (results go back with addToolOutput; sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls continues the turn).

Streaming on React Native

No polyfills are needed on Expo SDK 57: its WinterCG runtime (expo/src/winter) replaces the global fetch with the streaming expo/fetch and installs TextDecoderStream, TextEncoderStream and structuredClone (Metro injects ReadableStream). If your app sets EXPO_PUBLIC_USE_RN_FETCH=1 or is a bare React Native app, pass a streaming implementation through the fetch option (e.g. import { fetch } from 'expo/fetch') and install those globals yourself (@ungap/structured-clone, @stardazed/streams-text-encoding).

Host singletons

ai and @ai-sdk/react are not host singletons: the Chat instance lives inside the hook and only plain UIMessage data crosses the boundary (the same classification as on web), so the CLI installs them as regular dependencies. ai needs zod (^3.25.76 || ^4.1.8) as a peer.

Usage

import { useDocyrusAuth } from '@docyrus/signin/react-native';

import { DocyrusAgent, DocyrusAgentThreadsSidebar } from '@/components/docyrus-native/docyrus-agent';
import { useDocyrusAgentChat } from '@/hooks/docyrus-native/use-docyrus-agent-chat';
import { useDocyrusAgentInfo } from '@/hooks/docyrus-native/use-docyrus-agent-info';

export function AssistantScreen() {
  const { client, user } = useDocyrusAuth();
  const info = useDocyrusAgentInfo({ client, agentId: AGENT_ID });
  const chat = useDocyrusAgentChat({
    agentId: AGENT_ID,
    apiBaseUrl: 'https://api.docyrus.com/v1',
    getAccessToken: () => client?.getAccessToken() ?? null,
    client,
    userId: user?.id,
    clientTools: [{ name: 'open_record', execute: input => router.push(`/records/${(input as { id: string }).id}`) }]
  });

  if (!info.agent) return null;

  return (
    <DocyrusAgent
      agent={info.agent}
      suggestions={info.suggestions}
      capabilities={info.capabilities}
      allowAttachments={info.capabilities.supportFiles}
      messages={chat.messages}
      chatStatus={chat.chatStatus}
      onSendMessage={chat.sendMessage}
      onStopGeneration={chat.stop} />
  );
}

API Reference

UseDocyrusAgentChatArgs

OptionTypeDefaultDescription
agentIdstring-Tenant AI agent ID (required)
apiBaseUrlstring-API base URL including /v1 (required)
getAccessToken() => string | null | undefined | Promise<…>-Bearer token, called on every request
clientDocyrusAgentChatClient | null | undefined-REST client (get / post / patch / delete) for threads + uploads (required)
userIdstring | nullnullRestrict the thread list to this user
projectIdstring | nullnullScope new threads + the thread list to a project
deploymentIdstring | nullnullScope new threads to a deployment
senderNamestring'User'sender_name of new threads
extraBody() => Record<string, unknown> | null | undefined-Extra request-body fields (called per send)
additionalContext() => string | null | undefined-Per-turn system-prompt context (called per send)
dataSourceIdstring | nullnullSent as pageDataSourceId
clientToolsReadonlyArray<DocyrusAgentClientTool>-{ name, execute(input) } tools run on the device
modelIdstring'default'Model id sent to the backend
maxHistoryLengthnumber10Trailing messages sent as history
fetchtypeof fetchglobal fetchStreaming fetch implementation
throttlenumber50useChat UI update throttle (ms)
enableThreadsbooleantrueLoad the thread list

UseDocyrusAgentChatResult

FieldTypeDescription
messagesUIMessage[]Chat messages
chatStatusChatStatus'ready' | 'submitted' | 'streaming' | 'error'
errorError | undefinedLast transport error
sendMessage(payload: AgentMessagePayload) => Promise<void>Ensures a thread, uploads files, sends — pass to onSendMessage
stop() => voidAbort the stream — pass to onStopGeneration
regenerate() => Promise<void>Regenerate the last reply
newThread() => voidClear and start a new thread
selectThread(thread | id) => Promise<void>Load a thread's messages (its tool calls are never re-executed)
deleteThread(thread | id) => Promise<boolean>Delete; resets when it was active
renameThread(thread | id, subject) => Promise<boolean>Rename
activeThreadIdstring | nullCurrent thread
setMessages(messages: UIMessage[]) => voidReplace messages
threadsUseDocyrusAgentThreadsResultThread list + loading flags (for DocyrusAgentThreadsSidebar)

DocyrusAgentClientTool

FieldTypeDescription
namestringTool key — must match the tenant_ai_tool.key registered to the agent
execute(input: unknown) => unknown | Promise<unknown>Local handler; a thrown error is returned as { ok: false, error: { message } }

On this page