SDK Quickstart

Applies to: ChengOS v0.2.0+ | Last verified: 2026-09-25 | Source: chengflow-sdk/src/sdk, chengflow-sdk/src/hooks/use-channel.ts, chengflow-sdk/package.json, chengflow-sdk/src/sdk/ws-client.ts

The gateway SDK (@chengflow/chat) puts a ChengOS-backed chat interface into a web application. It comes in three layers, and you pick how much you want:

LayerWhat it gives youUse when
Core SDKFramework-agnostic TypeScript: REST client, session manager, WebSocket clientVue, Svelte, vanilla, or your own state layer
React hooksuseChannel, useWebSocket, useConversationReact, with your own UI
React componentsChatProvider, ChatWindow, MessageList, InputBarReact, want it working today

Read the gateway model first — the four identifiers below only make sense against it.

Install

npm install @chengflow/chat

The published release is @chengflow/[email protected]. It is ESM-only ("type": "module", import conditions only — no CommonJS build) and needs react and react-dom 18 or newer as peer dependencies. It has three entry points:

ImportContains
@chengflow/chatEverything: the core SDK, React hooks, and components
@chengflow/chat/sdkThe framework-free core only — no React in your bundle
@chengflow/chat/style.cssStyles for the bundled components; import it once when you use them

The 0.1.0 package renders assistant replies as Markdown. Math (KaTeX) and the other rich-content formatting in the source tree arrive with the next published release; do not rely on them from npm yet.

Configure

Everything starts from one config object:

import type { ChannelConfig } from "@chengflow/chat";

const config: ChannelConfig = {
  apiBaseUrl: "https://your-server.example.com/api/v1",
  wsBaseUrl: "wss://your-server.example.com/ws/executions",
  workspaceId: "<workspace-uuid>",   // required, always explicit
  channelId: "weather-app",          // where messages come from
  boundWorkflowId: "<workflow-uuid>",// what runs
  externalUserId: "user-123",        // optional
};

workspaceId is required and validated — an empty value raises VALIDATION_ERROR rather than silently defaulting, because a wrong workspace would mean cross-tenant routing.

Keep channelId and boundWorkflowId distinct. The first identifies the app; the second selects the workflow.

The fastest path: components

import { ChatProvider, ChatWindow } from "@chengflow/chat";
import "@chengflow/chat/style.css";

export function App() {
  return (
    <ChatProvider config={config}>
      <ChatWindow />
    </ChatProvider>
  );
}

That is a working chat UI with streaming, history, and approval cards.

Your own UI: the hook

import { useChannel } from "@chengflow/chat";

function Chat() {
  const {
    messages,
    sendMessage,
    isLoading,
    connectionStatus,
    streamingContent,
    supportsAttachments,
    error,
  } = useChannel(config);

  return (
    <div>
      <span>{connectionStatus}</span>
      {messages.map((m) => (
        <div key={m.id}>{m.content}</div>
      ))}
      {streamingContent && <div>{streamingContent}</div>}
      <button disabled={isLoading} onClick={() => sendMessage("Hello")}>
        Send
      </button>
      {error && <p>{error.message}</p>}
    </div>
  );
}

useChannel runs the whole send-and-await cycle: it creates an optimistic local message, calls the execute API, subscribes to the conversation and execution scopes, consumes WebSocket events, and updates message state.

Render streamingContent separately from messages. It holds the partially-received response while tokens are still arriving; when the turn completes, the finished text lands in messages. Treating them as one list makes text flicker or duplicate.

supportsAttachments is a capability probe: it reports whether the bound workflow actually contains an io/file_upload node. Use it to show or hide your attachment button, rather than offering uploads the workflow will discard.

Human-in-the-loop

Three callbacks handle the cases where a run needs a person:

CallbackFor
submitApproval(messageId, decision, scope?, reason?)An approval request — approve or reject, optionally scoped, optionally with a reason
continueAgentReview(messageId, nextAction?)An agent paused for review
submitContextMemoryReview(messageId, updates, trimmingMode)Context/memory trimming decisions

These are not optional extras if your workflows use utils/approver or long agent runs — without them a run that asks for a decision waits indefinitely.

resetConversation() starts a fresh conversation, abandoning the current history.

Core SDK without React

import { ChannelClient, SessionManager, WsClient } from "@chengflow/chat/sdk";

const client = new ChannelClient(config);
const result = await client.execute(/* … */);

ChannelClient also exposes resolveConversation, getConversationMessages, createMessage, submitApproval, resumeExecution, getExecution, getExecutionResult, workflowSupportsAttachments, and bulkUpdateMemoryControls.

Authentication

The browser uses a JWT and only a JWT. A BrowserAuthSession supplies the token by default; pass your own AuthTokenProvider to the ChannelClient constructor to integrate an existing auth system. Expiry is surfaced through an auth-expired notification so you can re-authenticate instead of failing silently.

Do not use chk_ channel keys from a browser — they are for server-side adapters and would put a long-lived shared secret in client code. See authentication.

WebSocket behaviour

The bundled client handles reconnection with exponential backoff plus jitter, PING/PONG heartbeats, multi-scope subscriptions, automatic subscription restoration after a reconnect, a maximum retry limit, and close-code interpretation so a deliberate close is not retried like a network failure. You do not need to implement any of that.

The client also opens every connection with a protocol handshake: its first frame declares the wire protocol version (1.0) and the SDK version. A server that cannot speak that major version answers PROTOCOL_MISMATCH and closes; the client then stops reconnecting and reports status protocol_mismatch, so show an "update the SDK" message rather than a spinner. Servers that predate the handshake ignore the frame, so a newer SDK keeps working against them.

Deployment

Docker deployments configure the gateway through APP_API_BASE_URL, APP_WS_URL, APP_CHANNEL_ID, APP_BOUND_WORKFLOW_ID, and PUBLIC_APP_URL — see the environment reference.

Next steps