@jini-ai/chat 0.3.11 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/README.md +152 -6
- package/dist/core/ag-ui/entry.d.ts +53 -0
- package/dist/core/ag-ui/entry.js +135 -0
- package/dist/core/agentic/chat-capabilities.d.ts +1 -1
- package/dist/core/agentic/index.d.ts +1 -1
- package/dist/core/agentic/index.js +1 -1
- package/dist/core/assistant-content.d.ts +6 -2
- package/dist/core/assistant-content.js +2 -2
- package/dist/core/compact-events.d.ts +3 -1
- package/dist/core/compact-events.js +1 -1
- package/dist/core/events.d.ts +1 -1
- package/dist/core/events.js +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/messages.d.ts +4 -2
- package/dist/core/messages.js +2 -2
- package/dist/core/partial-json.d.ts +6 -2
- package/dist/core/partial-json.js +3 -3
- package/dist/core/persistence/index.d.ts +1 -1
- package/dist/core/persistence/index.js +1 -1
- package/dist/core/persistence/ports.d.ts +2 -123
- package/dist/core/persistence/title.d.ts +3 -1
- package/dist/core/persistence/title.js +1 -1
- package/dist/core/question-form/format.d.ts +16 -7
- package/dist/core/question-form/format.js +3 -3
- package/dist/core/question-form/normalize.d.ts +7 -2
- package/dist/core/question-form/normalize.js +5 -5
- package/dist/core/question-form/scan.d.ts +18 -6
- package/dist/core/question-form/scan.js +14 -14
- package/dist/core/run-activity.d.ts +16 -4
- package/dist/core/run-activity.js +11 -11
- package/dist/core/run-events/entry.d.ts +121 -0
- package/dist/core/run-events/entry.js +290 -0
- package/dist/core/todos.d.ts +20 -8
- package/dist/core/todos.js +11 -11
- package/dist/core/tool-events.d.ts +3 -1
- package/dist/core/tool-events.js +1 -1
- package/dist/core/tool-output.d.ts +3 -1
- package/dist/core/tool-output.js +1 -1
- package/dist/core/tools.d.ts +14 -3
- package/dist/core/tools.js +3 -3
- package/dist/core/transcript.d.ts +13 -5
- package/dist/core/transcript.js +5 -5
- package/dist/core/util/manifest.d.ts +9 -3
- package/dist/core/util/manifest.js +3 -3
- package/dist/core/util/markdown-context.d.ts +11 -3
- package/dist/core/util/markdown-context.js +3 -3
- package/dist/core/util/markup-attributes.d.ts +3 -1
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.d.ts +9 -5
- package/dist/core/util/parser.js +13 -13
- package/dist/core/util/recover.d.ts +6 -2
- package/dist/core/util/recover.js +6 -6
- package/dist/core/util/strip.d.ts +19 -5
- package/dist/core/util/strip.js +17 -17
- package/dist/core/util/validate.d.ts +3 -1
- package/dist/core/util/validate.js +1 -1
- package/dist/embed-widget/browser/page-actions.d.ts +121 -0
- package/dist/embed-widget/browser/page-actions.js +113 -0
- package/dist/embed-widget/browser/session-store.d.ts +44 -0
- package/dist/embed-widget/browser/session-store.js +103 -0
- package/dist/embed-widget/react/embed.d.ts +45 -0
- package/dist/embed-widget/react/embed.js +87 -0
- package/dist/embed-widget/react/header.d.ts +21 -0
- package/dist/embed-widget/react/header.js +18 -0
- package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
- package/dist/embed-widget/transports/fetch-sse.js +175 -0
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
- package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
- package/dist/react/artifact-types.d.ts +3 -3
- package/dist/react/artifact-types.js +3 -3
- package/dist/react/components/A2uiSurfaceCard.js +34 -13
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
- package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
- package/dist/react/components/AttachmentPreviewModal.js +2 -2
- package/dist/react/components/AttachmentTray.d.ts +3 -1
- package/dist/react/components/AttachmentTray.js +2 -2
- package/dist/react/components/ChatFab.js +1 -1
- package/dist/react/components/Composer.js +9 -9
- package/dist/react/components/ConversationList.js +1 -1
- package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
- package/dist/react/components/McpUiSurfaceCard.js +5 -5
- package/dist/react/components/MessageList.js +1 -1
- package/dist/react/components/MessageRow.js +9 -9
- package/dist/react/components/QuestionForm.js +5 -5
- package/dist/react/components/ToolCard.js +9 -9
- package/dist/react/components/composer-discovery.d.ts +29 -8
- package/dist/react/components/composer-discovery.js +10 -10
- package/dist/react/ext-event-renderer-registry.d.ts +15 -5
- package/dist/react/ext-event-renderer-registry.js +4 -4
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
- package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +30 -30
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
- package/dist/react/features/chat-pane/rules.d.ts +13 -4
- package/dist/react/features/chat-pane/rules.js +5 -5
- package/dist/react/features/chat-pane/types.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
- package/dist/react/hooks/attachment-preview-cache.js +2 -2
- package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
- package/dist/react/hooks/composer-draft-cache.js +6 -6
- package/dist/react/hooks/useArtifactStream.d.ts +5 -1
- package/dist/react/hooks/useArtifactStream.js +3 -3
- package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
- package/dist/react/hooks/useChatFabDrag.js +4 -4
- package/dist/react/hooks/useComposer.js +8 -8
- package/dist/react/hooks/useConversation.js +56 -4
- package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
- package/dist/react/hooks/useExtEventGroups.js +1 -1
- package/dist/react/hooks/useLatestOperation.d.ts +8 -3
- package/dist/react/hooks/useLatestOperation.js +4 -4
- package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
- package/dist/react/hooks/usePinnedTodos.js +3 -3
- package/dist/react/hooks/useQuestionForms.d.ts +7 -2
- package/dist/react/hooks/useQuestionForms.js +7 -7
- package/dist/react/hooks/useRunActivity.d.ts +6 -1
- package/dist/react/hooks/useRunActivity.js +3 -3
- package/dist/react/hooks/useRunStream.d.ts +3 -1
- package/dist/react/hooks/useRunStream.js +1 -1
- package/dist/react/hooks/useToolTimeline.d.ts +3 -1
- package/dist/react/hooks/useToolTimeline.js +3 -3
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +6 -6
- package/dist/react/message-blocks.d.ts +10 -4
- package/dist/react/message-blocks.js +2 -2
- package/dist/react/styles/reference.css +3 -3
- package/dist/react/tool-renderer-registry.d.ts +8 -3
- package/dist/react/tool-renderer-registry.js +3 -3
- package/dist/react/util/defined-props.d.ts +3 -1
- package/dist/react/util/defined-props.js +1 -1
- package/dist/server/run-finalizer/entry.d.ts +47 -0
- package/dist/server/run-finalizer/entry.js +156 -0
- package/dist/server/run-finalizer/ports.d.ts +52 -0
- package/dist/store/contracts/index.d.ts +5 -0
- package/dist/store/contracts/index.js +5 -0
- package/dist/store/errors.d.ts +12 -0
- package/dist/store/errors.js +18 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.js +2 -0
- package/dist/store/legacy/index.d.ts +3 -0
- package/dist/store/legacy/index.js +2 -0
- package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
- package/dist/store/legacy/sqlite/conversations.js +274 -0
- package/dist/store/legacy/sqlite/index.d.ts +6 -0
- package/dist/store/legacy/sqlite/index.js +5 -0
- package/dist/store/legacy/sqlite/messages.d.ts +156 -0
- package/dist/store/legacy/sqlite/messages.js +246 -0
- package/dist/store/legacy/sqlite/schema.d.ts +3 -0
- package/dist/store/legacy/sqlite/schema.js +40 -0
- package/dist/store/pglite/index.d.ts +21 -0
- package/dist/store/pglite/index.js +17 -0
- package/dist/store/ports.d.ts +121 -0
- package/dist/store/ports.js +2 -0
- package/dist/store/postgres/index.d.ts +21 -0
- package/dist/store/postgres/index.js +17 -0
- package/dist/store/sql/boundary.d.ts +28 -0
- package/dist/store/sql/boundary.js +32 -0
- package/dist/store/sql/cursor.d.ts +24 -0
- package/dist/store/sql/cursor.js +49 -0
- package/dist/store/sql/maintenance.d.ts +9 -0
- package/dist/store/sql/maintenance.js +16 -0
- package/dist/store/sql/store.d.ts +22 -0
- package/dist/store/sql/store.js +340 -0
- package/dist/store/sql/tables.d.ts +34 -0
- package/dist/store/sql/tables.js +2 -0
- package/dist/store/sqlite/index.d.ts +36 -0
- package/dist/store/sqlite/index.js +29 -0
- package/dist/store/sqlite/schema.d.ts +49 -0
- package/dist/store/sqlite/schema.js +80 -0
- package/package.json +211 -14
- package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
- package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
- package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
- package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
- package/dist/react/features/model-picker/constants.d.ts +0 -6
- package/dist/react/features/model-picker/constants.js +0 -9
- package/dist/react/features/model-picker/dependencies.d.ts +0 -11
- package/dist/react/features/model-picker/dependencies.js +0 -10
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
- package/dist/react/features/model-picker/index.d.ts +0 -32
- package/dist/react/features/model-picker/index.js +0 -29
- package/dist/react/features/model-picker/ports.d.ts +0 -11
- package/dist/react/features/model-picker/rules.d.ts +0 -26
- package/dist/react/features/model-picker/rules.js +0 -84
- package/dist/react/features/model-picker/types.d.ts +0 -38
- package/dist/react/features/model-picker/types.js +0 -2
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
- package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
- package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
- /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
|
@@ -108,11 +108,11 @@ function resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAcce
|
|
|
108
108
|
if (uploadAttachments === undefined)
|
|
109
109
|
return {};
|
|
110
110
|
return {
|
|
111
|
-
attachmentPicker: definedProps({
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
attachmentPicker: definedProps({ source: {
|
|
112
|
+
onFiles: pane.addAttachments,
|
|
113
|
+
uploading: pane.isUploadingAttachments,
|
|
114
|
+
accept: attachmentAccept,
|
|
115
|
+
} }),
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
@@ -169,7 +169,7 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
|
|
|
169
169
|
// rather than refusing it, so the operator can keep writing while the agent thinks.
|
|
170
170
|
disabled: disabled || unavailable,
|
|
171
171
|
// The one blocker `send()` handles itself. Every other refusal still greys out the button.
|
|
172
|
-
sendDisabled: !pane.canSend && pane.sendBlocker !== 'streaming', running: pane.conversation.isStreaming, onCancel: pane.conversation.cancel, onInterrupt: pane.interruptSend, ...definedProps({ placeholder }), slots: slots, ...resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAccept), ...resolveComposerWorkingDirectory(workingDirectoryAccess, workingDirectoryControlPlacement, pane) }), _jsx(ChatPaneWorkingDirectoryBlock, { workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, pane: pane, t: t })] }));
|
|
172
|
+
sendDisabled: !pane.canSend && pane.sendBlocker !== 'streaming', running: pane.conversation.isStreaming, onCancel: pane.conversation.cancel, onInterrupt: pane.interruptSend, ...definedProps({ source: { placeholder } }), slots: slots, ...resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAccept), ...resolveComposerWorkingDirectory(workingDirectoryAccess, workingDirectoryControlPlacement, pane) }), _jsx(ChatPaneWorkingDirectoryBlock, { workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, pane: pane, t: t })] }));
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
175
|
* Renders the self-contained chat-pane composition, including runtime and
|
|
@@ -180,37 +180,37 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
|
|
|
180
180
|
*/
|
|
181
181
|
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, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, validateAttachments, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
|
|
182
182
|
const t = useT();
|
|
183
|
-
const composerPlaceholder = useChatPaneComposerPlaceholder(placeholder, placeholders);
|
|
184
|
-
const inventory = useChatPaneRuntimeInventory(definedProps({
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
|
|
184
|
+
const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
|
|
185
|
+
access: runtimeAccess,
|
|
186
|
+
initialAgents: injectedAgents,
|
|
187
|
+
pollIntervalMs: runtimeStatusPollMs,
|
|
188
|
+
} }));
|
|
189
189
|
const runtimeView = resolveRuntimeAccessView(runtimeAccess, injectedAgents, scanningAgents, daemonOnline, onRescanAgents, inventory);
|
|
190
190
|
// Computed once and threaded into both `useChatPane` (so `sendBlocker` agrees) and this
|
|
191
191
|
// component's own `unavailable` below (so the status banner and composer agree) — see
|
|
192
192
|
// `isChatPaneApiModeConfigured` for why API mode alone isn't enough.
|
|
193
193
|
const apiModeConfigured = isChatPaneApiModeConfigured({ executionMode, apiModeAvailable, byokRuntime });
|
|
194
|
-
const pane = useChatPane(definedProps({
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
194
|
+
const pane = useChatPane(definedProps({ source: {
|
|
195
|
+
transport,
|
|
196
|
+
agents: runtimeView.agents,
|
|
197
|
+
initialMessages,
|
|
198
|
+
conversationId,
|
|
199
|
+
initialSelection,
|
|
200
|
+
selection: controlledSelection,
|
|
201
|
+
onSelectionChange,
|
|
202
|
+
runContext,
|
|
203
|
+
initialDraft,
|
|
204
|
+
validateAttachments,
|
|
205
|
+
uploadAttachments,
|
|
206
|
+
onActivityChange,
|
|
207
|
+
onMessagesChange,
|
|
208
|
+
workingDirectory,
|
|
209
|
+
initialWorkingDirectory,
|
|
210
|
+
onChangeWorkingDirectory,
|
|
211
|
+
workingDirectoryAccess,
|
|
212
|
+
apiModeConfigured,
|
|
213
|
+
} }));
|
|
214
214
|
// Mirrors `Composer.tsx`'s own `draftRef`: `composerHandle.insertText` (below) is called from
|
|
215
215
|
// OUTSIDE any render, so it cannot close over `pane.composer.draft` directly — that would freeze
|
|
216
216
|
// it at whatever the draft was on the render that captured it. Reassigned unconditionally every
|
|
@@ -222,7 +222,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
222
222
|
return;
|
|
223
223
|
composerHandle.current = {
|
|
224
224
|
insertText: (text) => {
|
|
225
|
-
pane.composer.setDraft(appendComposerDiscovery(composerDraftRef.current, text));
|
|
225
|
+
pane.composer.setDraft(appendComposerDiscovery({ draft: composerDraftRef.current, insertText: text }));
|
|
226
226
|
},
|
|
227
227
|
};
|
|
228
228
|
return () => {
|
|
@@ -236,11 +236,11 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
236
236
|
// No `runContext` here on purpose: agent-driven sends go through `pane.sendPrompt`, which builds
|
|
237
237
|
// the context from the SAME `runContext` already handed to `useChatPane` above. A second copy
|
|
238
238
|
// would be a second source of truth that could silently drift from the composer's.
|
|
239
|
-
useChatPaneAgentControl(pane, definedProps({
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
useChatPaneAgentControl({ pane: pane }, definedProps({ source: {
|
|
240
|
+
enabled: agentControl?.enabled ?? false,
|
|
241
|
+
bridgeAccess: agentControl?.bridgeAccess,
|
|
242
|
+
webmcp: agentControl?.webmcp,
|
|
243
|
+
} }));
|
|
244
244
|
// Selection resolution only returns available agents, so absence normally means nothing usable
|
|
245
245
|
// is selected — except a configured BYOK turn (`apiModeConfigured`, computed above), which calls
|
|
246
246
|
// the provider directly over HTTP and never touches the CLI inventory.
|
|
@@ -256,11 +256,11 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
256
256
|
// `composerSlots`'s type excludes both `leadingAccessories` and `footerAccessories` (see
|
|
257
257
|
// `ChatPaneProps.composerSlots`'s doc), so the two explicit keys below are never overwriting a
|
|
258
258
|
// caller-supplied value — they are the ONLY source for both.
|
|
259
|
-
const slots = definedProps({
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
const slots = definedProps({ source: {
|
|
260
|
+
...composerSlots,
|
|
261
|
+
leadingAccessories: leadingAccessory,
|
|
262
|
+
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 } }) })),
|
|
263
|
+
} });
|
|
264
264
|
const { rootRef, controlsRef } = useChatPaneControlsHeight();
|
|
265
265
|
return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, 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: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), _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, t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
|
|
266
266
|
}
|
|
@@ -17,5 +17,8 @@ export interface CreateDaemonAttachmentUploaderOptions {
|
|
|
17
17
|
* @param baseUrl Origin (or same-origin path prefix) the daemon's API is reachable at. Pass `''`
|
|
18
18
|
* when the page is served through a proxy that already forwards `/api`.
|
|
19
19
|
*/
|
|
20
|
-
export declare function createDaemonAttachmentUploader(baseUrl
|
|
20
|
+
export declare function createDaemonAttachmentUploader({ baseUrl, fetch }: {
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
fetch?: typeof globalThis.fetch;
|
|
23
|
+
}, options?: CreateDaemonAttachmentUploaderOptions): NonNullable<ChatPaneProps['uploadAttachments']>;
|
|
21
24
|
//# sourceMappingURL=create-daemon-attachment-uploader.d.ts.map
|
|
@@ -1,34 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module create-daemon-attachment-uploader
|
|
3
|
-
*
|
|
4
|
-
* The client half of `@jini-ai/http-kit`'s attachment capability: a ready-made
|
|
5
|
-
* `ChatPaneProps['uploadAttachments']` that talks to `POST`/`DELETE /api/attachments`.
|
|
6
|
-
*
|
|
7
|
-
* ```tsx
|
|
8
|
-
* <ChatPane transport={transport} uploadAttachments={createDaemonAttachmentUploader(daemonUrl)} />
|
|
9
|
-
* ```
|
|
10
|
-
*
|
|
11
|
-
* That one line is the whole wire-up for composer drag-and-drop and the file picker — `ChatPane`
|
|
12
|
-
* gates both on `uploadAttachments` being present (see `ChatPane.tsx`'s `resolveDropTargetProps`).
|
|
13
|
-
*
|
|
14
|
-
* **Why the client enforces quotas at all**, when the daemon enforces them anyway: a user who drags
|
|
15
|
-
* in a 400 MB video should be told immediately, not after the browser has finished streaming it to
|
|
16
|
-
* localhost. These checks are a courtesy, never the security boundary — the daemon re-derives every
|
|
17
|
-
* one of them, and only its answer decides anything.
|
|
18
|
-
*
|
|
19
|
-
* **Bounded concurrency, preserved order.** Two workers pull from a shared index, so at most two
|
|
20
|
-
* requests are in flight while results still land at their input positions. The first failure aborts
|
|
21
|
-
* the rest and deletes whatever already landed, so a partly-uploaded turn never leaves files behind
|
|
22
|
-
* on the daemon waiting for their TTL.
|
|
23
|
-
*
|
|
24
|
-
* **`content-type` is always `application/octet-stream`**, deliberately not `file.type`. The daemon
|
|
25
|
-
* sniffs the real kind from the leading bytes and ignores this header entirely, so forwarding the
|
|
26
|
-
* browser's guess buys nothing — and actively breaks things: a dropped `.json` file gets
|
|
27
|
-
* `content-type: application/json` from the browser, which an app-wide `express.json()` on the
|
|
28
|
-
* daemon then claims, draining the request stream before the upload route can read a byte. A fixed
|
|
29
|
-
* octet-stream type is immune to every body parser.
|
|
30
|
-
*/
|
|
31
|
-
import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
|
|
32
1
|
const DEFAULTS = {
|
|
33
2
|
maxAttachmentBytes: 20 * 1024 * 1024,
|
|
34
3
|
maxAttachmentCount: 10,
|
|
@@ -80,7 +49,7 @@ async function messageForFailedUpload(response, fallback) {
|
|
|
80
49
|
* @param baseUrl Origin (or same-origin path prefix) the daemon's API is reachable at. Pass `''`
|
|
81
50
|
* when the page is served through a proxy that already forwards `/api`.
|
|
82
51
|
*/
|
|
83
|
-
export function createDaemonAttachmentUploader(baseUrl, options = {}) {
|
|
52
|
+
export function createDaemonAttachmentUploader({ baseUrl, fetch }, options = {}) {
|
|
84
53
|
const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULTS.maxAttachmentBytes;
|
|
85
54
|
const maxAttachmentCount = options.maxAttachmentCount ?? DEFAULTS.maxAttachmentCount;
|
|
86
55
|
const maxBatchBytes = options.maxBatchBytes ?? DEFAULTS.maxBatchBytes;
|
|
@@ -91,7 +60,7 @@ export function createDaemonAttachmentUploader(baseUrl, options = {}) {
|
|
|
91
60
|
const batchUsage = new Map();
|
|
92
61
|
async function uploadOne(file, batchId, signal) {
|
|
93
62
|
const query = new URLSearchParams({ batch: batchId, name: file.name });
|
|
94
|
-
const response = await fetch(`${endpoint}?${query}`, {
|
|
63
|
+
const response = await (fetch ?? globalThis.fetch)(`${endpoint}?${query}`, {
|
|
95
64
|
method: 'POST',
|
|
96
65
|
// Fixed, not `file.type` — see this module's doc.
|
|
97
66
|
headers: { 'content-type': 'application/octet-stream' },
|
|
@@ -115,11 +84,12 @@ export function createDaemonAttachmentUploader(baseUrl, options = {}) {
|
|
|
115
84
|
// caller-supplied signal to compose with and no large body — it is a small best-effort JSON
|
|
116
85
|
// DELETE, and an unprotected `fetch` here would hang `uploadAttachments`'s rejection (the
|
|
117
86
|
// `await` below) on a stalled daemon instead of surfacing `firstError` promptly.
|
|
118
|
-
await
|
|
87
|
+
await (fetch ?? globalThis.fetch)(endpoint, {
|
|
119
88
|
method: 'DELETE',
|
|
89
|
+
signal: AbortSignal.timeout(15_000),
|
|
120
90
|
headers: { 'content-type': 'application/json' },
|
|
121
91
|
body: JSON.stringify({ batchId, paths: attachments.map((attachment) => attachment.path) }),
|
|
122
|
-
}
|
|
92
|
+
});
|
|
123
93
|
}
|
|
124
94
|
catch {
|
|
125
95
|
// Best effort by design: the daemon expires unclaimed uploads on its own, so a failed cleanup
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* {@link McpUiToolCallHandler} that relays a View's `tools/call` to an HTTP endpoint.
|
|
6
6
|
*
|
|
7
7
|
* ```tsx
|
|
8
|
-
* registerMcpUiSurfaceRenderer({ onToolCall: createMcpUiToolCaller('') });
|
|
8
|
+
* registerMcpUiSurfaceRenderer({ onToolCall: createMcpUiToolCaller({ baseUrl: '', fetch }) });
|
|
9
9
|
* ```
|
|
10
10
|
*
|
|
11
11
|
* That one line completes the loop. `@jini-ai/daemon`'s `delegated-tool-bridge.ts` splits a UI
|
|
@@ -66,5 +66,8 @@ export interface McpUiToolCallRequest {
|
|
|
66
66
|
* proxied to its own API.
|
|
67
67
|
* @complexity O(1) — one request per call.
|
|
68
68
|
*/
|
|
69
|
-
export declare function createMcpUiToolCaller(baseUrl
|
|
69
|
+
export declare function createMcpUiToolCaller({ baseUrl, fetch }: {
|
|
70
|
+
baseUrl: string;
|
|
71
|
+
fetch: typeof globalThis.fetch;
|
|
72
|
+
}, options?: CreateMcpUiToolCallerOptions): McpUiToolCallHandler;
|
|
70
73
|
//# sourceMappingURL=create-mcp-ui-tool-caller.d.ts.map
|
|
@@ -38,7 +38,7 @@ function errorFrom(body, status) {
|
|
|
38
38
|
* proxied to its own API.
|
|
39
39
|
* @complexity O(1) — one request per call.
|
|
40
40
|
*/
|
|
41
|
-
export function createMcpUiToolCaller(baseUrl, options = {}) {
|
|
41
|
+
export function createMcpUiToolCaller({ baseUrl, fetch }, options = {}) {
|
|
42
42
|
const path = options.path ?? DEFAULT_PATH;
|
|
43
43
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
44
44
|
const endpoint = `${baseUrl.replace(/\/$/u, '')}${path}`;
|
|
@@ -29,34 +29,59 @@ import type { ByokRuntimeSummary, ChatPaneAgent, ChatPaneAgentSelection, Runtime
|
|
|
29
29
|
* @complexity Time: O(n) in the supplied option count; space: O(1).
|
|
30
30
|
* @overallScore 100/100
|
|
31
31
|
*/
|
|
32
|
-
export declare function runtimeOptionLabel(options:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
export declare function runtimeOptionLabel({ options, value, fallback }: {
|
|
33
|
+
options: readonly {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
}[] | undefined;
|
|
37
|
+
value: string | undefined;
|
|
38
|
+
fallback: string;
|
|
39
|
+
}): string;
|
|
36
40
|
/**
|
|
37
41
|
* Computes a viewport-bounded fixed position for the body-portaled picker.
|
|
38
42
|
*
|
|
39
43
|
* @complexity Time/space: O(1).
|
|
40
44
|
* @overallScore 100/100
|
|
41
45
|
*/
|
|
42
|
-
export declare function runtimePopoverPosition(trigger
|
|
46
|
+
export declare function runtimePopoverPosition({ trigger, placement }: {
|
|
47
|
+
trigger: DOMRect;
|
|
48
|
+
placement: RuntimePickerPlacement;
|
|
49
|
+
}): CSSProperties;
|
|
43
50
|
/** `Tab` wraps focus around the popover's control list instead of leaving it (the popover is a
|
|
44
51
|
* modal-ish dialog). Returns the index to wrap TO, or `null` when `Tab` should behave natively
|
|
45
52
|
* (not at an edge). `currentIndex` may be `-1` (active element not in `controls`); `atStart`/
|
|
46
53
|
* `atEnd` below only matter when it legitimately is 0 or `count - 1`. */
|
|
47
|
-
export declare function resolveTabWrapIndex(shiftKey
|
|
54
|
+
export declare function resolveTabWrapIndex({ shiftKey, currentIndex, count }: {
|
|
55
|
+
shiftKey: boolean;
|
|
56
|
+
currentIndex: number;
|
|
57
|
+
count: number;
|
|
58
|
+
}): number | null;
|
|
48
59
|
/** Arrow/Home/End roving-tabindex target, or `null` for any other key. `currentIndex === -1`
|
|
49
60
|
* (nothing in `controls` focused yet) is a legitimate input — the modulo arithmetic below relies
|
|
50
61
|
* on it wrapping the same way a real index would. */
|
|
51
|
-
export declare function resolveRovingIndex(key
|
|
62
|
+
export declare function resolveRovingIndex({ key, currentIndex, count }: {
|
|
63
|
+
key: string;
|
|
64
|
+
currentIndex: number;
|
|
65
|
+
count: number;
|
|
66
|
+
}): number | null;
|
|
52
67
|
/**
|
|
53
68
|
* The control index the popover's key handler should move focus to, or `null` to leave focus alone
|
|
54
69
|
* (and, in the caller, to skip `preventDefault()`). `Tab` is resolved and returned immediately —
|
|
55
70
|
* it must never fall through to the roving-tabindex logic below it.
|
|
56
71
|
*/
|
|
57
|
-
export declare function resolvePopoverKeyAction(key
|
|
72
|
+
export declare function resolvePopoverKeyAction({ key, shiftKey, isButtonTarget, currentIndex, count }: {
|
|
73
|
+
key: string;
|
|
74
|
+
shiftKey: boolean;
|
|
75
|
+
isButtonTarget: boolean;
|
|
76
|
+
currentIndex: number;
|
|
77
|
+
count: number;
|
|
78
|
+
}): number | null;
|
|
58
79
|
/** Builds the one-line "which runtime am I talking to" summary shown in the popover head. */
|
|
59
|
-
export declare function runtimeSummaryText(selectedAgent
|
|
80
|
+
export declare function runtimeSummaryText({ selectedAgent, modelLabel, t }: {
|
|
81
|
+
selectedAgent: ChatPaneAgent | undefined;
|
|
82
|
+
modelLabel: string;
|
|
83
|
+
t: (key: string) => string;
|
|
84
|
+
}): string;
|
|
60
85
|
/**
|
|
61
86
|
* The head/trigger labels while `executionMode` is `'api'`.
|
|
62
87
|
*
|
|
@@ -68,7 +93,10 @@ export declare function runtimeSummaryText(selectedAgent: ChatPaneAgent | undefi
|
|
|
68
93
|
*
|
|
69
94
|
* @complexity Time/space: O(1).
|
|
70
95
|
*/
|
|
71
|
-
export declare function byokSummaryText(byokRuntime
|
|
96
|
+
export declare function byokSummaryText({ byokRuntime, t }: {
|
|
97
|
+
byokRuntime: ByokRuntimeSummary | undefined;
|
|
98
|
+
t: (key: string) => string;
|
|
99
|
+
}): {
|
|
72
100
|
title: string;
|
|
73
101
|
modelLabel: string;
|
|
74
102
|
summary: string;
|
|
@@ -31,7 +31,7 @@ const FOCUSABLE_CONTROLS = 'button:not(:disabled), select:not(:disabled), input:
|
|
|
31
31
|
* @complexity Time: O(n) in the supplied option count; space: O(1).
|
|
32
32
|
* @overallScore 100/100
|
|
33
33
|
*/
|
|
34
|
-
export function runtimeOptionLabel(options, value, fallback) {
|
|
34
|
+
export function runtimeOptionLabel({ options, value, fallback }) {
|
|
35
35
|
if (!value || value === 'default')
|
|
36
36
|
return fallback;
|
|
37
37
|
return options?.find((option) => option.id === value)?.label ?? value;
|
|
@@ -42,7 +42,7 @@ export function runtimeOptionLabel(options, value, fallback) {
|
|
|
42
42
|
* @complexity Time/space: O(1).
|
|
43
43
|
* @overallScore 100/100
|
|
44
44
|
*/
|
|
45
|
-
export function runtimePopoverPosition(trigger, placement) {
|
|
45
|
+
export function runtimePopoverPosition({ trigger, placement }) {
|
|
46
46
|
const margin = 12;
|
|
47
47
|
const gap = 8;
|
|
48
48
|
const width = Math.min(320, window.innerWidth - margin * 2);
|
|
@@ -75,7 +75,7 @@ export function runtimePopoverPosition(trigger, placement) {
|
|
|
75
75
|
* modal-ish dialog). Returns the index to wrap TO, or `null` when `Tab` should behave natively
|
|
76
76
|
* (not at an edge). `currentIndex` may be `-1` (active element not in `controls`); `atStart`/
|
|
77
77
|
* `atEnd` below only matter when it legitimately is 0 or `count - 1`. */
|
|
78
|
-
export function resolveTabWrapIndex(shiftKey, currentIndex, count) {
|
|
78
|
+
export function resolveTabWrapIndex({ shiftKey, currentIndex, count }) {
|
|
79
79
|
if (shiftKey && currentIndex === 0)
|
|
80
80
|
return count - 1;
|
|
81
81
|
if (!shiftKey && currentIndex === count - 1)
|
|
@@ -85,7 +85,7 @@ export function resolveTabWrapIndex(shiftKey, currentIndex, count) {
|
|
|
85
85
|
/** Arrow/Home/End roving-tabindex target, or `null` for any other key. `currentIndex === -1`
|
|
86
86
|
* (nothing in `controls` focused yet) is a legitimate input — the modulo arithmetic below relies
|
|
87
87
|
* on it wrapping the same way a real index would. */
|
|
88
|
-
export function resolveRovingIndex(key, currentIndex, count) {
|
|
88
|
+
export function resolveRovingIndex({ key, currentIndex, count }) {
|
|
89
89
|
if (key === 'Home')
|
|
90
90
|
return 0;
|
|
91
91
|
if (key === 'End')
|
|
@@ -101,17 +101,17 @@ export function resolveRovingIndex(key, currentIndex, count) {
|
|
|
101
101
|
* (and, in the caller, to skip `preventDefault()`). `Tab` is resolved and returned immediately —
|
|
102
102
|
* it must never fall through to the roving-tabindex logic below it.
|
|
103
103
|
*/
|
|
104
|
-
export function resolvePopoverKeyAction(key, shiftKey, isButtonTarget, currentIndex, count) {
|
|
104
|
+
export function resolvePopoverKeyAction({ key, shiftKey, isButtonTarget, currentIndex, count }) {
|
|
105
105
|
if (key === 'Tab')
|
|
106
|
-
return resolveTabWrapIndex(shiftKey, currentIndex, count);
|
|
106
|
+
return resolveTabWrapIndex({ shiftKey: shiftKey, currentIndex: currentIndex, count: count });
|
|
107
107
|
// Native selects own their arrow/Home/End behavior. Roving applies only
|
|
108
108
|
// when a button (including the agent radios) has focus.
|
|
109
109
|
if (!isButtonTarget)
|
|
110
110
|
return null;
|
|
111
|
-
return resolveRovingIndex(key, currentIndex, count);
|
|
111
|
+
return resolveRovingIndex({ key: key, currentIndex: currentIndex, count: count });
|
|
112
112
|
}
|
|
113
113
|
/** Builds the one-line "which runtime am I talking to" summary shown in the popover head. */
|
|
114
|
-
export function runtimeSummaryText(selectedAgent, modelLabel, t) {
|
|
114
|
+
export function runtimeSummaryText({ selectedAgent, modelLabel, t }) {
|
|
115
115
|
if (selectedAgent === undefined)
|
|
116
116
|
return t('No agent selected');
|
|
117
117
|
const version = selectedAgent.version ? ` · ${selectedAgent.version}` : '';
|
|
@@ -128,7 +128,7 @@ export function runtimeSummaryText(selectedAgent, modelLabel, t) {
|
|
|
128
128
|
*
|
|
129
129
|
* @complexity Time/space: O(1).
|
|
130
130
|
*/
|
|
131
|
-
export function byokSummaryText(byokRuntime, t) {
|
|
131
|
+
export function byokSummaryText({ byokRuntime, t }) {
|
|
132
132
|
const title = byokRuntime?.providerLabel?.trim() || t('API · BYOK');
|
|
133
133
|
const model = byokRuntime?.model?.trim();
|
|
134
134
|
const modelLabel = model || t('No model configured');
|
|
@@ -146,16 +146,16 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
|
|
|
146
146
|
const [position, setPosition] = useState();
|
|
147
147
|
const triggerRef = useRef(null);
|
|
148
148
|
const popoverRef = useRef(null);
|
|
149
|
-
const orderedAgents = useMemo(() => orderChatPaneAgents(agents.filter((agent) => agent.available !== false)), [agents]);
|
|
149
|
+
const orderedAgents = useMemo(() => orderChatPaneAgents({ agents: agents.filter((agent) => agent.available !== false) }), [agents]);
|
|
150
150
|
const selectedAgent = orderedAgents.find((agent) => agent.id === value.agentId);
|
|
151
151
|
// `selectedAgent` stays resolved in BOTH modes on purpose — the CLI selection is still the stored
|
|
152
152
|
// one, and switching back to Local CLI must not have silently lost it. What changes in `'api'` is
|
|
153
153
|
// only what gets DESCRIBED, because none of the CLI's labels are true of an API turn.
|
|
154
|
-
const localModelLabel = runtimeOptionLabel(selectedAgent?.models, value.model, t('Default model'));
|
|
155
|
-
const byok = byokSummaryText(byokRuntime, t);
|
|
154
|
+
const localModelLabel = runtimeOptionLabel({ options: selectedAgent?.models, value: value.model, fallback: t('Default model') });
|
|
155
|
+
const byok = byokSummaryText({ byokRuntime: byokRuntime, t: t });
|
|
156
156
|
const isApi = executionMode === 'api';
|
|
157
157
|
const modelLabel = isApi ? byok.modelLabel : localModelLabel;
|
|
158
|
-
const runtimeSummary = isApi ? byok.summary : runtimeSummaryText(selectedAgent, localModelLabel, t);
|
|
158
|
+
const runtimeSummary = isApi ? byok.summary : runtimeSummaryText({ selectedAgent: selectedAgent, modelLabel: localModelLabel, t: t });
|
|
159
159
|
const triggerTitle = isApi ? byok.title : (selectedAgent?.name ?? t('Choose agent'));
|
|
160
160
|
// Read during render rather than in an effect: the popover only mounts once `open` is true, which
|
|
161
161
|
// is itself a re-render, and `triggerRef` is populated at the first commit — so by the time this
|
|
@@ -205,7 +205,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
|
|
|
205
205
|
const update = () => {
|
|
206
206
|
const rect = triggerRef.current?.getBoundingClientRect();
|
|
207
207
|
if (rect)
|
|
208
|
-
setPosition(runtimePopoverPosition(rect, placement));
|
|
208
|
+
setPosition(runtimePopoverPosition({ trigger: rect, placement: placement }));
|
|
209
209
|
};
|
|
210
210
|
update();
|
|
211
211
|
window.addEventListener('resize', update);
|
|
@@ -233,7 +233,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
|
|
|
233
233
|
if (controls.length === 0)
|
|
234
234
|
return;
|
|
235
235
|
const currentIndex = controls.indexOf(document.activeElement);
|
|
236
|
-
const nextIndex = resolvePopoverKeyAction(event.key, event.shiftKey, event.target instanceof HTMLButtonElement, currentIndex, controls.length);
|
|
236
|
+
const nextIndex = resolvePopoverKeyAction({ key: event.key, shiftKey: event.shiftKey, isButtonTarget: event.target instanceof HTMLButtonElement, currentIndex: currentIndex, count: controls.length });
|
|
237
237
|
if (nextIndex === null)
|
|
238
238
|
return;
|
|
239
239
|
event.preventDefault();
|
|
@@ -70,29 +70,29 @@ export function useChatPane(options) {
|
|
|
70
70
|
const attachmentBatchIdRef = useRef(createAttachmentBatchId());
|
|
71
71
|
const activeUploadsRef = useRef(new Set());
|
|
72
72
|
const [internalSelection, setInternalSelection] = useState(options.initialSelection ?? { agentId: '' });
|
|
73
|
-
const workingDirectoryState = useChatPaneWorkingDirectory(definedProps({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
const workingDirectoryState = useChatPaneWorkingDirectory(definedProps({ source: {
|
|
74
|
+
workingDirectory: options.workingDirectory,
|
|
75
|
+
initialWorkingDirectory: options.initialWorkingDirectory,
|
|
76
|
+
onChangeWorkingDirectory: options.onChangeWorkingDirectory,
|
|
77
|
+
workingDirectoryAccess: options.workingDirectoryAccess,
|
|
78
|
+
} }));
|
|
79
79
|
const requestedSelection = options.selection ?? internalSelection;
|
|
80
|
-
const selection = useMemo(() => resolveChatPaneSelection(options.agents, requestedSelection), [options.agents, requestedSelection]);
|
|
80
|
+
const selection = useMemo(() => resolveChatPaneSelection({ agents: options.agents, requested: requestedSelection }), [options.agents, requestedSelection]);
|
|
81
81
|
const selectedAgent = options.agents.find((agent) => agent.id === selection.agentId);
|
|
82
|
-
const composer = useComposer(definedProps({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const conversation = useConversation(definedProps({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
const composer = useComposer(definedProps({ source: {
|
|
83
|
+
initialDraft: options.initialDraft,
|
|
84
|
+
initialAgent: selection,
|
|
85
|
+
conversationId: options.conversationId,
|
|
86
|
+
validateAttachments: options.validateAttachments,
|
|
87
|
+
} }));
|
|
88
|
+
const conversation = useConversation(definedProps({ source: {
|
|
89
|
+
transport: options.transport,
|
|
90
|
+
initialMessages: options.initialMessages,
|
|
91
|
+
conversationId: options.conversationId,
|
|
92
|
+
// Keys off an empty string, not `undefined` — `selection.agentId` is always a string (never
|
|
93
|
+
// absent), so only the falsy "no agent selected" case should omit the key.
|
|
94
|
+
agentId: selection.agentId || undefined,
|
|
95
|
+
} }));
|
|
96
96
|
const activity = resolveChatPaneActivity(selectedAgent, conversation);
|
|
97
97
|
useEffect(() => {
|
|
98
98
|
options.onActivityChange?.(activity);
|
|
@@ -131,7 +131,7 @@ export function useChatPane(options) {
|
|
|
131
131
|
selection,
|
|
132
132
|
]);
|
|
133
133
|
const setSelection = useCallback((next) => {
|
|
134
|
-
const validated = resolveChatPaneSelection(options.agents, next);
|
|
134
|
+
const validated = resolveChatPaneSelection({ agents: options.agents, requested: next });
|
|
135
135
|
setInternalSelection(validated);
|
|
136
136
|
composer.setAgent(validated);
|
|
137
137
|
options.onSelectionChange?.(validated);
|
|
@@ -174,7 +174,7 @@ export function useChatPane(options) {
|
|
|
174
174
|
onAttachment: (attachment, file) => {
|
|
175
175
|
composer.addAttachment(attachment);
|
|
176
176
|
if (file)
|
|
177
|
-
cacheAttachmentPreviewSource(attachment.path, file);
|
|
177
|
+
cacheAttachmentPreviewSource({ path: attachment.path, file });
|
|
178
178
|
},
|
|
179
179
|
onError: setAttachmentError,
|
|
180
180
|
});
|
|
@@ -204,13 +204,13 @@ export function useChatPane(options) {
|
|
|
204
204
|
attachmentGenerationRef.current += 1;
|
|
205
205
|
attachmentBatchIdRef.current = createAttachmentBatchId();
|
|
206
206
|
options.onActivityChange?.('queued');
|
|
207
|
-
await conversation.sendMessage(trimmed, definedProps({
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
await conversation.sendMessage(trimmed, definedProps({ source: {
|
|
208
|
+
agentId: selection.agentId,
|
|
209
|
+
// Omitted when the array is EMPTY, not merely absent — an empty `attachments: []` would be a
|
|
210
|
+
// different (valid, present) value to the transport than "no attachments key at all".
|
|
211
|
+
attachments: attachments.length === 0 ? undefined : attachments,
|
|
212
|
+
context,
|
|
213
|
+
} }));
|
|
214
214
|
}, [
|
|
215
215
|
composer,
|
|
216
216
|
conversation,
|
|
@@ -226,7 +226,7 @@ export function useChatPane(options) {
|
|
|
226
226
|
return;
|
|
227
227
|
// Queue instead of no-op'ing. `setDraft('')` and NOT `composer.reset()`: reset also discards
|
|
228
228
|
// staged attachments, which this turn still needs when it finally goes out.
|
|
229
|
-
if (isChatPaneQueueableBlocker(sendBlocker)) {
|
|
229
|
+
if (isChatPaneQueueableBlocker({ blocker: sendBlocker })) {
|
|
230
230
|
queuedConversationIdRef.current = options.conversationId;
|
|
231
231
|
setQueuedPrompt(prompt);
|
|
232
232
|
composer.setDraft('');
|
|
@@ -34,5 +34,7 @@ export interface UseChatPaneAgentControlOptions {
|
|
|
34
34
|
* The relayed channel arrives having already passed `ToolExecutor`; a WebMCP call has no run and
|
|
35
35
|
* no principal, so it cannot. See {@link UseChatPaneAgentControlOptions.webmcp}.
|
|
36
36
|
*/
|
|
37
|
-
export declare function useChatPaneAgentControl(pane
|
|
37
|
+
export declare function useChatPaneAgentControl({ pane }: {
|
|
38
|
+
pane: UseChatPaneResult;
|
|
39
|
+
}, options?: UseChatPaneAgentControlOptions): void;
|
|
38
40
|
//# sourceMappingURL=useChatPaneAgentControl.hooks.d.ts.map
|
|
@@ -82,7 +82,7 @@ async function selectAgentAction(pane, input) {
|
|
|
82
82
|
}
|
|
83
83
|
const model = optionalStringField(input, 'model');
|
|
84
84
|
const reasoning = optionalStringField(input, 'reasoning');
|
|
85
|
-
pane.setSelection(definedProps({ agentId, model, reasoning }));
|
|
85
|
+
pane.setSelection(definedProps({ source: { agentId, model, reasoning } }));
|
|
86
86
|
return { ok: true };
|
|
87
87
|
}
|
|
88
88
|
async function cancelRunAction(pane) {
|
|
@@ -122,7 +122,7 @@ async function getStateAction(pane) {
|
|
|
122
122
|
canSend: pane.canSend,
|
|
123
123
|
sendBlocker: pane.sendBlocker,
|
|
124
124
|
messageCount: pane.conversation.messages.length,
|
|
125
|
-
...definedProps({ lastMessage: lastMessageSummary }),
|
|
125
|
+
...definedProps({ source: { lastMessage: lastMessageSummary } }),
|
|
126
126
|
/**
|
|
127
127
|
* Conversation content is user- and agent-authored text, not instructions from the
|
|
128
128
|
* host. A caller reading it back must treat it as data.
|
|
@@ -190,7 +190,7 @@ async function deliverBridgeAction(pane, bridge, action) {
|
|
|
190
190
|
* The relayed channel arrives having already passed `ToolExecutor`; a WebMCP call has no run and
|
|
191
191
|
* no principal, so it cannot. See {@link UseChatPaneAgentControlOptions.webmcp}.
|
|
192
192
|
*/
|
|
193
|
-
export function useChatPaneAgentControl(pane, options = {}) {
|
|
193
|
+
export function useChatPaneAgentControl({ pane }, options = {}) {
|
|
194
194
|
const enabled = options.enabled ?? false;
|
|
195
195
|
const paneRef = useRef(pane);
|
|
196
196
|
paneRef.current = pane;
|
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
* @complexity Time/space: O(1) per render; the rotation timer is a single `setInterval`.
|
|
7
7
|
* @overallScore 100/100
|
|
8
8
|
*/
|
|
9
|
-
export declare function useChatPaneComposerPlaceholder(placeholder
|
|
9
|
+
export declare function useChatPaneComposerPlaceholder({ placeholder, placeholders }: {
|
|
10
|
+
placeholder: string | undefined;
|
|
11
|
+
placeholders: readonly string[] | undefined;
|
|
12
|
+
}): string | undefined;
|
|
10
13
|
//# sourceMappingURL=useChatPaneComposerPlaceholder.hooks.d.ts.map
|
|
@@ -38,7 +38,7 @@ function usePrefersReducedMotion() {
|
|
|
38
38
|
* @complexity Time/space: O(1) per render; the rotation timer is a single `setInterval`.
|
|
39
39
|
* @overallScore 100/100
|
|
40
40
|
*/
|
|
41
|
-
export function useChatPaneComposerPlaceholder(placeholder, placeholders) {
|
|
41
|
+
export function useChatPaneComposerPlaceholder({ placeholder, placeholders }) {
|
|
42
42
|
const reducedMotion = usePrefersReducedMotion();
|
|
43
43
|
const [index, setIndex] = useState(0);
|
|
44
44
|
// A joined key, not the array reference: a host writing `placeholders={['a', 'b']}` inline
|