@jini-ai/chat 0.3.10 → 0.5.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 (206) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +152 -6
  3. package/dist/core/ag-ui/entry.d.ts +53 -0
  4. package/dist/core/ag-ui/entry.js +135 -0
  5. package/dist/core/agentic/chat-capabilities.d.ts +1 -1
  6. package/dist/core/agentic/index.d.ts +1 -1
  7. package/dist/core/agentic/index.js +1 -1
  8. package/dist/core/assistant-content.d.ts +6 -2
  9. package/dist/core/assistant-content.js +2 -2
  10. package/dist/core/compact-events.d.ts +3 -1
  11. package/dist/core/compact-events.js +1 -1
  12. package/dist/core/events.d.ts +1 -1
  13. package/dist/core/events.js +1 -1
  14. package/dist/core/index.d.ts +3 -3
  15. package/dist/core/index.js +3 -3
  16. package/dist/core/messages.d.ts +4 -2
  17. package/dist/core/messages.js +2 -2
  18. package/dist/core/partial-json.d.ts +6 -2
  19. package/dist/core/partial-json.js +3 -3
  20. package/dist/core/persistence/index.d.ts +1 -1
  21. package/dist/core/persistence/index.js +1 -1
  22. package/dist/core/persistence/ports.d.ts +2 -123
  23. package/dist/core/persistence/title.d.ts +3 -1
  24. package/dist/core/persistence/title.js +1 -1
  25. package/dist/core/question-form/format.d.ts +16 -7
  26. package/dist/core/question-form/format.js +3 -3
  27. package/dist/core/question-form/normalize.d.ts +7 -2
  28. package/dist/core/question-form/normalize.js +5 -5
  29. package/dist/core/question-form/scan.d.ts +18 -6
  30. package/dist/core/question-form/scan.js +14 -14
  31. package/dist/core/run-activity.d.ts +16 -4
  32. package/dist/core/run-activity.js +11 -11
  33. package/dist/core/run-events/entry.d.ts +121 -0
  34. package/dist/core/run-events/entry.js +290 -0
  35. package/dist/core/todos.d.ts +20 -8
  36. package/dist/core/todos.js +11 -11
  37. package/dist/core/tool-events.d.ts +3 -1
  38. package/dist/core/tool-events.js +1 -1
  39. package/dist/core/tool-output.d.ts +3 -1
  40. package/dist/core/tool-output.js +1 -1
  41. package/dist/core/tools.d.ts +14 -3
  42. package/dist/core/tools.js +3 -3
  43. package/dist/core/transcript.d.ts +13 -5
  44. package/dist/core/transcript.js +5 -5
  45. package/dist/core/util/manifest.d.ts +9 -3
  46. package/dist/core/util/manifest.js +3 -3
  47. package/dist/core/util/markdown-context.d.ts +11 -3
  48. package/dist/core/util/markdown-context.js +3 -3
  49. package/dist/core/util/markup-attributes.d.ts +3 -1
  50. package/dist/core/util/markup-attributes.js +1 -1
  51. package/dist/core/util/parser.d.ts +9 -5
  52. package/dist/core/util/parser.js +13 -13
  53. package/dist/core/util/recover.d.ts +6 -2
  54. package/dist/core/util/recover.js +6 -6
  55. package/dist/core/util/strip.d.ts +19 -5
  56. package/dist/core/util/strip.js +17 -17
  57. package/dist/core/util/validate.d.ts +3 -1
  58. package/dist/core/util/validate.js +1 -1
  59. package/dist/embed-widget/browser/page-actions.d.ts +121 -0
  60. package/dist/embed-widget/browser/page-actions.js +113 -0
  61. package/dist/embed-widget/browser/session-store.d.ts +44 -0
  62. package/dist/embed-widget/browser/session-store.js +103 -0
  63. package/dist/embed-widget/react/embed.d.ts +45 -0
  64. package/dist/embed-widget/react/embed.js +87 -0
  65. package/dist/embed-widget/react/header.d.ts +21 -0
  66. package/dist/embed-widget/react/header.js +18 -0
  67. package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
  68. package/dist/embed-widget/transports/fetch-sse.js +175 -0
  69. package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
  70. package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
  71. package/dist/react/artifact-types.d.ts +3 -3
  72. package/dist/react/artifact-types.js +3 -3
  73. package/dist/react/components/A2uiSurfaceCard.js +34 -13
  74. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
  75. package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
  76. package/dist/react/components/AttachmentPreviewModal.js +2 -2
  77. package/dist/react/components/AttachmentTray.d.ts +3 -1
  78. package/dist/react/components/AttachmentTray.js +2 -2
  79. package/dist/react/components/ChatFab.js +1 -1
  80. package/dist/react/components/Composer.js +9 -9
  81. package/dist/react/components/ConversationList.js +1 -1
  82. package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
  83. package/dist/react/components/McpUiSurfaceCard.js +5 -5
  84. package/dist/react/components/MessageList.js +1 -1
  85. package/dist/react/components/MessageRow.js +9 -9
  86. package/dist/react/components/QuestionForm.js +5 -5
  87. package/dist/react/components/ToolCard.js +9 -9
  88. package/dist/react/components/composer-discovery.d.ts +29 -8
  89. package/dist/react/components/composer-discovery.js +10 -10
  90. package/dist/react/ext-event-renderer-registry.d.ts +15 -5
  91. package/dist/react/ext-event-renderer-registry.js +4 -4
  92. package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
  93. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
  94. package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
  95. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
  96. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
  97. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
  98. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
  99. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
  100. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
  101. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
  102. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
  103. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
  104. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
  105. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
  106. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -0
  107. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +80 -38
  108. package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
  109. package/dist/react/features/chat-pane/rules.d.ts +13 -4
  110. package/dist/react/features/chat-pane/rules.js +5 -5
  111. package/dist/react/features/chat-pane/types.d.ts +1 -1
  112. package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
  113. package/dist/react/hooks/attachment-preview-cache.js +2 -2
  114. package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
  115. package/dist/react/hooks/composer-draft-cache.js +6 -6
  116. package/dist/react/hooks/useArtifactStream.d.ts +5 -1
  117. package/dist/react/hooks/useArtifactStream.js +3 -3
  118. package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
  119. package/dist/react/hooks/useChatFabDrag.js +4 -4
  120. package/dist/react/hooks/useComposer.js +8 -8
  121. package/dist/react/hooks/useConversation.js +56 -4
  122. package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
  123. package/dist/react/hooks/useExtEventGroups.js +1 -1
  124. package/dist/react/hooks/useLatestOperation.d.ts +8 -3
  125. package/dist/react/hooks/useLatestOperation.js +4 -4
  126. package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
  127. package/dist/react/hooks/usePinnedTodos.js +3 -3
  128. package/dist/react/hooks/useQuestionForms.d.ts +7 -2
  129. package/dist/react/hooks/useQuestionForms.js +7 -7
  130. package/dist/react/hooks/useRunActivity.d.ts +6 -1
  131. package/dist/react/hooks/useRunActivity.js +3 -3
  132. package/dist/react/hooks/useRunStream.d.ts +3 -1
  133. package/dist/react/hooks/useRunStream.js +1 -1
  134. package/dist/react/hooks/useToolTimeline.d.ts +3 -1
  135. package/dist/react/hooks/useToolTimeline.js +3 -3
  136. package/dist/react/index.d.ts +4 -4
  137. package/dist/react/index.js +6 -6
  138. package/dist/react/message-blocks.d.ts +10 -4
  139. package/dist/react/message-blocks.js +2 -2
  140. package/dist/react/styles/reference.css +3 -3
  141. package/dist/react/tool-renderer-registry.d.ts +8 -3
  142. package/dist/react/tool-renderer-registry.js +3 -3
  143. package/dist/react/util/defined-props.d.ts +3 -1
  144. package/dist/react/util/defined-props.js +1 -1
  145. package/dist/server/run-finalizer/entry.d.ts +47 -0
  146. package/dist/server/run-finalizer/entry.js +156 -0
  147. package/dist/server/run-finalizer/ports.d.ts +52 -0
  148. package/dist/store/contracts/index.d.ts +5 -0
  149. package/dist/store/contracts/index.js +5 -0
  150. package/dist/store/errors.d.ts +12 -0
  151. package/dist/store/errors.js +18 -0
  152. package/dist/store/index.d.ts +4 -0
  153. package/dist/store/index.js +2 -0
  154. package/dist/store/legacy/index.d.ts +3 -0
  155. package/dist/store/legacy/index.js +2 -0
  156. package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
  157. package/dist/store/legacy/sqlite/conversations.js +274 -0
  158. package/dist/store/legacy/sqlite/index.d.ts +6 -0
  159. package/dist/store/legacy/sqlite/index.js +5 -0
  160. package/dist/store/legacy/sqlite/messages.d.ts +156 -0
  161. package/dist/store/legacy/sqlite/messages.js +246 -0
  162. package/dist/store/legacy/sqlite/schema.d.ts +3 -0
  163. package/dist/store/legacy/sqlite/schema.js +40 -0
  164. package/dist/store/pglite/index.d.ts +21 -0
  165. package/dist/store/pglite/index.js +17 -0
  166. package/dist/store/ports.d.ts +121 -0
  167. package/dist/store/ports.js +2 -0
  168. package/dist/store/postgres/index.d.ts +21 -0
  169. package/dist/store/postgres/index.js +17 -0
  170. package/dist/store/sql/boundary.d.ts +28 -0
  171. package/dist/store/sql/boundary.js +32 -0
  172. package/dist/store/sql/cursor.d.ts +24 -0
  173. package/dist/store/sql/cursor.js +49 -0
  174. package/dist/store/sql/maintenance.d.ts +9 -0
  175. package/dist/store/sql/maintenance.js +16 -0
  176. package/dist/store/sql/store.d.ts +22 -0
  177. package/dist/store/sql/store.js +340 -0
  178. package/dist/store/sql/tables.d.ts +34 -0
  179. package/dist/store/sql/tables.js +2 -0
  180. package/dist/store/sqlite/index.d.ts +36 -0
  181. package/dist/store/sqlite/index.js +29 -0
  182. package/dist/store/sqlite/schema.d.ts +49 -0
  183. package/dist/store/sqlite/schema.js +80 -0
  184. package/package.json +211 -14
  185. package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
  186. package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
  187. package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
  188. package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
  189. package/dist/react/features/model-picker/constants.d.ts +0 -6
  190. package/dist/react/features/model-picker/constants.js +0 -9
  191. package/dist/react/features/model-picker/dependencies.d.ts +0 -11
  192. package/dist/react/features/model-picker/dependencies.js +0 -10
  193. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
  194. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
  195. package/dist/react/features/model-picker/index.d.ts +0 -32
  196. package/dist/react/features/model-picker/index.js +0 -29
  197. package/dist/react/features/model-picker/ports.d.ts +0 -11
  198. package/dist/react/features/model-picker/rules.d.ts +0 -26
  199. package/dist/react/features/model-picker/rules.js +0 -84
  200. package/dist/react/features/model-picker/types.d.ts +0 -38
  201. package/dist/react/features/model-picker/types.js +0 -2
  202. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
  203. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
  204. package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
  205. package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
  206. /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
@@ -0,0 +1,175 @@
1
+ /** Default JSON wire request is an explicit host choice, never a hidden endpoint default. O(history). */
2
+ export function buildJsonChatRequest({ message, history }) {
3
+ return { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, history }) };
4
+ }
5
+ /** Maps the text/error/end/directive JSON protocol with required directive vocabulary and copy. O(data). */
6
+ export function mapJsonChatSseFrame({ frame, directiveEventName, streamError }) {
7
+ if (!frame.data)
8
+ return {};
9
+ const data = JSON.parse(frame.data);
10
+ const obj = typeof data === 'object' && data !== null ? data : {};
11
+ if (frame.event === 'text') {
12
+ if (typeof obj.delta !== 'string')
13
+ throw new Error('Invalid text frame');
14
+ return { event: { kind: 'text', text: obj.delta } };
15
+ }
16
+ if (frame.event === 'error')
17
+ return { error: new Error(typeof obj.message === 'string' && obj.message ? obj.message : streamError) };
18
+ if (frame.event === 'end')
19
+ return { done: true };
20
+ if (frame.event === directiveEventName)
21
+ return { event: { kind: 'ext', name: directiveEventName, data } };
22
+ return {};
23
+ }
24
+ async function* responseChunks(body, signal) {
25
+ const reader = body.getReader();
26
+ const cancel = () => { void reader.cancel().catch(() => { }); };
27
+ signal.addEventListener('abort', cancel, { once: true });
28
+ if (signal.aborted)
29
+ cancel();
30
+ try {
31
+ for (;;) {
32
+ const { done, value } = await reader.read();
33
+ if (done)
34
+ return;
35
+ yield value;
36
+ }
37
+ }
38
+ finally {
39
+ signal.removeEventListener('abort', cancel);
40
+ try {
41
+ await reader.cancel();
42
+ }
43
+ catch { /* Already closed or aborted. */ }
44
+ reader.releaseLock();
45
+ }
46
+ }
47
+ /**
48
+ * Creates a cancellable, one-POST-per-run transport with no reattachable server state.
49
+ * Uses the host-bound SSE decoder rather than implementing another parser. Every run
50
+ * owns its controller, listeners and event log; cancellation and end settle once and
51
+ * prevent late events. Input and cancelSignal aborts release readers/listeners, and frames after
52
+ * end cannot mutate the settled reply. A later settlement clears an earlier pending directive.
53
+ * Default request bytes retain the {message, history} wire shape; a host requestBuilder may carry
54
+ * required agent identity differently. No browser module imports a Node-backed runtime barrel.
55
+ * The returned positional methods implement the existing ChatTransport.
56
+ * @complexity O(history + streamed bytes + emitted events); O(emitted events + decoder buffer) space.
57
+ */
58
+ export function createFetchSseTransport(args, options = {}) {
59
+ for (const limit of [args.maxHistoryMessages, args.maxHistoryMessageChars]) {
60
+ if (!Number.isSafeInteger(limit) || limit < 0)
61
+ throw new Error('Invalid history limit');
62
+ }
63
+ if (!args.endpoint || !args.agentId)
64
+ throw new Error('Endpoint and agent identity are required');
65
+ const activeRuns = new Map();
66
+ const observe = (runId, phase) => {
67
+ try {
68
+ options.onLifecycle?.({ runId, phase });
69
+ }
70
+ catch { /* Telemetry cannot break a run. */ }
71
+ };
72
+ return {
73
+ async startRun(input, handlers) {
74
+ let latestIndex = input.history.length - 1;
75
+ while (latestIndex >= 0 && input.history[latestIndex]?.role !== 'user')
76
+ latestIndex -= 1;
77
+ const message = input.history[latestIndex]?.content;
78
+ if (!message)
79
+ throw new Error(args.copy.noUserMessage);
80
+ const history = (args.maxHistoryMessages === 0 ? [] : input.history.slice(Math.max(0, latestIndex - args.maxHistoryMessages), latestIndex))
81
+ .filter(m => m.content.trim().length > 0)
82
+ .map(m => ({ role: m.role, content: m.content.slice(0, args.maxHistoryMessageChars) }));
83
+ // Build before registering a run, so a synchronous host-builder failure leaks no state.
84
+ const request = args.requestBuilder({ endpoint: args.endpoint, agentId: args.agentId, message, history, input });
85
+ const runId = args.ids({});
86
+ if (!runId || activeRuns.has(runId))
87
+ throw new Error('Run ID must be unique and nonempty');
88
+ const controller = options.createAbortController?.({}) ?? new AbortController();
89
+ activeRuns.set(runId, controller);
90
+ const collected = [];
91
+ let settled = false;
92
+ const signals = [input.signal, ...(input.cancelSignal ? [input.cancelSignal] : [])];
93
+ const finish = () => {
94
+ if (settled)
95
+ return;
96
+ settled = true;
97
+ activeRuns.delete(runId);
98
+ for (const signal of signals)
99
+ signal.removeEventListener('abort', abort);
100
+ controller.signal.removeEventListener('abort', onAborted);
101
+ observe(runId, 'settled');
102
+ handlers.onDone([...collected]);
103
+ };
104
+ const onAborted = () => { observe(runId, 'aborted'); finish(); };
105
+ const abort = () => controller.abort();
106
+ controller.signal.addEventListener('abort', onAborted, { once: true });
107
+ for (const signal of signals)
108
+ signal.addEventListener('abort', abort, { once: true });
109
+ observe(runId, 'started');
110
+ if (signals.some(signal => signal.aborted))
111
+ abort();
112
+ void (async () => {
113
+ try {
114
+ if (settled)
115
+ return;
116
+ const response = await args.fetch(args.endpoint, { ...request, signal: controller.signal });
117
+ if (settled) {
118
+ try {
119
+ await response.body?.cancel();
120
+ }
121
+ catch { /* closed body */ }
122
+ return;
123
+ }
124
+ if (!response.ok) {
125
+ const body = await response.json().catch(() => null);
126
+ if (settled)
127
+ return;
128
+ const error = typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string' && body.error
129
+ ? body.error : args.copy.requestFailed({ status: response.status });
130
+ observe(runId, 'error');
131
+ handlers.onError(new Error(error));
132
+ return;
133
+ }
134
+ if (!response.body)
135
+ throw new Error(args.copy.missingBody);
136
+ for await (const frame of args.decode({ source: responseChunks(response.body, controller.signal) })) {
137
+ if (settled)
138
+ break;
139
+ const result = args.frameMapper({ frame });
140
+ if (result.event) {
141
+ collected.push(result.event);
142
+ handlers.onEvent(result.event);
143
+ }
144
+ if (settled)
145
+ break;
146
+ if (result.error) {
147
+ observe(runId, 'error');
148
+ handlers.onError(result.error);
149
+ }
150
+ if (settled)
151
+ break;
152
+ if (result.done) {
153
+ finish();
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ catch (error) {
159
+ if (!settled && !controller.signal.aborted) {
160
+ observe(runId, 'error');
161
+ handlers.onError(error instanceof Error ? error : new Error(String(error)));
162
+ }
163
+ }
164
+ finally {
165
+ finish();
166
+ }
167
+ })();
168
+ return { runId };
169
+ },
170
+ async reattachRun(_runId, handlers) { handlers.onDone([]); },
171
+ async fetchRunStatus() { return null; },
172
+ async stopRun(runId) { activeRuns.get(runId)?.abort(); },
173
+ };
174
+ }
175
+ //# sourceMappingURL=fetch-sse.js.map
@@ -1,8 +1,6 @@
1
1
  import { type PageDriver } from '@jini-ai/agentic';
2
2
  import type { ChatPaneAgentBridgeAccess, ChatPaneAgentToolAction } from '../features/chat-pane/types.js';
3
3
  export interface FrontendSessionBridgeOptions {
4
- /** Daemon origin. Defaults to same-origin, which is how the dev server proxies `/api`. */
5
- readonly baseUrl?: string;
6
4
  /** Supplying one claims the six `page.*` verbs. Omit for a chat-only surface. */
7
5
  readonly pageDriver?: PageDriver;
8
6
  /**
@@ -15,6 +13,20 @@ export interface FrontendSessionBridgeOptions {
15
13
  /** Reported for failures that are nobody's tool call: stream errors, malformed frames. */
16
14
  readonly onError?: (error: unknown) => void;
17
15
  }
16
+ /** The host supplies its origin and stream transport; native fetch defaults may be overridden. */
17
+ export interface FrontendSessionBridgeArgs {
18
+ /** Pass an empty string for the same-origin proxy used by a browser host. */
19
+ readonly baseUrl: string;
20
+ readonly openStream: (args: {
21
+ url: string;
22
+ }) => EventSource;
23
+ readonly fetch?: typeof globalThis.fetch;
24
+ readonly request?: (args: {
25
+ url: string;
26
+ init: RequestInit;
27
+ timeoutMs: number;
28
+ }) => Promise<Response>;
29
+ }
18
30
  export interface FrontendSessionBridge {
19
31
  /** Hand this to `ChatPane`'s `agentControl.bridgeAccess`. */
20
32
  readonly bridgeAccess: ChatPaneAgentBridgeAccess;
@@ -41,5 +53,5 @@ export interface FrontendSessionBridge {
41
53
  * @param options - What this page can serve and where the daemon is.
42
54
  * @returns The pane's bridge, the attach result, and a teardown.
43
55
  */
44
- export declare function createFrontendSessionBridge(options?: FrontendSessionBridgeOptions): FrontendSessionBridge;
56
+ export declare function createFrontendSessionBridge({ baseUrl: base, openStream, request, fetch }: FrontendSessionBridgeArgs, options?: FrontendSessionBridgeOptions): FrontendSessionBridge;
45
57
  //# sourceMappingURL=frontend-session-bridge.d.ts.map
@@ -34,7 +34,6 @@
34
34
  * {@link FrontendSessionBridge.bindToken} answers "what is valid now", and only the latter
35
35
  * belongs in a run request.
36
36
  */
37
- import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
38
37
  import { CHAT_CAPABILITIES } from '../../core/index.js';
39
38
  import { PAGE_CAPABILITIES, executePageCapability } from '@jini-ai/agentic';
40
39
  /** How many settled invocation ids to remember for replay suppression. */
@@ -59,9 +58,13 @@ function errorMessage(error) {
59
58
  * @param options - What this page can serve and where the daemon is.
60
59
  * @returns The pane's bridge, the attach result, and a teardown.
61
60
  */
62
- export function createFrontendSessionBridge(options = {}) {
63
- const base = options.baseUrl ?? '';
61
+ export function createFrontendSessionBridge({ baseUrl: base, openStream, request, fetch }, options = {}) {
64
62
  const capabilities = claimedCapabilities(options);
63
+ const send = request ?? (({ url, init, timeoutMs }) => {
64
+ const timeout = AbortSignal.timeout(timeoutMs);
65
+ const signal = init.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
66
+ return (fetch ?? globalThis.fetch)(url, { ...init, signal });
67
+ });
65
68
  let sessionId;
66
69
  /** Replaced on every reattach; a promise resolved once could never carry this. */
67
70
  let currentBindToken;
@@ -79,11 +82,15 @@ export function createFrontendSessionBridge(options = {}) {
79
82
  /** Invocation ids already run here, so a stream reconnect cannot double-click a button. */
80
83
  const executed = new Set();
81
84
  const query = capabilities.map((id) => `capability=${encodeURIComponent(id)}`).join('&');
82
- const source = new EventSource(`${base}/api/frontend-sessions/stream?${query}`);
85
+ const source = openStream({ url: `${base}/api/frontend-sessions/stream?${query}` });
83
86
  async function respond(invocationId, body) {
84
87
  if (sessionId === undefined)
85
88
  throw new Error('the surface is not attached yet');
86
- const response = await fetchWithTimeout(`${base}/api/frontend-sessions/${encodeURIComponent(sessionId)}/responses`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ invocationId, ...body }) }, { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
89
+ const response = await send({
90
+ url: `${base}/api/frontend-sessions/${encodeURIComponent(sessionId)}/responses`,
91
+ init: { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ invocationId, ...body }) },
92
+ timeoutMs: 15_000,
93
+ });
87
94
  if (!response.ok)
88
95
  throw new Error(`answering "${invocationId}" failed: ${response.status}`);
89
96
  }
@@ -94,7 +101,7 @@ export function createFrontendSessionBridge(options = {}) {
94
101
  let output;
95
102
  try {
96
103
  if (options.pageDriver !== undefined && action.capabilityId.startsWith('page.')) {
97
- output = await executePageCapability(options.pageDriver, action.capabilityId, action.input);
104
+ output = await executePageCapability({ driver: options.pageDriver, capabilityId: action.capabilityId, input: action.input });
98
105
  }
99
106
  else {
100
107
  const entry = Object.entries(options.executors ?? {})
@@ -42,8 +42,8 @@ export interface ArtifactRenderMatch {
42
42
  file: ArtifactFile;
43
43
  }
44
44
  /**
45
- * A minimal ordered registry of `ArtifactRenderer`s: first-registered,
46
- * first-matched (later registrations can still override by registering
45
+ * A minimal ordered registry of `ArtifactRenderer`s: newest-registered,
46
+ * first-matched (later registrations override earlier matches by registering
47
47
  * before resolution, mirroring the tool-renderer registry's "last writer
48
48
  * wins on the same id" convention is intentionally NOT used here — artifact
49
49
  * renderers are matched by predicate, not by name, so ordering is the
@@ -53,7 +53,7 @@ export declare class RendererRegistry {
53
53
  private readonly renderers;
54
54
  /** Registers `renderer`, returning an unregister handle. */
55
55
  register(renderer: ArtifactRenderer): () => void;
56
- /** The first registered renderer whose `canRender` accepts `ctx`, or `null`. */
56
+ /** The newest registered renderer whose `canRender` accepts `ctx`, or `null`. */
57
57
  resolve(ctx: ArtifactRenderContext): ArtifactRenderMatch | null;
58
58
  /** Visible mainly for tests. */
59
59
  list(): readonly ArtifactRenderer[];
@@ -1,6 +1,6 @@
1
1
  /**
2
- * A minimal ordered registry of `ArtifactRenderer`s: first-registered,
3
- * first-matched (later registrations can still override by registering
2
+ * A minimal ordered registry of `ArtifactRenderer`s: newest-registered,
3
+ * first-matched (later registrations override earlier matches by registering
4
4
  * before resolution, mirroring the tool-renderer registry's "last writer
5
5
  * wins on the same id" convention is intentionally NOT used here — artifact
6
6
  * renderers are matched by predicate, not by name, so ordering is the
@@ -17,7 +17,7 @@ export class RendererRegistry {
17
17
  this.renderers.splice(idx, 1);
18
18
  };
19
19
  }
20
- /** The first registered renderer whose `canRender` accepts `ctx`, or `null`. */
20
+ /** The newest registered renderer whose `canRender` accepts `ctx`, or `null`. */
21
21
  resolve(ctx) {
22
22
  for (const renderer of this.renderers) {
23
23
  if (renderer.canRender(ctx))
@@ -32,12 +32,16 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
32
32
  * surfaces as a visible, honest notice instead of vanishing or hitting the wrong endpoint.
33
33
  */
34
34
  import { useEffect, useMemo, useRef, useState } from 'react';
35
- import { createA2uiInterpreter, createLabCatalog } from '@jini-ai/agentic/a2ui';
35
+ import { createA2uiInterpreter, createLabCatalog, DynamicValueSchema } from '@jini-ai/agentic/a2ui';
36
+ import { createSystemClock } from '@jini-ai/core/primitives';
36
37
  import { buildA2uiCatalogFromRegistry } from '@jini-ai/ui/a2ui';
37
38
  import { DEFAULT_INTERACTIVE_UI_REGISTRY } from '@jini-ai/ui/interactive-ui';
38
39
  import { useT } from '../hooks/context.js';
39
40
  /** Every agent→renderer message shape carries `surfaceId` under exactly one of these keys. */
40
41
  const SURFACE_ID_KEYS = ['createSurface', 'updateComponents', 'updateDataModel', 'deleteSurface'];
42
+ // Preserve the interpreter's former module-wide sequence so separate cards cannot reuse an
43
+ // action ID in the same millisecond; the wall-clock suffix keeps the existing wire format.
44
+ let actionIdCounter = 0;
41
45
  function extractSurfaceId(message) {
42
46
  if (typeof message !== 'object' || message === null)
43
47
  return undefined;
@@ -54,7 +58,7 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
54
58
  if (ancestors.has(componentId)) {
55
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"] }));
56
60
  }
57
- const surface = interpreter.getSurface(surfaceId);
61
+ const surface = interpreter.getSurface({ surfaceId });
58
62
  const component = surface?.components.get(componentId);
59
63
  if (!component) {
60
64
  return (_jsxs("span", { className: "a2ui-placeholder a2ui-placeholder-missing", "data-a2ui-status": "missing", "data-a2ui-component-id": componentId, children: ["\u26A0 missing component \u201C", componentId, "\u201D"] }));
@@ -63,7 +67,9 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
63
67
  nextAncestors.add(componentId);
64
68
  const childProps = { interpreter, surfaceId, ancestors: nextAncestors, onAction };
65
69
  function resolveText(value) {
66
- const result = interpreter.resolve(surfaceId, value);
70
+ // Text props already passed the catalog schema. Parsing preserves that wire type at this
71
+ // Record<string, unknown> boundary without asserting a shape the compiler cannot verify.
72
+ const result = interpreter.resolve({ surfaceId, value: DynamicValueSchema.parse(value) });
67
73
  if (!result.ok)
68
74
  return `⚠ unresolved (${result.reason})`;
69
75
  return typeof result.value === 'string' ? result.value : JSON.stringify(result.value);
@@ -104,7 +110,7 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
104
110
  // `buildA2uiCatalogFromRegistry` call below. Resolved against the SAME registry
105
111
  // `@jini-ai/ui`'s own `A2uiSurfaceRenderer` uses, so a type addable to the catalog and
106
112
  // renderable there is addable and renderable here too.
107
- const entry = DEFAULT_INTERACTIVE_UI_REGISTRY.resolveById(component.component);
113
+ const entry = DEFAULT_INTERACTIVE_UI_REGISTRY.resolveById({ id: component.component });
108
114
  if (!entry) {
109
115
  return (_jsxs("span", { className: "a2ui-placeholder", "data-a2ui-status": "unrenderable-type", children: ["\u26A0 no renderer yet for component type \u201C", component.component, "\u201D"] }));
110
116
  }
@@ -137,15 +143,26 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
137
143
  const t = useT();
138
144
  // Merges `DEFAULT_INTERACTIVE_UI_REGISTRY` (shadcn/recharts) into the lab catalog, keyed under
139
145
  // the SAME `catalogId` the lab catalog already uses — so a tool built against
140
- // `createLabCatalog().catalogId` (e.g. `demo-a2ui-tool.ts`) still validates unchanged, and now
146
+ // `createLabCatalog({}).catalogId` (e.g. `demo-a2ui-tool.ts`) still validates unchanged, and now
141
147
  // ALSO gains every registry component as a real, addable/removable catalog entry.
142
148
  const catalog = useMemo(() => {
143
- const base = createLabCatalog();
144
- return buildA2uiCatalogFromRegistry(DEFAULT_INTERACTIVE_UI_REGISTRY, base.catalogId, { base });
149
+ const base = createLabCatalog({});
150
+ return buildA2uiCatalogFromRegistry({ registry: DEFAULT_INTERACTIVE_UI_REGISTRY, catalogId: base.catalogId }, { base });
145
151
  }, []);
146
152
  const interpreterRef = useRef(null);
147
- if (!interpreterRef.current)
148
- interpreterRef.current = createA2uiInterpreter(catalog);
153
+ if (!interpreterRef.current) {
154
+ const clock = createSystemClock();
155
+ interpreterRef.current = createA2uiInterpreter({
156
+ catalog,
157
+ clock,
158
+ ids: {
159
+ next: (_required) => {
160
+ actionIdCounter += 1;
161
+ return `a2ui-action-${actionIdCounter}-${clock.nowMs().toString(36)}`;
162
+ },
163
+ },
164
+ });
165
+ }
149
166
  const interpreter = interpreterRef.current;
150
167
  const [, forceRender] = useState(0);
151
168
  const appliedCountRef = useRef(0);
@@ -157,7 +174,11 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
157
174
  const [pendingAgentAction, setPendingAgentAction] = useState(null);
158
175
  const [localActionResult, setLocalActionResult] = useState(null);
159
176
  const [deliveryFailureNotice, setDeliveryFailureNotice] = useState(null);
160
- useEffect(() => interpreter.subscribe(() => forceRender((n) => n + 1)), [interpreter]);
177
+ useEffect(() => {
178
+ const unsubscribe = interpreter.subscribe({ listener: () => forceRender((n) => n + 1) });
179
+ // React invokes cleanup with no arguments; the interpreter's port takes a required bag.
180
+ return () => unsubscribe({});
181
+ }, [interpreter]);
161
182
  useEffect(() => {
162
183
  while (appliedCountRef.current < events.length) {
163
184
  const message = events[appliedCountRef.current];
@@ -165,7 +186,7 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
165
186
  const surfaceId = extractSurfaceId(message);
166
187
  if (surfaceId)
167
188
  surfaceIdRef.current = surfaceId;
168
- const result = interpreter.applyAgentMessage(message);
189
+ const result = interpreter.applyAgentMessage({ raw: message });
169
190
  if (result.unattributedViolation) {
170
191
  setRefusalNotice(result.unattributedViolation);
171
192
  }
@@ -200,13 +221,13 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
200
221
  // the same way `handleAction`'s own (non-effect) call site already gets one on every render.
201
222
  }, [events, interpreter, onAgentAction, runId]);
202
223
  const surfaceId = surfaceIdRef.current;
203
- const root = surfaceId ? interpreter.getRoot(surfaceId) : undefined;
224
+ const root = surfaceId ? interpreter.getRoot({ surfaceId }) : undefined;
204
225
  function handleAction(componentId) {
205
226
  // `surfaceId` is non-null here by construction, not by luck: this callback is only ever reached
206
227
  // from a `Button` inside the rendered tree, and nothing renders until `root` resolves — which
207
228
  // the `!root` early return below only permits when `surfaceId` was already truthy. Asserted
208
229
  // rather than re-checked, exactly as the `RenderComponent` call site below already does.
209
- const built = interpreter.buildAction(surfaceId, componentId);
230
+ const built = interpreter.buildAction({ surfaceId: surfaceId, componentId });
210
231
  if (!built.ok) {
211
232
  setRefusalNotice(built.reason);
212
233
  return;
@@ -42,7 +42,9 @@ export type AttachmentPreviewStatus = 'image' | 'text' | 'unsupported';
42
42
  /** Hard cap on rendered text-preview characters, so a large attached log/CSV cannot hang the tab painting one giant `<pre>`. */
43
43
  export declare const MAX_TEXT_PREVIEW_CHARS = 200000;
44
44
  /** Exported for the component's own render branch, which needs the same answer without re-deriving it from `status`. */
45
- export declare function looksLikeImageAttachment(attachment: Pick<ChatAttachment, 'kind' | 'name'>): boolean;
45
+ export declare function looksLikeImageAttachment({ attachment }: {
46
+ attachment: Pick<ChatAttachment, 'kind' | 'name'>;
47
+ }): boolean;
46
48
  export interface AttachmentTextPreview {
47
49
  content: string;
48
50
  truncated: boolean;
@@ -66,5 +68,8 @@ export interface AttachmentPreviewController {
66
68
  * read on mount (bounded in practice by the upload route's own 20 MB per-attachment cap, and by
67
69
  * {@link MAX_TEXT_PREVIEW_CHARS} for what actually renders).
68
70
  */
69
- export declare function useAttachmentPreviewModal(attachment: ChatAttachment, onClose: () => void): AttachmentPreviewController;
71
+ export declare function useAttachmentPreviewModal({ attachment, onClose }: {
72
+ attachment: ChatAttachment;
73
+ onClose: () => void;
74
+ }): AttachmentPreviewController;
70
75
  //# sourceMappingURL=AttachmentPreviewModal.hooks.d.ts.map
@@ -50,7 +50,7 @@ function extensionOf(name) {
50
50
  return dot === -1 ? '' : name.slice(dot + 1).toLowerCase();
51
51
  }
52
52
  /** Exported for the component's own render branch, which needs the same answer without re-deriving it from `status`. */
53
- export function looksLikeImageAttachment(attachment) {
53
+ export function looksLikeImageAttachment({ attachment }) {
54
54
  return attachment.kind === 'image' || IMAGE_PREVIEW_EXTENSIONS.has(extensionOf(attachment.name));
55
55
  }
56
56
  function looksLikeTextAttachment(name) {
@@ -89,15 +89,15 @@ function canCreateObjectUrl() {
89
89
  * read on mount (bounded in practice by the upload route's own 20 MB per-attachment cap, and by
90
90
  * {@link MAX_TEXT_PREVIEW_CHARS} for what actually renders).
91
91
  */
92
- export function useAttachmentPreviewModal(attachment, onClose) {
92
+ export function useAttachmentPreviewModal({ attachment, onClose }) {
93
93
  const dialogRef = useRef(null);
94
94
  const triggerRef = useRef(null);
95
95
  const [imageFailed, setImageFailed] = useState(false);
96
96
  const [imageUrl, setImageUrl] = useState(undefined);
97
97
  const [text, setText] = useState(undefined);
98
98
  const [textLoading, setTextLoading] = useState(false);
99
- const cachedFile = getAttachmentPreviewSource(attachment.path);
100
- const attemptImage = !imageFailed && cachedFile !== undefined && looksLikeImageAttachment(attachment) && canCreateObjectUrl();
99
+ const cachedFile = getAttachmentPreviewSource({ path: attachment.path });
100
+ const attemptImage = !imageFailed && cachedFile !== undefined && looksLikeImageAttachment({ attachment: attachment }) && canCreateObjectUrl();
101
101
  const attemptText = !attemptImage && cachedFile !== undefined && looksLikeTextAttachment(attachment.name);
102
102
  const status = attemptImage ? 'image' : attemptText ? 'text' : 'unsupported';
103
103
  // Object URL: created/revoked in an effect (not `useMemo`) so a React 18 Strict Mode double-render
@@ -192,7 +192,7 @@ export function useAttachmentPreviewModal(attachment, onClose) {
192
192
  onImageError: () => setImageFailed(true),
193
193
  textLoading,
194
194
  text,
195
- sizeLabel: formatAttachmentSize(attachment.size),
195
+ sizeLabel: formatAttachmentSize({ size: attachment.size }),
196
196
  handleNativeCancel,
197
197
  handleBackdropClick,
198
198
  };
@@ -12,7 +12,7 @@ import { Icon } from './Icon.js';
12
12
  import { looksLikeImageAttachment, useAttachmentPreviewModal } from './AttachmentPreviewModal.hooks.js';
13
13
  export function AttachmentPreviewModal({ attachment, onClose }) {
14
14
  const t = useT();
15
- const c = useAttachmentPreviewModal(attachment, onClose);
16
- return (_jsxs("dialog", { ref: c.dialogRef, className: "jini-attachment-preview-dialog", role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: c.status === 'image' && c.imageUrl ? (_jsx("img", { src: c.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: c.onImageError })) : c.status === 'text' ? (c.textLoading ? (_jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') })) : (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: c.text?.content ?? '' }), c.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }))) : (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment(attachment) ? t('Image') : t('File') })] }), c.sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: c.sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] })) })] }));
15
+ const c = useAttachmentPreviewModal({ attachment: attachment, onClose: onClose });
16
+ return (_jsxs("dialog", { ref: c.dialogRef, className: "jini-attachment-preview-dialog", role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: c.status === 'image' && c.imageUrl ? (_jsx("img", { src: c.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: c.onImageError })) : c.status === 'text' ? (c.textLoading ? (_jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') })) : (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: c.text?.content ?? '' }), c.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }))) : (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment({ attachment: attachment }) ? t('Image') : t('File') })] }), c.sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: c.sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] })) })] }));
17
17
  }
18
18
  //# sourceMappingURL=AttachmentPreviewModal.js.map
@@ -2,5 +2,7 @@ import type { AttachmentTraySlot } from '../slots.js';
2
2
  export type AttachmentTrayProps = AttachmentTraySlot;
3
3
  export declare function AttachmentTray({ attachments, onRemove, renderItem }: AttachmentTrayProps): import("react").JSX.Element | null;
4
4
  /** Formats optional byte counts for the compact composer attachment chip. */
5
- export declare function formatAttachmentSize(size: number | undefined): string | null;
5
+ export declare function formatAttachmentSize({ size }: {
6
+ size: number | undefined;
7
+ }): string | null;
6
8
  //# sourceMappingURL=AttachmentTray.d.ts.map
@@ -8,11 +8,11 @@ export function AttachmentTray({ attachments, onRemove, renderItem }) {
8
8
  return (_jsx("div", { className: "jini-attachment-tray", "data-testid": "composer-attachment-tray", children: attachments.map((a) => (_jsxs("div", { className: "jini-attachment-chip", "data-testid": "attachment-chip", "data-attachment-kind": a.kind, "data-attachment-name": a.name, children: [renderItem ? renderItem(a) : _jsx(DefaultAttachmentChip, { attachment: a }), _jsx("button", { type: "button", className: "jini-attachment-remove", onClick: () => onRemove(a.path), title: t('Remove {name}', { name: a.name }), "aria-label": t('Remove {name}', { name: a.name }), children: _jsx(Icon, { name: "close", size: 12 }) })] }, a.path))) }));
9
9
  }
10
10
  function DefaultAttachmentChip({ attachment }) {
11
- const size = formatAttachmentSize(attachment.size);
11
+ const size = formatAttachmentSize({ size: attachment.size });
12
12
  return (_jsxs("span", { className: "jini-attachment-chip-body", children: [_jsx("span", { className: `jini-attachment-chip-icon is-${attachment.kind}`, children: _jsx(Icon, { name: attachment.kind === 'image' ? 'image' : 'file', size: 15 }) }), _jsxs("span", { className: "jini-attachment-chip-copy", children: [_jsx("span", { className: "jini-attachment-chip-name", title: attachment.name, children: attachment.name }), size ? _jsx("small", { className: "jini-attachment-chip-size", children: size }) : null] })] }));
13
13
  }
14
14
  /** Formats optional byte counts for the compact composer attachment chip. */
15
- export function formatAttachmentSize(size) {
15
+ export function formatAttachmentSize({ size }) {
16
16
  if (size === undefined || !Number.isFinite(size) || size < 0)
17
17
  return null;
18
18
  if (size < 1_024)
@@ -8,7 +8,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
8
8
  */
9
9
  import { useChatFabDrag } from '../hooks/useChatFabDrag.js';
10
10
  export function ChatFab({ open, onToggle, label = 'chat' }) {
11
- const { buttonRef, position, onPointerDown, onPointerMove, onPointerUp, onPointerCancel } = useChatFabDrag(onToggle);
11
+ const { buttonRef, position, onPointerDown, onPointerMove, onPointerUp, onPointerCancel } = useChatFabDrag({ onClick: onToggle });
12
12
  return (_jsx("button", { ref: buttonRef, type: "button", className: `chat-fab${open ? ' chat-fab-open' : ''}`,
13
13
  // Not `onClick`: the pointer gesture already decides click-versus-drag, and keeping both
14
14
  // would fire the toggle twice on a tap.
@@ -90,8 +90,8 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
90
90
  const resolvedPlaceholder = placeholder ?? t('Send a message…');
91
91
  const discoveryGroups = slots?.discoveryGroups ?? [];
92
92
  const hasDiscoveryItems = discoveryGroups.some((group) => group.items.length > 0);
93
- const slashQuery = parseComposerSlashQuery(composer.draft);
94
- const slashMatches = slashQuery === null ? [] : filterComposerDiscovery(discoveryGroups, slashQuery);
93
+ const slashQuery = parseComposerSlashQuery({ draft: composer.draft });
94
+ const slashMatches = slashQuery === null ? [] : filterComposerDiscovery({ groups: discoveryGroups, query: slashQuery });
95
95
  const slashOpen = slashMatches.length > 0 && dismissedSlashDraft !== composer.draft;
96
96
  /**
97
97
  * A bare "/" intentionally matches every item (`matchesFuzzyCommand`'s own doc) so the palette
@@ -167,14 +167,14 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
167
167
  function update() {
168
168
  const rect = composerRoot.getBoundingClientRect();
169
169
  if (discoveryMenuOpen)
170
- setDiscoveryMenuPosition(composerDiscoveryMenuPosition(rect));
170
+ setDiscoveryMenuPosition(composerDiscoveryMenuPosition({ composerRect: rect }));
171
171
  if (slashOpen)
172
- setSlashMenuPosition(composerSlashMenuPosition(rect));
172
+ setSlashMenuPosition(composerSlashMenuPosition({ composerRect: rect }));
173
173
  // Shares the discovery menu's own anchor math (same left inset off `.jini-composer`) rather
174
174
  // than measuring the working-dir button's own rect — one less measurement to keep in sync,
175
175
  // and the two popovers already never open at once (each is gated on its own trigger).
176
176
  if (workdirOpen)
177
- setWorkdirPosition(composerDiscoveryMenuPosition(rect));
177
+ setWorkdirPosition(composerDiscoveryMenuPosition({ composerRect: rect }));
178
178
  }
179
179
  update();
180
180
  window.addEventListener('scroll', update, true);
@@ -222,7 +222,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
222
222
  const match = slashMatches[index];
223
223
  if (!match)
224
224
  return;
225
- const resolution = resolveComposerSlashInvocation(composer.draft, match.item);
225
+ const resolution = resolveComposerSlashInvocation({ draft: composer.draft, item: match.item });
226
226
  if (!resolution)
227
227
  return;
228
228
  if (resolution.type === 'complete') {
@@ -235,7 +235,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
235
235
  }
236
236
  let expectedDraft = composer.draft;
237
237
  if (!match.item.command) {
238
- expectedDraft = replaceComposerSlashTrigger(composer.draft, match.item.insertText ?? match.item.label);
238
+ expectedDraft = replaceComposerSlashTrigger({ draft: composer.draft, insertText: match.item.insertText ?? match.item.label });
239
239
  composer.setDraft(expectedDraft);
240
240
  }
241
241
  setDismissedSlashDraft(null);
@@ -243,7 +243,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
243
243
  restoreComposerFocus();
244
244
  }
245
245
  function selectPlusItem(item) {
246
- const expectedDraft = item.insertText ? appendComposerDiscovery(composer.draft, item.insertText) : composer.draft;
246
+ const expectedDraft = item.insertText ? appendComposerDiscovery({ draft: composer.draft, insertText: item.insertText }) : composer.draft;
247
247
  if (item.insertText)
248
248
  composer.setDraft(expectedDraft);
249
249
  setDiscoveryMenuOpen(false);
@@ -271,7 +271,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
271
271
  if (event.nativeEvent.isComposing)
272
272
  return;
273
273
  if (slashOpen) {
274
- const action = resolveComposerSlashKeyAction(event.key, event.shiftKey);
274
+ const action = resolveComposerSlashKeyAction({ key: event.key, shiftKey: event.shiftKey });
275
275
  if (action.type !== 'none') {
276
276
  event.preventDefault();
277
277
  if (action.type === 'move') {
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * into `ChatPane`'s `leadingAccessory` slot — it needs no changes to `ChatPane` itself.
7
7
  *
8
8
  * Storage-agnostic by construction. It takes data and callbacks and owns nothing durable, so a
9
- * host backs it with `@jini-ai/sqlite`'s `ChatHistoryStore`, an HTTP endpoint, or an in-memory
9
+ * host backs it with `@jini-ai/sqlite-chat`'s `ChatHistoryStore`, an HTTP endpoint, or an in-memory
10
10
  * array without this file knowing the difference.
11
11
  *
12
12
  * Search is a **client-side filter by default**, and delegates to `onSearch` when a host provides
@@ -29,10 +29,12 @@ export declare function McpUiSurfaceCard({ events, sandboxProxyUrl, onToolCall,
29
29
  * The ext-event slot key for MCP-UI: one slot per `ui://` URI. Each resource is its own card, so a
30
30
  * run's second card renders where it arrived instead of inside the first card's slot, while a
31
31
  * re-emitted resource with the same URI still updates its card in place. Pass it as
32
- * `registerExtEventRenderer(MCP_UI_EXT_EVENT_NAME, renderer, { slotKey: mcpUiSurfaceSlotKey })`
32
+ * `registerExtEventRenderer({ name: MCP_UI_EXT_EVENT_NAME, renderer }, { slotKey: mcpUiSurfaceSlotKey })`
33
33
  * when registering a custom MCP-UI renderer; {@link registerMcpUiSurfaceRenderer} already does.
34
34
  */
35
- export declare function mcpUiSurfaceSlotKey(data: unknown): string | undefined;
35
+ export declare function mcpUiSurfaceSlotKey({ data }: {
36
+ data: unknown;
37
+ }): string | undefined;
36
38
  /**
37
39
  * Registers {@link McpUiSurfaceCard} against the ext-event registry.
38
40
  *