@calliopelabs/ui 0.13.0 → 0.14.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 (73) hide show
  1. package/README.md +50 -3
  2. package/dist/agent/AgentChat.d.ts +56 -0
  3. package/dist/agent/AgentChat.js +76 -0
  4. package/dist/agent/AgentLauncher.d.ts +25 -0
  5. package/dist/agent/AgentLauncher.js +25 -0
  6. package/dist/agent/AgentProvider.d.ts +82 -0
  7. package/dist/agent/AgentProvider.js +176 -0
  8. package/dist/agent/CalliopeAgent.d.ts +43 -0
  9. package/dist/agent/CalliopeAgent.js +111 -0
  10. package/dist/agent/controller.d.ts +148 -0
  11. package/dist/agent/controller.js +463 -0
  12. package/dist/agent/copilotkit/CopilotKitAgentProvider.d.ts +41 -0
  13. package/dist/agent/copilotkit/CopilotKitAgentProvider.js +145 -0
  14. package/dist/agent/copilotkit/index.d.ts +6 -0
  15. package/dist/agent/copilotkit/index.js +6 -0
  16. package/dist/agent/engine.d.ts +41 -0
  17. package/dist/agent/engine.js +93 -0
  18. package/dist/agent/index.d.ts +15 -0
  19. package/dist/agent/index.js +15 -0
  20. package/dist/agent/mock.d.ts +34 -0
  21. package/dist/agent/mock.js +112 -0
  22. package/dist/agent/surface.d.ts +30 -0
  23. package/dist/agent/surface.js +90 -0
  24. package/dist/agent/tool.d.ts +98 -0
  25. package/dist/agent/tool.js +81 -0
  26. package/dist/appearance/brands.d.ts +14 -12
  27. package/dist/appearance/brands.js +18 -51
  28. package/dist/chat/AgentProgress.d.ts +19 -0
  29. package/dist/chat/AgentProgress.js +34 -0
  30. package/dist/chat/Composer.d.ts +31 -0
  31. package/dist/chat/Composer.js +44 -0
  32. package/dist/chat/MessageBubble.d.ts +23 -0
  33. package/dist/chat/MessageBubble.js +26 -0
  34. package/dist/chat/MessageList.d.ts +36 -0
  35. package/dist/chat/MessageList.js +68 -0
  36. package/dist/chat/ModelPicker.d.ts +17 -0
  37. package/dist/chat/ModelPicker.js +13 -0
  38. package/dist/chat/ToolCallCard.d.ts +27 -0
  39. package/dist/chat/ToolCallCard.js +52 -0
  40. package/dist/chat/model.d.ts +123 -0
  41. package/dist/chat/model.js +337 -0
  42. package/dist/chat/patch.d.ts +12 -0
  43. package/dist/chat/patch.js +149 -0
  44. package/dist/chat/useAgUiRun.d.ts +52 -0
  45. package/dist/chat/useAgUiRun.js +137 -0
  46. package/dist/index.d.ts +9 -0
  47. package/dist/index.js +10 -0
  48. package/dist/primitives/Button.d.ts +1 -1
  49. package/dist/provider/messages.d.ts +66 -0
  50. package/dist/provider/messages.js +71 -0
  51. package/dist/theme/mappings/palette.d.ts +52 -0
  52. package/dist/theme/mappings/palette.js +208 -0
  53. package/dist/theme/mappings/render.d.ts +9 -0
  54. package/dist/theme/mappings/render.js +520 -0
  55. package/dist/theme/mappings/targets.d.ts +43 -0
  56. package/dist/theme/mappings/targets.js +222 -0
  57. package/dist/theme/tokens.d.ts +10 -9
  58. package/dist/theme/tokens.generated.d.ts +353 -0
  59. package/dist/theme/tokens.generated.js +357 -0
  60. package/dist/theme/tokens.js +20 -264
  61. package/dist/tokens/README.jupyterlab.md +125 -0
  62. package/dist/tokens/README.md +15 -0
  63. package/dist/tokens/README.native.md +29 -0
  64. package/dist/tokens/README.plain.md +84 -0
  65. package/dist/tokens/README.python.md +33 -0
  66. package/dist/tokens/README.vscode.md +114 -0
  67. package/dist/tokens/jupyterlab.css +1710 -0
  68. package/dist/tokens/plain.css +930 -0
  69. package/dist/tokens/tokens.native.d.ts +85 -0
  70. package/dist/tokens/tokens.native.js +773 -0
  71. package/dist/tokens/tokens.py +1043 -0
  72. package/dist/tokens/vscode.css +1138 -0
  73. package/package.json +35 -2
@@ -0,0 +1,41 @@
1
+ /**
2
+ * An engine runs an AG-UI agent for the controller: it starts runs, executes
3
+ * the browser's tools and decides on follow-up runs. Two ship: the direct
4
+ * engine here, over any `@ag-ui/client` agent (an `HttpAgent` on a plain
5
+ * AG-UI endpoint), and the CopilotKit engine (`@calliopelabs/ui/agent/copilotkit`),
6
+ * which hands the same work to CopilotKit's core. The controller reads the
7
+ * agent's events either way, so the conversation looks the same.
8
+ */
9
+ import type { AbstractAgent } from "@ag-ui/client";
10
+ import type { Context, ResumeEntry } from "@ag-ui/core";
11
+ import { type AgentTool } from "./tool.js";
12
+ /** Runs a tool call for an engine, with validation and human-in-the-loop waiting. */
13
+ export type ToolInvoker = (tool: AgentTool, args: unknown, call: {
14
+ toolCallId: string;
15
+ signal: AbortSignal;
16
+ }) => Promise<unknown>;
17
+ export interface EngineRunInput {
18
+ forwardedProps?: Record<string, unknown>;
19
+ /** Answers to the previous run's interrupts. */
20
+ resume?: ResumeEntry[];
21
+ }
22
+ export interface AgentEngine {
23
+ readonly agent: AbstractAgent;
24
+ /** Runs until the agent is done: frontend tool calls answered, follow-ups made. */
25
+ run(input: EngineRunInput): Promise<void>;
26
+ /** Stops the run in flight and any tool it is waiting on. */
27
+ stop(): void;
28
+ /** The tools the agent may call now; the controller calls this whenever they change. */
29
+ setTools(tools: readonly AgentTool[]): void;
30
+ /** The context (readables) the next run carries. */
31
+ setContext(context: readonly Context[]): void;
32
+ /** Lets go of everything the engine registered. */
33
+ dispose(): void;
34
+ }
35
+ /**
36
+ * The engine for a plain AG-UI agent. Each run sends the tools and context;
37
+ * when the agent calls a browser tool, the engine runs it, adds the result to
38
+ * the agent's messages and runs again (unless the tool says `followUp: false`).
39
+ * A run that ends on an interrupt stops there: the person's answer resumes it.
40
+ */
41
+ export declare function createDirectEngine(agent: AbstractAgent, invoke: ToolInvoker): AgentEngine;
@@ -0,0 +1,93 @@
1
+ import { toolJsonSchema, toolResultText } from "./tool.js";
2
+ /** Runs past this many follow-ups in one turn are a loop, not a conversation. */
3
+ const MAX_FOLLOW_UPS = 8;
4
+ /**
5
+ * The engine for a plain AG-UI agent. Each run sends the tools and context;
6
+ * when the agent calls a browser tool, the engine runs it, adds the result to
7
+ * the agent's messages and runs again (unless the tool says `followUp: false`).
8
+ * A run that ends on an interrupt stops there: the person's answer resumes it.
9
+ */
10
+ export function createDirectEngine(agent, invoke) {
11
+ let tools = [];
12
+ let context = [];
13
+ let aborter = null;
14
+ const answered = (toolCallId) => agent.messages.some((m) => m.role === "tool" && m.toolCallId === toolCallId);
15
+ return {
16
+ agent,
17
+ async run({ forwardedProps, resume }) {
18
+ aborter?.abort();
19
+ const own = new AbortController();
20
+ aborter = own;
21
+ let params = { resume };
22
+ try {
23
+ for (let turn = 0; turn <= MAX_FOLLOW_UPS; turn++) {
24
+ const offered = tools.filter((t) => t.available !== false);
25
+ const { newMessages } = await agent.runAgent({
26
+ ...params,
27
+ tools: offered.map((t) => ({
28
+ name: t.name,
29
+ description: t.description ?? "",
30
+ parameters: toolJsonSchema(t),
31
+ })),
32
+ context: [...context],
33
+ ...(forwardedProps ? { forwardedProps } : {}),
34
+ });
35
+ if (own.signal.aborted || agent.pendingInterrupts.length > 0)
36
+ return;
37
+ const calls = newMessages.flatMap((m) => m.role === "assistant" ? (m.toolCalls ?? []) : []);
38
+ let again = false;
39
+ for (const call of calls) {
40
+ const tool = offered.find((t) => t.name === call.function.name);
41
+ if (!tool || answered(call.id))
42
+ continue;
43
+ let content;
44
+ let error;
45
+ try {
46
+ const args = call.function.arguments ? JSON.parse(call.function.arguments) : {};
47
+ content = toolResultText(await invoke(tool, args, { toolCallId: call.id, signal: own.signal }));
48
+ }
49
+ catch (e) {
50
+ if (own.signal.aborted)
51
+ return;
52
+ error = e instanceof Error ? e.message : String(e);
53
+ content = `Error: ${error}`;
54
+ }
55
+ if (own.signal.aborted)
56
+ return;
57
+ agent.addMessage({
58
+ id: `${call.id}:result`,
59
+ role: "tool",
60
+ toolCallId: call.id,
61
+ content,
62
+ ...(error ? { error } : {}),
63
+ });
64
+ if (tool.followUp !== false)
65
+ again = true;
66
+ }
67
+ if (!again)
68
+ return;
69
+ params = {};
70
+ }
71
+ }
72
+ finally {
73
+ if (aborter === own)
74
+ aborter = null;
75
+ }
76
+ },
77
+ stop() {
78
+ aborter?.abort();
79
+ aborter = null;
80
+ if (agent.isRunning)
81
+ agent.abortRun();
82
+ },
83
+ setTools(next) {
84
+ tools = next;
85
+ },
86
+ setContext(next) {
87
+ context = next;
88
+ },
89
+ dispose() {
90
+ this.stop();
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@calliopelabs/ui/agent`: one embedded agent for every Calliope AI product,
3
+ * over the AG-UI protocol, drawn with the chat kit. Opt-in: it needs
4
+ * `@ag-ui/client`, which the package's main entry never loads. For a
5
+ * CopilotKit runtime, see `@calliopelabs/ui/agent/copilotkit`.
6
+ */
7
+ export { AgentController, agentId, type AgentContextEntry, type AgentControllerOptions, type AgentSnapshot, type AgentSuggestion, type AgentThread, type SendOptions, } from "./controller.js";
8
+ export { createDirectEngine, type AgentEngine, type EngineRunInput, type ToolInvoker, } from "./engine.js";
9
+ export { defineTool, isStandardSchema, toolJsonSchema, toolResultText, toolStandardSchema, validateToolArgs, type AgentTool, type AgentToolContext, type AgentToolRenderProps, type JsonSchema, type StandardSchemaV1, } from "./tool.js";
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";
12
+ export { AgentChat, NewConversationButton, type AgentChatProps } from "./AgentChat.js";
13
+ export { AgentLauncher, type AgentLauncherProps } from "./AgentLauncher.js";
14
+ export { CalliopeAgent, type CalliopeAgentMode, type CalliopeAgentProps } from "./CalliopeAgent.js";
15
+ export { shortcutLabel, useMediaQuery, useOpenState, useShortcut } from "./surface.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@calliopelabs/ui/agent`: one embedded agent for every Calliope AI product,
3
+ * over the AG-UI protocol, drawn with the chat kit. Opt-in: it needs
4
+ * `@ag-ui/client`, which the package's main entry never loads. For a
5
+ * CopilotKit runtime, see `@calliopelabs/ui/agent/copilotkit`.
6
+ */
7
+ export { AgentController, agentId, } from "./controller.js";
8
+ export { createDirectEngine, } from "./engine.js";
9
+ export { defineTool, isStandardSchema, toolJsonSchema, toolResultText, toolStandardSchema, validateToolArgs, } from "./tool.js";
10
+ export { CalliopeAgentProvider, useAgentController, useAgentReadable, useAgentSuggestions, useAgentTool, useCalliopeAgent, } from "./AgentProvider.js";
11
+ export { createMockAgent, textEvents, toolCallEvents, } from "./mock.js";
12
+ export { AgentChat, NewConversationButton } from "./AgentChat.js";
13
+ export { AgentLauncher } from "./AgentLauncher.js";
14
+ export { CalliopeAgent } from "./CalliopeAgent.js";
15
+ export { shortcutLabel, useMediaQuery, useOpenState, useShortcut } from "./surface.js";
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A mock AG-UI agent for Storybook and tests: a real `@ag-ui/client`
3
+ * `HttpAgent` whose fetch is answered from a script instead of the network,
4
+ * as a server-sent event stream. The client's own parser, verifier and
5
+ * message bookkeeping run exactly as they do against a live endpoint.
6
+ */
7
+ import { HttpAgent } from "@ag-ui/client";
8
+ import { type Message, type RunAgentInput } from "@ag-ui/core";
9
+ import type { AgUiEvent } from "../chat/model.js";
10
+ /** What the mock answers one run with: the events, without the run's start and finish. */
11
+ export type MockTurn = readonly AgUiEvent[] | ((input: RunAgentInput, turn: number) => readonly AgUiEvent[]);
12
+ export interface MockAgentOptions {
13
+ /**
14
+ * One entry per run, in order; the last repeats. A function sees the run's
15
+ * input (messages, tools, context, resume) and the run's index.
16
+ */
17
+ turns: readonly MockTurn[];
18
+ /** Pause between events, in ms; 0 for tests. Defaults to 30. */
19
+ delay?: number;
20
+ threadId?: string;
21
+ initialMessages?: Message[];
22
+ initialState?: unknown;
23
+ /** Every request's input, for a test to read. */
24
+ onRequest?: (input: RunAgentInput) => void;
25
+ }
26
+ /** An agent that answers from `turns`, streaming each event as SSE. */
27
+ export declare function createMockAgent({ turns, delay, threadId, initialMessages, initialState, onRequest, }: MockAgentOptions): HttpAgent;
28
+ /** An assistant's streamed reply, in `chunks`, as the events that make it. */
29
+ export declare function textEvents(messageId: string, ...chunks: string[]): AgUiEvent[];
30
+ /** A tool call with its arguments, optionally with a result (a backend tool). */
31
+ export declare function toolCallEvents(toolCallId: string, name: string, args: unknown, options?: {
32
+ parentMessageId?: string;
33
+ result?: unknown;
34
+ }): AgUiEvent[];
@@ -0,0 +1,112 @@
1
+ /**
2
+ * A mock AG-UI agent for Storybook and tests: a real `@ag-ui/client`
3
+ * `HttpAgent` whose fetch is answered from a script instead of the network,
4
+ * as a server-sent event stream. The client's own parser, verifier and
5
+ * message bookkeeping run exactly as they do against a live endpoint.
6
+ */
7
+ import { HttpAgent } from "@ag-ui/client";
8
+ import { EventType } from "@ag-ui/core";
9
+ const encoder = new TextEncoder();
10
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
11
+ if (signal?.aborted)
12
+ return reject(new DOMException("Aborted", "AbortError"));
13
+ const timer = setTimeout(resolve, ms);
14
+ signal?.addEventListener("abort", () => {
15
+ clearTimeout(timer);
16
+ reject(new DOMException("Aborted", "AbortError"));
17
+ }, { once: true });
18
+ });
19
+ /** Wraps a turn's events in its run's start and, unless they end it, its finish. */
20
+ function framed(input, events) {
21
+ const ends = events.some((e) => e.type === EventType.RUN_FINISHED || e.type === EventType.RUN_ERROR);
22
+ return [
23
+ { type: EventType.RUN_STARTED, threadId: input.threadId, runId: input.runId },
24
+ ...events,
25
+ ...(ends
26
+ ? []
27
+ : [
28
+ {
29
+ type: EventType.RUN_FINISHED,
30
+ threadId: input.threadId,
31
+ runId: input.runId,
32
+ },
33
+ ]),
34
+ ];
35
+ }
36
+ /** An agent that answers from `turns`, streaming each event as SSE. */
37
+ export function createMockAgent({ turns, delay = 30, threadId, initialMessages, initialState, onRequest, }) {
38
+ let turn = 0;
39
+ const fetch = async (_url, init) => {
40
+ const input = JSON.parse(String(init.body));
41
+ onRequest?.(input);
42
+ const index = turn++;
43
+ const script = turns[Math.min(index, turns.length - 1)] ?? [];
44
+ const events = framed(input, typeof script === "function" ? script(input, index) : script);
45
+ const signal = init.signal;
46
+ const body = new ReadableStream({
47
+ async start(controller) {
48
+ try {
49
+ for (const event of events) {
50
+ if (delay > 0)
51
+ await sleep(delay, signal);
52
+ else
53
+ await Promise.resolve();
54
+ if (signal?.aborted)
55
+ throw new DOMException("Aborted", "AbortError");
56
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
57
+ }
58
+ controller.close();
59
+ }
60
+ catch (error) {
61
+ controller.error(error);
62
+ }
63
+ },
64
+ });
65
+ return new Response(body, {
66
+ status: 200,
67
+ headers: { "content-type": "text/event-stream" },
68
+ });
69
+ };
70
+ return new HttpAgent({
71
+ url: "https://agent.invalid/agui",
72
+ fetch,
73
+ ...(threadId ? { threadId } : {}),
74
+ ...(initialMessages ? { initialMessages } : {}),
75
+ ...(initialState !== undefined ? { initialState: initialState } : {}),
76
+ });
77
+ }
78
+ /** An assistant's streamed reply, in `chunks`, as the events that make it. */
79
+ export function textEvents(messageId, ...chunks) {
80
+ return [
81
+ { type: EventType.TEXT_MESSAGE_START, messageId, role: "assistant" },
82
+ ...chunks.map((delta) => ({
83
+ type: EventType.TEXT_MESSAGE_CONTENT,
84
+ messageId,
85
+ delta,
86
+ })),
87
+ { type: EventType.TEXT_MESSAGE_END, messageId },
88
+ ];
89
+ }
90
+ /** A tool call with its arguments, optionally with a result (a backend tool). */
91
+ export function toolCallEvents(toolCallId, name, args, options = {}) {
92
+ return [
93
+ {
94
+ type: EventType.TOOL_CALL_START,
95
+ toolCallId,
96
+ toolCallName: name,
97
+ ...(options.parentMessageId ? { parentMessageId: options.parentMessageId } : {}),
98
+ },
99
+ { type: EventType.TOOL_CALL_ARGS, toolCallId, delta: JSON.stringify(args) },
100
+ { type: EventType.TOOL_CALL_END, toolCallId },
101
+ ...(options.result === undefined
102
+ ? []
103
+ : [
104
+ {
105
+ type: EventType.TOOL_CALL_RESULT,
106
+ messageId: `${toolCallId}:result`,
107
+ toolCallId,
108
+ content: typeof options.result === "string" ? options.result : JSON.stringify(options.result),
109
+ },
110
+ ]),
111
+ ];
112
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * An open state that is controlled (`open`), remembered per person in this
3
+ * browser (`persistKey`), or held here. The server renders `defaultOpen`; the
4
+ * client applies the saved state after hydration, without a mismatch, and a
5
+ * change in one tab reaches the others.
6
+ */
7
+ export declare function useOpenState({ open, defaultOpen, onOpenChange, persistKey, }: {
8
+ open?: boolean;
9
+ defaultOpen?: boolean;
10
+ onOpenChange?: (open: boolean) => void;
11
+ persistKey?: string;
12
+ }): readonly [boolean, (open: boolean) => void];
13
+ /** A shortcut as given: "mod+/" (Cmd on a Mac, Ctrl elsewhere) or a single key ("]"). */
14
+ export interface ParsedShortcut {
15
+ mod: boolean;
16
+ key: string;
17
+ }
18
+ export declare function parseShortcut(shortcut: string): ParsedShortcut;
19
+ /** The shortcut as people read it on this machine ("⌘/", "Ctrl+/"). */
20
+ export declare function shortcutLabel(shortcut: string): string;
21
+ /** The shortcut in `aria-keyshortcuts` form. */
22
+ export declare function shortcutAria(shortcut: string): string;
23
+ /**
24
+ * Calls `onPress` on the shortcut, anywhere in the page. A modified shortcut
25
+ * works while typing (the assistant opens from the middle of a form); a bare
26
+ * key does not, and neither works while a modal dialog holds the keyboard.
27
+ */
28
+ export declare function useShortcut(shortcut: string | false | undefined, onPress: () => void): void;
29
+ /** Whether a media query matches; the server and the first render say `serverValue`. */
30
+ export declare function useMediaQuery(query: string, serverValue?: boolean): boolean;
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { readLocal, subscribeLocal, writeLocal } from "../lib/local-store.js";
4
+ /**
5
+ * An open state that is controlled (`open`), remembered per person in this
6
+ * browser (`persistKey`), or held here. The server renders `defaultOpen`; the
7
+ * client applies the saved state after hydration, without a mismatch, and a
8
+ * change in one tab reaches the others.
9
+ */
10
+ export function useOpenState({ open, defaultOpen = false, onOpenChange, persistKey, }) {
11
+ const [own, setOwn] = React.useState(defaultOpen);
12
+ const saved = React.useSyncExternalStore(subscribeLocal, () => (persistKey ? readLocal(persistKey) : null), () => null);
13
+ const current = open ?? (persistKey && saved !== null ? saved === "1" : persistKey ? defaultOpen : own);
14
+ const set = React.useCallback((next) => {
15
+ if (open === undefined) {
16
+ if (persistKey)
17
+ writeLocal(persistKey, next ? "1" : "0");
18
+ else
19
+ setOwn(next);
20
+ }
21
+ onOpenChange?.(next);
22
+ }, [open, persistKey, onOpenChange]);
23
+ return [current, set];
24
+ }
25
+ export function parseShortcut(shortcut) {
26
+ const mod = shortcut.toLowerCase().startsWith("mod+");
27
+ return { mod, key: mod ? shortcut.slice(4) : shortcut };
28
+ }
29
+ const isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform || "");
30
+ /** The shortcut as people read it on this machine ("⌘/", "Ctrl+/"). */
31
+ export function shortcutLabel(shortcut) {
32
+ const { mod, key } = parseShortcut(shortcut);
33
+ if (!mod)
34
+ return key;
35
+ return isMac() ? `⌘${key}` : `Ctrl+${key}`;
36
+ }
37
+ /** The shortcut in `aria-keyshortcuts` form. */
38
+ export function shortcutAria(shortcut) {
39
+ const { mod, key } = parseShortcut(shortcut);
40
+ return mod ? `Meta+${key} Control+${key}` : key;
41
+ }
42
+ function typing(target) {
43
+ if (!(target instanceof HTMLElement))
44
+ return false;
45
+ return target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName);
46
+ }
47
+ /**
48
+ * Calls `onPress` on the shortcut, anywhere in the page. A modified shortcut
49
+ * works while typing (the assistant opens from the middle of a form); a bare
50
+ * key does not, and neither works while a modal dialog holds the keyboard.
51
+ */
52
+ export function useShortcut(shortcut, onPress) {
53
+ const latest = React.useRef(onPress);
54
+ React.useEffect(() => {
55
+ latest.current = onPress;
56
+ });
57
+ React.useEffect(() => {
58
+ if (!shortcut)
59
+ return;
60
+ const { mod, key } = parseShortcut(shortcut);
61
+ const onKey = (event) => {
62
+ if (event.defaultPrevented || event.isComposing || event.altKey)
63
+ return;
64
+ if (event.key !== key)
65
+ return;
66
+ const modified = event.metaKey || event.ctrlKey;
67
+ if (mod !== modified)
68
+ return;
69
+ if (!mod && typing(event.target))
70
+ return;
71
+ const target = event.target instanceof HTMLElement ? event.target : null;
72
+ if (target?.closest("[aria-modal='true'], [role='alertdialog']"))
73
+ return;
74
+ event.preventDefault();
75
+ latest.current();
76
+ };
77
+ window.addEventListener("keydown", onKey);
78
+ return () => window.removeEventListener("keydown", onKey);
79
+ }, [shortcut]);
80
+ }
81
+ /** Whether a media query matches; the server and the first render say `serverValue`. */
82
+ export function useMediaQuery(query, serverValue = true) {
83
+ return React.useSyncExternalStore((onChange) => {
84
+ if (typeof window.matchMedia !== "function")
85
+ return () => { };
86
+ const list = window.matchMedia(query);
87
+ list.addEventListener("change", onChange);
88
+ return () => list.removeEventListener("change", onChange);
89
+ }, () => typeof window.matchMedia === "function" ? window.matchMedia(query).matches : serverValue, () => serverValue);
90
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Frontend tools: what the agent may call in the browser, typed once. The
3
+ * parameters are a Standard Schema (zod 3.25+ and 4, valibot, arktype), which
4
+ * types the handler's arguments and validates them, or a plain JSON Schema
5
+ * with the argument type given as a type parameter. Pure: no React, no
6
+ * network, so a Server Component can import a tool's definition.
7
+ */
8
+ import type * as React from "react";
9
+ import type { ChatToolCall } from "../chat/model.js";
10
+ /** The Standard Schema v1 interface (standardschema.dev), inlined so no schema library is a dependency. */
11
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
12
+ readonly "~standard": {
13
+ readonly version: 1;
14
+ readonly vendor: string;
15
+ readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
16
+ readonly types?: {
17
+ readonly input: Input;
18
+ readonly output: Output;
19
+ } | undefined;
20
+ /** Standard JSON Schema v1, when the library implements it. */
21
+ readonly jsonSchema?: {
22
+ readonly input: (options: {
23
+ target: string;
24
+ }) => JsonSchema;
25
+ };
26
+ };
27
+ }
28
+ type StandardResult<Output> = {
29
+ readonly value: Output;
30
+ readonly issues?: undefined;
31
+ } | {
32
+ readonly issues: readonly {
33
+ readonly message: string;
34
+ }[];
35
+ };
36
+ /** A JSON Schema object, as AG-UI's `Tool.parameters` carries it. */
37
+ export type JsonSchema = Record<string, unknown>;
38
+ /** What a tool's handler is told besides its arguments. */
39
+ export interface AgentToolContext {
40
+ /** Aborts when the person stops the run or the agent unmounts. */
41
+ signal: AbortSignal;
42
+ toolCallId: string;
43
+ }
44
+ /** What a tool's renderer draws from. */
45
+ export interface AgentToolRenderProps<Args = unknown> {
46
+ call: ChatToolCall;
47
+ /** The arguments so far: undefined while they stream and do not parse yet. */
48
+ args: Partial<Args> | undefined;
49
+ /**
50
+ * Set while the call waits on the person (a tool with no handler): answers
51
+ * it, and the answer goes back to the agent as the tool's result.
52
+ */
53
+ respond?: (result: unknown) => void;
54
+ }
55
+ export interface AgentTool<Args = unknown> {
56
+ /** The name the agent calls it by. */
57
+ name: string;
58
+ /** What it does, for the agent. */
59
+ description?: string;
60
+ parameters?: StandardSchemaV1<unknown, Args> | JsonSchema;
61
+ /**
62
+ * Runs the call and returns its result (sent to the agent as JSON text). A
63
+ * tool with no handler and a `render` is a human-in-the-loop tool: the run
64
+ * waits until the rendered component calls `respond`.
65
+ */
66
+ handler?: (args: Args, context: AgentToolContext) => unknown;
67
+ /** Draws the call in the conversation instead of the default tool card. */
68
+ render?: (props: AgentToolRenderProps<Args>) => React.ReactNode;
69
+ /** Whether the agent runs again after the result. Defaults to true. */
70
+ followUp?: boolean;
71
+ /** False hides the tool from the agent without unregistering it. */
72
+ available?: boolean;
73
+ }
74
+ /** Types a tool's handler and renderer from its parameters; returns the tool as given. */
75
+ export declare function defineTool<Args = Record<string, unknown>>(tool: AgentTool<Args>): AgentTool<Args>;
76
+ export declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
77
+ /**
78
+ * A tool's parameters as JSON Schema for the agent: Standard JSON Schema when
79
+ * the library implements it, zod 4's `toJSONSchema()`, or the JSON Schema
80
+ * given. Throws for a schema it cannot convert (zod 3 without Standard JSON
81
+ * Schema), naming the tool.
82
+ */
83
+ export declare function toolJsonSchema(tool: Pick<AgentTool, "name" | "parameters">): JsonSchema;
84
+ /**
85
+ * The arguments as the handler receives them: validated (and transformed)
86
+ * through a Standard Schema, or as parsed for a JSON Schema. Rejects with
87
+ * the schema's issues.
88
+ */
89
+ export declare function validateToolArgs<Args>(tool: AgentTool<Args>, args: unknown): Promise<Args>;
90
+ /**
91
+ * A tool's parameters as a Standard Schema, for an engine that only takes
92
+ * one (CopilotKit): a JSON Schema becomes a schema that passes values through
93
+ * and reports itself as Standard JSON Schema.
94
+ */
95
+ export declare function toolStandardSchema(tool: AgentTool): StandardSchemaV1;
96
+ /** A tool result as the text AG-UI's tool message carries. */
97
+ export declare function toolResultText(result: unknown): string;
98
+ export {};
@@ -0,0 +1,81 @@
1
+ /** Types a tool's handler and renderer from its parameters; returns the tool as given. */
2
+ export function defineTool(tool) {
3
+ return tool;
4
+ }
5
+ export function isStandardSchema(value) {
6
+ return (typeof value === "object" &&
7
+ value !== null &&
8
+ "~standard" in value &&
9
+ typeof value["~standard"]?.validate === "function");
10
+ }
11
+ const EMPTY_OBJECT = { type: "object", properties: {} };
12
+ /**
13
+ * A tool's parameters as JSON Schema for the agent: Standard JSON Schema when
14
+ * the library implements it, zod 4's `toJSONSchema()`, or the JSON Schema
15
+ * given. Throws for a schema it cannot convert (zod 3 without Standard JSON
16
+ * Schema), naming the tool.
17
+ */
18
+ export function toolJsonSchema(tool) {
19
+ const { parameters } = tool;
20
+ if (parameters === undefined)
21
+ return EMPTY_OBJECT;
22
+ if (!isStandardSchema(parameters))
23
+ return parameters;
24
+ const standard = parameters["~standard"];
25
+ if (typeof standard.jsonSchema?.input === "function") {
26
+ return clean(standard.jsonSchema.input({ target: "draft-07" }));
27
+ }
28
+ const withMethod = parameters;
29
+ if (typeof withMethod.toJSONSchema === "function")
30
+ return clean(withMethod.toJSONSchema());
31
+ throw new Error(`Tool ${tool.name}: its ${standard.vendor} schema has no JSON Schema form. Use a library with Standard JSON Schema (zod 4, valibot 1, arktype 2) or pass a JSON Schema.`);
32
+ }
33
+ function clean(schema) {
34
+ const { $schema: _, ...rest } = schema;
35
+ return rest;
36
+ }
37
+ /**
38
+ * The arguments as the handler receives them: validated (and transformed)
39
+ * through a Standard Schema, or as parsed for a JSON Schema. Rejects with
40
+ * the schema's issues.
41
+ */
42
+ export async function validateToolArgs(tool, args) {
43
+ if (!isStandardSchema(tool.parameters))
44
+ return args;
45
+ const result = await tool.parameters["~standard"].validate(args);
46
+ if (result.issues) {
47
+ throw new Error(`Invalid arguments for ${tool.name}: ${result.issues.map((i) => i.message).join("; ")}`);
48
+ }
49
+ return result.value;
50
+ }
51
+ /**
52
+ * A tool's parameters as a Standard Schema, for an engine that only takes
53
+ * one (CopilotKit): a JSON Schema becomes a schema that passes values through
54
+ * and reports itself as Standard JSON Schema.
55
+ */
56
+ export function toolStandardSchema(tool) {
57
+ if (isStandardSchema(tool.parameters))
58
+ return tool.parameters;
59
+ const schema = toolJsonSchema(tool);
60
+ return {
61
+ "~standard": {
62
+ version: 1,
63
+ vendor: "calliope-json-schema",
64
+ validate: (value) => ({ value }),
65
+ jsonSchema: { input: () => schema },
66
+ },
67
+ };
68
+ }
69
+ /** A tool result as the text AG-UI's tool message carries. */
70
+ export function toolResultText(result) {
71
+ if (result === undefined)
72
+ return "";
73
+ if (typeof result === "string")
74
+ return result;
75
+ try {
76
+ return JSON.stringify(result);
77
+ }
78
+ catch {
79
+ return String(result);
80
+ }
81
+ }