> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thesys.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Thinking States

> Learn to enhance user experience by updating them about the agent's thinking process in real-time

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.

<Tabs>
  <Tab title="Node">
    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:

    <Steps>
      <Step title="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:

        ```ts app/api/chat/route.ts {6, 9, 36-38, 43, 54} [expandable] theme={null}
        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",
            },
          });
        }
        ```
      </Step>

      <Step title="Write a thinking state to the response object">
        To add a thinking state, use the `writeThinkItem` method defined on the `c1Response` object:

        ```ts app/api/chat/route.ts {11-14} [expandable] theme={null}
        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",
            },
          });
        }
        ```
      </Step>

      <Step title="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](/guides/integrate-data/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:

        ```ts app/api/chat/tools.ts {23} [expandable] theme={null}
        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:

        ```ts app/api/chat/route.ts {9 - 15} theme={null}
        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",
        });
        ```

        <Accordion title="Full API route code with tool calling and thinking states">
          ```ts app/api/chat/route.ts theme={null}
          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",
              },
            });
          }
          ```
        </Accordion>
      </Step>

      <Step title="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:

        <ResponseField name="thinkItems" type="ThinkItem[]">
          An array of thinking state items, where each item contains:

          * title: The title
            of the thinking state
          * content: The content/description of the thinking state
          * ephemeral: Whether this thinking state should be temporary or persist after the response is done streaming
        </ResponseField>

        <ResponseField name="thinkingInProgress" type="boolean">
          Indicates if a thinking state is active. Use this to display a loader or shimmer while processing.
        </ResponseField>

        Example custom think component:

        ```tsx [expandable] theme={null}
        import { ThinkComponent } from "@thesysai/genui-sdk";
        import styles from "./styles.module.css";

        const CustomThink: ThinkComponent = ({ thinkItems, thinkingInProgress }) => {
          return (
            <div className={styles.thinkContainer}>
              <div className={styles.thinkTitle}>
                {thinkingInProgress ? "Processing..." : "Processing complete!"}
              </div>
              <div className={styles.thinkItems}>
                {thinkItems.map((item) => (
                  <div key={item.title} className={styles.thinkItem}>
                    {item.title}
                  </div>
                ))}
              </div>
            </div>
          );
        };
        ```

        You may pass your custom component to the `C1Chat` component or the `useThreadManager` hook like this:

        <CodeGroup>
          ```tsx C1Chat {3} theme={null}
          <C1Chat
            apiUrl="/api/chat"
            customizeC1={{ thinkComponent: CustomThink }}
          />
          ```

          ```tsx useThreadManager {3} theme={null}
          const threadManager = useThreadManager({
            // other threadManager parameters
            customizeC1: { thinkComponent: CustomThink },
          });
          ```
        </CodeGroup>
      </Step>

      <Step title="Test it out">
        You should now see the thinking state on the UI while the agent is processing the response:

        <Frame>
          <img src="https://mintcdn.com/thesys/C1mGp0p_ygBsZ7UI/images/thinking-states.jpeg?fit=max&auto=format&n=C1mGp0p_ygBsZ7UI&q=85&s=760543abf48f752bd3e9101a0b39acf0" alt="Thinking states sample image" width="5088" height="3448" data-path="images/thinking-states.jpeg" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Python">
    The [`thesys_genui_sdk`](https://pypi.org/project/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.

    <Steps>
      <Step title="Create a c1_response object">
        <CodeGroup>
          ```python FastAPI [expandable] theme={null}
          # main.py
          from pydantic import BaseModel
          from fastapi import FastAPI, Request
          from thesys_genui_sdk.fast_api import with_c1_response
          from thesys_genui_sdk.context import write_content, get_assistant_message
          import openai

          app = 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: str

          async 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()
          ```

          ```python Framework-Independent [expandable] theme={null}
          # main.py
          import asyncio
          from thesys_genui_sdk import C1Response
          import openai

          openai_client = openai.OpenAI(
            api_key=os.getenv("THESYS_API_KEY"),
            base_url="https://api.thesys.dev/v1/embed",
          )

          async def generate_llm_response(c1_response: C1Response, prompt: str):
              stream = openai_client.chat.completions.create(
                  model="c1/anthropic/claude-sonnet-4/v-20251230",
                  messages=[{"role": "user", "content": prompt}],
                  stream=True,
              )

              async for chunk in stream:
                  content = chunk.choices[0].delta.content
                  if content:
                      await c1_response.write_content(content)

              # c1_response.get_assistant_message() allows you to
              # get the full response to store for message history
              assistant_message_for_history = c1_response.get_assistant_message()
              await c1_response.end()


          async def main():
              c1_response = C1Response()

              # In a web server, you would start an async task
              # to generate the response
              asyncio.create_task(generate_llm_response(c1_response, "Tell me about latest trends in AI."))

              # This is the stream you'd return from your route
              response_stream = c1_response.stream()

              # Example of how to consume the stream
              async for item in response_stream:
                  print(item, end="")

          if __name__ == "__main__":
              asyncio.run(main())
          ```
        </CodeGroup>
      </Step>

      <Step title="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.

        <CodeGroup>
          ```python FastAPI {15-19} [expandable] theme={null}
          # main.py
          from fastapi import FastAPI, Request
          from thesys_genui_sdk.fast_api import with_c1_response
          from thesys_genui_sdk.context import write_content, get_assistant_message, write_think_item
          import openai

          app = 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,
              )
              # ...
          ```

          ```python Framework-Independent {12-15} [expandable] theme={null}
          # main.py
          import asyncio
          from thesys_genui_sdk.context import C1Response
          import openai

          openai_client = openai.OpenAI(
            api_key=os.getenv("THESYS_API_KEY"),
            base_url="https://api.thesys.dev/v1/embed",
          )

          async def generate_llm_response(c1_response: C1Response, prompt: str):
              await c1_response.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": prompt}],
                  stream=True,
              )
              # ...
          ```
        </CodeGroup>
      </Step>

      <Step title="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.

        <CodeGroup>
          ```python FastAPI [expandable] theme={null}
          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
          ```

          ```python Framework-Independent [expandable] theme={null}
          async def web_search(query: str, c1_response: C1Response):
              await c1_response.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
          ```
        </CodeGroup>
      </Step>

      <Step title="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:

        <ResponseField name="thinkItems" type="ThinkItem[]">
          An array of thinking state items, where each item contains:

          * title: The title
            of the thinking state
          * content: The content/description of the thinking state
          * ephemeral: Whether this thinking state should be temporary or persist after the response is done streaming
        </ResponseField>

        <ResponseField name="thinkingInProgress" type="boolean">
          Indicates if a thinking state is active. Use this to display a loader or shimmer while processing.
        </ResponseField>

        Example custom think component:

        ```tsx [expandable] theme={null}
        import { ThinkComponent } from "@thesysai/genui-sdk";
        import styles from "./styles.module.css";

        const CustomThink: ThinkComponent = ({ thinkItems, thinkingInProgress }) => {
          return (
            <div className={styles.thinkContainer}>
              <div className={styles.thinkTitle}>
                {thinkingInProgress ? "Processing..." : "Processing complete!"}
              </div>
              <div className={styles.thinkItems}>
                {thinkItems.map((item) => (
                  <div key={item.title} className={styles.thinkItem}>
                    {item.title}
                  </div>
                ))}
              </div>
            </div>
          );
        };
        ```

        You may pass your custom component to the `C1Chat` component or the `useThreadManager` hook like this:

        <CodeGroup>
          ```tsx C1Chat {3} theme={null}
          <C1Chat
            apiUrl="/api/chat"
            customizeC1={{ thinkComponent: CustomThink }}
          />
          ```

          ```tsx useThreadManager {3} theme={null}
          const threadManager = useThreadManager({
            // other threadManager parameters
            customizeC1: { thinkComponent: CustomThink },
          });
          ```
        </CodeGroup>
      </Step>

      <Step title="Test it out">
        You should now see the thinking state on the UI while the agent is processing the response:

        <Frame>
          <img src="https://mintcdn.com/thesys/C1mGp0p_ygBsZ7UI/images/thinking-states.jpeg?fit=max&auto=format&n=C1mGp0p_ygBsZ7UI&q=85&s=760543abf48f752bd3e9101a0b39acf0" alt="Thinking states sample image" width="5088" height="3448" data-path="images/thinking-states.jpeg" />
        </Frame>
      </Step>
    </Steps>
  </Tab>
</Tabs>
