@pikku/assistant-ui 0.12.3 → 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 +34 -1
- package/dist/cjs/pikku-agent-chat.d.ts +16 -0
- package/dist/cjs/pikku-agent-chat.js +162 -70
- package/dist/cjs/use-pikku-agent-runtime.js +70 -19
- package/dist/esm/pikku-agent-chat.d.ts +16 -0
- package/dist/esm/pikku-agent-chat.js +175 -76
- package/dist/esm/use-pikku-agent-runtime.js +70 -19
- package/package.json +1 -1
- package/src/pikku-agent-chat.tsx +379 -154
- 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',
|
|
@@ -45,6 +45,9 @@ const darkColors = {
|
|
|
45
45
|
};
|
|
46
46
|
const ColorsContext = createContext(lightColors);
|
|
47
47
|
const HideToolCallsContext = createContext(undefined);
|
|
48
|
+
const ToolComponentsContext = createContext(undefined);
|
|
49
|
+
const GenerativeUIComponentsContext = createContext(undefined);
|
|
50
|
+
const RenderAssistantTextContext = createContext(undefined);
|
|
48
51
|
function shouldHideToolCall(hideToolCalls, toolName) {
|
|
49
52
|
if (!hideToolCalls)
|
|
50
53
|
return false;
|
|
@@ -135,7 +138,9 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
135
138
|
padding: '2px 6px',
|
|
136
139
|
borderRadius: 3,
|
|
137
140
|
background: responded === 'approved' ? colors.successBg : colors.errorBg,
|
|
138
|
-
color: responded === 'approved'
|
|
141
|
+
color: responded === 'approved'
|
|
142
|
+
? colors.successColor
|
|
143
|
+
: colors.errorColor,
|
|
139
144
|
}, children: responded })] }));
|
|
140
145
|
}
|
|
141
146
|
return (_jsxs("div", { style: {
|
|
@@ -185,7 +190,12 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
185
190
|
borderRadius: 4,
|
|
186
191
|
overflow: 'auto',
|
|
187
192
|
color: colors.text,
|
|
188
|
-
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
193
|
+
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
194
|
+
fontSize: 12,
|
|
195
|
+
color: colors.textMuted,
|
|
196
|
+
marginTop: 8,
|
|
197
|
+
marginBottom: 2,
|
|
198
|
+
}, children: "Result:" }), _jsx("pre", { style: {
|
|
189
199
|
fontSize: 11,
|
|
190
200
|
background: colors.codeBg,
|
|
191
201
|
padding: 8,
|
|
@@ -196,25 +206,68 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
196
206
|
? result
|
|
197
207
|
: JSON.stringify(result, null, 2) })] }))] }))] }));
|
|
198
208
|
};
|
|
199
|
-
const MarkdownText = ({ text, colors }) => {
|
|
209
|
+
const MarkdownText = ({ text, colors, }) => {
|
|
200
210
|
const components = useMemo(() => ({
|
|
201
|
-
p: ({ children }) => (_jsx("p", { style: {
|
|
211
|
+
p: ({ children }) => (_jsx("p", { style: {
|
|
212
|
+
margin: '0 0 8px',
|
|
213
|
+
fontSize: 14,
|
|
214
|
+
lineHeight: 1.6,
|
|
215
|
+
color: colors.text,
|
|
216
|
+
}, children: children })),
|
|
202
217
|
strong: ({ children }) => (_jsx("strong", { style: { fontWeight: 600, color: colors.text }, children: children })),
|
|
203
218
|
em: ({ children }) => (_jsx("em", { style: { color: colors.text }, children: children })),
|
|
204
|
-
ul: ({ children }) => (_jsx("ul", { style: {
|
|
205
|
-
|
|
219
|
+
ul: ({ children }) => (_jsx("ul", { style: {
|
|
220
|
+
margin: '4px 0 8px',
|
|
221
|
+
paddingLeft: 20,
|
|
222
|
+
fontSize: 14,
|
|
223
|
+
color: colors.text,
|
|
224
|
+
}, children: children })),
|
|
225
|
+
ol: ({ children }) => (_jsx("ol", { style: {
|
|
226
|
+
margin: '4px 0 8px',
|
|
227
|
+
paddingLeft: 20,
|
|
228
|
+
fontSize: 14,
|
|
229
|
+
color: colors.text,
|
|
230
|
+
}, children: children })),
|
|
206
231
|
li: ({ children }) => (_jsx("li", { style: { marginBottom: 2, lineHeight: 1.6 }, children: children })),
|
|
207
232
|
code: ({ children, className }) => {
|
|
208
233
|
const isBlock = className?.startsWith('language-');
|
|
209
234
|
if (isBlock) {
|
|
210
|
-
return (_jsx("pre", { style: {
|
|
235
|
+
return (_jsx("pre", { style: {
|
|
236
|
+
background: colors.codeBg,
|
|
237
|
+
padding: 10,
|
|
238
|
+
borderRadius: 4,
|
|
239
|
+
overflow: 'auto',
|
|
240
|
+
margin: '4px 0 8px',
|
|
241
|
+
fontSize: 12,
|
|
242
|
+
}, children: _jsx("code", { style: { color: colors.text }, children: children }) }));
|
|
211
243
|
}
|
|
212
|
-
return (_jsx("code", { style: {
|
|
244
|
+
return (_jsx("code", { style: {
|
|
245
|
+
background: colors.codeBg,
|
|
246
|
+
padding: '1px 4px',
|
|
247
|
+
borderRadius: 3,
|
|
248
|
+
fontSize: 13,
|
|
249
|
+
color: colors.text,
|
|
250
|
+
}, children: children }));
|
|
213
251
|
},
|
|
214
252
|
pre: ({ children }) => _jsx(_Fragment, { children: children }),
|
|
215
|
-
h1: ({ children }) => _jsx("h3", { style: {
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
h1: ({ children }) => (_jsx("h3", { style: {
|
|
254
|
+
margin: '8px 0 4px',
|
|
255
|
+
fontSize: 16,
|
|
256
|
+
fontWeight: 600,
|
|
257
|
+
color: colors.text,
|
|
258
|
+
}, children: children })),
|
|
259
|
+
h2: ({ children }) => (_jsx("h4", { style: {
|
|
260
|
+
margin: '8px 0 4px',
|
|
261
|
+
fontSize: 15,
|
|
262
|
+
fontWeight: 600,
|
|
263
|
+
color: colors.text,
|
|
264
|
+
}, children: children })),
|
|
265
|
+
h3: ({ children }) => (_jsx("h5", { style: {
|
|
266
|
+
margin: '8px 0 4px',
|
|
267
|
+
fontSize: 14,
|
|
268
|
+
fontWeight: 600,
|
|
269
|
+
color: colors.text,
|
|
270
|
+
}, children: children })),
|
|
218
271
|
a: ({ href, children }) => (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", style: { color: colors.textMuted, textDecoration: 'underline' }, children: children })),
|
|
219
272
|
}), [colors]);
|
|
220
273
|
return _jsx(Markdown, { components: components, children: text });
|
|
@@ -235,11 +288,18 @@ const UserMessage = () => {
|
|
|
235
288
|
borderRadius: 12,
|
|
236
289
|
backgroundColor: colors.userBubble,
|
|
237
290
|
}, children: _jsx(MessagePrimitive.Content, { components: {
|
|
238
|
-
Text: ({ text }) => (_jsx("span", { style: {
|
|
291
|
+
Text: ({ text }) => (_jsx("span", { style: {
|
|
292
|
+
fontSize: 14,
|
|
293
|
+
whiteSpace: 'pre-wrap',
|
|
294
|
+
color: colors.text,
|
|
295
|
+
}, children: text })),
|
|
239
296
|
} }) })] }) }));
|
|
240
297
|
};
|
|
241
298
|
const AssistantMessage = () => {
|
|
242
299
|
const colors = useContext(ColorsContext);
|
|
300
|
+
const toolComponents = useContext(ToolComponentsContext);
|
|
301
|
+
const generativeUIComponents = useContext(GenerativeUIComponentsContext);
|
|
302
|
+
const renderAssistantText = useContext(RenderAssistantTextContext);
|
|
243
303
|
return (_jsx("div", { style: {
|
|
244
304
|
display: 'flex',
|
|
245
305
|
justifyContent: 'flex-start',
|
|
@@ -249,10 +309,18 @@ const AssistantMessage = () => {
|
|
|
249
309
|
borderRadius: 12,
|
|
250
310
|
backgroundColor: colors.assistantBubble,
|
|
251
311
|
}, children: [_jsx(MessagePrimitive.Content, { components: {
|
|
252
|
-
Text: ({ text }) => (_jsx(MarkdownText, { text: text, colors: colors })),
|
|
312
|
+
Text: ({ text }) => renderAssistantText ? (_jsx(_Fragment, { children: renderAssistantText(text) })) : (_jsx(MarkdownText, { text: text, colors: colors })),
|
|
253
313
|
tools: {
|
|
314
|
+
by_name: toolComponents,
|
|
254
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 })),
|
|
255
316
|
},
|
|
317
|
+
...(generativeUIComponents
|
|
318
|
+
? {
|
|
319
|
+
generativeUI: {
|
|
320
|
+
components: generativeUIComponents,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
: {}),
|
|
256
324
|
} }), _jsx(MessagePrimitive.If, { last: true, children: _jsx(ThreadPrimitive.If, { running: true, children: _jsx("div", { style: {
|
|
257
325
|
display: 'flex',
|
|
258
326
|
alignItems: 'center',
|
|
@@ -262,81 +330,112 @@ const AssistantMessage = () => {
|
|
|
262
330
|
color: colors.textMuted,
|
|
263
331
|
}, children: "Thinking..." }) }) })] })] }) }));
|
|
264
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
|
+
};
|
|
265
345
|
const PikkuComposer = ({ disabled, }) => {
|
|
266
346
|
const colors = useContext(ColorsContext);
|
|
267
347
|
return (_jsx("div", { style: { padding: '8px 0 16px' }, children: _jsx(ComposerPrimitive.Root, { children: _jsxs("div", { style: {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
overflow: 'hidden',
|
|
348
|
+
position: 'relative',
|
|
349
|
+
zIndex: 2,
|
|
271
350
|
display: 'flex',
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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)',
|
|
360
|
+
...(disabled
|
|
361
|
+
? { opacity: 0.5, pointerEvents: 'none' }
|
|
362
|
+
: {}),
|
|
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',
|
|
278
366
|
border: 'none',
|
|
279
367
|
outline: 'none',
|
|
280
|
-
|
|
368
|
+
color: colors.text,
|
|
281
369
|
fontSize: 14,
|
|
282
370
|
fontFamily: 'inherit',
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
} }), _jsx(
|
|
287
|
-
width:
|
|
288
|
-
height: 28,
|
|
289
|
-
borderRadius: '50%',
|
|
290
|
-
border: 'none',
|
|
291
|
-
background: disabled ? colors.textMuted : colors.sendBg,
|
|
292
|
-
color: colors.sendColor,
|
|
293
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
371
|
+
resize: 'none',
|
|
372
|
+
lineHeight: 1.5,
|
|
373
|
+
overflowY: 'auto',
|
|
374
|
+
} }), _jsx("div", { style: {
|
|
375
|
+
width: '100%',
|
|
294
376
|
display: 'flex',
|
|
295
377
|
alignItems: 'center',
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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" }) })] }) }) }));
|
|
301
395
|
};
|
|
302
396
|
export function PikkuAgentChat(props) {
|
|
303
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props;
|
|
397
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, renderAssistantText, generativeUIComponents, initialPrompt, ...runtimeOptions } = props;
|
|
304
398
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = usePikkuAgentRuntime(runtimeOptions);
|
|
305
399
|
const colors = dark ? darkColors : lightColors;
|
|
306
|
-
return (_jsx(ColorsContext.Provider, { value: colors, children: _jsx(PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: _jsx(HideToolCallsContext.Provider, { value: hideToolCalls, children: _jsx(AssistantRuntimeProvider, { runtime: runtime, children: _jsx("div", { style: {
|
|
307
|
-
|
|
308
|
-
display: 'flex',
|
|
309
|
-
flexDirection: 'column',
|
|
310
|
-
background: colors.bg,
|
|
311
|
-
}, children: _jsxs(ThreadPrimitive.Root, { style: {
|
|
312
|
-
display: 'flex',
|
|
313
|
-
flexDirection: 'column',
|
|
314
|
-
flex: 1,
|
|
315
|
-
minHeight: 0,
|
|
316
|
-
}, children: [_jsx(ThreadPrimitive.Viewport, { style: {
|
|
317
|
-
flex: 1,
|
|
318
|
-
minHeight: 0,
|
|
319
|
-
overflowY: 'auto',
|
|
320
|
-
}, children: _jsxs("div", { style: {
|
|
321
|
-
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
322
|
-
margin: '0 auto',
|
|
323
|
-
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%',
|
|
324
402
|
display: 'flex',
|
|
325
403
|
flexDirection: 'column',
|
|
326
|
-
|
|
327
|
-
}, children:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
: '
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
404
|
+
background: colors.bg,
|
|
405
|
+
}, children: _jsxs(ThreadPrimitive.Root, { style: {
|
|
406
|
+
display: 'flex',
|
|
407
|
+
flexDirection: 'column',
|
|
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,
|
|
418
|
+
display: 'flex',
|
|
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 }) })] }) })] }) }) }) }) }) }) }));
|
|
342
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
|
}
|