@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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pikku/assistant-ui
2
2
 
3
+ ## 0.12.6
4
+
5
+ ### Patch Changes
6
+
7
+ - f4f7046: feat(assistant-ui): export useFileAttachment hook, modelSupportsVision, PendingFile, UploadAttachmentFn, and INLINE_SIZE_LIMIT
8
+
9
+ ## 0.12.5
10
+
11
+ ### Patch Changes
12
+
13
+ - 9060165: The console now shows function version history, live queue depths with a Failed column, and scheduler last-run status with run history. Workflow canvas and run selector have been polished. The console build is ~6.5× faster thanks to a switch to rolldown-vite (Vite 7 + Oxc React transform).
14
+
3
15
  ## 0.12.4
4
16
 
5
17
  ### Patch Changes
@@ -33,7 +45,6 @@
33
45
  ### Patch Changes
34
46
 
35
47
  - f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
36
-
37
48
  - Unified CredentialService with lazy loading per user via pikkuUserId
38
49
  - wire.getCredential() for typed single credential lookup
39
50
  - MissingCredentialError with structured payload for client-side connect flows
@@ -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
+ }
@@ -1,6 +1,8 @@
1
+ import { type ComponentType, type ReactNode } from 'react';
1
2
  import { type ToolCallMessagePartComponent } from '@assistant-ui/react';
2
3
  import { type PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js';
3
4
  export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
5
+ initialPrompt?: string;
4
6
  emptyMessage?: string;
5
7
  /** Hide tool calls from the chat display.
6
8
  * - `true`: hide all non-approval tool calls
@@ -21,5 +23,7 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
21
23
  * (which still respects `hideToolCalls` and the approval-request UI).
22
24
  */
23
25
  toolComponents?: Record<string, ToolCallMessagePartComponent>;
26
+ renderAssistantText?: (text: string) => ReactNode;
27
+ generativeUIComponents?: Record<string, ComponentType<any>>;
24
28
  }
25
29
  export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
@@ -60,6 +60,8 @@ const darkColors = {
60
60
  const ColorsContext = (0, react_1.createContext)(lightColors);
61
61
  const HideToolCallsContext = (0, react_1.createContext)(undefined);
62
62
  const ToolComponentsContext = (0, react_1.createContext)(undefined);
63
+ const GenerativeUIComponentsContext = (0, react_1.createContext)(undefined);
64
+ const RenderAssistantTextContext = (0, react_1.createContext)(undefined);
63
65
  function shouldHideToolCall(hideToolCalls, toolName) {
64
66
  if (!hideToolCalls)
65
67
  return false;
@@ -310,6 +312,8 @@ const UserMessage = () => {
310
312
  const AssistantMessage = () => {
311
313
  const colors = (0, react_1.useContext)(ColorsContext);
312
314
  const toolComponents = (0, react_1.useContext)(ToolComponentsContext);
315
+ const generativeUIComponents = (0, react_1.useContext)(GenerativeUIComponentsContext);
316
+ const renderAssistantText = (0, react_1.useContext)(RenderAssistantTextContext);
313
317
  return ((0, jsx_runtime_1.jsx)("div", { style: {
314
318
  display: 'flex',
315
319
  justifyContent: 'flex-start',
@@ -318,13 +322,16 @@ const AssistantMessage = () => {
318
322
  padding: 12,
319
323
  borderRadius: 12,
320
324
  backgroundColor: colors.assistantBubble,
321
- }, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: {
322
- Text: ({ text }) => (0, jsx_runtime_1.jsx)(MarkdownText, { text: text, colors: colors }),
323
- tools: {
325
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: Object.assign({ Text: ({ text }) => renderAssistantText ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: renderAssistantText(text) })) : ((0, jsx_runtime_1.jsx)(MarkdownText, { text: text, colors: colors })), tools: {
324
326
  by_name: toolComponents,
325
327
  Fallback: (props) => ((0, jsx_runtime_1.jsx)(ToolCallDisplay, { toolCallId: props.toolCallId, toolName: props.toolName, args: props.args, result: props.result, status: (0, use_pikku_agent_runtime_js_1.resolvePikkuToolStatus)(props.status, props.result), addResult: props.addResult })),
326
- },
327
- } }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { last: true, children: (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.If, { running: true, children: (0, jsx_runtime_1.jsx)("div", { style: {
328
+ } }, (generativeUIComponents
329
+ ? {
330
+ generativeUI: {
331
+ components: generativeUIComponents,
332
+ },
333
+ }
334
+ : {})) }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { last: true, children: (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.If, { running: true, children: (0, jsx_runtime_1.jsx)("div", { style: {
328
335
  display: 'flex',
329
336
  alignItems: 'center',
330
337
  gap: 6,
@@ -333,78 +340,99 @@ const AssistantMessage = () => {
333
340
  color: colors.textMuted,
334
341
  }, children: "Thinking..." }) }) })] })] }) }));
335
342
  };
343
+ const ComposerPrefill = ({ text }) => {
344
+ const composer = (0, react_2.useComposerRuntime)();
345
+ const filled = (0, react_1.useRef)(false);
346
+ (0, react_1.useEffect)(() => {
347
+ if (filled.current || !text)
348
+ return;
349
+ filled.current = true;
350
+ if (composer.getState().text === '')
351
+ composer.setText(text);
352
+ }, [text, composer]);
353
+ return null;
354
+ };
336
355
  const PikkuComposer = ({ disabled, }) => {
337
356
  const colors = (0, react_1.useContext)(ColorsContext);
338
- return ((0, jsx_runtime_1.jsx)("div", { style: { padding: '8px 0 16px' }, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Root, { children: (0, jsx_runtime_1.jsxs)("div", { style: Object.assign({ border: `1px solid ${colors.border}`, borderRadius: 12, overflow: 'hidden', display: 'flex', alignItems: 'flex-end', padding: '6px 12px', gap: 8 }, (disabled
357
+ return ((0, jsx_runtime_1.jsx)("div", { style: { padding: '8px 0 16px' }, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Root, { children: (0, jsx_runtime_1.jsxs)("div", { style: Object.assign({ position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', gap: 10, backgroundColor: colors.assistantBubble, border: `1px solid ${colors.border}`, borderRadius: 24, padding: '14px 12px 10px', 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)' }, (disabled
339
360
  ? { opacity: 0.5, pointerEvents: 'none' }
340
- : {})), children: [(0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 2, disabled: disabled, style: {
341
- flex: 1,
361
+ : {})), children: [(0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 1, disabled: disabled !== null && disabled !== void 0 ? disabled : false, style: {
362
+ width: '100%',
363
+ background: 'transparent',
342
364
  border: 'none',
343
365
  outline: 'none',
344
- resize: 'none',
366
+ color: colors.text,
345
367
  fontSize: 14,
346
368
  fontFamily: 'inherit',
347
- padding: '4px 0',
348
- background: colors.inputBg,
349
- color: colors.text,
350
- } }), (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Send, { disabled: disabled, style: {
351
- width: 28,
352
- height: 28,
353
- borderRadius: '50%',
354
- border: 'none',
355
- background: disabled ? colors.textMuted : colors.sendBg,
356
- color: colors.sendColor,
357
- cursor: disabled ? 'not-allowed' : 'pointer',
369
+ resize: 'none',
370
+ lineHeight: 1.5,
371
+ overflowY: 'auto',
372
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
373
+ width: '100%',
358
374
  display: 'flex',
359
375
  alignItems: 'center',
360
- justifyContent: 'center',
361
- flexShrink: 0,
362
- marginBottom: 2,
363
- fontSize: 14,
364
- }, children: "\u25B6" })] }) }) }));
376
+ gap: 6,
377
+ minWidth: 0,
378
+ justifyContent: 'flex-end',
379
+ }, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Send, { disabled: disabled !== null && disabled !== void 0 ? disabled : false, style: {
380
+ width: 30,
381
+ height: 30,
382
+ borderRadius: 999,
383
+ border: `1px solid ${colors.border}`,
384
+ background: '#e8e8e8',
385
+ color: '#111111',
386
+ cursor: disabled ? 'not-allowed' : 'pointer',
387
+ display: 'flex',
388
+ alignItems: 'center',
389
+ justifyContent: 'center',
390
+ flexShrink: 0,
391
+ transition: 'background-color 150ms ease, color 150ms ease, border-color 150ms ease',
392
+ }, children: "\u2191" }) })] }) }) }));
365
393
  };
366
394
  function PikkuAgentChat(props) {
367
- const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth", "toolComponents"]);
395
+ const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, renderAssistantText, generativeUIComponents, initialPrompt } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth", "toolComponents", "renderAssistantText", "generativeUIComponents", "initialPrompt"]);
368
396
  const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = (0, use_pikku_agent_runtime_js_1.usePikkuAgentRuntime)(runtimeOptions);
369
397
  const colors = dark ? darkColors : lightColors;
370
- return ((0, jsx_runtime_1.jsx)(ColorsContext.Provider, { value: colors, children: (0, jsx_runtime_1.jsx)(use_pikku_agent_runtime_js_1.PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: (0, jsx_runtime_1.jsx)(HideToolCallsContext.Provider, { value: hideToolCalls, children: (0, jsx_runtime_1.jsx)(ToolComponentsContext.Provider, { value: toolComponents, children: (0, jsx_runtime_1.jsx)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsx)("div", { style: {
371
- height: '100%',
372
- display: 'flex',
373
- flexDirection: 'column',
374
- background: colors.bg,
375
- }, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { style: {
376
- display: 'flex',
377
- flexDirection: 'column',
378
- flex: 1,
379
- minHeight: 0,
380
- }, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Viewport, { style: {
381
- flex: 1,
382
- minHeight: 0,
383
- overflowY: 'auto',
384
- }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
385
- maxWidth: maxWidth === 'none' ? undefined : maxWidth,
386
- margin: '0 auto',
387
- padding: 16,
398
+ return ((0, jsx_runtime_1.jsx)(ColorsContext.Provider, { value: colors, children: (0, jsx_runtime_1.jsx)(use_pikku_agent_runtime_js_1.PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: (0, jsx_runtime_1.jsx)(HideToolCallsContext.Provider, { value: hideToolCalls, children: (0, jsx_runtime_1.jsx)(ToolComponentsContext.Provider, { value: toolComponents, children: (0, jsx_runtime_1.jsx)(GenerativeUIComponentsContext.Provider, { value: generativeUIComponents, children: (0, jsx_runtime_1.jsx)(RenderAssistantTextContext.Provider, { value: renderAssistantText, children: (0, jsx_runtime_1.jsxs)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: [(0, jsx_runtime_1.jsx)(ComposerPrefill, { text: initialPrompt }), (0, jsx_runtime_1.jsx)("div", { style: {
399
+ height: '100%',
400
+ display: 'flex',
401
+ flexDirection: 'column',
402
+ background: colors.bg,
403
+ }, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { style: {
388
404
  display: 'flex',
389
405
  flexDirection: 'column',
390
- gap: 16,
391
- }, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: (0, jsx_runtime_1.jsx)("div", { style: {
406
+ flex: 1,
407
+ minHeight: 0,
408
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Viewport, { style: {
409
+ flex: 1,
410
+ minHeight: 0,
411
+ overflowY: 'auto',
412
+ }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
413
+ maxWidth: maxWidth === 'none' ? undefined : maxWidth,
414
+ margin: '0 auto',
415
+ padding: 16,
392
416
  display: 'flex',
393
- alignItems: 'center',
394
- justifyContent: 'center',
395
- minHeight: 300,
396
- color: colors.textMuted,
397
- textAlign: 'center',
398
- fontSize: 14,
399
- }, children: emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : (props.threadId
400
- ? 'Send a message to start the conversation.'
401
- : 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
402
- UserMessage,
403
- AssistantMessage,
404
- } })] }) }), (0, jsx_runtime_1.jsx)("div", { style: {
405
- maxWidth: maxWidth === 'none' ? undefined : maxWidth,
406
- margin: '0 auto',
407
- width: '100%',
408
- padding: '0 16px',
409
- }, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }) }));
417
+ flexDirection: 'column',
418
+ gap: 16,
419
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: (0, jsx_runtime_1.jsx)("div", { style: {
420
+ display: 'flex',
421
+ alignItems: 'center',
422
+ justifyContent: 'center',
423
+ minHeight: 300,
424
+ color: colors.textMuted,
425
+ textAlign: 'center',
426
+ fontSize: 14,
427
+ }, children: emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : (props.threadId
428
+ ? 'Send a message to start the conversation.'
429
+ : 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
430
+ UserMessage,
431
+ AssistantMessage,
432
+ } })] }) }), (0, jsx_runtime_1.jsx)("div", { style: {
433
+ maxWidth: maxWidth === 'none' ? undefined : maxWidth,
434
+ margin: '0 auto',
435
+ width: '100%',
436
+ padding: '0 16px',
437
+ }, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) })] }) }) }) }) }) }) }));
410
438
  }
@@ -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;