@pikku/assistant-ui 0.12.1 → 0.12.3
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 +20 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/pikku-agent-chat.d.ts +2 -0
- package/dist/cjs/pikku-agent-chat.js +14 -4
- package/dist/cjs/use-pikku-agent-runtime.d.ts +15 -2
- package/dist/cjs/use-pikku-agent-runtime.js +81 -15
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/pikku-agent-chat.d.ts +2 -0
- package/dist/esm/pikku-agent-chat.js +14 -4
- package/dist/esm/use-pikku-agent-runtime.d.ts +15 -2
- package/dist/esm/use-pikku-agent-runtime.js +81 -15
- package/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/pikku-agent-chat.tsx +24 -3
- package/src/use-pikku-agent-runtime.ts +95 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @pikku/assistant-ui
|
|
2
2
|
|
|
3
|
+
## 0.12.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
|
|
8
|
+
|
|
9
|
+
- Unified CredentialService with lazy loading per user via pikkuUserId
|
|
10
|
+
- wire.getCredential() for typed single credential lookup
|
|
11
|
+
- MissingCredentialError with structured payload for client-side connect flows
|
|
12
|
+
- Console UI: Global/Users credential tabs, per-user OAuth connect/revoke
|
|
13
|
+
- AI agent pre-flight check: detects missing OAuth credentials from addon metadata, shows "Connect your accounts" prompt before chat
|
|
14
|
+
- CLI codegen: generates credentialsMeta per addon package for runtime lookup
|
|
15
|
+
- Vercel AI runner: catches MissingCredentialError as runtime fallback
|
|
16
|
+
|
|
17
|
+
## 0.12.2
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- cc4a8e0: Show friendly error messages in agent chat instead of silently failing with a loading spinner
|
|
22
|
+
|
|
3
23
|
## 0.12.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { usePikkuAgentRuntime, usePikkuAgentNonStreamingRuntime, PikkuApprovalContext, usePikkuApproval, convertDbMessages, isDeniedResult, resolvePikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
2
|
-
export type { PikkuAgentRuntimeOptions, PendingApproval, PikkuApprovalContextValue, PikkuToolStatusType, PikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
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';
|
|
@@ -7,5 +7,7 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
hideToolCalls?: boolean | string[];
|
|
9
9
|
dark?: boolean;
|
|
10
|
+
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
|
+
maxWidth?: number | 'none';
|
|
10
12
|
}
|
|
11
13
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -34,6 +34,8 @@ const lightColors = {
|
|
|
34
34
|
successColor: '#2e7d32',
|
|
35
35
|
errorBg: '#ffebee',
|
|
36
36
|
errorColor: '#c62828',
|
|
37
|
+
warningBg: '#fff3e0',
|
|
38
|
+
warningColor: '#e65100',
|
|
37
39
|
};
|
|
38
40
|
const darkColors = {
|
|
39
41
|
bg: 'transparent',
|
|
@@ -52,6 +54,8 @@ const darkColors = {
|
|
|
52
54
|
successColor: '#00e68a',
|
|
53
55
|
errorBg: 'rgba(220, 38, 38, 0.1)',
|
|
54
56
|
errorColor: '#f87171',
|
|
57
|
+
warningBg: 'rgba(245, 158, 11, 0.1)',
|
|
58
|
+
warningColor: '#fbbf24',
|
|
55
59
|
};
|
|
56
60
|
const ColorsContext = (0, react_1.createContext)(lightColors);
|
|
57
61
|
const HideToolCallsContext = (0, react_1.createContext)(undefined);
|
|
@@ -170,7 +174,13 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
170
174
|
borderRadius: 3,
|
|
171
175
|
background: colors.errorBg,
|
|
172
176
|
color: colors.errorColor,
|
|
173
|
-
}, children: "error" })), status.type === '
|
|
177
|
+
}, children: "error" })), status.type === 'missing-credential' && ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
178
|
+
fontSize: 11,
|
|
179
|
+
padding: '1px 5px',
|
|
180
|
+
borderRadius: 3,
|
|
181
|
+
background: colors.warningBg,
|
|
182
|
+
color: colors.warningColor,
|
|
183
|
+
}, children: "credential required" })), status.type === 'denied' && ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
174
184
|
fontSize: 11,
|
|
175
185
|
padding: '1px 5px',
|
|
176
186
|
borderRadius: 3,
|
|
@@ -295,7 +305,7 @@ const PikkuComposer = ({ disabled, }) => {
|
|
|
295
305
|
}, children: "\u25B6" })] }) }) }));
|
|
296
306
|
};
|
|
297
307
|
function PikkuAgentChat(props) {
|
|
298
|
-
const { emptyMessage, hideToolCalls, dark } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark"]);
|
|
308
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768 } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth"]);
|
|
299
309
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = (0, use_pikku_agent_runtime_js_1.usePikkuAgentRuntime)(runtimeOptions);
|
|
300
310
|
const colors = dark ? darkColors : lightColors;
|
|
301
311
|
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)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
@@ -313,7 +323,7 @@ function PikkuAgentChat(props) {
|
|
|
313
323
|
minHeight: 0,
|
|
314
324
|
overflowY: 'auto',
|
|
315
325
|
}, children: (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
316
|
-
maxWidth:
|
|
326
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
317
327
|
margin: '0 auto',
|
|
318
328
|
padding: 16,
|
|
319
329
|
display: 'flex',
|
|
@@ -332,5 +342,5 @@ function PikkuAgentChat(props) {
|
|
|
332
342
|
: 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
|
|
333
343
|
UserMessage,
|
|
334
344
|
AssistantMessage,
|
|
335
|
-
} })] }) }), (0, jsx_runtime_1.jsx)("div", { style: { maxWidth:
|
|
345
|
+
} })] }) }), (0, jsx_runtime_1.jsx)("div", { style: { maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }));
|
|
336
346
|
}
|
|
@@ -17,6 +17,10 @@ export interface PendingApproval {
|
|
|
17
17
|
args: unknown;
|
|
18
18
|
reason?: string;
|
|
19
19
|
runId: string;
|
|
20
|
+
type?: 'approval-request' | 'credential-request';
|
|
21
|
+
credentialName?: string;
|
|
22
|
+
credentialType?: 'oauth2' | 'apikey';
|
|
23
|
+
connectUrl?: string;
|
|
20
24
|
}
|
|
21
25
|
export interface PikkuApprovalContextValue {
|
|
22
26
|
pendingApprovals: PendingApproval[];
|
|
@@ -25,9 +29,18 @@ export interface PikkuApprovalContextValue {
|
|
|
25
29
|
export declare const PikkuApprovalContext: import("react").Context<PikkuApprovalContextValue>;
|
|
26
30
|
export declare const usePikkuApproval: () => PikkuApprovalContextValue;
|
|
27
31
|
export declare function isDeniedResult(result: unknown): boolean;
|
|
28
|
-
export type PikkuToolStatusType = 'running' | 'requires-action' | 'completed' | 'denied' | 'error';
|
|
32
|
+
export type PikkuToolStatusType = 'running' | 'requires-action' | 'completed' | 'denied' | 'error' | 'missing-credential';
|
|
33
|
+
export type MissingCredentialPayload = {
|
|
34
|
+
error: 'missing_credential';
|
|
35
|
+
credentialName: string;
|
|
36
|
+
credentialType: 'oauth2' | 'apikey';
|
|
37
|
+
connectUrl?: string;
|
|
38
|
+
};
|
|
29
39
|
export type PikkuToolStatus = {
|
|
30
|
-
type: PikkuToolStatusType
|
|
40
|
+
type: Exclude<PikkuToolStatusType, 'missing-credential'>;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'missing-credential';
|
|
43
|
+
payload: MissingCredentialPayload;
|
|
31
44
|
};
|
|
32
45
|
export declare function resolvePikkuToolStatus(status: {
|
|
33
46
|
type: string;
|
|
@@ -129,6 +129,19 @@ function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
|
129
129
|
args: event.args,
|
|
130
130
|
reason: event.reason,
|
|
131
131
|
runId: event.runId,
|
|
132
|
+
type: 'approval-request',
|
|
133
|
+
});
|
|
134
|
+
break;
|
|
135
|
+
case 'credential-request':
|
|
136
|
+
pendingApprovals.push({
|
|
137
|
+
toolCallId: event.toolCallId,
|
|
138
|
+
toolName: event.toolName,
|
|
139
|
+
args: event.args,
|
|
140
|
+
runId: event.runId,
|
|
141
|
+
type: 'credential-request',
|
|
142
|
+
credentialName: event.credentialName,
|
|
143
|
+
credentialType: event.credentialType,
|
|
144
|
+
connectUrl: event.connectUrl,
|
|
132
145
|
});
|
|
133
146
|
break;
|
|
134
147
|
case 'error':
|
|
@@ -162,6 +175,22 @@ function isDeniedResult(result) {
|
|
|
162
175
|
return false;
|
|
163
176
|
}
|
|
164
177
|
}
|
|
178
|
+
function isMissingCredentialResult(result) {
|
|
179
|
+
if (typeof result === 'object' &&
|
|
180
|
+
result &&
|
|
181
|
+
result.error === 'missing_credential') {
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
if (typeof result === 'string') {
|
|
185
|
+
try {
|
|
186
|
+
const parsed = JSON.parse(result);
|
|
187
|
+
if ((parsed === null || parsed === void 0 ? void 0 : parsed.error) === 'missing_credential')
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
catch (_a) { }
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
165
194
|
function resolvePikkuToolStatus(status, result) {
|
|
166
195
|
if (status.type === 'running')
|
|
167
196
|
return { type: 'running' };
|
|
@@ -169,6 +198,9 @@ function resolvePikkuToolStatus(status, result) {
|
|
|
169
198
|
return { type: 'requires-action' };
|
|
170
199
|
if (isDeniedResult(result))
|
|
171
200
|
return { type: 'denied' };
|
|
201
|
+
const missingCred = isMissingCredentialResult(result);
|
|
202
|
+
if (missingCred)
|
|
203
|
+
return { type: 'missing-credential', payload: missingCred };
|
|
172
204
|
if (typeof result === 'string' && result.startsWith('Error:'))
|
|
173
205
|
return { type: 'error' };
|
|
174
206
|
return { type: 'completed' };
|
|
@@ -295,22 +327,56 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
295
327
|
}
|
|
296
328
|
}
|
|
297
329
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
330
|
+
let response;
|
|
331
|
+
try {
|
|
332
|
+
response = yield __await(fetch(`${opts.api}/${opts.agentName}/stream`, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
|
|
335
|
+
body: JSON.stringify({
|
|
336
|
+
agentName: opts.agentName,
|
|
337
|
+
message: messageText,
|
|
338
|
+
threadId: opts.threadId,
|
|
339
|
+
resourceId: opts.resourceId,
|
|
340
|
+
model: opts.model,
|
|
341
|
+
temperature: opts.temperature,
|
|
342
|
+
}),
|
|
343
|
+
signal: abortSignal,
|
|
344
|
+
credentials: opts.credentials,
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Unknown error';
|
|
349
|
+
let errorText = 'Failed to connect to the agent.';
|
|
350
|
+
if (msg.includes('Failed to fetch') ||
|
|
351
|
+
msg.includes('NetworkError') ||
|
|
352
|
+
msg.includes('CORS')) {
|
|
353
|
+
errorText =
|
|
354
|
+
'Unable to reach the agent server. The deployment may be down or the URL may be incorrect.';
|
|
355
|
+
}
|
|
356
|
+
else if (msg.includes('abort')) {
|
|
357
|
+
return yield __await(void 0);
|
|
358
|
+
}
|
|
359
|
+
yield yield __await({ content: [{ type: 'text', text: `⚠️ ${errorText}` }] });
|
|
360
|
+
return yield __await(void 0);
|
|
361
|
+
}
|
|
312
362
|
if (!response.ok || !response.body) {
|
|
313
|
-
|
|
363
|
+
let errorText = `Request failed (${response.status}).`;
|
|
364
|
+
if (response.status === 401 || response.status === 403) {
|
|
365
|
+
errorText = 'Authentication error.';
|
|
366
|
+
}
|
|
367
|
+
else if (response.status === 404) {
|
|
368
|
+
errorText =
|
|
369
|
+
'Agent not found — the agent may not be configured for this project.';
|
|
370
|
+
}
|
|
371
|
+
else if (response.status === 502 || response.status === 503) {
|
|
372
|
+
errorText =
|
|
373
|
+
'The agent server is currently unavailable. Try again in a moment.';
|
|
374
|
+
}
|
|
375
|
+
else if (response.status === 429) {
|
|
376
|
+
errorText = 'Rate limited — too many requests. Please wait a moment.';
|
|
377
|
+
}
|
|
378
|
+
yield yield __await({ content: [{ type: 'text', text: `⚠️ ${errorText}` }] });
|
|
379
|
+
return yield __await(void 0);
|
|
314
380
|
}
|
|
315
381
|
const text = { value: '' };
|
|
316
382
|
const toolCalls = [];
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { usePikkuAgentRuntime, usePikkuAgentNonStreamingRuntime, PikkuApprovalContext, usePikkuApproval, convertDbMessages, isDeniedResult, resolvePikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
2
|
-
export type { PikkuAgentRuntimeOptions, PendingApproval, PikkuApprovalContextValue, PikkuToolStatusType, PikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
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';
|
|
@@ -7,5 +7,7 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
hideToolCalls?: boolean | string[];
|
|
9
9
|
dark?: boolean;
|
|
10
|
+
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
|
+
maxWidth?: number | 'none';
|
|
10
12
|
}
|
|
11
13
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -20,6 +20,8 @@ const lightColors = {
|
|
|
20
20
|
successColor: '#2e7d32',
|
|
21
21
|
errorBg: '#ffebee',
|
|
22
22
|
errorColor: '#c62828',
|
|
23
|
+
warningBg: '#fff3e0',
|
|
24
|
+
warningColor: '#e65100',
|
|
23
25
|
};
|
|
24
26
|
const darkColors = {
|
|
25
27
|
bg: 'transparent',
|
|
@@ -38,6 +40,8 @@ const darkColors = {
|
|
|
38
40
|
successColor: '#00e68a',
|
|
39
41
|
errorBg: 'rgba(220, 38, 38, 0.1)',
|
|
40
42
|
errorColor: '#f87171',
|
|
43
|
+
warningBg: 'rgba(245, 158, 11, 0.1)',
|
|
44
|
+
warningColor: '#fbbf24',
|
|
41
45
|
};
|
|
42
46
|
const ColorsContext = createContext(lightColors);
|
|
43
47
|
const HideToolCallsContext = createContext(undefined);
|
|
@@ -156,7 +160,13 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
156
160
|
borderRadius: 3,
|
|
157
161
|
background: colors.errorBg,
|
|
158
162
|
color: colors.errorColor,
|
|
159
|
-
}, children: "error" })), status.type === '
|
|
163
|
+
}, children: "error" })), status.type === 'missing-credential' && (_jsx("span", { style: {
|
|
164
|
+
fontSize: 11,
|
|
165
|
+
padding: '1px 5px',
|
|
166
|
+
borderRadius: 3,
|
|
167
|
+
background: colors.warningBg,
|
|
168
|
+
color: colors.warningColor,
|
|
169
|
+
}, children: "credential required" })), status.type === 'denied' && (_jsx("span", { style: {
|
|
160
170
|
fontSize: 11,
|
|
161
171
|
padding: '1px 5px',
|
|
162
172
|
borderRadius: 3,
|
|
@@ -290,7 +300,7 @@ const PikkuComposer = ({ disabled, }) => {
|
|
|
290
300
|
}, children: "\u25B6" })] }) }) }));
|
|
291
301
|
};
|
|
292
302
|
export function PikkuAgentChat(props) {
|
|
293
|
-
const { emptyMessage, hideToolCalls, dark, ...runtimeOptions } = props;
|
|
303
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props;
|
|
294
304
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = usePikkuAgentRuntime(runtimeOptions);
|
|
295
305
|
const colors = dark ? darkColors : lightColors;
|
|
296
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: {
|
|
@@ -308,7 +318,7 @@ export function PikkuAgentChat(props) {
|
|
|
308
318
|
minHeight: 0,
|
|
309
319
|
overflowY: 'auto',
|
|
310
320
|
}, children: _jsxs("div", { style: {
|
|
311
|
-
maxWidth:
|
|
321
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
312
322
|
margin: '0 auto',
|
|
313
323
|
padding: 16,
|
|
314
324
|
display: 'flex',
|
|
@@ -328,5 +338,5 @@ export function PikkuAgentChat(props) {
|
|
|
328
338
|
: 'Start a new conversation.') }) }), _jsx(ThreadPrimitive.Messages, { components: {
|
|
329
339
|
UserMessage,
|
|
330
340
|
AssistantMessage,
|
|
331
|
-
} })] }) }), _jsx("div", { style: { maxWidth:
|
|
341
|
+
} })] }) }), _jsx("div", { style: { maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }, children: _jsx(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }));
|
|
332
342
|
}
|
|
@@ -17,6 +17,10 @@ export interface PendingApproval {
|
|
|
17
17
|
args: unknown;
|
|
18
18
|
reason?: string;
|
|
19
19
|
runId: string;
|
|
20
|
+
type?: 'approval-request' | 'credential-request';
|
|
21
|
+
credentialName?: string;
|
|
22
|
+
credentialType?: 'oauth2' | 'apikey';
|
|
23
|
+
connectUrl?: string;
|
|
20
24
|
}
|
|
21
25
|
export interface PikkuApprovalContextValue {
|
|
22
26
|
pendingApprovals: PendingApproval[];
|
|
@@ -25,9 +29,18 @@ export interface PikkuApprovalContextValue {
|
|
|
25
29
|
export declare const PikkuApprovalContext: import("react").Context<PikkuApprovalContextValue>;
|
|
26
30
|
export declare const usePikkuApproval: () => PikkuApprovalContextValue;
|
|
27
31
|
export declare function isDeniedResult(result: unknown): boolean;
|
|
28
|
-
export type PikkuToolStatusType = 'running' | 'requires-action' | 'completed' | 'denied' | 'error';
|
|
32
|
+
export type PikkuToolStatusType = 'running' | 'requires-action' | 'completed' | 'denied' | 'error' | 'missing-credential';
|
|
33
|
+
export type MissingCredentialPayload = {
|
|
34
|
+
error: 'missing_credential';
|
|
35
|
+
credentialName: string;
|
|
36
|
+
credentialType: 'oauth2' | 'apikey';
|
|
37
|
+
connectUrl?: string;
|
|
38
|
+
};
|
|
29
39
|
export type PikkuToolStatus = {
|
|
30
|
-
type: PikkuToolStatusType
|
|
40
|
+
type: Exclude<PikkuToolStatusType, 'missing-credential'>;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'missing-credential';
|
|
43
|
+
payload: MissingCredentialPayload;
|
|
31
44
|
};
|
|
32
45
|
export declare function resolvePikkuToolStatus(status: {
|
|
33
46
|
type: string;
|
|
@@ -84,6 +84,19 @@ async function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
|
84
84
|
args: event.args,
|
|
85
85
|
reason: event.reason,
|
|
86
86
|
runId: event.runId,
|
|
87
|
+
type: 'approval-request',
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case 'credential-request':
|
|
91
|
+
pendingApprovals.push({
|
|
92
|
+
toolCallId: event.toolCallId,
|
|
93
|
+
toolName: event.toolName,
|
|
94
|
+
args: event.args,
|
|
95
|
+
runId: event.runId,
|
|
96
|
+
type: 'credential-request',
|
|
97
|
+
credentialName: event.credentialName,
|
|
98
|
+
credentialType: event.credentialType,
|
|
99
|
+
connectUrl: event.connectUrl,
|
|
87
100
|
});
|
|
88
101
|
break;
|
|
89
102
|
case 'error':
|
|
@@ -108,6 +121,22 @@ export function isDeniedResult(result) {
|
|
|
108
121
|
return false;
|
|
109
122
|
}
|
|
110
123
|
}
|
|
124
|
+
function isMissingCredentialResult(result) {
|
|
125
|
+
if (typeof result === 'object' &&
|
|
126
|
+
result &&
|
|
127
|
+
result.error === 'missing_credential') {
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
if (typeof result === 'string') {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(result);
|
|
133
|
+
if (parsed?.error === 'missing_credential')
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
111
140
|
export function resolvePikkuToolStatus(status, result) {
|
|
112
141
|
if (status.type === 'running')
|
|
113
142
|
return { type: 'running' };
|
|
@@ -115,6 +144,9 @@ export function resolvePikkuToolStatus(status, result) {
|
|
|
115
144
|
return { type: 'requires-action' };
|
|
116
145
|
if (isDeniedResult(result))
|
|
117
146
|
return { type: 'denied' };
|
|
147
|
+
const missingCred = isMissingCredentialResult(result);
|
|
148
|
+
if (missingCred)
|
|
149
|
+
return { type: 'missing-credential', payload: missingCred };
|
|
118
150
|
if (typeof result === 'string' && result.startsWith('Error:'))
|
|
119
151
|
return { type: 'error' };
|
|
120
152
|
return { type: 'completed' };
|
|
@@ -243,22 +275,56 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
243
275
|
}
|
|
244
276
|
}
|
|
245
277
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
278
|
+
let response;
|
|
279
|
+
try {
|
|
280
|
+
response = await fetch(`${opts.api}/${opts.agentName}/stream`, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
agentName: opts.agentName,
|
|
285
|
+
message: messageText,
|
|
286
|
+
threadId: opts.threadId,
|
|
287
|
+
resourceId: opts.resourceId,
|
|
288
|
+
model: opts.model,
|
|
289
|
+
temperature: opts.temperature,
|
|
290
|
+
}),
|
|
291
|
+
signal: abortSignal,
|
|
292
|
+
credentials: opts.credentials,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
const msg = e?.message || 'Unknown error';
|
|
297
|
+
let errorText = 'Failed to connect to the agent.';
|
|
298
|
+
if (msg.includes('Failed to fetch') ||
|
|
299
|
+
msg.includes('NetworkError') ||
|
|
300
|
+
msg.includes('CORS')) {
|
|
301
|
+
errorText =
|
|
302
|
+
'Unable to reach the agent server. The deployment may be down or the URL may be incorrect.';
|
|
303
|
+
}
|
|
304
|
+
else if (msg.includes('abort')) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
yield { content: [{ type: 'text', text: `⚠️ ${errorText}` }] };
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
260
310
|
if (!response.ok || !response.body) {
|
|
261
|
-
|
|
311
|
+
let errorText = `Request failed (${response.status}).`;
|
|
312
|
+
if (response.status === 401 || response.status === 403) {
|
|
313
|
+
errorText = 'Authentication error.';
|
|
314
|
+
}
|
|
315
|
+
else if (response.status === 404) {
|
|
316
|
+
errorText =
|
|
317
|
+
'Agent not found — the agent may not be configured for this project.';
|
|
318
|
+
}
|
|
319
|
+
else if (response.status === 502 || response.status === 503) {
|
|
320
|
+
errorText =
|
|
321
|
+
'The agent server is currently unavailable. Try again in a moment.';
|
|
322
|
+
}
|
|
323
|
+
else if (response.status === 429) {
|
|
324
|
+
errorText = 'Rate limited — too many requests. Please wait a moment.';
|
|
325
|
+
}
|
|
326
|
+
yield { content: [{ type: 'text', text: `⚠️ ${errorText}` }] };
|
|
327
|
+
return;
|
|
262
328
|
}
|
|
263
329
|
const text = { value: '' };
|
|
264
330
|
const toolCalls = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/assistant-ui",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"tsc": "tsc",
|
|
17
17
|
"build:esm": "tsc -b && echo '{\"type\": \"module\"}' > dist/esm/package.json",
|
|
18
18
|
"build:cjs": "tsc -b tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json",
|
|
19
|
-
"build": "yarn build:esm && yarn build:cjs"
|
|
19
|
+
"build": "yarn build:esm && yarn build:cjs",
|
|
20
|
+
"prepublishOnly": "yarn build"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@assistant-ui/react": "^0.12.12",
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type {
|
|
|
13
13
|
PikkuApprovalContextValue,
|
|
14
14
|
PikkuToolStatusType,
|
|
15
15
|
PikkuToolStatus,
|
|
16
|
+
MissingCredentialPayload,
|
|
16
17
|
} from './use-pikku-agent-runtime.js'
|
|
17
18
|
export { PikkuAgentChat } from './pikku-agent-chat.js'
|
|
18
19
|
export type { PikkuAgentChatProps } from './pikku-agent-chat.js'
|
package/src/pikku-agent-chat.tsx
CHANGED
|
@@ -23,6 +23,8 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
hideToolCalls?: boolean | string[]
|
|
25
25
|
dark?: boolean
|
|
26
|
+
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
27
|
+
maxWidth?: number | 'none'
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
interface ChatColors {
|
|
@@ -42,6 +44,8 @@ interface ChatColors {
|
|
|
42
44
|
successColor: string
|
|
43
45
|
errorBg: string
|
|
44
46
|
errorColor: string
|
|
47
|
+
warningBg: string
|
|
48
|
+
warningColor: string
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const lightColors: ChatColors = {
|
|
@@ -61,6 +65,8 @@ const lightColors: ChatColors = {
|
|
|
61
65
|
successColor: '#2e7d32',
|
|
62
66
|
errorBg: '#ffebee',
|
|
63
67
|
errorColor: '#c62828',
|
|
68
|
+
warningBg: '#fff3e0',
|
|
69
|
+
warningColor: '#e65100',
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
const darkColors: ChatColors = {
|
|
@@ -80,6 +86,8 @@ const darkColors: ChatColors = {
|
|
|
80
86
|
successColor: '#00e68a',
|
|
81
87
|
errorBg: 'rgba(220, 38, 38, 0.1)',
|
|
82
88
|
errorColor: '#f87171',
|
|
89
|
+
warningBg: 'rgba(245, 158, 11, 0.1)',
|
|
90
|
+
warningColor: '#fbbf24',
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
const ColorsContext = createContext<ChatColors>(lightColors)
|
|
@@ -284,6 +292,19 @@ const ToolCallDisplay: FunctionComponent<{
|
|
|
284
292
|
error
|
|
285
293
|
</span>
|
|
286
294
|
)}
|
|
295
|
+
{status.type === 'missing-credential' && (
|
|
296
|
+
<span
|
|
297
|
+
style={{
|
|
298
|
+
fontSize: 11,
|
|
299
|
+
padding: '1px 5px',
|
|
300
|
+
borderRadius: 3,
|
|
301
|
+
background: colors.warningBg,
|
|
302
|
+
color: colors.warningColor,
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
305
|
+
credential required
|
|
306
|
+
</span>
|
|
307
|
+
)}
|
|
287
308
|
{status.type === 'denied' && (
|
|
288
309
|
<span
|
|
289
310
|
style={{
|
|
@@ -571,7 +592,7 @@ const PikkuComposer: FunctionComponent<{ disabled?: boolean }> = ({
|
|
|
571
592
|
}
|
|
572
593
|
|
|
573
594
|
export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
574
|
-
const { emptyMessage, hideToolCalls, dark, ...runtimeOptions } = props
|
|
595
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props
|
|
575
596
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } =
|
|
576
597
|
usePikkuAgentRuntime(runtimeOptions)
|
|
577
598
|
|
|
@@ -607,7 +628,7 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
|
607
628
|
>
|
|
608
629
|
<div
|
|
609
630
|
style={{
|
|
610
|
-
maxWidth:
|
|
631
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
611
632
|
margin: '0 auto',
|
|
612
633
|
padding: 16,
|
|
613
634
|
display: 'flex',
|
|
@@ -641,7 +662,7 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
|
641
662
|
/>
|
|
642
663
|
</div>
|
|
643
664
|
</ThreadPrimitive.Viewport>
|
|
644
|
-
<div style={{ maxWidth:
|
|
665
|
+
<div style={{ maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }}>
|
|
645
666
|
<PikkuComposer disabled={isAwaitingApproval} />
|
|
646
667
|
</div>
|
|
647
668
|
</ThreadPrimitive.Root>
|
|
@@ -31,6 +31,10 @@ export interface PendingApproval {
|
|
|
31
31
|
args: unknown
|
|
32
32
|
reason?: string
|
|
33
33
|
runId: string
|
|
34
|
+
type?: 'approval-request' | 'credential-request'
|
|
35
|
+
credentialName?: string
|
|
36
|
+
credentialType?: 'oauth2' | 'apikey'
|
|
37
|
+
connectUrl?: string
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export interface PikkuApprovalContextValue {
|
|
@@ -143,6 +147,19 @@ async function processStream(
|
|
|
143
147
|
args: event.args,
|
|
144
148
|
reason: event.reason,
|
|
145
149
|
runId: event.runId,
|
|
150
|
+
type: 'approval-request',
|
|
151
|
+
})
|
|
152
|
+
break
|
|
153
|
+
case 'credential-request':
|
|
154
|
+
pendingApprovals.push({
|
|
155
|
+
toolCallId: event.toolCallId,
|
|
156
|
+
toolName: event.toolName,
|
|
157
|
+
args: event.args,
|
|
158
|
+
runId: event.runId,
|
|
159
|
+
type: 'credential-request',
|
|
160
|
+
credentialName: event.credentialName,
|
|
161
|
+
credentialType: event.credentialType,
|
|
162
|
+
connectUrl: event.connectUrl,
|
|
146
163
|
})
|
|
147
164
|
break
|
|
148
165
|
case 'error':
|
|
@@ -174,8 +191,37 @@ export type PikkuToolStatusType =
|
|
|
174
191
|
| 'completed'
|
|
175
192
|
| 'denied'
|
|
176
193
|
| 'error'
|
|
194
|
+
| 'missing-credential'
|
|
177
195
|
|
|
178
|
-
export type
|
|
196
|
+
export type MissingCredentialPayload = {
|
|
197
|
+
error: 'missing_credential'
|
|
198
|
+
credentialName: string
|
|
199
|
+
credentialType: 'oauth2' | 'apikey'
|
|
200
|
+
connectUrl?: string
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type PikkuToolStatus =
|
|
204
|
+
| { type: Exclude<PikkuToolStatusType, 'missing-credential'> }
|
|
205
|
+
| { type: 'missing-credential'; payload: MissingCredentialPayload }
|
|
206
|
+
|
|
207
|
+
function isMissingCredentialResult(
|
|
208
|
+
result: unknown
|
|
209
|
+
): MissingCredentialPayload | null {
|
|
210
|
+
if (
|
|
211
|
+
typeof result === 'object' &&
|
|
212
|
+
result &&
|
|
213
|
+
(result as any).error === 'missing_credential'
|
|
214
|
+
) {
|
|
215
|
+
return result as MissingCredentialPayload
|
|
216
|
+
}
|
|
217
|
+
if (typeof result === 'string') {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(result)
|
|
220
|
+
if (parsed?.error === 'missing_credential') return parsed
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
return null
|
|
224
|
+
}
|
|
179
225
|
|
|
180
226
|
export function resolvePikkuToolStatus(
|
|
181
227
|
status: { type: string },
|
|
@@ -184,6 +230,8 @@ export function resolvePikkuToolStatus(
|
|
|
184
230
|
if (status.type === 'running') return { type: 'running' }
|
|
185
231
|
if (status.type === 'requires-action') return { type: 'requires-action' }
|
|
186
232
|
if (isDeniedResult(result)) return { type: 'denied' }
|
|
233
|
+
const missingCred = isMissingCredentialResult(result)
|
|
234
|
+
if (missingCred) return { type: 'missing-credential', payload: missingCred }
|
|
187
235
|
if (typeof result === 'string' && result.startsWith('Error:'))
|
|
188
236
|
return { type: 'error' }
|
|
189
237
|
return { type: 'completed' }
|
|
@@ -351,23 +399,54 @@ function createPikkuStreamingAdapter(
|
|
|
351
399
|
}
|
|
352
400
|
}
|
|
353
401
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
402
|
+
let response: Response
|
|
403
|
+
try {
|
|
404
|
+
response = await fetch(`${opts.api}/${opts.agentName}/stream`, {
|
|
405
|
+
method: 'POST',
|
|
406
|
+
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
|
407
|
+
body: JSON.stringify({
|
|
408
|
+
agentName: opts.agentName,
|
|
409
|
+
message: messageText,
|
|
410
|
+
threadId: opts.threadId,
|
|
411
|
+
resourceId: opts.resourceId,
|
|
412
|
+
model: opts.model,
|
|
413
|
+
temperature: opts.temperature,
|
|
414
|
+
}),
|
|
415
|
+
signal: abortSignal,
|
|
416
|
+
credentials: opts.credentials,
|
|
417
|
+
})
|
|
418
|
+
} catch (e: any) {
|
|
419
|
+
const msg = e?.message || 'Unknown error'
|
|
420
|
+
let errorText = 'Failed to connect to the agent.'
|
|
421
|
+
if (
|
|
422
|
+
msg.includes('Failed to fetch') ||
|
|
423
|
+
msg.includes('NetworkError') ||
|
|
424
|
+
msg.includes('CORS')
|
|
425
|
+
) {
|
|
426
|
+
errorText =
|
|
427
|
+
'Unable to reach the agent server. The deployment may be down or the URL may be incorrect.'
|
|
428
|
+
} else if (msg.includes('abort')) {
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
yield { content: [{ type: 'text' as const, text: `⚠️ ${errorText}` }] }
|
|
432
|
+
return
|
|
433
|
+
}
|
|
368
434
|
|
|
369
435
|
if (!response.ok || !response.body) {
|
|
370
|
-
|
|
436
|
+
let errorText = `Request failed (${response.status}).`
|
|
437
|
+
if (response.status === 401 || response.status === 403) {
|
|
438
|
+
errorText = 'Authentication error.'
|
|
439
|
+
} else if (response.status === 404) {
|
|
440
|
+
errorText =
|
|
441
|
+
'Agent not found — the agent may not be configured for this project.'
|
|
442
|
+
} else if (response.status === 502 || response.status === 503) {
|
|
443
|
+
errorText =
|
|
444
|
+
'The agent server is currently unavailable. Try again in a moment.'
|
|
445
|
+
} else if (response.status === 429) {
|
|
446
|
+
errorText = 'Rate limited — too many requests. Please wait a moment.'
|
|
447
|
+
}
|
|
448
|
+
yield { content: [{ type: 'text' as const, text: `⚠️ ${errorText}` }] }
|
|
449
|
+
return
|
|
371
450
|
}
|
|
372
451
|
|
|
373
452
|
const text = { value: '' }
|