@signal9/era-ui 34.4.1 → 34.6.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/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [34.6.0](https://github.com/sig-nine/era-ui/compare/v34.5.0...v34.6.0) (2026-08-28)
2
+
3
+ ### Features
4
+
5
+ * **docs:** add navi accent ([e874413](https://github.com/sig-nine/era-ui/commit/e874413180e8edf296fcab4a0d435e67a92cff6f))
6
+
7
+ ## [34.5.0](https://github.com/sig-nine/era-ui/compare/v34.4.1...v34.5.0) (2026-08-28)
8
+
9
+ ### Features
10
+
11
+ * **ai:** add compact tool inspector ([1fe91ec](https://github.com/sig-nine/era-ui/commit/1fe91ec0eaaf121ad16bc75de4ebe893e222b988))
12
+
13
+ ### Bug Fixes
14
+
15
+ * **ai:** render inspector input details ([2400f80](https://github.com/sig-nine/era-ui/commit/2400f806ed9aabed901bc2625751096453f168b5))
16
+ * **notes:** focus editor from trailing surface ([d4de399](https://github.com/sig-nine/era-ui/commit/d4de399d03b1d85db55b99aed1d5ffe7e0d5fcea))
17
+
1
18
  ## [34.4.1](https://github.com/sig-nine/era-ui/compare/v34.4.0...v34.4.1) (2026-08-26)
2
19
 
3
20
  ### Bug Fixes
@@ -0,0 +1,89 @@
1
+ <script lang="ts" module>
2
+ export type ConversationCapacityPhase =
3
+ 'quiet' | 'preparing' | 'compacting' | 'complete' | 'failed';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { HTMLAttributes } from 'svelte/elements';
8
+ import LoaderCircle from '@lucide/svelte/icons/loader-circle';
9
+ import CircleCheck from '@lucide/svelte/icons/circle-check';
10
+ import CircleAlert from '@lucide/svelte/icons/circle-alert';
11
+ import * as Collapsible from '../../ui/collapsible/index.js';
12
+ import { cn } from '../../utils/index.js';
13
+
14
+ let {
15
+ phase = 'quiet',
16
+ used,
17
+ total,
18
+ retained,
19
+ condensedToolResults,
20
+ setAsideTurns,
21
+ open = $bindable(false),
22
+ threshold = 0.7,
23
+ class: className
24
+ }: HTMLAttributes<HTMLDivElement> & {
25
+ phase?: ConversationCapacityPhase;
26
+ used?: number;
27
+ total?: number;
28
+ retained?: number;
29
+ condensedToolResults?: number;
30
+ setAsideTurns?: number;
31
+ open?: boolean;
32
+ threshold?: number;
33
+ } = $props();
34
+ const ratio = $derived(total && used !== undefined ? Math.max(0, Math.min(1, used / total)) : 0);
35
+ const hidden = $derived(phase === 'quiet' && ratio < threshold);
36
+ const label = $derived(
37
+ phase === 'preparing' || phase === 'compacting'
38
+ ? 'Making room for this conversation…'
39
+ : phase === 'complete'
40
+ ? 'Conversation space refreshed.'
41
+ : phase === 'failed'
42
+ ? 'Conversation space could not be refreshed. You can continue safely.'
43
+ : 'Conversation capacity'
44
+ );
45
+ </script>
46
+
47
+ {#if !hidden}
48
+ <Collapsible.Root bind:open class={cn('min-w-0', className)}>
49
+ <Collapsible.Trigger
50
+ class="flex h-control w-full items-center gap-gutter px-inset-control text-left text-body"
51
+ >
52
+ {#if phase === 'preparing' || phase === 'compacting'}<LoaderCircle
53
+ class="size-icon shrink-0 animate-spin text-primary"
54
+ aria-hidden="true"
55
+ />
56
+ {:else if phase === 'complete'}<CircleCheck
57
+ class="size-icon shrink-0 text-success"
58
+ aria-hidden="true"
59
+ />
60
+ {:else if phase === 'failed'}<CircleAlert
61
+ class="size-icon shrink-0 text-warning"
62
+ aria-hidden="true"
63
+ />{/if}
64
+ <span class="min-w-0 flex-1 truncate">{label}</span>
65
+ {#if total && used !== undefined}<span class="shrink-0 text-muted tabular-nums"
66
+ >{Math.round(ratio * 100)}%</span
67
+ >{/if}
68
+ </Collapsible.Trigger>
69
+ {#if total && used !== undefined}<div
70
+ class="mx-inset-control h-1 overflow-hidden rounded-full bg-well"
71
+ >
72
+ <div
73
+ class="h-full bg-primary duration-base motion-safe:transition-[width]"
74
+ style={`width: ${ratio * 100}%`}
75
+ ></div>
76
+ </div>{/if}
77
+ <Collapsible.Content class="overflow-hidden"
78
+ ><div
79
+ class="flex flex-wrap gap-x-panel gap-y-gutter px-inset-control py-gutter text-body text-muted"
80
+ >
81
+ {#if retained !== undefined}<span>{retained} retained</span
82
+ >{/if}{#if condensedToolResults !== undefined}<span
83
+ >{condensedToolResults} older tool results condensed</span
84
+ >{/if}{#if setAsideTurns !== undefined}<span>{setAsideTurns} earlier turns set aside</span
85
+ >{/if}
86
+ </div></Collapsible.Content
87
+ >
88
+ </Collapsible.Root>
89
+ {/if}
@@ -0,0 +1,15 @@
1
+ export type ConversationCapacityPhase = 'quiet' | 'preparing' | 'compacting' | 'complete' | 'failed';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ phase?: ConversationCapacityPhase;
5
+ used?: number;
6
+ total?: number;
7
+ retained?: number;
8
+ condensedToolResults?: number;
9
+ setAsideTurns?: number;
10
+ open?: boolean;
11
+ threshold?: number;
12
+ };
13
+ declare const ConversationCapacity: import("svelte").Component<$$ComponentProps, {}, "open">;
14
+ type ConversationCapacity = ReturnType<typeof ConversationCapacity>;
15
+ export default ConversationCapacity;
@@ -1 +1,2 @@
1
1
  export { default as Root } from './context.svelte';
2
+ export { default as Capacity, type ConversationCapacityPhase } from './conversation-capacity.svelte';
@@ -1 +1,2 @@
1
1
  export { default as Root } from './context.svelte';
2
+ export { default as Capacity } from './conversation-capacity.svelte';
@@ -22,6 +22,7 @@ export * as Swarm from './swarm/index.js';
22
22
  export * as CodeMode from './code-mode/index.js';
23
23
  export { Response, repairStreamingMarkdown } from './response/index.js';
24
24
  export { default as ContextGauge } from './context/context.svelte';
25
+ export * as Context from './context/index.js';
25
26
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
26
27
  export type { ModelGroup, ModelOption } from './model-selector/index.js';
27
28
  export { default as OpenInChat, providers as openInProviders } from './open-in-chat/open-in-chat.svelte';
package/dist/ai/index.js CHANGED
@@ -27,6 +27,7 @@ export * as Swarm from './swarm/index.js';
27
27
  export * as CodeMode from './code-mode/index.js';
28
28
  export { Response, repairStreamingMarkdown } from './response/index.js';
29
29
  export { default as ContextGauge } from './context/context.svelte';
30
+ export * as Context from './context/index.js';
30
31
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
31
32
  export { default as OpenInChat, providers as openInProviders } from './open-in-chat/open-in-chat.svelte';
32
33
  export { default as WebPreview } from './web-preview/web-preview.svelte';
@@ -1,2 +1,9 @@
1
+ import * as Dialog from '../../ui/dialog/index.js';
2
+ import * as Command from '../../ui/command/index.js';
1
3
  export { default as Root } from './model-selector.svelte';
4
+ /** Composable dialog parts for applications that need a custom picker trigger or layout. */
5
+ export declare const Trigger: typeof Dialog.Trigger;
6
+ export declare const Content: typeof Dialog.Content;
7
+ export declare const Group: typeof Command.Group;
8
+ export declare const Item: typeof Command.Item;
2
9
  export type { ModelGroup, ModelOption } from './model-selector.svelte';
@@ -1 +1,8 @@
1
+ import * as Dialog from '../../ui/dialog/index.js';
2
+ import * as Command from '../../ui/command/index.js';
1
3
  export { default as Root } from './model-selector.svelte';
4
+ /** Composable dialog parts for applications that need a custom picker trigger or layout. */
5
+ export const Trigger = Dialog.Trigger;
6
+ export const Content = Dialog.Content;
7
+ export const Group = Command.Group;
8
+ export const Item = Command.Item;
@@ -24,6 +24,7 @@
24
24
  import * as Dialog from '../../ui/dialog/index.js';
25
25
  import * as Command from '../../ui/command/index.js';
26
26
  import Search from '@lucide/svelte/icons/search';
27
+ import ChevronDown from '@lucide/svelte/icons/chevron-down';
27
28
  import { cn } from '../../utils/index.js';
28
29
 
29
30
  let {
@@ -32,6 +33,8 @@
32
33
  groups,
33
34
  value = $bindable(''),
34
35
  offline = false,
36
+ shortLabel = false,
37
+ showTrigger = true,
35
38
  onSelect,
36
39
  class: className
37
40
  }: {
@@ -43,14 +46,39 @@
43
46
  value?: string;
44
47
  /** Never loads remote logo URLs; local icon snippets continue to render. */
45
48
  offline?: boolean;
49
+ /** Use the provider/name only label when the picker sits in a narrow toolbar. */
50
+ shortLabel?: boolean;
51
+ /** Set false when composing a custom Dialog trigger around this picker. */
52
+ showTrigger?: boolean;
46
53
  onSelect?: (id: string, model: ModelOption) => void;
47
54
  class?: string;
48
55
  } = $props();
49
56
 
50
57
  const visibleGroups = $derived(groups ?? [{ id: 'models', label: 'Models', models }]);
58
+ const selected = $derived(
59
+ visibleGroups.flatMap((group) => group.models).find((model) => model.id === value)
60
+ );
51
61
  </script>
52
62
 
53
63
  <Dialog.Root bind:open>
64
+ {#if showTrigger}
65
+ <Dialog.Trigger
66
+ size="control"
67
+ disabled={offline || !selected}
68
+ aria-label={selected ? `Choose model, current ${selected.name}` : 'Choose model'}
69
+ aria-haspopup="dialog"
70
+ >
71
+ {#if selected?.icon}{@render selected.icon()}{/if}
72
+ <span class="min-w-0 truncate"
73
+ >{shortLabel
74
+ ? (selected?.name ?? 'Model')
75
+ : selected?.provider
76
+ ? `${selected.provider} · ${selected.name}`
77
+ : (selected?.name ?? 'Choose model')}</span
78
+ >
79
+ <ChevronDown class="size-icon shrink-0 text-muted" aria-hidden="true" />
80
+ </Dialog.Trigger>
81
+ {/if}
54
82
  <Dialog.Content class={cn('max-w-md p-gutter', className)}>
55
83
  <Command.Root bare>
56
84
  <Command.Input icon={Search} placeholder="Search models…" autofocus />
@@ -24,6 +24,10 @@ type $$ComponentProps = {
24
24
  value?: string;
25
25
  /** Never loads remote logo URLs; local icon snippets continue to render. */
26
26
  offline?: boolean;
27
+ /** Use the provider/name only label when the picker sits in a narrow toolbar. */
28
+ shortLabel?: boolean;
29
+ /** Set false when composing a custom Dialog trigger around this picker. */
30
+ showTrigger?: boolean;
27
31
  onSelect?: (id: string, model: ModelOption) => void;
28
32
  class?: string;
29
33
  };
@@ -1,7 +1,20 @@
1
1
  <script lang="ts" module>
2
2
  /** Aggregate run lifecycle. `running` pulses; the three active states
3
3
  * (queued/running/awaiting) surface the Cancel affordance. */
4
- export type RunState = 'idle' | 'queued' | 'running' | 'awaiting' | 'done' | 'error';
4
+ export type RunState =
5
+ | 'idle'
6
+ | 'queued'
7
+ | 'running'
8
+ | 'thinking'
9
+ | 'tool'
10
+ | 'browser'
11
+ | 'compacting'
12
+ | 'speaking'
13
+ | 'cancelling'
14
+ | 'awaiting'
15
+ | 'done'
16
+ | 'cancelled'
17
+ | 'error';
5
18
 
6
19
  /** Tone for the latest-log line — reuses the era semantic palette. */
7
20
  export type LogTone = 'default' | 'muted' | 'success' | 'warning' | 'destructive';
@@ -25,6 +38,30 @@
25
38
  idle: { label: 'idle', dot: 'bg-muted', text: 'text-muted', active: false, pulse: false },
26
39
  queued: { label: 'queued', dot: 'bg-muted', text: 'text-muted', active: true, pulse: false },
27
40
  running: { label: 'running', dot: 'bg-primary', text: 'text-fg', active: true, pulse: true },
41
+ thinking: { label: 'thinking', dot: 'bg-primary', text: 'text-fg', active: true, pulse: true },
42
+ tool: { label: 'using a tool', dot: 'bg-primary', text: 'text-fg', active: true, pulse: true },
43
+ browser: {
44
+ label: 'working in browser',
45
+ dot: 'bg-primary',
46
+ text: 'text-fg',
47
+ active: true,
48
+ pulse: true
49
+ },
50
+ compacting: {
51
+ label: 'making room',
52
+ dot: 'bg-primary',
53
+ text: 'text-fg',
54
+ active: true,
55
+ pulse: true
56
+ },
57
+ speaking: { label: 'speaking', dot: 'bg-primary', text: 'text-fg', active: true, pulse: true },
58
+ cancelling: {
59
+ label: 'stopping',
60
+ dot: 'bg-muted',
61
+ text: 'text-muted',
62
+ active: true,
63
+ pulse: true
64
+ },
28
65
  awaiting: {
29
66
  label: 'awaiting',
30
67
  dot: 'bg-warning',
@@ -33,6 +70,13 @@
33
70
  pulse: false
34
71
  },
35
72
  done: { label: 'done', dot: 'bg-success', text: 'text-success', active: false, pulse: false },
73
+ cancelled: {
74
+ label: 'stopped',
75
+ dot: 'bg-muted',
76
+ text: 'text-muted',
77
+ active: false,
78
+ pulse: false
79
+ },
36
80
  error: {
37
81
  label: 'error',
38
82
  dot: 'bg-destructive',
@@ -1,6 +1,6 @@
1
1
  /** Aggregate run lifecycle. `running` pulses; the three active states
2
2
  * (queued/running/awaiting) surface the Cancel affordance. */
3
- export type RunState = 'idle' | 'queued' | 'running' | 'awaiting' | 'done' | 'error';
3
+ export type RunState = 'idle' | 'queued' | 'running' | 'thinking' | 'tool' | 'browser' | 'compacting' | 'speaking' | 'cancelling' | 'awaiting' | 'done' | 'cancelled' | 'error';
4
4
  /** Tone for the latest-log line — reuses the era semantic palette. */
5
5
  export type LogTone = 'default' | 'muted' | 'success' | 'warning' | 'destructive';
6
6
  export interface RunTools {
@@ -5,4 +5,5 @@ export { default as Output } from './tool-output.svelte';
5
5
  export { default as Chip } from './tool-chip.svelte';
6
6
  export { default as Chips } from './tool-chips.svelte';
7
7
  export { default as Exec } from './tool-exec.svelte';
8
+ export { default as Inspector, toolValue, type ToolMetric, type ToolPresentation } from './tool-inspector.svelte';
8
9
  export { Content, Section } from '../../ui/step/index.js';
@@ -5,4 +5,5 @@ export { default as Output } from './tool-output.svelte';
5
5
  export { default as Chip } from './tool-chip.svelte';
6
6
  export { default as Chips } from './tool-chips.svelte';
7
7
  export { default as Exec } from './tool-exec.svelte';
8
+ export { default as Inspector, toolValue } from './tool-inspector.svelte';
8
9
  export { Content, Section } from '../../ui/step/index.js';
@@ -5,16 +5,23 @@
5
5
 
6
6
  let {
7
7
  type,
8
+ icon,
8
9
  children
9
10
  }: {
10
11
  /** The tool name, e.g. `search_flights`. */
11
12
  type: string;
13
+ /** Optional local category icon; avoids any remote asset dependency. */
14
+ icon?: Snippet;
12
15
  children?: Snippet;
13
16
  } = $props();
14
17
  </script>
15
18
 
16
19
  <Step.Summary>
17
- <Wrench class="size-icon shrink-0" aria-hidden="true" />
20
+ {#if icon}
21
+ {@render icon()}
22
+ {:else}
23
+ <Wrench class="size-icon shrink-0" aria-hidden="true" />
24
+ {/if}
18
25
  <span class="truncate text-fg">{type}</span>
19
26
  {@render children?.()}
20
27
  </Step.Summary>
@@ -2,6 +2,8 @@ import type { Snippet } from 'svelte';
2
2
  type $$ComponentProps = {
3
3
  /** The tool name, e.g. `search_flights`. */
4
4
  type: string;
5
+ /** Optional local category icon; avoids any remote asset dependency. */
6
+ icon?: Snippet;
5
7
  children?: Snippet;
6
8
  };
7
9
  declare const ToolHeader: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -0,0 +1,236 @@
1
+ <script lang="ts" module>
2
+ export interface ToolMetric {
3
+ label: string;
4
+ value: string | number;
5
+ }
6
+
7
+ /** Provider-neutral semantic presentation. Applications own all fetching and actions. */
8
+ export type ToolPresentation =
9
+ | { kind: 'generic' }
10
+ | { kind: 'code'; execution: import('../code-mode/types.js').CodeModeExecution }
11
+ | { kind: 'shell'; command?: string; stdout?: string; stderr?: string; exitCode?: number }
12
+ | { kind: 'table'; columns: string[]; rows: unknown[][]; maxRows?: number }
13
+ | { kind: 'chart'; description?: string }
14
+ | { kind: 'app'; label?: string; description?: string }
15
+ | {
16
+ kind: 'artifact';
17
+ name: string;
18
+ mimeType: string;
19
+ size?: string;
20
+ available?: boolean;
21
+ };
22
+
23
+ export function toolValue(
24
+ value: unknown,
25
+ limit = 12_000
26
+ ): {
27
+ value: unknown;
28
+ raw: string;
29
+ truncated: boolean;
30
+ encodedError?: string;
31
+ } {
32
+ let resolved = value;
33
+ if (typeof value === 'string') {
34
+ try {
35
+ resolved = JSON.parse(value);
36
+ } catch {
37
+ // A plain string remains a useful, valid raw result.
38
+ }
39
+ }
40
+ const encoded =
41
+ resolved && typeof resolved === 'object' && !Array.isArray(resolved)
42
+ ? (resolved as Record<string, unknown>)
43
+ : undefined;
44
+ const encodedError =
45
+ encoded && (encoded.success === false || encoded.isError === true)
46
+ ? String(encoded.error ?? encoded.message ?? 'Tool reported an error')
47
+ : undefined;
48
+ const raw =
49
+ typeof resolved === 'string' ? resolved : (JSON.stringify(resolved, null, 2) ?? 'null');
50
+ return {
51
+ value: resolved,
52
+ raw: raw.length > limit ? `${raw.slice(0, limit)}\n… truncated` : raw,
53
+ truncated: raw.length > limit,
54
+ encodedError
55
+ };
56
+ }
57
+ </script>
58
+
59
+ <script lang="ts">
60
+ import type { Snippet } from 'svelte';
61
+ import * as Tabs from '../../ui/tabs/index.js';
62
+ import * as Table from '../../ui/table/index.js';
63
+ import { CodeBlock } from '../../ui/code-block/index.js';
64
+ import { CopyButton } from '../../ui/copy-button/index.js';
65
+ import { cn } from '../../utils/index.js';
66
+ import * as CodeMode from '../code-mode/index.js';
67
+ import * as StructuredValue from '../structured-value/index.js';
68
+ import Root from './tool.svelte';
69
+ import Header from './tool-header.svelte';
70
+
71
+ let {
72
+ name,
73
+ status = 'pending',
74
+ input,
75
+ output,
76
+ error,
77
+ durationMs,
78
+ metrics = [],
79
+ presentation = { kind: 'generic' },
80
+ icon,
81
+ chart,
82
+ actions,
83
+ open = $bindable(false),
84
+ class: className
85
+ }: {
86
+ name: string;
87
+ status?: import('./tool.svelte').ToolStatus;
88
+ input?: unknown;
89
+ output?: unknown;
90
+ error?: string;
91
+ durationMs?: number;
92
+ metrics?: ToolMetric[];
93
+ presentation?: ToolPresentation;
94
+ icon?: Snippet;
95
+ /** A trusted application chart; the raw result always remains available. */
96
+ chart?: Snippet<[unknown]>;
97
+ actions?: Snippet;
98
+ open?: boolean;
99
+ class?: string;
100
+ } = $props();
101
+
102
+ let rawView = $state<'structured' | 'raw'>('structured');
103
+ let search = $state('');
104
+ const resultPayload = $derived(toolValue(output));
105
+ const reportedError = $derived(error ?? resultPayload.encodedError);
106
+ const visibleRaw = $derived(
107
+ search
108
+ ? resultPayload.raw
109
+ .split('\n')
110
+ .filter((line) => line.toLowerCase().includes(search.toLowerCase()))
111
+ .join('\n')
112
+ : resultPayload.raw
113
+ );
114
+ const tableRows = $derived(
115
+ presentation.kind === 'table' ? presentation.rows.slice(0, presentation.maxRows ?? 100) : []
116
+ );
117
+ const active = $derived(
118
+ status === 'pending' || status === 'running' || status === 'approval-required'
119
+ );
120
+ </script>
121
+
122
+ {#snippet inputDetail()}
123
+ <StructuredValue.Root value={toolValue(input).value} />
124
+ {/snippet}
125
+
126
+ <Root
127
+ bind:open
128
+ {status}
129
+ input={input === undefined ? undefined : inputDetail}
130
+ class={cn('min-w-0', className)}
131
+ >
132
+ {#snippet header()}
133
+ <Header type={name} {icon}>
134
+ {#if active}<span
135
+ class="ml-auto size-1.5 shrink-0 rounded-full bg-primary motion-safe:animate-pulse"
136
+ aria-label="active"
137
+ ></span>{/if}
138
+ {#if durationMs !== undefined}<span class="ml-auto shrink-0 text-muted tabular-nums"
139
+ >{durationMs}ms</span
140
+ >{/if}
141
+ {#each metrics.slice(0, 2) as metric (metric.label)}
142
+ <span class="shrink-0 text-muted tabular-nums">{metric.value} {metric.label}</span>
143
+ {/each}
144
+ </Header>
145
+ {/snippet}
146
+ {#snippet result()}
147
+ {#if presentation.kind === 'code'}
148
+ <CodeMode.Root {...presentation.execution} />
149
+ {:else if presentation.kind === 'shell'}
150
+ <div class="flex flex-col gap-panel">
151
+ {#if presentation.command}<CodeBlock code={`$ ${presentation.command}`} />{/if}
152
+ {#if presentation.stdout}<CodeBlock code={presentation.stdout} />{/if}
153
+ {#if presentation.stderr}<p class="text-body whitespace-pre-wrap text-destructive">
154
+ {presentation.stderr}
155
+ </p>{/if}
156
+ {#if presentation.exitCode !== undefined}<p class="text-body text-muted">
157
+ exit code {presentation.exitCode}
158
+ </p>{/if}
159
+ </div>
160
+ {:else if presentation.kind === 'table'}
161
+ <div class="flex flex-col gap-gutter">
162
+ <p class="text-body text-muted">
163
+ {presentation.rows.length} rows · {presentation.columns.length} columns
164
+ </p>
165
+ <div class="overflow-auto">
166
+ <Table.Root
167
+ ><Table.Head
168
+ ><Table.Row
169
+ >{#each presentation.columns as column (column)}<Table.HeadCell
170
+ >{column}</Table.HeadCell
171
+ >{/each}</Table.Row
172
+ ></Table.Head
173
+ ><Table.Body
174
+ >{#each tableRows as row, i (i)}<Table.Row
175
+ >{#each row as cell, j (j)}<Table.Cell>{String(cell ?? '')}</Table.Cell
176
+ >{/each}</Table.Row
177
+ >{/each}</Table.Body
178
+ ></Table.Root
179
+ >
180
+ </div>
181
+ {#if tableRows.length < presentation.rows.length}<p class="text-body text-muted">
182
+ Showing the first {tableRows.length} rows.
183
+ </p>{/if}
184
+ </div>
185
+ {:else if presentation.kind === 'chart' && chart}
186
+ {@render chart(resultPayload.value)}
187
+ {:else if presentation.kind === 'app'}
188
+ <p class="text-body text-muted">
189
+ {presentation.description ?? `${presentation.label ?? name} opened a visual destination.`}
190
+ </p>
191
+ {:else if presentation.kind === 'artifact'}
192
+ <div class="flex flex-wrap items-center gap-gutter text-body">
193
+ <strong class="text-fg">{presentation.name}</strong><span class="text-muted"
194
+ >{presentation.mimeType}</span
195
+ >{#if presentation.size}<span class="text-muted">{presentation.size}</span>{/if}<span
196
+ class={presentation.available === false ? 'text-warning' : 'text-success'}
197
+ >{presentation.available === false ? 'unavailable' : 'available'}</span
198
+ >{@render actions?.()}
199
+ </div>
200
+ {:else if output !== undefined}
201
+ <Tabs.Root bind:value={rawView}>
202
+ <Tabs.List
203
+ ><Tabs.Trigger value="structured">structured</Tabs.Trigger><Tabs.Trigger value="raw"
204
+ >raw</Tabs.Trigger
205
+ ></Tabs.List
206
+ >
207
+ <Tabs.Content value="structured" class="pt-panel"
208
+ ><StructuredValue.Root value={resultPayload.value} /></Tabs.Content
209
+ >
210
+ <Tabs.Content value="raw" class="flex flex-col gap-gutter pt-panel"
211
+ ><div class="flex gap-gutter">
212
+ <input
213
+ aria-label="Search raw output"
214
+ bind:value={search}
215
+ placeholder="Search"
216
+ class="min-w-0 flex-1 bg-well px-inset-control text-body outline-none"
217
+ /><CopyButton text={resultPayload.raw} label="Copy raw output" />
218
+ </div>
219
+ <CodeBlock
220
+ code={visibleRaw || 'No matching lines'}
221
+ class="whitespace-pre-wrap"
222
+ /></Tabs.Content
223
+ >
224
+ </Tabs.Root>
225
+ {/if}
226
+ {#if resultPayload.truncated}<p class="mt-gutter text-body text-muted">
227
+ Raw output is bounded to keep this transcript responsive.
228
+ </p>{/if}
229
+ {#if reportedError}<p
230
+ role="alert"
231
+ class="mt-gutter text-body whitespace-pre-wrap text-destructive"
232
+ >
233
+ {reportedError}
234
+ </p>{/if}
235
+ {/snippet}
236
+ </Root>
@@ -0,0 +1,61 @@
1
+ export interface ToolMetric {
2
+ label: string;
3
+ value: string | number;
4
+ }
5
+ /** Provider-neutral semantic presentation. Applications own all fetching and actions. */
6
+ export type ToolPresentation = {
7
+ kind: 'generic';
8
+ } | {
9
+ kind: 'code';
10
+ execution: import('../code-mode/types.js').CodeModeExecution;
11
+ } | {
12
+ kind: 'shell';
13
+ command?: string;
14
+ stdout?: string;
15
+ stderr?: string;
16
+ exitCode?: number;
17
+ } | {
18
+ kind: 'table';
19
+ columns: string[];
20
+ rows: unknown[][];
21
+ maxRows?: number;
22
+ } | {
23
+ kind: 'chart';
24
+ description?: string;
25
+ } | {
26
+ kind: 'app';
27
+ label?: string;
28
+ description?: string;
29
+ } | {
30
+ kind: 'artifact';
31
+ name: string;
32
+ mimeType: string;
33
+ size?: string;
34
+ available?: boolean;
35
+ };
36
+ export declare function toolValue(value: unknown, limit?: number): {
37
+ value: unknown;
38
+ raw: string;
39
+ truncated: boolean;
40
+ encodedError?: string;
41
+ };
42
+ import type { Snippet } from 'svelte';
43
+ type $$ComponentProps = {
44
+ name: string;
45
+ status?: import('./tool.svelte').ToolStatus;
46
+ input?: unknown;
47
+ output?: unknown;
48
+ error?: string;
49
+ durationMs?: number;
50
+ metrics?: ToolMetric[];
51
+ presentation?: ToolPresentation;
52
+ icon?: Snippet;
53
+ /** A trusted application chart; the raw result always remains available. */
54
+ chart?: Snippet<[unknown]>;
55
+ actions?: Snippet;
56
+ open?: boolean;
57
+ class?: string;
58
+ };
59
+ declare const ToolInspector: import("svelte").Component<$$ComponentProps, {}, "open">;
60
+ type ToolInspector = ReturnType<typeof ToolInspector>;
61
+ export default ToolInspector;