@candor-ui/react 1.3.0 → 1.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/adapters/ag-ui.d.ts +66 -1
- package/dist/adapters/ag-ui.js +308 -1
- package/dist/adapters/ai-sdk.d.ts +6 -1
- package/dist/adapters/ai-sdk.js +18 -1
- package/dist/adapters/claude-agent-sdk.d.ts +111 -2
- package/dist/adapters/claude-agent-sdk.js +163 -5
- package/dist/adapters/claude-agent-server.d.ts +195 -10
- package/dist/adapters/claude-agent-server.js +448 -49
- package/dist/adapters/use-ag-ui.d.ts +6 -0
- package/dist/adapters/use-ag-ui.js +10 -4
- package/dist/adapters/use-candor-chat.js +2 -2
- package/dist/adapters/use-claude-agent.d.ts +35 -6
- package/dist/adapters/use-claude-agent.js +96 -20
- package/dist/chat/ai-disclosure.d.ts +37 -0
- package/dist/chat/ai-disclosure.js +48 -0
- package/dist/chat/attachment-preview.js +1 -1
- package/dist/chat/card-registry.js +1 -1
- package/dist/chat/citation.js +5 -3
- package/dist/chat/entity-chip.js +1 -1
- package/dist/chat/folded-turns.js +1 -1
- package/dist/chat/message.js +1 -1
- package/dist/chat/payload.js +5 -2
- package/dist/chat/permission-prompt.d.ts +129 -0
- package/dist/chat/permission-prompt.js +144 -0
- package/dist/chat/reasoning.js +1 -1
- package/dist/chat/thread.js +1 -1
- package/dist/chat/tool-call.d.ts +5 -0
- package/dist/chat/tool-call.js +7 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/chat.d.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Info, Pencil, ShieldAlert, ShieldQuestion } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { useStrings } from "../blocks/locale.js";
|
|
7
|
+
import { Payload } from "./payload.js";
|
|
8
|
+
import { Button } from "../ui/button.js";
|
|
9
|
+
/** PermissionPrompt's fixed words; override them under `permissionPrompt` in LocaleProvider's `strings`. */
|
|
10
|
+
export const permissionPromptStrings = {
|
|
11
|
+
label: "Permission request: {title}",
|
|
12
|
+
fallbackTitle: "The agent wants to use {tool}",
|
|
13
|
+
why: "Why you’re asked",
|
|
14
|
+
mode: "Permission mode",
|
|
15
|
+
modeDefault: "Ask before acting",
|
|
16
|
+
modeAcceptEdits: "Accept file edits",
|
|
17
|
+
modePlan: "Plan only",
|
|
18
|
+
modeAuto: "Automatic, by a classifier",
|
|
19
|
+
modeDontAsk: "Don’t ask; deny what isn’t allowed",
|
|
20
|
+
modeBypassPermissions: "Allow everything",
|
|
21
|
+
blockedPath: "Path outside what’s allowed: {path}",
|
|
22
|
+
subagent: "A subagent asked.",
|
|
23
|
+
server: "From the tool server “{name}”, configured in {source}.",
|
|
24
|
+
input: "What it will run with",
|
|
25
|
+
edit: "Edit before allowing",
|
|
26
|
+
editLabel: "Input, as JSON",
|
|
27
|
+
editInvalid: "That isn’t a JSON object, so it can’t be used.",
|
|
28
|
+
changed: "You changed",
|
|
29
|
+
unchanged: "Nothing changed yet.",
|
|
30
|
+
cancelEdit: "Use the original",
|
|
31
|
+
remember: "Don’t ask again for",
|
|
32
|
+
rememberHint: "Optional. Nothing is remembered unless you tick it.",
|
|
33
|
+
ruleAllow: "Allow {what}",
|
|
34
|
+
ruleDeny: "Always deny {what}",
|
|
35
|
+
ruleAsk: "Always ask for {what}",
|
|
36
|
+
ruleRemove: "Stop applying the rule for {what}",
|
|
37
|
+
setMode: "Switch to: {mode}",
|
|
38
|
+
warnBypassPermissions: "After this, nothing is checked: the agent can run any command and change or delete any file it can reach.",
|
|
39
|
+
warnAuto: "After this, a classifier allows or blocks each action instead of you.",
|
|
40
|
+
directories: "Access to {what}",
|
|
41
|
+
other: "A permission change: {what}",
|
|
42
|
+
scopeSession: "for this session",
|
|
43
|
+
scopeLocal: "in this project, on this computer",
|
|
44
|
+
scopeProject: "in this project, for everyone on it",
|
|
45
|
+
scopeUser: "for you, in every project",
|
|
46
|
+
allow: "Allow",
|
|
47
|
+
allowEdited: "Allow as edited",
|
|
48
|
+
deny: "Deny",
|
|
49
|
+
answer: "Send answers",
|
|
50
|
+
otherOption: "Other",
|
|
51
|
+
otherLabel: "Your own answer to: {question}",
|
|
52
|
+
questions: "The agent has questions",
|
|
53
|
+
};
|
|
54
|
+
const modeKeys = {
|
|
55
|
+
default: "modeDefault",
|
|
56
|
+
acceptEdits: "modeAcceptEdits",
|
|
57
|
+
plan: "modePlan",
|
|
58
|
+
auto: "modeAuto",
|
|
59
|
+
dontAsk: "modeDontAsk",
|
|
60
|
+
bypassPermissions: "modeBypassPermissions",
|
|
61
|
+
};
|
|
62
|
+
const json = (v) => (typeof v === "string" ? v : JSON.stringify(v, null, 2));
|
|
63
|
+
/** Parses edited JSON into an object, or null when it isn't one. */
|
|
64
|
+
function parseObject(text) {
|
|
65
|
+
try {
|
|
66
|
+
const v = JSON.parse(text);
|
|
67
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A tool call waiting for a person's go-ahead: what it will do, why it reached them, and the permission mode the
|
|
75
|
+
* run is in. They can edit the input before allowing it and see exactly what they changed; Allow then sends that
|
|
76
|
+
* input, nothing else. "Don't ask again" rules are offered as boxes nobody has ticked, and are kept only if the
|
|
77
|
+
* person ticks them. When the agent is asking questions instead, it shows them as a short form. Words from the
|
|
78
|
+
* request (its title, reason, input) are shown as text, never markup.
|
|
79
|
+
*/
|
|
80
|
+
function PermissionPrompt({ request, mode, suggestions, editable = false, onAllow, onDeny, busy = false, className, }) {
|
|
81
|
+
// Keyed by the request, so an edit, a ticked rule or an answer never carries over to the next request.
|
|
82
|
+
return _jsx(PromptBody, { request, mode, suggestions, editable: editable && request.editable !== false, onAllow, onDeny, busy, className }, request.id);
|
|
83
|
+
}
|
|
84
|
+
function PromptBody({ request, mode, suggestions, editable, onAllow, onDeny, busy, className, }) {
|
|
85
|
+
const t = useStrings("permissionPrompt", permissionPromptStrings);
|
|
86
|
+
const id = React.useId();
|
|
87
|
+
const original = React.useMemo(() => json(request.input ?? {}), [request.input]);
|
|
88
|
+
const [editing, setEditing] = React.useState(false);
|
|
89
|
+
const [draft, setDraft] = React.useState(original);
|
|
90
|
+
const [ticked, setTicked] = React.useState([]);
|
|
91
|
+
const [answers, setAnswers] = React.useState({});
|
|
92
|
+
const edited = editing ? parseObject(draft) : null;
|
|
93
|
+
const proposed = parseObject(original);
|
|
94
|
+
const changedKeys = edited && proposed ? [...new Set([...Object.keys(proposed), ...Object.keys(edited)])].filter((k) => JSON.stringify(proposed[k]) !== JSON.stringify(edited[k])) : [];
|
|
95
|
+
const invalid = editing && !edited;
|
|
96
|
+
const title = request.title ?? t("fallbackTitle", { tool: request.tool });
|
|
97
|
+
const questions = request.questions;
|
|
98
|
+
// A request that grants more than it looks opens on Deny, so a stray Enter can't allow it.
|
|
99
|
+
const denyRef = React.useRef(null);
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
if (request.defaultToNo)
|
|
102
|
+
denyRef.current?.focus();
|
|
103
|
+
}, [request.id, request.defaultToNo]);
|
|
104
|
+
const word = (s) => {
|
|
105
|
+
const key = { allow: "ruleAllow", deny: "ruleDeny", ask: "ruleAsk", remove: "ruleRemove", mode: "setMode", directories: "directories", other: "other" }[s.action];
|
|
106
|
+
const scope = s.scope && { session: "scopeSession", local: "scopeLocal", project: "scopeProject", user: "scopeUser" }[s.scope];
|
|
107
|
+
const what = s.action === "mode" && modeKeys[s.what] ? t(modeKeys[s.what]) : s.what;
|
|
108
|
+
return { text: t(key, { what, mode: what }), scope: scope ? t(scope) : s.scope };
|
|
109
|
+
};
|
|
110
|
+
const answered = (q) => {
|
|
111
|
+
const a = answers[q.id];
|
|
112
|
+
return Boolean(a && (a.choice.length || (a.other && a.text.trim())));
|
|
113
|
+
};
|
|
114
|
+
const choose = (q, option) => setAnswers((prev) => {
|
|
115
|
+
const a = prev[q.id] ?? { choice: [], text: "", other: false };
|
|
116
|
+
const choice = q.multiple ? (a.choice.includes(option) ? a.choice.filter((c) => c !== option) : [...a.choice, option]) : [option];
|
|
117
|
+
return { ...prev, [q.id]: { ...a, choice, other: q.multiple ? a.other : false } };
|
|
118
|
+
});
|
|
119
|
+
const toggleOther = (q) => setAnswers((prev) => {
|
|
120
|
+
const a = prev[q.id] ?? { choice: [], text: "", other: false };
|
|
121
|
+
return { ...prev, [q.id]: { ...a, other: !a.other, choice: q.multiple || a.other ? a.choice : [] } };
|
|
122
|
+
});
|
|
123
|
+
const allow = () => {
|
|
124
|
+
if (busy)
|
|
125
|
+
return;
|
|
126
|
+
if (questions) {
|
|
127
|
+
if (!questions.every(answered))
|
|
128
|
+
return;
|
|
129
|
+
onAllow({ answers: questions.map((q) => ({ questionId: q.id, choice: answers[q.id]?.choice, text: answers[q.id]?.other ? answers[q.id]?.text.trim() : undefined })) });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (invalid)
|
|
133
|
+
return;
|
|
134
|
+
onAllow({ ...(edited && changedKeys.length && { updatedInput: edited }), ...(ticked.length && { alwaysAllow: ticked }) });
|
|
135
|
+
};
|
|
136
|
+
return (_jsxs("section", { "data-slot": "permission-prompt", "aria-label": t("label", { title: questions ? t("questions") : title }), className: cn("overflow-hidden rounded-xl border border-warning/50 bg-card text-sm shadow-xs", className), children: [_jsxs("header", { className: "flex items-start gap-2.5 bg-warning-weak/50 px-3.5 py-2.5", children: [_jsx(ShieldQuestion, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0 text-warning-text" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h3", { className: "font-semibold leading-snug", children: questions ? t("questions") : title }), request.description && _jsx("p", { className: "mt-0.5 text-muted-foreground", children: request.description })] }), _jsx("code", { className: "shrink-0 font-mono text-2xs text-muted-foreground", children: request.tool })] }), _jsxs("dl", { className: "flex flex-col gap-1.5 px-3.5 pt-2.5 text-sm", children: [request.reason && (_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground", children: t("why") }), _jsx("dd", { className: "min-w-0", children: request.reason })] })), mode && (_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground", children: t("mode") }), _jsx("dd", { "data-slot": "permission-mode", children: modeKeys[mode] ? t(modeKeys[mode]) : _jsx("code", { className: "font-mono text-xs", children: mode }) })] }))] }), (request.blockedPath || request.agentId || request.mcpServer) && (_jsxs("ul", { className: "mt-1.5 flex flex-col gap-0.5 px-3.5 text-xs text-muted-foreground", children: [request.blockedPath && _jsx("li", { children: t("blockedPath", { path: request.blockedPath }) }), request.agentId && _jsx("li", { children: t("subagent") }), request.mcpServer && _jsx("li", { children: t("server", { name: request.mcpServer.name, source: request.mcpServer.source }) })] })), questions ? (_jsx("div", { className: "flex flex-col gap-4 px-3.5 py-3", children: questions.map((q) => {
|
|
137
|
+
const a = answers[q.id];
|
|
138
|
+
return (_jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsxs("legend", { className: "mb-1 font-medium", children: [q.tag && _jsx("span", { className: "me-1.5 rounded-xs bg-muted px-1 text-2xs font-medium text-muted-foreground", children: q.tag }), q.prompt] }), q.options.map((o) => (_jsxs("label", { className: "flex min-h-6 items-start gap-2", children: [_jsx("input", { type: q.multiple ? "checkbox" : "radio", name: `${id}-${q.id}`, checked: a?.choice.includes(o.id) ?? false, onChange: () => choose(q, o.id), className: "mt-0.5 size-4 accent-[var(--primary)]" }), _jsxs("span", { children: [o.label, o.detail && _jsx("span", { className: "block text-xs text-muted-foreground", children: o.detail })] })] }, o.id))), q.other && (_jsxs(_Fragment, { children: [_jsxs("label", { className: "flex min-h-6 items-start gap-2", children: [_jsx("input", { type: q.multiple ? "checkbox" : "radio", name: `${id}-${q.id}`, checked: a?.other ?? false, onChange: () => toggleOther(q), className: "mt-0.5 size-4 accent-[var(--primary)]" }), _jsx("span", { children: t("otherOption") })] }), a?.other && (_jsx("textarea", { "aria-label": t("otherLabel", { question: q.prompt }), rows: 2, value: a.text, onChange: (e) => setAnswers((prev) => ({ ...prev, [q.id]: { ...prev[q.id], text: e.target.value } })), className: "ms-6 block resize-none rounded-md border border-input bg-card px-2.5 py-1.5 text-sm outline-none [field-sizing:content] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30" }))] }))] }, q.id));
|
|
139
|
+
}) })) : (_jsxs("div", { className: "flex flex-col gap-2 px-3.5 py-3", children: [editing ? (_jsxs(_Fragment, { children: [_jsx("label", { htmlFor: `${id}-edit`, className: "text-xs font-medium text-muted-foreground", children: t("editLabel") }), _jsx("textarea", { id: `${id}-edit`, value: draft, onChange: (e) => setDraft(e.target.value), spellCheck: false, rows: Math.min(12, draft.split("\n").length + 1), "aria-invalid": invalid || undefined, "aria-describedby": `${id}-changes`, className: "block w-full resize-y rounded-md border border-input bg-card px-2.5 py-1.5 font-mono text-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30 aria-invalid:border-destructive" }), _jsx("div", { id: `${id}-changes`, role: "status", className: "text-xs", children: invalid ? (_jsx("p", { className: "text-destructive-text", children: t("editInvalid") })) : changedKeys.length ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-muted-foreground", children: t("changed") }), _jsx("ul", { className: "flex flex-col gap-1", children: changedKeys.map((k) => (_jsxs("li", { className: "grid gap-0.5 font-mono", children: [_jsx("span", { className: "text-muted-foreground", children: k }), k in proposed && _jsx("span", { className: "rounded-xs bg-destructive-weak/60 px-1 line-through", children: json(proposed[k]) }), k in edited && _jsx("span", { className: "rounded-xs bg-success-weak/70 px-1", children: json(edited[k]) })] }, k))) })] })) : (_jsx("p", { className: "text-muted-foreground", children: t("unchanged") })) }), _jsx(Button, { size: "sm", variant: "ghost", className: "self-start", onClick: () => (setEditing(false), setDraft(original)), children: t("cancelEdit") })] })) : (_jsxs(_Fragment, { children: [_jsx(Payload, { language: "json", title: t("input"), text: original }), editable && proposed && (_jsxs(Button, { size: "sm", variant: "outline", className: "self-start", onClick: () => setEditing(true), children: [_jsx(Pencil, { "aria-hidden": true }), " ", t("edit")] }))] })), suggestions && suggestions.length > 0 && (_jsxs("fieldset", { className: "mt-1 flex flex-col gap-1 rounded-lg border border-dashed px-3 py-2", children: [_jsx("legend", { className: "px-1 text-xs font-medium", children: t("remember") }), _jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Info, { "aria-hidden": true, className: "size-3" }), " ", t("rememberHint")] }), suggestions.map((s, i) => {
|
|
140
|
+
const w = word(s);
|
|
141
|
+
return (_jsxs("label", { className: "flex min-h-6 items-start gap-2", children: [_jsx("input", { type: "checkbox", checked: ticked.includes(i), onChange: (e) => setTicked((prev) => (e.target.checked ? [...prev, i] : prev.filter((x) => x !== i))), className: "mt-0.5 size-4 accent-[var(--primary)]" }), _jsxs("span", { children: [_jsx("span", { className: "font-mono text-xs", children: w.text }), w.scope && _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", w.scope] }), s.action === "mode" && (s.what === "bypassPermissions" || s.what === "auto") && (_jsxs("span", { className: "mt-0.5 flex items-start gap-1 text-xs text-warning-text", children: [_jsx(ShieldAlert, { "aria-hidden": true, className: "mt-px size-3.5 shrink-0" }), t(s.what === "auto" ? "warnAuto" : "warnBypassPermissions")] }))] })] }, i));
|
|
142
|
+
})] }))] })), _jsxs("footer", { className: "flex flex-wrap justify-end gap-2 border-t bg-sunken px-3.5 py-2", children: [_jsx(Button, { ref: denyRef, size: "sm", variant: "outline", onClick: onDeny, disabled: busy, children: t("deny") }), _jsx(Button, { size: "sm", onClick: allow, disabled: busy || invalid || (questions ? !questions.every(answered) : false), "aria-busy": busy, children: questions ? t("answer") : edited && changedKeys.length ? t("allowEdited") : t("allow") })] })] }));
|
|
143
|
+
}
|
|
144
|
+
export { PermissionPrompt };
|
package/dist/chat/reasoning.js
CHANGED
|
@@ -35,6 +35,6 @@ function Reasoning({ entries, thinking = false, thoughtFor, className, }) {
|
|
|
35
35
|
React.useEffect(() => setOpen(thinking), [thinking]);
|
|
36
36
|
const assumed = entries.filter((e) => e.basis === "assumed").length;
|
|
37
37
|
const summary = thinking ? null : (_jsxs(_Fragment, { children: [t("reasoning"), thoughtFor && _jsxs(_Fragment, { children: [" \u00B7 ", thoughtFor] }), " \u00B7 ", t("steps", { count: entries.length }), assumed > 0 && _jsxs("span", { className: "text-warning-text", children: [" \u00B7 ", t("assumedCount", { count: assumed })] })] }));
|
|
38
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(Brain, { "aria-hidden": true, className: cn("size-3.5", thinking && "text-accent-text") }), thinking ? _jsx(ShimmerLabel, { className: "text-xs", children: t("thinking") }) : summary, _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform duration-200 ease-standard group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { className: "relative animate-rise-in", children: [_jsx("span", { "aria-hidden": true, className: "absolute top-1.5 -start-[1.2rem] size-1.5 rounded-full bg-border-strong" }), _jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: e.title }), _jsx("span", { className: cn("rounded-xs px-1 text-2xs font-medium", basisTone[e.basis]), children: t(e.basis) })] }), e.body && _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: e.body }), _jsx("p", { className: "mt-1 text-xs text-faint-foreground", children: e.basis === "verified" ? _jsxs(_Fragment, { children: [t("evidence"), " ", e.evidence] }) : _jsxs(_Fragment, { children: [t("assumption"), " ", e.assumption] }) })] }, e.id))) }) })] }));
|
|
38
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(Brain, { "aria-hidden": true, className: cn("size-3.5", thinking && "text-accent-text") }), thinking ? _jsx(ShimmerLabel, { className: "text-xs", children: t("thinking") }) : summary, _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform duration-200 ease-standard group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { className: "relative animate-rise-in", children: [_jsx("span", { "aria-hidden": true, className: "absolute top-1.5 -start-[1.2rem] size-1.5 rounded-full bg-border-strong" }), _jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: e.title }), _jsx("span", { className: cn("rounded-xs px-1 text-2xs font-medium", basisTone[e.basis]), children: t(e.basis) })] }), e.body && _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: e.body }), _jsx("p", { className: "mt-1 text-xs text-faint-foreground", children: e.basis === "verified" ? _jsxs(_Fragment, { children: [t("evidence"), " ", e.evidence] }) : _jsxs(_Fragment, { children: [t("assumption"), " ", e.assumption] }) })] }, e.id))) }) })] }));
|
|
39
39
|
}
|
|
40
40
|
export { Reasoning };
|
package/dist/chat/thread.js
CHANGED
|
@@ -79,7 +79,7 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
79
79
|
? (e) => {
|
|
80
80
|
setFocused(e.target.closest("[data-virtual-key]")?.getAttribute("data-virtual-key") ?? null);
|
|
81
81
|
}
|
|
82
|
-
: undefined, onBlur: virtualize ? (e) => !e.currentTarget.contains(e.relatedTarget) && setFocused(null) : undefined, className: "h-full overflow-y-auto overscroll-contain", children: _jsx("div", { ref: content, role: "log", "aria-label": t("messages"), className: cn("mx-auto flex w-full max-w-(--cd-thread-width) flex-col gap-7 px-4 py-6", contentClassName), children: !hasMessages ? (empty) : virtualize ? (_jsx(_Fragment, { children: list.rows.map((row) => "spacer" in row ? (_jsx("div", { "aria-hidden": true, style: { height: row.spacer } }, row.key)) : (_jsx("div", { ref: list.measure, "data-virtual-key": String(keyOf(row.index)), className: "flex flex-col", children: items[row.index] }, keyOf(row.index)))) })) : (children) }) }), !pinned && hasMessages && (_jsxs("button", { type: "button", onClick: () => {
|
|
82
|
+
: undefined, onBlur: virtualize ? (e) => !e.currentTarget.contains(e.relatedTarget) && setFocused(null) : undefined, className: "h-full scroll-pb-14 overflow-y-auto overscroll-contain", children: _jsx("div", { ref: content, role: "log", "aria-label": t("messages"), className: cn("mx-auto flex w-full max-w-(--cd-thread-width) flex-col gap-7 px-4 py-6", contentClassName), children: !hasMessages ? (empty) : virtualize ? (_jsx(_Fragment, { children: list.rows.map((row) => "spacer" in row ? (_jsx("div", { "aria-hidden": true, style: { height: row.spacer } }, row.key)) : (_jsx("div", { ref: list.measure, "data-virtual-key": String(keyOf(row.index)), className: "flex flex-col", children: items[row.index] }, keyOf(row.index)))) })) : (children) }) }), !pinned && hasMessages && (_jsxs("button", { type: "button", onClick: () => {
|
|
83
83
|
pinnedRef.current = true;
|
|
84
84
|
setPinned(true);
|
|
85
85
|
toBottom("smooth");
|
package/dist/chat/tool-call.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export declare const toolCallStrings: {
|
|
|
11
11
|
done: string;
|
|
12
12
|
failed: string;
|
|
13
13
|
working: string;
|
|
14
|
+
approvedAutomatically: string;
|
|
15
|
+
approvedByPerson: string;
|
|
16
|
+
editedBeforeRun: string;
|
|
17
|
+
editedNote: string;
|
|
18
|
+
proposed: string;
|
|
14
19
|
};
|
|
15
20
|
/**
|
|
16
21
|
* One tool call as a compact row: what it did in plain words, its status, and
|
package/dist/chat/tool-call.js
CHANGED
|
@@ -17,6 +17,11 @@ export const toolCallStrings = {
|
|
|
17
17
|
done: "{count} done",
|
|
18
18
|
failed: "{count} failed",
|
|
19
19
|
working: "working",
|
|
20
|
+
approvedAutomatically: "Approved automatically",
|
|
21
|
+
approvedByPerson: "Approved by a person",
|
|
22
|
+
editedBeforeRun: "Edited before it ran",
|
|
23
|
+
editedNote: "A person changed the input before allowing it. Input is what ran.",
|
|
24
|
+
proposed: "As the agent proposed it",
|
|
20
25
|
};
|
|
21
26
|
/**
|
|
22
27
|
* One tool call as a compact row: what it did in plain words, its status, and
|
|
@@ -28,8 +33,8 @@ function ToolCall({ call, className, }) {
|
|
|
28
33
|
const statusLabel = useStatusLabel();
|
|
29
34
|
const failed = call.status === "failed";
|
|
30
35
|
const [open, setOpen] = React.useState(failed);
|
|
31
|
-
const hasDetail = Boolean(call.input || call.output || call.error);
|
|
32
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none transition-colors duration-150 enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground transition-transform duration-200 ease-standard group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { className: "data-[state=open]:animate-in data-[state=open]:fade-in-0", children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
|
|
36
|
+
const hasDetail = Boolean(call.input || call.output || call.error || call.approval?.note || call.approval?.originalInput);
|
|
37
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none transition-colors duration-150 enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), call.approval?.originalInput !== undefined && (_jsx("span", { "data-slot": "tool-call-edited", className: "shrink-0 rounded-xs bg-warning-weak px-1 text-2xs text-warning-text", children: t("editedBeforeRun") })), call.approval?.by === "automatic" && (_jsx("span", { "data-slot": "tool-call-approval", className: "shrink-0 rounded-xs bg-muted px-1 text-2xs text-muted-foreground", children: t("approvedAutomatically") })), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground transition-transform duration-200 ease-standard group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { className: "data-[state=open]:animate-in data-[state=open]:fade-in-0", children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.approval?.note && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [call.approval.by === "automatic" ? t("approvedAutomatically") : t("approvedByPerson"), ": ", call.approval.note] })), call.approval?.originalInput !== undefined && _jsx("p", { className: "text-xs text-muted-foreground", children: t("editedNote") }), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.approval?.originalInput !== undefined && _jsx(Payload, { language: "json", title: t("proposed"), text: call.approval.originalInput }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
|
|
33
38
|
}
|
|
34
39
|
/** A run of tool calls, with a count when it's long enough to fold. */
|
|
35
40
|
function ToolCallGroup({ calls, className, }) {
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export * from "./ui/popover.js";
|
|
|
40
40
|
export * from "./ui/skeleton.js";
|
|
41
41
|
export * from "./lib/know.js";
|
|
42
42
|
export * from "./lib/render-prop.js";
|
|
43
|
+
export * from "./chat/ai-disclosure.js";
|
|
44
|
+
export * from "./chat/permission-prompt.js";
|
|
43
45
|
export * from "./lib/utils.js";
|
|
44
46
|
export { ToolCall } from "./chat/tool-call.js";
|
|
45
47
|
export type { ToolCall as ToolCallData } from "./lib/chat.js";
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,8 @@ export * from "./ui/popover.js";
|
|
|
40
40
|
export * from "./ui/skeleton.js";
|
|
41
41
|
export * from "./lib/know.js";
|
|
42
42
|
export * from "./lib/render-prop.js";
|
|
43
|
+
export * from "./chat/ai-disclosure.js";
|
|
44
|
+
export * from "./chat/permission-prompt.js";
|
|
43
45
|
export * from "./lib/utils.js";
|
|
44
46
|
export { ToolCall } from "./chat/tool-call.js";
|
|
45
47
|
export { Message } from "./chat/message.js";
|
package/dist/lib/chat.d.ts
CHANGED
|
@@ -108,6 +108,11 @@ export type MessageSource = {
|
|
|
108
108
|
origin?: string;
|
|
109
109
|
provisional?: boolean;
|
|
110
110
|
};
|
|
111
|
+
/** A source a tool's result supports, without the id and [n] an adapter assigns. */
|
|
112
|
+
export type ToolSource = Omit<MessageSource, "id" | "marker"> & {
|
|
113
|
+
/** Makes two results naming the same thing one source; defaults to `kind` and `label`. */
|
|
114
|
+
key?: string;
|
|
115
|
+
};
|
|
111
116
|
type ReasoningBase = {
|
|
112
117
|
id: string;
|
|
113
118
|
title: string;
|
|
@@ -136,6 +141,16 @@ export type ToolCall = {
|
|
|
136
141
|
duration?: string;
|
|
137
142
|
/** Required when status is "failed": what went wrong, in a sentence. */
|
|
138
143
|
error?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Who let it run, when it needed a go-ahead: a person, or something automatic (a policy, a classifier, a rule).
|
|
146
|
+
* `note` is why, in the source's words. Left out when it needed none or the source doesn't say.
|
|
147
|
+
*/
|
|
148
|
+
approval?: {
|
|
149
|
+
by: "person" | "automatic";
|
|
150
|
+
note?: string;
|
|
151
|
+
/** The input as the agent proposed it, when a person edited it before it ran; `input` is then what ran. */
|
|
152
|
+
originalInput?: string;
|
|
153
|
+
};
|
|
139
154
|
};
|
|
140
155
|
/** The block's language, e.g. "json", "sql", "python", "text". Shown as a label; never executed or highlighted as HTML. */
|
|
141
156
|
export type PayloadLanguage = string;
|
package/package.json
CHANGED