@pikku/assistant-ui 0.12.5 → 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 +6 -0
- 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/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 +2 -16
- 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/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 +2 -0
- 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/use-file-attachment.ts +110 -0
- package/src/use-pikku-agent-runtime.ts +6 -0
package/CHANGELOG.md
CHANGED
package/dist/cjs/index.d.ts
CHANGED
|
@@ -2,3 +2,6 @@ export { usePikkuAgentRuntime, usePikkuAgentNonStreamingRuntime, PikkuApprovalCo
|
|
|
2
2
|
export type { PikkuAgentRuntimeOptions, PendingApproval, PikkuApprovalContextValue, PikkuToolStatusType, PikkuToolStatus, MissingCredentialPayload, } from './use-pikku-agent-runtime.js';
|
|
3
3
|
export { PikkuAgentChat } from './pikku-agent-chat.js';
|
|
4
4
|
export type { PikkuAgentChatProps } from './pikku-agent-chat.js';
|
|
5
|
+
export { useFileAttachment, INLINE_SIZE_LIMIT } from './use-file-attachment.js';
|
|
6
|
+
export type { PendingFile, UploadAttachmentFn } from './use-file-attachment.js';
|
|
7
|
+
export { modelSupportsVision } from './model-capabilities.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PikkuAgentChat = exports.resolvePikkuToolStatus = exports.isDeniedResult = exports.convertDbMessages = exports.usePikkuApproval = exports.PikkuApprovalContext = exports.usePikkuAgentNonStreamingRuntime = exports.usePikkuAgentRuntime = void 0;
|
|
3
|
+
exports.modelSupportsVision = exports.INLINE_SIZE_LIMIT = exports.useFileAttachment = exports.PikkuAgentChat = exports.resolvePikkuToolStatus = exports.isDeniedResult = exports.convertDbMessages = exports.usePikkuApproval = exports.PikkuApprovalContext = exports.usePikkuAgentNonStreamingRuntime = exports.usePikkuAgentRuntime = void 0;
|
|
4
4
|
var use_pikku_agent_runtime_js_1 = require("./use-pikku-agent-runtime.js");
|
|
5
5
|
Object.defineProperty(exports, "usePikkuAgentRuntime", { enumerable: true, get: function () { return use_pikku_agent_runtime_js_1.usePikkuAgentRuntime; } });
|
|
6
6
|
Object.defineProperty(exports, "usePikkuAgentNonStreamingRuntime", { enumerable: true, get: function () { return use_pikku_agent_runtime_js_1.usePikkuAgentNonStreamingRuntime; } });
|
|
@@ -11,3 +11,8 @@ Object.defineProperty(exports, "isDeniedResult", { enumerable: true, get: functi
|
|
|
11
11
|
Object.defineProperty(exports, "resolvePikkuToolStatus", { enumerable: true, get: function () { return use_pikku_agent_runtime_js_1.resolvePikkuToolStatus; } });
|
|
12
12
|
var pikku_agent_chat_js_1 = require("./pikku-agent-chat.js");
|
|
13
13
|
Object.defineProperty(exports, "PikkuAgentChat", { enumerable: true, get: function () { return pikku_agent_chat_js_1.PikkuAgentChat; } });
|
|
14
|
+
var use_file_attachment_js_1 = require("./use-file-attachment.js");
|
|
15
|
+
Object.defineProperty(exports, "useFileAttachment", { enumerable: true, get: function () { return use_file_attachment_js_1.useFileAttachment; } });
|
|
16
|
+
Object.defineProperty(exports, "INLINE_SIZE_LIMIT", { enumerable: true, get: function () { return use_file_attachment_js_1.INLINE_SIZE_LIMIT; } });
|
|
17
|
+
var model_capabilities_js_1 = require("./model-capabilities.js");
|
|
18
|
+
Object.defineProperty(exports, "modelSupportsVision", { enumerable: true, get: function () { return model_capabilities_js_1.modelSupportsVision; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function modelSupportsVision(modelID: string): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.modelSupportsVision = modelSupportsVision;
|
|
4
|
+
const MODEL_VISION_SUPPORT = new Set([
|
|
5
|
+
'claude-sonnet-4-5',
|
|
6
|
+
'claude-opus-4-1',
|
|
7
|
+
'gpt-4o-mini',
|
|
8
|
+
'gpt-5',
|
|
9
|
+
'gpt-5-mini',
|
|
10
|
+
'gpt-5-codex',
|
|
11
|
+
'gemini-2.5-pro',
|
|
12
|
+
'gemini-2.5-flash',
|
|
13
|
+
]);
|
|
14
|
+
function modelSupportsVision(modelID) {
|
|
15
|
+
return MODEL_VISION_SUPPORT.has(modelID);
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type PendingFile = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
previewUrl: string;
|
|
6
|
+
contentUrl: string;
|
|
7
|
+
isImage: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type UploadAttachmentFn = (args: {
|
|
10
|
+
contentType: string;
|
|
11
|
+
sizeBytes: number;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
uploadUrl: string;
|
|
14
|
+
signedReadUrl: string;
|
|
15
|
+
uploadMethod?: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const INLINE_SIZE_LIMIT: number;
|
|
18
|
+
export declare function useFileAttachment(upload: UploadAttachmentFn): {
|
|
19
|
+
pendingFiles: PendingFile[];
|
|
20
|
+
uploading: boolean;
|
|
21
|
+
uploadError: string | null;
|
|
22
|
+
fileInputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
23
|
+
handleFileChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
24
|
+
removeFile: (id: string) => void;
|
|
25
|
+
clearFiles: () => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.INLINE_SIZE_LIMIT = void 0;
|
|
13
|
+
exports.useFileAttachment = useFileAttachment;
|
|
14
|
+
const react_1 = require("react");
|
|
15
|
+
exports.INLINE_SIZE_LIMIT = 1 * 1024 * 1024;
|
|
16
|
+
function useFileAttachment(upload) {
|
|
17
|
+
const [pendingFiles, setPendingFiles] = (0, react_1.useState)([]);
|
|
18
|
+
const [uploading, setUploading] = (0, react_1.useState)(false);
|
|
19
|
+
const [uploadError, setUploadError] = (0, react_1.useState)(null);
|
|
20
|
+
const fileInputRef = (0, react_1.useRef)(null);
|
|
21
|
+
const handleFileChange = (0, react_1.useCallback)((e) => __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
var _a;
|
|
23
|
+
const files = Array.from((_a = e.target.files) !== null && _a !== void 0 ? _a : []);
|
|
24
|
+
e.target.value = '';
|
|
25
|
+
if (!files.length)
|
|
26
|
+
return;
|
|
27
|
+
setUploading(true);
|
|
28
|
+
setUploadError(null);
|
|
29
|
+
try {
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
const previewUrl = URL.createObjectURL(file);
|
|
32
|
+
let contentUrl;
|
|
33
|
+
if (file.type.startsWith('image/') &&
|
|
34
|
+
file.size <= exports.INLINE_SIZE_LIMIT) {
|
|
35
|
+
contentUrl = yield new Promise((resolve, reject) => {
|
|
36
|
+
const reader = new FileReader();
|
|
37
|
+
reader.onload = (ev) => resolve(ev.target.result);
|
|
38
|
+
reader.onerror = reject;
|
|
39
|
+
reader.readAsDataURL(file);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const { uploadUrl, signedReadUrl, uploadMethod } = yield upload({
|
|
44
|
+
contentType: file.type,
|
|
45
|
+
sizeBytes: file.size,
|
|
46
|
+
});
|
|
47
|
+
const resp = yield fetch(uploadUrl, {
|
|
48
|
+
method: uploadMethod !== null && uploadMethod !== void 0 ? uploadMethod : 'PUT',
|
|
49
|
+
body: file,
|
|
50
|
+
headers: { 'Content-Type': file.type },
|
|
51
|
+
});
|
|
52
|
+
if (!resp.ok)
|
|
53
|
+
throw new Error(`Upload failed (${resp.status})`);
|
|
54
|
+
contentUrl = signedReadUrl;
|
|
55
|
+
}
|
|
56
|
+
setPendingFiles((prev) => [
|
|
57
|
+
...prev,
|
|
58
|
+
{
|
|
59
|
+
id: `file_${Date.now().toString(36)}`,
|
|
60
|
+
name: file.name,
|
|
61
|
+
mimeType: file.type,
|
|
62
|
+
previewUrl,
|
|
63
|
+
contentUrl,
|
|
64
|
+
isImage: file.type.startsWith('image/'),
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
setUploadError(err instanceof Error ? err.message : 'Upload failed');
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
setUploading(false);
|
|
74
|
+
}
|
|
75
|
+
}), [upload]);
|
|
76
|
+
const removeFile = (0, react_1.useCallback)((id) => {
|
|
77
|
+
setPendingFiles((prev) => {
|
|
78
|
+
const f = prev.find((x) => x.id === id);
|
|
79
|
+
if (f)
|
|
80
|
+
URL.revokeObjectURL(f.previewUrl);
|
|
81
|
+
return prev.filter((x) => x.id !== id);
|
|
82
|
+
});
|
|
83
|
+
}, []);
|
|
84
|
+
const clearFiles = (0, react_1.useCallback)(() => {
|
|
85
|
+
setPendingFiles((prev) => {
|
|
86
|
+
prev.forEach((f) => URL.revokeObjectURL(f.previewUrl));
|
|
87
|
+
return [];
|
|
88
|
+
});
|
|
89
|
+
}, []);
|
|
90
|
+
return {
|
|
91
|
+
pendingFiles,
|
|
92
|
+
uploading,
|
|
93
|
+
uploadError,
|
|
94
|
+
fileInputRef,
|
|
95
|
+
handleFileChange,
|
|
96
|
+
removeFile,
|
|
97
|
+
clearFiles,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -10,6 +10,10 @@ export interface PikkuAgentRuntimeOptions {
|
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
11
|
model?: string;
|
|
12
12
|
temperature?: number;
|
|
13
|
+
/** Structured context injected into the agent's system instructions.
|
|
14
|
+
* Provide upfront state (e.g. current org/project/branch/deployment IDs)
|
|
15
|
+
* so the agent can call tools without asking the user. */
|
|
16
|
+
context?: string;
|
|
13
17
|
}
|
|
14
18
|
export interface PendingApproval {
|
|
15
19
|
toolCallId: string;
|
|
@@ -379,14 +379,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
379
379
|
response = yield __await(fetch(`${opts.api}/${opts.agentName}/stream`, {
|
|
380
380
|
method: 'POST',
|
|
381
381
|
headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
|
|
382
|
-
body: JSON.stringify({
|
|
383
|
-
agentName: opts.agentName,
|
|
384
|
-
message: messageText,
|
|
385
|
-
threadId: opts.threadId,
|
|
386
|
-
resourceId: opts.resourceId,
|
|
387
|
-
model: opts.model,
|
|
388
|
-
temperature: opts.temperature,
|
|
389
|
-
}),
|
|
382
|
+
body: JSON.stringify(Object.assign({ agentName: opts.agentName, message: messageText, threadId: opts.threadId, resourceId: opts.resourceId, model: opts.model, temperature: opts.temperature }, (opts.context ? { context: opts.context } : {}))),
|
|
390
383
|
signal: abortSignal,
|
|
391
384
|
credentials: opts.credentials,
|
|
392
385
|
}));
|
|
@@ -729,14 +722,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
729
722
|
const response = yield __await(fetch(`${opts.api}/${opts.agentName}`, {
|
|
730
723
|
method: 'POST',
|
|
731
724
|
headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
|
|
732
|
-
body: JSON.stringify({
|
|
733
|
-
agentName: opts.agentName,
|
|
734
|
-
message: messageText,
|
|
735
|
-
threadId: opts.threadId,
|
|
736
|
-
resourceId: opts.resourceId,
|
|
737
|
-
model: opts.model,
|
|
738
|
-
temperature: opts.temperature,
|
|
739
|
-
}),
|
|
725
|
+
body: JSON.stringify(Object.assign({ agentName: opts.agentName, message: messageText, threadId: opts.threadId, resourceId: opts.resourceId, model: opts.model, temperature: opts.temperature }, (opts.context ? { context: opts.context } : {}))),
|
|
740
726
|
signal: abortSignal,
|
|
741
727
|
credentials: opts.credentials,
|
|
742
728
|
}));
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,3 +2,6 @@ export { usePikkuAgentRuntime, usePikkuAgentNonStreamingRuntime, PikkuApprovalCo
|
|
|
2
2
|
export type { PikkuAgentRuntimeOptions, PendingApproval, PikkuApprovalContextValue, PikkuToolStatusType, PikkuToolStatus, MissingCredentialPayload, } from './use-pikku-agent-runtime.js';
|
|
3
3
|
export { PikkuAgentChat } from './pikku-agent-chat.js';
|
|
4
4
|
export type { PikkuAgentChatProps } from './pikku-agent-chat.js';
|
|
5
|
+
export { useFileAttachment, INLINE_SIZE_LIMIT } from './use-file-attachment.js';
|
|
6
|
+
export type { PendingFile, UploadAttachmentFn } from './use-file-attachment.js';
|
|
7
|
+
export { modelSupportsVision } from './model-capabilities.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { usePikkuAgentRuntime, usePikkuAgentNonStreamingRuntime, PikkuApprovalContext, usePikkuApproval, convertDbMessages, isDeniedResult, resolvePikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
2
2
|
export { PikkuAgentChat } from './pikku-agent-chat.js';
|
|
3
|
+
export { useFileAttachment, INLINE_SIZE_LIMIT } from './use-file-attachment.js';
|
|
4
|
+
export { modelSupportsVision } from './model-capabilities.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function modelSupportsVision(modelID: string): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
export function modelSupportsVision(modelID) {
|
|
12
|
+
return MODEL_VISION_SUPPORT.has(modelID);
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type PendingFile = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
previewUrl: string;
|
|
6
|
+
contentUrl: string;
|
|
7
|
+
isImage: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type UploadAttachmentFn = (args: {
|
|
10
|
+
contentType: string;
|
|
11
|
+
sizeBytes: number;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
uploadUrl: string;
|
|
14
|
+
signedReadUrl: string;
|
|
15
|
+
uploadMethod?: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const INLINE_SIZE_LIMIT: number;
|
|
18
|
+
export declare function useFileAttachment(upload: UploadAttachmentFn): {
|
|
19
|
+
pendingFiles: PendingFile[];
|
|
20
|
+
uploading: boolean;
|
|
21
|
+
uploadError: string | null;
|
|
22
|
+
fileInputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
23
|
+
handleFileChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
24
|
+
removeFile: (id: string) => void;
|
|
25
|
+
clearFiles: () => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
export const INLINE_SIZE_LIMIT = 1 * 1024 * 1024;
|
|
3
|
+
export function useFileAttachment(upload) {
|
|
4
|
+
const [pendingFiles, setPendingFiles] = useState([]);
|
|
5
|
+
const [uploading, setUploading] = useState(false);
|
|
6
|
+
const [uploadError, setUploadError] = useState(null);
|
|
7
|
+
const fileInputRef = useRef(null);
|
|
8
|
+
const handleFileChange = useCallback(async (e) => {
|
|
9
|
+
const files = Array.from(e.target.files ?? []);
|
|
10
|
+
e.target.value = '';
|
|
11
|
+
if (!files.length)
|
|
12
|
+
return;
|
|
13
|
+
setUploading(true);
|
|
14
|
+
setUploadError(null);
|
|
15
|
+
try {
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const previewUrl = URL.createObjectURL(file);
|
|
18
|
+
let contentUrl;
|
|
19
|
+
if (file.type.startsWith('image/') &&
|
|
20
|
+
file.size <= INLINE_SIZE_LIMIT) {
|
|
21
|
+
contentUrl = await new Promise((resolve, reject) => {
|
|
22
|
+
const reader = new FileReader();
|
|
23
|
+
reader.onload = (ev) => resolve(ev.target.result);
|
|
24
|
+
reader.onerror = reject;
|
|
25
|
+
reader.readAsDataURL(file);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const { uploadUrl, signedReadUrl, uploadMethod } = await upload({
|
|
30
|
+
contentType: file.type,
|
|
31
|
+
sizeBytes: file.size,
|
|
32
|
+
});
|
|
33
|
+
const resp = await fetch(uploadUrl, {
|
|
34
|
+
method: uploadMethod ?? 'PUT',
|
|
35
|
+
body: file,
|
|
36
|
+
headers: { 'Content-Type': file.type },
|
|
37
|
+
});
|
|
38
|
+
if (!resp.ok)
|
|
39
|
+
throw new Error(`Upload failed (${resp.status})`);
|
|
40
|
+
contentUrl = signedReadUrl;
|
|
41
|
+
}
|
|
42
|
+
setPendingFiles((prev) => [
|
|
43
|
+
...prev,
|
|
44
|
+
{
|
|
45
|
+
id: `file_${Date.now().toString(36)}`,
|
|
46
|
+
name: file.name,
|
|
47
|
+
mimeType: file.type,
|
|
48
|
+
previewUrl,
|
|
49
|
+
contentUrl,
|
|
50
|
+
isImage: file.type.startsWith('image/'),
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
setUploadError(err instanceof Error ? err.message : 'Upload failed');
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
setUploading(false);
|
|
60
|
+
}
|
|
61
|
+
}, [upload]);
|
|
62
|
+
const removeFile = useCallback((id) => {
|
|
63
|
+
setPendingFiles((prev) => {
|
|
64
|
+
const f = prev.find((x) => x.id === id);
|
|
65
|
+
if (f)
|
|
66
|
+
URL.revokeObjectURL(f.previewUrl);
|
|
67
|
+
return prev.filter((x) => x.id !== id);
|
|
68
|
+
});
|
|
69
|
+
}, []);
|
|
70
|
+
const clearFiles = useCallback(() => {
|
|
71
|
+
setPendingFiles((prev) => {
|
|
72
|
+
prev.forEach((f) => URL.revokeObjectURL(f.previewUrl));
|
|
73
|
+
return [];
|
|
74
|
+
});
|
|
75
|
+
}, []);
|
|
76
|
+
return {
|
|
77
|
+
pendingFiles,
|
|
78
|
+
uploading,
|
|
79
|
+
uploadError,
|
|
80
|
+
fileInputRef,
|
|
81
|
+
handleFileChange,
|
|
82
|
+
removeFile,
|
|
83
|
+
clearFiles,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -10,6 +10,10 @@ export interface PikkuAgentRuntimeOptions {
|
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
11
|
model?: string;
|
|
12
12
|
temperature?: number;
|
|
13
|
+
/** Structured context injected into the agent's system instructions.
|
|
14
|
+
* Provide upfront state (e.g. current org/project/branch/deployment IDs)
|
|
15
|
+
* so the agent can call tools without asking the user. */
|
|
16
|
+
context?: string;
|
|
13
17
|
}
|
|
14
18
|
export interface PendingApproval {
|
|
15
19
|
toolCallId: string;
|
|
@@ -334,6 +334,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
334
334
|
resourceId: opts.resourceId,
|
|
335
335
|
model: opts.model,
|
|
336
336
|
temperature: opts.temperature,
|
|
337
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
337
338
|
}),
|
|
338
339
|
signal: abortSignal,
|
|
339
340
|
credentials: opts.credentials,
|
|
@@ -688,6 +689,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
688
689
|
resourceId: opts.resourceId,
|
|
689
690
|
model: opts.model,
|
|
690
691
|
temperature: opts.temperature,
|
|
692
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
691
693
|
}),
|
|
692
694
|
signal: abortSignal,
|
|
693
695
|
credentials: opts.credentials,
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export type PendingFile = {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
mimeType: string
|
|
7
|
+
previewUrl: string
|
|
8
|
+
contentUrl: string
|
|
9
|
+
isImage: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type UploadAttachmentFn = (args: {
|
|
13
|
+
contentType: string
|
|
14
|
+
sizeBytes: number
|
|
15
|
+
}) => Promise<{
|
|
16
|
+
uploadUrl: string
|
|
17
|
+
signedReadUrl: string
|
|
18
|
+
uploadMethod?: string
|
|
19
|
+
}>
|
|
20
|
+
|
|
21
|
+
export const INLINE_SIZE_LIMIT = 1 * 1024 * 1024
|
|
22
|
+
|
|
23
|
+
export function useFileAttachment(upload: UploadAttachmentFn) {
|
|
24
|
+
const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([])
|
|
25
|
+
const [uploading, setUploading] = useState(false)
|
|
26
|
+
const [uploadError, setUploadError] = useState<string | null>(null)
|
|
27
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
28
|
+
|
|
29
|
+
const handleFileChange = useCallback(
|
|
30
|
+
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
31
|
+
const files = Array.from(e.target.files ?? [])
|
|
32
|
+
e.target.value = ''
|
|
33
|
+
if (!files.length) return
|
|
34
|
+
setUploading(true)
|
|
35
|
+
setUploadError(null)
|
|
36
|
+
try {
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const previewUrl = URL.createObjectURL(file)
|
|
39
|
+
let contentUrl: string
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
file.type.startsWith('image/') &&
|
|
43
|
+
file.size <= INLINE_SIZE_LIMIT
|
|
44
|
+
) {
|
|
45
|
+
contentUrl = await new Promise<string>((resolve, reject) => {
|
|
46
|
+
const reader = new FileReader()
|
|
47
|
+
reader.onload = (ev) => resolve(ev.target!.result as string)
|
|
48
|
+
reader.onerror = reject
|
|
49
|
+
reader.readAsDataURL(file)
|
|
50
|
+
})
|
|
51
|
+
} else {
|
|
52
|
+
const { uploadUrl, signedReadUrl, uploadMethod } = await upload({
|
|
53
|
+
contentType: file.type,
|
|
54
|
+
sizeBytes: file.size,
|
|
55
|
+
})
|
|
56
|
+
const resp = await fetch(uploadUrl, {
|
|
57
|
+
method: uploadMethod ?? 'PUT',
|
|
58
|
+
body: file,
|
|
59
|
+
headers: { 'Content-Type': file.type },
|
|
60
|
+
})
|
|
61
|
+
if (!resp.ok) throw new Error(`Upload failed (${resp.status})`)
|
|
62
|
+
contentUrl = signedReadUrl
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setPendingFiles((prev) => [
|
|
66
|
+
...prev,
|
|
67
|
+
{
|
|
68
|
+
id: `file_${Date.now().toString(36)}`,
|
|
69
|
+
name: file.name,
|
|
70
|
+
mimeType: file.type,
|
|
71
|
+
previewUrl,
|
|
72
|
+
contentUrl,
|
|
73
|
+
isImage: file.type.startsWith('image/'),
|
|
74
|
+
},
|
|
75
|
+
])
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
setUploadError(err instanceof Error ? err.message : 'Upload failed')
|
|
79
|
+
} finally {
|
|
80
|
+
setUploading(false)
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[upload]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const removeFile = useCallback((id: string) => {
|
|
87
|
+
setPendingFiles((prev) => {
|
|
88
|
+
const f = prev.find((x) => x.id === id)
|
|
89
|
+
if (f) URL.revokeObjectURL(f.previewUrl)
|
|
90
|
+
return prev.filter((x) => x.id !== id)
|
|
91
|
+
})
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
const clearFiles = useCallback(() => {
|
|
95
|
+
setPendingFiles((prev) => {
|
|
96
|
+
prev.forEach((f) => URL.revokeObjectURL(f.previewUrl))
|
|
97
|
+
return []
|
|
98
|
+
})
|
|
99
|
+
}, [])
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
pendingFiles,
|
|
103
|
+
uploading,
|
|
104
|
+
uploadError,
|
|
105
|
+
fileInputRef,
|
|
106
|
+
handleFileChange,
|
|
107
|
+
removeFile,
|
|
108
|
+
clearFiles,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -23,6 +23,10 @@ export interface PikkuAgentRuntimeOptions {
|
|
|
23
23
|
headers?: Record<string, string>
|
|
24
24
|
model?: string
|
|
25
25
|
temperature?: number
|
|
26
|
+
/** Structured context injected into the agent's system instructions.
|
|
27
|
+
* Provide upfront state (e.g. current org/project/branch/deployment IDs)
|
|
28
|
+
* so the agent can call tools without asking the user. */
|
|
29
|
+
context?: string
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export interface PendingApproval {
|
|
@@ -486,6 +490,7 @@ function createPikkuStreamingAdapter(
|
|
|
486
490
|
resourceId: opts.resourceId,
|
|
487
491
|
model: opts.model,
|
|
488
492
|
temperature: opts.temperature,
|
|
493
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
489
494
|
}),
|
|
490
495
|
signal: abortSignal,
|
|
491
496
|
credentials: opts.credentials,
|
|
@@ -948,6 +953,7 @@ function createPikkuNonStreamingAdapter(
|
|
|
948
953
|
resourceId: opts.resourceId,
|
|
949
954
|
model: opts.model,
|
|
950
955
|
temperature: opts.temperature,
|
|
956
|
+
...(opts.context ? { context: opts.context } : {}),
|
|
951
957
|
}),
|
|
952
958
|
signal: abortSignal,
|
|
953
959
|
credentials: opts.credentials,
|