@pikku/assistant-ui 0.12.2 → 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 +14 -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 +41 -4
- 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 +41 -4
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/pikku-agent-chat.tsx +24 -3
- package/src/use-pikku-agent-runtime.ts +60 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## 0.12.2
|
|
4
18
|
|
|
5
19
|
### 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' };
|
|
@@ -315,8 +347,11 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
315
347
|
catch (e) {
|
|
316
348
|
const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Unknown error';
|
|
317
349
|
let errorText = 'Failed to connect to the agent.';
|
|
318
|
-
if (msg.includes('Failed to fetch') ||
|
|
319
|
-
|
|
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.';
|
|
320
355
|
}
|
|
321
356
|
else if (msg.includes('abort')) {
|
|
322
357
|
return yield __await(void 0);
|
|
@@ -330,10 +365,12 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
330
365
|
errorText = 'Authentication error.';
|
|
331
366
|
}
|
|
332
367
|
else if (response.status === 404) {
|
|
333
|
-
errorText =
|
|
368
|
+
errorText =
|
|
369
|
+
'Agent not found — the agent may not be configured for this project.';
|
|
334
370
|
}
|
|
335
371
|
else if (response.status === 502 || response.status === 503) {
|
|
336
|
-
errorText =
|
|
372
|
+
errorText =
|
|
373
|
+
'The agent server is currently unavailable. Try again in a moment.';
|
|
337
374
|
}
|
|
338
375
|
else if (response.status === 429) {
|
|
339
376
|
errorText = 'Rate limited — too many requests. Please wait a moment.';
|
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' };
|
|
@@ -263,8 +295,11 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
263
295
|
catch (e) {
|
|
264
296
|
const msg = e?.message || 'Unknown error';
|
|
265
297
|
let errorText = 'Failed to connect to the agent.';
|
|
266
|
-
if (msg.includes('Failed to fetch') ||
|
|
267
|
-
|
|
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.';
|
|
268
303
|
}
|
|
269
304
|
else if (msg.includes('abort')) {
|
|
270
305
|
return;
|
|
@@ -278,10 +313,12 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
278
313
|
errorText = 'Authentication error.';
|
|
279
314
|
}
|
|
280
315
|
else if (response.status === 404) {
|
|
281
|
-
errorText =
|
|
316
|
+
errorText =
|
|
317
|
+
'Agent not found — the agent may not be configured for this project.';
|
|
282
318
|
}
|
|
283
319
|
else if (response.status === 502 || response.status === 503) {
|
|
284
|
-
errorText =
|
|
320
|
+
errorText =
|
|
321
|
+
'The agent server is currently unavailable. Try again in a moment.';
|
|
285
322
|
}
|
|
286
323
|
else if (response.status === 429) {
|
|
287
324
|
errorText = 'Rate limited — too many requests. Please wait a moment.';
|
package/package.json
CHANGED
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' }
|
|
@@ -370,8 +418,13 @@ function createPikkuStreamingAdapter(
|
|
|
370
418
|
} catch (e: any) {
|
|
371
419
|
const msg = e?.message || 'Unknown error'
|
|
372
420
|
let errorText = 'Failed to connect to the agent.'
|
|
373
|
-
if (
|
|
374
|
-
|
|
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.'
|
|
375
428
|
} else if (msg.includes('abort')) {
|
|
376
429
|
return
|
|
377
430
|
}
|
|
@@ -384,9 +437,11 @@ function createPikkuStreamingAdapter(
|
|
|
384
437
|
if (response.status === 401 || response.status === 403) {
|
|
385
438
|
errorText = 'Authentication error.'
|
|
386
439
|
} else if (response.status === 404) {
|
|
387
|
-
errorText =
|
|
440
|
+
errorText =
|
|
441
|
+
'Agent not found — the agent may not be configured for this project.'
|
|
388
442
|
} else if (response.status === 502 || response.status === 503) {
|
|
389
|
-
errorText =
|
|
443
|
+
errorText =
|
|
444
|
+
'The agent server is currently unavailable. Try again in a moment.'
|
|
390
445
|
} else if (response.status === 429) {
|
|
391
446
|
errorText = 'Rate limited — too many requests. Please wait a moment.'
|
|
392
447
|
}
|