agentic-ui-kit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/dist/core.js +1 -0
  4. package/dist/index.js +2785 -0
  5. package/dist/sse-BA_ebMcH.js +408 -0
  6. package/dist/styles.css +3 -0
  7. package/dist/tailwind.css +333 -0
  8. package/dist/types/core/src/blocks.d.ts +22 -0
  9. package/dist/types/core/src/events.d.ts +75 -0
  10. package/dist/types/core/src/index.d.ts +7 -0
  11. package/dist/types/core/src/partial-json.d.ts +7 -0
  12. package/dist/types/core/src/reduce.d.ts +33 -0
  13. package/dist/types/core/src/sse.d.ts +8 -0
  14. package/dist/types/core/src/types.d.ts +171 -0
  15. package/dist/types/react/src/components/activity-group.d.ts +13 -0
  16. package/dist/types/react/src/components/approval-card.d.ts +11 -0
  17. package/dist/types/react/src/components/attachments.d.ts +78 -0
  18. package/dist/types/react/src/components/chat-header.d.ts +30 -0
  19. package/dist/types/react/src/components/chat-list.d.ts +47 -0
  20. package/dist/types/react/src/components/composer.d.ts +45 -0
  21. package/dist/types/react/src/components/context-panel.d.ts +13 -0
  22. package/dist/types/react/src/components/markdown.d.ts +8 -0
  23. package/dist/types/react/src/components/message-list.d.ts +18 -0
  24. package/dist/types/react/src/components/messages.d.ts +33 -0
  25. package/dist/types/react/src/components/model-picker.d.ts +16 -0
  26. package/dist/types/react/src/components/token-counter.d.ts +16 -0
  27. package/dist/types/react/src/components/tools.d.ts +68 -0
  28. package/dist/types/react/src/components/word-fade.d.ts +19 -0
  29. package/dist/types/react/src/core.d.ts +1 -0
  30. package/dist/types/react/src/hooks/agent.d.ts +86 -0
  31. package/dist/types/react/src/hooks/sidebar-state.d.ts +18 -0
  32. package/dist/types/react/src/hooks/smooth-text.d.ts +9 -0
  33. package/dist/types/react/src/hooks/sse-transport.d.ts +15 -0
  34. package/dist/types/react/src/hooks/use-chat.d.ts +68 -0
  35. package/dist/types/react/src/index.d.ts +56 -0
  36. package/dist/types/react/src/primitives/badge.d.ts +15 -0
  37. package/dist/types/react/src/primitives/button.d.ts +32 -0
  38. package/dist/types/react/src/primitives/caret.d.ts +4 -0
  39. package/dist/types/react/src/primitives/chat-theme.d.ts +13 -0
  40. package/dist/types/react/src/primitives/code-block.d.ts +13 -0
  41. package/dist/types/react/src/primitives/context-menu.d.ts +13 -0
  42. package/dist/types/react/src/primitives/disclosure.d.ts +16 -0
  43. package/dist/types/react/src/primitives/highlight.d.ts +8 -0
  44. package/dist/types/react/src/primitives/menu.d.ts +52 -0
  45. package/dist/types/react/src/primitives/overlay.d.ts +21 -0
  46. package/dist/types/react/src/primitives/panel.d.ts +19 -0
  47. package/dist/types/react/src/primitives/tooltip.d.ts +10 -0
  48. package/dist/types/react/src/views/app-shell.d.ts +20 -0
  49. package/dist/types/react/src/views/chat-view.d.ts +51 -0
  50. package/dist/types/react/src/views/sidebar.d.ts +38 -0
  51. package/package.json +80 -3
@@ -0,0 +1,78 @@
1
+ import type { AttachmentPart } from "../../../core/src/index.js";
2
+ /** An attachment in the composer, before it is sent. */
3
+ export interface DraftAttachment {
4
+ id: string;
5
+ kind: "text" | "image" | "file";
6
+ name: string;
7
+ mime: string;
8
+ size: number;
9
+ /** Pasted text. */
10
+ text?: string;
11
+ /** Local object URL for image thumbnails. */
12
+ previewUrl?: string;
13
+ status: "uploading" | "ready" | "error";
14
+ fileId?: string;
15
+ url?: string;
16
+ error?: string;
17
+ }
18
+ export interface UploadResult {
19
+ fileId: string;
20
+ url?: string;
21
+ }
22
+ export interface UseAttachmentsOptions {
23
+ /** Uploads a file. Without it files are kept locally and count as ready at once. */
24
+ upload?: (file: File, signal: AbortSignal) => Promise<UploadResult>;
25
+ }
26
+ export interface UseAttachments {
27
+ attachments: DraftAttachment[];
28
+ /** True while any file uploads; sending waits for it. */
29
+ uploading: boolean;
30
+ addFiles(files: Iterable<File>): void;
31
+ addText(text: string): void;
32
+ updateText(id: string, text: string): void;
33
+ remove(id: string): void;
34
+ clear(): void;
35
+ /** Ready attachments as message parts, for `send`. */
36
+ toParts(): AttachmentPart[];
37
+ }
38
+ /** Composer attachments: pasted text, images with thumbnails, files, each uploading on its own. */
39
+ export declare function useAttachments({ upload }?: UseAttachmentsOptions): UseAttachments;
40
+ /** What a card needs; both composer drafts and sent attachment parts fit. */
41
+ export interface AttachmentCardData {
42
+ name: string;
43
+ mime: string;
44
+ text?: string;
45
+ /** Image thumbnail. */
46
+ url?: string;
47
+ status?: "uploading" | "ready" | "error" | string;
48
+ error?: string;
49
+ }
50
+ export interface AttachmentCardProps {
51
+ attachment: AttachmentCardData;
52
+ /** Shows the remove button (on hover and focus; always on touch). */
53
+ onRemove?: () => void;
54
+ /** Pasted text: opens it to view or edit. */
55
+ onOpen?: () => void;
56
+ size?: "md" | "sm";
57
+ className?: string;
58
+ }
59
+ /** A square card: pasted text preview with a PASTED badge, an image thumbnail, or a file with its type. */
60
+ export declare function AttachmentCard({ attachment: a, onRemove, onOpen, size, className }: AttachmentCardProps): import("react").JSX.Element;
61
+ export interface AttachmentRowProps {
62
+ attachments: DraftAttachment[];
63
+ onRemove: (id: string) => void;
64
+ /** Saves edited pasted text. Without it pasted text opens read-only. */
65
+ onEditText?: (id: string, text: string) => void;
66
+ }
67
+ /** The composer's row of cards above the text. Renders nothing when empty. */
68
+ export declare function AttachmentRow({ attachments, onRemove, onEditText }: AttachmentRowProps): import("react").JSX.Element | null;
69
+ export interface PastedTextDialogProps {
70
+ /** Open while set. */
71
+ text: string | undefined;
72
+ onClose: () => void;
73
+ /** Makes the text editable. */
74
+ onSave?: (text: string) => void;
75
+ title?: string;
76
+ }
77
+ /** Shows pasted text in a modal, editable when `onSave` is set. */
78
+ export declare function PastedTextDialog({ text, onClose, onSave, title }: PastedTextDialogProps): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ChatHeaderProps {
3
+ title: ReactNode;
4
+ /** Opens from the title, e.g. `<ChatTitleMenu>`. Without it the title is plain text. */
5
+ menu?: ReactNode;
6
+ /** Left of the title, e.g. a project breadcrumb or the sidebar toggle on small screens. */
7
+ leading?: ReactNode;
8
+ /** Right side, e.g. Share or a context button. */
9
+ actions?: ReactNode;
10
+ className?: string;
11
+ }
12
+ /** The bar above a chat: breadcrumb and title with its menu, actions on the right. */
13
+ export declare function ChatHeader({ title, menu, leading, actions, className }: ChatHeaderProps): import("react").JSX.Element;
14
+ export interface ChatTitleMenuProps {
15
+ pinned?: boolean;
16
+ onPin?: () => void;
17
+ onRename?: () => void;
18
+ projects?: {
19
+ id: string;
20
+ name: string;
21
+ }[];
22
+ onAddToProject?: (projectId: string) => void;
23
+ onCreateProject?: () => void;
24
+ onShare?: () => void;
25
+ onDelete?: () => void;
26
+ /** Extra items, placed before Delete. */
27
+ children?: ReactNode;
28
+ }
29
+ /** Pin, Rename, Add to project (searchable), Share, Delete. Each shows only when its handler is set. */
30
+ export declare function ChatTitleMenu({ pinned, onPin, onRename, projects, onAddToProject, onCreateProject, onShare, onDelete, children }: ChatTitleMenuProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from "react";
2
+ export interface ChatSummary {
3
+ id: string;
4
+ title: string;
5
+ /** A reply is streaming in this chat. */
6
+ running?: boolean;
7
+ /** Something new since the user last opened it. */
8
+ unread?: boolean;
9
+ /** Link target. Rows render as links when set (middle-click, open in new tab), else as buttons. */
10
+ href?: string;
11
+ }
12
+ export interface ProjectSummary {
13
+ type: "project";
14
+ id: string;
15
+ name: string;
16
+ chats: ChatSummary[];
17
+ defaultExpanded?: boolean;
18
+ href?: string;
19
+ }
20
+ export type ChatListItem = ChatSummary | ProjectSummary;
21
+ export interface ChatListSection {
22
+ id: string;
23
+ /** "Pinned", "Chats and tasks". Without a title the section has no header and cannot collapse. */
24
+ title?: string;
25
+ items: ChatListItem[];
26
+ defaultExpanded?: boolean;
27
+ /** Next to the title, e.g. "10 new". */
28
+ badge?: ReactNode;
29
+ /** Right side of the header, e.g. a filter button. */
30
+ action?: ReactNode;
31
+ /** Shows a "View all" row after the items. */
32
+ onViewAll?: () => void;
33
+ }
34
+ export interface ChatListProps {
35
+ sections: ChatListSection[];
36
+ activeId?: string;
37
+ onSelect?: (chatId: string) => void;
38
+ /** Opens from a row's "…" button, e.g. `<ChatTitleMenu>` for that chat. */
39
+ rowMenu?: (chat: ChatSummary) => ReactNode;
40
+ "aria-label"?: string;
41
+ className?: string;
42
+ }
43
+ /**
44
+ * The chat picker: sections of chats and projects, collapsible, with an active row, running and unread
45
+ * markers and a per-row menu. Usable on its own (in a host's own sidebar) or inside `Sidebar`.
46
+ */
47
+ export declare function ChatList({ sections, activeId, onSelect, rowMenu, className, ...props }: ChatListProps): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { type ReactNode } from "react";
2
+ import type React from "react";
3
+ export interface ComposerProps {
4
+ onSubmit: (text: string) => void;
5
+ /** Shown as a stop button while `busy`. */
6
+ onStop?: () => void;
7
+ /** A reply is streaming: the send button becomes stop, and Enter does not send. */
8
+ busy?: boolean;
9
+ /** Controlled text. Leave both unset to let the composer keep its own. */
10
+ value?: string;
11
+ onChange?: (text: string) => void;
12
+ placeholder?: string;
13
+ /**
14
+ * "docked": under a chat in progress. "centered": the empty state, taller. "compact": one row, for
15
+ * panels and inline use.
16
+ */
17
+ variant?: "docked" | "centered" | "compact";
18
+ /** Left of the toolbar, e.g. `<AttachButton>` and toggles. */
19
+ leading?: ReactNode;
20
+ /** Right of the toolbar before send, e.g. `<ModelPicker>`. */
21
+ trailing?: ReactNode;
22
+ /** Above the text, e.g. attachment chips. */
23
+ above?: ReactNode;
24
+ /** Files pasted or dropped onto the composer. */
25
+ onFiles?: (files: File[]) => void;
26
+ /** Pasted text longer than `longPasteChars` goes here instead of into the box, e.g. as a card. */
27
+ onLongPaste?: (text: string) => void;
28
+ longPasteChars?: number;
29
+ /** Allows sending with no text, e.g. when attachments are ready. */
30
+ allowEmpty?: boolean;
31
+ /** Holds sending back, e.g. while a file uploads. */
32
+ submitDisabled?: boolean;
33
+ isDisabled?: boolean;
34
+ autoFocus?: boolean;
35
+ className?: string;
36
+ }
37
+ /** The chat box: growing textarea, toolbar slots, send / stop. Enter sends, Shift+Enter adds a line. */
38
+ export declare function Composer({ onSubmit, onStop, busy, value, onChange, placeholder, variant, leading, trailing, above, onFiles, onLongPaste, longPasteChars, allowEmpty, submitDisabled, isDisabled, autoFocus, className, }: ComposerProps): React.JSX.Element;
39
+ export interface AttachButtonProps {
40
+ /** The menu, e.g. `<Menu aria-label="Add">…</Menu>`. */
41
+ children: ReactNode;
42
+ label?: string;
43
+ }
44
+ /** The `+` button that opens the attach menu above the composer. */
45
+ export declare function AttachButton({ children, label }: AttachButtonProps): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { ContextUsage } from "../../../core/src/index.js";
2
+ export interface ContextPanelProps {
3
+ usage: ContextUsage;
4
+ /** Section title. */
5
+ title?: string;
6
+ defaultExpanded?: boolean;
7
+ className?: string;
8
+ }
9
+ /**
10
+ * What fills the context window: a usage line, a segmented bar, one row per category with its share, and
11
+ * expandable lists (each MCP tool, each memory file). Deferred categories are listed but not counted.
12
+ */
13
+ export declare function ContextPanel({ usage, title, defaultExpanded, className }: ContextPanelProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ export interface MarkdownProps {
2
+ children: string;
3
+ /** While true, text is paced and new words fade in; unfinished markdown is completed for display. */
4
+ streaming?: boolean;
5
+ className?: string;
6
+ }
7
+ /** Message markdown: GFM, KaTeX math ($…$ and $$…$$), Shiki code blocks, smooth fade-in while streaming. */
8
+ export declare function Markdown({ children, streaming, className }: MarkdownProps): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import type { Message, Part } from "../../../core/src/index.js";
2
+ import { type ReactNode } from "react";
3
+ import { type Feedback } from "./messages";
4
+ export interface MessageListProps {
5
+ messages: Message[];
6
+ onRetry?: (assistantId: string) => void;
7
+ onEdit?: (userId: string) => void;
8
+ onFeedback?: (assistantId: string, feedback: Feedback) => void;
9
+ renderPart?: (part: Part) => ReactNode;
10
+ /** Under the last message, inside the scroll area, e.g. the docked composer. Stays in view at the bottom. */
11
+ footer?: ReactNode;
12
+ className?: string;
13
+ }
14
+ /**
15
+ * The scrolling conversation. It follows a streaming reply while the reader is at the bottom and leaves
16
+ * them alone once they scroll up, with a button to jump back. Sending a message always jumps down.
17
+ */
18
+ export declare function MessageList({ messages, onRetry, onEdit, onFeedback, renderPart, footer, className }: MessageListProps): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ import { type Message, type Part } from "../../../core/src/index.js";
2
+ import { type ReactNode } from "react";
3
+ /** "just now", "5 minutes ago", "21 hours ago", "yesterday", then a date. */
4
+ export declare function formatRelativeTime(iso: string, now?: number): string;
5
+ /** Plain text of a message, for copy and read aloud. */
6
+ export declare function messageText(message: Message): string;
7
+ export interface UserMessageProps {
8
+ message: Message;
9
+ onRetry?: () => void;
10
+ onEdit?: () => void;
11
+ /** Extra buttons in the action row. */
12
+ actions?: ReactNode;
13
+ className?: string;
14
+ }
15
+ /** The user's turn: a bubble on the right, with time, retry, edit and copy under it on hover. */
16
+ export declare function UserMessage({ message, onRetry, onEdit, actions, className }: UserMessageProps): import("react").JSX.Element;
17
+ export type Feedback = "good" | "bad";
18
+ export interface AssistantMessageProps {
19
+ message: Message;
20
+ onRetry?: () => void;
21
+ onFeedback?: (feedback: Feedback) => void;
22
+ onReadAloud?: () => void;
23
+ /** Renders parts the kit does not draw itself (custom parts, widgets until WidgetFrame lands). */
24
+ renderPart?: (part: Part) => ReactNode;
25
+ /** Extra buttons in the action row. */
26
+ actions?: ReactNode;
27
+ className?: string;
28
+ }
29
+ /**
30
+ * The assistant's turn: activity groups (each with its own token count), markdown, and other parts in
31
+ * order. When done, copy / read aloud / feedback / retry and the time sit under it.
32
+ */
33
+ export declare function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPart, actions, className }: AssistantMessageProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { ModelInfo } from "../../../core/src/index.js";
2
+ import type { ReactNode } from "react";
3
+ export interface ModelPickerProps {
4
+ models: ModelInfo[];
5
+ model: string;
6
+ onModelChange: (id: string) => void;
7
+ effort?: string;
8
+ onEffortChange?: (id: string) => void;
9
+ /** Explains effort at the top of the Effort submenu. */
10
+ effortNote?: ReactNode;
11
+ /** Small print at the bottom of the menu, e.g. a link to model docs. */
12
+ footnote?: ReactNode;
13
+ className?: string;
14
+ }
15
+ /** "Opus 5.5 High ⌄": the models, an Effort submenu for models that have levels, and More models. */
16
+ export declare function ModelPicker({ models, model, onModelChange, effort, onEffortChange, effortNote, footnote, className, }: ModelPickerProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ /** Compact token count: 526, 1.4k, 33k, 76.8k, 1M. */
2
+ export declare function formatTokens(n: number): string;
3
+ /**
4
+ * Follows `target` with a number that counts up smoothly instead of jumping. Each frame closes part of the
5
+ * gap, so bursts of tokens roll in rather than land at once. Jumps straight there with reduced motion.
6
+ */
7
+ export declare function useCountUp(target: number): number;
8
+ export interface TokenCounterProps {
9
+ /** Tokens so far, e.g. from `activityTokens(parts)` or `outputTokens(message)`. */
10
+ tokens: number;
11
+ /** "standalone": "↓ 1.2k tokens". "inline": "(1.2k tokens)", for inside a heading. */
12
+ variant?: "standalone" | "inline";
13
+ className?: string;
14
+ }
15
+ /** A token count that counts up as `tokens` grows and holds still when it stops growing. */
16
+ export declare function TokenCounter({ tokens, variant, className }: TokenCounterProps): import("react").JSX.Element;
@@ -0,0 +1,68 @@
1
+ import type { ActivityPart, ToolCallPart } from "../../../core/src/index.js";
2
+ import { type ComponentType, type ReactNode } from "react";
3
+ import { type AgentTool } from "../hooks/agent";
4
+ /**
5
+ * How one kind of tool call shows up in an activity group. Registered by tool name; hosts add their own and
6
+ * override the built-in ones through `ChatProvider`.
7
+ */
8
+ export interface ToolView {
9
+ /** Row label, e.g. "Ran a command". Defaults to the part's title, then its name. */
10
+ label?: (part: ToolCallPart) => ReactNode;
11
+ /** Highlighted text after the label, e.g. the search query. Defaults to the part's subtitle. */
12
+ subtitle?: (part: ToolCallPart) => ReactNode;
13
+ /** Tooltip on the row label, e.g. the command's description. */
14
+ hint?: (part: ToolCallPart) => string | undefined;
15
+ /** The approval question, e.g. "Add 4 events to your calendar?". Defaults to "Allow <label>?". */
16
+ ask?: (part: ToolCallPart) => ReactNode;
17
+ /** Phrase for the group heading, lowercase: "ran a command", "ran 3 commands". */
18
+ summary?: (count: number) => string;
19
+ /** Body shown when the row is expanded. Defaults to the result, or the input as JSON. */
20
+ Detail?: ComponentType<{
21
+ part: ToolCallPart;
22
+ }>;
23
+ /**
24
+ * Renders the call on its own in the message instead of as a row in the activity group, e.g. the
25
+ * answers to a question or a chart.
26
+ */
27
+ Block?: ComponentType<{
28
+ part: ToolCallPart;
29
+ }>;
30
+ }
31
+ export type ToolViews = Record<string, ToolView>;
32
+ export declare function ToolViewsProvider({ tools, children }: {
33
+ tools: ToolViews;
34
+ children: ReactNode;
35
+ }): import("react").JSX.Element;
36
+ export declare function useToolView(name: string): ToolView;
37
+ /** The provider's views, plus views brought by tools the page registered (`ChatTool.view`). */
38
+ export declare function useToolViews(): ToolViews;
39
+ /** A tool with its look in the chat. Register it with `useChatTool` from the component that owns it. */
40
+ export interface ChatTool<Input = any, Output = unknown> extends AgentTool<Input, Output> {
41
+ view?: ToolView;
42
+ }
43
+ /** `useAgentTool` for tools that bring a view. */
44
+ export declare const useChatTool: <Input, Output>(tool: ChatTool<Input, Output>) => void;
45
+ /** Streaming or running: the model or a tool is busy with it. */
46
+ export declare const isWorking: (part: ActivityPart) => boolean;
47
+ /** Row label for any activity part. */
48
+ export declare function activityLabel(part: ActivityPart, view?: ToolView): ReactNode;
49
+ /**
50
+ * Heading of an activity group. While live it follows the latest step. Once done it lists what the tools
51
+ * did, in order ("Ran a command, searched the web"); a group of only thinking shows the first title.
52
+ */
53
+ export declare function activityHeading(parts: readonly ActivityPart[], views: ToolViews): ReactNode;
54
+ /** A shell command. Input `{ command, description? }`; result a string or `{ stdout, stderr, exitCode }`. */
55
+ export declare const commandTool: ToolView;
56
+ export interface SearchResult {
57
+ title: string;
58
+ url: string;
59
+ /** Icon url. Without one a globe is shown; the kit never fetches icons itself. */
60
+ favicon?: string;
61
+ }
62
+ /** A web search. Input `{ query }`; result `{ results: SearchResult[] }` or `SearchResult[]`. */
63
+ export declare const webSearchTool: ToolView;
64
+ /** Memory lookups, e.g. Anthropic's memory tool. Input `{ path }`. */
65
+ export declare const memoryTool: ToolView;
66
+ export declare const defaultToolViews: ToolViews;
67
+ /** "9s", "1m 5s". */
68
+ export declare function formatDuration(ms: number): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Rehype plugin that wraps each word of prose in `<span class="chat-word">`. CSS fades a span in when it
3
+ * mounts. React keeps the spans already on screen as the text grows, so a word fades once and is never cut
4
+ * short or restarted; words revealed together fade together.
5
+ *
6
+ * Code, math internals and SVG are left alone. A math element as a whole gets the class, so a formula
7
+ * fades in as one piece.
8
+ */
9
+ interface HastNode {
10
+ type: string;
11
+ tagName?: string;
12
+ value?: string;
13
+ properties?: Record<string, unknown> & {
14
+ className?: unknown;
15
+ };
16
+ children?: HastNode[];
17
+ }
18
+ export declare function rehypeWordFade(): (tree: HastNode) => void;
19
+ export {};
@@ -0,0 +1 @@
1
+ export * from "../../core/src/index.js";
@@ -0,0 +1,86 @@
1
+ import type { ToolCallPart } from "../../../core/src/index.js";
2
+ import { type ReactNode } from "react";
3
+ /** JSON Schema for a tool's input, as the model provider expects it. */
4
+ export type JsonSchema = Record<string, unknown>;
5
+ export interface ToolContext {
6
+ callId: string;
7
+ messageId: string;
8
+ /** Aborted when the user stops the chat. */
9
+ signal: AbortSignal;
10
+ }
11
+ /**
12
+ * One tool, as the page that owns it sees it. The same definition covers both kinds:
13
+ *
14
+ * - Client tool (has `execute`): the model calls it, the backend ends the run with the call awaiting, the
15
+ * browser runs `execute` (which can change the page directly) and sends the result back.
16
+ * - Server tool (no `execute`): the backend runs it. `onResult` lets the page react, e.g. refetch the
17
+ * record the tool changed or apply the patch it returned.
18
+ *
19
+ * Either kind can need the user's approval first, and either kind can bring its own look in the chat.
20
+ */
21
+ export interface AgentTool<Input = any, Output = unknown> {
22
+ name: string;
23
+ /** For the model. Sent with each request for client tools; server tools are described by the backend. */
24
+ description?: string;
25
+ inputSchema?: JsonSchema;
26
+ execute?: (input: Input, context: ToolContext) => Output | Promise<Output>;
27
+ /** Ask before running. The backend can also ask, per call, with `needsApproval`. */
28
+ approval?: boolean | ((input: Input) => boolean);
29
+ /**
30
+ * Called whenever a call of this tool changes: input streaming in, awaiting approval, running, done.
31
+ * For previews, e.g. drawing proposed calendar events while the user decides.
32
+ */
33
+ onCall?: (part: ToolCallPart & {
34
+ input?: Input;
35
+ result?: Output;
36
+ }) => void;
37
+ /** Called once when a call of this tool finishes, client or server, done or failed. */
38
+ onResult?: (part: ToolCallPart & {
39
+ input: Input;
40
+ result?: Output;
41
+ }) => void;
42
+ }
43
+ /** What a client tool looks like to the backend: sent with every request so it can offer them to the model. */
44
+ export interface ToolSpec {
45
+ name: string;
46
+ description?: string;
47
+ inputSchema?: JsonSchema;
48
+ }
49
+ /**
50
+ * The tools and context the page currently offers. Components register what they own while mounted, so
51
+ * the model's tools follow what is on screen.
52
+ */
53
+ export declare class AgentRegistry {
54
+ private tools;
55
+ private contexts;
56
+ private listeners;
57
+ private version;
58
+ addTool(holder: {
59
+ current: AgentTool;
60
+ }): () => void;
61
+ setContext(key: string, value: unknown): () => void;
62
+ tool(name: string): AgentTool | undefined;
63
+ list(): AgentTool[];
64
+ /** Client tools, for the request. */
65
+ specs(): ToolSpec[];
66
+ /** Everything the page shared with `useAgentContext`, for the request. */
67
+ context(): Record<string, unknown>;
68
+ subscribe: (listener: () => void) => () => boolean;
69
+ getVersion: () => number;
70
+ private emit;
71
+ }
72
+ /** Holds the tools and context for the chats and the page inside it. */
73
+ export declare function AgentProvider({ registry, children }: {
74
+ registry?: AgentRegistry;
75
+ children: ReactNode;
76
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<AgentRegistry | null>>;
77
+ export declare const useAgentRegistry: () => AgentRegistry | null;
78
+ /** Re-renders when tools or context change, e.g. for an inspector. */
79
+ export declare function useAgentSnapshot(): AgentRegistry | null;
80
+ /**
81
+ * Offers a tool while the calling component is mounted. The latest `tool` is always used, so `execute` can
82
+ * close over current state; it is only re-registered when the name changes.
83
+ */
84
+ export declare function useAgentTool<Input, Output>(tool: AgentTool<Input, Output>): void;
85
+ /** Shares page state with the model, e.g. the form's current values. Sent with each request. */
86
+ export declare function useAgentContext(key: string, value: unknown): void;
@@ -0,0 +1,18 @@
1
+ /** Below this width the sidebar is a drawer over the content (phones). */
2
+ export declare const DRAWER_QUERY = "(max-width: 767.98px)";
3
+ /** Below this width a pinned sidebar starts collapsed (tablet portrait). */
4
+ export declare const NARROW_QUERY = "(max-width: 1023.98px)";
5
+ export declare function useMediaQuery(query: string): boolean;
6
+ export interface SidebarState {
7
+ /** Pinned sidebar shows only icons. */
8
+ collapsed: boolean;
9
+ /** The sidebar is a drawer (small screens). */
10
+ drawer: boolean;
11
+ drawerOpen: boolean;
12
+ /** Collapses / expands the pinned sidebar, or opens / closes the drawer. */
13
+ toggle: () => void;
14
+ /** Closes the drawer after a pick; does nothing for a pinned sidebar. */
15
+ dismiss: () => void;
16
+ }
17
+ export declare const SidebarContext: import("react").Context<SidebarState>;
18
+ export declare const useSidebar: () => SidebarState;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Paces streamed text so it appears at an even rate instead of in network-sized bursts. The reveal speed
3
+ * follows the backlog: it never falls far behind and never stalls. It stops at word ends so a word fades
4
+ * in whole. When `streaming` ends the rest catches up quickly. With reduced motion it returns `text`.
5
+ *
6
+ * One animation loop runs while there is text left to show; new text only moves its target, so frequent
7
+ * small deltas do not restart it. Text is released in groups every CHUNK_MS rather than every frame.
8
+ */
9
+ export declare function useSmoothText(text: string, streaming: boolean): string;
@@ -0,0 +1,15 @@
1
+ import type { SendRequest, Transport } from "./use-chat";
2
+ export interface SseTransportOptions {
3
+ /** POST endpoint. Gets the request as JSON and answers with a `text/event-stream` of events. */
4
+ url: string;
5
+ /** Extra headers, e.g. auth. A function is called per request. */
6
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
7
+ /** POSTed `{ runId }` when the user stops a reply. Without it the stream is only closed. */
8
+ stopUrl?: string;
9
+ /** Defaults to the global `fetch`. */
10
+ fetch?: typeof fetch;
11
+ }
12
+ /** The request body the backend receives: everything in `SendRequest` except the abort signal. */
13
+ export type SseRequestBody = Omit<SendRequest, "signal">;
14
+ /** A `Transport` for backends that stream `StreamEvent`s over server-sent events (see `formatEvent`). */
15
+ export declare function sseTransport({ url, headers, stopUrl, fetch: fetchImpl }: SseTransportOptions): Transport;
@@ -0,0 +1,68 @@
1
+ import { type AttachmentPart, type Message, type StreamEvent, type ToolCallPart } from "../../../core/src/index.js";
2
+ import { type AgentRegistry, type ToolSpec } from "./agent";
3
+ export interface SendRequest {
4
+ conversationId?: string;
5
+ /**
6
+ * The user message just added (the reply's parent), or for a continuation the assistant message whose
7
+ * awaiting tool calls now have results or approvals. The backend carries on that same message.
8
+ */
9
+ message: Message;
10
+ /** The conversation up to and including `message`. */
11
+ history: Message[];
12
+ /** Set after client tools ran or the user answered an approval. */
13
+ continuation?: boolean;
14
+ /** Client tools the page offers right now (`useAgentTool`), for the backend to give the model. */
15
+ tools?: ToolSpec[];
16
+ /** Page state shared with `useAgentContext`. */
17
+ context?: Record<string, unknown>;
18
+ model?: string;
19
+ effort?: string;
20
+ signal: AbortSignal;
21
+ }
22
+ /** How the kit talks to a backend. The kit ships an SSE one; hosts with other backends write an adapter. */
23
+ export interface Transport {
24
+ send(request: SendRequest): AsyncIterable<StreamEvent>;
25
+ /** Tells the server to stop the run. The stream is aborted locally either way. */
26
+ stop?(runId: string): Promise<void>;
27
+ }
28
+ export interface UseChatOptions {
29
+ transport: Transport;
30
+ conversationId?: string;
31
+ initialMessages?: Message[];
32
+ onTitle?: (title: string) => void;
33
+ /** Tools and page context. Defaults to the nearest `AgentProvider`. */
34
+ agent?: AgentRegistry | null;
35
+ /** Continuations allowed per user message, so a looping model stops. Default 12. */
36
+ maxSteps?: number;
37
+ }
38
+ export interface SendOptions {
39
+ model?: string;
40
+ /** Only sent if set; pass `resolveEffort(model, effort)` so models without effort get none. */
41
+ effort?: string;
42
+ /** Shown above the text in the user's message. */
43
+ attachments?: AttachmentPart[];
44
+ }
45
+ /** A tool call waiting for the user to allow or deny it. */
46
+ export interface PendingApproval {
47
+ messageId: string;
48
+ part: ToolCallPart;
49
+ }
50
+ export interface UseChat {
51
+ messages: Message[];
52
+ /** True while a reply streams or client tools run. False while waiting on an approval. */
53
+ busy: boolean;
54
+ /** Tool calls waiting on the user. Sending a new message declines them. */
55
+ approvals: PendingApproval[];
56
+ respond(partId: string, granted: boolean): void;
57
+ send(text: string, options?: SendOptions): void;
58
+ stop(): void;
59
+ /** Drops the assistant reply and asks again from its user message. */
60
+ retry(assistantId: string, options?: SendOptions): void;
61
+ }
62
+ export declare function userMessage(text: string, parentId: string | null, attachments?: AttachmentPart[]): Message;
63
+ /**
64
+ * A linear conversation driven by a Transport: sends, streams the reply through `applyEvent`, stops and
65
+ * retries. With an `AgentProvider` it also runs the agent loop: client tools run in the page, approvals
66
+ * wait for the user, then the reply continues; every finished tool call reaches its tool's `onResult`.
67
+ */
68
+ export declare function useChat({ transport, conversationId, initialMessages, onTitle, agent, maxSteps }: UseChatOptions): UseChat;