@jini-ai/chat 0.6.6 → 0.6.7
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/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +184 -97
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.js +47 -31
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +17 -8
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.js +19 -17
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +12 -10
|
@@ -12,18 +12,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { useState } from 'react';
|
|
13
13
|
import { useT } from '../hooks/context.js';
|
|
14
14
|
import { Icon } from './Icon.js';
|
|
15
|
-
export function TodoCard({ todos, runStreaming
|
|
15
|
+
export function TodoCard({ todos, runStreaming, onDismiss }) {
|
|
16
16
|
const t = useT();
|
|
17
|
-
const
|
|
18
|
-
const hasPending = todos.some((todo) => todo.status === 'pending' || todo.status === 'in_progress');
|
|
19
|
-
const defaultExpanded = todos.length > 0 && (hasInProgress || hasPending || runStreaming);
|
|
17
|
+
const { defaultExpanded, inProgressTodo, done, allComplete } = todoProgress({ todos }, { runStreaming });
|
|
20
18
|
const [overrideExpanded, setOverrideExpanded] = useState(null);
|
|
21
19
|
const expanded = overrideExpanded ?? defaultExpanded;
|
|
20
|
+
const showDismiss = Boolean(onDismiss) && allComplete;
|
|
21
|
+
return (_jsxs("div", { className: `op-card op-todo${expanded ? '' : ' op-todo-collapsed'}`, children: [_jsxs("div", { className: "op-card-head op-todo-head", children: [_jsxs("button", { type: "button", className: "op-todo-toggle", "aria-expanded": expanded, onClick: () => setOverrideExpanded(!expanded), title: expanded ? t('Collapse todos') : t('Expand todos'), children: [_jsx("span", { className: "op-icon", "aria-hidden": true, children: "\u2610" }), _jsx("span", { className: "op-title", children: t('Todos') }), _jsxs("span", { className: "op-meta", children: [done, "/", todos.length] }), !expanded && inProgressTodo ? _jsx("span", { className: "op-todo-current", children: todoLabel({ todo: inProgressTodo }) }) : null, _jsx("span", { className: "op-todo-chev", "aria-hidden": true, children: expanded ? _jsx(Icon, { name: "chevron-down", size: 11 }) : _jsx(Icon, { name: "chevron-right", size: 11 }) })] }), showDismiss ? (_jsx("button", { type: "button", className: "op-todo-done", onClick: () => onDismiss?.(), title: t('Dismiss'), children: t('Done') })) : null] }), _jsx("div", { className: `accordion-collapsible${expanded ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsx("ul", { className: "todo-list", children: todos.map((todo, i) => (_jsxs("li", { className: `todo-item todo-${todo.status}`, children: [_jsx("span", { className: "todo-check", "aria-hidden": true, children: todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '◐' : todo.status === 'stopped' ? '!' : '○' }), _jsx("span", { className: "todo-text", children: todoLabel({ todo }) })] }, i))) }) }) })] }));
|
|
22
|
+
}
|
|
23
|
+
function todoProgress({ todos }, { runStreaming = false } = {}) {
|
|
24
|
+
const hasPending = todos.some((todo) => todo.status === 'pending' || todo.status === 'in_progress');
|
|
25
|
+
const defaultExpanded = todos.length > 0 && (hasPending || runStreaming);
|
|
22
26
|
const inProgressTodo = todos.find((todo) => todo.status === 'in_progress');
|
|
23
27
|
const completed = todos.filter((todo) => todo.status === 'completed').length;
|
|
24
28
|
const done = todos.filter((todo) => todo.status === 'completed' || todo.status === 'in_progress').length;
|
|
25
29
|
const allComplete = todos.length > 0 && completed === todos.length;
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
return { defaultExpanded, inProgressTodo, done, allComplete };
|
|
31
|
+
}
|
|
32
|
+
function todoLabel({ todo }, _options = {}) {
|
|
33
|
+
return todo.status === 'in_progress' && todo.activeForm ? todo.activeForm : todo.content;
|
|
28
34
|
}
|
|
29
35
|
//# sourceMappingURL=TodoCard.js.map
|
|
@@ -27,14 +27,11 @@ import { Icon } from './Icon.js';
|
|
|
27
27
|
import { TodoCard } from './TodoCard.js';
|
|
28
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. */
|
|
29
29
|
const JINI_TOOL_ID_PATTERN = /^[a-z][a-z_]*(\.[a-z][a-z_]*)+$/;
|
|
30
|
-
const SEARCH_TOOLS_NAMES = new Set(['search_tools', 'mcp__jini__search_tools']);
|
|
31
|
-
const DESCRIBE_TOOL_NAMES = new Set(['describe_tool', 'mcp__jini__describe_tool']);
|
|
32
30
|
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }) {
|
|
33
31
|
const registry = useToolRendererRegistry();
|
|
34
32
|
const name = use.name;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const content = renderToolCardBody(name, use, result, isStreaming, isSucceeded, { projectFileNames, onRequestOpenFile }, registry);
|
|
33
|
+
const card = { input: use.input, result: result, runStreaming: runStreaming ?? false, runSucceeded: runSucceeded ?? false };
|
|
34
|
+
const content = renderToolCardBody({ use, card, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
38
35
|
if (content === null)
|
|
39
36
|
return null;
|
|
40
37
|
// Tagged once here, at the single dispatch point every branch below funnels through, rather than
|
|
@@ -44,18 +41,42 @@ export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileN
|
|
|
44
41
|
// ambiguous the way a repeated literal handle would be.
|
|
45
42
|
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
43
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
// Hardcoded family cards for tools shipped with this package, keyed by every name each tool goes
|
|
45
|
+
// by. A `Map`, not an object literal, so a tool named `constructor`/`toString` can never resolve
|
|
46
|
+
// to an inherited `Object.prototype` member.
|
|
47
|
+
const FAMILY_CARDS = new Map([
|
|
48
|
+
[['Write', 'write', 'create_file'], (card, fileCtx) => _jsx(FileWriteCard, { ...card, ctx: fileCtx })],
|
|
49
|
+
[['Edit', 'str_replace_edit'], (card, fileCtx) => _jsx(FileEditCard, { ...card, ctx: fileCtx })],
|
|
50
|
+
[['Read', 'read_file'], (card, fileCtx) => _jsx(FileReadCard, { ...card, ctx: fileCtx })],
|
|
51
|
+
[['Bash'], (card) => _jsx(BashCard, { ...card })],
|
|
52
|
+
[['Glob', 'list_files'], (card) => _jsx(GlobCard, { ...card })],
|
|
53
|
+
[['Grep'], (card) => _jsx(GrepCard, { ...card })],
|
|
54
|
+
[['WebFetch', 'web_fetch'], (card) => _jsx(WebFetchCard, { ...card })],
|
|
55
|
+
[['WebSearch', 'web_search'], (card) => _jsx(WebSearchCard, { ...card })],
|
|
56
|
+
[['search_tools', 'mcp__jini__search_tools'], (card) => _jsx(SearchToolsCard, { ...card })],
|
|
57
|
+
[['describe_tool', 'mcp__jini__describe_tool'], (card) => _jsx(DescribeToolCard, { ...card })],
|
|
58
|
+
].flatMap(([names, render]) => names.map((name) => [name, render])));
|
|
59
|
+
/** A host-registered renderer's output, or `undefined` when none is registered, it renders nothing, or it throws (logged) — every one of those falls back to the built-in cards. */
|
|
60
|
+
function renderRegistered({ use, card, registry }) {
|
|
61
|
+
const custom = registry.get({ name: use.name });
|
|
62
|
+
if (!custom)
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
const node = custom(toRenderProps({ use: use, result: card.result, runStreaming: card.runStreaming }, { runSucceeded: card.runSucceeded }));
|
|
66
|
+
if (node !== undefined && node !== null && node !== false)
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(`[ToolCard] custom renderer for "${use.name}" threw; falling back`, err);
|
|
58
71
|
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
function renderToolCardBody(args) {
|
|
75
|
+
const custom = renderRegistered(args);
|
|
76
|
+
if (custom !== undefined)
|
|
77
|
+
return custom;
|
|
78
|
+
const { use, card, fileCtx } = args;
|
|
79
|
+
const name = use.name;
|
|
59
80
|
// `execute_delegated_tool` is a transport wrapper: `@jini-ai/daemon`'s `delegated-tool-bridge.ts`
|
|
60
81
|
// always emits a second, canonical `tool_use` named after the real Jini tool id (`page.fill`,
|
|
61
82
|
// not `execute_delegated_tool`) alongside it — before calling `ToolExecutor`, so it exists even
|
|
@@ -68,37 +89,23 @@ function renderToolCardBody(name, use, result, isStreaming, isSucceeded, fileCtx
|
|
|
68
89
|
// `useToolTimeline`: `foldDelegatedWrapperCalls` drops it in favour of that canonical row. This
|
|
69
90
|
// check still covers a host that renders `<ToolCard>` straight from raw events.)
|
|
70
91
|
// Covers the read-only gateway (`execute_readonly_delegated_tool`) too.
|
|
71
|
-
if (isDelegatedWrapperToolName({ name: name }) && result && !result.isError)
|
|
92
|
+
if (isDelegatedWrapperToolName({ name: name }) && card.result && !card.result.isError)
|
|
72
93
|
return null;
|
|
73
94
|
if (isTodoWriteToolName({ name: name }))
|
|
74
|
-
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
return
|
|
81
|
-
if (name === 'Bash')
|
|
82
|
-
return _jsx(BashCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
83
|
-
if (name === 'Glob' || name === 'list_files')
|
|
84
|
-
return _jsx(GlobCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
85
|
-
if (name === 'Grep')
|
|
86
|
-
return _jsx(GrepCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
87
|
-
if (name === 'WebFetch' || name === 'web_fetch')
|
|
88
|
-
return _jsx(WebFetchCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
89
|
-
if (name === 'WebSearch' || name === 'web_search')
|
|
90
|
-
return _jsx(WebSearchCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
95
|
+
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming: card.runStreaming });
|
|
96
|
+
// Jini tool ids are dotted and wrapper names are `execute_*delegated_tool`, so neither ever
|
|
97
|
+
// collides with a family name — the search/describe cards can sit in the same table as the rest
|
|
98
|
+
// even though they are looked up ahead of the Jini-id check below.
|
|
99
|
+
const family = FAMILY_CARDS.get(name);
|
|
100
|
+
if (family)
|
|
101
|
+
return family(card, fileCtx);
|
|
91
102
|
if (JINI_TOOL_ID_PATTERN.test(name) || isDelegatedWrapperToolName({ name: name })) {
|
|
92
103
|
// The unsuppressed (failed-wrapper) case falls through here too: `name` is still
|
|
93
104
|
// `execute_delegated_tool`, so `jiniToolIdFromInput` recovers the real id from
|
|
94
105
|
// `{toolId, input}` instead of `DelegatedToolCard` receiving its own wrapper name as the title.
|
|
95
|
-
return _jsx(DelegatedToolCard, { name: name,
|
|
106
|
+
return _jsx(DelegatedToolCard, { name: name, ...card });
|
|
96
107
|
}
|
|
97
|
-
|
|
98
|
-
return _jsx(SearchToolsCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
99
|
-
if (DESCRIBE_TOOL_NAMES.has(name))
|
|
100
|
-
return _jsx(DescribeToolCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
101
|
-
return _jsx(GenericCard, { name: name, input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
108
|
+
return _jsx(GenericCard, { name: name, ...card });
|
|
102
109
|
}
|
|
103
110
|
/** `"page.fill"` -> `"Page Fill"`, `"daemon.db.vacuum"` -> `"Daemon Db Vacuum"`. Cosmetic only — never used to make a routing decision. */
|
|
104
111
|
function humanizeToolId(id) {
|
|
@@ -9,8 +9,13 @@ export function parseComposerSlashQuery({ draft }) {
|
|
|
9
9
|
const match = SLASH_QUERY_RE.exec(draft);
|
|
10
10
|
if (!match)
|
|
11
11
|
return null;
|
|
12
|
+
// The `as string` casts are guaranteed by SLASH_QUERY_RE itself, not assumed: group 1 is a bare
|
|
13
|
+
// `*` capture outside any optional group, so it always participates (possibly as ''); group 3 is
|
|
14
|
+
// `*` inside the same optional group as group 2, so it participates whenever a separator does.
|
|
15
|
+
// `noUncheckedIndexedAccess` cannot see either guarantee, and the `?? ''` fallbacks that used to
|
|
16
|
+
// satisfy it were unreachable branches.
|
|
12
17
|
const [, command, separator, rest] = match;
|
|
13
|
-
return { command: command
|
|
18
|
+
return { command: command, argument: separator === undefined ? null : rest };
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Once a separator has been typed, only an item whose declared `command` exactly equals the typed
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module components/mcp-ui-surface-state
|
|
3
|
-
*
|
|
4
|
-
* Whether an MCP-UI card in a chat transcript is closed, and with which label. Kept out of
|
|
5
|
-
* `McpUiSurfaceCard.tsx` so the component only renders; moved here from that file 2026-10-04 when
|
|
6
|
-
* the card's own deadline (`useSurfaceExpiry`) became a second reason to close it.
|
|
7
|
-
*/
|
|
8
1
|
import type { McpUiToolCallHandler } from '@jini-ai/ui/mcp-ui';
|
|
9
2
|
import type { ExtEventCall } from '../ext-event-renderer-registry.js';
|
|
10
3
|
import type { useT } from '../hooks/context.js';
|
|
@@ -35,14 +28,16 @@ export declare function closedSurfaceLabel({ t, call, runStreaming, documentsFor
|
|
|
35
28
|
export declare function isDeliveredAnswer(result: unknown): boolean;
|
|
36
29
|
/**
|
|
37
30
|
* Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
|
|
38
|
-
* passed through unchanged
|
|
39
|
-
* be answered again).
|
|
31
|
+
* passed through unchanged; onAnswerText receives readable choice text when available. A rejected call
|
|
32
|
+
* reports nothing (the dialog shows its error and can be answered again).
|
|
40
33
|
*
|
|
41
|
-
* @complexity O(
|
|
34
|
+
* @complexity O(p + s*m) answer-label mapping, plus `onToolCall`'s own cost (see questionAnswerText).
|
|
42
35
|
*/
|
|
43
|
-
export declare function reportDeliveredAnswer({ onToolCall, onAnswered }: {
|
|
36
|
+
export declare function reportDeliveredAnswer({ onToolCall, onAnswered, onAnswerText, input }: {
|
|
44
37
|
onToolCall: McpUiToolCallHandler;
|
|
45
38
|
onAnswered: () => void;
|
|
39
|
+
onAnswerText?: ((answerText: string) => void) | undefined;
|
|
40
|
+
input?: unknown;
|
|
46
41
|
}): McpUiToolCallHandler;
|
|
47
42
|
export {};
|
|
48
43
|
//# sourceMappingURL=mcp-ui-surface-state.d.ts.map
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module components/mcp-ui-surface-state
|
|
3
|
+
*
|
|
4
|
+
* Whether an MCP-UI card in a chat transcript is closed, and with which label. Kept out of
|
|
5
|
+
* `McpUiSurfaceCard.tsx` so the component only renders; moved here from that file 2026-10-04 when
|
|
6
|
+
* the card's own deadline (`useSurfaceExpiry`) became a second reason to close it.
|
|
7
|
+
*/
|
|
8
|
+
import { questionAnswerText } from '../../core/question-answer.js';
|
|
1
9
|
/** Words in a finished call's result that mean nobody answered in time. */
|
|
2
10
|
const UNANSWERED_RESULT = /\b(expired|abandoned|did not respond|run ended)\b/i;
|
|
3
11
|
/**
|
|
@@ -6,17 +14,20 @@ const UNANSWERED_RESULT = /\b(expired|abandoned|did not respond|run ended)\b/i;
|
|
|
6
14
|
* A held-open question (the tool shows the card and waits) is over once its tool call returns, and
|
|
7
15
|
* dead once its run ends without that return. Its first document stays on screen either way, so
|
|
8
16
|
* without this an expired, answered or orphaned question still looked answerable (host stuck-chat
|
|
9
|
-
* investigation, 2026-09-27).
|
|
10
|
-
*
|
|
17
|
+
* investigation, 2026-09-27). A completed tool's follow-up document for the same `ui://` URI remains
|
|
18
|
+
* visible as its outcome; repeated documents without a tool result cannot reopen an orphaned question.
|
|
11
19
|
*
|
|
12
20
|
* The labels are read from the call's result text, so they are a best guess for a producer this
|
|
13
21
|
* package has not seen; the card is closed either way, which is the property that matters.
|
|
14
22
|
*/
|
|
15
23
|
function settledLabel(t, call, runStreaming, documentsForUri) {
|
|
16
|
-
if (call === undefined
|
|
24
|
+
if (call === undefined)
|
|
17
25
|
return undefined;
|
|
26
|
+
// MOVE: Replayed surface documents cannot keep an orphaned question live after its run ends.
|
|
18
27
|
if (call.result === undefined)
|
|
19
28
|
return runStreaming ? undefined : t('This question expired');
|
|
29
|
+
if (documentsForUri > 1)
|
|
30
|
+
return undefined;
|
|
20
31
|
return UNANSWERED_RESULT.test(call.result.content) ? t('This question expired') : t('Answered');
|
|
21
32
|
}
|
|
22
33
|
/**
|
|
@@ -43,16 +54,20 @@ export function isDeliveredAnswer(result) {
|
|
|
43
54
|
}
|
|
44
55
|
/**
|
|
45
56
|
* Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
|
|
46
|
-
* passed through unchanged
|
|
47
|
-
* be answered again).
|
|
57
|
+
* passed through unchanged; onAnswerText receives readable choice text when available. A rejected call
|
|
58
|
+
* reports nothing (the dialog shows its error and can be answered again).
|
|
48
59
|
*
|
|
49
|
-
* @complexity O(
|
|
60
|
+
* @complexity O(p + s*m) answer-label mapping, plus `onToolCall`'s own cost (see questionAnswerText).
|
|
50
61
|
*/
|
|
51
|
-
export function reportDeliveredAnswer({ onToolCall, onAnswered }) {
|
|
62
|
+
export function reportDeliveredAnswer({ onToolCall, onAnswered, onAnswerText, input }) {
|
|
52
63
|
return async (call) => {
|
|
53
64
|
const result = await onToolCall(call);
|
|
54
|
-
if (isDeliveredAnswer(result))
|
|
65
|
+
if (isDeliveredAnswer(result)) {
|
|
66
|
+
const text = questionAnswerText({ answer: call.arguments, input });
|
|
67
|
+
if (text !== undefined)
|
|
68
|
+
onAnswerText?.(text);
|
|
55
69
|
onAnswered();
|
|
70
|
+
}
|
|
56
71
|
return result;
|
|
57
72
|
};
|
|
58
73
|
}
|
|
@@ -26,6 +26,8 @@ import type { ReactNode } from 'react';
|
|
|
26
26
|
* the call is still waiting, or its run ended before it returned.
|
|
27
27
|
*/
|
|
28
28
|
export interface ExtEventCall {
|
|
29
|
+
/** Stable owning tool identity for matching persisted answer receipts. */
|
|
30
|
+
readonly id?: string;
|
|
29
31
|
readonly name: string;
|
|
30
32
|
readonly input: unknown;
|
|
31
33
|
readonly result?: {
|
|
@@ -58,6 +60,12 @@ export interface ExtEventRenderProps {
|
|
|
58
60
|
* the work the answer started, and the run must stop reading as "waiting for your answer".
|
|
59
61
|
*/
|
|
60
62
|
onAnswered?: () => void;
|
|
63
|
+
/** Optional readable answer delivered through the card; emitted before onAnswered. */
|
|
64
|
+
onAnswerText?: (answerText: string) => void;
|
|
65
|
+
/** Delivered answer text while the owning tool is still finishing. */
|
|
66
|
+
answerText?: string;
|
|
67
|
+
/** A matching user_message receipt already owns the visible bubble. */
|
|
68
|
+
answerRecorded?: boolean;
|
|
61
69
|
}
|
|
62
70
|
/**
|
|
63
71
|
* Ext-event render callback. Runs inline during `MessageRow`'s render — hook-free, like
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { acceptedInputEvent } from '../core/accepted-input.js';
|
|
1
2
|
/** Create independent extension renderer state, including each protocol's slot grouping.
|
|
2
3
|
* @returns A keyed registry with identity-safe unregister handles.
|
|
3
4
|
* @example const extensions = createExtEventRendererRegistry({}); extensions.register({ name: 'card', renderer });
|
|
@@ -26,8 +27,10 @@ export function createExtEventRendererRegistry(_required = {}) {
|
|
|
26
27
|
*/
|
|
27
28
|
function slot({ name, data }) {
|
|
28
29
|
const slotKey = renderers.get(name)?.slotKey;
|
|
29
|
-
if (!slotKey)
|
|
30
|
-
|
|
30
|
+
if (!slotKey) {
|
|
31
|
+
const input = acceptedInputEvent({ event: { kind: 'ext', name, data } });
|
|
32
|
+
return input ? `${name}:${input.data.id}` : name;
|
|
33
|
+
}
|
|
31
34
|
let key;
|
|
32
35
|
try {
|
|
33
36
|
key = slotKey({ data });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ChatMessage } from '../../../../core/messages.js';
|
|
2
|
+
/** One per-chat usage strip; removing a name persists its exclusion for future turns. */
|
|
3
|
+
export declare function AgentPluginUsage({ conversationId, messages }: {
|
|
4
|
+
conversationId?: string | null;
|
|
5
|
+
messages: readonly ChatMessage[];
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
7
|
+
//# sourceMappingURL=AgentPluginUsage.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { AGENT_PLUGIN_STATE_CHANGED, readAgentPluginChatState, writeAgentPluginChatState } from '../../../../core/agent-plugin-chat-state.js';
|
|
4
|
+
import { useT } from '../../../hooks/context.js';
|
|
5
|
+
/** The newest `agent_plugins` usage event across the transcript, if any turn reported one. */
|
|
6
|
+
function findLatestAgentPluginUsage(messages) {
|
|
7
|
+
let latest;
|
|
8
|
+
for (const message of messages)
|
|
9
|
+
for (const event of message.events ?? []) {
|
|
10
|
+
if (event.kind === 'ext' && event.name === 'agent_plugins')
|
|
11
|
+
latest = event.data;
|
|
12
|
+
}
|
|
13
|
+
return latest;
|
|
14
|
+
}
|
|
15
|
+
/** What the strip shows for this chat: the latest usage's plugins minus the ones the human
|
|
16
|
+
* excluded — or `null` when the latest usage belongs to another chat or nothing is left. */
|
|
17
|
+
function resolveAgentPluginStrip({ messages }, { conversationId }) {
|
|
18
|
+
const latest = findLatestAgentPluginUsage(messages);
|
|
19
|
+
const id = conversationId ?? latest?.conversationId;
|
|
20
|
+
if (!id || !latest || latest.conversationId !== id)
|
|
21
|
+
return null;
|
|
22
|
+
const state = readAgentPluginChatState({ conversationId: id });
|
|
23
|
+
const plugins = latest.plugins.filter(plugin => !state.excluded.includes(plugin.id));
|
|
24
|
+
if (!plugins.length)
|
|
25
|
+
return null;
|
|
26
|
+
return { id, state, plugins };
|
|
27
|
+
}
|
|
28
|
+
/** One per-chat usage strip; removing a name persists its exclusion for future turns. */
|
|
29
|
+
export function AgentPluginUsage({ conversationId, messages }) {
|
|
30
|
+
const t = useT();
|
|
31
|
+
const [revision, setRevision] = useState(0);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const refresh = () => setRevision(value => value + 1);
|
|
34
|
+
window.addEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh);
|
|
35
|
+
window.addEventListener('storage', refresh);
|
|
36
|
+
return () => { window.removeEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh); window.removeEventListener('storage', refresh); };
|
|
37
|
+
}, []);
|
|
38
|
+
void revision;
|
|
39
|
+
const strip = resolveAgentPluginStrip({ messages }, { conversationId });
|
|
40
|
+
if (!strip)
|
|
41
|
+
return null;
|
|
42
|
+
const { id, state, plugins } = strip;
|
|
43
|
+
return _jsxs("div", { role: "status", style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6, padding: '6px 12px', fontSize: 12 }, children: [_jsx("span", { children: t('Using:') }), plugins.map(plugin => _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 4, border: '1px solid currentColor', borderRadius: 12, padding: '1px 7px' }, children: [plugin.name, _jsx("button", { type: "button", "aria-label": `${t('Stop using')} ${plugin.name}`, title: t('Stops future use in this chat. Guidance already loaded stays in the current context.'), onClick: () => writeAgentPluginChatState({ conversationId: id, state: { ...state, excluded: [...state.excluded, plugin.id] } }), style: { background: 'none', border: 0, color: 'inherit', cursor: 'pointer', padding: '0 2px', fontSize: 16 }, children: "\u00D7" })] }, plugin.id))] });
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=AgentPluginUsage.js.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { pickerModelValue, pickerCatalogNote, pickerModelOptions } from '../model-options.js';
|
|
2
|
+
import { pickerModelValue, pickerCatalogNote, pickerModelOptions, shortModelLabel } from '../model-options.js';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { AgentIcon, RemixIcon, SearchableModelSelect } from '@jini-ai/ui';
|
|
5
5
|
import { useT } from '../../../hooks/context.js';
|
|
6
|
+
import { definedProps } from '../../../util/defined-props.js';
|
|
7
|
+
import { RuntimeDropUp } from './RuntimeDropUp.js';
|
|
6
8
|
import { defaultChatPaneSelection } from '../rules.js';
|
|
7
9
|
import { runtimeOptionLabel, runtimePopoverPosition, useAgentRuntimePicker, } from '../hooks/useAgentRuntimePicker.hooks.js';
|
|
8
10
|
// Re-exported so this module's public surface is unchanged by the behavior extraction; both are
|
|
@@ -25,23 +27,34 @@ function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t
|
|
|
25
27
|
return (_jsx(_Fragment, { children: orderedAgents.map((agent) => {
|
|
26
28
|
const active = agent.id === value.agentId;
|
|
27
29
|
const status = runtimeAgentStatus({ agent: agent });
|
|
28
|
-
return (_jsxs("button", { type: "button", role: "radio", "aria-checked": active, className: `jini-runtime-agent${active ? ' is-active' : ''}`, "aria-current": active ? 'true' : undefined, "aria-label": `${agent.name} · ${status}`, onClick: () => onChange(defaultChatPaneSelection({ agent: agent })), children: [_jsx(AgentIcon, { id: agent.id, size: 20, className: "jini-runtime-agent-icon", ...(
|
|
30
|
+
return (_jsxs("button", { type: "button", role: "radio", "aria-checked": active, className: `jini-runtime-agent${active ? ' is-active' : ''}`, "aria-current": active ? 'true' : undefined, "aria-label": `${agent.name} · ${status}`, onClick: () => onChange(defaultChatPaneSelection({ agent: agent })), children: [_jsx(AgentIcon, { id: agent.id, size: 20, className: "jini-runtime-agent-icon", ...definedProps({ source: { basePath: agentIconBasePath } }) }), _jsx("span", { className: "jini-runtime-agent__copy", children: _jsx("strong", { children: agent.name }) }), agent.supportsTools === false ? (_jsx("span", { className: "jini-runtime-agent__badge", title: t("This CLI has no MCP support, so it cannot use this app's tools."), children: t('No tools') })) : null, _jsx("span", { className: "jini-runtime-agent__status", children: active ? t('selected') : status }), active ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }, agent.id));
|
|
29
31
|
}) }));
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The composer bar's "Model ⌄" and "Effort ⌄" drop-ups for the selected CLI. These replace the
|
|
35
|
+
* Model / Reasoning selects that used to sit in the runtime popover under the agent list (owner
|
|
36
|
+
* 2026-10-18: pick them from the bar and keep the popover small); they read and write the same
|
|
37
|
+
* selection, so nothing else changed. Either one renders nothing when the agent publishes no
|
|
38
|
+
* choices for it, rather than an empty menu.
|
|
39
|
+
*/
|
|
40
|
+
function RuntimeBarMenus({ selectedAgent, value, onChange, modelLabel, t }) {
|
|
32
41
|
if (!selectedAgent)
|
|
33
42
|
return null;
|
|
34
43
|
const models = pickerModelOptions(selectedAgent);
|
|
35
|
-
const
|
|
36
|
-
return (_jsxs(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const efforts = selectedAgent.reasoningOptions ?? [];
|
|
45
|
+
return (_jsxs(_Fragment, { children: [models.length > 0 ? (_jsx(RuntimeDropUp, { label: t('Model'), valueLabel: modelLabel,
|
|
46
|
+
// The agent icon sits right beside it, so the bar drops the vendor word and caps the rest.
|
|
47
|
+
triggerLabel: shortModelLabel({ label: modelLabel, agentId: selectedAgent.id }), value: pickerModelValue(selectedAgent, value.model), options: models, note: pickerCatalogNote(selectedAgent, t), width: 260, onSelect: (model) => onChange({
|
|
48
|
+
agentId: value.agentId,
|
|
49
|
+
model,
|
|
50
|
+
...(value.reasoning ? { reasoning: value.reasoning } : {}),
|
|
51
|
+
}) })) : null, efforts.length > 0 ? (_jsx(RuntimeDropUp, { label: t('Reasoning'),
|
|
52
|
+
// An unset reasoning shows the first option, as the old native select did.
|
|
53
|
+
valueLabel: efforts.find((option) => option.id === value.reasoning)?.label ?? efforts[0].label, value: value.reasoning ?? '', options: efforts, width: 180, className: "jini-runtime-dropup-trigger--effort", onSelect: (reasoning) => onChange({
|
|
54
|
+
agentId: value.agentId,
|
|
55
|
+
...(value.model ? { model: value.model } : {}),
|
|
56
|
+
reasoning,
|
|
57
|
+
}) })) : null] }));
|
|
45
58
|
}
|
|
46
59
|
/**
|
|
47
60
|
* What the popover shows in place of the CLI inventory while `executionMode` is `'api'`: the model
|
|
@@ -64,17 +77,21 @@ function RuntimeModelSelects({ selectedAgent, value, onChange, t }) {
|
|
|
64
77
|
* disabled dropdown would read as "editable once something is enabled" when nothing here will
|
|
65
78
|
* enable it.
|
|
66
79
|
*/
|
|
67
|
-
function
|
|
80
|
+
function resolveByokModel({ onByokModelChange }, { byokRuntime }) {
|
|
68
81
|
const model = byokRuntime?.model?.trim();
|
|
69
82
|
const models = byokRuntime?.models ?? [];
|
|
70
|
-
const
|
|
71
|
-
return
|
|
83
|
+
const pickModel = onByokModelChange !== undefined && models.length > 0 ? onByokModelChange : undefined;
|
|
84
|
+
return { model, models, pickModel };
|
|
85
|
+
}
|
|
86
|
+
function RuntimeByokDetails({ byokRuntime, onByokModelChange, t }) {
|
|
87
|
+
const { model, models, pickModel } = resolveByokModel({ onByokModelChange }, { byokRuntime });
|
|
88
|
+
return (_jsxs("div", { className: "jini-runtime-models", children: [_jsxs("div", { className: "jini-runtime-select jini-runtime-byok-model", children: [_jsx("span", { children: t('Model') }), pickModel ? (_jsx(SearchableModelSelect, { models: models, value: model ?? '', onChange: (next) => pickModel(next), ariaLabel: t('Model'), searchPlaceholder: t('Search models'),
|
|
72
89
|
// The menu portals to `document.body` at the package default `z-index: 60`, while this
|
|
73
90
|
// popover positions itself at 1000 — so without this the model list opens BEHIND the
|
|
74
91
|
// popover that owns it. Raised here rather than in the shared stylesheet because 60 is
|
|
75
92
|
// right for every other consumer; only this one opens a portalled menu from inside an
|
|
76
93
|
// already-elevated surface.
|
|
77
|
-
menuClassName: "jini-runtime-model-menu" })) : (_jsx("strong", { children: model || t('No model configured') }))] }), !model && !
|
|
94
|
+
menuClassName: "jini-runtime-model-menu" })) : (_jsx("strong", { children: model || t('No model configured') }))] }), !model && !pickModel ? (_jsx("div", { className: "jini-runtime-empty", children: t('Set a model in the BYOK settings before sending a message.') })) : null] }));
|
|
78
95
|
}
|
|
79
96
|
function RuntimeRescanFooter({ onRescan, scanning, t }) {
|
|
80
97
|
if (onRescan) {
|
|
@@ -85,6 +102,25 @@ function RuntimeRescanFooter({ onRescan, scanning, t }) {
|
|
|
85
102
|
}
|
|
86
103
|
return null;
|
|
87
104
|
}
|
|
105
|
+
function RuntimeTriggerIcon({ picker, isApi, byokRuntime, agentIconBasePath }) {
|
|
106
|
+
const basePath = definedProps({ source: { basePath: agentIconBasePath } });
|
|
107
|
+
if (!isApi) {
|
|
108
|
+
return _jsx(AgentIcon, { id: picker.selectedAgent?.id ?? '', size: 22, className: "jini-runtime-agent-icon", ...basePath });
|
|
109
|
+
}
|
|
110
|
+
if (byokRuntime?.iconId) {
|
|
111
|
+
return _jsx(AgentIcon, { id: byokRuntime.iconId, size: 22, className: "jini-runtime-agent-icon", ...basePath });
|
|
112
|
+
}
|
|
113
|
+
return _jsx(RemixIcon, { name: "link", size: 20, className: "jini-runtime-agent-icon" });
|
|
114
|
+
}
|
|
115
|
+
function RuntimeTrigger({ picker, isApi, byokRuntime, agentIconBasePath, t }) {
|
|
116
|
+
return (_jsxs("button", { ref: picker.triggerRef, type: "button", className: isApi ? 'jini-runtime-trigger' : 'jini-runtime-trigger jini-runtime-trigger--icon-only', "aria-label": t('Choose AI runtime'), "aria-haspopup": "dialog", "aria-expanded": picker.open, "aria-controls": picker.dialogId, title: `${picker.triggerTitle} · ${picker.modelLabel}`, onClick: picker.toggleOpen, children: [_jsx(RuntimeTriggerIcon, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath }), _jsxs("span", { className: "jini-runtime-trigger__copy", children: [_jsx("strong", { children: picker.triggerTitle }), isApi ? _jsx("small", { children: picker.modelLabel }) : null] }), isApi ? (_jsx(RemixIcon, { name: "arrow-down-s-line", size: 15, className: "jini-runtime-trigger__chevron" })) : null] }));
|
|
117
|
+
}
|
|
118
|
+
function RuntimePopover({ picker, value, onChange, onRescan, scanning, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, agentIconBasePath, t, }) {
|
|
119
|
+
if (!picker.open || !picker.position)
|
|
120
|
+
return null;
|
|
121
|
+
const isApi = executionMode === 'api';
|
|
122
|
+
return createPortal(_jsxs("div", { ref: picker.popoverRef, className: "jini-runtime-popover", ...(picker.originTheme ? { 'data-theme': picker.originTheme } : {}), id: picker.dialogId, role: "dialog", "aria-label": t('Choose AI runtime'), "aria-modal": "false", style: picker.position, onKeyDown: picker.handlePopoverKeyDown, children: [_jsxs("div", { className: "jini-runtime-popover__head", children: [_jsx("strong", { children: executionMode === 'local' ? t('Local CLI') : t('API · BYOK') }), _jsx("span", { children: picker.runtimeSummary })] }), _jsx(RuntimeModeButtons, { executionMode: executionMode, daemonOnline: daemonOnline, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, t: t }), isApi ? (_jsx(RuntimeByokDetails, { byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, t: t })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "jini-runtime-section-label", children: t('Code agent') }), _jsx("div", { role: "radiogroup", "aria-label": t('Code agent'), children: _jsx(RuntimeAgentList, { orderedAgents: picker.orderedAgents, value: value, onChange: onChange, ...definedProps({ source: { agentIconBasePath } }), t: t }) }), _jsx(RuntimeRescanFooter, { onRescan: onRescan, scanning: scanning, t: t })] }))] }), document.body);
|
|
123
|
+
}
|
|
88
124
|
/**
|
|
89
125
|
* Renders the package-owned local/API, agent, model, and reasoning picker.
|
|
90
126
|
*
|
|
@@ -97,13 +133,13 @@ export function AgentRuntimePicker({ agents, value, onChange, onRescan, scanning
|
|
|
97
133
|
// its own asset directory can pass this explicitly, same as before.
|
|
98
134
|
agentIconBasePath, }) {
|
|
99
135
|
const t = useT();
|
|
100
|
-
const
|
|
136
|
+
const picker = useAgentRuntimePicker({ agents, value, placement, t, executionMode, byokRuntime });
|
|
101
137
|
// The one branch this component makes. Everything below the mode buttons describes a detected CLI
|
|
102
138
|
// — the agent radios, its model and reasoning selects, and the PATH rescan — and none of it is
|
|
103
139
|
// true of, or has any effect on, an API turn. Previously all of it rendered in both modes, so an
|
|
104
140
|
// operator on BYOK saw a CLI named as "selected" and a model reading "Default (CLI config)"
|
|
105
141
|
// while their messages went to a provider over an API key.
|
|
106
142
|
const isApi = executionMode === 'api';
|
|
107
|
-
return (_jsxs("div", { className: "jini-runtime-picker", children: [
|
|
143
|
+
return (_jsxs("div", { className: "jini-runtime-picker", children: [_jsx(RuntimeTrigger, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath, t: t }), isApi ? null : (_jsx(RuntimeBarMenus, { selectedAgent: picker.selectedAgent, value: value, onChange: onChange, modelLabel: picker.modelLabel, t: t })), _jsx(RuntimePopover, { picker: picker, value: value, onChange: onChange, onRescan: onRescan, scanning: scanning, daemonOnline: daemonOnline, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, agentIconBasePath: agentIconBasePath, t: t })] }));
|
|
108
144
|
}
|
|
109
145
|
//# sourceMappingURL=AgentRuntimePicker.js.map
|
|
@@ -6,5 +6,5 @@ import type { ChatPaneProps } from '../types.js';
|
|
|
6
6
|
* @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
|
|
7
7
|
* @overallScore 100/100
|
|
8
8
|
*/
|
|
9
|
-
export declare function ChatPane(
|
|
9
|
+
export declare function ChatPane(props: ChatPaneProps): import("react").JSX.Element;
|
|
10
10
|
//# sourceMappingURL=ChatPane.d.ts.map
|