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

@aseity/agent-chat-react

v0.9.2

Published

Headless React runtime for agent chat UI streams.

Readme

@aseity/agent-chat-react

Headless React runtime for agent chat UI streams.

This package owns transport adaptation, SSE parsing, optimistic user messages, agent event reduction, and React subscription hooks. It intentionally does not ship visual chat components.

Historical assistant errors are preserved by hydrateMessages() and store.hydrate(), including through the ky transport's protocol validation. The restored message exposes the supplied error with its code, message (including an empty string), and optional retry limit. Messages without error metadata remain unchanged. Applications decide how to display business error codes; the SDK does not generate product-specific error text.

Current Runtime Tradeoffs

  • SSE reconnects are bounded only for failures that happen before the stream opens. If a connection opens and then drops immediately while a turn is still active, the retry counter is reset and the client can continue reconnecting. This is an accepted product tradeoff for the current single-user/single-backend deployment shape; do not treat it as a bug unless the deployment model changes or reconnect loops become user-visible.
  • The ky transport ends an SSE stream after 45 seconds without receiving any bytes. Heartbeat comments count as activity, so long-running agent work does not trigger reconnects while the server heartbeat remains healthy. Set eventStreamInactivityTimeoutMs higher than the server heartbeat interval when creating the transport if the server configuration differs.
'use client';

import { useAgentChat } from '@aseity/agent-chat-react';
import { createKyAgentTransport } from '@aseity/agent-chat-react/transport/ky';
import { apiClient } from './http';

const transport = createKyAgentTransport(apiClient, {
  createSessionBody: { agentId: 'max' },
  eventStreamInactivityTimeoutMs: 45_000,
});

// Dedicated agents share the same transport with a trusted relative base path.
const simulationTransport = createKyAgentTransport(apiClient, {
  basePath: 'simulation-agent',
});

export function Chat() {
  const chat = useAgentChat({
    transport,
    autoCreate: true,
    throttleMs: 50,
  });

  return (
    <form
      onSubmit={event => {
        event.preventDefault();
        const data = new FormData(event.currentTarget);
        void chat.sendMessage(String(data.get('message') ?? ''));
        event.currentTarget.reset();
      }}
    >
      {chat.messages.map(message => (
        <div key={message.id}>{message.type === 'user' ? message.text : message.status}</div>
      ))}
      <input name="message" disabled={!chat.canSend} />
      <button disabled={!chat.canSend}>Send</button>
    </form>
  );
}

For long conversations, render chat.messageIds and subscribe per row with useAgentMessage(chat.store, id).

useAgent connects to the event stream when a loaded session is running, so a page opened mid-turn follows the turn live. Pass autoConnect: false for a view that shows only persisted history: a running session then stays disconnected, including after reload(), until the caller sends or calls connect().

Migrating from 0.3

This minor release changes the pre-1.0 transport contract:

  • Send receipts and pendingSend use turnId instead of runId. Custom transports must return { sessionId, turnId, messageId, status: 'running' }.
  • The default collection path is v1/sessions. Replace the agentId transport option with createSessionBody: { agentId }. createSessionBody is posted as supplied; a custom basePath still overrides the collection path.
  • History comes from GET {basePath}/{sessionId}/items, not /messages. Overriding basePath does not restore the old response shape. Applications retaining older APIs can override listMessages and sendMessage in their AgentChatTransport, as Max's chat service does.
  • Sessions can carry agentId, mode, currentTurn, and string-valued metadata. Hosts map session/turn waiting and stopping to running.
  • interrupted is accepted in both turn-end events and persisted history.

Items response

The ky entry point exports SessionItemsPage, SessionItem, and SessionTurnSummary for host implementations. The response envelope is { items, turns, nextCursor, prevCursor }. Items are chronological; nextCursor is passed as after until it is null, so initial hydration reaches the current turn. prevCursor is reserved for older-history navigation; the SDK does not provide infinite scrolling.

Each item carries id: "<turnId>:<seq>" and turnId. Sequence numbers are compared numerically. The content fields are:

| type | fields | | --- | --- | | user_message | text, optional attachments: Attachment[] | | assistant_text | text | | reasoning | text, optional durationMs | | tool_call | toolUseId, toolName, status, optional input, result, display, parentId |

Tool status uses the protocol's ToolStatus. parentId, when present, identifies another item's id. AskUserQuestion is a tool_call; its input and result retain the question and answer. Reasoning contains only saved text.

Each page supplies the involved turns with id, seq, status, and userMessageId, plus optional session id, timestamps, error, and pending interactions. Turn status is running or a protocol TurnStopStatus. Turns are grouped into assistant messages; user inputs retain userMessageId so optimistic messages keep their identity. Empty turns and error metadata are preserved. Attachment descriptors (id, displayName, mimeType, byteSize, kind) are preserved during pagination and reloads, including inputs with empty text. Send requests still carry attachments: [{ assetPointer }]; download URLs are not part of the history contract. Repeated items and turn summaries across pages use the last received value. validateProtocol validates the converted SessionMessages and SSE payloads; turn association and item identity are required even when validation is disabled.

Reasoning duration

The store measures reasoning locally from receipt of part_add with initial.kind: 'thinking' until part_complete, using a monotonic clock. Completed reasoning parts expose integer-millisecond duration_ms while the rest of the turn can still be running. Turn termination also settles unfinished reasoning timers. There is no ticking timer or elapsed-time update on every delta.

These durations measure client receipt intervals, including connection delays; buffered replay can produce very short durations. Full turn replay restarts local measurement. History hydration replaces local durations with the snapshot's optional recorded values, which may differ or be absent.

Event replay

Reuse the transport instance across reconnects. A transport instance serves one active store per session; independent concurrent stores need separate transport instances so their replay cursors cannot advance each other. It records each delivered SSE frame's id: per session and sends it as Last-Event-ID on reconnect. Invalid or incomplete frames do not advance the cursor; an empty id: clears it. A history reload clears that session's cursor because hydration replaces streamed parts and needs a full replay. The server must replay from turn_start when it cannot resume the supplied id. session_event.current_turn is preserved for application callbacks; the store continues to reconstruct the active turn and interactions from replayed events.

Workspace artifacts

The host emits turn_artifacts after collecting a turn's workspace artifacts and before turn_end. The event name, turn identity and collection timing are unchanged:

{
  type: 'turn_artifacts',
  turn_id: 'turn_1',
  artifacts: [
    { entryPath: 'reports/index.html', artifactType: 'webpage' },
    { entryPath: 'slides/index.html', artifactType: 'presentation' },
    { entryPath: 'simulations/pendulum.sim', artifactType: 'simulation' },
  ],
}

ArtifactReference contains a nonempty entryPath and an artifactType of document, webpage, presentation, spreadsheet, image, exercise or simulation. The host supplies the full relative entry path within the session workspace. Identity is scoped to the session: the same path in another session identifies a different artifact. Paths are kept intact; matching filenames in different directories are distinct.

The store replaces the referenced turn's collection and deduplicates by full entryPath, retaining the last reference for a repeated path. An empty array clears that turn's collection. The same path can appear independently in multiple turns. AssistantMessage.artifacts and SessionTurnSummary.artifacts preserve these references through history loading and pagination, including turns with no assistant text. Hydration applies the same per-turn deduplication as event replay.

References identify current workspace files, not library entries or content snapshots. The host resolves content and access permissions; the SDK does not save files to a library or define preview routes.

Migrating from 0.6.x

Upgrade @aseity/agent-ui-protocol and @aseity/agent-chat-react together to 0.7.0. Replace { id } in events and historical turn summaries with { entryPath, artifactType }; the old shape is no longer supported. Attachments and other message fields are unchanged.

The SDK's transport reads history afresh on every listMessages call and has no persistent history cache or cache format version. Hosts that cache history must bump their own cache/projection format version and discard old { id } data before hydrating the store, including when protocol validation is disabled. The default transport validation rejects legacy references; it does not migrate them. Harnex must upgrade its server history projection and client cache together.