@pikku/assistant-ui 0.12.4 → 0.12.5
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 +6 -1
- package/dist/cjs/pikku-agent-chat.d.ts +4 -0
- package/dist/cjs/pikku-agent-chat.js +91 -63
- package/dist/cjs/use-pikku-agent-runtime.js +70 -19
- package/dist/esm/pikku-agent-chat.d.ts +4 -0
- package/dist/esm/pikku-agent-chat.js +102 -67
- package/dist/esm/use-pikku-agent-runtime.js +70 -19
- package/package.json +1 -1
- package/src/pikku-agent-chat.tsx +362 -156
- package/src/use-pikku-agent-runtime.ts +103 -15
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState, useMemo, } from 'react';
|
|
2
|
+
import { createContext, useContext, useState, useMemo, useEffect, useRef, } from 'react';
|
|
3
3
|
import Markdown from 'react-markdown';
|
|
4
|
-
import { AssistantRuntimeProvider, ThreadPrimitive, MessagePrimitive, ComposerPrimitive, } from '@assistant-ui/react';
|
|
4
|
+
import { AssistantRuntimeProvider, ThreadPrimitive, MessagePrimitive, ComposerPrimitive, useComposerRuntime, } from '@assistant-ui/react';
|
|
5
5
|
import { usePikkuAgentRuntime, PikkuApprovalContext, usePikkuApproval, resolvePikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
6
6
|
const lightColors = {
|
|
7
7
|
bg: '#ffffff',
|
|
@@ -46,6 +46,8 @@ const darkColors = {
|
|
|
46
46
|
const ColorsContext = createContext(lightColors);
|
|
47
47
|
const HideToolCallsContext = createContext(undefined);
|
|
48
48
|
const ToolComponentsContext = createContext(undefined);
|
|
49
|
+
const GenerativeUIComponentsContext = createContext(undefined);
|
|
50
|
+
const RenderAssistantTextContext = createContext(undefined);
|
|
49
51
|
function shouldHideToolCall(hideToolCalls, toolName) {
|
|
50
52
|
if (!hideToolCalls)
|
|
51
53
|
return false;
|
|
@@ -296,6 +298,8 @@ const UserMessage = () => {
|
|
|
296
298
|
const AssistantMessage = () => {
|
|
297
299
|
const colors = useContext(ColorsContext);
|
|
298
300
|
const toolComponents = useContext(ToolComponentsContext);
|
|
301
|
+
const generativeUIComponents = useContext(GenerativeUIComponentsContext);
|
|
302
|
+
const renderAssistantText = useContext(RenderAssistantTextContext);
|
|
299
303
|
return (_jsx("div", { style: {
|
|
300
304
|
display: 'flex',
|
|
301
305
|
justifyContent: 'flex-start',
|
|
@@ -305,11 +309,18 @@ const AssistantMessage = () => {
|
|
|
305
309
|
borderRadius: 12,
|
|
306
310
|
backgroundColor: colors.assistantBubble,
|
|
307
311
|
}, children: [_jsx(MessagePrimitive.Content, { components: {
|
|
308
|
-
Text: ({ text }) => _jsx(MarkdownText, { text: text, colors: colors }),
|
|
312
|
+
Text: ({ text }) => renderAssistantText ? (_jsx(_Fragment, { children: renderAssistantText(text) })) : (_jsx(MarkdownText, { text: text, colors: colors })),
|
|
309
313
|
tools: {
|
|
310
314
|
by_name: toolComponents,
|
|
311
315
|
Fallback: (props) => (_jsx(ToolCallDisplay, { toolCallId: props.toolCallId, toolName: props.toolName, args: props.args, result: props.result, status: resolvePikkuToolStatus(props.status, props.result), addResult: props.addResult })),
|
|
312
316
|
},
|
|
317
|
+
...(generativeUIComponents
|
|
318
|
+
? {
|
|
319
|
+
generativeUI: {
|
|
320
|
+
components: generativeUIComponents,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
: {}),
|
|
313
324
|
} }), _jsx(MessagePrimitive.If, { last: true, children: _jsx(ThreadPrimitive.If, { running: true, children: _jsx("div", { style: {
|
|
314
325
|
display: 'flex',
|
|
315
326
|
alignItems: 'center',
|
|
@@ -319,88 +330,112 @@ const AssistantMessage = () => {
|
|
|
319
330
|
color: colors.textMuted,
|
|
320
331
|
}, children: "Thinking..." }) }) })] })] }) }));
|
|
321
332
|
};
|
|
333
|
+
const ComposerPrefill = ({ text }) => {
|
|
334
|
+
const composer = useComposerRuntime();
|
|
335
|
+
const filled = useRef(false);
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (filled.current || !text)
|
|
338
|
+
return;
|
|
339
|
+
filled.current = true;
|
|
340
|
+
if (composer.getState().text === '')
|
|
341
|
+
composer.setText(text);
|
|
342
|
+
}, [text, composer]);
|
|
343
|
+
return null;
|
|
344
|
+
};
|
|
322
345
|
const PikkuComposer = ({ disabled, }) => {
|
|
323
346
|
const colors = useContext(ColorsContext);
|
|
324
347
|
return (_jsx("div", { style: { padding: '8px 0 16px' }, children: _jsx(ComposerPrimitive.Root, { children: _jsxs("div", { style: {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
overflow: 'hidden',
|
|
348
|
+
position: 'relative',
|
|
349
|
+
zIndex: 2,
|
|
328
350
|
display: 'flex',
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
351
|
+
flexDirection: 'column',
|
|
352
|
+
gap: 10,
|
|
353
|
+
backgroundColor: colors.assistantBubble,
|
|
354
|
+
border: `1px solid ${colors.border}`,
|
|
355
|
+
borderRadius: 24,
|
|
356
|
+
padding: '14px 12px 10px',
|
|
357
|
+
boxShadow: darkColors.text === colors.text
|
|
358
|
+
? '0 14px 30px rgba(0,0,0,0.24)'
|
|
359
|
+
: '0 14px 30px rgba(0,0,0,0.08)',
|
|
332
360
|
...(disabled
|
|
333
361
|
? { opacity: 0.5, pointerEvents: 'none' }
|
|
334
362
|
: {}),
|
|
335
|
-
}, children: [_jsx(ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows:
|
|
336
|
-
|
|
363
|
+
}, children: [_jsx(ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 1, disabled: disabled ?? false, style: {
|
|
364
|
+
width: '100%',
|
|
365
|
+
background: 'transparent',
|
|
337
366
|
border: 'none',
|
|
338
367
|
outline: 'none',
|
|
339
|
-
|
|
368
|
+
color: colors.text,
|
|
340
369
|
fontSize: 14,
|
|
341
370
|
fontFamily: 'inherit',
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} }), _jsx(
|
|
346
|
-
width:
|
|
347
|
-
height: 28,
|
|
348
|
-
borderRadius: '50%',
|
|
349
|
-
border: 'none',
|
|
350
|
-
background: disabled ? colors.textMuted : colors.sendBg,
|
|
351
|
-
color: colors.sendColor,
|
|
352
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
371
|
+
resize: 'none',
|
|
372
|
+
lineHeight: 1.5,
|
|
373
|
+
overflowY: 'auto',
|
|
374
|
+
} }), _jsx("div", { style: {
|
|
375
|
+
width: '100%',
|
|
353
376
|
display: 'flex',
|
|
354
377
|
alignItems: 'center',
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
378
|
+
gap: 6,
|
|
379
|
+
minWidth: 0,
|
|
380
|
+
justifyContent: 'flex-end',
|
|
381
|
+
}, children: _jsx(ComposerPrimitive.Send, { disabled: disabled ?? false, style: {
|
|
382
|
+
width: 30,
|
|
383
|
+
height: 30,
|
|
384
|
+
borderRadius: 999,
|
|
385
|
+
border: `1px solid ${colors.border}`,
|
|
386
|
+
background: '#e8e8e8',
|
|
387
|
+
color: '#111111',
|
|
388
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
389
|
+
display: 'flex',
|
|
390
|
+
alignItems: 'center',
|
|
391
|
+
justifyContent: 'center',
|
|
392
|
+
flexShrink: 0,
|
|
393
|
+
transition: 'background-color 150ms ease, color 150ms ease, border-color 150ms ease',
|
|
394
|
+
}, children: "\u2191" }) })] }) }) }));
|
|
360
395
|
};
|
|
361
396
|
export function PikkuAgentChat(props) {
|
|
362
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, ...runtimeOptions } = props;
|
|
397
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, renderAssistantText, generativeUIComponents, initialPrompt, ...runtimeOptions } = props;
|
|
363
398
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = usePikkuAgentRuntime(runtimeOptions);
|
|
364
399
|
const colors = dark ? darkColors : lightColors;
|
|
365
|
-
return (_jsx(ColorsContext.Provider, { value: colors, children: _jsx(PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: _jsx(HideToolCallsContext.Provider, { value: hideToolCalls, children: _jsx(ToolComponentsContext.Provider, { value: toolComponents, children: _jsx(AssistantRuntimeProvider, { runtime: runtime, children: _jsx("div", { style: {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
display: 'flex',
|
|
372
|
-
flexDirection: 'column',
|
|
373
|
-
flex: 1,
|
|
374
|
-
minHeight: 0,
|
|
375
|
-
}, children: [_jsx(ThreadPrimitive.Viewport, { style: {
|
|
376
|
-
flex: 1,
|
|
377
|
-
minHeight: 0,
|
|
378
|
-
overflowY: 'auto',
|
|
379
|
-
}, children: _jsxs("div", { style: {
|
|
380
|
-
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
381
|
-
margin: '0 auto',
|
|
382
|
-
padding: 16,
|
|
400
|
+
return (_jsx(ColorsContext.Provider, { value: colors, children: _jsx(PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: _jsx(HideToolCallsContext.Provider, { value: hideToolCalls, children: _jsx(ToolComponentsContext.Provider, { value: toolComponents, children: _jsx(GenerativeUIComponentsContext.Provider, { value: generativeUIComponents, children: _jsx(RenderAssistantTextContext.Provider, { value: renderAssistantText, children: _jsxs(AssistantRuntimeProvider, { runtime: runtime, children: [_jsx(ComposerPrefill, { text: initialPrompt }), _jsx("div", { style: {
|
|
401
|
+
height: '100%',
|
|
402
|
+
display: 'flex',
|
|
403
|
+
flexDirection: 'column',
|
|
404
|
+
background: colors.bg,
|
|
405
|
+
}, children: _jsxs(ThreadPrimitive.Root, { style: {
|
|
383
406
|
display: 'flex',
|
|
384
407
|
flexDirection: 'column',
|
|
385
|
-
|
|
386
|
-
|
|
408
|
+
flex: 1,
|
|
409
|
+
minHeight: 0,
|
|
410
|
+
}, children: [_jsx(ThreadPrimitive.Viewport, { style: {
|
|
411
|
+
flex: 1,
|
|
412
|
+
minHeight: 0,
|
|
413
|
+
overflowY: 'auto',
|
|
414
|
+
}, children: _jsxs("div", { style: {
|
|
415
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
416
|
+
margin: '0 auto',
|
|
417
|
+
padding: 16,
|
|
387
418
|
display: 'flex',
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
flexDirection: 'column',
|
|
420
|
+
gap: 16,
|
|
421
|
+
}, children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { style: {
|
|
422
|
+
display: 'flex',
|
|
423
|
+
alignItems: 'center',
|
|
424
|
+
justifyContent: 'center',
|
|
425
|
+
minHeight: 300,
|
|
426
|
+
color: colors.textMuted,
|
|
427
|
+
textAlign: 'center',
|
|
428
|
+
fontSize: 14,
|
|
429
|
+
}, children: emptyMessage ??
|
|
430
|
+
(props.threadId
|
|
431
|
+
? 'Send a message to start the conversation.'
|
|
432
|
+
: 'Start a new conversation.') }) }), _jsx(ThreadPrimitive.Messages, { components: {
|
|
433
|
+
UserMessage,
|
|
434
|
+
AssistantMessage,
|
|
435
|
+
} })] }) }), _jsx("div", { style: {
|
|
436
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
437
|
+
margin: '0 auto',
|
|
438
|
+
width: '100%',
|
|
439
|
+
padding: '0 16px',
|
|
440
|
+
}, children: _jsx(PikkuComposer, { disabled: isAwaitingApproval }) })] }) })] }) }) }) }) }) }) }));
|
|
406
441
|
}
|
|
@@ -34,7 +34,7 @@ async function* parseSSEStream(reader) {
|
|
|
34
34
|
* Shared helper: consume an SSE stream and populate text/toolCalls.
|
|
35
35
|
* Returns an array of PendingApprovals when the stream requests them, or empty when done.
|
|
36
36
|
*/
|
|
37
|
-
async function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
37
|
+
async function processStream(reader, text, toolCalls, structuredParts, yieldContent, onFinish) {
|
|
38
38
|
const pendingApprovals = [];
|
|
39
39
|
for await (const event of parseSSEStream(reader)) {
|
|
40
40
|
switch (event.type) {
|
|
@@ -77,6 +77,31 @@ async function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
|
77
77
|
}
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
|
+
case 'generative-ui': {
|
|
81
|
+
const nextPart = {
|
|
82
|
+
type: 'generative-ui',
|
|
83
|
+
spec: event.spec,
|
|
84
|
+
};
|
|
85
|
+
const existingIndex = structuredParts.findIndex((part) => part.type === 'generative-ui');
|
|
86
|
+
if (existingIndex === -1)
|
|
87
|
+
structuredParts.push(nextPart);
|
|
88
|
+
else
|
|
89
|
+
structuredParts[existingIndex] = nextPart;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'data': {
|
|
93
|
+
const nextPart = {
|
|
94
|
+
type: 'data',
|
|
95
|
+
name: event.name,
|
|
96
|
+
data: event.data,
|
|
97
|
+
};
|
|
98
|
+
const existingIndex = structuredParts.findIndex((part) => part.type === 'data' && part.name === event.name);
|
|
99
|
+
if (existingIndex === -1)
|
|
100
|
+
structuredParts.push(nextPart);
|
|
101
|
+
else
|
|
102
|
+
structuredParts[existingIndex] = nextPart;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
80
105
|
case 'approval-request':
|
|
81
106
|
pendingApprovals.push({
|
|
82
107
|
toolCallId: event.toolCallId,
|
|
@@ -151,13 +176,32 @@ export function resolvePikkuToolStatus(status, result) {
|
|
|
151
176
|
return { type: 'error' };
|
|
152
177
|
return { type: 'completed' };
|
|
153
178
|
}
|
|
154
|
-
function
|
|
179
|
+
function buildRichContent(text, structuredParts, toolCalls) {
|
|
155
180
|
const content = [];
|
|
156
181
|
if (text.value)
|
|
157
182
|
content.push({ type: 'text', text: text.value });
|
|
183
|
+
content.push(...structuredParts);
|
|
158
184
|
content.push(...toolCalls);
|
|
159
185
|
return content;
|
|
160
186
|
}
|
|
187
|
+
function buildContentFromAgentResult(result) {
|
|
188
|
+
const content = [];
|
|
189
|
+
if (typeof result === 'string') {
|
|
190
|
+
if (result)
|
|
191
|
+
content.push({ type: 'text', text: result });
|
|
192
|
+
return content;
|
|
193
|
+
}
|
|
194
|
+
if (!result || typeof result !== 'object')
|
|
195
|
+
return content;
|
|
196
|
+
const record = result;
|
|
197
|
+
if (typeof record.text === 'string' && record.text) {
|
|
198
|
+
content.push({ type: 'text', text: record.text });
|
|
199
|
+
}
|
|
200
|
+
if (record.ui != null) {
|
|
201
|
+
content.push({ type: 'generative-ui', spec: record.ui });
|
|
202
|
+
}
|
|
203
|
+
return content;
|
|
204
|
+
}
|
|
161
205
|
function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
|
|
162
206
|
return {
|
|
163
207
|
async *run({ messages, abortSignal }) {
|
|
@@ -181,6 +225,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
181
225
|
// The last resume triggers continuation (next LLM step).
|
|
182
226
|
let lastText = { value: '' };
|
|
183
227
|
let lastToolCalls = [];
|
|
228
|
+
let lastStructuredParts = [];
|
|
184
229
|
let nextApprovals = [];
|
|
185
230
|
for (let i = 0; i < decisions.length; i++) {
|
|
186
231
|
const decision = decisions[i];
|
|
@@ -209,19 +254,21 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
209
254
|
}
|
|
210
255
|
const text = { value: '' };
|
|
211
256
|
const toolCalls = [];
|
|
257
|
+
const structuredParts = [];
|
|
212
258
|
const reader = resumeResponse.body.getReader();
|
|
213
|
-
const streamApprovals = await processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
|
|
259
|
+
const streamApprovals = await processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
|
|
214
260
|
? (onFinishRef.current ?? undefined)
|
|
215
261
|
: undefined);
|
|
216
262
|
// Keep the last resume's output (it has continuation content)
|
|
217
263
|
lastText = text;
|
|
218
264
|
lastToolCalls = toolCalls;
|
|
265
|
+
lastStructuredParts = structuredParts;
|
|
219
266
|
if (streamApprovals.length > 0) {
|
|
220
267
|
nextApprovals = streamApprovals;
|
|
221
268
|
}
|
|
222
269
|
}
|
|
223
270
|
// Build content from the last resume's output
|
|
224
|
-
const content =
|
|
271
|
+
const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
225
272
|
if (nextApprovals.length > 0) {
|
|
226
273
|
// More approvals from continuation — show them
|
|
227
274
|
pendingApprovalsRef.current = nextApprovals;
|
|
@@ -249,7 +296,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
249
296
|
lastToolCalls.push(approvalToolCall);
|
|
250
297
|
}
|
|
251
298
|
}
|
|
252
|
-
const updatedContent =
|
|
299
|
+
const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
253
300
|
yield {
|
|
254
301
|
content: updatedContent,
|
|
255
302
|
status: {
|
|
@@ -328,14 +375,15 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
328
375
|
}
|
|
329
376
|
const text = { value: '' };
|
|
330
377
|
const toolCalls = [];
|
|
378
|
+
const structuredParts = [];
|
|
331
379
|
let pendingContent = null;
|
|
332
380
|
const yieldContent = () => {
|
|
333
|
-
const content =
|
|
381
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
334
382
|
if (content.length > 0)
|
|
335
383
|
pendingContent = content;
|
|
336
384
|
};
|
|
337
385
|
const reader = response.body.getReader();
|
|
338
|
-
const approvals = await processStream(reader, text, toolCalls, yieldContent, onFinishRef.current ?? undefined);
|
|
386
|
+
const approvals = await processStream(reader, text, toolCalls, structuredParts, yieldContent, onFinishRef.current ?? undefined);
|
|
339
387
|
if (approvals.length === 0) {
|
|
340
388
|
// No approval needed — yield final content and done
|
|
341
389
|
if (pendingContent) {
|
|
@@ -378,7 +426,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
378
426
|
toolCalls.splice(i, 1);
|
|
379
427
|
}
|
|
380
428
|
}
|
|
381
|
-
const content =
|
|
429
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
382
430
|
yield {
|
|
383
431
|
content,
|
|
384
432
|
status: {
|
|
@@ -446,7 +494,12 @@ export const convertDbMessages = (dbMessages) => {
|
|
|
446
494
|
continue;
|
|
447
495
|
const parts = [];
|
|
448
496
|
if (msg.content) {
|
|
449
|
-
|
|
497
|
+
if (Array.isArray(msg.content)) {
|
|
498
|
+
parts.push(...msg.content);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
parts.push({ type: 'text', text: msg.content });
|
|
502
|
+
}
|
|
450
503
|
}
|
|
451
504
|
if (Array.isArray(msg.toolCalls)) {
|
|
452
505
|
for (const tc of msg.toolCalls) {
|
|
@@ -533,6 +586,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
533
586
|
// Resume uses SSE (same as streaming mode)
|
|
534
587
|
let lastText = { value: '' };
|
|
535
588
|
let lastToolCalls = [];
|
|
589
|
+
let lastStructuredParts = [];
|
|
536
590
|
let nextApprovals = [];
|
|
537
591
|
for (let i = 0; i < decisions.length; i++) {
|
|
538
592
|
const decision = decisions[i];
|
|
@@ -560,17 +614,19 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
560
614
|
}
|
|
561
615
|
const text = { value: '' };
|
|
562
616
|
const toolCalls = [];
|
|
617
|
+
const structuredParts = [];
|
|
563
618
|
const reader = resumeResponse.body.getReader();
|
|
564
|
-
const streamApprovals = await processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
|
|
619
|
+
const streamApprovals = await processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
|
|
565
620
|
? (onFinishRef.current ?? undefined)
|
|
566
621
|
: undefined);
|
|
567
622
|
lastText = text;
|
|
568
623
|
lastToolCalls = toolCalls;
|
|
624
|
+
lastStructuredParts = structuredParts;
|
|
569
625
|
if (streamApprovals.length > 0) {
|
|
570
626
|
nextApprovals = streamApprovals;
|
|
571
627
|
}
|
|
572
628
|
}
|
|
573
|
-
const content =
|
|
629
|
+
const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
574
630
|
if (nextApprovals.length > 0) {
|
|
575
631
|
pendingApprovalsRef.current = nextApprovals;
|
|
576
632
|
setPendingApprovalsRef.current(nextApprovals);
|
|
@@ -596,7 +652,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
596
652
|
lastToolCalls.push(approvalToolCall);
|
|
597
653
|
}
|
|
598
654
|
}
|
|
599
|
-
const updatedContent =
|
|
655
|
+
const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
600
656
|
yield {
|
|
601
657
|
content: updatedContent,
|
|
602
658
|
status: {
|
|
@@ -656,9 +712,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
656
712
|
},
|
|
657
713
|
}));
|
|
658
714
|
const content = [];
|
|
659
|
-
|
|
660
|
-
content.push({ type: 'text', text: json.result });
|
|
661
|
-
}
|
|
715
|
+
content.push(...buildContentFromAgentResult(json.result));
|
|
662
716
|
content.push(...toolCalls);
|
|
663
717
|
yield {
|
|
664
718
|
content,
|
|
@@ -671,10 +725,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
671
725
|
}
|
|
672
726
|
// No approvals — yield complete content
|
|
673
727
|
onFinishRef.current?.();
|
|
674
|
-
const content =
|
|
675
|
-
if (json.result) {
|
|
676
|
-
content.push({ type: 'text', text: String(json.result) });
|
|
677
|
-
}
|
|
728
|
+
const content = buildContentFromAgentResult(json.result);
|
|
678
729
|
if (content.length > 0) {
|
|
679
730
|
yield { content };
|
|
680
731
|
}
|