@cntyclub/agent-react 0.3.0 → 0.5.0

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { ChatMessage, ChatMessageBubble, Markdown, ChatToolChip, DataTablePaged, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Button, Spinner, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, ChatTypingIndicator, cn, ChatInput, useMediaQuery, TooltipProvider, Fab } from '@cntyclub/ui-react';
2
- import { WrenchIcon, CheckIcon, ShieldAlertIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Maximize2Icon, XIcon, Trash2Icon, SparklesIcon } from 'lucide-react';
3
- import * as React2 from 'react';
1
+ import { ChatMessage, ChatMessageBubble, Markdown, Collapsible, CollapsibleTrigger, ChatToolChip, cn, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Button, Group, Spinner, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, ChatTypingIndicator, ChatInput, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
2
+ import { ChevronDownIcon, CheckIcon, ShieldAlertIcon, CheckCheckIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Maximize2Icon, XIcon, Trash2Icon, SparklesIcon, CodeIcon } from 'lucide-react';
3
+ import * as React3 from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  // src/api-client.ts
@@ -77,84 +77,418 @@ var AgentApiClient = class {
77
77
  });
78
78
  }
79
79
  };
80
- function humanizeToolName(toolName) {
81
- return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
80
+
81
+ // src/message-group.ts
82
+ function groupMessages(messages) {
83
+ const items = [];
84
+ let group = null;
85
+ let groupKey = "";
86
+ const stepIndexByCallId = /* @__PURE__ */ new Map();
87
+ const flush = () => {
88
+ if (group && group.length > 0) {
89
+ items.push({ key: `tools-${groupKey}`, kind: "tool-activity", steps: group });
90
+ }
91
+ group = null;
92
+ groupKey = "";
93
+ stepIndexByCallId.clear();
94
+ };
95
+ for (const message of messages) {
96
+ if (message.role === "user") {
97
+ flush();
98
+ items.push({ key: message.id, kind: "user", message });
99
+ continue;
100
+ }
101
+ if (message.role === "assistant") {
102
+ const hasText = Boolean(message.content && message.content.trim());
103
+ if (hasText) {
104
+ flush();
105
+ items.push({ key: message.id, kind: "assistant-text", message });
106
+ }
107
+ for (const toolCall of message.tool_calls ?? []) {
108
+ const callId2 = toolCall.id ?? `${message.id}-${toolCall.name}`;
109
+ if (!group) {
110
+ group = [];
111
+ groupKey = message.id;
112
+ }
113
+ stepIndexByCallId.set(callId2, group.length);
114
+ group.push({
115
+ args: toolCall.arguments ?? {},
116
+ callId: callId2,
117
+ name: toolCall.name ?? "tool",
118
+ status: "running"
119
+ });
120
+ }
121
+ continue;
122
+ }
123
+ const callId = message.tool_call_id ?? "";
124
+ if (!group) {
125
+ group = [];
126
+ groupKey = message.id;
127
+ }
128
+ const existingIndex = callId ? stepIndexByCallId.get(callId) : void 0;
129
+ if (existingIndex !== void 0) {
130
+ const step = group[existingIndex];
131
+ step.status = "done";
132
+ step.result = message.result || step.result;
133
+ step.blocks = message.blocks ?? step.blocks;
134
+ } else {
135
+ group.push({
136
+ args: {},
137
+ blocks: message.blocks,
138
+ callId: callId || message.id,
139
+ name: message.tool_name ?? "tool",
140
+ result: message.result,
141
+ status: "done"
142
+ });
143
+ }
144
+ }
145
+ flush();
146
+ return items;
82
147
  }
83
148
  function MessageItem({ message }) {
84
149
  if (message.role === "user") {
85
150
  return /* @__PURE__ */ jsx(ChatMessage, { from: "user", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) });
86
151
  }
87
152
  if (message.role === "assistant") {
88
- const requestedTools = message.tool_calls ?? [];
89
- if (!message.content && requestedTools.length === 0) return null;
90
- return /* @__PURE__ */ jsxs(ChatMessage, { from: "assistant", children: [
91
- message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) : null,
92
- requestedTools.map((toolCall) => /* @__PURE__ */ jsxs(ChatToolChip, { icon: /* @__PURE__ */ jsx(WrenchIcon, {}), children: [
93
- "Using ",
94
- humanizeToolName(toolCall.name ?? "tool"),
95
- "\u2026"
96
- ] }, toolCall.id ?? toolCall.name))
97
- ] });
153
+ if (!message.content || !message.content.trim()) return null;
154
+ return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) });
98
155
  }
99
- const tableBlocks = (message.blocks ?? []).filter((block) => block.type === "table");
100
- return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: tableBlocks.length > 0 ? tableBlocks.map((block, index) => /* @__PURE__ */ jsx("div", { className: "w-full max-w-full", children: /* @__PURE__ */ jsx(
101
- DataTablePaged,
102
- {
103
- columns: block.columns.map((key) => ({ key })),
104
- pageSize: 8,
105
- rows: block.rows,
106
- totalCount: typeof block.pagination?.count === "number" ? block.pagination.count : block.row_count
156
+ return null;
157
+ }
158
+ var NOISE_KEYS = /* @__PURE__ */ new Set([
159
+ "id",
160
+ "pk",
161
+ "uuid",
162
+ "slug",
163
+ "object_id",
164
+ "external_id",
165
+ "client_id",
166
+ "content_type",
167
+ "provider_call_id",
168
+ "tool_call_id"
169
+ ]);
170
+ var NOISE_PATTERNS = [
171
+ /_id$/i,
172
+ /_ids$/i,
173
+ /_key$/i,
174
+ /_hash$/i,
175
+ /embedding/i,
176
+ /vector/i,
177
+ /password/i,
178
+ /secret/i,
179
+ /(^|_)token(_|$)/i
180
+ ];
181
+ var IMAGE_KEY = /(image|logo|avatar|photo|picture|icon|thumbnail|banner|cover|headshot)/i;
182
+ var IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|avif)(\?|$)/i;
183
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
184
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T|$)/;
185
+ var URL_RE = /^https?:\/\//i;
186
+ var PRIORITY_KEYS = ["name", "title", "label", "full_name", "email"];
187
+ var LABEL_KEYS = ["name", "title", "label", "full_name", "email", "code"];
188
+ function isUuid(value) {
189
+ return typeof value === "string" && UUID_RE.test(value);
190
+ }
191
+ function humanize(key) {
192
+ const text = key.replace(/_/g, " ").trim();
193
+ return text.charAt(0).toUpperCase() + text.slice(1);
194
+ }
195
+ function isNoiseColumn(key, rows) {
196
+ if (NOISE_KEYS.has(key.toLowerCase())) return true;
197
+ if (NOISE_PATTERNS.some((pattern) => pattern.test(key))) return true;
198
+ const values = rows.map((row) => row[key]).filter((value) => value !== null && value !== void 0 && value !== "");
199
+ return values.length > 0 && values.every(isUuid);
200
+ }
201
+ function pickLabel(obj) {
202
+ for (const key of LABEL_KEYS) {
203
+ const value = obj[key];
204
+ if (typeof value === "string" && value.trim()) return value;
205
+ if (typeof value === "number") return String(value);
206
+ }
207
+ return null;
208
+ }
209
+ function formatObject(obj) {
210
+ const label = pickLabel(obj);
211
+ if (label) return label;
212
+ const parts = [];
213
+ for (const [key, value] of Object.entries(obj)) {
214
+ if (value === null || value === void 0 || value === "") continue;
215
+ if (typeof value === "object") continue;
216
+ parts.push(`${humanize(key)} ${value}`);
217
+ if (parts.length >= 6) break;
218
+ }
219
+ return parts.join(" \xB7 ") || "\u2014";
220
+ }
221
+ function formatArray(arr) {
222
+ const items = arr.map((item) => {
223
+ if (item === null || item === void 0) return "";
224
+ if (Array.isArray(item)) return "";
225
+ if (typeof item === "object") return pickLabel(item) ?? "";
226
+ return String(item);
227
+ }).filter(Boolean);
228
+ if (items.length === 0) return "\u2014";
229
+ const shown = items.slice(0, 8);
230
+ const extra = items.length - shown.length;
231
+ return shown.join(", ") + (extra > 0 ? ` +${extra} more` : "");
232
+ }
233
+ function renderScalar(key, value) {
234
+ if (typeof value !== "string") return value;
235
+ const text = value.trim();
236
+ if (URL_RE.test(text) && (IMAGE_EXT.test(text) || IMAGE_KEY.test(key))) {
237
+ return /* @__PURE__ */ jsx(
238
+ "img",
239
+ {
240
+ alt: humanize(key),
241
+ className: "h-9 w-9 rounded-md border border-border object-cover",
242
+ loading: "lazy",
243
+ src: text
244
+ }
245
+ );
246
+ }
247
+ if (URL_RE.test(text)) {
248
+ let label = text;
249
+ try {
250
+ label = new URL(text).hostname.replace(/^www\./, "");
251
+ } catch {
107
252
  }
108
- ) }, `${message.id}-${index}`)) : /* @__PURE__ */ jsxs(ChatToolChip, { icon: /* @__PURE__ */ jsx(CheckIcon, {}), children: [
109
- humanizeToolName(message.tool_name ?? "tool"),
110
- " finished"
111
- ] }) });
253
+ return /* @__PURE__ */ jsx(
254
+ "a",
255
+ {
256
+ className: "text-primary underline underline-offset-2",
257
+ href: text,
258
+ rel: "noreferrer",
259
+ target: "_blank",
260
+ children: label
261
+ }
262
+ );
263
+ }
264
+ if (ISO_DATE_RE.test(text)) {
265
+ const date = new Date(text);
266
+ if (!Number.isNaN(date.getTime())) return date.toLocaleDateString();
267
+ }
268
+ return text;
112
269
  }
113
- function humanizeToolName2(toolName) {
270
+ function formatCellValue(key, value) {
271
+ if (value === null || value === void 0 || value === "") return value;
272
+ if (Array.isArray(value)) return formatArray(value);
273
+ if (typeof value === "object") return formatObject(value);
274
+ return renderScalar(key, value);
275
+ }
276
+ function toDisplayTable(block) {
277
+ const rows = block.rows ?? [];
278
+ const kept = (block.columns ?? []).filter((key) => !isNoiseColumn(key, rows));
279
+ const columnKeys = kept.length > 0 ? kept : block.columns ?? [];
280
+ const ordered = [...columnKeys].sort((a, b) => {
281
+ const ra = PRIORITY_KEYS.indexOf(a.toLowerCase());
282
+ const rb = PRIORITY_KEYS.indexOf(b.toLowerCase());
283
+ const pa = ra === -1 ? PRIORITY_KEYS.length : ra;
284
+ const pb = rb === -1 ? PRIORITY_KEYS.length : rb;
285
+ if (pa !== pb) return pa - pb;
286
+ return columnKeys.indexOf(a) - columnKeys.indexOf(b);
287
+ });
288
+ const columns = ordered.map((key) => ({ key, label: humanize(key) }));
289
+ const displayRows = rows.map((row) => {
290
+ const out = {};
291
+ for (const key of ordered) out[key] = formatCellValue(key, row[key]);
292
+ return out;
293
+ });
294
+ return { columns, rows: displayRows };
295
+ }
296
+
297
+ // src/tool-presentation.ts
298
+ function humanizeToolName(toolName) {
114
299
  return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
115
300
  }
116
- function ToolApprovalCard({ approval, onResolve, resolving }) {
117
- const argumentEntries = Object.entries(approval.arguments ?? {});
118
- return /* @__PURE__ */ jsxs(ChatToolCard, { className: "border-warning/40", children: [
119
- /* @__PURE__ */ jsxs(ChatToolCardHeader, { children: [
120
- /* @__PURE__ */ jsx(ShieldAlertIcon, { className: "size-4 text-warning" }),
121
- "Approval needed"
122
- ] }),
123
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
124
- "The assistant wants to run",
125
- " ",
126
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: humanizeToolName2(approval.tool_name) }),
127
- " ",
128
- "\u2014 this will change your data."
301
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
302
+ function isTechnicalKey(key) {
303
+ const lower = key.toLowerCase();
304
+ return lower === "id" || lower === "client_id" || lower === "slug" || lower.endsWith("_id") || lower.endsWith("_ids") || lower.endsWith("_key") || lower.endsWith("_slug") || lower.endsWith("_uuid") || lower === "uuid" || lower.endsWith("_s3_key") || lower === "embedding";
305
+ }
306
+ function isTechnicalValue(value) {
307
+ return typeof value === "string" && UUID_RE2.test(value.trim());
308
+ }
309
+ function humanizeKey(key) {
310
+ const spaced = key.replaceAll("_", " ").trim();
311
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
312
+ }
313
+ function formatValue(value) {
314
+ if (value === null || value === void 0) return "\u2014";
315
+ if (typeof value === "boolean") return value ? "Yes" : "No";
316
+ if (typeof value === "string" || typeof value === "number") return String(value);
317
+ if (Array.isArray(value)) {
318
+ const items = value.filter((item) => !isTechnicalValue(item)).map((item) => formatValue(item));
319
+ return items.length ? items.join(", ") : `${value.length} item${value.length === 1 ? "" : "s"}`;
320
+ }
321
+ if (typeof value === "object") {
322
+ const record = value;
323
+ for (const key of ["name", "title", "label", "email"]) {
324
+ if (typeof record[key] === "string") return record[key];
325
+ }
326
+ return JSON.stringify(value);
327
+ }
328
+ return String(value);
329
+ }
330
+ function deriveDetails(args) {
331
+ const details = [];
332
+ for (const [key, value] of Object.entries(args ?? {})) {
333
+ if (isTechnicalKey(key)) continue;
334
+ if (isTechnicalValue(value)) continue;
335
+ if (value === "" || value === null || value === void 0) continue;
336
+ const formatted = formatValue(value);
337
+ if (!formatted || formatted === "\u2014") continue;
338
+ details.push({ label: humanizeKey(key), value: formatted.length > 140 ? `${formatted.slice(0, 140)}\u2026` : formatted });
339
+ }
340
+ return details;
341
+ }
342
+ function resolveToolPresentation(describe, toolName, args) {
343
+ let description;
344
+ try {
345
+ description = describe?.(toolName, args ?? {});
346
+ } catch {
347
+ description = null;
348
+ }
349
+ const title = description?.title?.trim() || humanizeToolName(toolName);
350
+ const summary = description?.summary?.trim() || null;
351
+ let details = description?.details ?? [];
352
+ const wantsDerived = description?.showDerivedDetails ?? description?.details === void 0;
353
+ if (details.length === 0 && wantsDerived) {
354
+ details = deriveDetails(args ?? {});
355
+ }
356
+ return { details, summary, title };
357
+ }
358
+ function StepTables({ blocks, stepKey }) {
359
+ const tables = (blocks ?? []).filter((block) => block.type === "table");
360
+ if (tables.length === 0) return null;
361
+ return /* @__PURE__ */ jsx(Fragment, { children: tables.map((block, index) => {
362
+ const { columns, rows } = toDisplayTable(block);
363
+ return /* @__PURE__ */ jsx("div", { className: "w-full max-w-full", children: /* @__PURE__ */ jsx(
364
+ DataTablePaged,
365
+ {
366
+ columns,
367
+ pageSize: 8,
368
+ rows,
369
+ totalCount: typeof block.pagination?.count === "number" ? block.pagination.count : block.row_count
370
+ }
371
+ ) }, `${stepKey}-table-${index}`);
372
+ }) });
373
+ }
374
+ function RawJsonDisclosure({ result }) {
375
+ return /* @__PURE__ */ jsxs(Collapsible, { className: "mt-1.5", children: [
376
+ /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "group flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground", children: [
377
+ /* @__PURE__ */ jsx(CodeIcon, { className: "size-3" }),
378
+ /* @__PURE__ */ jsx("span", { children: "Raw JSON" }),
379
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 transition-transform group-data-[panel-open]:rotate-180" })
129
380
  ] }),
130
- argumentEntries.length > 0 && /* @__PURE__ */ jsxs("dl", { className: "mt-2 space-y-0.5 rounded-lg bg-muted p-2 font-mono text-xs", children: [
131
- argumentEntries.slice(0, 8).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
132
- /* @__PURE__ */ jsxs("dt", { className: "shrink-0 text-muted-foreground", children: [
133
- key,
134
- ":"
135
- ] }),
136
- /* @__PURE__ */ jsx("dd", { className: "truncate", children: typeof value === "object" ? JSON.stringify(value) : String(value) })
137
- ] }, key)),
138
- argumentEntries.length > 8 && /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
139
- "\u2026and ",
140
- argumentEntries.length - 8,
141
- " more"
142
- ] })
381
+ /* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-64 overflow-auto rounded-lg bg-muted p-2 font-mono text-[11px] leading-snug text-muted-foreground", children: result }) })
382
+ ] });
383
+ }
384
+ function StepDetail({
385
+ describeToolCall,
386
+ step
387
+ }) {
388
+ const { details, summary, title } = resolveToolPresentation(describeToolCall, step.name, step.args);
389
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/72 bg-card p-2.5", children: [
390
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 font-medium text-sm", children: [
391
+ step.status === "running" ? /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-warning" }) : /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5 text-success" }),
392
+ title
143
393
  ] }),
144
- /* @__PURE__ */ jsxs(ChatToolCardActions, { children: [
394
+ summary ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-muted-foreground text-xs", children: summary }) : null,
395
+ details.length > 0 ? /* @__PURE__ */ jsx("dl", { className: "mt-1.5 space-y-0.5 text-xs", children: details.map((detail) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
396
+ /* @__PURE__ */ jsx("dt", { className: "shrink-0 text-muted-foreground", children: detail.label }),
397
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words", children: detail.value })
398
+ ] }, detail.label)) }) : null,
399
+ step.result ? /* @__PURE__ */ jsx(RawJsonDisclosure, { result: step.result }) : null
400
+ ] });
401
+ }
402
+ function ToolActivityGroup({ describeToolCall, steps }) {
403
+ const [open, setOpen] = React3.useState(false);
404
+ if (steps.length === 0) return null;
405
+ const last = steps[steps.length - 1];
406
+ const anyRunning = steps.some((step) => step.status === "running");
407
+ const lastTitle = resolveToolPresentation(describeToolCall, last.name, last.args).title || humanizeToolName(last.name);
408
+ const chipLabel = anyRunning ? `${lastTitle}\u2026` : steps.length > 1 ? lastTitle : `${lastTitle} finished`;
409
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2", children: [
410
+ /* @__PURE__ */ jsxs(Collapsible, { onOpenChange: setOpen, open, children: [
145
411
  /* @__PURE__ */ jsx(
146
- Button,
412
+ CollapsibleTrigger,
147
413
  {
148
- disabled: resolving,
149
- onClick: () => onResolve(approval.id, false),
150
- size: "sm",
151
- variant: "outline",
152
- children: "Decline"
414
+ render: /* @__PURE__ */ jsx(
415
+ ChatToolChip,
416
+ {
417
+ className: "cursor-pointer pr-2 hover:bg-muted",
418
+ icon: anyRunning ? /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-warning" }) : /* @__PURE__ */ jsx(CheckIcon, {}),
419
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
420
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: chipLabel }),
421
+ steps.length > 1 ? /* @__PURE__ */ jsxs("span", { className: "rounded-full bg-background px-1.5 text-[10px] text-foreground/72", children: [
422
+ steps.length,
423
+ " steps"
424
+ ] }) : null,
425
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", open && "rotate-180") })
426
+ ] })
427
+ }
428
+ )
153
429
  }
154
430
  ),
155
- /* @__PURE__ */ jsxs(Button, { disabled: resolving, onClick: () => onResolve(approval.id, true), size: "sm", children: [
156
- resolving ? /* @__PURE__ */ jsx(Spinner, {}) : null,
157
- "Approve"
431
+ /* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1.5", children: steps.map((step) => /* @__PURE__ */ jsx(StepDetail, { describeToolCall, step }, step.callId)) }) })
432
+ ] }),
433
+ steps.map((step) => /* @__PURE__ */ jsx(StepTables, { blocks: step.blocks, stepKey: step.callId }, `${step.callId}-tables`))
434
+ ] });
435
+ }
436
+ function ToolApprovalCard({
437
+ approval,
438
+ describeToolCall,
439
+ onAlwaysApprove,
440
+ onResolve,
441
+ resolving
442
+ }) {
443
+ const { details, summary, title } = resolveToolPresentation(
444
+ describeToolCall,
445
+ approval.tool_name,
446
+ approval.arguments ?? {}
447
+ );
448
+ return /* @__PURE__ */ jsxs(ChatToolCard, { className: "border-warning/40", children: [
449
+ /* @__PURE__ */ jsxs(ChatToolCardHeader, { children: [
450
+ /* @__PURE__ */ jsx(ShieldAlertIcon, { className: "size-4 text-warning" }),
451
+ "Approval needed"
452
+ ] }),
453
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-muted-foreground", children: summary ?? /* @__PURE__ */ jsxs(Fragment, { children: [
454
+ "The assistant wants to ",
455
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: title.toLowerCase() }),
456
+ " \u2014 this will change your data."
457
+ ] }) }),
458
+ details.length > 0 && /* @__PURE__ */ jsx("dl", { className: "mt-2 space-y-1 rounded-lg bg-muted p-2 text-xs", children: details.map((detail) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
459
+ /* @__PURE__ */ jsx("dt", { className: "shrink-0 text-muted-foreground", children: detail.label }),
460
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words font-medium text-foreground", children: detail.value })
461
+ ] }, detail.label)) }),
462
+ /* @__PURE__ */ jsxs(ChatToolCardActions, { children: [
463
+ /* @__PURE__ */ jsx(Button, { disabled: resolving, onClick: () => onResolve(approval.id, false), size: "sm", variant: "outline", children: "Decline" }),
464
+ /* @__PURE__ */ jsxs(Group, { children: [
465
+ /* @__PURE__ */ jsxs(Button, { disabled: resolving, onClick: () => onResolve(approval.id, true), size: "sm", children: [
466
+ resolving ? /* @__PURE__ */ jsx(Spinner, {}) : null,
467
+ "Approve"
468
+ ] }),
469
+ /* @__PURE__ */ jsxs(Menu, { children: [
470
+ /* @__PURE__ */ jsx(
471
+ MenuTrigger,
472
+ {
473
+ render: /* @__PURE__ */ jsx(Button, { "aria-label": "More approve options", disabled: resolving, size: "icon-sm", variant: "default", children: /* @__PURE__ */ jsx(ChevronDownIcon, {}) })
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsx(MenuPopup, { align: "end", children: /* @__PURE__ */ jsxs(
477
+ MenuItem,
478
+ {
479
+ onClick: () => {
480
+ onAlwaysApprove(approval.tool_name);
481
+ onResolve(approval.id, true);
482
+ },
483
+ children: [
484
+ /* @__PURE__ */ jsx(CheckCheckIcon, {}),
485
+ "Always approve \u201C",
486
+ title,
487
+ "\u201D"
488
+ ]
489
+ }
490
+ ) })
491
+ ] })
158
492
  ] })
159
493
  ] })
160
494
  ] });
@@ -165,24 +499,25 @@ function AgentPanel({
165
499
  chat,
166
500
  className,
167
501
  config,
502
+ onAlwaysApprove,
168
503
  onClose,
169
504
  onToggleAgentMode
170
505
  }) {
171
- const [draft, setDraft] = React2.useState("");
172
- const [view, setView] = React2.useState("chat");
506
+ const [draft, setDraft] = React3.useState("");
507
+ const [view, setView] = React3.useState("chat");
173
508
  const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
174
509
  const suggestions = config.suggestions ?? [
175
510
  "What can you help me with?",
176
511
  "Give me a quick summary of my data."
177
512
  ];
178
- const handleSubmit = React2.useCallback(
513
+ const handleSubmit = React3.useCallback(
179
514
  (text) => {
180
515
  setDraft("");
181
516
  void chat.send(text);
182
517
  },
183
518
  [chat]
184
519
  );
185
- const openHistory = React2.useCallback(() => {
520
+ const openHistory = React3.useCallback(() => {
186
521
  setView("history");
187
522
  void chat.refreshConversations();
188
523
  }, [chat]);
@@ -320,11 +655,15 @@ function AgentPanel({
320
655
  suggestion
321
656
  )) })
322
657
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
323
- chat.messages.map((message) => /* @__PURE__ */ jsx(MessageItem, { message }, message.id)),
658
+ groupMessages(chat.messages).map(
659
+ (item) => item.kind === "tool-activity" ? /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(ToolActivityGroup, { describeToolCall: config.describeToolCall, steps: item.steps }) }, item.key) : /* @__PURE__ */ jsx(MessageItem, { message: item.message }, item.key)
660
+ ),
324
661
  chat.pendingApprovals.map((approval) => /* @__PURE__ */ jsx(
325
662
  ToolApprovalCard,
326
663
  {
327
664
  approval,
665
+ describeToolCall: config.describeToolCall,
666
+ onAlwaysApprove,
328
667
  onResolve: (callId, approve) => void chat.resolveApproval(callId, approve),
329
668
  resolving: chat.resolvingApprovalId === approval.id
330
669
  },
@@ -353,16 +692,16 @@ function AgentPanel({
353
692
  );
354
693
  }
355
694
  function useAgentChat(config) {
356
- const client = React2.useMemo(() => new AgentApiClient(config), [config]);
357
- const [agentInfo, setAgentInfo] = React2.useState(null);
358
- const [conversationId, setConversationId] = React2.useState(null);
359
- const [conversations, setConversations] = React2.useState([]);
360
- const [messages, setMessages] = React2.useState([]);
361
- const [pendingApprovals, setPendingApprovals] = React2.useState([]);
362
- const [sending, setSending] = React2.useState(false);
363
- const [resolvingApprovalId, setResolvingApprovalId] = React2.useState(null);
364
- const [error, setError] = React2.useState(null);
365
- React2.useEffect(() => {
695
+ const client = React3.useMemo(() => new AgentApiClient(config), [config]);
696
+ const [agentInfo, setAgentInfo] = React3.useState(null);
697
+ const [conversationId, setConversationId] = React3.useState(null);
698
+ const [conversations, setConversations] = React3.useState([]);
699
+ const [messages, setMessages] = React3.useState([]);
700
+ const [pendingApprovals, setPendingApprovals] = React3.useState([]);
701
+ const [sending, setSending] = React3.useState(false);
702
+ const [resolvingApprovalId, setResolvingApprovalId] = React3.useState(null);
703
+ const [error, setError] = React3.useState(null);
704
+ React3.useEffect(() => {
366
705
  let cancelled = false;
367
706
  client.getConfig().then((info) => {
368
707
  if (!cancelled) setAgentInfo(info);
@@ -372,11 +711,11 @@ function useAgentChat(config) {
372
711
  cancelled = true;
373
712
  };
374
713
  }, [client]);
375
- const handleFailure = React2.useCallback((err) => {
714
+ const handleFailure = React3.useCallback((err) => {
376
715
  const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
377
716
  setError(message);
378
717
  }, []);
379
- const applyResponse = React2.useCallback(
718
+ const applyResponse = React3.useCallback(
380
719
  (response) => {
381
720
  if (response.status === "error" && !response.messages?.length) {
382
721
  setError(response.message ?? "The assistant could not answer.");
@@ -397,7 +736,7 @@ function useAgentChat(config) {
397
736
  },
398
737
  []
399
738
  );
400
- const send = React2.useCallback(
739
+ const send = React3.useCallback(
401
740
  async (text) => {
402
741
  if (sending) return;
403
742
  setSending(true);
@@ -413,7 +752,7 @@ function useAgentChat(config) {
413
752
  },
414
753
  [applyResponse, client, conversationId, handleFailure, sending]
415
754
  );
416
- const resolveApproval = React2.useCallback(
755
+ const resolveApproval = React3.useCallback(
417
756
  async (callId, approve) => {
418
757
  if (resolvingApprovalId) return;
419
758
  setResolvingApprovalId(callId);
@@ -429,13 +768,13 @@ function useAgentChat(config) {
429
768
  },
430
769
  [applyResponse, client, handleFailure, resolvingApprovalId]
431
770
  );
432
- const newChat = React2.useCallback(() => {
771
+ const newChat = React3.useCallback(() => {
433
772
  setConversationId(null);
434
773
  setMessages([]);
435
774
  setPendingApprovals([]);
436
775
  setError(null);
437
776
  }, []);
438
- const refreshConversations = React2.useCallback(async () => {
777
+ const refreshConversations = React3.useCallback(async () => {
439
778
  try {
440
779
  const { conversations: list } = await client.listConversations();
441
780
  setConversations(list);
@@ -443,7 +782,7 @@ function useAgentChat(config) {
443
782
  handleFailure(err);
444
783
  }
445
784
  }, [client, handleFailure]);
446
- const openConversation = React2.useCallback(
785
+ const openConversation = React3.useCallback(
447
786
  async (id) => {
448
787
  setError(null);
449
788
  try {
@@ -457,7 +796,7 @@ function useAgentChat(config) {
457
796
  },
458
797
  [client, handleFailure]
459
798
  );
460
- const deleteConversation = React2.useCallback(
799
+ const deleteConversation = React3.useCallback(
461
800
  async (id) => {
462
801
  try {
463
802
  await client.deleteConversation(id);
@@ -469,7 +808,7 @@ function useAgentChat(config) {
469
808
  },
470
809
  [client, conversationId, handleFailure, newChat]
471
810
  );
472
- const clearError = React2.useCallback(() => setError(null), []);
811
+ const clearError = React3.useCallback(() => setError(null), []);
473
812
  return {
474
813
  agentInfo,
475
814
  clearError,
@@ -488,26 +827,86 @@ function useAgentChat(config) {
488
827
  sending
489
828
  };
490
829
  }
830
+ var STORAGE_PREFIX = "cc-agent-always-approve:";
831
+ function storageKey(clientId) {
832
+ return `${STORAGE_PREFIX}${clientId || "default"}`;
833
+ }
834
+ function readStored(clientId) {
835
+ if (typeof window === "undefined") return /* @__PURE__ */ new Set();
836
+ try {
837
+ const raw = window.localStorage.getItem(storageKey(clientId));
838
+ if (!raw) return /* @__PURE__ */ new Set();
839
+ const parsed = JSON.parse(raw);
840
+ return Array.isArray(parsed) ? new Set(parsed.filter((v) => typeof v === "string")) : /* @__PURE__ */ new Set();
841
+ } catch {
842
+ return /* @__PURE__ */ new Set();
843
+ }
844
+ }
845
+ function useAlwaysApprove(clientId) {
846
+ const [approved, setApproved] = React3.useState(() => /* @__PURE__ */ new Set());
847
+ React3.useEffect(() => {
848
+ setApproved(readStored(clientId));
849
+ }, [clientId]);
850
+ const persist = React3.useCallback(
851
+ (next) => {
852
+ setApproved(next);
853
+ if (typeof window === "undefined") return;
854
+ try {
855
+ window.localStorage.setItem(storageKey(clientId), JSON.stringify([...next]));
856
+ } catch {
857
+ }
858
+ },
859
+ [clientId]
860
+ );
861
+ const isAlwaysApproved = React3.useCallback((toolName) => approved.has(toolName), [approved]);
862
+ const setAlwaysApprove = React3.useCallback(
863
+ (toolName) => {
864
+ if (approved.has(toolName)) return;
865
+ const next = new Set(approved);
866
+ next.add(toolName);
867
+ persist(next);
868
+ },
869
+ [approved, persist]
870
+ );
871
+ const clearAlwaysApprove = React3.useCallback(
872
+ (toolName) => {
873
+ if (!approved.has(toolName)) return;
874
+ const next = new Set(approved);
875
+ next.delete(toolName);
876
+ persist(next);
877
+ },
878
+ [approved, persist]
879
+ );
880
+ return { clearAlwaysApprove, isAlwaysApproved, setAlwaysApprove };
881
+ }
491
882
  function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
492
- const [uncontrolledOpen, setUncontrolledOpen] = React2.useState(false);
883
+ const [uncontrolledOpen, setUncontrolledOpen] = React3.useState(false);
493
884
  const open = controlledOpen ?? uncontrolledOpen;
494
- const setOpen = React2.useCallback(
885
+ const setOpen = React3.useCallback(
495
886
  (next) => {
496
887
  setUncontrolledOpen(next);
497
888
  onOpenChange?.(next);
498
889
  },
499
890
  [onOpenChange]
500
891
  );
501
- const [agentMode, setAgentMode] = React2.useState(false);
892
+ const [agentMode, setAgentMode] = React3.useState(false);
502
893
  const isDesktop = useMediaQuery("(min-width: 640px)", {
503
894
  defaultSSRValue: true,
504
895
  ssr: true
505
896
  });
506
897
  const fullscreen = !isDesktop || agentMode;
507
898
  const chat = useAgentChat(config);
508
- const lastNavigatedMessageId = React2.useRef(null);
899
+ const alwaysApprove = useAlwaysApprove(config.clientId);
900
+ const { pendingApprovals, resolveApproval, resolvingApprovalId } = chat;
901
+ const { isAlwaysApproved } = alwaysApprove;
902
+ React3.useEffect(() => {
903
+ if (!open || resolvingApprovalId) return;
904
+ const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
905
+ if (target) void resolveApproval(target.id, true);
906
+ }, [open, pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
907
+ const lastNavigatedMessageId = React3.useRef(null);
509
908
  const { messages } = chat;
510
- React2.useEffect(() => {
909
+ React3.useEffect(() => {
511
910
  if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
512
911
  const toolMessages = messages.filter((message) => message.role === "tool");
513
912
  const latest = toolMessages[toolMessages.length - 1];
@@ -519,7 +918,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
519
918
  const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
520
919
  if (path) config.navigate(path);
521
920
  }, [config, fullscreen, messages, open]);
522
- const closePanel = React2.useCallback(() => {
921
+ const closePanel = React3.useCallback(() => {
523
922
  setOpen(false);
524
923
  setAgentMode(false);
525
924
  }, [setOpen]);
@@ -547,6 +946,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
547
946
  chat,
548
947
  className: "flex-1",
549
948
  config,
949
+ onAlwaysApprove: alwaysApprove.setAlwaysApprove,
550
950
  onClose: closePanel,
551
951
  onToggleAgentMode: () => setAgentMode((value) => !value)
552
952
  }
@@ -572,6 +972,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
572
972
  chat,
573
973
  className: "flex-1",
574
974
  config,
975
+ onAlwaysApprove: alwaysApprove.setAlwaysApprove,
575
976
  onClose: closePanel,
576
977
  onToggleAgentMode: () => setAgentMode(true)
577
978
  }
@@ -596,6 +997,6 @@ function defineAgentConfig(config) {
596
997
  return config;
597
998
  }
598
999
 
599
- export { AgentApiClient, AgentApiError, AgentPanel, AgentWidget, MessageItem, ToolApprovalCard, defineAgentConfig, useAgentChat };
1000
+ export { AgentApiClient, AgentApiError, AgentPanel, AgentWidget, MessageItem, ToolActivityGroup, ToolApprovalCard, defineAgentConfig, deriveDetails, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove };
600
1001
  //# sourceMappingURL=index.js.map
601
1002
  //# sourceMappingURL=index.js.map