Connect an existing Vercel AI SDK React chat to a published Awaken Agent. You will send one recognizable task, render its native UI Message Stream, and read the same thread from committed history.
Goal
Finish with a useChat component that sends a stable threadId to Awaken on
every turn. The browser displays the live stream; Awaken’s history endpoint
remains the durable record to reload after a refresh or process restart.
Prerequisites
- Run
awakenand complete self-hosting setup. - Publish a runnable model and, if the UI targets one saved Agent, publish that Agent first.
- Use a Node.js React project. The pinned Awaken Console source declares the
ai7.x and@ai-sdk/react4.x package lines used by this example. - Decide whether browser and API share an origin. For a cross-origin local setup, configure CORS at a reverse proxy before opening the page.
1. Choose the run endpoint
Use the default route while proving the UI connection:
http://localhost:8080/v1/ai-sdk/chat
To run one published Agent, use its scoped route instead:
http://localhost:8080/v1/ai-sdk/agents/<agent_id>/runs
Keep the complete route and event catalog in the AI SDK protocol reference. This page needs only the endpoint your component will call.
2. Install the client packages
npm install ai@7 @ai-sdk/react@4
3. Send the thread ID with each turn
Add a chat component. useChat({ id }) identifies client-side chat state, but
Awaken reads durable identity from the request body’s threadId. Use
prepareSendMessagesRequest to send both threadId and messages:
import { useState } from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
const threadId = "support-demo-1";
const transport = new DefaultChatTransport({
api: "http://localhost:8080/v1/ai-sdk/chat",
prepareSendMessagesRequest: ({ messages }) => ({
body: { threadId, messages },
}),
});
export default function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, status, error } = useChat({
id: threadId,
transport,
});
return (
<main>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>{" "}
{message.parts.map((part, index) =>
part.type === "text" ? <span key={index}>{part.text}</span> : null,
)}
</div>
))}
{error && <p role="alert">{error.message}</p>}
<form
onSubmit={(event) => {
event.preventDefault();
const text = input.trim();
if (!text || status === "submitted" || status === "streaming") return;
sendMessage({ text });
setInput("");
}}
>
<input
value={input}
onChange={(event) => setInput(event.target.value)}
aria-label="Message"
/>
<button type="submit">Send</button>
</form>
</main>
);
}
For a saved Agent, change only api to the scoped endpoint chosen in step 1.
Keep threadId stable for work that should continue in the same Session; choose
a new value when the user starts unrelated work.
4. Verify
-
Open the frontend and send a recognizable task, for example:
Summarize this note in one sentence: shipping is blocked by a missing credential. -
Confirm text appears while
statusisstreamingand the turn eventually leaves the streaming state. -
Read the same thread from Awaken:
curl -sS http://localhost:8080/v1/ai-sdk/threads/support-demo-1/messages -
Confirm the returned
itemsinclude the submitted note and the committed assistant response.
The history endpoint does not hydrate useChat automatically. On page load,
fetch this history and pass the projected messages into your application’s chat
state if refresh recovery is part of your product.
5. Move to a production boundary
The preceding URL is a local connection check. Do not give a browser a Workspace service API key or expose an unauthenticated local listener to the internet.
For an internet-facing application:
- Let your backend authenticate the user and create or resolve one Managed Session.
- Use the Console Protocols workflow to mint a short-lived application
access token limited to
ai-sdk,thread.run,thread.messages.read, and an explicit external-thread-to-Session binding. - Return only that short-lived token and bound thread ID to the browser.
- Point
DefaultChatTransportat/v1/ai-sdk/threads/<thread_id>/runsand addAuthorization: Bearer <application_access_token>. - When the token expires, bind its replacement to the same Managed Session if the user is continuing the same work. Re-run the stream and history checks through the production ingress.
Authentication, TLS, and CORS belong at the server boundary. Provider credentials and Workspace service keys never belong in frontend code.
Troubleshooting
If the table does not resolve the problem, record the route, HTTP status, response error code, thread ID, Session ID if known, and correlation ID before contacting support. Remove bearer tokens and message content first.
| Symptom | Check | Action |
|---|---|---|
| Every turn appears under a different thread | Inspect the POST body for threadId | Keep prepareSendMessagesRequest; useChat’s id alone is not the Awaken request field |
| The browser reports a CORS error | Compare the page origin with localhost:8080 | Serve the UI from the same origin or configure the reverse proxy’s allowed origin, methods, and headers |
useChat receives no stream parts | Inspect the request URL and response content type | Use the exact run endpoint and confirm the response is text/event-stream |
| The run route returns 404 | Confirm the bind address and selected route | Start awaken, then compare the URL with the protocol reference |
| A production request returns 401 or 403 | Check token expiry, protocol, operation, and thread binding | Have the backend mint a new least-scope application token for the same authorized Session |
| The live reply appeared but refresh is empty | Check whether the app loads the history endpoint | Hydrate the client from committed history; do not treat the transient stream as storage |
Next steps
- Read the AI SDK protocol reference before rendering tools, approvals, files, usage, or platform metadata.
- Use Manage a Session when your product needs interrupt, archive, or lifecycle behavior.
- Review Console and authentication ownership before exposing an application route.
- Choose CopilotKit over AG-UI when CopilotKit owns the application UI.