assistant-cloud

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:

MemberCalls
threadslist({ 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.messageslist(threadId, { format?, limit?, after? }), create(threadId, { parent_id, format, content }), update(threadId, messageId, { content }), feedback(threadId, messageId, { type })
runsreport(report), stream({ thread_id, assistant_id, messages })
eventstrack({ 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
scorescreate({ name, data_type, value?, string_value?, comment?, thread_id?, message_id?, run_id? })
filesgeneratePresignedUploadUrl({ filename, content_type?, content_length? }), generatePresignedDownloadUrl({ key } | { url })
auth.tokenscreate(), a user token minted with an API key
projects.threadsthe project wide thread and message reads of an API key
telemetrythe 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

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

AssistantCloudEvents
constructor?(cloud: AssistantCloudAPI, isEnabled: () => boolean) => AssistantCloudEvents

track?(event: AssistantCloudEvent) => void

dispose?() => void

AssistantCloudScores

AssistantCloudScores
constructor?(cloud: AssistantCloudAPI) => AssistantCloudScores

create?(body: AssistantCloudScoreBody) => Promise<AssistantCloudScoreResponse>

CloudAPIError

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.

CloudEngagementReporter
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.

CloudMessagePersistence
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

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.

CloudRunReporter
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

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.

createRunTelemetryToolCall
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.

deriveRunOutcome
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.

describeRunError
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.

extractRunTelemetryModelId
metadataRecord<string, unknown>

generateThreadTitle

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.

normalizeRunTelemetryUsage
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.

truncateRunTelemetryText
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
wrapSamplingHandler
handlerMcpSamplingHandler

onSamplingCall(data: SamplingCallData) => void