@calliopelabs/ui 0.14.0 → 0.16.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 (64) hide show
  1. package/README.md +142 -1
  2. package/dist/agent/AgentChat.d.ts +8 -1
  3. package/dist/agent/AgentChat.js +43 -2
  4. package/dist/agent/WorkflowRenderers.d.ts +61 -0
  5. package/dist/agent/WorkflowRenderers.js +102 -0
  6. package/dist/agent/controller.d.ts +19 -0
  7. package/dist/agent/controller.js +33 -0
  8. package/dist/agent/copilotkit/CopilotKitAgentProvider.js +2 -2
  9. package/dist/agent/index.d.ts +4 -2
  10. package/dist/agent/index.js +3 -1
  11. package/dist/agent/mock.d.ts +26 -4
  12. package/dist/agent/mock.js +68 -37
  13. package/dist/agent/renderers.d.ts +49 -0
  14. package/dist/agent/renderers.js +27 -0
  15. package/dist/appearance/brands.d.ts +17 -0
  16. package/dist/appearance/brands.js +14 -0
  17. package/dist/chat/MessageBubble.js +1 -1
  18. package/dist/chat/MessageList.d.ts +9 -2
  19. package/dist/chat/MessageList.js +11 -8
  20. package/dist/css/appearance.js +1 -1
  21. package/dist/css/calliope.css +5 -0
  22. package/dist/css/tokens.css +5 -0
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +6 -1
  25. package/dist/primitives/Badge.d.ts +14 -0
  26. package/dist/primitives/Badge.js +25 -0
  27. package/dist/primitives/ContextMenu.d.ts +18 -0
  28. package/dist/primitives/ContextMenu.js +34 -0
  29. package/dist/primitives/ScrollArea.d.ts +16 -0
  30. package/dist/primitives/ScrollArea.js +11 -0
  31. package/dist/primitives/Toast.d.ts +37 -0
  32. package/dist/primitives/Toast.js +43 -0
  33. package/dist/primitives/Tooltip.d.ts +16 -0
  34. package/dist/primitives/Tooltip.js +26 -0
  35. package/dist/provider/messages.d.ts +69 -0
  36. package/dist/provider/messages.js +60 -0
  37. package/dist/theme/css-layer.js +2 -1
  38. package/dist/theme/tokens.generated.d.ts +3 -0
  39. package/dist/theme/tokens.generated.js +3 -0
  40. package/dist/workbench/FileBrowser.d.ts +62 -0
  41. package/dist/workbench/FileBrowser.js +117 -0
  42. package/dist/workbench/FileGrid.d.ts +13 -0
  43. package/dist/workbench/FileGrid.js +114 -0
  44. package/dist/workbench/FileIcon.d.ts +10 -0
  45. package/dist/workbench/FileIcon.js +24 -0
  46. package/dist/workbench/FileList.d.ts +35 -0
  47. package/dist/workbench/FileList.js +160 -0
  48. package/dist/workbench/FilePreview.d.ts +66 -0
  49. package/dist/workbench/FilePreview.js +108 -0
  50. package/dist/workbench/FileTree.d.ts +37 -0
  51. package/dist/workbench/FileTree.js +218 -0
  52. package/dist/workbench/UploadDropzone.d.ts +52 -0
  53. package/dist/workbench/UploadDropzone.js +115 -0
  54. package/dist/workbench/dnd.d.ts +17 -0
  55. package/dist/workbench/dnd.js +77 -0
  56. package/dist/workbench/index.d.ts +16 -0
  57. package/dist/workbench/index.js +16 -0
  58. package/dist/workbench/model.d.ts +72 -0
  59. package/dist/workbench/model.js +214 -0
  60. package/dist/workbench/selection.d.ts +16 -0
  61. package/dist/workbench/selection.js +46 -0
  62. package/dist/workbench/typeahead.d.ts +15 -0
  63. package/dist/workbench/typeahead.js +22 -0
  64. package/package.json +5 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@ It is built Storybook first: every component and every screen pattern is built a
16
16
  npm i @calliopelabs/ui
17
17
  ```
18
18
 
19
- Peer dependencies: `react` and `react-dom` 19, and `tailwindcss` 4 for styling.
19
+ Peer dependencies: `react` and `react-dom` 19, and `tailwindcss` 4 for styling. The opt-in agent entries add `@ag-ui/client` and `@copilotkit/react-core` (see [Agent](#agent)).
20
20
 
21
21
  ## Styles
22
22
 
@@ -428,6 +428,147 @@ const chat = useAgUiRun();
428
428
 
429
429
  `agent` above is the host's own client. The catalog's `Chat/ChatPanel` composes the pieces the same way, driven by a recorded run. Their copy is in the `chat` message group.
430
430
 
431
+ ## Agent
432
+
433
+ `CalliopeAgent` is the one embedded agent for every Calliope AI product: a floating bubble, the shell's right rail, inline in a page, or a page of its own. It speaks [AG-UI](https://docs.ag-ui.com) to any agent, directly or through a [CopilotKit](https://docs.copilotkit.ai) runtime, and draws every pixel with the chat kit above, on the product's tokens, mark and runtime brand. CopilotKit wires the runtime and runs the agent; its UI and stylesheet are never loaded.
434
+
435
+ It is an opt-in entry, so apps without an agent never load its dependencies:
436
+
437
+ ```sh
438
+ npm i @ag-ui/client # @calliopelabs/ui/agent
439
+ npm i @ag-ui/client @copilotkit/react-core # @calliopelabs/ui/agent/copilotkit (1.79.1 or later, 1.x)
440
+ ```
441
+
442
+ Both are optional peers. `npm run pack:check` fails if the main entry ever reaches them. Most of the entry's weight is `@ag-ui/client` itself (with its protobuf codec, about 150 KB gzipped; the agent's own code is about 24 KB minified), which a CopilotKit app already ships. To keep it off a page's first load, load the provider when the person first opens the assistant (`next/dynamic` with `ssr: false`) and keep it mounted after.
443
+
444
+ ### A Next.js app with a CopilotKit runtime
445
+
446
+ ```ts
447
+ // app/api/copilotkit/route.ts: the runtime, on the server (agents, keys and auth stay here)
448
+ import { HttpAgent } from "@ag-ui/client";
449
+ import { CopilotRuntime, createCopilotRuntimeHandler } from "@copilotkit/runtime/v2";
450
+
451
+ const runtime = new CopilotRuntime({
452
+ agents: { default: new HttpAgent({ url: process.env.AGENT_URL! }) },
453
+ });
454
+ const handler = createCopilotRuntimeHandler({
455
+ runtime,
456
+ basePath: "/api/copilotkit",
457
+ mode: "single-route",
458
+ });
459
+ export const POST = (request: Request) => handler(request);
460
+ ```
461
+
462
+ ```tsx
463
+ // app/assistant.tsx
464
+ "use client";
465
+ import { CalliopeAgent, useAgentReadable, useAgentTool } from "@calliopelabs/ui/agent";
466
+ import { CopilotKitAgentProvider } from "@calliopelabs/ui/agent/copilotkit";
467
+ import { z } from "zod";
468
+
469
+ export function Assistant({ children, project }: { children: React.ReactNode; project: string }) {
470
+ return (
471
+ <CopilotKitAgentProvider
472
+ runtimeUrl="/api/copilotkit"
473
+ useSingleEndpoint
474
+ headers={{ "x-project": project }}
475
+ >
476
+ {children}
477
+ <CalliopeAgent mode="bubble" persistKey="app.assistant.open" />
478
+ </CopilotKitAgentProvider>
479
+ );
480
+ }
481
+
482
+ // Anywhere under it, while mounted:
483
+ useAgentReadable("The open project", { project });
484
+ useAgentTool({
485
+ name: "open_scene",
486
+ parameters: z.object({ id: z.string() }),
487
+ handler: ({ id }) => router.push(`/scenes/${id}`),
488
+ });
489
+ ```
490
+
491
+ Mount it from the root layout (a Server Component) as `<Assistant project={slug}>{children}</Assistant>`. Tools carry functions, so they are registered in client components, never passed from a server one.
492
+
493
+ ### A plain AG-UI endpoint
494
+
495
+ ```tsx
496
+ "use client";
497
+ import { CalliopeAgent, CalliopeAgentProvider } from "@calliopelabs/ui/agent";
498
+
499
+ <CalliopeAgentProvider url="/api/agui" headers={{ "x-project": slug }} suggestions={startingPoints}>
500
+ <AppShell
501
+ {...shell}
502
+ rightRail={<CalliopeAgent mode="rail" open={open} onOpenChange={setOpen} />}
503
+ rightRailLabel="Assistant"
504
+ onToggleRightRail={() => setOpen((o) => !o)}
505
+ >
506
+ {children}
507
+ </AppShell>
508
+ </CalliopeAgentProvider>;
509
+ ```
510
+
511
+ `url` makes an `HttpAgent` (same origin, so the session cookie goes along); `agent` takes any `AbstractAgent` instead. `createFunctionAgent(run)` turns a function that yields AG-UI events (or an existing non-AG-UI endpoint, adapted) into one.
512
+
513
+ ### What it does
514
+
515
+ - **One conversation, one store.** `useCalliopeAgent()` returns the chat kit's `ChatView` (messages, tool calls, steps, interrupts, shared state, custom events) and the actions: `send(text, { parts })`, `stop()` (aborts the run and any tool waiting), `regenerate()`, `approve(call)` and `deny(call, reason)`, `resolveInterrupt(id, response)`, `respond(toolCallId, result)`, `getState()` and `setState(state)`, `switchThread(id)` (with the provider's `loadThread`) and `newThread()`, plus `running`, `waiting`, `unread` and `suggestions`.
516
+ - **Tools.** `useAgentTool(defineTool({ name, description, parameters, handler, render, followUp }))` offers a browser tool while the component is mounted. `parameters` is a Standard Schema (zod 4, valibot, arktype), which types and validates the arguments, or a JSON Schema. A tool with `render` and no `handler` is human-in-the-loop: the run waits until its card calls `respond(result)`, and the composer holds meanwhile.
517
+ - **Readables and suggestions.** `useAgentReadable(description, value)` sends page context with every run; `useAgentSuggestions([{ title, message }])` offers starting points. Both also come as provider props for the whole app.
518
+ - **Approvals.** A run that ends on an AG-UI interrupt pauses. A tool call it names gets approve and deny on its card (a denial always with a reason); any other interrupt gets `InterruptCard`, or the host's `renderers.interrupts[reason]`. The run resumes with AG-UI `resume` once every open interrupt has an answer; a new message cancels what is still open.
519
+ - **Generative UI.** `renderers` on `CalliopeAgent` maps tool names (backend tools too), `CUSTOM` event names and interrupt reasons to host components, and draws the shared state above the conversation. `workflowRenderers` draws the run and review components: `calliope.run` on `RunTimeline` (events with one `id` update one card), `calliope.artifact` on `ArtifactCard` with `DocumentReview`, `MediaReview` or `FormReview` (signing or sending the review resumes the run), and `calliope.question` interrupts on `WorkflowQuestionCard`.
520
+ - **Surfaces.** `mode="bubble"` (a launcher and a non-modal panel; a drawer on phones), `"rail"` (a `RailPanel` for `AppShell`'s `rightRail`; the shell's drawer on phones), `"inline"` and `"page"`. Open state is controlled, held, or remembered per person with `persistKey`; `shortcut` (`mod+/` by default) toggles it; answers that land while it is closed show as unread on the launcher. Slots: `renderContent` (the host's markdown), `attachments` and `sendOptions` (attachments as AG-UI input parts), `composerActions`, `modelPicker` (pass the choice in the provider's `forwardedProps`), `headerActions`, `empty`.
521
+ - **Telemetry and errors.** `onEvent(event)` sees every AG-UI event; `onError(error)` every failed run (also shown in the conversation with retry).
522
+ - **Testing.** `createMockAgent({ turns })` plays recorded turns; `createFunctionAgent(run)` makes an agent of any function that yields AG-UI events. Both are real `HttpAgent`s answered in the page, so stories and tests need no network.
523
+
524
+ Headers on the provider run in the browser: never put a secret there. Keys, the model and authorization stay on the server, in the runtime route or the agent. Copy is in the `agent` message group.
525
+
526
+ ### Moving to it
527
+
528
+ | Product | Today | With `CalliopeAgent` |
529
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
530
+ | Astrolift Studio | `CopilotKitProvider` (1.79.1) and its own `AssistantPanel`, `AssistantTranscript`, `AssistantInput`, `SuggestionChips`, `AssistantLauncher`, `dock.tsx`, `useStored` open state | `CopilotKitAgentProvider runtimeUrl="/api/assistant" useSingleEndpoint agentId="studio" headers={{ [PROJECT_HEADER]: slug }}` (or keep its `CopilotKitProvider`: ours reuses it) and `<CalliopeAgent mode="rail" persistKey={openKey}>` in the shell's rail. The 15 `useHumanInTheLoop` actions become `useAgentTool({ name, parameters, render })` with `ActionCard` as `render` (calling `respond`); the whiteboard tools become `useAgentTool` with their zod schemas; `useAgentContext` readables become `useAgentReadable`; `startingPoints` becomes `useAgentSuggestions`; thread loading becomes `loadThread`. The server route and runner stay. |
531
+ | Zentinelle | `ChatBubble` (sparkle button, `Drawer`), `use-chat` parsing a custom `data:` stream, `MessageList`, `ChatInput`, `ModelSelector`, full-page `AssistantScreen` | `<CalliopeAgent mode="bubble">` and `mode="page"`, with `modelPicker` fed from `/assistant/providers` and the model in `forwardedProps`. Transport: port `/assistant/chat` to AG-UI (`pending_action` as an interrupt, `navigation` as a `CUSTOM` event drawn as a link chip), or until then adapt it with `createFunctionAgent` over `authenticatedFetch`. An approval resolves the interrupt; the host's `execute-tool` call with the approval token stays a host action in the interrupt renderer, gated on `canMutate`. |
532
+ | Astrolift app (Wayfinding) | `WayfindingBubble` and `use-wayfinding` posting to `/wayfinding/ask/` | `<CalliopeAgent mode="bubble">` over `createFunctionAgent` that posts the question and returns the answer as text and the routes as a `CUSTOM` event, drawn by a `renderers.custom.routes` component of `Link` chips that close the panel. No tools: read-only by construction. A 503 thrown from the function shows as the run's error. |
533
+ | Boilerworks django-nextjs-copilotkit | CopilotKit 1.63.1 v1 (`CopilotKit`, `CopilotSidebar`, `useCopilotAction`, `useCopilotReadable`, `react-ui` CSS re-themed) | Upgrade to `@copilotkit/react-core` 1.79+ and `@copilotkit/runtime/v2` (the runtime route keeps its `HttpAgent` to Django), then `CopilotKitAgentProvider runtimeUrl="/api/copilotkit" agentId="boilerworks_agent"` and `<CalliopeAgent mode="rail">` in place of `CopilotSidebar`. `useCopilotReadable` becomes `useAgentReadable`; `renderAndWaitForResponse` becomes `useAgentTool` with `render` and `respond`; the render-only `draft_form_definition` card becomes `renderers.tools.draft_form_definition`. Drop `@copilotkit/react-ui` and `copilot.css`. |
534
+
535
+ ## Workbench
536
+
537
+ `@calliopelabs/ui/workbench` is an opt-in entry for IDE-style tools (File Manager, Loadr, Chat Studio): an app without a file browser never loads it. It needs nothing beyond the package's own dependencies, and its styles come from the preset. Like the rest, it is host-driven: data comes in as props, every change goes out through a callback, and nothing fetches.
538
+
539
+ - **`FileBrowser`**: breadcrumb (its crumbs are drop targets), list and grid views, sorting (folders first, natural order), selection, a context menu, drag-and-drop moves (Alt copies) and an optional folder tree. View, sort and selection work uncontrolled or controlled. Copy for an empty folder, a toolbar slot, and thumbnails for grid tiles.
540
+ - **`FileTree`**: a lazy tree on the WAI-ARIA tree pattern (arrows open and move, Home, End, typeahead). The host answers `onLoad(path)` with a `FileBranch` (`loading`, `error`, `ready`) per directory in `branches`; a failed branch retries through `onRetry`.
541
+ - **`FileList`** and **`FileGrid`**, the two views on their own: a grid (WAI-ARIA grid pattern: arrows between cells, selection follows focus, Shift extends, Cmd/Ctrl moves without selecting, Cmd/Ctrl+A, Escape, Enter opens, Delete, F2 renames) and a multi-select listbox of tiles with two-dimensional arrows.
542
+ - **`UploadDropzone`**: wraps an area (an overlay while desktop files hover, progress floating in a corner) or draws its own box with a picker. `accept` and `maxSize` filter into `onFiles` and `onReject`; the host's `uploads` (`queued`, `uploading` with a percentage or none, `done`, `error`, `canceled`) draw as progress bars with cancel and dismiss.
543
+ - **`FilePreview`**: CSV and TSV (parsed here, quoted fields included) and server-parsed tables in the library's `DataTable` with search, sort and paging; JSON pretty-printed; images; text; markdown through the host's `renderMarkdown` (the package ships no markdown parser, so without one the source shows); a binary fallback, loading, error and a truncation note.
544
+ - Helpers: `FileItem`, `sortFiles`, `previewKindFor`, `parseDelimited`, `formatBytes`, `canDrop`, `pathSegments`, `parentPath`, `joinPath`, `FileIcon`.
545
+
546
+ ```tsx
547
+ import {
548
+ FileBrowser,
549
+ FilePreview,
550
+ UploadDropzone,
551
+ previewKindFor,
552
+ } from "@calliopelabs/ui/workbench";
553
+
554
+ <UploadDropzone onFiles={(files) => upload(files, path)} uploads={uploads} onCancel={cancel}>
555
+ <FileBrowser
556
+ path={path}
557
+ items={listing.data?.entries.map(toFileItem)}
558
+ error={listing.error}
559
+ onRetry={listing.refetch}
560
+ onNavigate={setPath}
561
+ onOpen={(item) => setPreview(item.path)}
562
+ onMove={(sources, target, { copy }) => (copy ? copyTo : moveTo)(sources, target)}
563
+ onDelete={confirmDelete}
564
+ contextMenu={(targets) => <FileActions targets={targets} />}
565
+ tree={{ root, branches, onLoad: loadFolder, onRetry: loadFolder }}
566
+ />
567
+ </UploadDropzone>;
568
+ ```
569
+
570
+ Their copy is in the `workbench` message group. The catalog's **Workbench** section shows every state, light and dark, narrow, and large trees.
571
+
431
572
  ## Appearance
432
573
 
433
574
  The person sets four axes, per product: the **ground** (black, charcoal, graphite, emerald, navy, paper, mist; it decides light or dark), the **accent** (the product's own, a family accent, or any colour that stands 3:1 off the ground), the **density** (`compact` hairline rows or boxed `cards`) and the **corners** (0, 2, 4 or 10 px). `AppearancePicker` is the settings UI for all four.
@@ -3,6 +3,7 @@ import type { ChatContentRenderer } from "../chat/MessageBubble.js";
3
3
  import { type ChatToolCall } from "../chat/model.js";
4
4
  import { type ModelPickerProps } from "../chat/ModelPicker.js";
5
5
  import type { SendOptions } from "./controller.js";
6
+ import { type AgentRenderers } from "./renderers.js";
6
7
  export interface AgentChatProps {
7
8
  /** The panel's title. Defaults to the product's assistant ("Astrolift AI assistant"). */
8
9
  title?: string;
@@ -23,6 +24,12 @@ export interface AgentChatProps {
23
24
  renderToolCall?: (call: ChatToolCall) => React.ReactNode;
24
25
  /** The tool's name for people, per call. */
25
26
  toolTitle?: (call: ChatToolCall) => string | undefined;
27
+ /**
28
+ * Generative UI: host components for tool calls, custom events, interrupts
29
+ * and the shared state (see `AgentRenderers`; `workflowRenderers` draws the
30
+ * run and review components). `renderToolCall` still wins for a call.
31
+ */
32
+ renderers?: AgentRenderers;
26
33
  /** "line": a one-line status while running (bubble, rail). "steps": the run's steps on the run timeline (page). */
27
34
  progress?: "line" | "steps" | "none";
28
35
  /** Replaces the empty state (the suggestions still show under it). */
@@ -53,4 +60,4 @@ export declare function NewConversationButton({ className }: {
53
60
  * their own calls; one that waits on the person gets `respond`, and the
54
61
  * composer holds until it is answered.
55
62
  */
56
- export declare function AgentChat({ title, hideHeader, headerActions, onClose, expanded, onExpandedChange, hideNewConversation, renderContent, renderToolCall, toolTitle, progress, empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun, className, }: AgentChatProps): React.JSX.Element;
63
+ export declare function AgentChat({ title, hideHeader, headerActions, onClose, expanded, onExpandedChange, hideNewConversation, renderContent, renderToolCall, toolTitle, renderers, progress, empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun, className, }: AgentChatProps): React.JSX.Element;
@@ -14,6 +14,8 @@ import { useUI } from "../provider/UIProvider.js";
14
14
  import { EmptyState } from "../states/EmptyState.js";
15
15
  import { StatusDot } from "../status/StatusDot.js";
16
16
  import { useCalliopeAgent } from "./AgentProvider.js";
17
+ import { parseResult } from "./renderers.js";
18
+ import { InterruptCard } from "./WorkflowRenderers.js";
17
19
  function ToolRender({ tool, call, waiting, respond, }) {
18
20
  return (_jsx(_Fragment, { children: tool.render?.({
19
21
  call,
@@ -43,7 +45,7 @@ function Suggestions({ suggestions, disabled, onPick, }) {
43
45
  * their own calls; one that waits on the person gets `respond`, and the
44
46
  * composer holds until it is answered.
45
47
  */
46
- export function AgentChat({ title, hideHeader = false, headerActions, onClose, expanded, onExpandedChange, hideNewConversation = false, renderContent, renderToolCall, toolTitle, progress = "line", empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun = false, className, }) {
48
+ export function AgentChat({ title, hideHeader = false, headerActions, onClose, expanded, onExpandedChange, hideNewConversation = false, renderContent, renderToolCall, toolTitle, renderers, progress = "line", empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun = false, className, }) {
47
49
  const agent = useCalliopeAgent();
48
50
  const { messages } = useUI();
49
51
  const product = useProduct();
@@ -64,13 +66,52 @@ export function AgentChat({ title, hideHeader = false, headerActions, onClose, e
64
66
  const custom = renderToolCall?.(call);
65
67
  if (custom !== undefined)
66
68
  return custom;
69
+ const Renderer = renderers?.tools?.[call.name];
70
+ if (Renderer) {
71
+ return (_jsx(Renderer, { call: call, args: parseToolCallArgs(call.args), result: parseResult(call), agent: agent, ...(waiting.includes(call.id)
72
+ ? { respond: (result) => agent.respond(call.id, result) }
73
+ : {}) }));
74
+ }
67
75
  const tool = tools.find((t) => t.name === call.name && t.render);
68
76
  if (!tool)
69
77
  return undefined;
70
78
  return (_jsx(ToolRender, { tool: tool, call: call, waiting: waiting.includes(call.id), respond: agent.respond }));
71
79
  };
80
+ // Custom events, each after the timeline item it arrived after (the first, when before any).
81
+ const first = view.timeline[0];
82
+ const firstKey = first ? `${first.kind}:${first.id}` : null;
83
+ const drawAfter = (item) => {
84
+ const key = `${item.kind}:${item.id}`;
85
+ const here = agent.custom.filter((c) => renderers?.custom?.[c.name] && (c.after ?? firstKey) === key);
86
+ if (here.length === 0)
87
+ return null;
88
+ return here.map((c) => {
89
+ const Renderer = renderers.custom[c.name];
90
+ return (_jsx("div", { "data-slot": "agent-custom", className: "min-w-0", children: _jsx(Renderer, { name: c.name, value: c.value, history: c.history, agent: agent }) }, c.key));
91
+ });
92
+ };
93
+ // Interrupts that no tool card or artifact answers: drawn after the conversation.
94
+ const claimed = new Set(agent.custom.flatMap((c) => {
95
+ const id = c.value?.interruptId;
96
+ return renderers?.custom?.[c.name] && typeof id === "string" ? [id] : [];
97
+ }));
98
+ const loose = view.status === "interrupted"
99
+ ? view.interrupts.filter((i) => !i.toolCallId && !claimed.has(i.id))
100
+ : [];
101
+ const footer = loose.length > 0
102
+ ? loose.map((interrupt) => {
103
+ const Renderer = renderers?.interrupts?.[interrupt.reason] ?? InterruptCard;
104
+ return (_jsx(Renderer, { interrupt: interrupt, agent: agent, resolve: (payload) => agent.resolveInterrupt(interrupt.id, {
105
+ status: "resolved",
106
+ ...(payload === undefined ? {} : { payload }),
107
+ }), cancel: () => agent.resolveInterrupt(interrupt.id, { status: "cancelled" }) }, interrupt.id));
108
+ })
109
+ : undefined;
110
+ const StateView = renderers?.state;
72
111
  const step = [...view.steps].reverse().find((s) => s.status === "running");
73
- return (_jsxs("section", { ref: root, "aria-labelledby": hideHeader ? undefined : headingId, "aria-label": hideHeader ? name : undefined, "data-slot": "agent-chat", className: cn("flex min-h-0 min-w-0 flex-col bg-card text-card-foreground", className), children: [!hideHeader && (_jsxs("div", { className: "flex min-w-0 items-center gap-1 border-b px-3 py-2", children: [_jsx("h2", { id: headingId, className: "min-w-0 flex-1 truncate text-sm font-semibold", children: name }), headerActions, !hideNewConversation && _jsx(NewConversationButton, {}), onExpandedChange && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": expanded ? t.restore : t.expand, title: expanded ? t.restore : t.expand, onClick: () => onExpandedChange(!expanded), children: expanded ? _jsx(Minimize2Icon, { "aria-hidden": true }) : _jsx(Maximize2Icon, { "aria-hidden": true }) })), onClose && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": t.close, title: t.close, onClick: onClose, children: _jsx(XIcon, { "aria-hidden": true }) }))] })), progress === "steps" && view.steps.length > 0 && (_jsx(AgentProgress, { view: view, className: "border-b px-3 py-2" })), _jsx(MessageList, { view: view, renderContent: renderContent, renderToolCall: drawCall, toolTitle: toolTitle, onApprove: (call) => agent.approve(call), onDeny: (call, reason) => agent.deny(call, reason), onRetry: () => void agent.regenerate(), empty: _jsxs("div", { className: "flex flex-col items-center gap-4 py-6", children: [empty ?? _jsx(EmptyState, { title: t.emptyTitle, description: t.emptyDescription }), _jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })] }), className: "min-h-0 flex-1" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [suggestionsAfterRun && !fresh && !running && (_jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })), _jsx("p", { role: "status", "data-slot": "agent-status", className: cn("flex min-w-0 items-center gap-2 text-xs text-muted-foreground", !(progress === "line" && running) && !blocked && "sr-only"), children: blocked ? (t.waitingOnYou) : running ? (_jsxs(_Fragment, { children: [_jsx(StatusDot, { tone: "info", pulse: true }), _jsx("span", { className: "truncate", children: step ? step.name : messages.chat.runStatus("running") })] })) : null }), _jsx(Composer, { onSubmit: send, running: running, onStop: agent.stop, disabled: blocked, placeholder: placeholder,
112
+ return (_jsxs("section", { ref: root, "aria-labelledby": hideHeader ? undefined : headingId, "aria-label": hideHeader ? name : undefined, "data-slot": "agent-chat", className: cn("flex min-h-0 min-w-0 flex-col bg-card text-card-foreground", className), children: [!hideHeader && (_jsxs("div", { className: "flex min-w-0 items-center gap-1 border-b px-3 py-2", children: [_jsx("h2", { id: headingId, className: "min-w-0 flex-1 truncate text-sm font-semibold", children: name }), headerActions, !hideNewConversation && _jsx(NewConversationButton, {}), onExpandedChange && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": expanded ? t.restore : t.expand, title: expanded ? t.restore : t.expand, onClick: () => onExpandedChange(!expanded), children: expanded ? _jsx(Minimize2Icon, { "aria-hidden": true }) : _jsx(Maximize2Icon, { "aria-hidden": true }) })), onClose && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": t.close, title: t.close, onClick: onClose, children: _jsx(XIcon, { "aria-hidden": true }) }))] })), progress === "steps" && view.steps.length > 0 && (_jsx(AgentProgress, { view: view, className: "border-b px-3 py-2" })), StateView && view.state !== undefined && (_jsx("div", { role: "region", "aria-label": t.sharedState, "data-slot": "agent-state",
113
+ // A scrolling region is reached by keyboard (WCAG 2.1.1).
114
+ tabIndex: 0, className: "max-h-48 min-w-0 shrink-0 overflow-y-auto border-b px-3 py-2", children: _jsx(StateView, { state: view.state, setState: agent.setState, agent: agent }) })), _jsx(MessageList, { view: view, renderContent: renderContent, renderToolCall: drawCall, renderAfter: drawAfter, footer: footer, toolTitle: toolTitle, onApprove: (call) => agent.approve(call), onDeny: (call, reason) => agent.deny(call, reason), onRetry: () => void agent.regenerate(), empty: _jsxs("div", { className: "flex flex-col items-center gap-4 py-6", children: [empty ?? _jsx(EmptyState, { title: t.emptyTitle, description: t.emptyDescription }), _jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })] }), className: "min-h-0 flex-1" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [suggestionsAfterRun && !fresh && !running && (_jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })), _jsx("p", { role: "status", "data-slot": "agent-status", className: cn("flex min-w-0 items-center gap-2 text-xs text-muted-foreground", !(progress === "line" && running) && !blocked && "sr-only"), children: blocked ? (t.waitingOnYou) : running ? (_jsxs(_Fragment, { children: [_jsx(StatusDot, { tone: "info", pulse: true }), _jsx("span", { className: "truncate", children: step ? step.name : messages.chat.runStatus("running") })] })) : null }), _jsx(Composer, { onSubmit: send, running: running, onStop: agent.stop, disabled: blocked, placeholder: placeholder,
74
115
  // eslint-disable-next-line jsx-a11y/no-autofocus -- the person just opened the assistant to type
75
116
  autoFocus: autoFocus, attachments: attachments, actions: modelPicker || composerActions ? (_jsxs(_Fragment, { children: [modelPicker && _jsx(ModelPicker, { ...modelPicker }), composerActions] })) : undefined })] })] }));
76
117
  }
@@ -0,0 +1,61 @@
1
+ import * as React from "react";
2
+ import { type DocumentReviewProps } from "../workflow/DocumentReview.js";
3
+ import { type FormReviewProps } from "../workflow/FormReview.js";
4
+ import { type MediaReviewProps } from "../workflow/MediaReview.js";
5
+ import type { WorkflowQuestion } from "../workflow/question.js";
6
+ import type { Artifact, RunStage } from "../workflow/run.js";
7
+ import type { AgentRenderers, CustomRendererProps, InterruptRendererProps } from "./renderers.js";
8
+ /** `calliope.run`: a run's stages as the agent reports them. Send it again with the same `id` to update. */
9
+ export interface RunProgressValue {
10
+ id?: string;
11
+ label?: string;
12
+ stages: readonly RunStage[];
13
+ }
14
+ /** The review viewer an artifact opens in. */
15
+ export type ArtifactReviewValue = ({
16
+ viewer: "document";
17
+ } & Pick<DocumentReviewProps, "title" | "pages" | "annotations" | "signOffs" | "signOffRole">) | ({
18
+ viewer: "media";
19
+ } & Pick<MediaReviewProps, "kind" | "src" | "title" | "poster" | "captions" | "duration" | "comments">) | ({
20
+ viewer: "form";
21
+ } & Pick<FormReviewProps, "title" | "pages" | "fields" | "threshold">);
22
+ /**
23
+ * `calliope.artifact`: an output for the person to look at, and to review
24
+ * when the run waits on it. `interruptId` names the run's interrupt the
25
+ * review answers: signing a document resolves it with `{ action: "sign-off" }`,
26
+ * sending a form's review with `{ action: "form-review", fields }`.
27
+ */
28
+ export interface ArtifactValue {
29
+ id: string;
30
+ artifact: Artifact;
31
+ review?: ArtifactReviewValue;
32
+ interruptId?: string;
33
+ }
34
+ /** `calliope.question` interrupts carry a question in `metadata.question`. */
35
+ export interface QuestionInterruptMetadata {
36
+ question: WorkflowQuestion;
37
+ }
38
+ /** The run timeline for `calliope.run` events: progress drawn in the conversation. */
39
+ export declare function RunProgressCard({ value }: CustomRendererProps<RunProgressValue>): React.JSX.Element;
40
+ /**
41
+ * An artifact the agent produced, with its review viewer (document, media or
42
+ * form) as the preview. While the run waits on its review, signing off or
43
+ * sending the form's review answers the interrupt and resumes the run.
44
+ */
45
+ export declare function ArtifactReviewCard({ value, agent }: CustomRendererProps<ArtifactValue>): React.JSX.Element;
46
+ /** A `calliope.question` interrupt: the runtime question card, its answers resuming the run. */
47
+ export declare function QuestionInterruptCard({ interrupt, resolve }: InterruptRendererProps): React.JSX.Element;
48
+ /**
49
+ * Any other open interrupt not tied to a tool call: the agent's question in
50
+ * its words, with approve and decline (a decline always with a reason).
51
+ */
52
+ export declare function InterruptCard({ interrupt, resolve, className, }: InterruptRendererProps & {
53
+ className?: string;
54
+ }): React.JSX.Element;
55
+ /**
56
+ * The workflow run and review components as generative UI: `calliope.run`
57
+ * custom events draw the run timeline, `calliope.artifact` events draw the
58
+ * artifact with its document, media or form review, and `calliope.question`
59
+ * interrupts draw the runtime question card. Merge with the host's own.
60
+ */
61
+ export declare const workflowRenderers: AgentRenderers;
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Button } from "../primitives/Button.js";
6
+ import { useUI } from "../provider/UIProvider.js";
7
+ import { ArtifactCard } from "../workflow/ArtifactCard.js";
8
+ import { DocumentReview } from "../workflow/DocumentReview.js";
9
+ import { FormReview } from "../workflow/FormReview.js";
10
+ import { MediaReview } from "../workflow/MediaReview.js";
11
+ import { RunTimeline } from "../workflow/RunTimeline.js";
12
+ import { WorkflowQuestionCard } from "../workflow/WorkflowQuestionCard.js";
13
+ /** The run timeline for `calliope.run` events: progress drawn in the conversation. */
14
+ export function RunProgressCard({ value }) {
15
+ return (_jsx("div", { className: "min-w-0 rounded-lg border bg-card p-3", children: _jsx(RunTimeline, { stages: value.stages, label: value.label }) }));
16
+ }
17
+ function openInterrupt(agent, id) {
18
+ if (!id || agent.view.status !== "interrupted")
19
+ return undefined;
20
+ return agent.view.interrupts.find((i) => i.id === id);
21
+ }
22
+ /**
23
+ * An artifact the agent produced, with its review viewer (document, media or
24
+ * form) as the preview. While the run waits on its review, signing off or
25
+ * sending the form's review answers the interrupt and resumes the run.
26
+ */
27
+ export function ArtifactReviewCard({ value, agent }) {
28
+ const { messages } = useUI();
29
+ const waiting = openInterrupt(agent, value.interruptId);
30
+ const [fields, setFields] = React.useState({});
31
+ const resolve = (payload) => waiting && agent.resolveInterrupt(waiting.id, { status: "resolved", payload });
32
+ const review = value.review;
33
+ let preview = null;
34
+ if (review?.viewer === "document") {
35
+ const { viewer: _, ...props } = review;
36
+ preview = (_jsx(DocumentReview, { ...props, ...(waiting
37
+ ? { onSignOff: () => resolve({ action: "sign-off", artifactId: value.id }) }
38
+ : {}) }));
39
+ }
40
+ else if (review?.viewer === "media") {
41
+ const { viewer: _, ...props } = review;
42
+ preview = _jsx(MediaReview, { ...props });
43
+ }
44
+ else if (review?.viewer === "form") {
45
+ const { viewer: _, ...props } = review;
46
+ const current = props.fields.map((f) => fields[f.id] ?? f);
47
+ preview = (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsx(FormReview, { ...props, fields: current, ...(waiting
48
+ ? {
49
+ onAccept: (field) => setFields((s) => ({ ...s, [field.id]: { ...field, status: "accepted" } })),
50
+ onCorrect: (field, corrected) => setFields((s) => ({
51
+ ...s,
52
+ [field.id]: { ...field, status: "corrected", corrected },
53
+ })),
54
+ }
55
+ : {}) }), waiting && (_jsx(Button, { size: "sm", className: "self-end", onClick: () => resolve({
56
+ action: "form-review",
57
+ artifactId: value.id,
58
+ fields: current.map(({ id, status, corrected }) => ({ id, status, corrected })),
59
+ }), children: messages.agent.sendReview }))] }));
60
+ }
61
+ return _jsx(ArtifactCard, { artifact: value.artifact, preview: preview, headingLevel: "h3" });
62
+ }
63
+ /** A `calliope.question` interrupt: the runtime question card, its answers resuming the run. */
64
+ export function QuestionInterruptCard({ interrupt, resolve }) {
65
+ const question = interrupt.metadata?.question;
66
+ const [state, setState] = React.useState("waiting");
67
+ return (_jsx(WorkflowQuestionCard, { question: question ?? null, state: state, onSubmit: (submission) => {
68
+ setState("submitting");
69
+ resolve({ answers: submission.answers, revision: submission.revision });
70
+ } }));
71
+ }
72
+ /**
73
+ * Any other open interrupt not tied to a tool call: the agent's question in
74
+ * its words, with approve and decline (a decline always with a reason).
75
+ */
76
+ export function InterruptCard({ interrupt, resolve, className, }) {
77
+ const { messages } = useUI();
78
+ const t = messages.agent;
79
+ const [declining, setDeclining] = React.useState(false);
80
+ const [reason, setReason] = React.useState("");
81
+ const reasonId = React.useId();
82
+ return (_jsxs("section", { "aria-label": t.interrupt, "data-slot": "agent-interrupt", className: cn("flex min-w-0 flex-col gap-2 rounded-lg border border-warning-border bg-warning-bg p-3 text-sm text-warning-fg", className), children: [_jsx("p", { className: "[overflow-wrap:anywhere]", children: interrupt.message ?? messages.chat.approvalNeeded }), declining ? (_jsxs("form", { className: "flex min-w-0 flex-col gap-2", onSubmit: (event) => {
83
+ event.preventDefault();
84
+ if (reason.trim())
85
+ resolve({ approved: false, reason: reason.trim() });
86
+ }, children: [_jsx("label", { htmlFor: reasonId, className: "text-xs font-medium", children: messages.chat.denyReason }), _jsx("textarea", { id: reasonId, value: reason, onChange: (event) => setReason(event.target.value), rows: 2, className: "min-w-0 rounded-md border border-input bg-background p-2 text-sm text-foreground" }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setDeclining(false), children: t.back }), _jsx(Button, { type: "submit", variant: "destructive", size: "sm", disabled: !reason.trim(), children: t.decline })] })] })) : (_jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setDeclining(true), children: t.decline }), _jsx(Button, { size: "sm", onClick: () => resolve({ approved: true }), children: messages.chat.approve })] }))] }));
87
+ }
88
+ /**
89
+ * The workflow run and review components as generative UI: `calliope.run`
90
+ * custom events draw the run timeline, `calliope.artifact` events draw the
91
+ * artifact with its document, media or form review, and `calliope.question`
92
+ * interrupts draw the runtime question card. Merge with the host's own.
93
+ */
94
+ export const workflowRenderers = {
95
+ custom: {
96
+ "calliope.run": RunProgressCard,
97
+ "calliope.artifact": ArtifactReviewCard,
98
+ },
99
+ interrupts: {
100
+ "calliope.question": QuestionInterruptCard,
101
+ },
102
+ };
@@ -38,6 +38,20 @@ export interface AgentControllerOptions {
38
38
  /** Every AG-UI event, for the host's telemetry. */
39
39
  onEvent?: (event: AgUiEvent) => void;
40
40
  }
41
+ /**
42
+ * A custom event as the conversation draws it: events whose value carries
43
+ * the same `id` are one item (progress updating in place), placed where the
44
+ * first of them arrived.
45
+ */
46
+ export interface AgentCustomItem {
47
+ key: string;
48
+ name: string;
49
+ /** The latest value. */
50
+ value: unknown;
51
+ history: readonly unknown[];
52
+ /** The timeline item it arrived after ("message:<id>", "tool-call:<id>"), or null before any. */
53
+ after: string | null;
54
+ }
41
55
  export interface AgentSnapshot {
42
56
  view: ChatView;
43
57
  /** True from send until the agent is done, follow-ups and browser tools included. */
@@ -49,6 +63,8 @@ export interface AgentSnapshot {
49
63
  unread: number;
50
64
  suggestions: readonly AgentSuggestion[];
51
65
  tools: readonly AgentTool[];
66
+ /** The conversation's custom events, grouped and placed (see `AgentCustomItem`). */
67
+ custom: readonly AgentCustomItem[];
52
68
  }
53
69
  export interface SendOptions {
54
70
  /** Attachments and other parts after the text (AG-UI input content). */
@@ -76,6 +92,8 @@ export declare class AgentController {
76
92
  private visible;
77
93
  private read;
78
94
  private snapshot;
95
+ /** Where each custom event arrived, by its index in the view. */
96
+ private anchors;
79
97
  private disposed;
80
98
  private connected;
81
99
  private threadSeq;
@@ -97,6 +115,7 @@ export declare class AgentController {
97
115
  private emit;
98
116
  private finishedAnswers;
99
117
  private build;
118
+ private customItems;
100
119
  private bind;
101
120
  /** The view the agent's own messages and state make, for a fresh start or a thread switch. */
102
121
  private fromAgent;
@@ -43,6 +43,8 @@ export class AgentController {
43
43
  visible = true;
44
44
  read = 0;
45
45
  snapshot;
46
+ /** Where each custom event arrived, by its index in the view. */
47
+ anchors = [];
46
48
  disposed = false;
47
49
  connected = false;
48
50
  threadSeq = 0;
@@ -140,8 +142,32 @@ export class AgentController {
140
142
  unread: Math.max(0, this.finishedAnswers() - this.read),
141
143
  suggestions,
142
144
  tools: [...this.tools.values()].sort((a, b) => a.order - b.order).map((t) => t.tool),
145
+ custom: this.customItems(),
143
146
  };
144
147
  }
148
+ customItems() {
149
+ const items = new Map();
150
+ this.view.custom.forEach((event, i) => {
151
+ const value = event.value;
152
+ const id = typeof value === "object" && value !== null && typeof value.id === "string";
153
+ const key = id ? `${event.name}:${value.id}` : `${event.name}#${i}`;
154
+ const item = items.get(key);
155
+ if (item) {
156
+ item.value = event.value;
157
+ item.history.push(event.value);
158
+ }
159
+ else {
160
+ items.set(key, {
161
+ key,
162
+ name: event.name,
163
+ value: event.value,
164
+ history: [event.value],
165
+ after: this.anchors[i] ?? null,
166
+ });
167
+ }
168
+ });
169
+ return [...items.values()];
170
+ }
145
171
  bind() {
146
172
  const agent = this.engine.agent;
147
173
  this.unsubscribe();
@@ -160,6 +186,7 @@ export class AgentController {
160
186
  /** The view the agent's own messages and state make, for a fresh start or a thread switch. */
161
187
  fromAgent(threadId) {
162
188
  const agent = this.engine.agent;
189
+ this.anchors = [];
163
190
  let view = { ...createChatView(), threadId };
164
191
  if (agent.messages.length > 0) {
165
192
  view = reduceChat(view, {
@@ -180,7 +207,12 @@ export class AgentController {
180
207
  if (this.halted && event.type !== EventType.RUN_STARTED)
181
208
  return;
182
209
  this.halted = false;
210
+ const before = this.view.custom.length;
183
211
  this.view = reduceChat(this.view, event);
212
+ if (this.view.custom.length > before) {
213
+ const last = this.view.timeline.at(-1);
214
+ this.anchors.push(last ? `${last.kind}:${last.id}` : null);
215
+ }
184
216
  this.emit();
185
217
  }
186
218
  /** Tool results the agent's messages gained outside the event stream: the browser's own tools. */
@@ -435,6 +467,7 @@ export class AgentController {
435
467
  this.agent.setMessages([]);
436
468
  this.agent.setState({});
437
469
  this.failed.clear();
470
+ this.anchors = [];
438
471
  this.view = { ...createChatView(), threadId: id };
439
472
  this.emit();
440
473
  return id;
@@ -38,13 +38,13 @@ export function createCopilotKitEngine(copilotkit, agent, invoke, agentId) {
38
38
  },
39
39
  setTools(next) {
40
40
  clearTools();
41
- for (const tool of next) {
41
+ // A render-only tool (available: false) draws the agent's own calls; CopilotKit must not run it.
42
+ for (const tool of next.filter((t) => t.available !== false)) {
42
43
  copilotkit.addTool({
43
44
  name: tool.name,
44
45
  ...(tool.description !== undefined ? { description: tool.description } : {}),
45
46
  parameters: toolStandardSchema(tool),
46
47
  ...(tool.followUp !== undefined ? { followUp: tool.followUp } : {}),
47
- ...(tool.available !== undefined ? { available: tool.available } : {}),
48
48
  ...(agentId ? { agentId } : {}),
49
49
  handler: (args, ctx) => invoke(tool, args, {
50
50
  toolCallId: ctx.toolCall.id,
@@ -4,12 +4,14 @@
4
4
  * `@ag-ui/client`, which the package's main entry never loads. For a
5
5
  * CopilotKit runtime, see `@calliopelabs/ui/agent/copilotkit`.
6
6
  */
7
- export { AgentController, agentId, type AgentContextEntry, type AgentControllerOptions, type AgentSnapshot, type AgentSuggestion, type AgentThread, type SendOptions, } from "./controller.js";
7
+ export { AgentController, agentId, type AgentContextEntry, type AgentCustomItem, type AgentControllerOptions, type AgentSnapshot, type AgentSuggestion, type AgentThread, type SendOptions, } from "./controller.js";
8
8
  export { createDirectEngine, type AgentEngine, type EngineRunInput, type ToolInvoker, } from "./engine.js";
9
9
  export { defineTool, isStandardSchema, toolJsonSchema, toolResultText, toolStandardSchema, validateToolArgs, type AgentTool, type AgentToolContext, type AgentToolRenderProps, type JsonSchema, type StandardSchemaV1, } from "./tool.js";
10
10
  export { CalliopeAgentProvider, useAgentController, useAgentReadable, useAgentSuggestions, useAgentTool, useCalliopeAgent, type CalliopeAgentApi, type CalliopeAgentOptions, type CalliopeAgentProviderProps, } from "./AgentProvider.js";
11
- export { createMockAgent, textEvents, toolCallEvents, type MockAgentOptions, type MockTurn, } from "./mock.js";
11
+ export { createFunctionAgent, createMockAgent, type AgentRunFunction, type FunctionAgentOptions, textEvents, toolCallEvents, type MockAgentOptions, type MockTurn, } from "./mock.js";
12
12
  export { AgentChat, NewConversationButton, type AgentChatProps } from "./AgentChat.js";
13
13
  export { AgentLauncher, type AgentLauncherProps } from "./AgentLauncher.js";
14
14
  export { CalliopeAgent, type CalliopeAgentMode, type CalliopeAgentProps } from "./CalliopeAgent.js";
15
15
  export { shortcutLabel, useMediaQuery, useOpenState, useShortcut } from "./surface.js";
16
+ export { mergeRenderers, parseResult, type AgentRenderers, type CustomRendererProps, type InterruptRendererProps, type StateRendererProps, type ToolRendererProps, } from "./renderers.js";
17
+ export { ArtifactReviewCard, InterruptCard, QuestionInterruptCard, RunProgressCard, workflowRenderers, type ArtifactReviewValue, type ArtifactValue, type QuestionInterruptMetadata, type RunProgressValue, } from "./WorkflowRenderers.js";
@@ -8,8 +8,10 @@ export { AgentController, agentId, } from "./controller.js";
8
8
  export { createDirectEngine, } from "./engine.js";
9
9
  export { defineTool, isStandardSchema, toolJsonSchema, toolResultText, toolStandardSchema, validateToolArgs, } from "./tool.js";
10
10
  export { CalliopeAgentProvider, useAgentController, useAgentReadable, useAgentSuggestions, useAgentTool, useCalliopeAgent, } from "./AgentProvider.js";
11
- export { createMockAgent, textEvents, toolCallEvents, } from "./mock.js";
11
+ export { createFunctionAgent, createMockAgent, textEvents, toolCallEvents, } from "./mock.js";
12
12
  export { AgentChat, NewConversationButton } from "./AgentChat.js";
13
13
  export { AgentLauncher } from "./AgentLauncher.js";
14
14
  export { CalliopeAgent } from "./CalliopeAgent.js";
15
15
  export { shortcutLabel, useMediaQuery, useOpenState, useShortcut } from "./surface.js";
16
+ export { mergeRenderers, parseResult, } from "./renderers.js";
17
+ export { ArtifactReviewCard, InterruptCard, QuestionInterruptCard, RunProgressCard, workflowRenderers, } from "./WorkflowRenderers.js";