@jini-ai/chat 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ag-ui/entry.js +2 -0
- package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -1
- package/dist/core/assistant-client/durable-run-subscription.js +13 -4
- package/dist/core/durable-projection.d.ts +3 -1
- package/dist/core/durable-projection.js +40 -7
- package/dist/core/events.d.ts +3 -0
- package/dist/core/history-tool-metadata.d.ts +23 -0
- package/dist/core/history-tool-metadata.js +13 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/persistence/title.d.ts +24 -0
- package/dist/core/persistence/title.js +60 -9
- package/dist/core/run-activity.d.ts +15 -2
- package/dist/core/run-activity.js +6 -4
- package/dist/core/run-events/entry.d.ts +12 -1
- package/dist/core/run-events/entry.js +19 -6
- package/dist/core/tool-call-detail.d.ts +51 -0
- package/dist/core/tool-call-detail.js +67 -0
- package/dist/core/turn-usage.d.ts +50 -0
- package/dist/core/turn-usage.js +114 -0
- package/dist/core/usage-format.d.ts +10 -0
- package/dist/core/usage-format.js +15 -0
- package/dist/embed-widget/browser/lazy-loader.d.ts +34 -0
- package/dist/embed-widget/browser/lazy-loader.js +116 -0
- package/dist/embed-widget/react/dialog.hooks.d.ts +28 -0
- package/dist/embed-widget/react/dialog.hooks.js +54 -0
- package/dist/embed-widget/react/embed.d.ts +1 -0
- package/dist/embed-widget/react/embed.js +5 -3
- package/dist/embed-widget/react/header.d.ts +3 -1
- package/dist/embed-widget/react/header.hooks.d.ts +13 -0
- package/dist/embed-widget/react/header.hooks.js +32 -0
- package/dist/embed-widget/react/header.js +4 -14
- package/dist/embed-widget/vanilla/index.d.ts +63 -0
- package/dist/embed-widget/vanilla/index.js +319 -0
- package/dist/embed-widget/vanilla/state.d.ts +48 -0
- package/dist/embed-widget/vanilla/state.js +120 -0
- package/dist/embed-widget/vanilla/style.css +29 -0
- package/dist/react/chat-pane.d.ts +2 -2
- package/dist/react/chat-pane.js +1 -1
- package/dist/react/components/A2uiSurfaceCard.d.ts +5 -1
- package/dist/react/components/A2uiSurfaceCard.js +9 -9
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -0
- package/dist/react/components/AttachmentPreviewModal.hooks.js +1 -1
- package/dist/react/components/ChatFab.d.ts +4 -1
- package/dist/react/components/ChatFab.js +2 -2
- package/dist/react/components/Composer.d.ts +2 -0
- package/dist/react/components/Composer.js +5 -2
- package/dist/react/components/ConversationList.d.ts +8 -1
- package/dist/react/components/ConversationList.hooks.d.ts +18 -0
- package/dist/react/components/ConversationList.hooks.js +25 -0
- package/dist/react/components/ConversationList.js +12 -5
- package/dist/react/components/ConversationList.rules.d.ts +25 -0
- package/dist/react/components/ConversationList.rules.js +19 -0
- package/dist/react/components/Markdown.js +6 -6
- package/dist/react/components/McpUiSurfaceCard.d.ts +1 -1
- package/dist/react/components/McpUiSurfaceCard.js +8 -4
- package/dist/react/components/MessageList.d.ts +8 -13
- package/dist/react/components/MessageList.hooks.d.ts +4 -0
- package/dist/react/components/MessageList.hooks.js +6 -1
- package/dist/react/components/MessageList.js +23 -4
- package/dist/react/components/MessageRow.d.ts +23 -1
- package/dist/react/components/MessageRow.js +61 -36
- package/dist/react/components/MessageRow.usage.d.ts +48 -0
- package/dist/react/components/MessageRow.usage.js +47 -0
- package/dist/react/components/MidRunUserMessage.d.ts +17 -0
- package/dist/react/components/MidRunUserMessage.js +41 -0
- package/dist/react/components/SentAttachment.d.ts +19 -0
- package/dist/react/components/SentAttachment.hooks.d.ts +44 -0
- package/dist/react/components/SentAttachment.hooks.js +56 -0
- package/dist/react/components/SentAttachment.js +14 -0
- package/dist/react/components/ToolCallGroup.d.ts +12 -0
- package/dist/react/components/ToolCallGroup.hooks.d.ts +20 -0
- package/dist/react/components/ToolCallGroup.hooks.js +23 -0
- package/dist/react/components/ToolCallGroup.js +13 -0
- package/dist/react/components/ToolCard.js +41 -22
- package/dist/react/components/mcp-ui-surface-state.d.ts +21 -0
- package/dist/react/components/mcp-ui-surface-state.js +26 -0
- package/dist/react/ext-event-renderer-registry.d.ts +7 -0
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +20 -7
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +65 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +4 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.d.ts +9 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.js +11 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.d.ts +17 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.js +15 -0
- package/dist/react/features/chat-pane/index.d.ts +2 -1
- package/dist/react/features/chat-pane/index.js +1 -0
- package/dist/react/features/chat-pane/rules.d.ts +27 -0
- package/dist/react/features/chat-pane/rules.js +32 -0
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +92 -1
- package/dist/react/features/chat-pane/types.d.ts +39 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
- package/dist/react/hooks/composer-draft-cache.js +20 -0
- package/dist/react/hooks/useAnsweredCards.d.ts +20 -0
- package/dist/react/hooks/useAnsweredCards.js +23 -0
- package/dist/react/hooks/useComposer.d.ts +14 -0
- package/dist/react/hooks/useComposer.js +78 -6
- package/dist/react/hooks/useConversation.d.ts +8 -0
- package/dist/react/hooks/useConversation.js +34 -0
- package/dist/react/hooks/useRunActivity.d.ts +5 -3
- package/dist/react/hooks/useRunActivity.js +4 -3
- package/dist/react/index.d.ts +8 -2
- package/dist/react/index.js +6 -1
- package/dist/react/message-blocks.d.ts +22 -3
- package/dist/react/message-blocks.js +25 -3
- package/dist/react/tool-call-display.d.ts +90 -0
- package/dist/react/tool-call-display.js +97 -0
- package/dist/server/durable-runs/recover.js +19 -11
- package/dist/server/run-finalizer/entry.js +2 -1
- package/dist/store/ports.d.ts +25 -2
- package/dist/store/runtime/durable-run-store.js +5 -2
- package/dist/store/runtime/entry.d.ts +1 -0
- package/dist/store/runtime/entry.js +1 -0
- package/dist/store/runtime/history-tools.d.ts +27 -0
- package/dist/store/runtime/history-tools.js +169 -0
- package/dist/store/runtime/run-ledger.js +5 -2
- package/dist/store/sql/store.js +52 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js +22 -0
- package/package.json +48 -18
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* @module ToolCard
|
|
4
4
|
*
|
|
@@ -20,8 +20,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
* `AskUserQuestion`) falls through to `GenericCard`, which is a correct,
|
|
21
21
|
* generic rendering for it.
|
|
22
22
|
*/
|
|
23
|
-
import { useState } from 'react';
|
|
24
|
-
import { formatToolOutputForDisplay, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, toRenderProps } from '../../core/index.js';
|
|
23
|
+
import { useId, useState } from 'react';
|
|
24
|
+
import { describeCollapsedToolCall, describeToolCallDetail, formatToolOutputForDisplay, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, toRenderProps } from '../../core/index.js';
|
|
25
25
|
import { useT } from '../hooks/context.js';
|
|
26
26
|
import { getToolRenderer } from '../tool-renderer-registry.js';
|
|
27
27
|
import { Icon } from './Icon.js';
|
|
@@ -132,7 +132,6 @@ function primaryTarget(input) {
|
|
|
132
132
|
*/
|
|
133
133
|
function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
134
134
|
const t = useT();
|
|
135
|
-
const [open, setOpen] = useState(false);
|
|
136
135
|
// Wrapper form (only reached on a failed wrapper call, per ToolCard's suppression comment):
|
|
137
136
|
// the real id and args are nested under {toolId, input}, not `input` itself.
|
|
138
137
|
const wrapper = (input ?? {});
|
|
@@ -140,21 +139,19 @@ function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded })
|
|
|
140
139
|
const args = isDelegatedWrapperToolName({ name: name }) ? wrapper.input : input;
|
|
141
140
|
const target = primaryTarget(args);
|
|
142
141
|
const isRunning = runStreaming && !result;
|
|
143
|
-
return (
|
|
142
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsxs("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: [t('Tool call'), " \u00B7 ", humanizeToolId(toolId)] }), target ? _jsx("span", { className: "op-meta", children: target }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
|
|
144
143
|
}
|
|
145
144
|
function SearchToolsCard({ input, result, runStreaming, runSucceeded }) {
|
|
146
145
|
const t = useT();
|
|
147
|
-
const [open, setOpen] = useState(false);
|
|
148
146
|
const obj = (input ?? {});
|
|
149
147
|
const isRunning = runStreaming && !result;
|
|
150
|
-
return (
|
|
148
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Search tools') }), obj.query ? _jsx("span", { className: "op-meta", children: obj.query }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
151
149
|
}
|
|
152
150
|
function DescribeToolCard({ input, result, runStreaming, runSucceeded }) {
|
|
153
151
|
const t = useT();
|
|
154
|
-
const [open, setOpen] = useState(false);
|
|
155
152
|
const obj = (input ?? {});
|
|
156
153
|
const isRunning = runStreaming && !result;
|
|
157
|
-
return (
|
|
154
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Describe tool') }), obj.id ? _jsx("span", { className: "op-meta", children: obj.id }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
158
155
|
}
|
|
159
156
|
function OpenInTabButton({ filePath, ctx }) {
|
|
160
157
|
const t = useT();
|
|
@@ -174,68 +171,90 @@ function OpenInTabButton({ filePath, ctx }) {
|
|
|
174
171
|
}
|
|
175
172
|
function FileWriteCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
176
173
|
const t = useT();
|
|
177
|
-
const [open, setOpen] = useState(false);
|
|
178
174
|
const obj = (input ?? {});
|
|
179
175
|
const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
|
|
180
176
|
// See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
|
|
181
177
|
const baseName = file.split('/').pop();
|
|
182
178
|
const lines = typeof obj.content === 'string' ? obj.content.split('\n').length : null;
|
|
183
179
|
const isRunning = runStreaming && !result;
|
|
184
|
-
return (_jsxs(
|
|
180
|
+
return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Write') }), _jsxs("span", { className: "op-meta", children: [baseName, lines !== null ? ` · ${t('{n} lines', { n: lines })}` : ''] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
185
181
|
}
|
|
186
182
|
function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
187
183
|
const t = useT();
|
|
188
|
-
const [open, setOpen] = useState(false);
|
|
189
184
|
const obj = (input ?? {});
|
|
190
185
|
const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
|
|
191
186
|
// See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
|
|
192
187
|
const baseName = file.split('/').pop();
|
|
193
188
|
const editCount = Array.isArray(obj.edits) ? obj.edits.length : 1;
|
|
194
189
|
const isRunning = runStreaming && !result;
|
|
195
|
-
return (_jsxs(
|
|
190
|
+
return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Edit') }), _jsxs("span", { className: "op-meta", children: [baseName, " \u00B7 ", editCount, " ", editCount === 1 ? t('change') : t('changes')] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
196
191
|
}
|
|
197
192
|
function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
198
193
|
const t = useT();
|
|
199
|
-
const [open, setOpen] = useState(false);
|
|
200
194
|
const obj = (input ?? {});
|
|
201
195
|
const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
|
|
202
196
|
// See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
|
|
203
197
|
const baseName = file.split('/').pop();
|
|
204
198
|
const isRunning = runStreaming && !result;
|
|
205
|
-
return (_jsxs(
|
|
199
|
+
return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Read') }), _jsx("span", { className: "op-meta", children: baseName })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
206
200
|
}
|
|
207
201
|
function BashCard({ input, result, runStreaming, runSucceeded }) {
|
|
208
202
|
const t = useT();
|
|
209
203
|
const obj = (input ?? {});
|
|
210
204
|
const command = obj.command ?? '';
|
|
211
205
|
const desc = obj.description;
|
|
212
|
-
const [open, setOpen] = useState(false);
|
|
213
206
|
const isRunning = runStreaming && !result;
|
|
214
|
-
return (
|
|
207
|
+
return (_jsx(ExpandableCard, { className: "op-bash", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Bash') }), desc ? _jsx("span", { className: "op-meta op-desc", children: desc }) : null] }), children: _jsxs("div", { className: "op-card-detail", children: [_jsx("pre", { className: "op-command", children: truncate(command, 400) }), result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 4000) }) : null] }) }));
|
|
215
208
|
}
|
|
216
209
|
function GlobCard({ input, result, runStreaming, runSucceeded }) {
|
|
217
210
|
const t = useT();
|
|
218
211
|
const obj = (input ?? {});
|
|
219
|
-
return (_jsx(
|
|
212
|
+
return (_jsx(ExpandableCard, { className: "op-search", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search files') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '*', obj.path ? ` in ${obj.path}` : ''] })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
220
213
|
}
|
|
221
214
|
function GrepCard({ input, result, runStreaming, runSucceeded }) {
|
|
222
215
|
const t = useT();
|
|
223
216
|
const obj = (input ?? {});
|
|
224
|
-
return (_jsx(
|
|
217
|
+
return (_jsx(ExpandableCard, { className: "op-search", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search content') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '', obj.path ? ` in ${obj.path}` : ''] })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
225
218
|
}
|
|
226
219
|
function WebFetchCard({ input, result, runStreaming, runSucceeded }) {
|
|
227
220
|
const t = useT();
|
|
228
221
|
const obj = (input ?? {});
|
|
229
|
-
return (_jsx(
|
|
222
|
+
return (_jsx(ExpandableCard, { className: "op-web", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Fetch') }), _jsx("span", { className: "op-meta", children: obj.url ?? '' })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
230
223
|
}
|
|
231
224
|
function WebSearchCard({ input, result, runStreaming, runSucceeded }) {
|
|
232
225
|
const t = useT();
|
|
233
226
|
const obj = (input ?? {});
|
|
234
|
-
return (_jsx(
|
|
227
|
+
return (_jsx(ExpandableCard, { className: "op-web", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search') }), _jsx("span", { className: "op-meta", children: obj.query ?? '' })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
235
228
|
}
|
|
229
|
+
/** Any tool this package has no family card for — an MCP tool, or a card tool such as `assistant_ask_choice` whose form renders elsewhere in the turn. The row still opens to the full call. */
|
|
236
230
|
function GenericCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
237
231
|
const summary = describeInput(input);
|
|
238
|
-
return (
|
|
232
|
+
return (_jsx(ExpandableCard, { className: "op-generic", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: name }), summary ? _jsx("span", { className: "op-meta", children: truncate(summary, 200) }) : null] }), after: _jsx(ToolResultMedia, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The one row shell every card shares: a real `<button>` head (Enter/Space toggle it) that reports
|
|
236
|
+
* `aria-expanded` and points `aria-controls` at its panel. The panel's `children` mount only while
|
|
237
|
+
* open: a long chat mounts hundreds of collapsed rows, and keeping every full detail mounted made a
|
|
238
|
+
* 38-message chat carry ~13k DOM nodes and 3.45 MB of hidden text. Collapsed, the panel holds one
|
|
239
|
+
* hidden node with the call's capped gist instead, so a `page.*` DOM reader still sees what was
|
|
240
|
+
* called and what came back through `textContent` without anyone clicking.
|
|
241
|
+
* `after` renders outside the panel, for content that must stay visible collapsed (an error, media).
|
|
242
|
+
*/
|
|
243
|
+
function ExpandableCard({ className, head, after, call, children }) {
|
|
244
|
+
const [open, setOpen] = useState(false);
|
|
245
|
+
const panelId = useId();
|
|
246
|
+
return (_jsxs("div", { className: `op-card ${className}`, children: [_jsxs("button", { type: "button", className: "op-card-head", "aria-expanded": open, "aria-controls": panelId, onClick: () => setOpen((o) => !o), children: [head, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { id: panelId, className: `accordion-collapsible${open ? ' open' : ''}`, children: open ? _jsx("div", { className: "accordion-collapsible-inner", children: children }) : _jsx(CollapsedReaderText, { call: call }) }), after] }));
|
|
247
|
+
}
|
|
248
|
+
/** The collapsed row's stand-in for its panel — see {@link ExpandableCard} and `describeCollapsedToolCall`. */
|
|
249
|
+
function CollapsedReaderText({ call }) {
|
|
250
|
+
const text = describeCollapsedToolCall({ input: call.input, result: call.result }, { runStreaming: call.runStreaming, runSucceeded: call.runSucceeded });
|
|
251
|
+
return _jsx("span", { className: "op-card-reader-text", hidden: true, children: text });
|
|
252
|
+
}
|
|
253
|
+
/** The call's input, its result (or why there is none), and any extra `children` — see `describeToolCallDetail` for the redaction and formatting rules. */
|
|
254
|
+
function ToolCallDetail({ input, result, runStreaming, runSucceeded, children }) {
|
|
255
|
+
const t = useT();
|
|
256
|
+
const detail = describeToolCallDetail({ input: input, result: result }, { runStreaming: runStreaming, runSucceeded: runSucceeded });
|
|
257
|
+
return (_jsxs("div", { className: "op-card-detail", children: [detail.inputText !== null ? _jsx("pre", { className: "op-command op-input", children: detail.inputText }) : null, detail.outputText !== null ? _jsx("pre", { className: "op-output", children: detail.outputText }) : null, detail.noteKey !== null ? _jsx("p", { className: "op-detail-note", children: t(detail.noteKey) }) : null, children] }));
|
|
239
258
|
}
|
|
240
259
|
function ResultBadge({ result, runStreaming, runSucceeded }) {
|
|
241
260
|
const t = useT();
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* `McpUiSurfaceCard.tsx` so the component only renders; moved here from that file 2026-10-04 when
|
|
6
6
|
* the card's own deadline (`useSurfaceExpiry`) became a second reason to close it.
|
|
7
7
|
*/
|
|
8
|
+
import type { McpUiToolCallHandler } from '@jini-ai/ui/mcp-ui';
|
|
8
9
|
import type { ExtEventCall } from '../ext-event-renderer-registry.js';
|
|
9
10
|
import type { useT } from '../hooks/context.js';
|
|
10
11
|
type Translate = ReturnType<typeof useT>;
|
|
@@ -23,5 +24,25 @@ export declare function closedSurfaceLabel({ t, call, runStreaming, documentsFor
|
|
|
23
24
|
documentsForUri: number;
|
|
24
25
|
expired: boolean;
|
|
25
26
|
}): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a View's `tools/call` result is a held-open question's delivery receipt: the host route
|
|
29
|
+
* answers `{delivered: true}` (HTTP 202) the moment the click reaches the parked tool call. That
|
|
30
|
+
* call then keeps running the work the answer started, so this receipt — not its later result — is
|
|
31
|
+
* when the card stops waiting on the person.
|
|
32
|
+
*
|
|
33
|
+
* @complexity O(1).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isDeliveredAnswer(result: unknown): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
|
|
38
|
+
* passed through unchanged, and a rejected call reports nothing (the dialog shows its error and can
|
|
39
|
+
* be answered again).
|
|
40
|
+
*
|
|
41
|
+
* @complexity O(1) per call, plus `onToolCall`'s own cost.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reportDeliveredAnswer({ onToolCall, onAnswered }: {
|
|
44
|
+
onToolCall: McpUiToolCallHandler;
|
|
45
|
+
onAnswered: () => void;
|
|
46
|
+
}): McpUiToolCallHandler;
|
|
26
47
|
export {};
|
|
27
48
|
//# sourceMappingURL=mcp-ui-surface-state.d.ts.map
|
|
@@ -30,4 +30,30 @@ function settledLabel(t, call, runStreaming, documentsForUri) {
|
|
|
30
30
|
export function closedSurfaceLabel({ t, call, runStreaming, documentsForUri, expired }) {
|
|
31
31
|
return settledLabel(t, call, runStreaming, documentsForUri) ?? (expired ? t('This question expired') : undefined);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a View's `tools/call` result is a held-open question's delivery receipt: the host route
|
|
35
|
+
* answers `{delivered: true}` (HTTP 202) the moment the click reaches the parked tool call. That
|
|
36
|
+
* call then keeps running the work the answer started, so this receipt — not its later result — is
|
|
37
|
+
* when the card stops waiting on the person.
|
|
38
|
+
*
|
|
39
|
+
* @complexity O(1).
|
|
40
|
+
*/
|
|
41
|
+
export function isDeliveredAnswer(result) {
|
|
42
|
+
return typeof result === 'object' && result !== null && result.delivered === true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
|
|
46
|
+
* passed through unchanged, and a rejected call reports nothing (the dialog shows its error and can
|
|
47
|
+
* be answered again).
|
|
48
|
+
*
|
|
49
|
+
* @complexity O(1) per call, plus `onToolCall`'s own cost.
|
|
50
|
+
*/
|
|
51
|
+
export function reportDeliveredAnswer({ onToolCall, onAnswered }) {
|
|
52
|
+
return async (call) => {
|
|
53
|
+
const result = await onToolCall(call);
|
|
54
|
+
if (isDeliveredAnswer(result))
|
|
55
|
+
onAnswered();
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
33
59
|
//# sourceMappingURL=mcp-ui-surface-state.js.map
|
|
@@ -51,6 +51,13 @@ export interface ExtEventRenderProps {
|
|
|
51
51
|
runId: string | undefined;
|
|
52
52
|
/** See {@link ExtEventCall}. Absent when the group's first event arrived outside any tool call. */
|
|
53
53
|
call?: ExtEventCall;
|
|
54
|
+
/** Whether this card already reported its answer delivered (see {@link onAnswered}). */
|
|
55
|
+
answered?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* A card calls this once the person's answer reached the server. Its tool call may stay open for
|
|
58
|
+
* the work the answer started, and the run must stop reading as "waiting for your answer".
|
|
59
|
+
*/
|
|
60
|
+
onAnswered?: () => void;
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
56
63
|
* Ext-event render callback. Runs inline during `MessageRow`'s render — hook-free, like
|
|
@@ -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({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
|
|
9
|
+
export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, refreshSignal, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, deliverMidRunMessage, composerHandle, placeholder, placeholders, suggestions, emptyState, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, toolCallDisplay, showUsageLine, showThinking, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
|
|
10
10
|
//# sourceMappingURL=ChatPane.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { SECRET_REDACTED_NOTICE } from '../../../../core/user-text-redaction.js';
|
|
3
|
-
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { WorkingDirPicker } from '@jini-ai/ui';
|
|
5
5
|
import { Composer } from '../../../components/Composer.js';
|
|
6
6
|
import { appendComposerDiscovery } from '../../../components/composer-discovery.js';
|
|
@@ -12,6 +12,9 @@ import { useChatPane } from '../hooks/useChatPane.hooks.js';
|
|
|
12
12
|
import { useChatPaneAgentControl } from '../hooks/useChatPaneAgentControl.hooks.js';
|
|
13
13
|
import { useChatPaneComposerPlaceholder } from '../hooks/useChatPaneComposerPlaceholder.hooks.js';
|
|
14
14
|
import { useChatPaneControlsHeight } from '../hooks/useChatPaneControlsHeight.hooks.js';
|
|
15
|
+
import { useChatPaneNewTabLinks } from '../hooks/useChatPaneNewTabLinks.hooks.js';
|
|
16
|
+
import { useChatPaneSurface } from '../hooks/useChatPaneSurface.hooks.js';
|
|
17
|
+
import { useChatPaneSuggestion } from '../hooks/useChatPaneSuggestion.hooks.js';
|
|
15
18
|
import { useChatPaneFileDrop, } from '../hooks/useChatPaneFileDrop.hooks.js';
|
|
16
19
|
import { useChatPaneRuntimeInventory, } from '../hooks/useChatPaneRuntimeInventory.hooks.js';
|
|
17
20
|
import { CHAT_PANE_STYLES } from '../styles.js';
|
|
@@ -179,8 +182,9 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
|
|
|
179
182
|
* @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
|
|
180
183
|
* @overallScore 100/100
|
|
181
184
|
*/
|
|
182
|
-
export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
|
|
185
|
+
export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, refreshSignal, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, deliverMidRunMessage, composerHandle, placeholder, placeholders, suggestions = [], emptyState, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, toolCallDisplay = 'show', showUsageLine = true, showThinking = false, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
|
|
183
186
|
const t = useT();
|
|
187
|
+
const surface = useChatPaneSurface({ variant, composerSlots, workingDirectoryControlPlacement, uploadAttachments });
|
|
184
188
|
const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
|
|
185
189
|
const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
|
|
186
190
|
access: runtimeAccess,
|
|
@@ -196,6 +200,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
196
200
|
transport,
|
|
197
201
|
agents: runtimeView.agents,
|
|
198
202
|
initialMessages,
|
|
203
|
+
refreshSignal,
|
|
199
204
|
conversationId,
|
|
200
205
|
initialSelection,
|
|
201
206
|
selection: controlledSelection,
|
|
@@ -216,6 +221,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
216
221
|
workingDirectoryAccess,
|
|
217
222
|
apiModeConfigured,
|
|
218
223
|
deliverTypedAnswer,
|
|
224
|
+
deliverMidRunMessage,
|
|
219
225
|
} }));
|
|
220
226
|
// Mirrors `Composer.tsx`'s own `draftRef`: `composerHandle.insertText` (below) is called from
|
|
221
227
|
// OUTSIDE any render, so it cannot close over `pane.composer.draft` directly — that would freeze
|
|
@@ -252,7 +258,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
252
258
|
// the provider directly over HTTP and never touches the CLI inventory.
|
|
253
259
|
const unavailable = pane.selectedAgent === undefined && !apiModeConfigured;
|
|
254
260
|
const fileDrop = useChatPaneFileDrop({
|
|
255
|
-
enabled: uploadAttachments !== undefined
|
|
261
|
+
enabled: surface.uploadAttachments !== undefined
|
|
256
262
|
&& !disabled
|
|
257
263
|
&& !unavailable
|
|
258
264
|
&& !pane.conversation.isStreaming
|
|
@@ -263,11 +269,18 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
263
269
|
// `ChatPaneProps.composerSlots`'s doc), so the two explicit keys below are never overwriting a
|
|
264
270
|
// caller-supplied value — they are the ONLY source for both.
|
|
265
271
|
const slots = definedProps({ source: {
|
|
266
|
-
...composerSlots,
|
|
267
|
-
leadingAccessories: leadingAccessory,
|
|
268
|
-
footerAccessories: (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ source: { onRescan: runtimeView.rescanAgents } }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ source: { onExecutionModeChange, byokRuntime, onByokModelChange } }) })),
|
|
272
|
+
...surface.composerSlots,
|
|
273
|
+
leadingAccessories: surface.embed ? undefined : leadingAccessory,
|
|
274
|
+
footerAccessories: surface.embed ? undefined : (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ source: { onRescan: runtimeView.rescanAgents } }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ source: { onExecutionModeChange, byokRuntime, onByokModelChange } }) })),
|
|
269
275
|
} });
|
|
270
276
|
const { rootRef, controlsRef } = useChatPaneControlsHeight();
|
|
271
|
-
|
|
277
|
+
const selectSuggestion = useChatPaneSuggestion({ rootRef, setDraft: pane.composer.setDraft });
|
|
278
|
+
const openLinkInNewTab = useChatPaneNewTabLinks();
|
|
279
|
+
// `MessageList` is memoized so a composer keystroke does not re-render the transcript; these two
|
|
280
|
+
// props would otherwise be fresh objects on every render and defeat it.
|
|
281
|
+
const { queuedPrompt, cancelQueued } = pane;
|
|
282
|
+
const pendingPrompt = useMemo(() => (queuedPrompt === null ? null : { text: queuedPrompt, onCancel: cancelQueued }), [queuedPrompt, cancelQueued]);
|
|
283
|
+
const projectFileNameSet = useMemo(() => (projectFileNames === undefined ? undefined : new Set(projectFileNames)), [projectFileNames]);
|
|
284
|
+
return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [pane.conversation.messages.length === 0 && emptyState ? (_jsxs("div", { className: "jini-message-list jini-message-list--empty", children: [emptyState, surface.embed && _jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: selectSuggestion, t: t })] })) : (_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pendingPrompt, toolCallDisplay: toolCallDisplay, showUsageLine: showUsageLine, showThinking: showThinking, ...(projectFileNameSet === undefined ? {} : { projectFileNames: projectFileNameSet }) })), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [(!surface.embed || (pane.conversation.messages.length === 0 && !emptyState)) && (_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: selectSuggestion, t: t })), pane.secretRedacted ? _jsx("div", { className: "jini-chat-pane__notice", role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: surface.uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: surface.workingDirectoryControlPlacement, t: t })] })] }), footer] }));
|
|
272
285
|
}
|
|
273
286
|
//# sourceMappingURL=ChatPane.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module create-mid-run-message-poster
|
|
3
|
+
*
|
|
4
|
+
* The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
|
|
5
|
+
* human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
|
|
6
|
+
* `deliverMidRunMessage`.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <ChatPane deliverMidRunMessage={createMidRunMessagePoster({ baseUrl: '', fetch })} />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
|
|
13
|
+
* `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
|
|
14
|
+
* with `details.delivery` when it did not.
|
|
15
|
+
*
|
|
16
|
+
* ## Why it resolves an outcome instead of throwing
|
|
17
|
+
*
|
|
18
|
+
* The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
|
|
19
|
+
* run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
|
|
20
|
+
* which is already ending (or may be — never cut short a run the host could not reach). A 404 means
|
|
21
|
+
* the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
|
|
22
|
+
* too: no live process to steer, but an interrupt still works.
|
|
23
|
+
*/
|
|
24
|
+
import { type UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
|
|
25
|
+
import type { DeliverMidRunMessage } from './types.js';
|
|
26
|
+
export interface CreateMidRunMessagePosterOptions extends UserTextRedactionOptions {
|
|
27
|
+
/** Abandon a post after this many ms (reported as `'failed'`). Defaults to 30s. */
|
|
28
|
+
readonly timeoutMs?: number;
|
|
29
|
+
/** Extra headers per request (e.g. a CSRF token). `content-type` is always set and cannot be overridden. */
|
|
30
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
|
|
34
|
+
*
|
|
35
|
+
* @param deps.baseUrl - Origin to call; `''` for same-origin.
|
|
36
|
+
* @param deps.fetch - The fetch to use (the browser's, in production).
|
|
37
|
+
* @returns Resolves to the outcome; never rejects.
|
|
38
|
+
* @complexity O(1) — one request per call.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createMidRunMessagePoster({ baseUrl, fetch }: {
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
fetch: typeof globalThis.fetch;
|
|
43
|
+
}, options?: CreateMidRunMessagePosterOptions): DeliverMidRunMessage;
|
|
44
|
+
//# sourceMappingURL=create-mid-run-message-poster.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module create-mid-run-message-poster
|
|
3
|
+
*
|
|
4
|
+
* The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
|
|
5
|
+
* human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
|
|
6
|
+
* `deliverMidRunMessage`.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <ChatPane deliverMidRunMessage={createMidRunMessagePoster({ baseUrl: '', fetch })} />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
|
|
13
|
+
* `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
|
|
14
|
+
* with `details.delivery` when it did not.
|
|
15
|
+
*
|
|
16
|
+
* ## Why it resolves an outcome instead of throwing
|
|
17
|
+
*
|
|
18
|
+
* The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
|
|
19
|
+
* run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
|
|
20
|
+
* which is already ending (or may be — never cut short a run the host could not reach). A 404 means
|
|
21
|
+
* the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
|
|
22
|
+
* too: no live process to steer, but an interrupt still works.
|
|
23
|
+
*/
|
|
24
|
+
import { redactUserText } from '../../../core/user-text-redaction.js';
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
26
|
+
/** Reads `error.details.delivery` off a 409 body; anything unreadable counts as `'unsupported'`. */
|
|
27
|
+
async function conflictDelivery(response) {
|
|
28
|
+
const body = await response.json().catch(() => null);
|
|
29
|
+
const delivery = body?.error?.details?.delivery;
|
|
30
|
+
return delivery === 'not-running' ? 'not-running' : 'unsupported';
|
|
31
|
+
}
|
|
32
|
+
/** Maps the route's response onto an outcome. Only 202 is a delivery. */
|
|
33
|
+
async function deliveryFor(response) {
|
|
34
|
+
if (response.status === 202)
|
|
35
|
+
return 'delivered';
|
|
36
|
+
if (response.status === 409)
|
|
37
|
+
return conflictDelivery(response);
|
|
38
|
+
return response.status === 404 ? 'unsupported' : 'failed';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
|
|
42
|
+
*
|
|
43
|
+
* @param deps.baseUrl - Origin to call; `''` for same-origin.
|
|
44
|
+
* @param deps.fetch - The fetch to use (the browser's, in production).
|
|
45
|
+
* @returns Resolves to the outcome; never rejects.
|
|
46
|
+
* @complexity O(1) — one request per call.
|
|
47
|
+
*/
|
|
48
|
+
export function createMidRunMessagePoster({ baseUrl, fetch }, options = {}) {
|
|
49
|
+
const origin = baseUrl.replace(/\/$/u, '');
|
|
50
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
51
|
+
return async ({ runId, text }) => {
|
|
52
|
+
const result = (options.redactUserText ?? redactUserText)({ text });
|
|
53
|
+
if (result.secretRedacted)
|
|
54
|
+
options.onSecretRedacted?.({ secretRedacted: true, count: result.count });
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
57
|
+
const outcome = await fetch(`${origin}/api/runs/${encodeURIComponent(runId)}/messages`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
// Same-origin cookie session: the human's own credentials authorize steering their run.
|
|
60
|
+
credentials: 'same-origin',
|
|
61
|
+
headers: { ...options.headers, 'content-type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({ text: result.text }),
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
}).then(deliveryFor, () => 'failed');
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
return outcome;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=create-mid-run-message-poster.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { UserTextRedactionOptions } from '../../../../core/user-text-redaction.js';
|
|
2
2
|
import { type ChatPaneSendBlocker } from '../rules.js';
|
|
3
|
-
import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
|
|
3
|
+
import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverMidRunMessage, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
|
|
4
4
|
import type { ChatAttachment, ChatMessage } from '@jini-ai/chat';
|
|
5
5
|
import type { ComposerHistoryStoragePort } from '../../../../core/composer-history.js';
|
|
6
6
|
import type { ChatTransport } from '@jini-ai/chat';
|
|
@@ -11,6 +11,7 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
|
|
|
11
11
|
transport: ChatTransport;
|
|
12
12
|
agents: readonly ChatPaneAgent[];
|
|
13
13
|
initialMessages?: ChatMessage[];
|
|
14
|
+
refreshSignal?: import('../../../hooks/useConversation.js').UseConversationOptions['refreshSignal'];
|
|
14
15
|
conversationId?: string | null;
|
|
15
16
|
initialSelection?: ChatPaneAgentSelection;
|
|
16
17
|
selection?: ChatPaneAgentSelection;
|
|
@@ -36,6 +37,8 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
|
|
|
36
37
|
apiModeConfigured?: boolean;
|
|
37
38
|
/** See `ChatPaneProps.deliverTypedAnswer`. Omitted keeps today's queue-while-streaming behavior. */
|
|
38
39
|
deliverTypedAnswer?: DeliverTypedAnswer;
|
|
40
|
+
/** See `ChatPaneProps.deliverMidRunMessage`. Omitted keeps today's queue-while-streaming behavior. */
|
|
41
|
+
deliverMidRunMessage?: DeliverMidRunMessage;
|
|
39
42
|
}
|
|
40
43
|
export interface UseChatPaneResult extends UseChatPaneWorkingDirectoryResult {
|
|
41
44
|
/** Show a localized non-secret notice when a paste was removed. */
|