basalt-ui 1.10.0 → 1.11.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/AGENTS.md +2 -2
- package/CHANGELOG.md +10 -0
- package/dist/agent/ai-sdk-transport.d.ts +3 -52
- package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
- package/dist/agent/ai-sdk-transport.js +108 -24
- package/dist/agent/coalesce.d.ts +26 -0
- package/dist/agent/coalesce.d.ts.map +1 -0
- package/dist/agent/coalesce.js +69 -0
- package/dist/agent/foreign.d.ts +97 -0
- package/dist/agent/foreign.d.ts.map +1 -0
- package/dist/agent/foreign.js +18 -0
- package/dist/agent/history.d.ts +2 -0
- package/dist/agent/history.d.ts.map +1 -1
- package/dist/agent/id.d.ts +35 -0
- package/dist/agent/id.d.ts.map +1 -0
- package/dist/agent/id.js +14 -0
- package/dist/agent/index.d.ts +9 -4
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +15 -3
- package/dist/agent/merge.d.ts +8 -0
- package/dist/agent/merge.d.ts.map +1 -0
- package/dist/agent/merge.js +23 -0
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/part-list.js +16 -6
- package/dist/agent/parts.d.ts +100 -21
- package/dist/agent/parts.d.ts.map +1 -1
- package/dist/agent/parts.js +128 -11
- package/dist/agent/transport.d.ts +39 -3
- package/dist/agent/transport.d.ts.map +1 -1
- package/dist/agent/transport.js +20 -17
- package/dist/agent/use-agent-stream.d.ts +2 -1
- package/dist/agent/use-agent-stream.d.ts.map +1 -1
- package/dist/agent/use-agent-stream.js +5 -2
- package/dist/agent/use-agent-thread-runs.d.ts +9 -3
- package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.js +75 -23
- package/dist/agent-chat/index.d.ts +2 -0
- package/dist/agent-chat/index.d.ts.map +1 -1
- package/dist/agent-chat/index.js +2 -0
- package/dist/agent-chat/thread-message.d.ts +24 -7
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +102 -43
- package/dist/agent-chat/tool-chip.d.ts +21 -0
- package/dist/agent-chat/tool-chip.d.ts.map +1 -0
- package/dist/agent-chat/tool-chip.js +120 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +1 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/surfaces.d.ts +2 -2
- package/dist/surfaces.js +2 -2
- package/llms.txt +4 -4
- package/package.json +1 -1
- package/src/agent/ai-sdk-transport.test.ts +338 -40
- package/src/agent/ai-sdk-transport.ts +206 -53
- package/src/agent/coalesce.test.ts +194 -0
- package/src/agent/coalesce.ts +118 -0
- package/src/agent/foreign.test.ts +47 -0
- package/src/agent/foreign.ts +140 -0
- package/src/agent/history.ts +2 -0
- package/src/agent/id.test.ts +71 -0
- package/src/agent/id.ts +43 -0
- package/src/agent/index.ts +26 -3
- package/src/agent/merge.test.ts +119 -0
- package/src/agent/merge.ts +67 -0
- package/src/agent/part-list.test.tsx +82 -0
- package/src/agent/part-list.tsx +31 -6
- package/src/agent/parts.test.ts +401 -59
- package/src/agent/parts.ts +262 -32
- package/src/agent/transport.test.ts +70 -0
- package/src/agent/transport.ts +72 -20
- package/src/agent/use-agent-stream.test.tsx +47 -17
- package/src/agent/use-agent-stream.ts +13 -3
- package/src/agent/use-agent-thread-runs.foreign.test.tsx +133 -0
- package/src/agent/use-agent-thread-runs.resume.test.tsx +34 -6
- package/src/agent/use-agent-thread-runs.test.tsx +397 -14
- package/src/agent/use-agent-thread-runs.ts +170 -26
- package/src/agent/use-agent-thread-runs.wedge.test.tsx +4 -2
- package/src/agent-chat/index.ts +4 -0
- package/src/agent-chat/thread-message.test.tsx +211 -0
- package/src/agent-chat/thread-message.tsx +235 -62
- package/src/agent-chat/tool-chip.test.tsx +139 -0
- package/src/agent-chat/tool-chip.tsx +219 -0
- package/src/guard/check-source.test.ts +8 -4
- package/src/guard/index.ts +1 -3
- package/src/index.ts +2 -0
- package/src/surfaces.ts +2 -2
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolChip — a one-line collapsed row for a tool-call part, expanding to its input/output JSON.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `thread-message.tsx`'s old always-expanded `ToolRenderer`, which unconditionally
|
|
5
|
+
* rendered both input and output `<pre>` blocks. Collapsed, it shows a mono micro-label, the tool
|
|
6
|
+
* name, a state dot, and `durationMs` when present; expanded, it shows whatever the part's state
|
|
7
|
+
* actually carries — input, output, the surviving `rawInput` for a call whose input never
|
|
8
|
+
* validated, the error text, the denial reason, or the approve/deny affordances.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { ToolChip } from 'basalt-ui/agent-chat'
|
|
12
|
+
*
|
|
13
|
+
* <ToolChip
|
|
14
|
+
* part={part}
|
|
15
|
+
* onApprove={(id) => api.runs.approve.post({ approvalId: id })}
|
|
16
|
+
* onDeny={(id, reason) => api.runs.deny.post({ approvalId: id, reason })}
|
|
17
|
+
* />
|
|
18
|
+
*/
|
|
19
|
+
import { Box, Button, Collapse, Group, Stack, Text, UnstyledButton } from '@mantine/core'
|
|
20
|
+
import { useDisclosure } from '@mantine/hooks'
|
|
21
|
+
import type { JSX } from 'react'
|
|
22
|
+
import { isToolCallSettled } from '../agent'
|
|
23
|
+
import type { ToolCallPart } from '../agent'
|
|
24
|
+
import { alpha, VX } from '../tokens'
|
|
25
|
+
|
|
26
|
+
// Mirrors thread-message.tsx's micro-label/rail/code-block idiom (docs/DESIGN-SPEC.md §3/§5) —
|
|
27
|
+
// duplicated locally rather than imported, matching the existing part-list.tsx/thread-message.tsx
|
|
28
|
+
// precedent of each Mantine-facing renderer file owning its own copy of these style objects.
|
|
29
|
+
const MICRO_LABEL_STYLE = {
|
|
30
|
+
fontFamily: 'var(--basalt-font-mono)',
|
|
31
|
+
fontSize: VX.text.micro,
|
|
32
|
+
fontWeight: 500,
|
|
33
|
+
letterSpacing: '0.06em',
|
|
34
|
+
textTransform: 'uppercase',
|
|
35
|
+
color: VX.faint,
|
|
36
|
+
} as const
|
|
37
|
+
const RAIL_STYLE = {
|
|
38
|
+
borderLeft: `2px solid ${VX.divider}`,
|
|
39
|
+
paddingLeft: 'var(--vx-space-agent-rail-inset-x)',
|
|
40
|
+
} as const
|
|
41
|
+
const CODE_BLOCK_STYLE = {
|
|
42
|
+
margin: 0,
|
|
43
|
+
marginTop: 'var(--vx-space-agent-part-gap-top)',
|
|
44
|
+
fontFamily: 'var(--basalt-font-mono)',
|
|
45
|
+
backgroundColor: alpha(VX.ink, 0.06),
|
|
46
|
+
borderRadius: 'var(--vx-radius-card)',
|
|
47
|
+
padding: 'var(--vx-space-agent-code-inset)',
|
|
48
|
+
whiteSpace: 'pre-wrap',
|
|
49
|
+
overflowWrap: 'anywhere',
|
|
50
|
+
} as const
|
|
51
|
+
|
|
52
|
+
/** Per-state (label, dot color) — the exhaustive switch that drives the collapsed row. */
|
|
53
|
+
function stateMeta(part: ToolCallPart): { readonly label: string; readonly dotToken: string } {
|
|
54
|
+
switch (part.state) {
|
|
55
|
+
case 'input-streaming':
|
|
56
|
+
return { label: 'streaming', dotToken: VX.status.neutral }
|
|
57
|
+
case 'input-available':
|
|
58
|
+
// No 'running' state on the wire — 'input-available' IS the running state (input complete,
|
|
59
|
+
// no output yet). This label is a UI-only gloss on that wire state, not a new state.
|
|
60
|
+
return { label: 'running', dotToken: VX.status.neutral }
|
|
61
|
+
case 'approval-requested':
|
|
62
|
+
return { label: 'needs approval', dotToken: VX.status.warn }
|
|
63
|
+
case 'approval-responded':
|
|
64
|
+
return part.approval.approved
|
|
65
|
+
? { label: 'approved', dotToken: VX.status.neutral }
|
|
66
|
+
: { label: 'declined', dotToken: VX.status.bad }
|
|
67
|
+
case 'output-available':
|
|
68
|
+
// preliminary vs final must be visually distinguishable — the label carries that here; the
|
|
69
|
+
// expanded output body below is identical either way.
|
|
70
|
+
return part.preliminary === true
|
|
71
|
+
? { label: 'preliminary', dotToken: VX.status.warn }
|
|
72
|
+
: { label: 'done', dotToken: VX.status.good }
|
|
73
|
+
case 'output-error':
|
|
74
|
+
return { label: 'error', dotToken: VX.status.bad }
|
|
75
|
+
case 'output-denied':
|
|
76
|
+
return { label: 'denied', dotToken: VX.status.bad }
|
|
77
|
+
default: {
|
|
78
|
+
// `part` is `never` here ONLY at compile time — this switch is genuinely exhaustive today,
|
|
79
|
+
// so adding an eighth ToolCallPart state without a case above makes this assignment a tsc
|
|
80
|
+
// error, the same gate `assertNever` provides. But `part` at RUNTIME can be a value this
|
|
81
|
+
// build never declared: `createThreadsStore` persists to localStorage with no shape
|
|
82
|
+
// validation, and `edenTransport` trusts the wire with zero validation, so a 1.10.0-shaped
|
|
83
|
+
// flat tool part (`state` undefined) or a future/foreign `state` string both reach here.
|
|
84
|
+
// That must never THROW during render — mirrors ThreadTranscript's fallbackRenderer
|
|
85
|
+
// doctrine (unknown data renders inert, it doesn't blank the transcript) — so, unlike a
|
|
86
|
+
// genuinely-exhaustive switch elsewhere, this default returns a neutral descriptor instead
|
|
87
|
+
// of calling assertNever.
|
|
88
|
+
const exhaustive: never = part
|
|
89
|
+
void exhaustive
|
|
90
|
+
return { label: 'unknown', dotToken: VX.status.neutral }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatDuration(ms: number): string {
|
|
96
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type ToolChipProps = {
|
|
100
|
+
readonly part: ToolCallPart
|
|
101
|
+
readonly defaultExpanded?: boolean
|
|
102
|
+
/** Rendered only in 'approval-requested'. Omit both to render a read-only chip. */
|
|
103
|
+
readonly onApprove?: (approvalId: string) => void
|
|
104
|
+
readonly onDeny?: (approvalId: string, reason?: string) => void
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A collapsed-by-default row for a single tool-call part. All seven wire states render something
|
|
109
|
+
* sane; the switch above still gates an eighth (compile-time-unhandled) state as a tsc error via a
|
|
110
|
+
* `const _: never = part` assignment, but unlike a truly-closed switch, its `default` branch never
|
|
111
|
+
* throws at RUNTIME — a value from persisted/wire data that reaches here with a `state` this build
|
|
112
|
+
* doesn't recognize renders an inert 'unknown' chip instead of blanking the transcript.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* <ToolChip part={part} />
|
|
116
|
+
*/
|
|
117
|
+
export function ToolChip({
|
|
118
|
+
part,
|
|
119
|
+
defaultExpanded = false,
|
|
120
|
+
onApprove,
|
|
121
|
+
onDeny,
|
|
122
|
+
}: ToolChipProps): JSX.Element {
|
|
123
|
+
const [expanded, { toggle }] = useDisclosure(defaultExpanded)
|
|
124
|
+
const { label, dotToken } = stateMeta(part)
|
|
125
|
+
const settled = isToolCallSettled(part)
|
|
126
|
+
|
|
127
|
+
const input = 'input' in part ? part.input : undefined
|
|
128
|
+
const rawInput = part.state === 'output-error' ? part.rawInput : undefined
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Box style={RAIL_STYLE}>
|
|
132
|
+
<Stack gap={4}>
|
|
133
|
+
<UnstyledButton onClick={toggle}>
|
|
134
|
+
<Group gap={6} align="center" wrap="nowrap">
|
|
135
|
+
<Box
|
|
136
|
+
aria-hidden
|
|
137
|
+
style={{
|
|
138
|
+
width: 6,
|
|
139
|
+
height: 6,
|
|
140
|
+
flexShrink: 0,
|
|
141
|
+
borderRadius: '50%', // theme-allow: a 6px state dot is a circle, not a surface corner — no radius token expresses it
|
|
142
|
+
backgroundColor: dotToken,
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
<Text style={MICRO_LABEL_STYLE}>{part.toolName}</Text>
|
|
146
|
+
<Text size="xs" c="dimmed">
|
|
147
|
+
{label}
|
|
148
|
+
</Text>
|
|
149
|
+
{part.durationMs !== undefined && (
|
|
150
|
+
<Text
|
|
151
|
+
style={{
|
|
152
|
+
fontFamily: 'var(--basalt-font-mono)',
|
|
153
|
+
fontSize: VX.text.micro,
|
|
154
|
+
color: VX.faint,
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{formatDuration(part.durationMs)}
|
|
158
|
+
</Text>
|
|
159
|
+
)}
|
|
160
|
+
{!settled && (
|
|
161
|
+
<Text size="xs" c="dimmed">
|
|
162
|
+
…
|
|
163
|
+
</Text>
|
|
164
|
+
)}
|
|
165
|
+
</Group>
|
|
166
|
+
</UnstyledButton>
|
|
167
|
+
<Collapse expanded={expanded}>
|
|
168
|
+
<Stack gap={6}>
|
|
169
|
+
{input !== undefined && (
|
|
170
|
+
<Text component="pre" size="xs" style={CODE_BLOCK_STYLE}>
|
|
171
|
+
{JSON.stringify(input, null, 2)}
|
|
172
|
+
</Text>
|
|
173
|
+
)}
|
|
174
|
+
{rawInput !== undefined && (
|
|
175
|
+
<Text component="pre" size="xs" style={CODE_BLOCK_STYLE}>
|
|
176
|
+
{JSON.stringify(rawInput, null, 2)}
|
|
177
|
+
</Text>
|
|
178
|
+
)}
|
|
179
|
+
{part.state === 'output-available' && (
|
|
180
|
+
<Text component="pre" size="xs" style={CODE_BLOCK_STYLE}>
|
|
181
|
+
{JSON.stringify(part.output, null, 2)}
|
|
182
|
+
</Text>
|
|
183
|
+
)}
|
|
184
|
+
{part.state === 'output-error' && (
|
|
185
|
+
<Text size="xs" style={{ color: VX.status.bad }}>
|
|
186
|
+
{part.errorText}
|
|
187
|
+
</Text>
|
|
188
|
+
)}
|
|
189
|
+
{part.state === 'output-denied' && part.approval.reason !== undefined && (
|
|
190
|
+
<Text size="xs" c="dimmed">
|
|
191
|
+
{part.approval.reason}
|
|
192
|
+
</Text>
|
|
193
|
+
)}
|
|
194
|
+
{part.state === 'approval-requested' &&
|
|
195
|
+
(onApprove !== undefined || onDeny !== undefined) && (
|
|
196
|
+
<Group gap={6}>
|
|
197
|
+
{onApprove !== undefined && (
|
|
198
|
+
<Button size="compact-sm" onClick={() => onApprove(part.approval.id)}>
|
|
199
|
+
Approve
|
|
200
|
+
</Button>
|
|
201
|
+
)}
|
|
202
|
+
{onDeny !== undefined && (
|
|
203
|
+
<Button
|
|
204
|
+
size="compact-sm"
|
|
205
|
+
variant="light"
|
|
206
|
+
color="red"
|
|
207
|
+
onClick={() => onDeny(part.approval.id)}
|
|
208
|
+
>
|
|
209
|
+
Deny
|
|
210
|
+
</Button>
|
|
211
|
+
)}
|
|
212
|
+
</Group>
|
|
213
|
+
)}
|
|
214
|
+
</Stack>
|
|
215
|
+
</Collapse>
|
|
216
|
+
</Stack>
|
|
217
|
+
</Box>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
@@ -209,11 +209,15 @@ describe('mantine-shade-index', () => {
|
|
|
209
209
|
})
|
|
210
210
|
|
|
211
211
|
// The grace-minor doctrine (package CLAUDE.md): a kind that rejects previously-passing code
|
|
212
|
-
// lands as `warn` for one minor
|
|
213
|
-
//
|
|
214
|
-
|
|
212
|
+
// lands as `warn` for one minor, then its GRACE_PERIOD_KINDS entry is deleted and it becomes an
|
|
213
|
+
// error. This one ran its grace across FOUR minors — introduced 1.7.0, then deferred by 1.8.0
|
|
214
|
+
// (shipped the same day as 1.7.0), 1.9.0 (carried the chart-layer batch the same consumer was
|
|
215
|
+
// waiting on) and 1.10.0 (shipped without the promotion at all). Promoted in 1.11.0, verified
|
|
216
|
+
// against the only consumer first: argo's `check-theme` reports zero violations of any kind, so
|
|
217
|
+
// the promotion breaks nothing that was passing.
|
|
218
|
+
it('is an error now that its grace period has ended', () => {
|
|
215
219
|
const f = find(`<Text c="yellow.7" />`)
|
|
216
|
-
expect(f.find((x) => x.kind === 'mantine-shade-index')?.severity).toBe('
|
|
220
|
+
expect(f.find((x) => x.kind === 'mantine-shade-index')?.severity).toBe('error')
|
|
217
221
|
})
|
|
218
222
|
})
|
|
219
223
|
|
package/src/guard/index.ts
CHANGED
|
@@ -296,9 +296,7 @@ export const DEFAULT_GUARD_CONFIG: GuardConfig = {
|
|
|
296
296
|
* 'raw-font-family': 'introduced 1.4.0 — promote to error in 1.5.0',
|
|
297
297
|
* }
|
|
298
298
|
*/
|
|
299
|
-
const GRACE_PERIOD_KINDS: Partial<Record<GuardKind, string>> = {
|
|
300
|
-
'mantine-shade-index': 'introduced 1.7.0 — promote to error in 1.10.0',
|
|
301
|
-
}
|
|
299
|
+
const GRACE_PERIOD_KINDS: Partial<Record<GuardKind, string>> = {}
|
|
302
300
|
|
|
303
301
|
/** A kind's effective severity: consumer override first, then the grace table, then `error`. */
|
|
304
302
|
function severityOf(kind: GuardKind, cfg: GuardConfig): GuardSeverity {
|
package/src/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ export {
|
|
|
118
118
|
Composer,
|
|
119
119
|
threadPartRenderers,
|
|
120
120
|
ThreadTranscript,
|
|
121
|
+
ToolChip,
|
|
121
122
|
} from './agent-chat'
|
|
122
123
|
export type {
|
|
123
124
|
ThreadWorkspaceProps,
|
|
@@ -126,6 +127,7 @@ export type {
|
|
|
126
127
|
ThreadDetailPanelProps,
|
|
127
128
|
ComposerProps,
|
|
128
129
|
ThreadTranscriptProps,
|
|
130
|
+
ToolChipProps,
|
|
129
131
|
} from './agent-chat'
|
|
130
132
|
export { createThreadsStore, heuristicOutcome, useAgentThreadRuns } from './agent'
|
|
131
133
|
export type {
|
package/src/surfaces.ts
CHANGED
|
@@ -362,7 +362,7 @@ export const SURFACES = {
|
|
|
362
362
|
skill: ['basalt-app'],
|
|
363
363
|
guardKinds: [],
|
|
364
364
|
description:
|
|
365
|
-
'Headless streaming-chat layer: useAgentStream, aiSdkTransport (recommended default) + edenTransport, PartList, plus the multi-thread createThreadsStore + useAgentThreadRuns + outcome-resolver seam (Mantine-free)',
|
|
365
|
+
'Headless streaming-chat layer: useAgentStream, aiSdkTransport (recommended default) + edenTransport, isResumable/ResumableAgentTransport (stream-resumption seam), PartList, coalesceParts, the ForeignPart/definePartRenderers/narrowAgentPart open part-registry seam, plus the multi-thread createThreadsStore + useAgentThreadRuns + outcome-resolver seam (Mantine-free)',
|
|
366
366
|
optionalPeers: ['ai', 'use-stick-to-bottom'],
|
|
367
367
|
globs: {
|
|
368
368
|
shipped: [],
|
|
@@ -378,7 +378,7 @@ export const SURFACES = {
|
|
|
378
378
|
skill: ['basalt-app'],
|
|
379
379
|
guardKinds: [],
|
|
380
380
|
description:
|
|
381
|
-
'Mantine-styled thread-chat components over basalt-ui/agent: ThreadWorkspace, ThreadFeed, ThreadOutcomeCard, ThreadDetailPanel, Composer, ThreadTranscript, threadPartRenderers (Mantine-coupled). remend and motion are required, not optional — ThreadTranscript imports content/markdown.tsx (remend eagerly), and ThreadFeed/ThreadDetailPanel import motion/react eagerly, so this subpath fails to resolve without both installed even though peerDependenciesMeta marks them optional (npm has no per-subpath optionality).',
|
|
381
|
+
'Mantine-styled thread-chat components over basalt-ui/agent: ThreadWorkspace, ThreadFeed, ThreadOutcomeCard, ThreadDetailPanel, Composer, ThreadTranscript (open part-renderer registry via its renderers/fallbackRenderer props), threadPartRenderers, ToolChip (Mantine-coupled). remend and motion are required, not optional — ThreadTranscript imports content/markdown.tsx (remend eagerly), and ThreadFeed/ThreadDetailPanel import motion/react eagerly, so this subpath fails to resolve without both installed even though peerDependenciesMeta marks them optional (npm has no per-subpath optionality).',
|
|
382
382
|
optionalPeers: [
|
|
383
383
|
'ai',
|
|
384
384
|
'motion',
|