Use this file to discover all available pages before exploring further.
Thinking states are a great way to let the user know what the model is doing. This is especially useful for long-running tasks or when the user is waiting for a response,
such as when the agent is performing a slow tool call.
Node
Python
The @thesysai/genui-sdk package provides a makeC1Response function that can be used to add data related to thinking states to the response.Here’s a simple example of how to use thinking states:
1
Create a c1Response object
Use the makeC1Response function to create a c1Response object by importing it from the @thesysai/genui-sdk package, and start writing the LLM response
content to this object:
app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";import OpenAI from "openai";import type { ChatCompletionMessageParam } from "openai/resources.mjs";import { transformStream } from "@crayonai/stream";import { getMessageStore } from "./messageStore";import { makeC1Response } from "@thesysai/genui-sdk/server";export async function POST(req: NextRequest) { const c1Response = makeC1Response(); const { prompt, threadId, responseId } = (await req.json()) as { prompt: ChatCompletionMessageParam; threadId: string; responseId: string; }; const client = new OpenAI({ baseURL: "https://api.thesys.dev/v1/embed", apiKey: process.env.THESYS_API_KEY, // Use the API key you created in the previous step }); const messageStore = getMessageStore(threadId); messageStore.addMessage(prompt); const llmStream = await client.chat.completions.create({ model: "c1/anthropic/claude-sonnet-4/v-20251230", messages: messageStore.getOpenAICompatibleMessageList(), stream: true, }); // Unwrap the OpenAI stream to a C1 stream transformStream( llmStream, (chunk) => { const contentDelta = chunk.choices[0].delta.content; if (contentDelta) { c1Response.writeContent(contentDelta); } return contentDelta; }, { onEnd: ({ accumulated }) => { c1Response.end(); // This is necessary to stop showing the "loading" state once the response is done streaming. const message = accumulated.filter((chunk) => chunk).join(""); messageStore.addMessage({ id: responseId, role: "assistant", content: message, }); }, } ) as ReadableStream<string>; return new NextResponse(c1Response.responseStream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, });}
2
Write a thinking state to the response object
To add a thinking state, use the writeThinkItem method defined on the c1Response object:
app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";import OpenAI from "openai";import type { ChatCompletionMessageParam } from "openai/resources.mjs";import { transformStream } from "@crayonai/stream";import { getMessageStore } from "./messageStore";import { makeC1Response } from "@thesysai/genui-sdk/server";export async function POST(req: NextRequest) { const c1Response = makeC1Response(); c1Response.writeThinkItem({ title: "Thinking...", description: "Diving into the digital depths to craft you an answer.", }); const { prompt, threadId, responseId } = (await req.json()) as { prompt: ChatCompletionMessageParam; threadId: string; responseId: string; }; const client = new OpenAI({ baseURL: "https://api.thesys.dev/v1/embed", apiKey: process.env.THESYS_API_KEY, // Use the API key you created in the previous step }); const messageStore = getMessageStore(threadId); messageStore.addMessage(prompt); const llmStream = await client.chat.completions.create({ model: "c1/anthropic/claude-sonnet-4/v-20251230", messages: messageStore.getOpenAICompatibleMessageList(), stream: true, }); // Unwrap the OpenAI stream to a C1 stream transformStream( llmStream, (chunk) => { const contentDelta = chunk.choices[0].delta.content; if (contentDelta) { c1Response.writeContent(contentDelta); } return contentDelta; }, { onEnd: ({ accumulated }) => { c1Response.end(); // This is necessary to stop showing the "loading" state once the response is done streaming. const message = accumulated.filter((chunk) => chunk).join(""); messageStore.addMessage({ id: responseId, role: "assistant", content: message, }); }, } ) as ReadableStream<string>; return new NextResponse(c1Response.responseStream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, });}
3
Use thinking states with long-running tool calls (optional)
If you’d like to use thinking states with long-running tool calls, simply call the aforementioned writeThinkItem method inside the
tool call handler. For example, for the webSearch tool implemented in the Tool Calling guide, you can add a thinking state as follows:Next, modify the tool call handler to call a function that updates the thinking state:
app/api/chat/tools.ts
import type { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs";import { z } from "zod";import { zodToJsonSchema } from "zod-to-json-schema";import Exa from "exa-js";import type { JSONSchema } from "openai/lib/jsonschema.mjs";const exa = new Exa(process.env.EXA_API_KEY!);export const getWebSearchTool = ( writeThinkingState: () => void): RunnableToolFunctionWithParse<{ query: string }> => ({ type: "function", function: { name: "webSearch", description: "Use this tool to perform a web search.", parse: JSON.parse, parameters: zodToJsonSchema( z.object({ query: z.string().describe("The query to search for."), }) ) as JSONSchema, function: async ({ query }: { query: string }) => { writeThinkingState(); return await exa.search(query, { numResults: 5 }); }, strict: true, },});
Next, pass the writeThinkingState function to the tool call handler:
app/api/chat/route.ts
const llmStream = await client.beta.chat.completions.runTools({ model: "c1/anthropic/claude-sonnet-4/v-20251230", messages: [ { role: "system", content: systemPrompt }, ...messageStore.getOpenAICompatibleMessageList(), ], stream: true, tools: [ getWebSearchTool(() => { c1Response.writeThinkItem({ title: "Searching the web...", description: "Scouring the digital universe for the most relevant and up-to-date insights.", }); }), ], toolChoice: "auto",});
Full API route code with tool calling and thinking states
app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";import OpenAI from "openai";import { transformStream } from "@crayonai/stream";import { DBMessage, getMessageStore } from "./messageStore";import { makeC1Response } from "@thesysai/genui-sdk/server";import { getWebSearchTool } from "./tools";import { systemPrompt } from "./systemPrompt";export async function POST(req: NextRequest) { const c1Response = makeC1Response(); c1Response.writeThinkItem({ title: "Thinking...", description: "Diving into the digital depths to craft you an answer.", }); const { prompt, threadId, responseId } = (await req.json()) as { prompt: DBMessage; threadId: string; responseId: string; }; const client = new OpenAI({ baseURL: "https://api.thesys.dev/v1/embed", apiKey: process.env.THESYS_API_KEY, }); const messageStore = getMessageStore(threadId); messageStore.addMessage(prompt); const llmStream = await client.beta.chat.completions.runTools({ model: "c1/anthropic/claude-sonnet-4/v-20251230", messages: [ { role: "system", content: systemPrompt }, ...messageStore.getOpenAICompatibleMessageList(), ], stream: true, tools: [ getWebSearchTool(() => { c1Response.writeThinkItem({ title: "Searching the web...", description: "Scouring the digital universe for the most relevant and up-to-date insights.", }); }), ], toolChoice: "auto", }); transformStream( llmStream, (chunk) => { const contentDelta = chunk.choices[0].delta.content; if (contentDelta) { c1Response.writeContent(contentDelta); } return contentDelta; }, { onEnd: ({ accumulated }) => { c1Response.end(); const message = accumulated.filter((message) => message).join(""); messageStore.addMessage({ role: "assistant", content: message, id: responseId, }); }, } ) as ReadableStream<string>; return new NextResponse(c1Response.responseStream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, });}
4
Add a custom think component (optional)
If you’d like to add a custom thinking state component, you can do so by passing a customizeC1 prop to the C1Chat component or the
useThreadManager hook.Your custom component should accept the following props:
You should now see the thinking state on the UI while the agent is processing the response:
The thesys_genui_sdk package provides a C1Response class that can be used to add data related to thinking states to the response.
If you are using FastAPI, the package provides a handy decorator with_c1_response to make this even easier.
1
Create a c1_response object
# main.pyfrom pydantic import BaseModelfrom fastapi import FastAPI, Requestfrom thesys_genui_sdk.fast_api import with_c1_responsefrom thesys_genui_sdk.context import write_content, get_assistant_messageimport openaiapp = FastAPI()openai_client = openai.OpenAI( api_key=os.getenv("THESYS_API_KEY"), base_url="https://api.thesys.dev/v1/embed",)@app.post("/chat")# this decorator will add the c1_response in a context variable# and internally return the stream from your endpoint.@with_c1_response()async def chat(request: ChatRequest): await generate_llm_response(request)class ChatRequest(BaseModel): prompt: Prompt threadId: str responseId: strasync def generate_llm_response(request: ChatRequest): stream = openai_client.chat.completions.create( model="c1/anthropic/claude-sonnet-4/v-20251230", messages=[{"role": "user", "content": request.prompt}], stream=True, ) async for chunk in stream: content = chunk.choices[0].delta.content if content: await write_content(content) # get_assistant_message() allows you to get the full response to store for message history assistant_message_for_history = get_assistant_message()
2
Write a thinking state to the response object
To add a thinking state, use the write_think_item function. This is useful to let the user know that a long-running process is happening.
# main.pyfrom fastapi import FastAPI, Requestfrom thesys_genui_sdk.fast_api import with_c1_responsefrom thesys_genui_sdk.context import write_content, get_assistant_message, write_think_itemimport openaiapp = FastAPI()openai_client = openai.OpenAI( api_key=os.getenv("THESYS_API_KEY"), base_url="https://api.thesys.dev/v1/embed",)async def generate_llm_response(request: Request): # ... await write_think_item( title="Thinking...", description="Diving into the digital depths to craft you an answer." ) stream = openai_client.chat.completions.create( model="c1/anthropic/claude-sonnet-4/v-20251230", messages=[{"role": "user", "content": request.prompt}], stream=True, ) # ...
3
Use thinking states with long-running tool calls (optional)
If you are using tools, you can call write_think_item before executing a long-running tool to provide feedback to the user.
async def web_search(query: str): await write_think_item( title="Searching the web...", description=f"Looking for information on '{query}'" ) # ... perform web search results = await some_search_api(query) return results
4
Add a custom think component (optional)
This is a frontend customization and is independent of the backend implementation. You can follow the same guide as for Node.js by passing a customizeC1 prop to the C1Chat component or the useThreadManager hook.Your custom component should accept the following props: