The assistant-cloud client for threads, messages, runs, events, scores and files, with the reporters, persistence and run report helpers an integration is built from.
assistant-cloud is the client the assistant-ui runtimes drive and the building blocks an integration of your own is made of. It runs in the browser, in React Native, in Node and on the edge, and its only dependency is assistant-stream. @assistant-ui/react re-exports AssistantCloud and readAnonymousRefreshToken. The guides are under Assistant Cloud; the wire protocol is the REST API.
The client
new AssistantCloud(config) takes one of the three authentication modes, { baseUrl, anonymous: true }, { baseUrl, authToken } or, on a server only, { apiKey, userId, workspaceId }, and an optional telemetry configuration: enabled, events, environment, release, tags and beforeReport, with telemetry: false as shorthand for off. Its members are one sub client per resource:
| Member | Calls |
|---|---|
threads | list({ is_archived?, limit?, after? }), get(id), create({ last_message_at, title?, metadata?, external_id? }), update(id, { title?, last_message_at?, metadata?, is_archived? }), delete(id), claim({ refresh_token }) |
threads.messages | list(threadId, { format?, limit?, after? }), create(threadId, { parent_id, format, content }), update(threadId, messageId, { content }), feedback(threadId, messageId, { type }) |
runs | report(report), stream({ thread_id, assistant_id, messages }) |
events | track({ kind, thread_id?, message_id?, run_id?, value?, props? }); events are batched and flushed after two seconds of quiet, at twenty waiting and when the page hides |
scores | create({ name, data_type, value?, string_value?, comment?, thread_id?, message_id?, run_id? }) |
files | generatePresignedUploadUrl({ filename, content_type?, content_length? }), generatePresignedDownloadUrl({ key } | { url }) |
auth.tokens | create(), a user token minted with an API key |
projects.threads | the project wide thread and message reads of an API key |
telemetry | the normalized telemetry configuration |
registerSdk({ name, version }) | adds an integration's identity to the Aui-Sdk header; the runtimes call it for you |
Every call returns a promise and throws CloudAPIError on a non 2xx response, with status, code (the response's error field, for instance plan_limit_reached) and details, or CloudResponseError when a response does not have the expected shape.
Building an integration
CloudRunReporter and CloudEngagementReporter are the two reporters every integration shares, createRunReport builds the wire body from a RunReportInit, and createFormattedPersistence over CloudMessagePersistence stores messages in a format of your choice. assistant-cloud/ai-sdk supplies the AI SDK format adapter and telemetry extraction, assistant-cloud/telemetry the OpenTelemetry exporter. The package README walks through how they fit together.
API Reference
AssistantCloud
- constructor?(config: AssistantCloudConfig) => AssistantCloud
- threads?AssistantCloudThreads
- projects?AssistantCloudProjects
- auth?{ tokens: AssistantCloudAuthTokens; }
- runs?AssistantCloudRuns
- files?AssistantCloudFiles
- events?AssistantCloudEvents
- scores?AssistantCloudScores
- telemetry?AssistantCloudTelemetryConfig
- registerSdk?(sdk: SdkIdentity) => void
AssistantCloudEvents
- constructor?(cloud: AssistantCloudAPI, isEnabled: () => boolean) => AssistantCloudEvents
- track?(event: AssistantCloudEvent) => void
- dispose?() => void
AssistantCloudScores
- constructor?(cloud: AssistantCloudAPI) => AssistantCloudScores
- create?(body: AssistantCloudScoreBody) => Promise<AssistantCloudScoreResponse>
CloudAPIError
- constructor?(message: string, status: number, code?: string, details?: Record<string, unknown>) => CloudAPIError
- status?number
- code?string
- details?Record<string, unknown>
CloudEngagementReporter
Derives engagement events from what a chat integration observes and keeps the per thread state the events need: a run's start for the stop duration, a run's end for the time to the next message, one error and one suggestion list per run or thread. A started run is kept until it ends or stops; the rest is kept for the 256 most recently touched threads, so a long session does not grow it without bound. Delivery goes through the cloud's event buffer, so a disabled telemetry setting drops everything here as well.
- constructor?(cloud: AssistantCloud | (() => AssistantCloud), resolveIds: EngagementIdResolver = passThroughIds) => CloudEngagementReporter
- runStarted?(threadId: string) => void
- runEnded?(threadId: string) => void
- runStopped?(threadId: string) => void
- messageSent?(threadId: string, init: { messageId?: string | undefined; chars: number; attachments: number; }) => void
- messageEdited?(threadId: string, init: { messageId: string; chars: number }) => void
- messageRegenerated?(threadId: string, messageId?: string) => void
- errorShown?(threadId: string, init: { messageId?: string | undefined; reason: string }) => void
- suggestionsShown?(threadId: string, count: number) => void
- suggestionClicked?(threadId: string) => void
- attachmentAdded?(threadId: string, init: { messageId?: string | undefined; contentType?: string | undefined }) => void
- attachmentFailed?(threadId: string, init: { messageId?: string | undefined; contentType?: string | undefined }) => void
- voiceStarted?(threadId: string) => void
- speechStarted?(threadId: string, messageId?: string) => void
- branchSwitched?(threadId: string, messageId?: string) => void
- messageCopied?(threadId: string, messageId?: string) => void
- threadSwitched?(threadId: string) => void
CloudMessagePersistence
Appends, updates and loads cloud messages while mapping local ids to cloud ids and chaining parent_id. A parent that is still being created is awaited, so concurrent appends land under the right parent.
- constructor?(cloud: AssistantCloud | (() => AssistantCloud)) => CloudMessagePersistence
- append?(threadId: string, messageId: string, parentId: string | null, format: string, content: ReadonlyJSONObject) => Promise<void>
- update?(threadId: string, messageId: string, _format: string, content: ReadonlyJSONObject) => Promise<void>
- isPersisted?(messageId: string) => boolean
- getRemoteId?(messageId: string) => Promise<string | undefined>
- getResolvedRemoteId?(messageId: string) => string | undefined
- load?(threadId: string, format?: string) => Promise<CloudMessage[]>
- reset?() => void
CloudResponseError
- constructor?(message: string) => CloudResponseError
CloudRunReporter
Sends run reports the way every client integration has to: nothing while
telemetry is off, the cloud's environment, release and tags on every report,
the beforeReport hook applied last, and a failed send that never surfaces.
A report given a key is sent once per key, so an integration that observes
the same finished run twice reports it once.
- constructor?(cloud: AssistantCloud | (() => AssistantCloud)) => CloudRunReporter
- report?(init: CloudRunReportInit, key?: string) => Promise<void>
createFormattedPersistence
Wraps a CloudMessagePersistence with a MessageFormatAdapter's encode and decode. The persistence parameter is typed structurally, so a caller does not need to import the class.
const createFormattedPersistence: <TMessage, TStorageFormat>(persistence: { append: (threadId: string, messageId: string, parentId: string | null, format: string, content: ReadonlyJSONObject) => Promise<void>; load: (threadId: string, format?: string) => Promise<any[]>; isPersisted: (messageId: string) => boolean; update?: (threadId: string, messageId: string, format: string, content: ReadonlyJSONObject) => Promise<void>; }, adapter: MessageFormatAdapter<TMessage, TStorageFormat>) => { append: (threadId: string, item: { parentId: string | null; message: TMessage; }) => Promise<void>; update: ((threadId: string, item: { parentId: string | null; message: TMessage; }, messageId: string) => Promise<void>) | undefined; load: (threadId: string) => Promise<{ messages: { parentId: string | null; message: TMessage; }[]; }>; isPersisted: (messageId: string) => boolean; };createRunReport
- initRunReportInit
- RunReportInit
- threadIdstring
- statusAssistantCloudRunReport["status"]
- outcome?RunReportOutcome | undefined
- errorCode?string | undefined
- error?string | undefined
- messageId?string | undefined
- traceId?string | undefined
- modelId?string | undefined
- provider?string | undefined
- usage?RunTelemetryUsageInit | undefined
- RunReportInit["usage"]
- inputTokens?number | undefined
- outputTokens?number | undefined
- reasoningTokens?number | undefined
- cachedInputTokens?number | undefined
- promptTokens?number | undefined
- completionTokens?number | undefined
- inputTokenDetails?RunReportInit["usage"]["inputTokenDetails"]
- RunReportInit["usage"]["inputTokenDetails"]
- cacheReadTokens?number
- outputTokenDetails?RunReportInit["usage"]["outputTokenDetails"]
- RunReportInit["usage"]["outputTokenDetails"]
- reasoningTokens?number
- steps?RunReportStepInit[] | undefined
- totalSteps?number | undefined
- toolCalls?AssistantCloudRunReportToolCall[] | undefined
- durationMs?number | undefined
- firstTokenMs?number | undefined
- outputText?string | undefined
- metadata?Record<string, unknown> | undefined
- telemetry?RunReportInit["telemetry"]
- RunReportInit["telemetry"]
- environment?string | undefined
- release?string | undefined
- tags?readonly string[] | undefined
createRunTelemetryToolCall
Serializes one tool call into the shape the runs endpoint accepts. An mcp
source has its result summarized, because MCP content blocks carry inline
base64 image and audio payloads that would otherwise dominate the report.
- initRunTelemetryToolCallInit
- RunTelemetryToolCallInit
- toolNamestring
- toolCallIdstring
- args?unknown
- argsText?string | undefined
Pre-serialized arguments, used in place of serializing `args`. Values over the span size are clamped before they are included in the report.
- result?unknown
- toolSource?"mcp" | "frontend" | "backend" | undefined
createSamplingCollector
Creates a collector that accumulates sampling call data during tool execution.
Use with wrapSamplingHandler to capture all sampling calls for a tool invocation.
const collector = createSamplingCollector();
const wrappedHandler = wrapSamplingHandler(handler, collector.collect);
// ... execute MCP tool ...
const calls = collector.getCalls(); // SamplingCallData[]function createSamplingCollector(): { collect: (data: SamplingCallData) => number; getCalls: () => SamplingCallData[]; reset: () => void; };deriveRunOutcome
Maps a finish event to the report status and outcome. fallbackStatus
applies when the event carries neither a finish reason nor a failure flag.
- input{ finishReason?: string | undefined; isAbort?: boolean | undefined; isDisconnect?: boolean | undefined; isError?: boolean | undefined; }
- { finishReason?: string | undefined; isAbort?: boolean | undefined; isDisconnect?: boolean | undefined; isError?: boolean | undefined; }
- finishReason?string | undefined
- isAbort?boolean | undefined
- isDisconnect?boolean | undefined
- isError?boolean | undefined
- fallbackStatus?"incomplete" | "completed"
describeRunError
Reads the message and code the runs endpoint stores for a failed run. The
code is the error's code when it has one, else its class name.
- errorunknown
extractRunTelemetryModelId
Resolves the model ID a run reports, in the order an app can supply it: an
explicit modelId, the custom bag, then the per-step response.modelId
that a messageMetadata callback copies off the AI SDK's finish-step part.
The AI SDK puts no model ID on a UI message part, so message metadata is the
only channel one arrives on.
- metadataRecord<string, unknown>
generateThreadTitle
- cloudAssistantCloud
- AssistantCloud
- threadsAssistantCloudThreads
- AssistantCloudThreads
- messagesAssistantCloudThreadMessages
- AssistantCloudThreadMessages
- cloudAssistantCloudAPI
- list(threadId: string, query?: AssistantCloudThreadMessageListQuery) => Promise<AssistantCloudThreadMessageListResponse>
- create(threadId: string, body: AssistantCloudThreadMessageCreateBody) => Promise<AssistantCloudMessageCreateResponse>
- update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody) => Promise<void>
- feedback(threadId: string, messageId: string, body: AssistantCloudThreadMessageFeedbackBody) => Promise<AssistantCloudThreadMessageFeedbackResponse>
- cloudAssistantCloudAPI
- AssistantCloudAPI
- _authAssistantCloudAuthStrategy
- _baseUrlstring
- registerSdk(sdk: SdkIdentity) => void
- sdkHeader() => string
- initializeAuth() => Promise<boolean>
- makeRawRequest(endpoint: string, options?: MakeRequestOptions) => Promise<Response>
- makeRequest(endpoint: string, options?: MakeRequestOptions) => Promise<any>
- list(query?: AssistantCloudThreadsListQuery) => Promise<AssistantCloudThreadsListResponse>
- get(threadId: string) => Promise<CloudThread>
- create(body: AssistantCloudThreadsCreateBody) => Promise<AssistantCloudThreadsCreateResponse>
- update(threadId: string, body: AssistantCloudThreadsUpdateBody) => Promise<void>
- claim(body: AssistantCloudThreadsClaimBody) => Promise<AssistantCloudThreadsClaimResponse>
Moves every thread of the anonymous identity behind `refresh_token` into the caller's workspace.
- delete(threadId: string) => Promise<void>
- projectsAssistantCloudProjects
- AssistantCloudProjects
- threadsAssistantCloudProjectThreads
- AssistantCloudProjectThreads
- messagesAssistantCloudProjectThreadMessages
- cloudAssistantCloudAPI
- list(query?: AssistantCloudProjectThreadsListQuery) => Promise<AssistantCloudProjectThreadsListResponse>
- auth__object
- __object
- tokensAssistantCloudAuthTokens
- AssistantCloudAuthTokens
- cloudAssistantCloudAPI
- create() => Promise<AssistantCloudAuthTokensCreateResponse>
- runsAssistantCloudRuns
- AssistantCloudRuns
- cloudAssistantCloudAPI
- AssistantCloudAPI
- _authAssistantCloudAuthStrategy
- _baseUrlstring
- registerSdk(sdk: SdkIdentity) => void
- sdkHeader() => string
- initializeAuth() => Promise<boolean>
- makeRawRequest(endpoint: string, options?: MakeRequestOptions) => Promise<Response>
- makeRequest(endpoint: string, options?: MakeRequestOptions) => Promise<any>
- stream(body: AssistantCloudRunsStreamBody) => Promise<AssistantStream>
- report(body: AssistantCloudRunReport) => Promise<{ run_id: string; }>
- filesAssistantCloudFiles
- AssistantCloudFiles
- cloudAssistantCloudAPI
- AssistantCloudAPI
- _authAssistantCloudAuthStrategy
- _baseUrlstring
- registerSdk(sdk: SdkIdentity) => void
- sdkHeader() => string
- initializeAuth() => Promise<boolean>
- makeRawRequest(endpoint: string, options?: MakeRequestOptions) => Promise<Response>
- makeRequest(endpoint: string, options?: MakeRequestOptions) => Promise<any>
- pdfToImages(body: PdfToImagesRequestBody) => Promise<PdfToImagesResponse>
- generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody) => Promise<GeneratePresignedUploadUrlResponse>
- generatePresignedDownloadUrl(body: { key: string; } | { url: string; }) => Promise<GeneratePresignedDownloadUrlResponse>
- eventsAssistantCloudEvents
- AssistantCloudEvents
- bufferAssistantCloudEvent[]
- timer?ReturnType<typeof setTimeout> | undefined
- AssistantCloudEvents["timer"]
- close() => Timeout
Cancels the timeout.
- hasRef() => boolean
If true, the `Timeout` object will keep the Node.js event loop active.
- ref() => Timeout
When called, requests that the Node.js event loop _not_ exit so long as the `Timeout` is active. Calling `timeout.ref()` multiple times will have no effect. By default, all `Timeout` objects are "ref'ed", making it normally unnecessary to call `timeout.ref()` unless `timeout.unref()` had been called previously.
- refresh() => Timeout
Sets the timer's start time to the current time, and reschedules the timer to call its callback at the previously specified duration adjusted to the current time. This is useful for refreshing a timer without allocating a new JavaScript object. Using this on a timer that has already called its callback will reactivate the timer.
- unref() => Timeout
When called, the active `Timeout` object will not require the Node.js event loop to remain active. If there is no other activity keeping the event loop running, the process may exit before the `Timeout` object's callback is invoked. Calling `timeout.unref()` multiple times will have no effect.
- _onTimeout(...args: any[]) => void
- flushing?Promise<void> | undefined
- AssistantCloudEvents["flushing"]
- then<TResult1, TResult2>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
- catch<TResult>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined) => Promise<void | TResult>
- finally(onfinally?: (() => void) | null | undefined) => Promise<void>
- cloudAssistantCloudAPI
- AssistantCloudAPI
- _authAssistantCloudAuthStrategy
- _baseUrlstring
- registerSdk(sdk: SdkIdentity) => void
- sdkHeader() => string
- initializeAuth() => Promise<boolean>
- makeRawRequest(endpoint: string, options?: MakeRequestOptions) => Promise<Response>
- makeRequest(endpoint: string, options?: MakeRequestOptions) => Promise<any>
- isEnabled() => boolean
- listeningboolean
- track(event: AssistantCloudEvent) => void
- listen() => void
- unlisten() => void
- dispose() => void
- onVisibilityChange() => void
- flush() => Promise<void>
- flushPending() => Promise<void>
- scheduleFlush() => void
- clearFlushTimer() => void
- scoresAssistantCloudScores
- AssistantCloudScores
- cloudAssistantCloudAPI
- AssistantCloudAPI
- _authAssistantCloudAuthStrategy
- _baseUrlstring
- registerSdk(sdk: SdkIdentity) => void
- sdkHeader() => string
- initializeAuth() => Promise<boolean>
- makeRawRequest(endpoint: string, options?: MakeRequestOptions) => Promise<Response>
- makeRequest(endpoint: string, options?: MakeRequestOptions) => Promise<any>
- create(body: AssistantCloudScoreBody) => Promise<AssistantCloudScoreResponse>
- telemetryAssistantCloudTelemetryConfig
- AssistantCloudTelemetryConfig
- enabled?boolean
Enables Assistant Cloud telemetry. Defaults to `true`. Set to `false` to disable both run reports and engagement events.
- events?boolean
Enables Assistant Cloud engagement events. Defaults to `true` when telemetry is enabled. Set to `false` to keep run reports while disabling engagement events.
- release?string
- environment?string
- tags?string[]
- beforeReport?( report: AssistantCloudRunReport, ) => AssistantCloudRunReport | null
Called before each telemetry report is sent. Return a modified report to enrich it (e.g. add `model_id`), or return `null` to skip the report.
- registerSdk(sdk: SdkIdentity) => void
- options{ threadId: string; messages: readonly { role: string; content: readonly { type: "text"; text: string; }[]; }[]; }
- { threadId: string; messages: readonly { role: string; content: readonly { type: "text"; text: string; }[]; }[]; }
- threadIdstring
- messagesreadonly { role: string; content: readonly { type: "text"; text: string }[]; }[]
normalizeRunTelemetryUsage
Resolves the token counts a provider reports under any of the names the AI SDK has used: the current top-level ones, the legacy prompt/completion pair, and the v7 token detail objects. Returns undefined when no count is present, so callers can tell an empty usage object from a zeroed one.
- usageRunTelemetryUsageInit
- RunTelemetryUsageInit
- inputTokens?number | undefined
- outputTokens?number | undefined
- reasoningTokens?number | undefined
- cachedInputTokens?number | undefined
- promptTokens?number | undefined
- completionTokens?number | undefined
- inputTokenDetails?RunTelemetryUsageInit["inputTokenDetails"]
- RunTelemetryUsageInit["inputTokenDetails"]
- cacheReadTokens?number
- outputTokenDetails?RunTelemetryUsageInit["outputTokenDetails"]
- RunTelemetryUsageInit["outputTokenDetails"]
- reasoningTokens?number
readAnonymousRefreshToken
The refresh token of the anonymous identity this browser holds for baseUrl, or null when it has none.
const readAnonymousRefreshToken: (baseUrl: string) => string | null;truncateRunTelemetryText
Clamps a string to the size the runs endpoint accepts for a single span field.
- valuestring
wrapSamplingHandler
Wraps an MCP sampling handler to intercept and measure sampling calls.
const samplingCalls: SamplingCallData[] = [];
const wrapped = wrapSamplingHandler(
originalHandler,
(data) => samplingCalls.push(data),
);
// Use `wrapped` as the MCP client's sampling handler
// After tool execution, `samplingCalls` contains metrics for all nested LLM calls- handlerMcpSamplingHandler
- onSamplingCall(data: SamplingCallData) => void