@signal9/era-ui 2.8.2 → 2.9.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.
Files changed (56) hide show
  1. package/dist/ai/confirmation/approval.svelte.d.ts +42 -0
  2. package/dist/ai/confirmation/approval.svelte.js +122 -0
  3. package/dist/ai/confirmation/confirmation-args.svelte +67 -0
  4. package/dist/ai/confirmation/confirmation-args.svelte.d.ts +8 -0
  5. package/dist/ai/confirmation/index.d.ts +2 -0
  6. package/dist/ai/confirmation/index.js +2 -0
  7. package/dist/ai/conversations/conversations-group.svelte +28 -0
  8. package/dist/ai/conversations/conversations-group.svelte.d.ts +10 -0
  9. package/dist/ai/conversations/conversations-item.svelte +208 -0
  10. package/dist/ai/conversations/conversations-item.svelte.d.ts +28 -0
  11. package/dist/ai/conversations/conversations-root.svelte +27 -0
  12. package/dist/ai/conversations/conversations-root.svelte.d.ts +10 -0
  13. package/dist/ai/conversations/conversations.svelte.d.ts +38 -0
  14. package/dist/ai/conversations/conversations.svelte.js +73 -0
  15. package/dist/ai/conversations/index.d.ts +5 -0
  16. package/dist/ai/conversations/index.js +4 -0
  17. package/dist/ai/error-panel/categorize-error.d.ts +32 -0
  18. package/dist/ai/error-panel/categorize-error.js +106 -0
  19. package/dist/ai/error-panel/error-panel.svelte +130 -0
  20. package/dist/ai/error-panel/error-panel.svelte.d.ts +17 -0
  21. package/dist/ai/error-panel/index.d.ts +2 -0
  22. package/dist/ai/error-panel/index.js +2 -0
  23. package/dist/ai/index.d.ts +11 -0
  24. package/dist/ai/index.js +7 -0
  25. package/dist/ai/message/index.d.ts +2 -0
  26. package/dist/ai/message/index.js +1 -0
  27. package/dist/ai/message/message-usage.svelte +126 -0
  28. package/dist/ai/message/message-usage.svelte.d.ts +22 -0
  29. package/dist/ai/project.d.ts +64 -0
  30. package/dist/ai/project.js +70 -0
  31. package/dist/ai/run-status/index.d.ts +2 -0
  32. package/dist/ai/run-status/index.js +1 -0
  33. package/dist/ai/run-status/run-status.svelte +132 -0
  34. package/dist/ai/run-status/run-status.svelte.d.ts +26 -0
  35. package/dist/ai/runtime-feed/index.d.ts +2 -0
  36. package/dist/ai/runtime-feed/index.js +2 -0
  37. package/dist/ai/runtime-feed/runtime-feed-item.svelte +64 -0
  38. package/dist/ai/runtime-feed/runtime-feed-item.svelte.d.ts +27 -0
  39. package/dist/ai/runtime-feed/runtime-feed-root.svelte +35 -0
  40. package/dist/ai/runtime-feed/runtime-feed-root.svelte.d.ts +13 -0
  41. package/dist/ai/structured-value/index.d.ts +1 -0
  42. package/dist/ai/structured-value/index.js +1 -0
  43. package/dist/ai/structured-value/structured-value.svelte +130 -0
  44. package/dist/ai/structured-value/structured-value.svelte.d.ts +10 -0
  45. package/dist/ai/tool/index.d.ts +3 -0
  46. package/dist/ai/tool/index.js +3 -0
  47. package/dist/ai/tool/tool-chip.svelte +66 -0
  48. package/dist/ai/tool/tool-chip.svelte.d.ts +17 -0
  49. package/dist/ai/tool/tool-chips.svelte +56 -0
  50. package/dist/ai/tool/tool-chips.svelte.d.ts +13 -0
  51. package/dist/ai/tool/tool-exec.svelte +49 -0
  52. package/dist/ai/tool/tool-exec.svelte.d.ts +11 -0
  53. package/dist/era-ui.css +1 -1
  54. package/dist/ui/badge/badge.svelte +3 -2
  55. package/dist/ui/badge/badge.svelte.d.ts +4 -1
  56. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ export { default as Root } from './conversations-root.svelte';
2
+ export { default as Group } from './conversations-group.svelte';
3
+ export { default as Item } from './conversations-item.svelte';
4
+ export { bucketConversations, ConversationsController } from './conversations.svelte.js';
5
+ export type { ConversationEntry, ConversationGroup, ConversationStatus, TimeBucket } from './conversations.svelte.js';
@@ -0,0 +1,4 @@
1
+ export { default as Root } from './conversations-root.svelte';
2
+ export { default as Group } from './conversations-group.svelte';
3
+ export { default as Item } from './conversations-item.svelte';
4
+ export { bucketConversations, ConversationsController } from './conversations.svelte.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Headless error-categorization engine for the ErrorPanel.
3
+ *
4
+ * Turns a raw LLM/tool error (an `Error`, a JSON error blob, or a plain
5
+ * string) into a user-facing classification: a stable `category`, a short
6
+ * `label`, an actionable `hint`, and a `tone` that drives the panel's icon
7
+ * and color. Ported from term's llm error-utils.
8
+ */
9
+ /** Coarse buckets an LLM/tool error can fall into. */
10
+ export type ErrorCategory = 'unknown_tool' | 'tool_unavailable' | 'tool_failed' | 'invalid_args' | 'auth' | 'no_org' | 'no_api_key' | 'generic';
11
+ /** Panel tone — AI/config faults warn, real failures are destructive. */
12
+ export type ErrorTone = 'warning' | 'destructive';
13
+ /** The categorization of an error. */
14
+ export interface ErrorCategorization {
15
+ category: ErrorCategory;
16
+ /** Tiny uppercase-ready label, e.g. "Tool Unavailable". */
17
+ label: string;
18
+ /** One-line, user-facing guidance. */
19
+ hint: string;
20
+ tone: ErrorTone;
21
+ }
22
+ /** Pull a tool name out of a provider message, e.g. `call tool 'run_command'`. */
23
+ export declare function extractToolName(message: string): string | null;
24
+ /** Reduce any raw error input to its human message string. */
25
+ export declare function toErrorMessage(input: unknown): string;
26
+ /** Build the full categorization for a known category. */
27
+ export declare function describeCategory(category: ErrorCategory): ErrorCategorization;
28
+ /**
29
+ * Categorize a raw error into `{ category, label, hint, tone }`.
30
+ * Accepts an `Error`, a JSON error blob, or a plain message string.
31
+ */
32
+ export declare function categorizeError(input: unknown): ErrorCategorization;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Headless error-categorization engine for the ErrorPanel.
3
+ *
4
+ * Turns a raw LLM/tool error (an `Error`, a JSON error blob, or a plain
5
+ * string) into a user-facing classification: a stable `category`, a short
6
+ * `label`, an actionable `hint`, and a `tone` that drives the panel's icon
7
+ * and color. Ported from term's llm error-utils.
8
+ */
9
+ const HINTS = {
10
+ unknown_tool: "The AI tried to use a tool that doesn't exist. This is an AI error, not yours.",
11
+ tool_unavailable: "You don't have access to this tool. The AI was confused about what's available.",
12
+ tool_failed: 'The tool ran but reported an error.',
13
+ invalid_args: 'The AI sent malformed data to the tool.',
14
+ auth: 'Please log in to continue.',
15
+ no_org: 'Select an organization from the sidebar.',
16
+ no_api_key: 'Configure an API key in Organization settings.',
17
+ generic: 'An unexpected error occurred.'
18
+ };
19
+ const LABELS = {
20
+ unknown_tool: 'AI Error',
21
+ tool_unavailable: 'Tool Unavailable',
22
+ tool_failed: 'Tool Failed',
23
+ invalid_args: 'Parse Error',
24
+ auth: 'Auth Error',
25
+ no_org: 'No Organization',
26
+ no_api_key: 'Missing API Key',
27
+ generic: 'Error'
28
+ };
29
+ // AI hallucinations and user-resolvable config gaps warn rather than alarm;
30
+ // only genuine tool failures and unknown errors read as destructive.
31
+ const TONES = {
32
+ unknown_tool: 'warning',
33
+ tool_unavailable: 'warning',
34
+ invalid_args: 'warning',
35
+ auth: 'warning',
36
+ no_org: 'warning',
37
+ no_api_key: 'warning',
38
+ tool_failed: 'destructive',
39
+ generic: 'destructive'
40
+ };
41
+ /** Pull a tool name out of a provider message, e.g. `call tool 'run_command'`. */
42
+ export function extractToolName(message) {
43
+ const match = /attempted to call tool '([^']+)'/.exec(message);
44
+ return match ? match[1] : null;
45
+ }
46
+ /** Reduce any raw error input to its human message string. */
47
+ export function toErrorMessage(input) {
48
+ if (input instanceof Error)
49
+ return input.message;
50
+ if (typeof input !== 'string') {
51
+ try {
52
+ return JSON.stringify(input);
53
+ }
54
+ catch {
55
+ return String(input);
56
+ }
57
+ }
58
+ try {
59
+ const parsed = JSON.parse(input);
60
+ if (typeof parsed === 'object' && parsed !== null && 'error' in parsed) {
61
+ return String(parsed.error);
62
+ }
63
+ }
64
+ catch {
65
+ // Not JSON — use as-is.
66
+ }
67
+ return input;
68
+ }
69
+ function classify(message) {
70
+ const lower = message.toLowerCase();
71
+ if (lower.includes('unknown tool:'))
72
+ return 'unknown_tool';
73
+ if (lower.includes('was not in request.tools') ||
74
+ lower.includes('tool call validation failed') ||
75
+ lower.includes('tool_use_failed')) {
76
+ return 'tool_unavailable';
77
+ }
78
+ if (lower.includes('invalid tool arguments') || lower.includes('malformed json')) {
79
+ return 'invalid_args';
80
+ }
81
+ if (lower === 'unauthorized' || lower.includes('please log in'))
82
+ return 'auth';
83
+ if (lower.includes('no organization context'))
84
+ return 'no_org';
85
+ if (lower.includes('api key') && (lower.includes('no ') || lower.includes('configured'))) {
86
+ return 'no_api_key';
87
+ }
88
+ if (lower.includes('tool execution failed') || lower.includes('failed'))
89
+ return 'tool_failed';
90
+ return 'generic';
91
+ }
92
+ /** Build the full categorization for a known category. */
93
+ export function describeCategory(category) {
94
+ return { category, label: LABELS[category], hint: HINTS[category], tone: TONES[category] };
95
+ }
96
+ /**
97
+ * Categorize a raw error into `{ category, label, hint, tone }`.
98
+ * Accepts an `Error`, a JSON error blob, or a plain message string.
99
+ */
100
+ export function categorizeError(input) {
101
+ const message = toErrorMessage(input);
102
+ const category = classify(message);
103
+ const described = describeCategory(category);
104
+ // tool_failed's hint is the real error text — more useful than a generic line.
105
+ return category === 'tool_failed' ? { ...described, hint: message } : described;
106
+ }
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { Collapsible } from 'bits-ui';
3
+ import TriangleAlert from '@lucide/svelte/icons/triangle-alert';
4
+ import CircleX from '@lucide/svelte/icons/circle-x';
5
+ import ChevronDown from '@lucide/svelte/icons/chevron-down';
6
+ import { cn } from '../../utils/index.js';
7
+ import { CodeBlock } from '../../ui/code-block/index.js';
8
+ import {
9
+ categorizeError,
10
+ describeCategory,
11
+ toErrorMessage,
12
+ type ErrorCategorization,
13
+ type ErrorCategory
14
+ } from './categorize-error.js';
15
+
16
+ let {
17
+ open = $bindable(false),
18
+ error,
19
+ message,
20
+ category,
21
+ detail,
22
+ hint,
23
+ children: _children,
24
+ class: className,
25
+ ...restProps
26
+ }: Collapsible.RootProps & {
27
+ /** Raw error to self-categorize (Error, JSON blob, or string). */
28
+ error?: unknown;
29
+ /** Override the summary line; defaults to the error's message. */
30
+ message?: string;
31
+ /** Force a category instead of inferring one from `error`. */
32
+ category?: ErrorCategory;
33
+ /** Raw error text for the expandable CodeBlock; derived from `error` if omitted. */
34
+ detail?: string;
35
+ /** Override the guidance line; defaults to the category hint. */
36
+ hint?: string;
37
+ } = $props();
38
+
39
+ // Self-categorize from the raw error, unless a category is forced.
40
+ const info = $derived<ErrorCategorization>(
41
+ category ? describeCategory(category) : categorizeError(error ?? message ?? '')
42
+ );
43
+
44
+ const summary = $derived(message ?? (error !== undefined ? toErrorMessage(error) : info.label));
45
+ // Skip the guidance line when it just echoes the summary (tool_failed's hint
46
+ // is the raw message, which the summary already shows).
47
+ const resolvedHint = $derived(hint ?? info.hint);
48
+ const guidance = $derived(resolvedHint === summary ? undefined : resolvedHint);
49
+
50
+ // Raw detail for the CodeBlock — only when it adds something past the summary.
51
+ const rawDetail = $derived(detail ?? (error !== undefined ? toErrorMessage(error) : undefined));
52
+ const showDetail = $derived(!!rawDetail && rawDetail !== summary);
53
+ </script>
54
+
55
+ {#snippet head()}
56
+ {#if info.tone === 'warning'}
57
+ <TriangleAlert class="size-(--era-h-xs) shrink-0 text-warning" aria-hidden="true" />
58
+ {:else}
59
+ <CircleX class="size-(--era-h-xs) shrink-0 text-destructive" aria-hidden="true" />
60
+ {/if}
61
+ <span class="min-w-0 flex-1 truncate text-fg era-text-trim">{summary}</span>
62
+ {/snippet}
63
+
64
+ <Collapsible.Root
65
+ bind:open
66
+ data-tone={info.tone}
67
+ class={cn(
68
+ 'group/error flex min-w-0 flex-col gap-(--era-gap) rounded-(--era-rd-lg) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow)',
69
+ className
70
+ )}
71
+ {...restProps}
72
+ >
73
+ {#if showDetail}
74
+ <!-- The whole summary row toggles the raw detail; quiet until hovered. -->
75
+ <Collapsible.Trigger
76
+ class="flex w-full min-w-0 cursor-pointer items-center gap-(--era-gap) text-body text-muted era-interactive"
77
+ >
78
+ {@render head()}
79
+ <ChevronDown
80
+ class="size-(--era-h-xs) shrink-0 transition-transform duration-(--era-duration) ease-(--era-ease) group-data-[state=open]/error:rotate-180"
81
+ aria-hidden="true"
82
+ />
83
+ </Collapsible.Trigger>
84
+ {:else}
85
+ <div class="flex min-w-0 items-center gap-(--era-gap) text-body">
86
+ {@render head()}
87
+ </div>
88
+ {/if}
89
+
90
+ {#if guidance}
91
+ <p class="text-body text-muted">{guidance}</p>
92
+ {/if}
93
+
94
+ {#if showDetail}
95
+ <Collapsible.Content data-era-error-detail class="overflow-hidden">
96
+ <div class="pt-(--era-gap)">
97
+ <CodeBlock code={rawDetail} />
98
+ </div>
99
+ </Collapsible.Content>
100
+ {/if}
101
+ </Collapsible.Root>
102
+
103
+ <style>
104
+ :global([data-era-error-detail][data-state='open']) {
105
+ animation: era-error-expand var(--era-duration) var(--era-ease);
106
+ }
107
+ :global([data-era-error-detail][data-state='closed']) {
108
+ animation: era-error-collapse var(--era-duration) var(--era-ease);
109
+ }
110
+ @keyframes -global-era-error-expand {
111
+ from {
112
+ height: 0;
113
+ opacity: 0;
114
+ }
115
+ to {
116
+ height: var(--bits-collapsible-content-height);
117
+ opacity: 1;
118
+ }
119
+ }
120
+ @keyframes -global-era-error-collapse {
121
+ from {
122
+ height: var(--bits-collapsible-content-height);
123
+ opacity: 1;
124
+ }
125
+ to {
126
+ height: 0;
127
+ opacity: 0;
128
+ }
129
+ }
130
+ </style>
@@ -0,0 +1,17 @@
1
+ import { Collapsible } from 'bits-ui';
2
+ import { type ErrorCategory } from './categorize-error.js';
3
+ type $$ComponentProps = Collapsible.RootProps & {
4
+ /** Raw error to self-categorize (Error, JSON blob, or string). */
5
+ error?: unknown;
6
+ /** Override the summary line; defaults to the error's message. */
7
+ message?: string;
8
+ /** Force a category instead of inferring one from `error`. */
9
+ category?: ErrorCategory;
10
+ /** Raw error text for the expandable CodeBlock; derived from `error` if omitted. */
11
+ detail?: string;
12
+ /** Override the guidance line; defaults to the category hint. */
13
+ hint?: string;
14
+ };
15
+ declare const ErrorPanel: import("svelte").Component<$$ComponentProps, {}, "open">;
16
+ type ErrorPanel = ReturnType<typeof ErrorPanel>;
17
+ export default ErrorPanel;
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './error-panel.svelte';
2
+ export { categorizeError, describeCategory, extractToolName, toErrorMessage, type ErrorCategory, type ErrorTone, type ErrorCategorization } from './categorize-error.js';
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './error-panel.svelte';
2
+ export { categorizeError, describeCategory, extractToolName, toErrorMessage } from './categorize-error.js';
@@ -12,6 +12,11 @@ export * as Plan from './plan/index.js';
12
12
  export * as Queue from './queue/index.js';
13
13
  export * as Confirmation from './confirmation/index.js';
14
14
  export * as InlineCitation from './inline-citation/index.js';
15
+ export * as Conversations from './conversations/index.js';
16
+ export * as StructuredValue from './structured-value/index.js';
17
+ export * as RuntimeFeed from './runtime-feed/index.js';
18
+ export * as ErrorPanel from './error-panel/index.js';
19
+ export * as RunStatus from './run-status/index.js';
15
20
  export { Response, repairStreamingMarkdown } from './response/index.js';
16
21
  export { default as ContextGauge } from './context/context.svelte';
17
22
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
@@ -27,3 +32,9 @@ export { StickToBottom } from './stick-to-bottom.svelte.js';
27
32
  export type { PromptAttachment, PromptMessage, ChatStatus } from './prompt-input/index.js';
28
33
  export type { MessageRole, MessageVersion } from './message/index.js';
29
34
  export type { ToolState } from './tool/index.js';
35
+ export type { UsageBreakdown } from './message/index.js';
36
+ export type { ConversationEntry, ConversationGroup, ConversationStatus, TimeBucket } from './conversations/index.js';
37
+ export type { RunState, LogTone, RunTools } from './run-status/index.js';
38
+ export type { ErrorCategory, ErrorTone, ErrorCategorization } from './error-panel/index.js';
39
+ export { categorizeError } from './error-panel/index.js';
40
+ export { projectMessages, toolState, type ChatMessage, type ChatPart, type ChatRole, type ProjectedMessage, type ProjectedBlock, type ProjectedTool } from './project.js';
package/dist/ai/index.js CHANGED
@@ -17,6 +17,11 @@ export * as Plan from './plan/index.js';
17
17
  export * as Queue from './queue/index.js';
18
18
  export * as Confirmation from './confirmation/index.js';
19
19
  export * as InlineCitation from './inline-citation/index.js';
20
+ export * as Conversations from './conversations/index.js';
21
+ export * as StructuredValue from './structured-value/index.js';
22
+ export * as RuntimeFeed from './runtime-feed/index.js';
23
+ export * as ErrorPanel from './error-panel/index.js';
24
+ export * as RunStatus from './run-status/index.js';
20
25
  export { Response, repairStreamingMarkdown } from './response/index.js';
21
26
  export { default as ContextGauge } from './context/context.svelte';
22
27
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
@@ -29,3 +34,5 @@ export { default as Shimmer } from './shimmer.svelte';
29
34
  export { default as AIImage } from './image.svelte';
30
35
  export { default as CopyButton } from './copy-button.svelte';
31
36
  export { StickToBottom } from './stick-to-bottom.svelte.js';
37
+ export { categorizeError } from './error-panel/index.js';
38
+ export { projectMessages, toolState } from './project.js';
@@ -1,6 +1,8 @@
1
1
  export { default as Root } from './message.svelte';
2
2
  export { default as Content } from './message-content.svelte';
3
3
  export { default as Toolbar } from './message-toolbar.svelte';
4
+ export { default as Usage } from './message-usage.svelte';
5
+ export type { UsageBreakdown } from './message-usage.svelte';
4
6
  export { default as Branch } from './message-branch.svelte';
5
7
  export { default as BranchContent } from './message-branch-content.svelte';
6
8
  export { default as BranchSelector } from './message-branch-selector.svelte';
@@ -1,6 +1,7 @@
1
1
  export { default as Root } from './message.svelte';
2
2
  export { default as Content } from './message-content.svelte';
3
3
  export { default as Toolbar } from './message-toolbar.svelte';
4
+ export { default as Usage } from './message-usage.svelte';
4
5
  export { default as Branch } from './message-branch.svelte';
5
6
  export { default as BranchContent } from './message-branch-content.svelte';
6
7
  export { default as BranchSelector } from './message-branch-selector.svelte';
@@ -0,0 +1,126 @@
1
+ <script lang="ts" module>
2
+ /** Per-slice token accounting behind the info affordance. All counts are
3
+ * token estimates; the two `*Messages` fields are message counts. */
4
+ export interface UsageBreakdown {
5
+ instructions?: number;
6
+ text?: number;
7
+ toolArgs?: number;
8
+ toolResults?: number;
9
+ summary?: number;
10
+ recentMessages?: number;
11
+ summarizedMessages?: number;
12
+ }
13
+
14
+ // Pure, allocation-cheap formatters — no Date.now / Math.random so the row
15
+ // renders identically on server and client.
16
+
17
+ /** Compact token count: 940 · 12.4k · 3.1M. */
18
+ function formatTokens(n: number): string {
19
+ if (n < 1000) return String(n);
20
+ if (n < 1_000_000) return `${trim(n / 1000)}k`;
21
+ return `${trim(n / 1_000_000)}M`;
22
+ }
23
+
24
+ /** One decimal, but drop a trailing .0 and don't over-precision ≥100. */
25
+ function trim(v: number): string {
26
+ if (v >= 100) return String(Math.round(v));
27
+ return v.toFixed(1).replace(/\.0$/, '');
28
+ }
29
+
30
+ /** Tiered cost precision: sub-cent shows 4dp, sub-dollar 3dp, else 2dp. */
31
+ function formatCost(usd: number): string {
32
+ if (usd === 0) return '$0';
33
+ if (usd < 0.01) return `$${usd.toFixed(4)}`;
34
+ if (usd < 1) return `$${usd.toFixed(3)}`;
35
+ return `$${usd.toFixed(2)}`;
36
+ }
37
+
38
+ /** Strip a provider prefix: `anthropic/claude-opus-4` → `claude-opus-4`. */
39
+ function shortModel(model: string): string {
40
+ return model.split('/').pop() || model;
41
+ }
42
+ </script>
43
+
44
+ <script lang="ts">
45
+ import type { HTMLAttributes } from 'svelte/elements';
46
+ import Info from '@lucide/svelte/icons/info';
47
+ import * as Tooltip from '../../ui/tooltip/index.js';
48
+ import { cn } from '../../utils/index.js';
49
+
50
+ let {
51
+ tokensIn,
52
+ tokensOut,
53
+ model,
54
+ cost,
55
+ breakdown,
56
+ class: className,
57
+ ...restProps
58
+ }: HTMLAttributes<HTMLDivElement> & {
59
+ tokensIn?: number;
60
+ tokensOut?: number;
61
+ model?: string;
62
+ cost?: number;
63
+ breakdown?: UsageBreakdown;
64
+ } = $props();
65
+
66
+ const total = $derived((tokensIn ?? 0) + (tokensOut ?? 0));
67
+ const hasTokens = $derived(tokensIn != null || tokensOut != null);
68
+
69
+ // Only the token slices present get a row; message counts trail as a caption.
70
+ const rows = $derived(
71
+ breakdown
72
+ ? (
73
+ [
74
+ ['instructions', breakdown.instructions],
75
+ ['text', breakdown.text],
76
+ ['tool args', breakdown.toolArgs],
77
+ ['tool results', breakdown.toolResults],
78
+ ['summary', breakdown.summary]
79
+ ] as [string, number | undefined][]
80
+ ).filter((r): r is [string, number] => r[1] != null)
81
+ : []
82
+ );
83
+ </script>
84
+
85
+ <div
86
+ class={cn(
87
+ 'flex items-center gap-(--era-inset-sm) font-mono text-body tabular-nums text-muted era-text-trim',
88
+ className
89
+ )}
90
+ {...restProps}
91
+ >
92
+ {#if hasTokens}
93
+ <span class="text-fg">{formatTokens(total)} tok</span>
94
+ <span>in {formatTokens(tokensIn ?? 0)} · out {formatTokens(tokensOut ?? 0)}</span>
95
+ {/if}
96
+ {#if model}<span>{shortModel(model)}</span>{/if}
97
+ {#if cost != null}<span class="text-fg">{formatCost(cost)}</span>{/if}
98
+
99
+ {#if breakdown}
100
+ <Tooltip.Provider delayDuration={150}>
101
+ <Tooltip.Root>
102
+ <Tooltip.Trigger
103
+ class="inline-flex items-center text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:text-fg"
104
+ aria-label="Context window breakdown"
105
+ >
106
+ <Info class="size-(--era-h-xs)" aria-hidden="true" />
107
+ </Tooltip.Trigger>
108
+ <Tooltip.Content
109
+ class="h-auto flex-col items-start gap-(--era-inset-xxs) py-(--era-inset-xxs) whitespace-normal"
110
+ >
111
+ {#each rows as [label, value] (label)}
112
+ <span class="flex w-full items-center justify-between gap-(--era-inset-md) tabular-nums">
113
+ <span class="text-muted">{label}</span>
114
+ <span>{formatTokens(value)}</span>
115
+ </span>
116
+ {/each}
117
+ {#if breakdown.recentMessages != null || breakdown.summarizedMessages != null}
118
+ <span class="text-muted tabular-nums">
119
+ {breakdown.recentMessages ?? 0} recent · {breakdown.summarizedMessages ?? 0} summarized
120
+ </span>
121
+ {/if}
122
+ </Tooltip.Content>
123
+ </Tooltip.Root>
124
+ </Tooltip.Provider>
125
+ {/if}
126
+ </div>
@@ -0,0 +1,22 @@
1
+ /** Per-slice token accounting behind the info affordance. All counts are
2
+ * token estimates; the two `*Messages` fields are message counts. */
3
+ export interface UsageBreakdown {
4
+ instructions?: number;
5
+ text?: number;
6
+ toolArgs?: number;
7
+ toolResults?: number;
8
+ summary?: number;
9
+ recentMessages?: number;
10
+ summarizedMessages?: number;
11
+ }
12
+ import type { HTMLAttributes } from 'svelte/elements';
13
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
14
+ tokensIn?: number;
15
+ tokensOut?: number;
16
+ model?: string;
17
+ cost?: number;
18
+ breakdown?: UsageBreakdown;
19
+ };
20
+ declare const MessageUsage: import("svelte").Component<$$ComponentProps, {}, "">;
21
+ type MessageUsage = ReturnType<typeof MessageUsage>;
22
+ export default MessageUsage;
@@ -0,0 +1,64 @@
1
+ import type { ToolState } from './tool/index.js';
2
+ export type ChatRole = 'user' | 'assistant' | 'system';
3
+ export type ChatPart = {
4
+ type: 'text';
5
+ text: string;
6
+ } | {
7
+ type: 'reasoning';
8
+ text: string;
9
+ } | {
10
+ type: 'tool-call';
11
+ toolCallId: string;
12
+ toolName: string;
13
+ input?: unknown;
14
+ } | {
15
+ type: 'tool-result';
16
+ toolCallId: string;
17
+ output?: unknown;
18
+ errorText?: string;
19
+ };
20
+ export interface ChatMessage {
21
+ id: string;
22
+ role: ChatRole;
23
+ parts: ChatPart[];
24
+ }
25
+ export interface ProjectedTool {
26
+ toolCallId: string;
27
+ toolName: string;
28
+ input?: unknown;
29
+ output?: unknown;
30
+ errorText?: string;
31
+ /** Derived lifecycle, ready to hand straight to <Tool.Root state={…}>. */
32
+ state: ToolState;
33
+ }
34
+ export type ProjectedBlock = {
35
+ type: 'text';
36
+ key: string;
37
+ text: string;
38
+ } | {
39
+ type: 'reasoning';
40
+ key: string;
41
+ text: string;
42
+ } | {
43
+ type: 'tool-row';
44
+ key: string;
45
+ tools: ProjectedTool[];
46
+ };
47
+ export interface ProjectedMessage {
48
+ id: string;
49
+ role: ChatRole;
50
+ blocks: ProjectedBlock[];
51
+ }
52
+ type ToolResultInfo = {
53
+ output?: unknown;
54
+ errorText?: string;
55
+ };
56
+ /**
57
+ * Derive a tool's AI-SDK lifecycle from its call + (maybe) result. A present
58
+ * result wins: an `errorText` is `output-error`, otherwise `output-available`.
59
+ * With no result yet, a call whose input is still `undefined` is streaming its
60
+ * arguments; once the input is populated it's awaiting execution.
61
+ */
62
+ export declare function toolState(input: unknown, result: ToolResultInfo | undefined): ToolState;
63
+ export declare function projectMessages(messages: ChatMessage[]): ProjectedMessage[];
64
+ export {};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Derive a tool's AI-SDK lifecycle from its call + (maybe) result. A present
3
+ * result wins: an `errorText` is `output-error`, otherwise `output-available`.
4
+ * With no result yet, a call whose input is still `undefined` is streaming its
5
+ * arguments; once the input is populated it's awaiting execution.
6
+ */
7
+ export function toolState(input, result) {
8
+ if (result)
9
+ return result.errorText !== undefined ? 'output-error' : 'output-available';
10
+ return input === undefined ? 'input-streaming' : 'input-available';
11
+ }
12
+ export function projectMessages(messages) {
13
+ // First pass: index every tool-result by id, from anywhere in the history —
14
+ // results routinely arrive in a message after the one that made the call.
15
+ const results = new Map();
16
+ for (const message of messages) {
17
+ for (const part of message.parts) {
18
+ if (part.type === 'tool-result') {
19
+ results.set(part.toolCallId, { output: part.output, errorText: part.errorText });
20
+ }
21
+ }
22
+ }
23
+ // Second pass: build ordered blocks, deduping each tool-call id to its first
24
+ // appearance (SDKs replay the full transcript every turn).
25
+ const seenTools = new Set();
26
+ return messages.map((message) => {
27
+ const blocks = [];
28
+ let row = [];
29
+ const flushRow = () => {
30
+ if (row.length === 0)
31
+ return;
32
+ blocks.push({ type: 'tool-row', key: `${message.id}:tools:${row[0].toolCallId}`, tools: row });
33
+ row = [];
34
+ };
35
+ for (const [i, part] of message.parts.entries()) {
36
+ switch (part.type) {
37
+ case 'text':
38
+ flushRow();
39
+ if (part.text)
40
+ blocks.push({ type: 'text', key: `${message.id}:text:${i}`, text: part.text });
41
+ break;
42
+ case 'reasoning':
43
+ flushRow();
44
+ if (part.text)
45
+ blocks.push({ type: 'reasoning', key: `${message.id}:reasoning:${i}`, text: part.text });
46
+ break;
47
+ case 'tool-call': {
48
+ if (seenTools.has(part.toolCallId))
49
+ break;
50
+ seenTools.add(part.toolCallId);
51
+ const result = results.get(part.toolCallId);
52
+ row.push({
53
+ toolCallId: part.toolCallId,
54
+ toolName: part.toolName,
55
+ input: part.input,
56
+ output: result?.output,
57
+ errorText: result?.errorText,
58
+ state: toolState(part.input, result)
59
+ });
60
+ break;
61
+ }
62
+ case 'tool-result':
63
+ // Consumed into its call above; never a standalone block.
64
+ break;
65
+ }
66
+ }
67
+ flushRow();
68
+ return { id: message.id, role: message.role, blocks };
69
+ });
70
+ }
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './run-status.svelte';
2
+ export type { RunState, LogTone, RunTools } from './run-status.svelte';
@@ -0,0 +1 @@
1
+ export { default as Root } from './run-status.svelte';