@pikku/assistant-ui 0.12.4 → 0.12.6
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 +12 -1
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/model-capabilities.d.ts +1 -0
- package/dist/cjs/model-capabilities.js +16 -0
- package/dist/cjs/pikku-agent-chat.d.ts +4 -0
- package/dist/cjs/pikku-agent-chat.js +91 -63
- package/dist/cjs/use-file-attachment.d.ts +26 -0
- package/dist/cjs/use-file-attachment.js +99 -0
- package/dist/cjs/use-pikku-agent-runtime.d.ts +4 -0
- package/dist/cjs/use-pikku-agent-runtime.js +72 -35
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/model-capabilities.d.ts +1 -0
- package/dist/esm/model-capabilities.js +13 -0
- package/dist/esm/pikku-agent-chat.d.ts +4 -0
- package/dist/esm/pikku-agent-chat.js +102 -67
- package/dist/esm/use-file-attachment.d.ts +26 -0
- package/dist/esm/use-file-attachment.js +85 -0
- package/dist/esm/use-pikku-agent-runtime.d.ts +4 -0
- package/dist/esm/use-pikku-agent-runtime.js +72 -19
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/model-capabilities.ts +14 -0
- package/src/pikku-agent-chat.tsx +362 -156
- package/src/use-file-attachment.ts +110 -0
- package/src/use-pikku-agent-runtime.ts +109 -15
|
@@ -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: {
|
|
@@ -287,6 +334,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
287
334
|
resourceId: opts.resourceId,
|
|
288
335
|
model: opts.model,
|
|
289
336
|
temperature: opts.temperature,
|
|
337
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
290
338
|
}),
|
|
291
339
|
signal: abortSignal,
|
|
292
340
|
credentials: opts.credentials,
|
|
@@ -328,14 +376,15 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
328
376
|
}
|
|
329
377
|
const text = { value: '' };
|
|
330
378
|
const toolCalls = [];
|
|
379
|
+
const structuredParts = [];
|
|
331
380
|
let pendingContent = null;
|
|
332
381
|
const yieldContent = () => {
|
|
333
|
-
const content =
|
|
382
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
334
383
|
if (content.length > 0)
|
|
335
384
|
pendingContent = content;
|
|
336
385
|
};
|
|
337
386
|
const reader = response.body.getReader();
|
|
338
|
-
const approvals = await processStream(reader, text, toolCalls, yieldContent, onFinishRef.current ?? undefined);
|
|
387
|
+
const approvals = await processStream(reader, text, toolCalls, structuredParts, yieldContent, onFinishRef.current ?? undefined);
|
|
339
388
|
if (approvals.length === 0) {
|
|
340
389
|
// No approval needed — yield final content and done
|
|
341
390
|
if (pendingContent) {
|
|
@@ -378,7 +427,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
378
427
|
toolCalls.splice(i, 1);
|
|
379
428
|
}
|
|
380
429
|
}
|
|
381
|
-
const content =
|
|
430
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
382
431
|
yield {
|
|
383
432
|
content,
|
|
384
433
|
status: {
|
|
@@ -446,7 +495,12 @@ export const convertDbMessages = (dbMessages) => {
|
|
|
446
495
|
continue;
|
|
447
496
|
const parts = [];
|
|
448
497
|
if (msg.content) {
|
|
449
|
-
|
|
498
|
+
if (Array.isArray(msg.content)) {
|
|
499
|
+
parts.push(...msg.content);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
parts.push({ type: 'text', text: msg.content });
|
|
503
|
+
}
|
|
450
504
|
}
|
|
451
505
|
if (Array.isArray(msg.toolCalls)) {
|
|
452
506
|
for (const tc of msg.toolCalls) {
|
|
@@ -533,6 +587,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
533
587
|
// Resume uses SSE (same as streaming mode)
|
|
534
588
|
let lastText = { value: '' };
|
|
535
589
|
let lastToolCalls = [];
|
|
590
|
+
let lastStructuredParts = [];
|
|
536
591
|
let nextApprovals = [];
|
|
537
592
|
for (let i = 0; i < decisions.length; i++) {
|
|
538
593
|
const decision = decisions[i];
|
|
@@ -560,17 +615,19 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
560
615
|
}
|
|
561
616
|
const text = { value: '' };
|
|
562
617
|
const toolCalls = [];
|
|
618
|
+
const structuredParts = [];
|
|
563
619
|
const reader = resumeResponse.body.getReader();
|
|
564
|
-
const streamApprovals = await processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
|
|
620
|
+
const streamApprovals = await processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
|
|
565
621
|
? (onFinishRef.current ?? undefined)
|
|
566
622
|
: undefined);
|
|
567
623
|
lastText = text;
|
|
568
624
|
lastToolCalls = toolCalls;
|
|
625
|
+
lastStructuredParts = structuredParts;
|
|
569
626
|
if (streamApprovals.length > 0) {
|
|
570
627
|
nextApprovals = streamApprovals;
|
|
571
628
|
}
|
|
572
629
|
}
|
|
573
|
-
const content =
|
|
630
|
+
const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
574
631
|
if (nextApprovals.length > 0) {
|
|
575
632
|
pendingApprovalsRef.current = nextApprovals;
|
|
576
633
|
setPendingApprovalsRef.current(nextApprovals);
|
|
@@ -596,7 +653,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
596
653
|
lastToolCalls.push(approvalToolCall);
|
|
597
654
|
}
|
|
598
655
|
}
|
|
599
|
-
const updatedContent =
|
|
656
|
+
const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
600
657
|
yield {
|
|
601
658
|
content: updatedContent,
|
|
602
659
|
status: {
|
|
@@ -632,6 +689,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
632
689
|
resourceId: opts.resourceId,
|
|
633
690
|
model: opts.model,
|
|
634
691
|
temperature: opts.temperature,
|
|
692
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
635
693
|
}),
|
|
636
694
|
signal: abortSignal,
|
|
637
695
|
credentials: opts.credentials,
|
|
@@ -656,9 +714,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
656
714
|
},
|
|
657
715
|
}));
|
|
658
716
|
const content = [];
|
|
659
|
-
|
|
660
|
-
content.push({ type: 'text', text: json.result });
|
|
661
|
-
}
|
|
717
|
+
content.push(...buildContentFromAgentResult(json.result));
|
|
662
718
|
content.push(...toolCalls);
|
|
663
719
|
yield {
|
|
664
720
|
content,
|
|
@@ -671,10 +727,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
671
727
|
}
|
|
672
728
|
// No approvals — yield complete content
|
|
673
729
|
onFinishRef.current?.();
|
|
674
|
-
const content =
|
|
675
|
-
if (json.result) {
|
|
676
|
-
content.push({ type: 'text', text: String(json.result) });
|
|
677
|
-
}
|
|
730
|
+
const content = buildContentFromAgentResult(json.result);
|
|
678
731
|
if (content.length > 0) {
|
|
679
732
|
yield { content };
|
|
680
733
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/index.ts","../src/model-capabilities.ts","../src/use-file-attachment.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/index.ts","../src/model-capabilities.ts","../src/use-file-attachment.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/assistant-ui",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"prepublishOnly": "yarn build"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@assistant-ui/react": "^0.12.12",
|
|
23
|
+
"@assistant-ui/react": "^0.12.12 || ^0.14.0",
|
|
24
24
|
"react-markdown": "^10.1.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -17,3 +17,6 @@ export type {
|
|
|
17
17
|
} from './use-pikku-agent-runtime.js'
|
|
18
18
|
export { PikkuAgentChat } from './pikku-agent-chat.js'
|
|
19
19
|
export type { PikkuAgentChatProps } from './pikku-agent-chat.js'
|
|
20
|
+
export { useFileAttachment, INLINE_SIZE_LIMIT } from './use-file-attachment.js'
|
|
21
|
+
export type { PendingFile, UploadAttachmentFn } from './use-file-attachment.js'
|
|
22
|
+
export { modelSupportsVision } from './model-capabilities.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const MODEL_VISION_SUPPORT = new Set([
|
|
2
|
+
'claude-sonnet-4-5',
|
|
3
|
+
'claude-opus-4-1',
|
|
4
|
+
'gpt-4o-mini',
|
|
5
|
+
'gpt-5',
|
|
6
|
+
'gpt-5-mini',
|
|
7
|
+
'gpt-5-codex',
|
|
8
|
+
'gemini-2.5-pro',
|
|
9
|
+
'gemini-2.5-flash',
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
export function modelSupportsVision(modelID: string): boolean {
|
|
13
|
+
return MODEL_VISION_SUPPORT.has(modelID)
|
|
14
|
+
}
|