@jini-ai/chat 0.5.7 → 0.5.8

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 (105) hide show
  1. package/dist/core/ag-ui/entry.js +2 -0
  2. package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -1
  3. package/dist/core/assistant-client/durable-run-subscription.js +13 -4
  4. package/dist/core/events.d.ts +3 -0
  5. package/dist/core/history-tool-metadata.d.ts +23 -0
  6. package/dist/core/history-tool-metadata.js +13 -0
  7. package/dist/core/index.d.ts +3 -0
  8. package/dist/core/index.js +3 -0
  9. package/dist/core/persistence/title.d.ts +24 -0
  10. package/dist/core/persistence/title.js +60 -9
  11. package/dist/core/run-activity.d.ts +15 -2
  12. package/dist/core/run-activity.js +6 -4
  13. package/dist/core/run-events/entry.js +2 -0
  14. package/dist/core/tool-call-detail.d.ts +15 -0
  15. package/dist/core/tool-call-detail.js +18 -0
  16. package/dist/core/turn-usage.d.ts +42 -3
  17. package/dist/core/turn-usage.js +101 -4
  18. package/dist/core/usage-format.d.ts +10 -0
  19. package/dist/core/usage-format.js +15 -0
  20. package/dist/embed-widget/browser/lazy-loader.d.ts +34 -0
  21. package/dist/embed-widget/browser/lazy-loader.js +116 -0
  22. package/dist/embed-widget/react/dialog.hooks.d.ts +28 -0
  23. package/dist/embed-widget/react/dialog.hooks.js +54 -0
  24. package/dist/embed-widget/react/embed.d.ts +1 -0
  25. package/dist/embed-widget/react/embed.js +5 -3
  26. package/dist/embed-widget/react/header.d.ts +3 -1
  27. package/dist/embed-widget/react/header.hooks.d.ts +13 -0
  28. package/dist/embed-widget/react/header.hooks.js +32 -0
  29. package/dist/embed-widget/react/header.js +4 -14
  30. package/dist/embed-widget/vanilla/index.d.ts +63 -0
  31. package/dist/embed-widget/vanilla/index.js +319 -0
  32. package/dist/embed-widget/vanilla/state.d.ts +48 -0
  33. package/dist/embed-widget/vanilla/state.js +120 -0
  34. package/dist/embed-widget/vanilla/style.css +29 -0
  35. package/dist/react/components/A2uiSurfaceCard.d.ts +5 -1
  36. package/dist/react/components/A2uiSurfaceCard.js +9 -9
  37. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -0
  38. package/dist/react/components/AttachmentPreviewModal.hooks.js +1 -1
  39. package/dist/react/components/ChatFab.d.ts +4 -1
  40. package/dist/react/components/ChatFab.js +2 -2
  41. package/dist/react/components/ConversationList.d.ts +8 -1
  42. package/dist/react/components/ConversationList.hooks.d.ts +18 -0
  43. package/dist/react/components/ConversationList.hooks.js +25 -0
  44. package/dist/react/components/ConversationList.js +12 -5
  45. package/dist/react/components/ConversationList.rules.d.ts +25 -0
  46. package/dist/react/components/ConversationList.rules.js +19 -0
  47. package/dist/react/components/McpUiSurfaceCard.d.ts +1 -1
  48. package/dist/react/components/McpUiSurfaceCard.js +8 -4
  49. package/dist/react/components/MessageList.d.ts +1 -1
  50. package/dist/react/components/MessageList.hooks.d.ts +4 -0
  51. package/dist/react/components/MessageList.hooks.js +6 -1
  52. package/dist/react/components/MessageList.js +4 -3
  53. package/dist/react/components/MessageRow.d.ts +17 -0
  54. package/dist/react/components/MessageRow.js +54 -35
  55. package/dist/react/components/MessageRow.usage.d.ts +48 -0
  56. package/dist/react/components/MessageRow.usage.js +47 -0
  57. package/dist/react/components/MidRunUserMessage.d.ts +2 -12
  58. package/dist/react/components/MidRunUserMessage.js +4 -2
  59. package/dist/react/components/SentAttachment.d.ts +19 -0
  60. package/dist/react/components/SentAttachment.hooks.d.ts +44 -0
  61. package/dist/react/components/SentAttachment.hooks.js +56 -0
  62. package/dist/react/components/SentAttachment.js +14 -0
  63. package/dist/react/components/ToolCallGroup.d.ts +12 -0
  64. package/dist/react/components/ToolCallGroup.hooks.d.ts +20 -0
  65. package/dist/react/components/ToolCallGroup.hooks.js +23 -0
  66. package/dist/react/components/ToolCallGroup.js +13 -0
  67. package/dist/react/components/ToolCard.js +25 -17
  68. package/dist/react/components/mcp-ui-surface-state.d.ts +21 -0
  69. package/dist/react/components/mcp-ui-surface-state.js +26 -0
  70. package/dist/react/ext-event-renderer-registry.d.ts +7 -0
  71. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  72. package/dist/react/features/chat-pane/components/ChatPane.js +11 -6
  73. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +1 -0
  74. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +16 -10
  75. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +4 -0
  76. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.d.ts +9 -0
  77. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.js +11 -0
  78. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.d.ts +17 -0
  79. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.js +15 -0
  80. package/dist/react/features/chat-pane/rules.d.ts +14 -0
  81. package/dist/react/features/chat-pane/rules.js +18 -0
  82. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  83. package/dist/react/features/chat-pane/styles.js +85 -1
  84. package/dist/react/features/chat-pane/types.d.ts +16 -1
  85. package/dist/react/hooks/useAnsweredCards.d.ts +20 -0
  86. package/dist/react/hooks/useAnsweredCards.js +23 -0
  87. package/dist/react/hooks/useConversation.d.ts +8 -0
  88. package/dist/react/hooks/useConversation.js +34 -0
  89. package/dist/react/hooks/useRunActivity.d.ts +5 -3
  90. package/dist/react/hooks/useRunActivity.js +4 -3
  91. package/dist/react/index.d.ts +4 -0
  92. package/dist/react/index.js +3 -0
  93. package/dist/react/message-blocks.d.ts +22 -3
  94. package/dist/react/message-blocks.js +25 -3
  95. package/dist/react/tool-call-display.d.ts +90 -0
  96. package/dist/react/tool-call-display.js +97 -0
  97. package/dist/store/ports.d.ts +25 -2
  98. package/dist/store/runtime/entry.d.ts +1 -0
  99. package/dist/store/runtime/entry.js +1 -0
  100. package/dist/store/runtime/history-tools.d.ts +27 -0
  101. package/dist/store/runtime/history-tools.js +169 -0
  102. package/dist/store/sql/store.js +52 -0
  103. package/dist/testing/index.d.ts +7 -0
  104. package/dist/testing/index.js +22 -0
  105. package/package.json +48 -18
@@ -0,0 +1,319 @@
1
+ import { CHAT_BUBBLE_STYLE } from '../browser/lazy-loader.js';
2
+ import { createVanillaChatController } from './state.js';
3
+ import { isQueuedPageAction } from '../../core/browser/page-actions.js';
4
+ export { createVanillaChatController } from './state.js';
5
+ function svgIcon({ document, path }) {
6
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
7
+ svg.setAttribute('viewBox', '0 0 24 24');
8
+ svg.setAttribute('aria-hidden', 'true');
9
+ const shape = document.createElementNS(svg.namespaceURI, 'path');
10
+ shape.setAttribute('d', path);
11
+ shape.setAttribute('fill', 'none');
12
+ shape.setAttribute('stroke', 'currentColor');
13
+ shape.setAttribute('stroke-width', '2');
14
+ svg.append(shape);
15
+ return svg;
16
+ }
17
+ /**
18
+ * Mounts a DOM-only chat panel. Native buttons/forms supply keyboard semantics;
19
+ * explicitly opened dialogs trap focus, restore the bubble on close and announce settled replies.
20
+ * Restored panels leave page focus alone. Streaming markdown is rendered at most once per frame.
21
+ * All listeners, scheduled page effects and runs belong to this mount and are disposed.
22
+ * Markdown is an injected host capability; no framework or icon font enters this graph.
23
+ */
24
+ export function mountVanillaChat(args, options = {}) {
25
+ const { document, element, copy } = args;
26
+ const node = (tag, className, text = '') => {
27
+ const result = document.createElement(tag);
28
+ result.className = className;
29
+ result.textContent = text;
30
+ return result;
31
+ };
32
+ const button = (label) => {
33
+ const result = node('button', 'jini-chat-vanilla__button', label);
34
+ result.type = 'button';
35
+ return result;
36
+ };
37
+ const shell = node('div', 'jini-chat-vanilla');
38
+ const fab = button('');
39
+ fab.className = 'jini-chat-bubble';
40
+ fab.setAttribute('aria-label', copy.fabLabel);
41
+ fab.setAttribute('aria-haspopup', 'dialog');
42
+ fab.append(svgIcon({ document, path: 'M4 4h16v12H9l-5 4V4z' }));
43
+ const panel = node('section', 'jini-chat-vanilla__panel');
44
+ panel.id = `jini-chat-${args.ids({})}`;
45
+ fab.setAttribute('aria-controls', panel.id);
46
+ panel.setAttribute('role', 'dialog');
47
+ panel.setAttribute('aria-modal', 'false');
48
+ panel.setAttribute('aria-label', copy.title);
49
+ const header = node('header', 'jini-chat-vanilla__header');
50
+ const title = node('strong', 'jini-chat-vanilla__title', copy.title);
51
+ const reset = button(copy.reset);
52
+ const close = button('');
53
+ close.setAttribute('aria-label', copy.close);
54
+ close.append(svgIcon({ document, path: 'M6 6l12 12M18 6L6 18' }));
55
+ header.append(title, reset, close);
56
+ const confirmation = node('div', 'jini-chat-vanilla__confirmation');
57
+ const confirm = button(copy.confirm);
58
+ confirm.classList.add('jini-chat-vanilla__discard');
59
+ const keep = button(copy.keep);
60
+ confirmation.append(node('span', '', copy.resetQuestion), confirm, keep);
61
+ confirmation.hidden = true;
62
+ const log = node('div', 'jini-chat-vanilla__messages');
63
+ log.setAttribute('role', 'log');
64
+ log.setAttribute('aria-live', 'off');
65
+ log.setAttribute('aria-relevant', 'additions text');
66
+ log.setAttribute('aria-label', copy.title);
67
+ log.tabIndex = 0;
68
+ const status = node('div', 'jini-chat-vanilla__status');
69
+ status.setAttribute('role', 'status');
70
+ const announcements = node('div', 'jini-chat-vanilla__announcements');
71
+ announcements.setAttribute('role', 'log');
72
+ announcements.setAttribute('aria-live', 'polite');
73
+ announcements.setAttribute('aria-relevant', 'additions');
74
+ announcements.setAttribute('aria-atomic', 'false');
75
+ const error = node('div', 'jini-chat-vanilla__error');
76
+ error.setAttribute('role', 'alert');
77
+ const proposal = node('div', 'jini-chat-vanilla__proposal');
78
+ const proposalText = node('span', '');
79
+ const go = button(copy.proposalGo);
80
+ proposal.append(proposalText, go);
81
+ const form = node('form', 'jini-chat-vanilla__composer');
82
+ const input = node('textarea', 'jini-chat-vanilla__input');
83
+ input.placeholder = copy.placeholder;
84
+ input.setAttribute('aria-label', copy.placeholder);
85
+ input.maxLength = 8000;
86
+ input.rows = 2;
87
+ const send = button(copy.send);
88
+ send.type = 'submit';
89
+ send.classList.add('jini-chat-vanilla__send');
90
+ send.setAttribute('aria-label', copy.send);
91
+ send.replaceChildren(svgIcon({ document, path: 'M5 4l15 8-15 8v-6l9-2-9-2V4z' }));
92
+ const stop = button(copy.stop);
93
+ form.append(input, send, stop);
94
+ const greeting = node('p', 'jini-chat-vanilla__greeting', copy.greeting);
95
+ const suggestions = node('div', 'jini-chat-vanilla__suggestions');
96
+ if (copy.suggestionsLabel)
97
+ suggestions.setAttribute('aria-label', copy.suggestionsLabel);
98
+ const suggestionButtons = (args.suggestions ?? []).map(text => button(text));
99
+ suggestions.append(...suggestionButtons);
100
+ log.append(greeting, suggestions);
101
+ panel.append(header, confirmation, log, status, announcements, error, proposal, form);
102
+ const bubbleStyle = node('style', '', CHAT_BUBBLE_STYLE);
103
+ shell.append(bubbleStyle, fab, panel);
104
+ element.replaceChildren(shell);
105
+ const rows = new Map();
106
+ let wasOpen = false;
107
+ let initialized = false;
108
+ const liveReplies = new Set();
109
+ const renderChrome = ({ state }) => {
110
+ panel.hidden = !state.open;
111
+ panel.setAttribute('aria-modal', String(state.open));
112
+ fab.setAttribute('aria-expanded', String(state.open));
113
+ fab.querySelector('path')?.setAttribute('d', state.open ? 'M6 6l12 12M18 6L6 18' : 'M4 4h16v12H9l-5 4V4z');
114
+ greeting.hidden = !copy.greeting || state.messages.length > 0;
115
+ suggestions.hidden = suggestionButtons.length === 0 || state.messages.length > 0;
116
+ suggestionButtons.forEach(button => { button.disabled = state.busy; });
117
+ error.textContent = state.error ?? '';
118
+ error.hidden = !state.error;
119
+ status.textContent = state.busy ? copy.thinking : '';
120
+ send.disabled = state.busy || !input.value.trim();
121
+ input.disabled = state.busy;
122
+ stop.hidden = !state.busy;
123
+ proposal.hidden = state.proposal === null;
124
+ proposalText.textContent = state.proposal ? copy.proposalLabel({ title: state.proposal.navigate.target.title }) : '';
125
+ };
126
+ const renderRows = ({ state }) => {
127
+ const pinned = log.scrollHeight - log.scrollTop - log.clientHeight < 80;
128
+ const ids = new Set(state.messages.map(message => message.id));
129
+ for (const [id, row] of rows)
130
+ if (!ids.has(id)) {
131
+ row.element.remove();
132
+ rows.delete(id);
133
+ }
134
+ for (const message of state.messages) {
135
+ let row = rows.get(message.id);
136
+ if (!row) {
137
+ row = { element: node('div', `jini-chat-vanilla__message jini-chat-vanilla__message--${message.role}`), content: '\0' };
138
+ rows.set(message.id, row);
139
+ log.append(row.element);
140
+ }
141
+ if (row.content !== message.content) {
142
+ if (message.role === 'user')
143
+ row.element.textContent = message.content;
144
+ else
145
+ args.renderMarkdown({ content: message.content, element: row.element });
146
+ row.content = message.content;
147
+ }
148
+ }
149
+ // A dedicated additions-only region announces each LIVE settled reply once. Markdown rows
150
+ // may be rebuilt while streaming, but restored transcripts and old text stay silent.
151
+ for (const message of state.messages) {
152
+ if (message.role !== 'assistant' || message.runStatus === 'running' || !liveReplies.delete(message.id))
153
+ continue;
154
+ const text = rows.get(message.id)?.element.textContent;
155
+ if (text) {
156
+ announcements.append(node('span', '', text));
157
+ // Bound the additions-only live region without replaying earlier replies.
158
+ while (announcements.childElementCount > 2)
159
+ announcements.firstElementChild.remove();
160
+ }
161
+ }
162
+ if (state.messages.length === 0)
163
+ announcements.replaceChildren();
164
+ if (pinned)
165
+ log.scrollTop = log.scrollHeight;
166
+ };
167
+ const renderFocus = ({ state, composerHadFocus, stopHadFocus, panelHadFocus }) => {
168
+ if (state.open && !wasOpen && initialized) {
169
+ if (state.busy)
170
+ close.focus();
171
+ else
172
+ input.focus();
173
+ }
174
+ else if (state.open && state.busy && composerHadFocus)
175
+ stop.focus();
176
+ else if (state.open && !state.busy && stopHadFocus)
177
+ input.focus();
178
+ if (!state.open && wasOpen) {
179
+ confirmation.hidden = true;
180
+ panel.setAttribute('aria-modal', 'false');
181
+ if (panelHadFocus)
182
+ fab.focus();
183
+ }
184
+ wasOpen = state.open;
185
+ };
186
+ let latestState;
187
+ let framePending = false;
188
+ let cancelRenderFrame = () => { };
189
+ const render = ({ state }) => {
190
+ latestState = state;
191
+ const panelHadFocus = panel.contains(document.activeElement);
192
+ const composerHadFocus = document.activeElement === input;
193
+ const stopHadFocus = document.activeElement === stop;
194
+ renderChrome({ state });
195
+ renderFocus({ state, composerHadFocus, stopHadFocus, panelHadFocus });
196
+ if (initialized && state.busy) {
197
+ for (const message of state.messages)
198
+ if (message.role === 'assistant' && message.runStatus === 'running')
199
+ liveReplies.add(message.id);
200
+ if (framePending)
201
+ return;
202
+ framePending = true;
203
+ cancelRenderFrame = args.scheduleFrame({ callback: () => {
204
+ framePending = false;
205
+ renderRows({ state: latestState });
206
+ } });
207
+ return;
208
+ }
209
+ cancelRenderFrame();
210
+ framePending = false;
211
+ renderRows({ state });
212
+ };
213
+ const controller = createVanillaChatController({ ...args, onChange: render });
214
+ const listeners = [];
215
+ const listen = (target, type, handler) => {
216
+ target.addEventListener(type, handler);
217
+ listeners.push(() => target.removeEventListener(type, handler));
218
+ };
219
+ suggestionButtons.forEach(button => listen(button, 'click', () => {
220
+ input.value = button.textContent ?? '';
221
+ renderChrome({ state: controller.getState({}) });
222
+ input.focus();
223
+ }));
224
+ listen(input, 'input', () => renderChrome({ state: controller.getState({}) }));
225
+ listen(fab, 'click', () => controller.setOpen({ open: !controller.getState({}).open }));
226
+ listen(close, 'click', () => controller.setOpen({ open: false }));
227
+ listen(reset, 'click', () => {
228
+ if (controller.getState({}).messages.length === 0 && input.value.length === 0) {
229
+ controller.reset({});
230
+ input.focus();
231
+ return;
232
+ }
233
+ confirm.textContent = controller.getState({}).messages.length === 0 ? copy.confirmDraft ?? copy.confirm : copy.confirm;
234
+ confirmation.hidden = false;
235
+ keep.focus();
236
+ });
237
+ listen(keep, 'click', () => { confirmation.hidden = true; reset.focus(); });
238
+ listen(confirm, 'click', () => { confirmation.hidden = true; input.value = ''; controller.reset({}); input.focus(); });
239
+ listen(go, 'click', () => controller.acceptProposal({}));
240
+ listen(stop, 'click', () => controller.cancel({}));
241
+ listen(form, 'submit', event => {
242
+ event.preventDefault();
243
+ const text = input.value;
244
+ if (!text.trim() || controller.getState({}).busy)
245
+ return;
246
+ input.value = '';
247
+ void controller.send({ text }).then(() => {
248
+ const state = controller.getState({});
249
+ if (state.open && !state.busy && panel.contains(document.activeElement))
250
+ input.focus();
251
+ });
252
+ });
253
+ listen(input, 'keydown', event => {
254
+ const key = event;
255
+ if (key.key === 'Enter' && !key.shiftKey && !key.isComposing) {
256
+ key.preventDefault();
257
+ form.requestSubmit();
258
+ }
259
+ });
260
+ listen(document, 'keydown', event => {
261
+ if (!controller.getState({}).open || !panel.contains(document.activeElement))
262
+ return;
263
+ const key = event;
264
+ if (key.key === 'Escape') {
265
+ key.preventDefault();
266
+ if (!confirmation.hidden) {
267
+ confirmation.hidden = true;
268
+ reset.focus();
269
+ }
270
+ else
271
+ controller.setOpen({ open: false });
272
+ return;
273
+ }
274
+ if (key.key !== 'Tab')
275
+ return;
276
+ const focusable = Array.from(panel.querySelectorAll('button:not(:disabled), textarea:not(:disabled), a[href], [tabindex="0"]'))
277
+ .filter(candidate => !candidate.closest('[hidden]'));
278
+ const first = focusable[0];
279
+ const last = focusable.at(-1);
280
+ if (key.shiftKey && (document.activeElement === first || !panel.contains(document.activeElement))) {
281
+ key.preventDefault();
282
+ last?.focus();
283
+ }
284
+ else if (!key.shiftKey && (document.activeElement === last || !panel.contains(document.activeElement))) {
285
+ key.preventDefault();
286
+ first?.focus();
287
+ }
288
+ });
289
+ listen(element, 'jini-chat-open', () => {
290
+ controller.setOpen({ open: true });
291
+ if (controller.getState({}).busy)
292
+ close.focus();
293
+ else
294
+ input.focus();
295
+ });
296
+ const queued = { value: args.session.drain({}) };
297
+ let cancelFrame = () => { };
298
+ if (isQueuedPageAction(queued) && queued.value.type !== 'navigate') {
299
+ const action = queued.value;
300
+ cancelFrame = args.scheduleFrame({ callback: () => { args.pageActions.execute({ action }); } });
301
+ }
302
+ controller.setOpen({ open: options.initialOpen ?? controller.getState({}).open });
303
+ initialized = true;
304
+ element.dataset.chatMounted = 'true';
305
+ element.dispatchEvent(new (document.defaultView?.Event ?? Event)('jini-chat-mounted'));
306
+ return {
307
+ controller,
308
+ unmount(_input = {}, _opts = {}) {
309
+ listeners.forEach(dispose => dispose());
310
+ cancelFrame();
311
+ cancelRenderFrame();
312
+ delete element.dataset.chatMounted;
313
+ controller.dispose({});
314
+ args.pageActions.clearHighlight({});
315
+ shell.remove();
316
+ },
317
+ };
318
+ }
319
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,48 @@
1
+ import type { ChatMessage } from '../../core/messages.js';
2
+ import type { ChatTransport } from '../../core/transport.js';
3
+ import type { BrowserSessionStore } from '../browser/session-store.js';
4
+ import type { PageActionPort, NavigateAction, NonNavigateAction } from '../../core/browser/page-actions.js';
5
+ export interface VanillaChatState {
6
+ readonly open: boolean;
7
+ readonly messages: ChatMessage[];
8
+ readonly busy: boolean;
9
+ readonly error: string | null;
10
+ readonly proposal: {
11
+ navigate: NavigateAction;
12
+ bundled: NonNavigateAction | null;
13
+ } | null;
14
+ }
15
+ export interface VanillaChatControllerArgs {
16
+ readonly transport: ChatTransport;
17
+ readonly session: BrowserSessionStore;
18
+ readonly pageActions: PageActionPort;
19
+ readonly directiveEventName: string;
20
+ readonly agentId: string;
21
+ readonly ids: (args: Record<string, never>) => string;
22
+ readonly createAbortController: (args: Record<string, never>) => AbortController;
23
+ readonly onChange: (args: {
24
+ state: VanillaChatState;
25
+ }) => void;
26
+ }
27
+ /**
28
+ * Framework-free visitor state over the existing transport/session/page-action owners.
29
+ * Restored messages never replay directives. Only a successful live settlement can act;
30
+ * persistence and queued effects finish synchronously before navigation tears down the page.
31
+ * Reset invalidates callbacks before aborting, preventing late chunks from reviving a thread.
32
+ * @complexity O(retained transcript + reply events + reply characters) per update; storage writes
33
+ * occur only on settle/open/reset/navigation, never per streamed chunk. Session bounds persisted bytes.
34
+ */
35
+ export declare function createVanillaChatController(args: VanillaChatControllerArgs, _options?: {}): {
36
+ getState(_input?: Record<string, never>, _opts?: {}): VanillaChatState;
37
+ setOpen({ open }: {
38
+ open: boolean;
39
+ }, _opts?: {}): void;
40
+ send({ text }: {
41
+ text: string;
42
+ }, _opts?: {}): Promise<boolean>;
43
+ acceptProposal(_input?: Record<string, never>, _opts?: {}): void;
44
+ cancel(_input?: Record<string, never>, _opts?: {}): void;
45
+ reset(_input?: Record<string, never>, _opts?: {}): void;
46
+ dispose(_input?: Record<string, never>, _opts?: {}): void;
47
+ };
48
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1,120 @@
1
+ import { assistantContentFromEvents } from '../../core/assistant-content.js';
2
+ import { extractPageActions, splitPageActions } from '../../core/browser/page-actions.js';
3
+ /**
4
+ * Framework-free visitor state over the existing transport/session/page-action owners.
5
+ * Restored messages never replay directives. Only a successful live settlement can act;
6
+ * persistence and queued effects finish synchronously before navigation tears down the page.
7
+ * Reset invalidates callbacks before aborting, preventing late chunks from reviving a thread.
8
+ * @complexity O(retained transcript + reply events + reply characters) per update; storage writes
9
+ * occur only on settle/open/reset/navigation, never per streamed chunk. Session bounds persisted bytes.
10
+ */
11
+ export function createVanillaChatController(args, _options = {}) {
12
+ const initial = args.session.load({});
13
+ let state = { ...initial, busy: false, error: null, proposal: null };
14
+ let generation = 0;
15
+ let active = null;
16
+ const publish = ({ persist = true } = {}) => {
17
+ if (persist)
18
+ args.session.save({ state: { open: state.open, messages: state.messages } });
19
+ args.onChange({ state });
20
+ };
21
+ const go = (proposal) => {
22
+ if (!args.pageActions.allows({ action: proposal.navigate }))
23
+ return;
24
+ if (proposal.bundled && args.pageActions.allows({ action: proposal.bundled }))
25
+ args.session.enqueue({ action: proposal.bundled });
26
+ state = { ...state, proposal: null };
27
+ publish();
28
+ args.pageActions.navigate({ action: proposal.navigate });
29
+ };
30
+ const settleActions = (events) => {
31
+ const actions = extractPageActions({ events, directiveEventName: args.directiveEventName })
32
+ .filter(action => args.pageActions.allows({ action }));
33
+ const { navigate, other } = splitPageActions({ actions });
34
+ if (!navigate) {
35
+ if (other)
36
+ args.pageActions.execute({ action: other });
37
+ return;
38
+ }
39
+ const proposal = { navigate, bundled: other };
40
+ if (navigate.auto)
41
+ go(proposal);
42
+ else {
43
+ state = { ...state, proposal };
44
+ publish();
45
+ }
46
+ };
47
+ return {
48
+ getState(_input = {}, _opts = {}) { return state; },
49
+ setOpen({ open }, _opts = {}) { state = { ...state, open }; publish(); },
50
+ async send({ text }, _opts = {}) {
51
+ const content = text.trim();
52
+ if (!content || state.busy)
53
+ return false;
54
+ const token = ++generation;
55
+ const abort = args.createAbortController({});
56
+ active = abort;
57
+ const history = [...state.messages, { id: args.ids({}), role: 'user', content }];
58
+ const reply = { id: args.ids({}), role: 'assistant', content: '', events: [], runStatus: 'running' };
59
+ state = { ...state, messages: [...history, reply], busy: true, error: null, proposal: null };
60
+ publish({ persist: false });
61
+ let events = [];
62
+ let failed = false;
63
+ let settled = false;
64
+ const current = () => token === generation && !settled;
65
+ const updateReply = (next, { persist = false } = {}) => {
66
+ state = { ...state, messages: state.messages.map(message => message.id === reply.id ? next : message) };
67
+ publish({ persist });
68
+ };
69
+ const fail = (error) => {
70
+ if (!current())
71
+ return;
72
+ failed = true;
73
+ state = { ...state, error: error.message };
74
+ updateReply({ ...reply, content: assistantContentFromEvents({ events }), events: [...events], runStatus: 'failed' });
75
+ };
76
+ const done = (final) => {
77
+ if (!current())
78
+ return;
79
+ settled = true;
80
+ active = null;
81
+ events = final;
82
+ state = { ...state, busy: false };
83
+ updateReply({ ...reply, content: assistantContentFromEvents({ events }), events: [...events],
84
+ runStatus: failed ? 'failed' : abort.signal.aborted ? 'canceled' : 'succeeded' }, { persist: true });
85
+ if (!failed && !abort.signal.aborted)
86
+ settleActions(events);
87
+ };
88
+ try {
89
+ await args.transport.startRun({ history, agentId: args.agentId, assistantMessageId: reply.id, signal: abort.signal }, {
90
+ onEvent(event) {
91
+ if (!current())
92
+ return;
93
+ events = [...events, event];
94
+ updateReply({ ...reply, content: assistantContentFromEvents({ events }), events: [...events], runStatus: failed ? 'failed' : 'running' });
95
+ },
96
+ onError: fail,
97
+ onDone: done,
98
+ });
99
+ }
100
+ catch (error) {
101
+ fail(error instanceof Error ? error : new Error(String(error)));
102
+ done(events);
103
+ }
104
+ return true;
105
+ },
106
+ acceptProposal(_input = {}, _opts = {}) { if (state.proposal)
107
+ go(state.proposal); },
108
+ cancel(_input = {}, _opts = {}) { active?.abort(); },
109
+ reset(_input = {}, _opts = {}) {
110
+ generation += 1;
111
+ active?.abort();
112
+ active = null;
113
+ args.session.clear({});
114
+ state = { open: state.open, messages: [], busy: false, error: null, proposal: null };
115
+ publish();
116
+ },
117
+ dispose(_input = {}, _opts = {}) { generation += 1; active?.abort(); active = null; },
118
+ };
119
+ }
120
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,29 @@
1
+ .jini-chat-bubble{position:fixed;right:var(--jini-embed-right,24px);bottom:24px;width:56px;height:56px;border:0;border-radius:50%;background:var(--jini-chat-accent,var(--color-primary,#2563eb));color:var(--jini-chat-accent-text,#fff);box-shadow:0 4px 16px #0003;cursor:pointer;z-index:var(--jini-embed-z-index,1000);display:grid;place-items:center}
2
+ .jini-chat-bubble[hidden]{display:none!important}
3
+ .jini-chat-bubble svg{width:24px;height:24px}
4
+ .jini-chat-bubble:focus-visible{outline:3px solid var(--jini-chat-accent,#2563eb);outline-offset:3px}
5
+ @media(max-width:480px){.jini-chat-bubble{right:16px;bottom:16px}}
6
+ .jini-chat-bubble[aria-busy="true"]{cursor:progress;opacity:.65}
7
+ .jini-chat-bubble[data-load-failed="true"]{outline:2px solid var(--jini-chat-error,#b91c1c);outline-offset:3px}
8
+ .jini-chat-vanilla [hidden]{display:none!important}
9
+ .jini-chat-vanilla svg{width:24px;height:24px}
10
+ .jini-chat-vanilla :focus-visible{outline:3px solid var(--jini-chat-accent,#2563eb);outline-offset:3px}
11
+ .jini-chat-vanilla{font:14px/1.5 var(--font-body,system-ui,sans-serif);color:var(--jini-chat-text,var(--color-text,CanvasText))}
12
+ .jini-chat-vanilla__panel{position:fixed;right:var(--jini-embed-right,24px);bottom:88px;width:min(var(--jini-embed-width,440px),calc(100vw - 32px));height:min(var(--jini-embed-height,620px),calc(100dvh - 120px));z-index:var(--jini-embed-z-index,1000);display:flex;flex-direction:column;background:var(--jini-chat-background,var(--color-background,Canvas));border:1px solid var(--jini-chat-border,#767676);border-radius:16px;box-shadow:0 12px 48px #0003;overflow:hidden}
13
+ .jini-chat-vanilla__header,.jini-chat-vanilla__composer,.jini-chat-vanilla__confirmation,.jini-chat-vanilla__proposal{display:flex;align-items:center;gap:8px;padding:12px;border-bottom:1px solid var(--jini-chat-border,#767676)}
14
+ .jini-chat-vanilla__title{flex:1}
15
+ .jini-chat-vanilla__button{font:inherit;color:inherit;background:transparent;border:1px solid var(--jini-chat-border,#767676);border-radius:8px;padding:6px 10px;cursor:pointer}
16
+ .jini-chat-vanilla__button:disabled{opacity:.5;cursor:default}
17
+ .jini-chat-vanilla__messages{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:16px}
18
+ .jini-chat-vanilla__message{margin-bottom:16px;overflow-wrap:anywhere}
19
+ .jini-chat-vanilla__message--user{white-space:pre-wrap;padding:10px;border-radius:8px;background:var(--jini-chat-user-background,#8882)}
20
+ .jini-chat-vanilla__message p{margin:0 0 8px}
21
+ .jini-chat-vanilla__message pre{overflow:auto;padding:8px;background:#8882;border-radius:8px}
22
+ .jini-chat-vanilla__message a{color:var(--jini-chat-accent,var(--color-primary,#2563eb));text-decoration:underline}
23
+ .jini-chat-vanilla__input{flex:1;min-width:0;resize:none;font:inherit;font-size:16px;color:inherit;background:transparent;border:1px solid var(--jini-chat-border,#767676);border-radius:8px;padding:8px}
24
+ .jini-chat-vanilla__status,.jini-chat-vanilla__error{padding:0 12px}
25
+ .jini-chat-vanilla__error{color:var(--jini-chat-error,#b91c1c)}
26
+ .jini-chat-vanilla__confirmation{flex-wrap:wrap}
27
+ @media(max-width:480px){.jini-chat-vanilla__panel{right:8px;bottom:88px;width:calc(100vw - 16px);height:calc(100dvh - 104px)}}
28
+
29
+ .jini-chat-vanilla__announcements{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
@@ -1,3 +1,4 @@
1
+ import { type InteractiveUiRegistry } from '@jini-ai/ui/interactive-ui';
1
2
  import type { ExtEventRenderProps } from '../ext-event-renderer-registry.js';
2
3
  /**
3
4
  * What a host reports back after attempting to deliver an agent-directed action. Returned (not
@@ -12,6 +13,9 @@ export type A2uiAgentActionOutcome = {
12
13
  readonly reason: string;
13
14
  };
14
15
  export interface A2uiSurfaceCardProps extends ExtEventRenderProps {
16
+ /** Host registry used for both wire validation and rendering. Defaults to ui's built-ins.
17
+ * Supply a stable registry for the card's lifetime; remount the card to switch catalogs. */
18
+ registry?: Pick<InteractiveUiRegistry, 'list' | 'resolveById'>;
15
19
  /**
16
20
  * Called when a rendered surface produces a `message`-shaped action meant for the agent (as
17
21
  * opposed to a `local` client-side function call, which this component already resolves and
@@ -31,5 +35,5 @@ export interface A2uiSurfaceCardProps extends ExtEventRenderProps {
31
35
  onAgentAction?: (runId: string | undefined, message: unknown) => void | Promise<A2uiAgentActionOutcome>;
32
36
  }
33
37
  /** Registered against `ext-event-renderer-registry.ts`'s `'a2ui'` name — see module doc. */
34
- export declare function A2uiSurfaceCard({ events, runId, onAgentAction }: A2uiSurfaceCardProps): import("react").JSX.Element;
38
+ export declare function A2uiSurfaceCard({ events, runId, onAgentAction, registry }: A2uiSurfaceCardProps): import("react").JSX.Element;
35
39
  //# sourceMappingURL=A2uiSurfaceCard.d.ts.map
@@ -54,7 +54,7 @@ function extractSurfaceId(message) {
54
54
  return undefined;
55
55
  }
56
56
  /** Renders one component and its children by walking the surface's component map from `componentId` — identical degrade-sanely behavior to `A2uiLab.tsx`'s `RenderComponent`. */
57
- function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAction, }) {
57
+ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAction, registry, }) {
58
58
  if (ancestors.has(componentId)) {
59
59
  return (_jsxs("span", { className: "a2ui-placeholder a2ui-placeholder-cycle", "data-a2ui-status": "cycle", "data-a2ui-component-id": componentId, children: ["\u27F2 circular reference at \u201C", componentId, "\u201D"] }));
60
60
  }
@@ -65,7 +65,7 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
65
65
  }
66
66
  const nextAncestors = new Set(ancestors);
67
67
  nextAncestors.add(componentId);
68
- const childProps = { interpreter, surfaceId, ancestors: nextAncestors, onAction };
68
+ const childProps = { interpreter, surfaceId, ancestors: nextAncestors, onAction, registry };
69
69
  function resolveText(value) {
70
70
  // Text props already passed the catalog schema. Parsing preserves that wire type at this
71
71
  // Record<string, unknown> boundary without asserting a shape the compiler cannot verify.
@@ -108,9 +108,9 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
108
108
  // one of the 13 basic types this switch has no case for, or a registry-sourced component
109
109
  // (`shadcn.*`/`recharts.*`) merged into the catalog by `A2uiSurfaceCard`'s own
110
110
  // `buildA2uiCatalogFromRegistry` call below. Resolved against the SAME registry
111
- // `@jini-ai/ui`'s own `A2uiSurfaceRenderer` uses, so a type addable to the catalog and
111
+ // the catalog below uses, so a type addable to the catalog and
112
112
  // renderable there is addable and renderable here too.
113
- const entry = DEFAULT_INTERACTIVE_UI_REGISTRY.resolveById({ id: component.component });
113
+ const entry = registry.resolveById({ id: component.component });
114
114
  if (!entry) {
115
115
  return (_jsxs("span", { className: "a2ui-placeholder", "data-a2ui-status": "unrenderable-type", children: ["\u26A0 no renderer yet for component type \u201C", component.component, "\u201D"] }));
116
116
  }
@@ -139,16 +139,16 @@ function isThenable(value) {
139
139
  return typeof value === 'object' && value !== null && typeof value.then === 'function';
140
140
  }
141
141
  /** Registered against `ext-event-renderer-registry.ts`'s `'a2ui'` name — see module doc. */
142
- export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
142
+ export function A2uiSurfaceCard({ events, runId, onAgentAction, registry = DEFAULT_INTERACTIVE_UI_REGISTRY }) {
143
143
  const t = useT();
144
- // Merges `DEFAULT_INTERACTIVE_UI_REGISTRY` (shadcn/recharts) into the lab catalog, keyed under
144
+ // Merges the host registry (shadcn/recharts by default) into the lab catalog, keyed under
145
145
  // the SAME `catalogId` the lab catalog already uses — so a tool built against
146
146
  // `createLabCatalog({}).catalogId` (e.g. `demo-a2ui-tool.ts`) still validates unchanged, and now
147
147
  // ALSO gains every registry component as a real, addable/removable catalog entry.
148
148
  const catalog = useMemo(() => {
149
149
  const base = createLabCatalog({});
150
- return buildA2uiCatalogFromRegistry({ registry: DEFAULT_INTERACTIVE_UI_REGISTRY, catalogId: base.catalogId }, { base });
151
- }, []);
150
+ return buildA2uiCatalogFromRegistry({ registry, catalogId: base.catalogId }, { base });
151
+ }, [registry]);
152
152
  const interpreterRef = useRef(null);
153
153
  if (!interpreterRef.current) {
154
154
  const clock = createSystemClock();
@@ -283,7 +283,7 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
283
283
  if (!root) {
284
284
  return refusalNotice ? (_jsx("div", { className: "a2ui-surface-card a2ui-surface-refused", role: "status", children: t('A2UI surface refused: {reason}', { reason: refusalNotice }) })) : (_jsx("div", { className: "a2ui-surface-card a2ui-surface-waiting", role: "status", children: t('Waiting for the agent to send a root component…') }));
285
285
  }
286
- return (_jsxs("div", { className: "a2ui-surface-card", "data-a2ui-surface-id": surfaceId, children: [_jsx(RenderComponent, { interpreter: interpreter, surfaceId: surfaceId, componentId: "root", ancestors: new Set(), onAction: handleAction }), refusalNotice ? (_jsx("div", { className: "a2ui-surface-notice a2ui-surface-notice-refused", role: "status", children: t('Action refused: {reason}', { reason: refusalNotice }) })) : null, pendingAgentAction ? (_jsx("div", { className: "a2ui-surface-notice a2ui-surface-notice-unwired", role: "status", children: t('This action is meant for the agent, but this host has not wired up a live agent-action relay yet.') })) : null, deliveryFailureNotice ? (
286
+ return (_jsxs("div", { className: "a2ui-surface-card", "data-a2ui-surface-id": surfaceId, children: [_jsx(RenderComponent, { interpreter: interpreter, surfaceId: surfaceId, componentId: "root", ancestors: new Set(), onAction: handleAction, registry: registry }), refusalNotice ? (_jsx("div", { className: "a2ui-surface-notice a2ui-surface-notice-refused", role: "status", children: t('Action refused: {reason}', { reason: refusalNotice }) })) : null, pendingAgentAction ? (_jsx("div", { className: "a2ui-surface-notice a2ui-surface-notice-unwired", role: "status", children: t('This action is meant for the agent, but this host has not wired up a live agent-action relay yet.') })) : null, deliveryFailureNotice ? (
287
287
  // Deliberately its own notice, not folded into `refusalNotice` above: that one fires
288
288
  // *before* anything is sent (the interpreter said no); this one fires *after* — the message
289
289
  // left this component but the host reports it never arrived. Conflating them would tell the
@@ -58,6 +58,13 @@ export declare function readFileAsText({ file }: {
58
58
  }, { createReader }?: {
59
59
  createReader?: () => FileReader;
60
60
  }): Promise<string>;
61
+ /**
62
+ * `false` in an environment with no Blob-URL support — real browsers have shipped this for well
63
+ * over a decade, but jsdom (this package's own test environment) never implemented it, and this
64
+ * package cannot assume every embedding host is a browser tab. Feature-detected the same way this
65
+ * file already feature-detects `<dialog>`'s `showModal`/`close`, rather than assumed.
66
+ */
67
+ export declare function canCreateObjectUrl(): boolean;
61
68
  export interface AttachmentTextPreview {
62
69
  content: string;
63
70
  truncated: boolean;
@@ -78,7 +78,7 @@ export function readFileAsText({ file }, { createReader = () => new FileReader()
78
78
  * package cannot assume every embedding host is a browser tab. Feature-detected the same way this
79
79
  * file already feature-detects `<dialog>`'s `showModal`/`close`, rather than assumed.
80
80
  */
81
- function canCreateObjectUrl() {
81
+ export function canCreateObjectUrl() {
82
82
  return typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function';
83
83
  }
84
84
  /**
@@ -3,6 +3,9 @@ export interface ChatFabProps {
3
3
  readonly onToggle: () => void;
4
4
  /** Rendered as the accessible name, and announced as a state change to assistive tech. */
5
5
  readonly label?: string;
6
+ readonly 'aria-label'?: string;
7
+ readonly 'aria-controls'?: string;
8
+ readonly 'aria-haspopup'?: 'dialog';
6
9
  }
7
- export declare function ChatFab({ open, onToggle, label }: ChatFabProps): import("react").JSX.Element;
10
+ export declare function ChatFab({ open, onToggle, label, 'aria-label': accessibleLabel, 'aria-controls': controls, 'aria-haspopup': popup }: ChatFabProps): import("react").JSX.Element;
8
11
  //# sourceMappingURL=ChatFab.d.ts.map