Back to Copilotkit

Thread

showcase/shell-docs/src/content/reference/bot/classes/Thread.mdx

1.60.16.5 KB
Original Source

Overview

A Thread is the per-conversation handle passed to every handler, tool context, and interaction context. It posts UI (JSX from the component vocabulary or plain strings), drives the agent run loop, resolves human-in-the-loop choices, and exposes platform power through capability-gated methods that degrade gracefully on surfaces that don't support them.

ts
interface Thread {
  readonly platform: string;
  post(ui: Renderable): Promise<MessageRef>;
  update(ref: MessageRef, ui: Renderable): Promise<MessageRef>;
  delete(ref: MessageRef): Promise<void>;
  stream(src: string | AsyncIterable<string>): Promise<MessageRef>;
  runAgent(input?: {
    context?: ContextEntry[];
    tools?: BotTool[];
    prompt?: string;
  }): Promise<MessageRef | undefined>;
  resume(value: unknown): Promise<MessageRef | undefined>;
  awaitChoice<T = unknown>(ui: Renderable): Promise<T>;
  getMessages(): Promise<ThreadMessage[]>;
  lookupUser(query: string): Promise<PlatformUser | undefined>;
  postFile(args: {
    bytes: Uint8Array;
    filename: string;
    title?: string;
    altText?: string;
  }): Promise<{ ok: boolean; fileId?: string; error?: string }>;
}

Properties

<PropertyReference name="platform" type="string" required> The surface this conversation lives on, e.g. `"slack"`. </PropertyReference>

Methods

<PropertyReference name="post" type="(ui: Renderable) => Promise<MessageRef>"> Render and post a message. JSX is rendered to the [BotNode IR](/reference/bot/types/BotNode), every event-prop handler in the tree is **bound** (minted a content-stable id, snapshotted, rewritten to the bare id), and the IR is handed to the adapter. Returns a `MessageRef` for later `update` / `delete`. </PropertyReference> <PropertyReference name="update" type="(ref: MessageRef, ui: Renderable) => Promise<MessageRef>"> Re-render an existing message in place — e.g. flipping a confirmation card to its approved state from a button's `onClick`. </PropertyReference> <PropertyReference name="delete" type="(ref: MessageRef) => Promise<void>"> Delete a posted message. </PropertyReference> <PropertyReference name="stream" type="(src: string | AsyncIterable<string>) => Promise<MessageRef>"> Post a placeholder and edit it in place as the source yields text. On Slack this is throttled `chat.update` with multi-message chunking — see [slack()](/reference/bot/slack). </PropertyReference> <PropertyReference name="runAgent" type="(input?) => Promise<MessageRef | undefined>"> Resolve the conversation's agent session, create the adapter's run renderer, and drive the run/tool/interrupt loop: streamed text is rendered into the thread, registered [tools](/reference/bot/functions/defineBotTool) are executed when the agent calls them, and captured interrupts are dispatched to `onInterrupt` handlers. <PropertyReference name="context" type="ContextEntry[]"> Extra context entries merged on top of the bot-level `context` for this run only. </PropertyReference> <PropertyReference name="tools" type="BotTool[]"> Extra tools merged on top of the bot-level `tools` for this run only. </PropertyReference> <PropertyReference name="prompt" type="string"> A user message injected before running. Use when the input isn't already in the history the adapter reconstructs — e.g. a slash command's text, which is never posted to the channel. </PropertyReference> </PropertyReference> <PropertyReference name="resume" type="(value: unknown) => Promise<MessageRef | undefined>"> Re-enter a paused interrupt run, forwarding `value` to the agent (sent as `forwardedProps.command = { resume: value }` — the LangGraph `Command` shape). Typically called from a picker button's `onClick` inside an `onInterrupt` handler. </PropertyReference> <PropertyReference name="awaitChoice" type="<T>(ui: Renderable) => Promise<T>"> Post a picker and **block** until an interaction in this conversation resolves it — the human-in-the-loop primitive. Resolves to the clicked control's `value`; pass `T` to type it. See the [Button](/reference/bot/components/Button) page for a full confirm-card example. </PropertyReference> <PropertyReference name="getMessages" type="() => Promise<ThreadMessage[]>"> Read the conversation's messages — each a `ThreadMessage` (`{ user?, text, ts?, isBot? }`). **Capability-gated**: returns `[]` when the adapter can't read history. On Slack, backed by `conversations.replies`. </PropertyReference> <PropertyReference name="lookupUser" type="(query: string) => Promise<PlatformUser | undefined>"> Resolve a platform user from a free-form query (name, handle, email). **Capability-gated**: returns `undefined` when unsupported. </PropertyReference> <PropertyReference name="postFile" type="(args: { bytes: Uint8Array; filename: string; title?: string; altText?: string }) => Promise<{ ok: boolean; fileId?: string; error?: string }>"> Upload a file into the conversation. **Capability-gated**: resolves `{ ok: false, error }` when the surface doesn't support uploads. On Slack, backed by `files.uploadV2`. </PropertyReference>

Usage

tsx
bot.onMention(async ({ thread, message }) => {
  // Run the agent with extra per-run context:
  await thread.runAgent({
    context: [
      { description: "Requesting user", value: message.user.name ?? message.user.id },
    ],
  });
});
tsx
// Inside a tool: read the thread, then block on approval.
async handler({ summary }, { thread }) {
  const choice = await thread.awaitChoice<{ confirmed: boolean }>(
    <ConfirmWrite action={summary} />,
  );
  return choice ?? { confirmed: false }; // serialized for the agent automatically
}

Behavior

  • Capability gating keeps tools portablegetMessages / lookupUser / postFile delegate to the adapter when supported and degrade gracefully ([] / undefined / { ok: false }) when not, so the same tool runs on any surface.
  • Per-run mergingrunAgent's tools and context apply to that run only, layered on top of the bot-level defaults.
  • History reconstruction — on Slack, the conversation's agent.messages are rebuilt from Slack history each turn; the platform is the source of truth, so bot restarts don't lose conversations.
  • createBot — where handlers receive a Thread
  • defineBotTool — tools receive the Thread in ctx.thread
  • Button — interactive messages and awaitChoice
  • slack() — how the Slack adapter backs these methods