@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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module useAnsweredCards
|
|
3
|
+
*
|
|
4
|
+
* Which of a message's cards the person has already answered, by ext-event slot. A held-open
|
|
5
|
+
* question's tool call stays open after the click while the confirmed work runs, so the event
|
|
6
|
+
* stream alone still reads "waiting for an answer"; the card reports its own delivered answer here
|
|
7
|
+
* and the activity line and countdown clear at once (owner, 2026-10-11).
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useState } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* @param slotOf - Maps an ext event to its render slot (`extEventSlot`).
|
|
12
|
+
* @complexity O(1) per lookup; O(answered cards) space.
|
|
13
|
+
*/
|
|
14
|
+
export function useAnsweredCards({ slotOf }) {
|
|
15
|
+
const [answered, setAnswered] = useState(() => new Set());
|
|
16
|
+
const markAnswered = useCallback((slot) => {
|
|
17
|
+
setAnswered((current) => (current.has(slot) ? current : new Set(current).add(slot)));
|
|
18
|
+
}, []);
|
|
19
|
+
const isAnswered = useCallback((slot) => answered.has(slot), [answered]);
|
|
20
|
+
const isAnsweredCard = useCallback((card) => answered.has(slotOf(card)), [answered, slotOf]);
|
|
21
|
+
return { isAnswered, markAnswered, isAnsweredCard };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=useAnsweredCards.js.map
|
|
@@ -69,6 +69,20 @@ export interface UseComposerResult {
|
|
|
69
69
|
canSubmit: boolean;
|
|
70
70
|
/** Clears the draft and staged attachments (called after a successful send). */
|
|
71
71
|
reset: () => void;
|
|
72
|
+
/**
|
|
73
|
+
* Writes a draft still waiting out {@link DRAFT_PERSIST_DEBOUNCE_MS} to storage and the host's
|
|
74
|
+
* persistence port now. `<Composer>` calls it on blur. Optional for compatibility with
|
|
75
|
+
* host-created composer controllers.
|
|
76
|
+
*/
|
|
77
|
+
flushDraft?: () => void;
|
|
72
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* How long typing must pause before the draft reaches `localStorage` and the host's persistence
|
|
81
|
+
* port. Those writes used to run synchronously on every keystroke (owner report 2026-10-08: typing
|
|
82
|
+
* lagged in a long conversation). The in-memory cache tier still updates per keystroke, and every
|
|
83
|
+
* exit path (send, conversation switch, unmount, page hide, blur) writes or discards immediately,
|
|
84
|
+
* so the delay can only cost a draft on a hard crash inside this window.
|
|
85
|
+
*/
|
|
86
|
+
export declare const DRAFT_PERSIST_DEBOUNCE_MS = 800;
|
|
73
87
|
export declare function useComposer(options?: UseComposerOptions): UseComposerResult;
|
|
74
88
|
//# sourceMappingURL=useComposer.d.ts.map
|
|
@@ -29,20 +29,89 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
31
31
|
import { useComposerHistory } from './useComposerHistory.hooks.js';
|
|
32
|
-
import { readCachedAttachments, readCachedDraft, writeCachedAttachments, writeCachedDraft, } from './composer-draft-cache.js';
|
|
32
|
+
import { readCachedAttachments, readCachedDraft, rememberCachedDraft, writeCachedAttachments, writeCachedDraft, } from './composer-draft-cache.js';
|
|
33
33
|
import { cacheAttachmentPreviewSource } from './attachment-preview-cache.js';
|
|
34
34
|
const EMPTY_MENTION = { open: false, query: '', results: [] };
|
|
35
|
+
/**
|
|
36
|
+
* How long typing must pause before the draft reaches `localStorage` and the host's persistence
|
|
37
|
+
* port. Those writes used to run synchronously on every keystroke (owner report 2026-10-08: typing
|
|
38
|
+
* lagged in a long conversation). The in-memory cache tier still updates per keystroke, and every
|
|
39
|
+
* exit path (send, conversation switch, unmount, page hide, blur) writes or discards immediately,
|
|
40
|
+
* so the delay can only cost a draft on a hard crash inside this window.
|
|
41
|
+
*/
|
|
42
|
+
export const DRAFT_PERSIST_DEBOUNCE_MS = 800;
|
|
43
|
+
/**
|
|
44
|
+
* One pending durable draft write at a time, trailing-edge debounced. Flushes on unmount and when
|
|
45
|
+
* the page is hidden (`pagehide`, or `visibilitychange` to hidden — the last event a mobile browser
|
|
46
|
+
* reliably delivers before discarding the tab).
|
|
47
|
+
* @complexity Time/space: O(1) per call.
|
|
48
|
+
*/
|
|
49
|
+
function useDeferredDraftWrite() {
|
|
50
|
+
const pendingRef = useRef(null);
|
|
51
|
+
const cancel = useCallback(() => {
|
|
52
|
+
const pending = pendingRef.current;
|
|
53
|
+
if (!pending)
|
|
54
|
+
return;
|
|
55
|
+
pendingRef.current = null;
|
|
56
|
+
clearTimeout(pending.timer);
|
|
57
|
+
}, []);
|
|
58
|
+
const flush = useCallback(() => {
|
|
59
|
+
const pending = pendingRef.current;
|
|
60
|
+
if (!pending)
|
|
61
|
+
return;
|
|
62
|
+
pendingRef.current = null;
|
|
63
|
+
clearTimeout(pending.timer);
|
|
64
|
+
pending.write();
|
|
65
|
+
}, []);
|
|
66
|
+
const schedule = useCallback((write) => {
|
|
67
|
+
if (pendingRef.current)
|
|
68
|
+
clearTimeout(pendingRef.current.timer);
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
pendingRef.current = null;
|
|
71
|
+
write();
|
|
72
|
+
}, DRAFT_PERSIST_DEBOUNCE_MS);
|
|
73
|
+
pendingRef.current = { timer, write };
|
|
74
|
+
}, []);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (typeof window === 'undefined')
|
|
77
|
+
return flush;
|
|
78
|
+
const onVisibilityChange = () => {
|
|
79
|
+
if (document.visibilityState === 'hidden')
|
|
80
|
+
flush();
|
|
81
|
+
};
|
|
82
|
+
window.addEventListener('pagehide', flush);
|
|
83
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
84
|
+
return () => {
|
|
85
|
+
window.removeEventListener('pagehide', flush);
|
|
86
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
87
|
+
flush();
|
|
88
|
+
};
|
|
89
|
+
}, [flush]);
|
|
90
|
+
return useMemo(() => ({ schedule, flush, cancel }), [schedule, flush, cancel]);
|
|
91
|
+
}
|
|
35
92
|
export function useComposer(options = {}) {
|
|
36
93
|
const { project, composerSlots, persistence, conversationId, validateAttachments } = options;
|
|
37
94
|
const [draft, setDraftState] = useState(() => options.initialDraft ?? persistence?.read() ?? readCachedDraft({ conversationId: conversationId }) ?? '');
|
|
38
95
|
const [attachments, setAttachments] = useState([]);
|
|
39
96
|
const [agent, setAgent] = useState(options.initialAgent);
|
|
40
97
|
const [mention, setMention] = useState(EMPTY_MENTION);
|
|
98
|
+
const deferredWrite = useDeferredDraftWrite();
|
|
41
99
|
const commitDraft = useCallback((next) => {
|
|
42
100
|
setDraftState(next);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
101
|
+
rememberCachedDraft({ conversationId: conversationId, draft: next });
|
|
102
|
+
const write = () => {
|
|
103
|
+
persistence?.write(next);
|
|
104
|
+
writeCachedDraft({ conversationId: conversationId, draft: next });
|
|
105
|
+
};
|
|
106
|
+
// A cleared draft (send, reset) is written at once and drops any pending write, so a stale
|
|
107
|
+
// timer can never put the sent text back.
|
|
108
|
+
if (next.trim() === '') {
|
|
109
|
+
deferredWrite.cancel();
|
|
110
|
+
write();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
deferredWrite.schedule(write);
|
|
114
|
+
}, [persistence, conversationId, deferredWrite]);
|
|
46
115
|
const history = useComposerHistory({ setRecalledDraft: setDraftState }, {
|
|
47
116
|
...(options.historyMessages === undefined ? {} : { messages: options.historyMessages }),
|
|
48
117
|
...(options.historyScope === undefined ? {} : { scope: options.historyScope }),
|
|
@@ -68,6 +137,8 @@ export function useComposer(options = {}) {
|
|
|
68
137
|
if (conversationId === previousConversationIdRef.current)
|
|
69
138
|
return;
|
|
70
139
|
previousConversationIdRef.current = conversationId;
|
|
140
|
+
// The pending write closed over the conversation being left, so this lands under its id.
|
|
141
|
+
deferredWrite.flush();
|
|
71
142
|
const restored = readCachedDraft({ conversationId: conversationId });
|
|
72
143
|
// An id arriving where there was none is NOT a switch between two conversations — it is the one
|
|
73
144
|
// the operator is already looking at finally getting a key (a new chat created server-side
|
|
@@ -85,7 +156,7 @@ export function useComposer(options = {}) {
|
|
|
85
156
|
setDraftState(restored ?? '');
|
|
86
157
|
setAttachments([]);
|
|
87
158
|
setMention(EMPTY_MENTION);
|
|
88
|
-
}, [conversationId]);
|
|
159
|
+
}, [conversationId, deferredWrite]);
|
|
89
160
|
// Held in a ref so an inline arrow from the host does not re-run the restore below on every
|
|
90
161
|
// render. Reassigned each render so a host that swaps implementations still gets the new one.
|
|
91
162
|
const validateAttachmentsRef = useRef(validateAttachments);
|
|
@@ -221,6 +292,7 @@ export function useComposer(options = {}) {
|
|
|
221
292
|
selectMention,
|
|
222
293
|
canSubmit,
|
|
223
294
|
reset,
|
|
224
|
-
|
|
295
|
+
flushDraft: deferredWrite.flush,
|
|
296
|
+
}), [draft, history, setDraft, attachments, addAttachments, addAttachment, removeAttachment, clearAttachments, agent, mention, openMention, closeMention, selectMention, canSubmit, reset, deferredWrite.flush]);
|
|
225
297
|
}
|
|
226
298
|
//# sourceMappingURL=useComposer.js.map
|
|
@@ -4,6 +4,14 @@ import type { ChatTransport, RunContext } from '../../core/index.js';
|
|
|
4
4
|
export interface UseConversationOptions extends UserTextRedactionOptions {
|
|
5
5
|
transport: ChatTransport;
|
|
6
6
|
initialMessages?: ChatMessage[];
|
|
7
|
+
/** Optional host loader and browser ports, e.g. `{ loadMessages, visibility: document, focus: window }`; refresh stays passive. */
|
|
8
|
+
refreshSignal?: {
|
|
9
|
+
loadMessages: () => Promise<ChatMessage[]>;
|
|
10
|
+
visibility: EventTarget & {
|
|
11
|
+
readonly visibilityState: string;
|
|
12
|
+
};
|
|
13
|
+
focus: EventTarget;
|
|
14
|
+
};
|
|
7
15
|
conversationId?: string | null;
|
|
8
16
|
agentId?: string;
|
|
9
17
|
/** Defaults to a `crypto.randomUUID()`-based id generator when available, else a counter. */
|
|
@@ -34,6 +34,40 @@ export function useConversation(options) {
|
|
|
34
34
|
const setMessages = useCallback((updater) => {
|
|
35
35
|
setMessagesState((prev) => (typeof updater === 'function' ? updater(prev) : updater));
|
|
36
36
|
}, []);
|
|
37
|
+
// Map upserts preserve order in O(local + saved) time/space; cleanup fences late reads on switch/unmount/local runs.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const source = options.refreshSignal;
|
|
40
|
+
if (!source || run.isStreaming)
|
|
41
|
+
return;
|
|
42
|
+
let disposed = false, pending = false, timer;
|
|
43
|
+
const refresh = async () => {
|
|
44
|
+
if (pending)
|
|
45
|
+
return;
|
|
46
|
+
pending = true;
|
|
47
|
+
try {
|
|
48
|
+
const saved = await source.loadMessages();
|
|
49
|
+
if (!disposed)
|
|
50
|
+
setMessagesState(prev => [...new Map([...prev, ...saved].map(message => [message.id, message])).values()]);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!disposed)
|
|
54
|
+
console.error('[chat] conversation refresh failed', error);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
pending = false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const schedule = () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (source.visibility.visibilityState === 'visible')
|
|
63
|
+
timer = setTimeout(() => { void refresh(); }, 500);
|
|
64
|
+
};
|
|
65
|
+
const events = [[source.visibility, 'visibilitychange'], [source.focus, 'focus']];
|
|
66
|
+
for (const [target, event] of events)
|
|
67
|
+
target.addEventListener(event, schedule);
|
|
68
|
+
return () => { disposed = true; clearTimeout(timer); for (const [target, event] of events)
|
|
69
|
+
target.removeEventListener(event, schedule); };
|
|
70
|
+
}, [options.refreshSignal, run.isStreaming]);
|
|
37
71
|
/**
|
|
38
72
|
* The mount-time snapshot of `initialMessages`, captured once — the same "read once, never react
|
|
39
73
|
* to a later identity change" contract `messages`'s own `useState` initializer above already has
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import type { AgentEvent } from '../../core/index.js';
|
|
1
|
+
import type { AgentEvent, IsAnsweredCard } from '../../core/index.js';
|
|
2
2
|
/** Wall-clock source, swappable in tests. */
|
|
3
3
|
export type NowFn = () => number;
|
|
4
4
|
/**
|
|
5
5
|
* @param events - The running message's events.
|
|
6
6
|
* @param active - True while the turn is in progress; the clock only ticks while true.
|
|
7
7
|
* @param now - Clock source (default `Date.now`).
|
|
8
|
+
* @param isAnsweredCard - Cards the person already answered (see `RunActivityOptions`).
|
|
8
9
|
* @returns The activity line's text, or `null` when `active` is false.
|
|
9
|
-
* @complexity O(n) in events per render (memoized on the events array).
|
|
10
|
+
* @complexity O(n) in events per render (memoized on the events array and the answered predicate).
|
|
10
11
|
*/
|
|
11
12
|
export declare function useRunActivity({ events, active }: {
|
|
12
13
|
events: readonly AgentEvent[] | undefined;
|
|
13
14
|
active: boolean;
|
|
14
|
-
}, { now }?: {
|
|
15
|
+
}, { now, isAnsweredCard }?: {
|
|
15
16
|
now?: (NowFn) | undefined;
|
|
17
|
+
isAnsweredCard?: IsAnsweredCard | undefined;
|
|
16
18
|
}): string | null;
|
|
17
19
|
//# sourceMappingURL=useRunActivity.d.ts.map
|
|
@@ -12,12 +12,13 @@ import { useT } from './context.js';
|
|
|
12
12
|
* @param events - The running message's events.
|
|
13
13
|
* @param active - True while the turn is in progress; the clock only ticks while true.
|
|
14
14
|
* @param now - Clock source (default `Date.now`).
|
|
15
|
+
* @param isAnsweredCard - Cards the person already answered (see `RunActivityOptions`).
|
|
15
16
|
* @returns The activity line's text, or `null` when `active` is false.
|
|
16
|
-
* @complexity O(n) in events per render (memoized on the events array).
|
|
17
|
+
* @complexity O(n) in events per render (memoized on the events array and the answered predicate).
|
|
17
18
|
*/
|
|
18
|
-
export function useRunActivity({ events, active }, { now = Date.now } = {}) {
|
|
19
|
+
export function useRunActivity({ events, active }, { now = Date.now, isAnsweredCard } = {}) {
|
|
19
20
|
const t = useT();
|
|
20
|
-
const state = useMemo(() => deriveRunActivity({ events: events }), [events]);
|
|
21
|
+
const state = useMemo(() => deriveRunActivity({ events: events }, { isAnsweredCard }), [events, isAnsweredCard]);
|
|
21
22
|
const [, setTick] = useState(0);
|
|
22
23
|
const signal = useRef(null);
|
|
23
24
|
const visible = useRef(null);
|
package/dist/react/index.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export type { ExtEventRenderProps, ExtEventRenderer, ExtEventRendererOptions, Ex
|
|
|
18
18
|
* This alias has no removal date scheduled yet; see `chat-pane.ts` for the canonical surface and
|
|
19
19
|
* the module doc there for why the split exists.
|
|
20
20
|
*/
|
|
21
|
-
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
22
|
-
export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAgentToolDef, ChatPaneAgentToolInputSchema, ChatPaneAgentToolRisk, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, CreateDaemonAttachmentUploaderOptions, CreateMcpUiToolCallerOptions, CreateTypedAnswerPosterOptions, DeliverTypedAnswer, McpUiToolCallRequest, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, UseChatPaneAgentControlOptions, UseChatPaneOptions, UseChatPaneResult, UseChatPaneRuntimeInventoryOptions, UseChatPaneRuntimeInventoryResult, UseChatPaneWorkingDirectoryOptions, UseChatPaneWorkingDirectoryResult, } from './chat-pane.js';
|
|
21
|
+
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createMidRunMessagePoster, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
22
|
+
export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAgentToolDef, ChatPaneAgentToolInputSchema, ChatPaneAgentToolRisk, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, CreateDaemonAttachmentUploaderOptions, CreateMcpUiToolCallerOptions, CreateMidRunMessagePosterOptions, CreateTypedAnswerPosterOptions, DeliverMidRunMessage, DeliverTypedAnswer, McpUiToolCallRequest, MidRunMessageDelivery, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, UseChatPaneAgentControlOptions, UseChatPaneOptions, UseChatPaneResult, UseChatPaneRuntimeInventoryOptions, UseChatPaneRuntimeInventoryResult, UseChatPaneWorkingDirectoryOptions, UseChatPaneWorkingDirectoryResult, } from './chat-pane.js';
|
|
23
23
|
export { useRunStream, } from './hooks/useRunStream.js';
|
|
24
24
|
export type { RunStreamState, RunStreamStatus, StartRunOptions, UseRunStreamResult, } from './hooks/useRunStream.js';
|
|
25
25
|
export { useConversation, } from './hooks/useConversation.js';
|
|
@@ -84,6 +84,10 @@ export { appendComposerDiscovery } from './components/composer-discovery.js';
|
|
|
84
84
|
*/
|
|
85
85
|
export { interleaveMessageBlocks } from './message-blocks.js';
|
|
86
86
|
export type { MessageBlock } from './message-blocks.js';
|
|
87
|
+
/** Transcript display modes for tool calls (`ChatPaneProps.toolCallDisplay`, 2026-10-11) — public so
|
|
88
|
+
* a custom message row can apply the same show / collapsed / hidden rules to its own blocks. */
|
|
89
|
+
export { applyToolCallDisplay, isFailedToolRow, summarizeToolGroup, toolGroupLabel, toolRowsForDisplay } from './tool-call-display.js';
|
|
90
|
+
export type { ToolCallDisplay, ToolDisplayRow, ToolGroupSummary } from './tool-call-display.js';
|
|
87
91
|
export { TodoCard } from './components/TodoCard.js';
|
|
88
92
|
export type { TodoCardProps } from './components/TodoCard.js';
|
|
89
93
|
/**
|
|
@@ -111,6 +115,8 @@ export type { A2uiAgentActionOutcome, A2uiSurfaceCardProps } from './components/
|
|
|
111
115
|
*/
|
|
112
116
|
export { McpUiSurfaceCard, mcpUiSurfaceSlotKey, registerMcpUiSurfaceRenderer, MCP_UI_EXT_EVENT_NAME } from './components/McpUiSurfaceCard.js';
|
|
113
117
|
export type { McpUiSurfaceCardProps } from './components/McpUiSurfaceCard.js';
|
|
118
|
+
/** A message sent mid-run (`ChatPane`'s `deliverMidRunMessage`), drawn as a user bubble inside the running turn. */
|
|
119
|
+
export { MidRunUserMessage, midRunUserMessageSlotKey, registerMidRunUserMessageRenderer, MID_RUN_USER_MESSAGE_EXT_EVENT_NAME } from './components/MidRunUserMessage.js';
|
|
114
120
|
export { ExtEventErrorBoundary } from './components/ExtEventErrorBoundary.js';
|
|
115
121
|
export type { ExtEventErrorBoundaryProps } from './components/ExtEventErrorBoundary.js';
|
|
116
122
|
export { QuestionForm } from './components/QuestionForm.js';
|
package/dist/react/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export { clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtE
|
|
|
26
26
|
* This alias has no removal date scheduled yet; see `chat-pane.ts` for the canonical surface and
|
|
27
27
|
* the module doc there for why the split exists.
|
|
28
28
|
*/
|
|
29
|
-
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
29
|
+
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createMidRunMessagePoster, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
30
30
|
export { useRunStream, } from './hooks/useRunStream.js';
|
|
31
31
|
export { useConversation, } from './hooks/useConversation.js';
|
|
32
32
|
export { useComposer, } from './hooks/useComposer.js';
|
|
@@ -78,6 +78,9 @@ export { appendComposerDiscovery } from './components/composer-discovery.js';
|
|
|
78
78
|
* the one live instance §10 found of R10's documented transitive-reach scope limit.
|
|
79
79
|
*/
|
|
80
80
|
export { interleaveMessageBlocks } from './message-blocks.js';
|
|
81
|
+
/** Transcript display modes for tool calls (`ChatPaneProps.toolCallDisplay`, 2026-10-11) — public so
|
|
82
|
+
* a custom message row can apply the same show / collapsed / hidden rules to its own blocks. */
|
|
83
|
+
export { applyToolCallDisplay, isFailedToolRow, summarizeToolGroup, toolGroupLabel, toolRowsForDisplay } from './tool-call-display.js';
|
|
81
84
|
export { TodoCard } from './components/TodoCard.js';
|
|
82
85
|
/**
|
|
83
86
|
* The conversation switcher — new / select / delete / search / rename-on-double-click. Storage
|
|
@@ -99,6 +102,8 @@ export { A2uiSurfaceCard } from './components/A2uiSurfaceCard.js';
|
|
|
99
102
|
* resources instead of A2UI surfaces.
|
|
100
103
|
*/
|
|
101
104
|
export { McpUiSurfaceCard, mcpUiSurfaceSlotKey, registerMcpUiSurfaceRenderer, MCP_UI_EXT_EVENT_NAME } from './components/McpUiSurfaceCard.js';
|
|
105
|
+
/** A message sent mid-run (`ChatPane`'s `deliverMidRunMessage`), drawn as a user bubble inside the running turn. */
|
|
106
|
+
export { MidRunUserMessage, midRunUserMessageSlotKey, registerMidRunUserMessageRenderer, MID_RUN_USER_MESSAGE_EXT_EVENT_NAME } from './components/MidRunUserMessage.js';
|
|
102
107
|
export { ExtEventErrorBoundary } from './components/ExtEventErrorBoundary.js';
|
|
103
108
|
export { QuestionForm } from './components/QuestionForm.js';
|
|
104
109
|
export { QuestionsPanel } from './components/QuestionsPanel.js';
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* so every check below prefers the former.
|
|
36
36
|
*
|
|
37
37
|
* It refuses when:
|
|
38
|
-
* - there are no events, or no tool rows AND no ext events
|
|
39
|
-
* path is identical);
|
|
38
|
+
* - there are no events, or no tool rows AND no ext events (AND no thinking, when the caller asked
|
|
39
|
+
* for it) to interleave (nothing to do — the flat path is identical);
|
|
40
40
|
* - the concatenated text events do not reproduce `content` byte for byte. This is the load-bearing
|
|
41
41
|
* check. A message persisted before the events schema settled, one rebuilt by a host that
|
|
42
42
|
* assembles `content` some other way, or any future divergence between the two, all land here;
|
|
@@ -69,6 +69,21 @@ export type MessageBlock<Row> = {
|
|
|
69
69
|
readonly name: string;
|
|
70
70
|
readonly slot: string;
|
|
71
71
|
readonly key: string;
|
|
72
|
+
}
|
|
73
|
+
/** Where reply `index` of a run ends: just before the mid-run user message that started the next
|
|
74
|
+
* reply (`isReplyBoundary`). `MessageRow` hangs that reply's own "Done" line here. */
|
|
75
|
+
| {
|
|
76
|
+
readonly kind: 'reply-end';
|
|
77
|
+
readonly index: number;
|
|
78
|
+
readonly key: string;
|
|
79
|
+
}
|
|
80
|
+
/** The model's reasoning (`kind: 'thinking'` events), where it happened. Emitted only when the
|
|
81
|
+
* caller passes `includeThinking` (a host's "show thinking" display choice, 2026-10-11);
|
|
82
|
+
* back-to-back thinking events with no text between them join into one block. */
|
|
83
|
+
| {
|
|
84
|
+
readonly kind: 'thinking';
|
|
85
|
+
readonly text: string;
|
|
86
|
+
readonly key: string;
|
|
72
87
|
};
|
|
73
88
|
/**
|
|
74
89
|
* Rebuilds an assistant message as ordered blocks, or returns `null` if that cannot be done
|
|
@@ -79,6 +94,9 @@ export type MessageBlock<Row> = {
|
|
|
79
94
|
* @param rows - The tool timeline rows, already deduped and paired by `useToolTimeline`. Only rows
|
|
80
95
|
* present here are emitted, so a row the timeline chose to drop stays dropped.
|
|
81
96
|
* @param slotOf - The render slot an ext event belongs to (default: its `name`).
|
|
97
|
+
* @param includeThinking - Emit `thinking` blocks for the model's reasoning (default `false`: thinking
|
|
98
|
+
* events are skipped, the layout every host had before this option). Thinking text is not part of
|
|
99
|
+
* `content`, so it never affects the lossless check below.
|
|
82
100
|
* @returns Ordered blocks, or `null` to signal "render the flat layout instead".
|
|
83
101
|
* @complexity O(n + m) in the event and row counts.
|
|
84
102
|
*/
|
|
@@ -88,10 +106,11 @@ export declare function interleaveMessageBlocks<Row extends {
|
|
|
88
106
|
events: readonly AgentEvent[] | undefined;
|
|
89
107
|
content: string;
|
|
90
108
|
rows: readonly Row[];
|
|
91
|
-
}, { slotOf }?: {
|
|
109
|
+
}, { slotOf, includeThinking }?: {
|
|
92
110
|
slotOf?: (ev: {
|
|
93
111
|
readonly name: string;
|
|
94
112
|
readonly data: unknown;
|
|
95
113
|
}) => string;
|
|
114
|
+
includeThinking?: boolean;
|
|
96
115
|
}): MessageBlock<Row>[] | null;
|
|
97
116
|
//# sourceMappingURL=message-blocks.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assistantContentFromEvents, legacyAssistantContentFromEvents } from '../core/index.js';
|
|
1
|
+
import { assistantContentFromEvents, isReplyBoundary, legacyAssistantContentFromEvents } from '../core/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Rebuilds an assistant message as ordered blocks, or returns `null` if that cannot be done
|
|
4
4
|
* losslessly.
|
|
@@ -8,14 +8,18 @@ import { assistantContentFromEvents, legacyAssistantContentFromEvents } from '..
|
|
|
8
8
|
* @param rows - The tool timeline rows, already deduped and paired by `useToolTimeline`. Only rows
|
|
9
9
|
* present here are emitted, so a row the timeline chose to drop stays dropped.
|
|
10
10
|
* @param slotOf - The render slot an ext event belongs to (default: its `name`).
|
|
11
|
+
* @param includeThinking - Emit `thinking` blocks for the model's reasoning (default `false`: thinking
|
|
12
|
+
* events are skipped, the layout every host had before this option). Thinking text is not part of
|
|
13
|
+
* `content`, so it never affects the lossless check below.
|
|
11
14
|
* @returns Ordered blocks, or `null` to signal "render the flat layout instead".
|
|
12
15
|
* @complexity O(n + m) in the event and row counts.
|
|
13
16
|
*/
|
|
14
|
-
export function interleaveMessageBlocks({ events, content, rows }, { slotOf = (ev) => ev.name } = {}) {
|
|
17
|
+
export function interleaveMessageBlocks({ events, content, rows }, { slotOf = (ev) => ev.name, includeThinking = false } = {}) {
|
|
15
18
|
if (!events || events.length === 0)
|
|
16
19
|
return null;
|
|
17
20
|
const hasExtEvent = events.some((ev) => ev.kind === 'ext');
|
|
18
|
-
|
|
21
|
+
const hasThinking = includeThinking && events.some((ev) => ev.kind === 'thinking' && ev.text.trim() !== '');
|
|
22
|
+
if (rows.length === 0 && !hasExtEvent && !hasThinking)
|
|
19
23
|
return null;
|
|
20
24
|
// The reconstruction is only trustworthy if the text events are demonstrably the source `content`
|
|
21
25
|
// was built from. Anything else and the offsets below are fiction.
|
|
@@ -28,6 +32,7 @@ export function interleaveMessageBlocks({ events, content, rows }, { slotOf = (e
|
|
|
28
32
|
const emitted = new Set();
|
|
29
33
|
const emittedExtSlots = new Set();
|
|
30
34
|
let buffer = '';
|
|
35
|
+
let replies = 0;
|
|
31
36
|
const flushText = () => {
|
|
32
37
|
if (buffer.length === 0)
|
|
33
38
|
return;
|
|
@@ -39,6 +44,23 @@ export function interleaveMessageBlocks({ events, content, rows }, { slotOf = (e
|
|
|
39
44
|
buffer += ev.text;
|
|
40
45
|
continue;
|
|
41
46
|
}
|
|
47
|
+
if (ev.kind === 'thinking') {
|
|
48
|
+
if (!includeThinking)
|
|
49
|
+
continue;
|
|
50
|
+
flushText();
|
|
51
|
+
const last = blocks[blocks.length - 1];
|
|
52
|
+
if (last && last.kind === 'thinking')
|
|
53
|
+
blocks[blocks.length - 1] = { kind: 'thinking', text: last.text + ev.text, key: last.key };
|
|
54
|
+
else
|
|
55
|
+
blocks.push({ kind: 'thinking', text: ev.text, key: `thinking-${blocks.length}` });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (isReplyBoundary({ event: ev })) {
|
|
59
|
+
// Emitted per boundary, before any slot dedupe below, so every reply gets its own end marker.
|
|
60
|
+
flushText();
|
|
61
|
+
blocks.push({ kind: 'reply-end', index: replies, key: `reply-end-${replies}` });
|
|
62
|
+
replies += 1;
|
|
63
|
+
}
|
|
42
64
|
if (ev.kind === 'ext') {
|
|
43
65
|
// One block per SLOT, at its first occurrence — later events in the same slot fold into it
|
|
44
66
|
// (`useExtEventGroups`'s job in `MessageRow.tsx`), not a second block. A slot is the `name`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module tool-call-display
|
|
3
|
+
*
|
|
4
|
+
* How a transcript shows tool calls (owner, 2026-10-11 — the Tovu dock's Chat settings → Display):
|
|
5
|
+
*
|
|
6
|
+
* - `'show'` — every tool card, where it happened. The layout every host had before this.
|
|
7
|
+
* - `'collapsed'` — each turn's tool cards fold into ONE line ("Used 3 tools") that expands on
|
|
8
|
+
* click; while a tool runs the line names it instead ("Running Bash…").
|
|
9
|
+
* - `'hidden'` — no tool cards at all, EXCEPT a failed one: an error must never vanish from the
|
|
10
|
+
* transcript just because the reader asked for a quieter view.
|
|
11
|
+
*
|
|
12
|
+
* Pure rules over `interleaveMessageBlocks`' output (or the flat row list when that refused), kept
|
|
13
|
+
* out of `MessageRow.tsx` so every mode is testable without rendering.
|
|
14
|
+
*/
|
|
15
|
+
import type { ToolStatus } from '../core/index.js';
|
|
16
|
+
import type { MessageBlock } from './message-blocks.js';
|
|
17
|
+
export type ToolCallDisplay = 'show' | 'collapsed' | 'hidden';
|
|
18
|
+
/** The fields of a tool row these rules read — `ToolTimelineRow` satisfies it. */
|
|
19
|
+
export interface ToolDisplayRow {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly status: ToolStatus;
|
|
23
|
+
readonly use?: {
|
|
24
|
+
readonly input: unknown;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether a row is a failure the reader must always see: the tool returned an error, or the run
|
|
29
|
+
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
30
|
+
* @complexity O(1).
|
|
31
|
+
*/
|
|
32
|
+
export declare function isFailedToolRow({ row }: {
|
|
33
|
+
row: Pick<ToolDisplayRow, 'status'>;
|
|
34
|
+
}): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Applies a display mode to an interleaved message.
|
|
37
|
+
*
|
|
38
|
+
* `'collapsed'` merges every `tools` block of one turn into the turn's FIRST `tools` block (its
|
|
39
|
+
* position and key), so the turn shows a single fold-up line. A turn ends at a `reply-end` block —
|
|
40
|
+
* the mid-run user message that starts the next reply — so a message holding several replies keeps
|
|
41
|
+
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
42
|
+
* streaming updates, since later blocks append after it.
|
|
43
|
+
*
|
|
44
|
+
* `'hidden'` keeps only failed rows and drops `tools` blocks left empty.
|
|
45
|
+
*
|
|
46
|
+
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
47
|
+
* @param input.mode - The display mode.
|
|
48
|
+
* @returns New blocks; the input is never mutated. `'show'` returns `blocks` itself.
|
|
49
|
+
* @complexity O(n) in blocks + rows.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyToolCallDisplay<Row extends ToolDisplayRow>({ blocks, mode }: {
|
|
52
|
+
blocks: readonly MessageBlock<Row>[];
|
|
53
|
+
mode: ToolCallDisplay;
|
|
54
|
+
}): readonly MessageBlock<Row>[];
|
|
55
|
+
/**
|
|
56
|
+
* The flat layout's counterpart (used when `interleaveMessageBlocks` refused): `'hidden'` keeps only
|
|
57
|
+
* failed rows; the other modes keep every row (`'collapsed'` folds them into one line at render).
|
|
58
|
+
* @complexity O(n) in rows.
|
|
59
|
+
*/
|
|
60
|
+
export declare function toolRowsForDisplay<Row extends ToolDisplayRow>({ rows, mode }: {
|
|
61
|
+
rows: readonly Row[];
|
|
62
|
+
mode: ToolCallDisplay;
|
|
63
|
+
}): readonly Row[];
|
|
64
|
+
/** What a collapsed tool line says: the tool running now, or how many ran (and how many failed). */
|
|
65
|
+
export type ToolGroupSummary = {
|
|
66
|
+
readonly kind: 'running';
|
|
67
|
+
readonly name: string;
|
|
68
|
+
} | {
|
|
69
|
+
readonly kind: 'done';
|
|
70
|
+
readonly count: number;
|
|
71
|
+
readonly failed: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Summarises one turn's tool rows. The LAST running row wins, so a line over several in-flight calls
|
|
75
|
+
* names the newest one, matching what the run is doing now.
|
|
76
|
+
* @complexity O(n) in rows.
|
|
77
|
+
*/
|
|
78
|
+
export declare function summarizeToolGroup({ rows }: {
|
|
79
|
+
rows: readonly ToolDisplayRow[];
|
|
80
|
+
}): ToolGroupSummary;
|
|
81
|
+
/**
|
|
82
|
+
* The collapsed line's text. Each phrase is its own translation key (the English string is the key,
|
|
83
|
+
* see `slots.ts`), with `{count}`/`{name}` filled by the translator.
|
|
84
|
+
* @complexity O(1).
|
|
85
|
+
*/
|
|
86
|
+
export declare function toolGroupLabel({ summary, t }: {
|
|
87
|
+
summary: ToolGroupSummary;
|
|
88
|
+
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
89
|
+
}): string;
|
|
90
|
+
//# sourceMappingURL=tool-call-display.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { humanizeToolName } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a row is a failure the reader must always see: the tool returned an error, or the run
|
|
4
|
+
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
5
|
+
* @complexity O(1).
|
|
6
|
+
*/
|
|
7
|
+
export function isFailedToolRow({ row }) {
|
|
8
|
+
return row.status === 'error';
|
|
9
|
+
}
|
|
10
|
+
/** Running = no result yet while the run streams (`'executing'`), or not started (`'inProgress'`). */
|
|
11
|
+
function isRunningToolRow(row) {
|
|
12
|
+
return row.status === 'executing' || row.status === 'inProgress';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Applies a display mode to an interleaved message.
|
|
16
|
+
*
|
|
17
|
+
* `'collapsed'` merges every `tools` block of one turn into the turn's FIRST `tools` block (its
|
|
18
|
+
* position and key), so the turn shows a single fold-up line. A turn ends at a `reply-end` block —
|
|
19
|
+
* the mid-run user message that starts the next reply — so a message holding several replies keeps
|
|
20
|
+
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
21
|
+
* streaming updates, since later blocks append after it.
|
|
22
|
+
*
|
|
23
|
+
* `'hidden'` keeps only failed rows and drops `tools` blocks left empty.
|
|
24
|
+
*
|
|
25
|
+
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
26
|
+
* @param input.mode - The display mode.
|
|
27
|
+
* @returns New blocks; the input is never mutated. `'show'` returns `blocks` itself.
|
|
28
|
+
* @complexity O(n) in blocks + rows.
|
|
29
|
+
*/
|
|
30
|
+
export function applyToolCallDisplay({ blocks, mode }) {
|
|
31
|
+
if (mode === 'show')
|
|
32
|
+
return blocks;
|
|
33
|
+
if (mode === 'hidden') {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const block of blocks) {
|
|
36
|
+
if (block.kind !== 'tools') {
|
|
37
|
+
out.push(block);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const failed = block.rows.filter((row) => isFailedToolRow({ row }));
|
|
41
|
+
if (failed.length > 0)
|
|
42
|
+
out.push({ kind: 'tools', rows: failed, key: block.key });
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
const out = [];
|
|
47
|
+
let turnToolsIndex = -1;
|
|
48
|
+
for (const block of blocks) {
|
|
49
|
+
if (block.kind === 'reply-end')
|
|
50
|
+
turnToolsIndex = -1;
|
|
51
|
+
if (block.kind !== 'tools') {
|
|
52
|
+
out.push(block);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const first = turnToolsIndex === -1 ? undefined : out[turnToolsIndex];
|
|
56
|
+
if (first && first.kind === 'tools') {
|
|
57
|
+
out[turnToolsIndex] = { kind: 'tools', rows: [...first.rows, ...block.rows], key: first.key };
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
turnToolsIndex = out.length;
|
|
61
|
+
out.push(block);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The flat layout's counterpart (used when `interleaveMessageBlocks` refused): `'hidden'` keeps only
|
|
67
|
+
* failed rows; the other modes keep every row (`'collapsed'` folds them into one line at render).
|
|
68
|
+
* @complexity O(n) in rows.
|
|
69
|
+
*/
|
|
70
|
+
export function toolRowsForDisplay({ rows, mode }) {
|
|
71
|
+
return mode === 'hidden' ? rows.filter((row) => isFailedToolRow({ row })) : rows;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Summarises one turn's tool rows. The LAST running row wins, so a line over several in-flight calls
|
|
75
|
+
* names the newest one, matching what the run is doing now.
|
|
76
|
+
* @complexity O(n) in rows.
|
|
77
|
+
*/
|
|
78
|
+
export function summarizeToolGroup({ rows }) {
|
|
79
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
80
|
+
const row = rows[i];
|
|
81
|
+
if (isRunningToolRow(row))
|
|
82
|
+
return { kind: 'running', name: humanizeToolName({ name: row.name }, { input: row.use?.input }) };
|
|
83
|
+
}
|
|
84
|
+
return { kind: 'done', count: rows.length, failed: rows.filter((row) => isFailedToolRow({ row })).length };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The collapsed line's text. Each phrase is its own translation key (the English string is the key,
|
|
88
|
+
* see `slots.ts`), with `{count}`/`{name}` filled by the translator.
|
|
89
|
+
* @complexity O(1).
|
|
90
|
+
*/
|
|
91
|
+
export function toolGroupLabel({ summary, t }) {
|
|
92
|
+
if (summary.kind === 'running')
|
|
93
|
+
return t('Running {name}…', { name: summary.name });
|
|
94
|
+
const used = summary.count === 1 ? t('Used 1 tool') : t('Used {count} tools', { count: summary.count });
|
|
95
|
+
return summary.failed > 0 ? `${used} · ${t('{count} failed', { count: summary.failed })}` : used;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=tool-call-display.js.map
|