@jini-ai/chat 0.6.6 → 0.6.10
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 +129 -1
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +187 -98
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +75 -47
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +19 -9
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +53 -24
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +18 -16
|
@@ -1,4 +1,84 @@
|
|
|
1
1
|
import { assistantContentFromEvents, isReplyBoundary, legacyAssistantContentFromEvents } from '../core/index.js';
|
|
2
|
+
/** Whether there is anything besides plain text to place: a tool row, an ext event, or (when asked
|
|
3
|
+
* for) non-blank thinking. With none of these the flat path is identical. */
|
|
4
|
+
function hasSomethingToInterleave({ events, rows }, { includeThinking }) {
|
|
5
|
+
if (rows.length > 0)
|
|
6
|
+
return true;
|
|
7
|
+
return events.some((ev) => ev.kind === 'ext' || (includeThinking && ev.kind === 'thinking' && ev.text.trim() !== ''));
|
|
8
|
+
}
|
|
9
|
+
/** Whether `content` is demonstrably built from the text events. */
|
|
10
|
+
function contentMatchesEvents({ events, content }) {
|
|
11
|
+
// Both producers' shapes count: today's (a paragraph break between steps, see
|
|
12
|
+
// `assistantContentFromEvents`) and the old plain concatenation older saved rows carry.
|
|
13
|
+
return content === assistantContentFromEvents({ events }) || content === legacyAssistantContentFromEvents({ events });
|
|
14
|
+
}
|
|
15
|
+
function flushText({ state }) {
|
|
16
|
+
if (state.buffer.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
state.blocks.push({ kind: 'text', text: state.buffer, key: `text-${state.blocks.length}` });
|
|
19
|
+
state.buffer = '';
|
|
20
|
+
}
|
|
21
|
+
function placeThinking({ state, text }) {
|
|
22
|
+
flushText({ state });
|
|
23
|
+
const { blocks } = state;
|
|
24
|
+
const last = blocks[blocks.length - 1];
|
|
25
|
+
if (last && last.kind === 'thinking')
|
|
26
|
+
blocks[blocks.length - 1] = { kind: 'thinking', text: last.text + text, key: last.key };
|
|
27
|
+
else
|
|
28
|
+
blocks.push({ kind: 'thinking', text, key: `thinking-${blocks.length}` });
|
|
29
|
+
}
|
|
30
|
+
function placeReplyEnd({ state }) {
|
|
31
|
+
flushText({ state });
|
|
32
|
+
state.blocks.push({ kind: 'reply-end', index: state.replies, key: `reply-end-${state.replies}` });
|
|
33
|
+
state.replies += 1;
|
|
34
|
+
}
|
|
35
|
+
function placeExt({ state, ev }, { slotOf }) {
|
|
36
|
+
// One block per SLOT, at its first occurrence — later events in the same slot fold into it
|
|
37
|
+
// (`useExtEventGroups`'s job in `MessageRow.tsx`), not a second block. A slot is the `name`
|
|
38
|
+
// unless its renderer keys events into separate surfaces (see `ExtEventSlotKey`).
|
|
39
|
+
const slot = slotOf(ev);
|
|
40
|
+
if (state.emittedExtSlots.has(slot))
|
|
41
|
+
return;
|
|
42
|
+
state.emittedExtSlots.add(slot);
|
|
43
|
+
flushText({ state });
|
|
44
|
+
state.blocks.push({ kind: 'ext', name: ev.name, slot, key: `ext-${slot}` });
|
|
45
|
+
}
|
|
46
|
+
function placeToolUse({ state, id }) {
|
|
47
|
+
const row = state.rowById.get(id);
|
|
48
|
+
// A `tool_use` the timeline did not surface (deduped away as a repeat of one already seen).
|
|
49
|
+
// Skipping it keeps this function's output consistent with `rows`, which is the authority on
|
|
50
|
+
// what gets a card.
|
|
51
|
+
if (!row || state.emitted.has(id))
|
|
52
|
+
return;
|
|
53
|
+
state.emitted.add(id);
|
|
54
|
+
flushText({ state });
|
|
55
|
+
const { blocks } = state;
|
|
56
|
+
const last = blocks[blocks.length - 1];
|
|
57
|
+
if (last && last.kind === 'tools') {
|
|
58
|
+
blocks[blocks.length - 1] = { kind: 'tools', rows: [...last.rows, row], key: last.key };
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
blocks.push({ kind: 'tools', rows: [row], key: `tools-${blocks.length}` });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function placeEvent({ state, ev }, { slotOf, includeThinking }) {
|
|
65
|
+
if (ev.kind === 'text') {
|
|
66
|
+
state.buffer += ev.text;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (ev.kind === 'thinking') {
|
|
70
|
+
if (includeThinking)
|
|
71
|
+
placeThinking({ state, text: ev.text });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Emitted per boundary, before any slot dedupe below, so every reply gets its own end marker.
|
|
75
|
+
if (isReplyBoundary({ event: ev }))
|
|
76
|
+
placeReplyEnd({ state });
|
|
77
|
+
if (ev.kind === 'ext')
|
|
78
|
+
placeExt({ state, ev }, { slotOf });
|
|
79
|
+
else if (ev.kind === 'tool_use')
|
|
80
|
+
placeToolUse({ state, id: ev.id });
|
|
81
|
+
}
|
|
2
82
|
/**
|
|
3
83
|
* Rebuilds an assistant message as ordered blocks, or returns `null` if that cannot be done
|
|
4
84
|
* losslessly.
|
|
@@ -17,85 +97,27 @@ import { assistantContentFromEvents, isReplyBoundary, legacyAssistantContentFrom
|
|
|
17
97
|
export function interleaveMessageBlocks({ events, content, rows }, { slotOf = (ev) => ev.name, includeThinking = false } = {}) {
|
|
18
98
|
if (!events || events.length === 0)
|
|
19
99
|
return null;
|
|
20
|
-
|
|
21
|
-
const hasThinking = includeThinking && events.some((ev) => ev.kind === 'thinking' && ev.text.trim() !== '');
|
|
22
|
-
if (rows.length === 0 && !hasExtEvent && !hasThinking)
|
|
100
|
+
if (!hasSomethingToInterleave({ events, rows }, { includeThinking }))
|
|
23
101
|
return null;
|
|
24
102
|
// The reconstruction is only trustworthy if the text events are demonstrably the source `content`
|
|
25
103
|
// was built from. Anything else and the offsets below are fiction.
|
|
26
|
-
|
|
27
|
-
// `assistantContentFromEvents`) and the old plain concatenation older saved rows carry.
|
|
28
|
-
if (content !== assistantContentFromEvents({ events }) && content !== legacyAssistantContentFromEvents({ events: events }))
|
|
104
|
+
if (!contentMatchesEvents({ events, content }))
|
|
29
105
|
return null;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (buffer.length === 0)
|
|
38
|
-
return;
|
|
39
|
-
blocks.push({ kind: 'text', text: buffer, key: `text-${blocks.length}` });
|
|
40
|
-
buffer = '';
|
|
106
|
+
const state = {
|
|
107
|
+
rowById: new Map(rows.map((row) => [row.id, row])),
|
|
108
|
+
blocks: [],
|
|
109
|
+
emitted: new Set(),
|
|
110
|
+
emittedExtSlots: new Set(),
|
|
111
|
+
buffer: '',
|
|
112
|
+
replies: 0,
|
|
41
113
|
};
|
|
42
|
-
for (const ev of events)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
continue;
|
|
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
|
-
}
|
|
64
|
-
if (ev.kind === 'ext') {
|
|
65
|
-
// One block per SLOT, at its first occurrence — later events in the same slot fold into it
|
|
66
|
-
// (`useExtEventGroups`'s job in `MessageRow.tsx`), not a second block. A slot is the `name`
|
|
67
|
-
// unless its renderer keys events into separate surfaces (see `ExtEventSlotKey`).
|
|
68
|
-
const slot = slotOf(ev);
|
|
69
|
-
if (emittedExtSlots.has(slot))
|
|
70
|
-
continue;
|
|
71
|
-
emittedExtSlots.add(slot);
|
|
72
|
-
flushText();
|
|
73
|
-
blocks.push({ kind: 'ext', name: ev.name, slot, key: `ext-${slot}` });
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (ev.kind !== 'tool_use')
|
|
77
|
-
continue;
|
|
78
|
-
const row = rowById.get(ev.id);
|
|
79
|
-
// A `tool_use` the timeline did not surface (deduped away as a repeat of one already seen).
|
|
80
|
-
// Skipping it keeps this function's output consistent with `rows`, which is the authority on
|
|
81
|
-
// what gets a card.
|
|
82
|
-
if (!row || emitted.has(ev.id))
|
|
83
|
-
continue;
|
|
84
|
-
emitted.add(ev.id);
|
|
85
|
-
flushText();
|
|
86
|
-
const last = blocks[blocks.length - 1];
|
|
87
|
-
if (last && last.kind === 'tools') {
|
|
88
|
-
blocks[blocks.length - 1] = { kind: 'tools', rows: [...last.rows, row], key: last.key };
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
blocks.push({ kind: 'tools', rows: [row], key: `tools-${blocks.length}` });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
flushText();
|
|
114
|
+
for (const ev of events)
|
|
115
|
+
placeEvent({ state, ev }, { slotOf, includeThinking });
|
|
116
|
+
flushText({ state });
|
|
95
117
|
// Every row must have found its place. If one did not, the message would render with a tool card
|
|
96
118
|
// missing entirely — strictly worse than the flat layout it would have had.
|
|
97
|
-
if (emitted.size !== rows.length)
|
|
119
|
+
if (state.emitted.size !== rows.length)
|
|
98
120
|
return null;
|
|
99
|
-
return blocks;
|
|
121
|
+
return state.blocks;
|
|
100
122
|
}
|
|
101
123
|
//# sourceMappingURL=message-blocks.js.map
|
package/dist/react/slots.d.ts
CHANGED
|
@@ -179,8 +179,8 @@ export interface ComposerSlots {
|
|
|
179
179
|
* Host controls rendered at the START of the footer row, immediately after the attach/discovery
|
|
180
180
|
* "+" trigger — for a control that belongs with the composer's other action-row buttons (e.g. a
|
|
181
181
|
* push-to-talk mic button) rather than in {@link leadingAccessories}'s pinned-context zone above
|
|
182
|
-
* the input, or in {@link footerAccessories}'s
|
|
183
|
-
*
|
|
182
|
+
* the input, or in {@link footerAccessories}'s slot (reserved for an agent/model picker, which
|
|
183
|
+
* follows this one; only the send group is pushed to the row's far end).
|
|
184
184
|
*/
|
|
185
185
|
footerLeadingAccessory?: ReactNode;
|
|
186
186
|
onAttach?: (a: ChatAttachment) => void;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - `'show'` — every tool card, where it happened. The layout every host had before this.
|
|
7
7
|
* - `'collapsed'` — each turn's tool cards fold into ONE line ("Used 3 tools") that expands on
|
|
8
8
|
* click; while a tool runs the line names it instead ("Running Bash…").
|
|
9
|
-
* - `'hidden'` —
|
|
10
|
-
*
|
|
9
|
+
* - `'hidden'` — recovered failures disappear with successful calls. Unrecovered failures stay
|
|
10
|
+
* behind one compact disclosure per reply: work that stopped must never vanish silently.
|
|
11
11
|
*
|
|
12
12
|
* Pure rules over `interleaveMessageBlocks`' output (or the flat row list when that refused), kept
|
|
13
13
|
* out of `MessageRow.tsx` so every mode is testable without rendering.
|
|
@@ -25,7 +25,7 @@ export interface ToolDisplayRow {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Whether a row
|
|
28
|
+
* Whether a row failed: the tool returned an error, or the run
|
|
29
29
|
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
30
30
|
* @complexity O(1).
|
|
31
31
|
*/
|
|
@@ -41,7 +41,8 @@ export declare function isFailedToolRow({ row }: {
|
|
|
41
41
|
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
42
42
|
* streaming updates, since later blocks append after it.
|
|
43
43
|
*
|
|
44
|
-
* `'hidden'` keeps
|
|
44
|
+
* `'hidden'` keeps unrecovered failures in one block per reply. Later assistant text or a successful
|
|
45
|
+
* tool proves recovery; running tools and reasoning do not. An errored final reply keeps its failures.
|
|
45
46
|
*
|
|
46
47
|
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
47
48
|
* @param input.mode - The display mode.
|
|
@@ -51,15 +52,20 @@ export declare function isFailedToolRow({ row }: {
|
|
|
51
52
|
export declare function applyToolCallDisplay<Row extends ToolDisplayRow>({ blocks, mode }: {
|
|
52
53
|
blocks: readonly MessageBlock<Row>[];
|
|
53
54
|
mode: ToolCallDisplay;
|
|
55
|
+
}, { runFailed }?: {
|
|
56
|
+
runFailed?: boolean;
|
|
54
57
|
}): readonly MessageBlock<Row>[];
|
|
55
58
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
59
|
+
* Row-level recovery scan, also used by the flat fallback. Without trustworthy text ordering,
|
|
60
|
+
* only later successful rows prove recovery. Other modes keep every row.
|
|
58
61
|
* @complexity O(n) in rows.
|
|
59
62
|
*/
|
|
60
63
|
export declare function toolRowsForDisplay<Row extends ToolDisplayRow>({ rows, mode }: {
|
|
61
64
|
rows: readonly Row[];
|
|
62
65
|
mode: ToolCallDisplay;
|
|
66
|
+
}, { recovered, runFailed }?: {
|
|
67
|
+
recovered?: boolean;
|
|
68
|
+
runFailed?: boolean;
|
|
63
69
|
}): readonly Row[];
|
|
64
70
|
/** What a collapsed tool line says: the tool running now, or how many ran (and how many failed). */
|
|
65
71
|
export type ToolGroupSummary = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { humanizeToolName } from '../core/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* Whether a row
|
|
3
|
+
* Whether a row failed: the tool returned an error, or the run
|
|
4
4
|
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
5
5
|
* @complexity O(1).
|
|
6
6
|
*/
|
|
@@ -20,55 +20,84 @@ function isRunningToolRow(row) {
|
|
|
20
20
|
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
21
21
|
* streaming updates, since later blocks append after it.
|
|
22
22
|
*
|
|
23
|
-
* `'hidden'` keeps
|
|
23
|
+
* `'hidden'` keeps unrecovered failures in one block per reply. Later assistant text or a successful
|
|
24
|
+
* tool proves recovery; running tools and reasoning do not. An errored final reply keeps its failures.
|
|
24
25
|
*
|
|
25
26
|
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
26
27
|
* @param input.mode - The display mode.
|
|
27
28
|
* @returns New blocks; the input is never mutated. `'show'` returns `blocks` itself.
|
|
28
29
|
* @complexity O(n) in blocks + rows.
|
|
29
30
|
*/
|
|
30
|
-
export function applyToolCallDisplay({ blocks, mode }) {
|
|
31
|
+
export function applyToolCallDisplay({ blocks, mode }, { runFailed = false } = {}) {
|
|
31
32
|
if (mode === 'show')
|
|
32
33
|
return blocks;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
return mode === 'hidden' ? hideToolCalls({ blocks, runFailed }) : collapseToolCalls({ blocks });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Scan backwards so only later content in the SAME reply can prove recovery. Failures stay visible
|
|
38
|
+
* while streaming until that evidence arrives. This is a display filter, never a deletion from the
|
|
39
|
+
* durable events: see assistant-client/transport.ts's forensics rationale.
|
|
40
|
+
* @complexity O(n) in blocks + rows, excluding the shared grouping pass.
|
|
41
|
+
*/
|
|
42
|
+
function hideToolCalls({ blocks, runFailed }) {
|
|
43
|
+
const out = [];
|
|
44
|
+
let recovered = false;
|
|
45
|
+
let failedReply = runFailed;
|
|
46
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
47
|
+
const block = blocks[i];
|
|
48
|
+
if (block.kind === 'reply-end') {
|
|
49
|
+
recovered = false;
|
|
50
|
+
failedReply = false;
|
|
51
|
+
}
|
|
52
|
+
if (block.kind === 'text' && block.text.trim())
|
|
53
|
+
recovered = true;
|
|
54
|
+
if (block.kind !== 'tools') {
|
|
55
|
+
out.push(block);
|
|
56
|
+
continue;
|
|
43
57
|
}
|
|
44
|
-
|
|
58
|
+
const rows = toolRowsForDisplay({ rows: block.rows, mode: 'hidden' }, { recovered, runFailed: failedReply });
|
|
59
|
+
if (rows.length > 0)
|
|
60
|
+
out.push({ ...block, rows });
|
|
61
|
+
if (block.rows.some((row) => row.status === 'complete'))
|
|
62
|
+
recovered = true;
|
|
45
63
|
}
|
|
64
|
+
return collapseToolCalls({ blocks: out.reverse() });
|
|
65
|
+
}
|
|
66
|
+
/** `'collapsed'`: merges every `tools` block of a turn into the turn's first one (see above). */
|
|
67
|
+
function collapseToolCalls({ blocks }) {
|
|
46
68
|
const out = [];
|
|
47
69
|
let turnToolsIndex = -1;
|
|
48
70
|
for (const block of blocks) {
|
|
49
71
|
if (block.kind === 'reply-end')
|
|
50
72
|
turnToolsIndex = -1;
|
|
51
|
-
if (block.kind !== 'tools') {
|
|
52
|
-
out.push(block);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
73
|
const first = turnToolsIndex === -1 ? undefined : out[turnToolsIndex];
|
|
56
|
-
if (
|
|
74
|
+
if (block.kind === 'tools' && first?.kind === 'tools') {
|
|
57
75
|
out[turnToolsIndex] = { kind: 'tools', rows: [...first.rows, ...block.rows], key: first.key };
|
|
58
76
|
continue;
|
|
59
77
|
}
|
|
60
|
-
|
|
78
|
+
if (block.kind === 'tools')
|
|
79
|
+
turnToolsIndex = out.length;
|
|
61
80
|
out.push(block);
|
|
62
81
|
}
|
|
63
82
|
return out;
|
|
64
83
|
}
|
|
65
84
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
85
|
+
* Row-level recovery scan, also used by the flat fallback. Without trustworthy text ordering,
|
|
86
|
+
* only later successful rows prove recovery. Other modes keep every row.
|
|
68
87
|
* @complexity O(n) in rows.
|
|
69
88
|
*/
|
|
70
|
-
export function toolRowsForDisplay({ rows, mode }) {
|
|
71
|
-
|
|
89
|
+
export function toolRowsForDisplay({ rows, mode }, { recovered = false, runFailed = false } = {}) {
|
|
90
|
+
if (mode !== 'hidden')
|
|
91
|
+
return rows;
|
|
92
|
+
const failed = [];
|
|
93
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
94
|
+
const row = rows[i];
|
|
95
|
+
if (isFailedToolRow({ row }) && (runFailed || !recovered))
|
|
96
|
+
failed.push(row);
|
|
97
|
+
if (row.status === 'complete')
|
|
98
|
+
recovered = true;
|
|
99
|
+
}
|
|
100
|
+
return failed.reverse();
|
|
72
101
|
}
|
|
73
102
|
/**
|
|
74
103
|
* Summarises one turn's tool rows. The LAST running row wins, so a line over several in-flight calls
|