@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
|
@@ -87,6 +87,17 @@ type ToolCall = {
|
|
|
87
87
|
isError?: boolean
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
type StructuredPart =
|
|
91
|
+
| {
|
|
92
|
+
type: 'generative-ui'
|
|
93
|
+
spec: unknown
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
type: 'data'
|
|
97
|
+
name: string
|
|
98
|
+
data: unknown
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
/**
|
|
91
102
|
* Shared helper: consume an SSE stream and populate text/toolCalls.
|
|
92
103
|
* Returns an array of PendingApprovals when the stream requests them, or empty when done.
|
|
@@ -95,6 +106,7 @@ async function processStream(
|
|
|
95
106
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
96
107
|
text: { value: string },
|
|
97
108
|
toolCalls: ToolCall[],
|
|
109
|
+
structuredParts: StructuredPart[],
|
|
98
110
|
yieldContent: () => void,
|
|
99
111
|
onFinish?: () => void
|
|
100
112
|
): Promise<PendingApproval[]> {
|
|
@@ -140,6 +152,31 @@ async function processStream(
|
|
|
140
152
|
}
|
|
141
153
|
break
|
|
142
154
|
}
|
|
155
|
+
case 'generative-ui': {
|
|
156
|
+
const nextPart = {
|
|
157
|
+
type: 'generative-ui' as const,
|
|
158
|
+
spec: event.spec,
|
|
159
|
+
}
|
|
160
|
+
const existingIndex = structuredParts.findIndex(
|
|
161
|
+
(part) => part.type === 'generative-ui'
|
|
162
|
+
)
|
|
163
|
+
if (existingIndex === -1) structuredParts.push(nextPart)
|
|
164
|
+
else structuredParts[existingIndex] = nextPart
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
case 'data': {
|
|
168
|
+
const nextPart = {
|
|
169
|
+
type: 'data' as const,
|
|
170
|
+
name: event.name,
|
|
171
|
+
data: event.data,
|
|
172
|
+
}
|
|
173
|
+
const existingIndex = structuredParts.findIndex(
|
|
174
|
+
(part) => part.type === 'data' && part.name === event.name
|
|
175
|
+
)
|
|
176
|
+
if (existingIndex === -1) structuredParts.push(nextPart)
|
|
177
|
+
else structuredParts[existingIndex] = nextPart
|
|
178
|
+
break
|
|
179
|
+
}
|
|
143
180
|
case 'approval-request':
|
|
144
181
|
pendingApprovals.push({
|
|
145
182
|
toolCallId: event.toolCallId,
|
|
@@ -237,13 +274,39 @@ export function resolvePikkuToolStatus(
|
|
|
237
274
|
return { type: 'completed' }
|
|
238
275
|
}
|
|
239
276
|
|
|
240
|
-
function
|
|
277
|
+
function buildRichContent(
|
|
278
|
+
text: { value: string },
|
|
279
|
+
structuredParts: StructuredPart[],
|
|
280
|
+
toolCalls: ToolCall[]
|
|
281
|
+
): any[] {
|
|
241
282
|
const content: any[] = []
|
|
242
283
|
if (text.value) content.push({ type: 'text' as const, text: text.value })
|
|
284
|
+
content.push(...structuredParts)
|
|
243
285
|
content.push(...toolCalls)
|
|
244
286
|
return content
|
|
245
287
|
}
|
|
246
288
|
|
|
289
|
+
function buildContentFromAgentResult(result: unknown): any[] {
|
|
290
|
+
const content: any[] = []
|
|
291
|
+
|
|
292
|
+
if (typeof result === 'string') {
|
|
293
|
+
if (result) content.push({ type: 'text' as const, text: result })
|
|
294
|
+
return content
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!result || typeof result !== 'object') return content
|
|
298
|
+
|
|
299
|
+
const record = result as Record<string, unknown>
|
|
300
|
+
if (typeof record.text === 'string' && record.text) {
|
|
301
|
+
content.push({ type: 'text' as const, text: record.text })
|
|
302
|
+
}
|
|
303
|
+
if (record.ui != null) {
|
|
304
|
+
content.push({ type: 'generative-ui' as const, spec: record.ui })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return content
|
|
308
|
+
}
|
|
309
|
+
|
|
247
310
|
function createPikkuStreamingAdapter(
|
|
248
311
|
optionsRef: React.RefObject<PikkuAgentRuntimeOptions>,
|
|
249
312
|
pendingApprovalsRef: React.RefObject<PendingApproval[]>,
|
|
@@ -281,6 +344,7 @@ function createPikkuStreamingAdapter(
|
|
|
281
344
|
// The last resume triggers continuation (next LLM step).
|
|
282
345
|
let lastText = { value: '' }
|
|
283
346
|
let lastToolCalls: ToolCall[] = []
|
|
347
|
+
let lastStructuredParts: StructuredPart[] = []
|
|
284
348
|
let nextApprovals: PendingApproval[] = []
|
|
285
349
|
|
|
286
350
|
for (let i = 0; i < decisions.length; i++) {
|
|
@@ -320,11 +384,13 @@ function createPikkuStreamingAdapter(
|
|
|
320
384
|
|
|
321
385
|
const text = { value: '' }
|
|
322
386
|
const toolCalls: ToolCall[] = []
|
|
387
|
+
const structuredParts: StructuredPart[] = []
|
|
323
388
|
const reader = resumeResponse.body.getReader()
|
|
324
389
|
const streamApprovals = await processStream(
|
|
325
390
|
reader,
|
|
326
391
|
text,
|
|
327
392
|
toolCalls,
|
|
393
|
+
structuredParts,
|
|
328
394
|
() => {},
|
|
329
395
|
i === decisions.length - 1
|
|
330
396
|
? (onFinishRef.current ?? undefined)
|
|
@@ -334,13 +400,18 @@ function createPikkuStreamingAdapter(
|
|
|
334
400
|
// Keep the last resume's output (it has continuation content)
|
|
335
401
|
lastText = text
|
|
336
402
|
lastToolCalls = toolCalls
|
|
403
|
+
lastStructuredParts = structuredParts
|
|
337
404
|
if (streamApprovals.length > 0) {
|
|
338
405
|
nextApprovals = streamApprovals
|
|
339
406
|
}
|
|
340
407
|
}
|
|
341
408
|
|
|
342
409
|
// Build content from the last resume's output
|
|
343
|
-
const content =
|
|
410
|
+
const content = buildRichContent(
|
|
411
|
+
lastText,
|
|
412
|
+
lastStructuredParts,
|
|
413
|
+
lastToolCalls
|
|
414
|
+
)
|
|
344
415
|
|
|
345
416
|
if (nextApprovals.length > 0) {
|
|
346
417
|
// More approvals from continuation — show them
|
|
@@ -372,7 +443,11 @@ function createPikkuStreamingAdapter(
|
|
|
372
443
|
}
|
|
373
444
|
}
|
|
374
445
|
|
|
375
|
-
const updatedContent =
|
|
446
|
+
const updatedContent = buildRichContent(
|
|
447
|
+
lastText,
|
|
448
|
+
lastStructuredParts,
|
|
449
|
+
lastToolCalls
|
|
450
|
+
)
|
|
376
451
|
yield {
|
|
377
452
|
content: updatedContent,
|
|
378
453
|
status: {
|
|
@@ -451,9 +526,10 @@ function createPikkuStreamingAdapter(
|
|
|
451
526
|
|
|
452
527
|
const text = { value: '' }
|
|
453
528
|
const toolCalls: ToolCall[] = []
|
|
529
|
+
const structuredParts: StructuredPart[] = []
|
|
454
530
|
let pendingContent: any[] | null = null
|
|
455
531
|
const yieldContent = () => {
|
|
456
|
-
const content =
|
|
532
|
+
const content = buildRichContent(text, structuredParts, toolCalls)
|
|
457
533
|
if (content.length > 0) pendingContent = content
|
|
458
534
|
}
|
|
459
535
|
|
|
@@ -462,6 +538,7 @@ function createPikkuStreamingAdapter(
|
|
|
462
538
|
reader,
|
|
463
539
|
text,
|
|
464
540
|
toolCalls,
|
|
541
|
+
structuredParts,
|
|
465
542
|
yieldContent,
|
|
466
543
|
onFinishRef.current ?? undefined
|
|
467
544
|
)
|
|
@@ -516,7 +593,7 @@ function createPikkuStreamingAdapter(
|
|
|
516
593
|
}
|
|
517
594
|
}
|
|
518
595
|
|
|
519
|
-
const content =
|
|
596
|
+
const content = buildRichContent(text, structuredParts, toolCalls)
|
|
520
597
|
yield {
|
|
521
598
|
content,
|
|
522
599
|
status: {
|
|
@@ -596,7 +673,11 @@ export const convertDbMessages = (dbMessages: any[]): ThreadMessageLike[] => {
|
|
|
596
673
|
const parts: any[] = []
|
|
597
674
|
|
|
598
675
|
if (msg.content) {
|
|
599
|
-
|
|
676
|
+
if (Array.isArray(msg.content)) {
|
|
677
|
+
parts.push(...msg.content)
|
|
678
|
+
} else {
|
|
679
|
+
parts.push({ type: 'text' as const, text: msg.content })
|
|
680
|
+
}
|
|
600
681
|
}
|
|
601
682
|
|
|
602
683
|
if (Array.isArray(msg.toolCalls)) {
|
|
@@ -732,6 +813,7 @@ function createPikkuNonStreamingAdapter(
|
|
|
732
813
|
// Resume uses SSE (same as streaming mode)
|
|
733
814
|
let lastText = { value: '' }
|
|
734
815
|
let lastToolCalls: ToolCall[] = []
|
|
816
|
+
let lastStructuredParts: StructuredPart[] = []
|
|
735
817
|
let nextApprovals: PendingApproval[] = []
|
|
736
818
|
|
|
737
819
|
for (let i = 0; i < decisions.length; i++) {
|
|
@@ -770,11 +852,13 @@ function createPikkuNonStreamingAdapter(
|
|
|
770
852
|
|
|
771
853
|
const text = { value: '' }
|
|
772
854
|
const toolCalls: ToolCall[] = []
|
|
855
|
+
const structuredParts: StructuredPart[] = []
|
|
773
856
|
const reader = resumeResponse.body.getReader()
|
|
774
857
|
const streamApprovals = await processStream(
|
|
775
858
|
reader,
|
|
776
859
|
text,
|
|
777
860
|
toolCalls,
|
|
861
|
+
structuredParts,
|
|
778
862
|
() => {},
|
|
779
863
|
i === decisions.length - 1
|
|
780
864
|
? (onFinishRef.current ?? undefined)
|
|
@@ -783,12 +867,17 @@ function createPikkuNonStreamingAdapter(
|
|
|
783
867
|
|
|
784
868
|
lastText = text
|
|
785
869
|
lastToolCalls = toolCalls
|
|
870
|
+
lastStructuredParts = structuredParts
|
|
786
871
|
if (streamApprovals.length > 0) {
|
|
787
872
|
nextApprovals = streamApprovals
|
|
788
873
|
}
|
|
789
874
|
}
|
|
790
875
|
|
|
791
|
-
const content =
|
|
876
|
+
const content = buildRichContent(
|
|
877
|
+
lastText,
|
|
878
|
+
lastStructuredParts,
|
|
879
|
+
lastToolCalls
|
|
880
|
+
)
|
|
792
881
|
|
|
793
882
|
if (nextApprovals.length > 0) {
|
|
794
883
|
pendingApprovalsRef.current = nextApprovals
|
|
@@ -818,7 +907,11 @@ function createPikkuNonStreamingAdapter(
|
|
|
818
907
|
}
|
|
819
908
|
}
|
|
820
909
|
|
|
821
|
-
const updatedContent =
|
|
910
|
+
const updatedContent = buildRichContent(
|
|
911
|
+
lastText,
|
|
912
|
+
lastStructuredParts,
|
|
913
|
+
lastToolCalls
|
|
914
|
+
)
|
|
822
915
|
yield {
|
|
823
916
|
content: updatedContent,
|
|
824
917
|
status: {
|
|
@@ -884,9 +977,7 @@ function createPikkuNonStreamingAdapter(
|
|
|
884
977
|
}))
|
|
885
978
|
|
|
886
979
|
const content: any[] = []
|
|
887
|
-
|
|
888
|
-
content.push({ type: 'text' as const, text: json.result })
|
|
889
|
-
}
|
|
980
|
+
content.push(...buildContentFromAgentResult(json.result))
|
|
890
981
|
content.push(...toolCalls)
|
|
891
982
|
|
|
892
983
|
yield {
|
|
@@ -901,10 +992,7 @@ function createPikkuNonStreamingAdapter(
|
|
|
901
992
|
|
|
902
993
|
// No approvals — yield complete content
|
|
903
994
|
onFinishRef.current?.()
|
|
904
|
-
const content
|
|
905
|
-
if (json.result) {
|
|
906
|
-
content.push({ type: 'text' as const, text: String(json.result) })
|
|
907
|
-
}
|
|
995
|
+
const content = buildContentFromAgentResult(json.result)
|
|
908
996
|
if (content.length > 0) {
|
|
909
997
|
yield { content }
|
|
910
998
|
}
|