@jini-ai/chat 0.6.0 → 0.6.1
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.
- package/CHANGELOG.md +4 -0
- package/dist/react/components/JiniChatProvider.d.ts +9 -4
- package/dist/react/components/JiniChatProvider.js +9 -5
- package/dist/react/components/McpUiSurfaceCard.d.ts +4 -1
- package/dist/react/components/McpUiSurfaceCard.js +5 -4
- package/dist/react/components/MessageRow.js +8 -8
- package/dist/react/components/ToolCard.js +5 -5
- package/dist/react/ext-event-renderer-registry.d.ts +27 -12
- package/dist/react/ext-event-renderer-registry.js +53 -33
- package/dist/react/hooks/context.d.ts +8 -0
- package/dist/react/hooks/context.js +8 -0
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +2 -2
- package/dist/react/tool-renderer-registry.d.ts +25 -8
- package/dist/react/tool-renderer-registry.js +31 -16
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 0.6.1 — 2026-10-13
|
|
2
|
+
|
|
3
|
+
- Owned renderer registries: `createToolRendererRegistry` / `createExtEventRendererRegistry`, optional `JiniChatProvider` props `toolRendererRegistry` / `extEventRendererRegistry`, and `registerMcpUiSurfaceRenderer({ registry })`. The global register/get/clear functions keep working against a compatibility default. A stale unregister handle no longer removes a replacement registered with the same callback.
|
|
4
|
+
|
|
1
5
|
## 0.6.0 — 2026-10-13
|
|
2
6
|
|
|
3
7
|
- Remove Recharts peer/dev dependencies. Legacy chart wire components now use the neutral UI host with a styled table fallback; require the extracted UI ^0.5.0 so older built-in Recharts imports cannot leak into a clean consumer.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module JiniChatProvider
|
|
3
3
|
*
|
|
4
|
-
* Wires transport, project, analytics, i18n, and the artifact
|
|
5
|
-
*
|
|
4
|
+
* Wires transport, project, analytics, i18n, and the artifact/tool/extension renderer
|
|
5
|
+
* registries into context — exactly the `JiniChatProviderProps` shape from
|
|
6
6
|
* `ADS-memory/reports/jini-port/recon/r4b-webui-design.md` §2. This is the ONE file in
|
|
7
7
|
* this package's presentational layer allowed to bind concrete
|
|
8
8
|
* adapters/slots to context (mirrors the OD slice discipline's
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* boundary lint item 3 — the composition-root analogue for a package
|
|
11
11
|
* instead of a feature slice).
|
|
12
12
|
*
|
|
13
|
-
* `slots`/`onFeedback
|
|
13
|
+
* `slots`/`onFeedback` are threaded through unchanged for a
|
|
14
14
|
* consuming host to read via `useJiniChatSlots()`/`useOnFeedback()` — this
|
|
15
15
|
* package's own components (`<Composer>`, `<MessageRow>`, ...) take the
|
|
16
16
|
* slots they need directly as props rather than reading them from context
|
|
@@ -22,6 +22,8 @@ import { type ReactNode } from 'react';
|
|
|
22
22
|
import type { ChatTransport, OnFeedback } from '../../core/index.js';
|
|
23
23
|
import type { AnalyticsAdapter, AttachmentTraySlot, ComposerSlots, FilePreviewSlot, I18nAdapter, ModelAgentPickerSlot, AnnotationAdapter, ProjectContextValue } from '../slots.js';
|
|
24
24
|
import type { RendererRegistry } from '../artifact-types.js';
|
|
25
|
+
import type { ToolRendererRegistry } from '../tool-renderer-registry.js';
|
|
26
|
+
import type { ExtEventRendererRegistry } from '../ext-event-renderer-registry.js';
|
|
25
27
|
export interface JiniChatSlots {
|
|
26
28
|
modelPicker?: ModelAgentPickerSlot;
|
|
27
29
|
composer?: ComposerSlots;
|
|
@@ -35,11 +37,14 @@ export interface JiniChatProviderProps {
|
|
|
35
37
|
analytics?: AnalyticsAdapter;
|
|
36
38
|
i18n?: I18nAdapter;
|
|
37
39
|
artifactRegistry?: RendererRegistry;
|
|
40
|
+
/** Optional owned state; omitted bindings inherit the nearest provider or global default. */
|
|
41
|
+
toolRendererRegistry?: ToolRendererRegistry;
|
|
42
|
+
extEventRendererRegistry?: ExtEventRendererRegistry;
|
|
38
43
|
slots?: JiniChatSlots;
|
|
39
44
|
onFeedback?: OnFeedback;
|
|
40
45
|
children: ReactNode;
|
|
41
46
|
}
|
|
42
|
-
export declare function JiniChatProvider({ transport, project, analytics, i18n, artifactRegistry, slots, onFeedback, children }: JiniChatProviderProps): import("react").JSX.Element;
|
|
47
|
+
export declare function JiniChatProvider({ transport, project, analytics, i18n, artifactRegistry, toolRendererRegistry, extEventRendererRegistry, slots, onFeedback, children }: JiniChatProviderProps): import("react").JSX.Element;
|
|
43
48
|
export declare function useJiniChatSlots(): JiniChatSlots;
|
|
44
49
|
export declare function useOnFeedback(): OnFeedback | undefined;
|
|
45
50
|
//# sourceMappingURL=JiniChatProvider.d.ts.map
|
|
@@ -2,8 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* @module JiniChatProvider
|
|
4
4
|
*
|
|
5
|
-
* Wires transport, project, analytics, i18n, and the artifact
|
|
6
|
-
*
|
|
5
|
+
* Wires transport, project, analytics, i18n, and the artifact/tool/extension renderer
|
|
6
|
+
* registries into context — exactly the `JiniChatProviderProps` shape from
|
|
7
7
|
* `ADS-memory/reports/jini-port/recon/r4b-webui-design.md` §2. This is the ONE file in
|
|
8
8
|
* this package's presentational layer allowed to bind concrete
|
|
9
9
|
* adapters/slots to context (mirrors the OD slice discipline's
|
|
@@ -11,7 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
* boundary lint item 3 — the composition-root analogue for a package
|
|
12
12
|
* instead of a feature slice).
|
|
13
13
|
*
|
|
14
|
-
* `slots`/`onFeedback
|
|
14
|
+
* `slots`/`onFeedback` are threaded through unchanged for a
|
|
15
15
|
* consuming host to read via `useJiniChatSlots()`/`useOnFeedback()` — this
|
|
16
16
|
* package's own components (`<Composer>`, `<MessageRow>`, ...) take the
|
|
17
17
|
* slots they need directly as props rather than reading them from context
|
|
@@ -20,11 +20,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
20
20
|
* without re-threading props through every layer.
|
|
21
21
|
*/
|
|
22
22
|
import { createContext, useContext } from 'react';
|
|
23
|
-
import { AnalyticsContext, ArtifactRegistryContext, ChatTransportContext, I18nContext, ProjectContext } from '../hooks/context.js';
|
|
23
|
+
import { AnalyticsContext, ArtifactRegistryContext, ToolRendererRegistryContext, ExtEventRendererRegistryContext, ChatTransportContext, I18nContext, ProjectContext } from '../hooks/context.js';
|
|
24
24
|
const JiniChatSlotsContext = createContext({});
|
|
25
25
|
const OnFeedbackContext = createContext(undefined);
|
|
26
|
-
export function JiniChatProvider({ transport, project, analytics, i18n, artifactRegistry, slots, onFeedback, children }) {
|
|
26
|
+
export function JiniChatProvider({ transport, project, analytics, i18n, artifactRegistry, toolRendererRegistry, extEventRendererRegistry, slots, onFeedback, children }) {
|
|
27
27
|
let tree = (_jsx(ChatTransportContext.Provider, { value: transport, children: _jsx(JiniChatSlotsContext.Provider, { value: slots ?? {}, children: _jsx(OnFeedbackContext.Provider, { value: onFeedback, children: children }) }) }));
|
|
28
|
+
if (toolRendererRegistry)
|
|
29
|
+
tree = _jsx(ToolRendererRegistryContext.Provider, { value: toolRendererRegistry, children: tree });
|
|
30
|
+
if (extEventRendererRegistry)
|
|
31
|
+
tree = _jsx(ExtEventRendererRegistryContext.Provider, { value: extEventRendererRegistry, children: tree });
|
|
28
32
|
if (artifactRegistry)
|
|
29
33
|
tree = _jsx(ArtifactRegistryContext.Provider, { value: artifactRegistry, children: tree });
|
|
30
34
|
if (project)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type McpUiToolCallHandler } from '@jini-ai/ui/mcp-ui';
|
|
2
|
-
import { type ExtEventRenderProps } from '../ext-event-renderer-registry.js';
|
|
2
|
+
import { type ExtEventRendererRegistry, type ExtEventRenderProps } from '../ext-event-renderer-registry.js';
|
|
3
3
|
import { type SurfaceExpiryClock } from '../hooks/useSurfaceExpiry.js';
|
|
4
4
|
/** The `kind: 'ext'` event name this renderer claims. */
|
|
5
5
|
export declare const MCP_UI_EXT_EVENT_NAME = "mcp-ui";
|
|
@@ -55,6 +55,7 @@ export declare function mcpUiSurfaceSlotKey({ data }: {
|
|
|
55
55
|
* @param options.maxHeight - See {@link McpUiSurfaceCardProps.maxHeight}. A host with a fixed-width
|
|
56
56
|
* pane (a docked sidebar rather than a full-width transcript) should set this once here instead of
|
|
57
57
|
* accepting the library's full-width-oriented 720px default.
|
|
58
|
+
* @param options.registry - Owned registry bound to JiniChatProvider; omitted for the global default.
|
|
58
59
|
* @returns An unregister handle, so a test or a hot reload can dispose cleanly.
|
|
59
60
|
*/
|
|
60
61
|
export declare function registerMcpUiSurfaceRenderer(options: {
|
|
@@ -63,5 +64,7 @@ export declare function registerMcpUiSurfaceRenderer(options: {
|
|
|
63
64
|
onOpenLink?: (url: string) => void;
|
|
64
65
|
name?: string;
|
|
65
66
|
maxHeight?: number;
|
|
67
|
+
/** Register in owned state when supplied; otherwise preserve the global API. */
|
|
68
|
+
registry?: ExtEventRendererRegistry;
|
|
66
69
|
}): () => void;
|
|
67
70
|
//# sourceMappingURL=McpUiSurfaceCard.d.ts.map
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
*
|
|
5
5
|
* Renders MCP-UI Views inline in a chat transcript, fed by a message's `kind: 'ext', name: 'mcp-ui'`
|
|
6
6
|
* events — the same extensibility seam `A2uiSurfaceCard.tsx` uses, and registered the same way: a
|
|
7
|
-
* host calls {@link registerMcpUiSurfaceRenderer}
|
|
8
|
-
*
|
|
7
|
+
* host calls {@link registerMcpUiSurfaceRenderer} with its owned registry (or the compatibility
|
|
8
|
+
* default) and matching events in that registry's chat tree render through it.
|
|
9
9
|
*
|
|
10
10
|
* Each event's `data` is expected to be an MCP `EmbeddedResource` carrying UI — exactly the object a
|
|
11
11
|
* tool result's `content` array holds, unwrapped. One shape, deliberately: accepting several
|
|
@@ -63,7 +63,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
63
63
|
*/
|
|
64
64
|
import { Fragment, useMemo } from 'react';
|
|
65
65
|
import { McpUiHost, parseUIResource, readActionPlan, readExpiresAt, readPreferredFrameSize, } from '@jini-ai/ui/mcp-ui';
|
|
66
|
-
import {
|
|
66
|
+
import { defaultExtEventRendererRegistry } from '../ext-event-renderer-registry.js';
|
|
67
67
|
import { useT } from '../hooks/context.js';
|
|
68
68
|
import { useSurfaceExpiry } from '../hooks/useSurfaceExpiry.js';
|
|
69
69
|
import { closedSurfaceLabel, reportDeliveredAnswer } from './mcp-ui-surface-state.js';
|
|
@@ -181,9 +181,10 @@ export function mcpUiSurfaceSlotKey({ data }) {
|
|
|
181
181
|
* @param options.maxHeight - See {@link McpUiSurfaceCardProps.maxHeight}. A host with a fixed-width
|
|
182
182
|
* pane (a docked sidebar rather than a full-width transcript) should set this once here instead of
|
|
183
183
|
* accepting the library's full-width-oriented 720px default.
|
|
184
|
+
* @param options.registry - Owned registry bound to JiniChatProvider; omitted for the global default.
|
|
184
185
|
* @returns An unregister handle, so a test or a hot reload can dispose cleanly.
|
|
185
186
|
*/
|
|
186
187
|
export function registerMcpUiSurfaceRenderer(options) {
|
|
187
|
-
return
|
|
188
|
+
return (options.registry ?? defaultExtEventRendererRegistry).register({ name: options.name ?? MCP_UI_EXT_EVENT_NAME, renderer: (props) => (_jsx(McpUiSurfaceCard, { ...props, sandboxProxyUrl: options.sandboxProxyUrl, ...(options.onToolCall === undefined ? {} : { onToolCall: options.onToolCall }), ...(options.onOpenLink === undefined ? {} : { onOpenLink: options.onOpenLink }), ...(options.maxHeight === undefined ? {} : { maxHeight: options.maxHeight }) })) }, { slotKey: mcpUiSurfaceSlotKey });
|
|
188
189
|
}
|
|
189
190
|
//# sourceMappingURL=McpUiSurfaceCard.js.map
|
|
@@ -59,8 +59,7 @@ import { useCopyToClipboard } from '../hooks/useCopyToClipboard.js';
|
|
|
59
59
|
import { useRunActivity } from '../hooks/useRunActivity.js';
|
|
60
60
|
import { useAnsweredCards } from '../hooks/useAnsweredCards.js';
|
|
61
61
|
import { interleaveMessageBlocks } from '../message-blocks.js';
|
|
62
|
-
import { useT } from '../hooks/context.js';
|
|
63
|
-
import { extEventSlot, getExtEventRenderer } from '../ext-event-renderer-registry.js';
|
|
62
|
+
import { useT, useExtEventRendererRegistry } from '../hooks/context.js';
|
|
64
63
|
import { ExtEventErrorBoundary } from './ExtEventErrorBoundary.js';
|
|
65
64
|
import { AttachmentPreviewModal } from './AttachmentPreviewModal.js';
|
|
66
65
|
import { SentAttachmentButton } from './SentAttachment.js';
|
|
@@ -116,7 +115,7 @@ function UsageSummary({ usage, showTotal }) {
|
|
|
116
115
|
return null;
|
|
117
116
|
return (_jsxs("div", { className: "jini-message-usage", title: title, role: "note", "aria-label": ariaLabel, children: [_jsx("span", { className: "jini-message-usage-dot", "aria-hidden": true, children: "\u25CF" }), t('Done'), " \u00B7 ", text] }));
|
|
118
117
|
}
|
|
119
|
-
function messageBlocks({ visibleContent, message, rows }, { toolCallDisplay = 'show', includeThinking = false } = {}) {
|
|
118
|
+
function messageBlocks({ visibleContent, message, rows, registry }, { toolCallDisplay = 'show', includeThinking = false } = {}) {
|
|
120
119
|
// Interleaving is skipped outright when an artifact was stripped. `stripArtifact` operates on the
|
|
121
120
|
// whole string and trims, so the surviving text no longer lines up with the per-run offsets the
|
|
122
121
|
// event walk produces, and an artifact block could legitimately span a tool call. Rather than
|
|
@@ -124,7 +123,7 @@ function messageBlocks({ visibleContent, message, rows }, { toolCallDisplay = 's
|
|
|
124
123
|
// messages are dominated by the artifact panel anyway, so the ordering matters least there.
|
|
125
124
|
if (visibleContent !== message.content)
|
|
126
125
|
return null;
|
|
127
|
-
const blocks = interleaveMessageBlocks({ events: message.events, content: message.content, rows }, { slotOf: (ev) =>
|
|
126
|
+
const blocks = interleaveMessageBlocks({ events: message.events, content: message.content, rows }, { slotOf: (ev) => registry.slot({ name: ev.name, data: ev.data }), includeThinking });
|
|
128
127
|
return blocks ? applyToolCallDisplay({ blocks, mode: toolCallDisplay }) : null;
|
|
129
128
|
}
|
|
130
129
|
/** The model's reasoning, folded under a "Thinking" summary so it never crowds the answer. */
|
|
@@ -164,10 +163,11 @@ function CopyMessageButton({ text, label }) {
|
|
|
164
163
|
*/
|
|
165
164
|
export const MessageRow = memo(function MessageRow({ message, usageSummary, showUsageTotal = true, showUsageLine = true, toolCallDisplay = 'show', showThinking = false, runStreaming = false, runSucceeded = false, questionFormInteractive = false, questionFormSubmittedAnswers, onQuestionFormSubmit, projectFileNames, onRequestOpenFile, renderAttachment, }) {
|
|
166
165
|
const t = useT();
|
|
166
|
+
const registry = useExtEventRendererRegistry();
|
|
167
167
|
const messageUsage = useMessageUsage({ message, ...(usageSummary === undefined ? {} : { summary: usageSummary }) });
|
|
168
168
|
const timeline = useToolTimeline({ events: message.events }, { runStreaming, runSucceeded });
|
|
169
|
-
const extGroups = useExtEventGroups({ events: message.events }, { slotOf: (name, data) =>
|
|
170
|
-
const answeredCards = useAnsweredCards({ slotOf:
|
|
169
|
+
const extGroups = useExtEventGroups({ events: message.events }, { slotOf: (name, data) => registry.slot({ name, data }) });
|
|
170
|
+
const answeredCards = useAnsweredCards({ slotOf: registry.slot });
|
|
171
171
|
// Which attachment chip (if any) this row's preview modal is open for. Row-local, not
|
|
172
172
|
// module-level: two attachments in the same message can never be "open" at once, but two
|
|
173
173
|
// different messages opening their own attachments independently is fine and expected.
|
|
@@ -178,7 +178,7 @@ export const MessageRow = memo(function MessageRow({ message, usageSummary, show
|
|
|
178
178
|
const terminalNotice = terminalMessageNotice({ message }, {});
|
|
179
179
|
const visibleContent = stripArtifact({ content: message.content });
|
|
180
180
|
const segments = splitOnQuestionForms({ input: visibleContent });
|
|
181
|
-
const blocks = messageBlocks({ visibleContent, message, rows: timeline.rows }, { toolCallDisplay, includeThinking: showThinking });
|
|
181
|
+
const blocks = messageBlocks({ visibleContent, message, rows: timeline.rows, registry }, { toolCallDisplay, includeThinking: showThinking });
|
|
182
182
|
const pending = isPendingWithNoContent(message, visibleContent, timeline.rows.length);
|
|
183
183
|
const runInProgress = isRunInProgress(message.runStatus);
|
|
184
184
|
const awaitingAnswer = messageAwaitingAnswer({ runStreaming, runInProgress, events: message.events }, { isAnsweredCard: answeredCards.isAnsweredCard });
|
|
@@ -191,7 +191,7 @@ export const MessageRow = memo(function MessageRow({ message, usageSummary, show
|
|
|
191
191
|
: _jsx("div", { className: "jini-message-tools", children: rows.map(renderToolCard) }, key));
|
|
192
192
|
const flatToolRows = toolRowsForDisplay({ rows: timeline.rows, mode: toolCallDisplay });
|
|
193
193
|
const renderExtGroup = (group) => {
|
|
194
|
-
const renderer =
|
|
194
|
+
const renderer = registry.get({ name: group.name });
|
|
195
195
|
if (!renderer)
|
|
196
196
|
return null;
|
|
197
197
|
const node = renderer({
|
|
@@ -22,8 +22,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
22
22
|
*/
|
|
23
23
|
import { useId, useState } from 'react';
|
|
24
24
|
import { describeCollapsedToolCall, describeToolCallDetail, formatToolOutputForDisplay, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, toRenderProps } from '../../core/index.js';
|
|
25
|
-
import { useT } from '../hooks/context.js';
|
|
26
|
-
import { getToolRenderer } from '../tool-renderer-registry.js';
|
|
25
|
+
import { useT, useToolRendererRegistry } from '../hooks/context.js';
|
|
27
26
|
import { Icon } from './Icon.js';
|
|
28
27
|
import { TodoCard } from './TodoCard.js';
|
|
29
28
|
/** A Jini registry tool id: dotted, lowercase, snake_case segments — `page.fill`, `daemon.db.vacuum`, `chat.send_message`. Never matches an agent-vendor tool name (`Bash`, `Write`, `mcp__jini__execute_delegated_tool`), which is the point — this is how a canonical delegated-tool event is told apart from everything else. */
|
|
@@ -31,10 +30,11 @@ const JINI_TOOL_ID_PATTERN = /^[a-z][a-z_]*(\.[a-z][a-z_]*)+$/;
|
|
|
31
30
|
const SEARCH_TOOLS_NAMES = new Set(['search_tools', 'mcp__jini__search_tools']);
|
|
32
31
|
const DESCRIBE_TOOL_NAMES = new Set(['describe_tool', 'mcp__jini__describe_tool']);
|
|
33
32
|
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }) {
|
|
33
|
+
const registry = useToolRendererRegistry();
|
|
34
34
|
const name = use.name;
|
|
35
35
|
const isStreaming = runStreaming ?? false;
|
|
36
36
|
const isSucceeded = runSucceeded ?? false;
|
|
37
|
-
const content = renderToolCardBody(name, use, result, isStreaming, isSucceeded, { projectFileNames, onRequestOpenFile });
|
|
37
|
+
const content = renderToolCardBody(name, use, result, isStreaming, isSucceeded, { projectFileNames, onRequestOpenFile }, registry);
|
|
38
38
|
if (content === null)
|
|
39
39
|
return null;
|
|
40
40
|
// Tagged once here, at the single dispatch point every branch below funnels through, rather than
|
|
@@ -44,8 +44,8 @@ export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileN
|
|
|
44
44
|
// ambiguous the way a repeated literal handle would be.
|
|
45
45
|
return (_jsx("div", { "data-agent-element": `tool-call-${use.id}`, "data-agent-role": "region", "data-agent-label": `A ${humanizeToolId(name)} tool call the AI made`, children: content }));
|
|
46
46
|
}
|
|
47
|
-
function renderToolCardBody(name, use, result, isStreaming, isSucceeded, fileCtx) {
|
|
48
|
-
const custom =
|
|
47
|
+
function renderToolCardBody(name, use, result, isStreaming, isSucceeded, fileCtx, registry) {
|
|
48
|
+
const custom = registry.get({ name });
|
|
49
49
|
if (custom) {
|
|
50
50
|
try {
|
|
51
51
|
const node = custom(toRenderProps({ use: use, result: result, runStreaming: isStreaming }, { runSucceeded: isSucceeded }));
|
|
@@ -88,26 +88,41 @@ export interface ExtEventRendererOptions {
|
|
|
88
88
|
/** See {@link ExtEventSlotKey}. Omit for one shared slot per `name`. */
|
|
89
89
|
slotKey?: ExtEventSlotKey;
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
/** Owned renderer and slot-key state for one chat tree. */
|
|
92
|
+
export interface ExtEventRendererRegistry {
|
|
93
|
+
register(required: {
|
|
94
|
+
name: string;
|
|
95
|
+
renderer: ExtEventRenderer;
|
|
96
|
+
}, options?: ExtEventRendererOptions): () => void;
|
|
97
|
+
get(required: {
|
|
98
|
+
name: string;
|
|
99
|
+
}): ExtEventRenderer | undefined;
|
|
100
|
+
slot(required: {
|
|
101
|
+
name: string;
|
|
102
|
+
data: unknown;
|
|
103
|
+
}): string;
|
|
104
|
+
clear(): void;
|
|
105
|
+
}
|
|
106
|
+
/** Create independent extension renderer state, including each protocol's slot grouping.
|
|
107
|
+
* @returns A keyed registry with identity-safe unregister handles.
|
|
108
|
+
* @example const extensions = createExtEventRendererRegistry({}); extensions.register({ name: 'card', renderer });
|
|
94
109
|
*/
|
|
95
|
-
export declare function
|
|
110
|
+
export declare function createExtEventRendererRegistry(_required?: Record<string, never>): ExtEventRendererRegistry;
|
|
111
|
+
/** Compatibility default for hosts that have not supplied owned state. */
|
|
112
|
+
export declare const defaultExtEventRendererRegistry: ExtEventRendererRegistry;
|
|
113
|
+
/** Register against the compatibility default; see the owned registry's registration contract. */
|
|
114
|
+
export declare function registerExtEventRenderer(required: {
|
|
96
115
|
name: string;
|
|
97
116
|
renderer: ExtEventRenderer;
|
|
98
117
|
}, options?: ExtEventRendererOptions): () => void;
|
|
99
|
-
export declare function getExtEventRenderer(
|
|
118
|
+
export declare function getExtEventRenderer(required: {
|
|
100
119
|
name: string;
|
|
101
120
|
}): ExtEventRenderer | undefined;
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
* registered with a {@link ExtEventSlotKey} that recognizes this event. A throwing key function
|
|
105
|
-
* falls back to the shared slot rather than taking the whole message down.
|
|
106
|
-
*/
|
|
107
|
-
export declare function extEventSlot({ name, data }: {
|
|
121
|
+
/** Resolve a slot using the compatibility default's registered slot key. */
|
|
122
|
+
export declare function extEventSlot(required: {
|
|
108
123
|
name: string;
|
|
109
124
|
data: unknown;
|
|
110
125
|
}): string;
|
|
111
|
-
/** Visible mainly for tests. */
|
|
126
|
+
/** Visible mainly for tests using the compatibility default. */
|
|
112
127
|
export declare function clearExtEventRenderers(): void;
|
|
113
128
|
//# sourceMappingURL=ext-event-renderer-registry.d.ts.map
|
|
@@ -1,39 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* can dispose cleanly. Re-registering the same name overwrites — last writer wins.
|
|
1
|
+
/** Create independent extension renderer state, including each protocol's slot grouping.
|
|
2
|
+
* @returns A keyed registry with identity-safe unregister handles.
|
|
3
|
+
* @example const extensions = createExtEventRendererRegistry({}); extensions.register({ name: 'card', renderer });
|
|
5
4
|
*/
|
|
6
|
-
export function
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* registered with a {@link ExtEventSlotKey} that recognizes this event. A throwing key function
|
|
20
|
-
* falls back to the shared slot rather than taking the whole message down.
|
|
21
|
-
*/
|
|
22
|
-
export function extEventSlot({ name, data }) {
|
|
23
|
-
const slotKey = renderers.get(name)?.slotKey;
|
|
24
|
-
if (!slotKey)
|
|
25
|
-
return name;
|
|
26
|
-
let key;
|
|
27
|
-
try {
|
|
28
|
-
key = slotKey({ data });
|
|
5
|
+
export function createExtEventRendererRegistry(_required = {}) {
|
|
6
|
+
const renderers = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Register a renderer for an ext-event `name`. Returns an unregister handle so tests / hot-reloads
|
|
9
|
+
* can dispose cleanly. Re-registering the same name overwrites — last writer wins.
|
|
10
|
+
*/
|
|
11
|
+
function register({ name, renderer }, options = {}) {
|
|
12
|
+
const entry = { renderer, slotKey: options.slotKey };
|
|
13
|
+
renderers.set(name, entry);
|
|
14
|
+
return () => {
|
|
15
|
+
if (renderers.get(name) === entry)
|
|
16
|
+
renderers.delete(name);
|
|
17
|
+
};
|
|
29
18
|
}
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
function get({ name }) {
|
|
20
|
+
return renderers.get(name)?.renderer;
|
|
32
21
|
}
|
|
33
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The render slot an ext event belongs to: its `name`, or `name:key` when the name's renderer was
|
|
24
|
+
* registered with a {@link ExtEventSlotKey} that recognizes this event. A throwing key function
|
|
25
|
+
* falls back to the shared slot rather than taking the whole message down.
|
|
26
|
+
*/
|
|
27
|
+
function slot({ name, data }) {
|
|
28
|
+
const slotKey = renderers.get(name)?.slotKey;
|
|
29
|
+
if (!slotKey)
|
|
30
|
+
return name;
|
|
31
|
+
let key;
|
|
32
|
+
try {
|
|
33
|
+
key = slotKey({ data });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
key = undefined;
|
|
37
|
+
}
|
|
38
|
+
return key === undefined || key === '' ? name : `${name}:${key}`;
|
|
39
|
+
}
|
|
40
|
+
/** Visible mainly for tests. */
|
|
41
|
+
function clear() { renderers.clear(); }
|
|
42
|
+
return { register, get, slot, clear };
|
|
43
|
+
}
|
|
44
|
+
/** Compatibility default for hosts that have not supplied owned state. */
|
|
45
|
+
export const defaultExtEventRendererRegistry = createExtEventRendererRegistry({});
|
|
46
|
+
/** Register against the compatibility default; see the owned registry's registration contract. */
|
|
47
|
+
export function registerExtEventRenderer(required, options = {}) {
|
|
48
|
+
return defaultExtEventRendererRegistry.register(required, options);
|
|
49
|
+
}
|
|
50
|
+
export function getExtEventRenderer(required) {
|
|
51
|
+
return defaultExtEventRendererRegistry.get(required);
|
|
34
52
|
}
|
|
35
|
-
/**
|
|
36
|
-
export function
|
|
37
|
-
|
|
53
|
+
/** Resolve a slot using the compatibility default's registered slot key. */
|
|
54
|
+
export function extEventSlot(required) {
|
|
55
|
+
return defaultExtEventRendererRegistry.slot(required);
|
|
38
56
|
}
|
|
57
|
+
/** Visible mainly for tests using the compatibility default. */
|
|
58
|
+
export function clearExtEventRenderers() { defaultExtEventRendererRegistry.clear(); }
|
|
39
59
|
//# sourceMappingURL=ext-event-renderer-registry.js.map
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { AnalyticsAdapter, I18nAdapter, ProjectContextValue } from '../slots.js';
|
|
2
2
|
import type { ChatTransport } from '../../core/index.js';
|
|
3
3
|
import type { RendererRegistry } from '../artifact-types.js';
|
|
4
|
+
import { type ToolRendererRegistry } from '../tool-renderer-registry.js';
|
|
5
|
+
import { type ExtEventRendererRegistry } from '../ext-event-renderer-registry.js';
|
|
4
6
|
export declare const I18nContext: import("react").Context<I18nAdapter>;
|
|
5
7
|
export declare const AnalyticsContext: import("react").Context<AnalyticsAdapter>;
|
|
6
8
|
export declare const ProjectContext: import("react").Context<ProjectContextValue | undefined>;
|
|
7
9
|
export declare const ChatTransportContext: import("react").Context<ChatTransport | undefined>;
|
|
10
|
+
export declare const ToolRendererRegistryContext: import("react").Context<ToolRendererRegistry>;
|
|
11
|
+
export declare const ExtEventRendererRegistryContext: import("react").Context<ExtEventRendererRegistry>;
|
|
8
12
|
export declare const ArtifactRegistryContext: import("react").Context<RendererRegistry | undefined>;
|
|
9
13
|
/** Convenience for call sites that only need the translator function. */
|
|
10
14
|
export declare function useT(): I18nAdapter['t'];
|
|
@@ -15,4 +19,8 @@ export declare function useProjectContext(): ProjectContextValue | undefined;
|
|
|
15
19
|
/** Throws when read outside `<JiniChatProvider>` — unlike the other contexts, hooks that need a transport (`useConversation`/`useRunStream`) have no sensible no-op fallback. */
|
|
16
20
|
export declare function useChatTransport(): ChatTransport;
|
|
17
21
|
export declare function useArtifactRegistry(): RendererRegistry | undefined;
|
|
22
|
+
/** Read the nearest tool binding, or the compatibility default outside an owned provider. */
|
|
23
|
+
export declare function useToolRendererRegistry(): ToolRendererRegistry;
|
|
24
|
+
/** Renderer lookup and grouping must read the same provider binding. */
|
|
25
|
+
export declare function useExtEventRendererRegistry(): ExtEventRendererRegistry;
|
|
18
26
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* allowed dependency per `ADS-memory/reports/jini-port/recon/r4b-webui-design.md` §1).
|
|
16
16
|
*/
|
|
17
17
|
import { createContext, useContext } from 'react';
|
|
18
|
+
import { defaultToolRendererRegistry } from '../tool-renderer-registry.js';
|
|
19
|
+
import { defaultExtEventRendererRegistry } from '../ext-event-renderer-registry.js';
|
|
18
20
|
const PASSTHROUGH_I18N = {
|
|
19
21
|
t: (key, vars) => interpolate(key, vars),
|
|
20
22
|
locale: 'en',
|
|
@@ -32,6 +34,8 @@ export const I18nContext = createContext(PASSTHROUGH_I18N);
|
|
|
32
34
|
export const AnalyticsContext = createContext(NOOP_ANALYTICS);
|
|
33
35
|
export const ProjectContext = createContext(undefined);
|
|
34
36
|
export const ChatTransportContext = createContext(undefined);
|
|
37
|
+
export const ToolRendererRegistryContext = createContext(defaultToolRendererRegistry);
|
|
38
|
+
export const ExtEventRendererRegistryContext = createContext(defaultExtEventRendererRegistry);
|
|
35
39
|
export const ArtifactRegistryContext = createContext(undefined);
|
|
36
40
|
/** Convenience for call sites that only need the translator function. */
|
|
37
41
|
export function useT() {
|
|
@@ -57,4 +61,8 @@ export function useChatTransport() {
|
|
|
57
61
|
export function useArtifactRegistry() {
|
|
58
62
|
return useContext(ArtifactRegistryContext);
|
|
59
63
|
}
|
|
64
|
+
/** Read the nearest tool binding, or the compatibility default outside an owned provider. */
|
|
65
|
+
export function useToolRendererRegistry() { return useContext(ToolRendererRegistryContext); }
|
|
66
|
+
/** Renderer lookup and grouping must read the same provider binding. */
|
|
67
|
+
export function useExtEventRendererRegistry() { return useContext(ExtEventRendererRegistryContext); }
|
|
60
68
|
//# sourceMappingURL=context.js.map
|
package/dist/react/index.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ export type { ChatTransport, FeedbackChange, OnFeedback, ReattachRunOptions, Run
|
|
|
7
7
|
export { RendererRegistry, } from './artifact-types.js';
|
|
8
8
|
export type { ArtifactFile, ArtifactRenderContext, ArtifactRenderMatch, ArtifactRenderer, } from './artifact-types.js';
|
|
9
9
|
export type { AgentOption, AgentSelection, AnalyticsAdapter, AnnotationAdapter, AttachmentTraySlot, ComposerDiscoveryArgument, ComposerDiscoveryGroup, ComposerDiscoveryItem, ComposerDiscoveryOutcome, ComposerDiscoverySelection, ComposerPlusItem, ComposerSlots, FilePreviewSlot, I18nAdapter, MentionResult, MentionSource, ModelAgentPickerSlot, ProjectContextValue, } from './slots.js';
|
|
10
|
-
export { clearToolRenderers, getToolRenderer, registerToolRenderer, } from './tool-renderer-registry.js';
|
|
11
|
-
export type { ToolRenderer, } from './tool-renderer-registry.js';
|
|
12
|
-
export { clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtEventRenderer, } from './ext-event-renderer-registry.js';
|
|
13
|
-
export type { ExtEventRenderProps, ExtEventRenderer, ExtEventRendererOptions, ExtEventSlotKey, } from './ext-event-renderer-registry.js';
|
|
10
|
+
export { createToolRendererRegistry, clearToolRenderers, getToolRenderer, registerToolRenderer, } from './tool-renderer-registry.js';
|
|
11
|
+
export type { ToolRendererRegistry, ToolRenderer, } from './tool-renderer-registry.js';
|
|
12
|
+
export { createExtEventRendererRegistry, clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtEventRenderer, } from './ext-event-renderer-registry.js';
|
|
13
|
+
export type { ExtEventRendererRegistry, ExtEventRenderProps, ExtEventRenderer, ExtEventRendererOptions, ExtEventSlotKey, } from './ext-event-renderer-registry.js';
|
|
14
14
|
/**
|
|
15
15
|
* @deprecated REF-001 Step C (2026-08-05): `ChatPane` and its companions moved to their own
|
|
16
16
|
* subpath, `@jini-ai/chat/react/chat-pane` — import from there in new code. Re-exported here,
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { RendererRegistry, } from './artifact-types.js';
|
|
2
|
-
export { clearToolRenderers, getToolRenderer, registerToolRenderer, } from './tool-renderer-registry.js';
|
|
3
|
-
export { clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtEventRenderer, } from './ext-event-renderer-registry.js';
|
|
2
|
+
export { createToolRendererRegistry, clearToolRenderers, getToolRenderer, registerToolRenderer, } from './tool-renderer-registry.js';
|
|
3
|
+
export { createExtEventRendererRegistry, clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtEventRenderer, } from './ext-event-renderer-registry.js';
|
|
4
4
|
// `features/model-picker/` (REF-001 Step B, 2026-08-05): NOT re-exported here. It was, on the
|
|
5
5
|
// theory in the comment this replaced — "an independent slice... for a consumer that wants
|
|
6
6
|
// everything from one barrel" — but that consumer never existed. Checked twice, once before and
|
|
@@ -28,19 +28,36 @@ import type { ToolRenderProps } from '../core/index.js';
|
|
|
28
28
|
* step (a built-in family card, then the generic fallback).
|
|
29
29
|
*/
|
|
30
30
|
export type ToolRenderer = (props: ToolRenderProps) => ReactNode;
|
|
31
|
+
/** Owned lookup and registration state for one chat tree. */
|
|
32
|
+
export interface ToolRendererRegistry {
|
|
33
|
+
register(required: {
|
|
34
|
+
name: string;
|
|
35
|
+
renderer: ToolRenderer;
|
|
36
|
+
}): () => void;
|
|
37
|
+
get(required: {
|
|
38
|
+
name: string;
|
|
39
|
+
}): ToolRenderer | undefined;
|
|
40
|
+
clear(required: Record<string, never>): void;
|
|
41
|
+
}
|
|
42
|
+
/** Create independent tool renderer state; registrations never leak to another tree.
|
|
43
|
+
* @returns A keyed registry with identity-safe unregister handles.
|
|
44
|
+
* @example const tools = createToolRendererRegistry({}); tools.register({ name: 'Read', renderer });
|
|
45
|
+
*/
|
|
46
|
+
export declare function createToolRendererRegistry(_required?: Record<string, never>): ToolRendererRegistry;
|
|
47
|
+
/** Compatibility default for hosts that have not supplied owned state. */
|
|
48
|
+
export declare const defaultToolRendererRegistry: ToolRendererRegistry;
|
|
31
49
|
/**
|
|
32
|
-
* Register a renderer for a tool name. Returns an unregister handle so
|
|
33
|
-
* tests / hot-reloads can dispose cleanly. Names are matched case-sensitively
|
|
34
|
-
*
|
|
35
|
-
* writer wins.
|
|
50
|
+
* Register a renderer for a tool name in the compatibility default. Returns an unregister handle so
|
|
51
|
+
* tests / hot-reloads can dispose cleanly. Names are matched case-sensitively against
|
|
52
|
+
* `tool_use.name`. Re-registering the same name overwrites — last writer wins.
|
|
36
53
|
*/
|
|
37
|
-
export declare function registerToolRenderer(
|
|
54
|
+
export declare function registerToolRenderer(required: {
|
|
38
55
|
name: string;
|
|
39
56
|
renderer: ToolRenderer;
|
|
40
57
|
}): () => void;
|
|
41
|
-
export declare function getToolRenderer(
|
|
58
|
+
export declare function getToolRenderer(required: {
|
|
42
59
|
name: string;
|
|
43
60
|
}): ToolRenderer | undefined;
|
|
44
|
-
/** Visible mainly for tests. */
|
|
45
|
-
export declare function clearToolRenderers(
|
|
61
|
+
/** Visible mainly for tests using the compatibility default. */
|
|
62
|
+
export declare function clearToolRenderers(required: Record<string, never>): void;
|
|
46
63
|
//# sourceMappingURL=tool-renderer-registry.d.ts.map
|
|
@@ -1,22 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* tests / hot-reloads can dispose cleanly. Names are matched case-sensitively
|
|
5
|
-
* against `tool_use.name`. Re-registering the same name overwrites — last
|
|
6
|
-
* writer wins.
|
|
1
|
+
/** Create independent tool renderer state; registrations never leak to another tree.
|
|
2
|
+
* @returns A keyed registry with identity-safe unregister handles.
|
|
3
|
+
* @example const tools = createToolRendererRegistry({}); tools.register({ name: 'Read', renderer });
|
|
7
4
|
*/
|
|
8
|
-
export function
|
|
9
|
-
renderers
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
export function createToolRendererRegistry(_required = {}) {
|
|
6
|
+
const renderers = new Map();
|
|
7
|
+
return {
|
|
8
|
+
// Names are case-sensitive; last writer wins. Compare the registration entry rather than
|
|
9
|
+
// the callback: re-registering the same callback still invalidates its old cleanup handle.
|
|
10
|
+
register({ name, renderer }) {
|
|
11
|
+
const entry = { renderer };
|
|
12
|
+
renderers.set(name, entry);
|
|
13
|
+
return () => { if (renderers.get(name) === entry)
|
|
14
|
+
renderers.delete(name); };
|
|
15
|
+
},
|
|
16
|
+
get({ name }) { return renderers.get(name)?.renderer; },
|
|
17
|
+
clear(_required) { renderers.clear(); },
|
|
13
18
|
};
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
/** Compatibility default for hosts that have not supplied owned state. */
|
|
21
|
+
export const defaultToolRendererRegistry = createToolRendererRegistry({});
|
|
22
|
+
/**
|
|
23
|
+
* Register a renderer for a tool name in the compatibility default. Returns an unregister handle so
|
|
24
|
+
* tests / hot-reloads can dispose cleanly. Names are matched case-sensitively against
|
|
25
|
+
* `tool_use.name`. Re-registering the same name overwrites — last writer wins.
|
|
26
|
+
*/
|
|
27
|
+
export function registerToolRenderer(required) {
|
|
28
|
+
return defaultToolRendererRegistry.register(required);
|
|
29
|
+
}
|
|
30
|
+
export function getToolRenderer(required) {
|
|
31
|
+
return defaultToolRendererRegistry.get(required);
|
|
17
32
|
}
|
|
18
|
-
/** Visible mainly for tests. */
|
|
19
|
-
export function clearToolRenderers(
|
|
20
|
-
|
|
33
|
+
/** Visible mainly for tests using the compatibility default. */
|
|
34
|
+
export function clearToolRenderers(required) {
|
|
35
|
+
defaultToolRendererRegistry.clear(required);
|
|
21
36
|
}
|
|
22
37
|
//# sourceMappingURL=tool-renderer-registry.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/chat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Jini's chat/artifact frontend, consolidated into one package with two subpaths: framework-free chat vocabulary and pure parsers in `/core`, headless hooks + presentational components + slots in `/react`. Import only the layer you need.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
"registry": "https://registry.npmjs.org"
|
|
197
197
|
},
|
|
198
198
|
"dependencies": {
|
|
199
|
-
"@jini-ai/core": "^0.4.
|
|
199
|
+
"@jini-ai/core": "^0.4.5"
|
|
200
200
|
},
|
|
201
201
|
"peerDependencies": {
|
|
202
202
|
"react": "^18.3.0 || ^19.0.0",
|
|
@@ -304,11 +304,11 @@
|
|
|
304
304
|
"@ag-ui/core": "0.0.58",
|
|
305
305
|
"@jini-ai/db": "0.3.4",
|
|
306
306
|
"@jini-ai/agent-runtime": "0.4.8",
|
|
307
|
-
"@jini-ai/core": "^0.4.
|
|
308
|
-
"@jini-ai/protocol": "^0.4.4",
|
|
309
|
-
"@jini-ai/ui": "^0.5.0",
|
|
307
|
+
"@jini-ai/core": "^0.4.5",
|
|
310
308
|
"@jini-ai/diagnostics": "^0.5.2",
|
|
311
|
-
"@jini-ai/
|
|
309
|
+
"@jini-ai/ui": "^0.5.1",
|
|
310
|
+
"@jini-ai/agentic": "^0.4.4",
|
|
311
|
+
"@jini-ai/protocol": "^0.4.4"
|
|
312
312
|
},
|
|
313
313
|
"typesVersions": {
|
|
314
314
|
"*": {
|