npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kognitivedev/ui

v0.2.28

Published

Provider-agnostic React components for building agentic chat UIs with Kognitive

Downloads

1,037

Readme

@kognitivedev/ui

React providers, primitives, components, and hooks for building provider-agnostic chat UIs with Kognitive.

Installation

bun add @kognitivedev/ui

Peer dependencies: react, zod

Quick Start

The simplest setup is to point the UI at browser-reachable app routes:

"use client";

import { KognitiveUI } from "@kognitivedev/ui";

export default function ChatPage() {
  return (
    <KognitiveUI serverUrl="http://localhost:3000" agentName="assistant">
      <KognitiveUI.Thread />
    </KognitiveUI>
  );
}

The UI sends and receives Kognitive-native message and stream events. It does not depend on ai or @ai-sdk/react.

Product Shells

@kognitivedev/ui is organized in three practical layers:

  • KognitiveUI as the runtime/provider boundary
  • default components such as Thread, ThreadList, Composer, and Message
  • primitives such as ThreadPrimitive, ThreadListPrimitive, ComposerPrimitive, and MessagePrimitive for custom shells

The new create-kognitive starter uses the package in this product-shell mode:

"use client";

import {
  ComposerPrimitive,
  KognitiveUI,
  MessagePrimitive,
  ThreadListPrimitive,
  ThreadPrimitive,
} from "@kognitivedev/ui";

export default function ChatPage() {
  return (
    <KognitiveUI
      api="/api/kognitive/agents/assistant/stream"
      agentName="assistant"
      threads={{ apiBase: "/api/kognitive/threads/agents/assistant" }}
    >
      <div className="app-shell">
        <ThreadListPrimitive.Root>
          <ThreadListPrimitive.New>New chat</ThreadListPrimitive.New>
          <ThreadListPrimitive.Items>
            {(thread, isActive) => (
              <ThreadListPrimitive.Item key={thread.sessionId} thread={thread} isActive={isActive}>
                <span>{thread.title}</span>
              </ThreadListPrimitive.Item>
            )}
          </ThreadListPrimitive.Items>
        </ThreadListPrimitive.Root>

        <ThreadPrimitive.Root>
          <ThreadPrimitive.Messages>
            {(message, index) => (
              <MessagePrimitive.Root key={message.id} message={message} index={index}>
                <MessagePrimitive.Content />
              </MessagePrimitive.Root>
            )}
          </ThreadPrimitive.Messages>

          <ComposerPrimitive.Root>
            <ComposerPrimitive.Input placeholder="Ask anything" />
            <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
          </ComposerPrimitive.Root>
        </ThreadPrimitive.Root>
      </div>
    </KognitiveUI>
  );
}

Features

  • Drop-in <KognitiveUI.Thread />
  • Headless primitives for custom product shells and layouts
  • useKognitiveChat() for hook-level control
  • Tool call rendering with makeToolUI() and toolkit()
  • File attachments
  • Message editing and branch management
  • Thread management and runtime event hooks

Custom Tool UIs

import { makeToolUI } from "@kognitivedev/ui";
import { weatherTool } from "./tools";

const WeatherUI = makeToolUI({
  tool: weatherTool,
  render: ({ input, output, state }) => (
    <div>{input.city}: {state === "output-available" ? output?.temperature : "Loading..."}</div>
  ),
});

Tool rendering lifecycle

Tool UIs are rendered once per toolCallId. The stream may emit a tool-call and then a tool-result, but <KognitiveUI /> groups those into one invocation before rendering.

input is pulled from the tool-call and output is filled from the tool-result as soon as it arrives. The state field advances with the tool lifecycle (input-available, approval-requested, output-available, etc.), so each renderer can animate progress without creating a second widget.

This means toolkit({ ... }) and makeToolUI({ ... }) usage does not change; the rendering behavior is handled inside MessageContent.

Standalone Hook

import { useKognitiveChat } from "@kognitivedev/ui";

function MyChat() {
  const { messages, send, status, stop } = useKognitiveChat({
    serverUrl: "http://localhost:3000",
    agentName: "assistant",
  });

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>{message.role}</div>
      ))}
      <button onClick={() => send("Hello")}>Send</button>
      <button onClick={stop} disabled={status !== "streaming"}>Stop</button>
    </div>
  );
}

Auto-submitting after completed tool calls

useKognitiveChat and <KognitiveUI /> support an optional sendAutomaticallyWhen callback. If provided, it is evaluated when stream processing finishes (status becomes ready) and can re-submit the current message history automatically.

No tool output callback is required, and auto-submit is opt-in (sendAutomaticallyWhen is undefined by default). Predicates can return boolean or PromiseLike<boolean>.

import {
  KognitiveUI,
  lastAssistantMessageIsCompleteWithToolCalls,
} from "@kognitivedev/ui";

function DemoChat() {
  return (
    <KognitiveUI
      serverUrl="http://localhost:3000"
      agentName="assistant"
      sendAutomaticallyWhen={lastAssistantMessageIsCompleteWithToolCalls}
    >
      <KognitiveUI.Thread />
    </KognitiveUI>
  );
}

You can also provide a custom predicate that inspects the latest messages and decides when to re-submit:

import { useKognitiveChat } from "@kognitivedev/ui";

function MyChat() {
  const { messages, send, status } = useKognitiveChat({
    serverUrl: "http://localhost:3000",
    agentName: "assistant",
    sendAutomaticallyWhen: ({ messages }) => {
      const last = messages.at(-1);
      if (!last || last.role !== "assistant") return false;
      return last.parts.some((part) => part.type === "tool-call");
    },
  });

  return <></>;
}

The predicate receives full UI messages with existing tool-call and tool-result parts and runs only after the assistant stream is ready.

Routing

  • serverUrl points the browser at your app server.
  • api lets you override the chat endpoint when needed.
  • threads={{ apiBase }} lets you override the thread endpoint base when your product uses custom thread routes.

Server-side Kognitive.baseUrl in @kognitivedev/core is different. It points to the Kognitive backend/cloud and should not be passed to browser UI components.

Bring Your Own API

Use api when your app needs a business-specific backend contract:

<KognitiveUI
  api="/api/chat"
  agentName="assistant"
  resourceId={{ userId: "user-1" }}
  threads={{ apiBase: "/api/chat/threads" }}
/>

The chat endpoint should accept:

{
  "messages": [],
  "sessionId": "session_123",
  "resourceId": { "userId": "user-1" },
  "agentName": "assistant"
}

sessionId is top-level. resourceId should carry user and tenant context, not duplicate the public session identifier.

If you enable built-in threads, the thread API base should expose:

  • GET /threads
  • POST /threads
  • GET /threads/:sessionId
  • PATCH /threads/:sessionId
  • DELETE /threads/:sessionId

Each endpoint should use the same public sessionId the chat API sees. Automatic thread titles are generated from the stream execution flow and persisted by the backend after conversation snapshots are stored.

Stable Primitive Attributes

The primitive roots expose stable data attributes so custom shells can style and test against runtime state without depending on private internals:

  • ComposerPrimitive.Root: data-disabled, data-has-input, data-has-files, data-drag-over
  • ThreadPrimitive.Root: data-status, data-streaming, data-empty
  • MessagePrimitive.Root: data-role, data-editing, data-branch-count, data-branch-index
  • ThreadListPrimitive.Root: data-empty, data-has-active-thread
  • ThreadListPrimitive.Item: data-active, data-status, data-session-id