@theoremjs/react 0.0.0-stage → 0.3.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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -2
  3. package/client/attachment-hover-preview.d.ts +11 -0
  4. package/client/attachment-hover-preview.js +36 -0
  5. package/client/audio-level.d.ts +5 -0
  6. package/client/audio-level.js +24 -0
  7. package/client/composer-attachments.d.ts +30 -0
  8. package/client/composer-attachments.js +33 -0
  9. package/client/composer-drawer.d.ts +19 -0
  10. package/client/composer-drawer.js +15 -0
  11. package/client/composer-hints.d.ts +25 -0
  12. package/client/composer-hints.js +20 -0
  13. package/client/composer-primary.d.ts +16 -0
  14. package/client/composer-primary.js +25 -0
  15. package/client/decision-transport.d.ts +48 -0
  16. package/client/decision-transport.js +102 -0
  17. package/client/decode-composer-draft.d.ts +11 -0
  18. package/client/decode-composer-draft.js +12 -0
  19. package/client/encode-composer-draft.d.ts +9 -0
  20. package/client/encode-composer-draft.js +34 -0
  21. package/client/encode-files.d.ts +18 -0
  22. package/client/encode-files.js +54 -0
  23. package/client/failure.d.ts +32 -0
  24. package/client/failure.js +34 -0
  25. package/client/generation-selection.d.ts +15 -0
  26. package/client/generation-selection.js +24 -0
  27. package/client/host-call.d.ts +51 -0
  28. package/client/host-call.js +42 -0
  29. package/client/host-transport.d.ts +68 -0
  30. package/client/host-transport.js +74 -0
  31. package/client/image-output.d.ts +6 -0
  32. package/client/image-output.js +13 -0
  33. package/client/index.d.ts +20 -0
  34. package/client/index.js +18 -0
  35. package/client/ink-waveform.d.ts +17 -0
  36. package/client/ink-waveform.js +69 -0
  37. package/client/live/apply-live-turn-tool-event.d.ts +14 -0
  38. package/client/live/apply-live-turn-tool-event.js +39 -0
  39. package/client/live/live-captions.d.ts +23 -0
  40. package/client/live/live-captions.js +70 -0
  41. package/client/live/live-mic-forward.d.ts +29 -0
  42. package/client/live/live-mic-forward.js +37 -0
  43. package/client/live/live-state.d.ts +11 -0
  44. package/client/live/live-state.js +20 -0
  45. package/client/live/live-tool.d.ts +11 -0
  46. package/client/live/live-tool.js +1 -0
  47. package/client/live/live-video.d.ts +10 -0
  48. package/client/live/live-video.js +71 -0
  49. package/client/live/run-live-tool-call.d.ts +17 -0
  50. package/client/live/run-live-tool-call.js +32 -0
  51. package/client/live-client.d.ts +141 -0
  52. package/client/live-client.js +638 -0
  53. package/client/live-errors.d.ts +2 -0
  54. package/client/live-errors.js +9 -0
  55. package/client/live-messages.d.ts +110 -0
  56. package/client/live-messages.js +67 -0
  57. package/client/mic-capture.d.ts +4 -0
  58. package/client/mic-capture.js +6 -0
  59. package/client/oauth-popup.d.ts +26 -0
  60. package/client/oauth-popup.js +30 -0
  61. package/client/pcm-downsample.d.ts +4 -0
  62. package/client/pcm-downsample.js +45 -0
  63. package/client/run-commit.d.ts +61 -0
  64. package/client/run-commit.js +124 -0
  65. package/client/run-session.d.ts +80 -0
  66. package/client/run-session.js +215 -0
  67. package/client/schema-fields.d.ts +63 -0
  68. package/client/schema-fields.js +178 -0
  69. package/client/shaped-data.d.ts +118 -0
  70. package/client/shaped-data.js +314 -0
  71. package/client/source-citations.d.ts +13 -0
  72. package/client/source-citations.js +56 -0
  73. package/client/state-fields.d.ts +16 -0
  74. package/client/state-fields.js +44 -0
  75. package/client/tool-result.d.ts +78 -0
  76. package/client/tool-result.js +275 -0
  77. package/client/tool-resume.d.ts +33 -0
  78. package/client/tool-resume.js +11 -0
  79. package/client/trace-feed.d.ts +13 -0
  80. package/client/trace-feed.js +18 -0
  81. package/client/trace-story.d.ts +163 -0
  82. package/client/trace-story.js +416 -0
  83. package/client/trace-view.d.ts +130 -0
  84. package/client/trace-view.js +293 -0
  85. package/client/transcript-groups.d.ts +114 -0
  86. package/client/transcript-groups.js +167 -0
  87. package/client/transcript-scroll.d.ts +7 -0
  88. package/client/transcript-scroll.js +6 -0
  89. package/client/transport.d.ts +176 -0
  90. package/client/transport.js +256 -0
  91. package/client/turn-client.d.ts +35 -0
  92. package/client/turn-client.js +93 -0
  93. package/client/voice-label.d.ts +2 -0
  94. package/client/voice-label.js +15 -0
  95. package/client/voice-recorder.d.ts +27 -0
  96. package/client/voice-recorder.js +165 -0
  97. package/client/walk-away.d.ts +34 -0
  98. package/client/walk-away.js +48 -0
  99. package/client/wire-line.d.ts +56 -0
  100. package/client/wire-line.js +90 -0
  101. package/client/worklets/mic-capture.js +16 -0
  102. package/components/InkWaveform.d.ts +10 -0
  103. package/components/InkWaveform.js +108 -0
  104. package/components/live/use-live-runner-controls.d.ts +48 -0
  105. package/components/live/use-live-runner-controls.js +100 -0
  106. package/components/live/use-live-runner-model.d.ts +45 -0
  107. package/components/live/use-live-runner-model.js +175 -0
  108. package/components/live/use-live-runner-ui.d.ts +65 -0
  109. package/components/live/use-live-runner-ui.js +159 -0
  110. package/components/live/use-live-session-client.d.ts +41 -0
  111. package/components/live/use-live-session-client.js +123 -0
  112. package/components/use-composer-voice.d.ts +28 -0
  113. package/components/use-composer-voice.js +90 -0
  114. package/components/use-editor-selection.d.ts +3 -0
  115. package/components/use-editor-selection.js +16 -0
  116. package/components/use-voice-playback.d.ts +16 -0
  117. package/components/use-voice-playback.js +68 -0
  118. package/hooks/use-described.d.ts +8 -0
  119. package/hooks/use-described.js +23 -0
  120. package/hooks/use-theorem-chat-actions.d.ts +75 -0
  121. package/hooks/use-theorem-chat-actions.js +265 -0
  122. package/hooks/use-theorem-chat-state.d.ts +66 -0
  123. package/hooks/use-theorem-chat-state.js +118 -0
  124. package/hooks/use-theorem-chat.d.ts +63 -0
  125. package/hooks/use-theorem-chat.js +340 -0
  126. package/hooks/use-theorem-decision.d.ts +23 -0
  127. package/hooks/use-theorem-decision.js +75 -0
  128. package/hooks/use-theorem-host.d.ts +44 -0
  129. package/hooks/use-theorem-host.js +117 -0
  130. package/hooks/use-theorem-interface.d.ts +19 -0
  131. package/hooks/use-theorem-interface.js +25 -0
  132. package/hooks/use-trace-records.d.ts +4 -0
  133. package/hooks/use-trace-records.js +7 -0
  134. package/index.d.ts +14 -0
  135. package/index.js +14 -0
  136. package/live.d.ts +8 -0
  137. package/live.js +10 -0
  138. package/package.json +73 -4
  139. package/server/credential-store.d.ts +24 -0
  140. package/server/credential-store.js +14 -0
  141. package/server/decision-handler.d.ts +34 -0
  142. package/server/decision-handler.js +55 -0
  143. package/server/handler.d.ts +114 -0
  144. package/server/handler.js +568 -0
  145. package/server/json-response.d.ts +2 -0
  146. package/server/json-response.js +7 -0
  147. package/server/mod.d.ts +16 -0
  148. package/server/mod.js +15 -0
  149. package/server/request-check.d.ts +16 -0
  150. package/server/request-check.js +34 -0
  151. package/server/session-store.d.ts +74 -0
  152. package/server/session-store.js +49 -0
  153. package/server/steer-inbox.d.ts +35 -0
  154. package/server/steer-inbox.js +52 -0
  155. package/server/walk-away.d.ts +22 -0
  156. package/server/walk-away.js +41 -0
  157. package/styles/ink-controls.css +134 -0
  158. package/ui/ChatComposerBar.d.ts +39 -0
  159. package/ui/ChatComposerBar.js +251 -0
  160. package/ui/ChatTranscript.d.ts +30 -0
  161. package/ui/ChatTranscript.js +474 -0
  162. package/ui/ConsoleFrame.d.ts +47 -0
  163. package/ui/ConsoleFrame.js +46 -0
  164. package/ui/DecisionAnswers.d.ts +15 -0
  165. package/ui/DecisionAnswers.js +102 -0
  166. package/ui/LiveRunner.d.ts +28 -0
  167. package/ui/LiveRunner.js +193 -0
  168. package/ui/SchemaFields.d.ts +10 -0
  169. package/ui/SchemaFields.js +122 -0
  170. package/ui/ShapedData.d.ts +11 -0
  171. package/ui/ShapedData.js +151 -0
  172. package/ui/SidePanel.d.ts +57 -0
  173. package/ui/SidePanel.js +50 -0
  174. package/ui/StateFields.d.ts +36 -0
  175. package/ui/StateFields.js +96 -0
  176. package/ui/TheoremChat.d.ts +71 -0
  177. package/ui/TheoremChat.js +146 -0
  178. package/ui/TheoremDecision.d.ts +38 -0
  179. package/ui/TheoremDecision.js +87 -0
  180. package/ui/TheoremHost.d.ts +38 -0
  181. package/ui/TheoremHost.js +150 -0
  182. package/ui/ToolGateCard.d.ts +28 -0
  183. package/ui/ToolGateCard.js +100 -0
  184. package/ui/ToolResult.d.ts +10 -0
  185. package/ui/ToolResult.js +60 -0
  186. package/ui/ToolResultCharts.d.ts +5 -0
  187. package/ui/ToolResultCharts.js +86 -0
  188. package/ui/TraceCharts.d.ts +13 -0
  189. package/ui/TraceCharts.js +235 -0
  190. package/ui/TraceGuardrails.d.ts +10 -0
  191. package/ui/TraceGuardrails.js +56 -0
  192. package/ui/TraceInspector.d.ts +6 -0
  193. package/ui/TraceInspector.js +189 -0
  194. package/ui/TraceInspectorPanel.d.ts +24 -0
  195. package/ui/TraceInspectorPanel.js +57 -0
  196. package/ui/TraceOverview.d.ts +34 -0
  197. package/ui/TraceOverview.js +96 -0
  198. package/ui/TraceSpanDetail.d.ts +10 -0
  199. package/ui/TraceSpanDetail.js +188 -0
  200. package/ui/TraceStory.d.ts +20 -0
  201. package/ui/TraceStory.js +107 -0
  202. package/ui/TraceValues.d.ts +36 -0
  203. package/ui/TraceValues.js +113 -0
  204. package/ui/TraceWaterfall.d.ts +13 -0
  205. package/ui/TraceWaterfall.js +132 -0
  206. package/ui/VoiceNote.d.ts +12 -0
  207. package/ui/VoiceNote.js +28 -0
  208. package/ui/built/theme.css +774 -0
  209. package/ui/built/theorem.d.ts +13 -0
  210. package/ui/built/theorem.js +631 -0
  211. package/ui/built/theorem.variants.d.ts +16 -0
  212. package/ui/disclosure-motion.d.ts +6 -0
  213. package/ui/disclosure-motion.js +163 -0
  214. package/ui/icons.d.ts +4 -0
  215. package/ui/icons.js +37 -0
  216. package/ui/index.d.ts +25 -0
  217. package/ui/index.js +29 -0
  218. package/ui/labels-provider.d.ts +16 -0
  219. package/ui/labels-provider.js +61 -0
  220. package/ui/labels.d.ts +1153 -0
  221. package/ui/labels.js +603 -0
  222. package/ui/theme.d.ts +10 -0
  223. package/ui/theme.js +13 -0
  224. package/ui/theorem-theme.d.ts +10 -0
  225. package/ui/theorem-theme.js +83 -0
  226. package/ui/trace-palette.d.ts +24 -0
  227. package/ui/trace-palette.js +24 -0
  228. package/ui/trace-patterns.d.ts +32 -0
  229. package/ui/trace-patterns.js +44 -0
  230. package/ui/transcript-copy-text.d.ts +4 -0
  231. package/ui/transcript-copy-text.js +39 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * What a tool's result leads with, above its data: the figures that answer it,
3
+ * the series worth a chart, and the images it links. Plain decisions over the
4
+ * value alone, so any tool's output gets them without a schema saying so; the
5
+ * data itself still reads in full below, drawn by `ShapedData`.
6
+ *
7
+ * @module
8
+ */
9
+ import { columnStore, fieldLabel, humanize, ISO_DATE, isPlain, isRow, printableUnit, shownEntries, storeRows, titleKey, unitsOf, unpacked, } from "./shaped-data.js";
10
+ const MAX_FIGURES = 4;
11
+ const MAX_CHARTS = 3;
12
+ /** Series one time chart overlays, when they share a unit. */
13
+ const MAX_SERIES = 3;
14
+ /** Bars a category chart ranks. */
15
+ const MAX_BARS = 8;
16
+ const MAX_IMAGES = 6;
17
+ /** Rows a list needs before it reads as a chart. */
18
+ const MIN_POINTS = 3;
19
+ /** Levels searched for charts and images. */
20
+ const MAX_SEARCH_DEPTH = 3;
21
+ /** Numbers that locate or name a thing rather than measure it. */
22
+ const NOT_A_MEASURE = /(^|_)(id|ids|lat|lon|lng|latitude|longitude|offset|interval|index|rank|order)$|Id$|^(generationtime_ms|utc_offset_seconds)$/i;
23
+ const IMAGE_PATH = /\.(png|jpe?g|gif|webp|avif|svg)$/i;
24
+ function isImageUrl(text) {
25
+ if (/\s/.test(text))
26
+ return false;
27
+ try {
28
+ const url = new URL(text);
29
+ return (url.protocol === 'http:' || url.protocol === 'https:') && IMAGE_PATH.test(url.pathname);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function isMeasure(key) {
36
+ return !NOT_A_MEASURE.test(key);
37
+ }
38
+ /** A code-like key (`USD`, `EUR`) keeps its case; others read as words. */
39
+ function keyLabel(key) {
40
+ return /^[A-Z][A-Z0-9]{1,5}$/.test(key) ? key : humanize(key);
41
+ }
42
+ /** A field's reading, unless its units say it's a code (`wmo code`, `iso8601`, a flag with no unit). */
43
+ function measured(key, units) {
44
+ if (!isMeasure(key))
45
+ return undefined;
46
+ if (units && key in units && printableUnit(units[key]) === undefined)
47
+ return undefined;
48
+ const { label, unit } = fieldLabel(key, units);
49
+ return { label: keyLabel(key) === key ? key : label, unit };
50
+ }
51
+ function figuresOf(row, units) {
52
+ const figures = [];
53
+ for (const [key, value] of shownEntries(row)) {
54
+ if (typeof value !== 'number' || !Number.isFinite(value))
55
+ continue;
56
+ const reading = measured(key, units);
57
+ if (reading)
58
+ figures.push({ key, value, ...reading });
59
+ }
60
+ // Numbers with a unit say more at a glance: they lead.
61
+ return figures.sort((a, b) => Number(b.unit !== undefined) - Number(a.unit !== undefined)).slice(0, MAX_FIGURES);
62
+ }
63
+ /**
64
+ * The figures: the numbers of the first nested object of plain fields (the
65
+ * `current` reading beside a forecast, a conversion's `rates`), else the
66
+ * value's own top-level numbers. Beside a nested reading, top-level numbers
67
+ * are usually the request echoed back.
68
+ */
69
+ function leadFigures(value) {
70
+ if (!isRow(value))
71
+ return [];
72
+ for (const [key, entry] of shownEntries(value)) {
73
+ const inner = unpacked(entry);
74
+ if (!isRow(inner) || columnStore(inner))
75
+ continue;
76
+ const plain = Object.values(inner);
77
+ if (plain.length === 0 || !plain.every(isPlain))
78
+ continue;
79
+ const figures = figuresOf(inner, unitsOf(value, key));
80
+ if (figures.length > 0)
81
+ return figures;
82
+ }
83
+ return figuresOf(value, undefined);
84
+ }
85
+ function distinct(rows, key) {
86
+ return new Set(rows.map((row) => row[key])).size;
87
+ }
88
+ /** Fields every row holds as a number, that measure something and vary. */
89
+ function numericSeries(rows, skip, units) {
90
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].filter((key) => key !== skip && rows.every((row) => typeof row[key] === 'number' || row[key] === null) && distinct(rows, key) > 1);
91
+ return keys.flatMap((key) => {
92
+ const reading = measured(key, units);
93
+ return reading ? [{ key, ...reading }] : [];
94
+ });
95
+ }
96
+ /** The field every row holds as an ISO date, if one does. */
97
+ function dateKey(rows) {
98
+ const [first] = rows;
99
+ if (!first)
100
+ return undefined;
101
+ return Object.keys(first).find((key) => rows.every((row) => typeof row[key] === 'string' && ISO_DATE.test(row[key])));
102
+ }
103
+ /** Series that share leading words ("Temperature 2m max", "… min") are titled by them, and each keeps the rest. */
104
+ function sharedWords(series) {
105
+ const words = series.map((one) => one.label.split(' '));
106
+ const [first = []] = words;
107
+ let shared = 0;
108
+ while (shared < first.length && words.every((one) => one.length > shared + 1 && one[shared] === first[shared]))
109
+ shared++;
110
+ if (!shared)
111
+ return undefined;
112
+ return {
113
+ title: first.slice(0, shared).join(' '),
114
+ series: series.map((one, at) => {
115
+ const rest = (words[at] ?? []).slice(shared).join(' ');
116
+ return { ...one, label: rest.charAt(0).toUpperCase() + rest.slice(1) };
117
+ }),
118
+ };
119
+ }
120
+ /** Series over time, one chart per unit they share, each overlaying up to MAX_SERIES. */
121
+ function timeCharts(path, title, rows, x, units) {
122
+ const byUnit = new Map();
123
+ for (const series of numericSeries(rows, x, units)) {
124
+ const group = byUnit.get(series.unit ?? '') ?? [];
125
+ group.push(series);
126
+ byUnit.set(series.unit ?? '', group);
127
+ }
128
+ return [...byUnit.values()].map((group) => {
129
+ const shown = group.slice(0, MAX_SERIES);
130
+ const shared = shown.length > 1 ? sharedWords(shown) : undefined;
131
+ return {
132
+ kind: 'time',
133
+ key: `${path}:${shown.map((series) => series.key).join('+')}`,
134
+ title: shown.length === 1 ? (shown[0]?.label ?? title) : (shared?.title ?? (title || shown.map((series) => series.label).join(' · '))),
135
+ x,
136
+ series: shared?.series ?? shown,
137
+ rows,
138
+ };
139
+ });
140
+ }
141
+ /** The field that tells rows apart: their title field, or else the shortest text field that differs on every row ("Paris" ×4 → city, suburb, town). */
142
+ function labelKey(rows) {
143
+ const [first] = rows;
144
+ const title = first ? titleKey(first) : undefined;
145
+ if (!first || !title || !rows.every((row) => typeof row[title] === 'string' || typeof row[title] === 'number'))
146
+ return undefined;
147
+ if (distinct(rows, title) === rows.length)
148
+ return title;
149
+ const length = (key) => rows.reduce((sum, row) => sum + String(row[key]).length, 0);
150
+ const [unique] = Object.keys(first)
151
+ .filter((key) => rows.every((row) => typeof row[key] === 'string') && distinct(rows, key) === rows.length)
152
+ .sort((a, b) => length(a) - length(b));
153
+ return unique ?? title;
154
+ }
155
+ /** Rows named by a title field, ranked by each series that varies, most varied first. */
156
+ function categoryCharts(path, title, rows, units) {
157
+ const label = labelKey(rows);
158
+ if (!label)
159
+ return [];
160
+ return numericSeries(rows, label, units)
161
+ .sort((a, b) => distinct(rows, b.key) - distinct(rows, a.key))
162
+ .map((series) => ({
163
+ kind: 'category',
164
+ key: `${path}:${series.key}`,
165
+ title: title ? `${title} · ${series.label}` : series.label,
166
+ series,
167
+ rows: rows
168
+ .filter((row) => typeof row[series.key] === 'number')
169
+ .map((row) => ({ label: String(row[label]), value: row[series.key] }))
170
+ .sort((a, b) => b.value - a.value)
171
+ .slice(0, MAX_BARS),
172
+ }));
173
+ }
174
+ function rowsCharts(path, title, rows, units) {
175
+ if (rows.length < MIN_POINTS)
176
+ return [];
177
+ const x = dateKey(rows);
178
+ return x ? timeCharts(path, title, rows, x, units) : categoryCharts(path, title, rows, units);
179
+ }
180
+ /** Charts anywhere in the value, to MAX_SEARCH_DEPTH: lists of rows, column stores, and objects of numbers. */
181
+ function chartsIn(value, path, title, units, depth) {
182
+ if (depth > MAX_SEARCH_DEPTH)
183
+ return [];
184
+ if (Array.isArray(value)) {
185
+ const rows = value.map(unpacked);
186
+ return rows.every(isRow) ? rowsCharts(path, title, rows, units) : [];
187
+ }
188
+ if (!isRow(value))
189
+ return [];
190
+ const store = columnStore(value);
191
+ if (store)
192
+ return rowsCharts(path, title, storeRows(value, store), units);
193
+ const entries = shownEntries(value);
194
+ const numbers = entries.filter(([key, entry]) => typeof entry === 'number' && isMeasure(key));
195
+ if (depth > 0 && numbers.length >= MIN_POINTS && numbers.length === entries.length) {
196
+ // An object of numbers (`rates`): each key a bar.
197
+ const rows = numbers.map(([key, entry]) => ({ label: keyLabel(key), value: entry }));
198
+ return [
199
+ {
200
+ kind: 'category',
201
+ key: path,
202
+ title,
203
+ series: { key: 'value', label: title },
204
+ rows: rows.sort((a, b) => b.value - a.value).slice(0, MAX_BARS),
205
+ },
206
+ ];
207
+ }
208
+ return entries.flatMap(([key, entry]) => chartsIn(unpacked(entry), path ? `${path}.${key}` : key, depth === 0 ? humanize(key) : `${title} › ${humanize(key)}`, unitsOf(value, key), depth + 1));
209
+ }
210
+ /** Image URLs anywhere in the value, first found first, each once. */
211
+ function imagesIn(value, key, depth, found) {
212
+ if (found.size >= MAX_IMAGES || depth > MAX_SEARCH_DEPTH + 1)
213
+ return;
214
+ const inner = unpacked(value);
215
+ if (typeof inner === 'string') {
216
+ if (isImageUrl(inner) && !found.has(inner))
217
+ found.set(inner, { src: inner, alt: humanize(key || 'image') });
218
+ return;
219
+ }
220
+ if (Array.isArray(inner)) {
221
+ for (const item of inner)
222
+ imagesIn(item, key, depth + 1, found);
223
+ return;
224
+ }
225
+ if (isRow(inner))
226
+ for (const [child, entry] of Object.entries(inner))
227
+ imagesIn(entry, child, depth + 1, found);
228
+ }
229
+ /** What a tool's output leads with. Empty when nothing stands out: the data alone reads best. */
230
+ export function layoutResult(output) {
231
+ const value = unpacked(output);
232
+ const images = new Map();
233
+ imagesIn(value, '', 0, images);
234
+ return {
235
+ figures: leadFigures(value),
236
+ charts: chartsIn(value, '', '', undefined, 0).slice(0, MAX_CHARTS),
237
+ images: [...images.values()],
238
+ };
239
+ }
240
+ function mediaSrc(part) {
241
+ return part.mimeType && part.data ? `data:${part.mimeType};base64,${part.data}` : undefined;
242
+ }
243
+ /** The images and audio a call returned, each with its bytes; parts without them are left out. */
244
+ export function returnedMedia(layout, parts, imageAlt) {
245
+ const sourced = parts.flatMap((part) => {
246
+ const src = mediaSrc(part);
247
+ return src ? [{ type: part.type, src, mimeType: part.mimeType }] : [];
248
+ });
249
+ return {
250
+ images: [...sourced.filter((part) => part.type === 'image').map(({ src }) => ({ src, alt: imageAlt })), ...layout.images],
251
+ audio: sourced.filter((part) => part.type === 'audio').map(({ src, mimeType }) => ({ src, ...(mimeType ? { mimeType } : {}) })),
252
+ };
253
+ }
254
+ /** Whether the result has anything to lead with above its data. */
255
+ export function hasLead(layout, media) {
256
+ return layout.figures.length + layout.charts.length + media.images.length + media.audio.length > 0;
257
+ }
258
+ const DAY_MS = 86_400_000;
259
+ /** Days a time chart names by weekday; longer runs read as dates. */
260
+ const MAX_WEEKDAYS = 10;
261
+ /**
262
+ * How a time chart's dates read, on its axis and in its tooltip: hours within
263
+ * a day, else dates. A bare date is a calendar day, parsed at UTC midnight, so
264
+ * it reads in UTC and doesn't slip a day.
265
+ */
266
+ export function timeFormats(dates) {
267
+ if (dates.some((date) => date.includes('T'))) {
268
+ const span = Math.abs(Date.parse(dates.at(-1) ?? '') - Date.parse(dates[0] ?? ''));
269
+ return { tick: span <= DAY_MS ? { hour: 'numeric' } : { month: 'short', day: 'numeric' }, when: { dateStyle: 'medium', timeStyle: 'short' } };
270
+ }
271
+ return {
272
+ tick: dates.length <= MAX_WEEKDAYS ? { weekday: 'short', timeZone: 'UTC' } : { month: 'short', day: 'numeric', timeZone: 'UTC' },
273
+ when: { dateStyle: 'medium', timeZone: 'UTC' },
274
+ };
275
+ }
@@ -0,0 +1,33 @@
1
+ import type { ToolGate } from '@theoremjs/agents/kernel';
2
+ export type ToolDecisionAction = 'allow' | 'deny';
3
+ export type ToolGateResolution = {
4
+ action: ToolDecisionAction;
5
+ }
6
+ /** Signed in: `secret` is a key the user typed; after an OAuth callback there is none. */
7
+ | {
8
+ action: 'auth';
9
+ secret?: string;
10
+ };
11
+ /** An answer on its way to the gate on `callId`; the gate shows it until the answer settles or fails. */
12
+ export type AnsweringGate = {
13
+ callId: string;
14
+ action: ToolGateResolution['action'];
15
+ };
16
+ /**
17
+ * What the browser sends for the user's answer to a gate. The host settles a
18
+ * refusal; an approval carries the permissions the session holds after it,
19
+ * by the rule the host applies (`sessionPermissionsAfterApproval`).
20
+ */
21
+ export type GatedToolContinue = {
22
+ decision: 'deny';
23
+ } | {
24
+ decision: 'approve';
25
+ secret?: string;
26
+ sessionPermissions: string[];
27
+ };
28
+ export declare function continueGatedToolInvocation(args: {
29
+ toolName: string;
30
+ gate: Pick<ToolGate, 'permission'>;
31
+ sessionPermissions: readonly string[];
32
+ resolution: ToolGateResolution;
33
+ }): GatedToolContinue;
@@ -0,0 +1,11 @@
1
+ import { sessionPermissionsAfterApproval } from '@theoremjs/agents/kernel';
2
+ export function continueGatedToolInvocation(args) {
3
+ if (args.resolution.action === 'deny')
4
+ return { decision: 'deny' };
5
+ const { resolution } = args;
6
+ return {
7
+ decision: 'approve',
8
+ ...(resolution.action === 'auth' && resolution.secret !== undefined ? { secret: resolution.secret } : {}),
9
+ sessionPermissions: sessionPermissionsAfterApproval(args.sessionPermissions, args.toolName, args.gate.permission),
10
+ };
11
+ }
@@ -0,0 +1,13 @@
1
+ import type { TraceRecord } from '@theoremjs/agents';
2
+ /**
3
+ * Trace records a host sends back to the client, in arrival order. Only a host
4
+ * that delivers its traces (the playground) has one; `useTraceRecords` reads it.
5
+ */
6
+ export interface TraceFeed {
7
+ push(record: TraceRecord): void;
8
+ /** The records so far; a new array after every push. */
9
+ records(): readonly TraceRecord[];
10
+ /** Called after every push; returns the unsubscribe. */
11
+ subscribe(listener: () => void): () => void;
12
+ }
13
+ export declare function createTraceFeed(): TraceFeed;
@@ -0,0 +1,18 @@
1
+ export function createTraceFeed() {
2
+ let records = [];
3
+ const listeners = new Set();
4
+ return {
5
+ push(record) {
6
+ records = [...records, record];
7
+ for (const listener of listeners)
8
+ listener();
9
+ },
10
+ records: () => records,
11
+ subscribe(listener) {
12
+ listeners.add(listener);
13
+ return () => {
14
+ listeners.delete(listener);
15
+ };
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,163 @@
1
+ import { type TraceNode, type TraceTotals } from './trace-view.ts';
2
+ /**
3
+ * The trace panel's story of a turn: who acted, in what order, and where the
4
+ * time went, read from the span tree. The words still come from the trace
5
+ * catalog; this only decides what a person reads first.
6
+ *
7
+ * @module
8
+ */
9
+ /** Who acted in a step: sets its colour and icon. */
10
+ export type TraceActor = 'user' | 'model' | 'tool' | 'host' | 'theorem' | 'error';
11
+ /** One root of the tree (a turn, a Live session, a host span) with its totals and where its time went. */
12
+ export interface TraceTurn {
13
+ node: TraceNode;
14
+ totals: TraceTotals;
15
+ split: TraceTimeSplit;
16
+ /** The turn itself ended in an error (a step that failed and was recovered from does not count). */
17
+ failed: boolean;
18
+ }
19
+ export declare function traceTurns(tree: readonly TraceNode[]): TraceTurn[];
20
+ /** A host's trace: each root is one tool call, not a conversation's turn. */
21
+ export declare function isCallTrace(turns: readonly TraceTurn[]): boolean;
22
+ /** A row of the whole-trace list: who it's by, what names it, and how it ended. */
23
+ export type TraceTurnRow = {
24
+ actor: TraceActor;
25
+ title?: string;
26
+ outcome?: TraceOutcome;
27
+ answered?: string;
28
+ };
29
+ /** A turn named by what the person asked, a host's call by its tool; a failure by its outcome, else the answer. */
30
+ export declare function traceTurnRow(turn: TraceTurn, isCall: boolean): TraceTurnRow;
31
+ export declare function traceActor(node: TraceNode): TraceActor;
32
+ /** One line of the story. `offsetMs` is from the turn's start. */
33
+ export type TraceStep = {
34
+ kind: 'ask';
35
+ id: string;
36
+ node: TraceNode;
37
+ offsetMs: number;
38
+ text: string;
39
+ } | {
40
+ kind: 'span';
41
+ id: string;
42
+ node: TraceNode;
43
+ offsetMs: number;
44
+ nested: boolean;
45
+ requested: number;
46
+ } | {
47
+ kind: 'event';
48
+ id: string;
49
+ node: TraceNode;
50
+ offsetMs: number;
51
+ label: string;
52
+ detail: readonly string[];
53
+ doc: string;
54
+ } | {
55
+ kind: 'answer';
56
+ id: string;
57
+ node: TraceNode;
58
+ offsetMs: number;
59
+ text?: string;
60
+ };
61
+ /** The last message's text from `role`, read from a span's input or output messages. */
62
+ export declare function messageText(node: TraceNode, key: 'gen_ai.input.messages' | 'gen_ai.output.messages', role: string): string;
63
+ /**
64
+ * A turn as it happened: what the user asked, each model call and the tool
65
+ * calls it asked for (nested under it), the events that changed the course,
66
+ * and how it ended. HTTP tries stay in the timeline; they are how a call
67
+ * travelled, not a step of the story.
68
+ */
69
+ export declare function traceStory(root: TraceNode): TraceStep[];
70
+ /** A tool call's arguments as `[key, value]` pairs of plain values, for a one-line summary. */
71
+ export declare function toolArguments(node: TraceNode): [string, string][];
72
+ /** A stored JSON or text value, parsed when it is JSON. */
73
+ export declare function storedValue(node: TraceNode, key: string): unknown;
74
+ /** How a span ended when it did not simply succeed: a tool denied, paused or failed, or any span's error. */
75
+ export interface TraceOutcome {
76
+ label: string;
77
+ doc?: string;
78
+ /** The tool's own failure code, shown as is. */
79
+ code?: string;
80
+ tone: 'error' | 'warning';
81
+ }
82
+ export declare function traceOutcome(node: TraceNode): TraceOutcome | undefined;
83
+ /** One rule a guardrail check matched, worded by the catalog. */
84
+ export interface TraceGuardrailHit {
85
+ rule: string;
86
+ /** What the rule caught, in words; the id itself for a rule the catalog does not know, such as a host's own. */
87
+ ruleLabel: string;
88
+ /** Why the rule exists and what it does. */
89
+ ruleDoc?: string;
90
+ severity: string;
91
+ /** The severity's label, when the catalog knows it. */
92
+ severityLabel: string;
93
+ /** The matched text, kept only when the profile records match previews. */
94
+ match?: string;
95
+ }
96
+ /** One guardrail check: what it checked, what it did and why, and how long it took when timed. */
97
+ export interface TraceGuardrailCheck {
98
+ id: string;
99
+ /** The span it happened on: the turn, or the tool whose call or result it checked. */
100
+ node: TraceNode;
101
+ offsetMs: number;
102
+ stage: string;
103
+ stageLabel: string;
104
+ /** Which timed check ran, in words, when the check was timed. */
105
+ checkLabel?: string;
106
+ action: string;
107
+ actionLabel: string;
108
+ actionDoc?: string;
109
+ durationMs?: number;
110
+ hits: readonly TraceGuardrailHit[];
111
+ /** The tool the checked text came from. */
112
+ tool?: string;
113
+ /** The guardrail's own reason, for the builder. */
114
+ reason?: string;
115
+ }
116
+ /** Every guardrail check the turn recorded, in the order they ran: the passes as well as what they caught. */
117
+ export declare function traceGuardrails(root: TraceNode): TraceGuardrailCheck[];
118
+ /**
119
+ * Where a turn's time went: waiting on the model, running tools, guardrail
120
+ * checks, host hooks, and everything else. Guardrail checks during a stream
121
+ * come out of the model's time; the rest, like the hooks, out of the time
122
+ * between calls.
123
+ */
124
+ export interface TraceTimeSplit {
125
+ model: number;
126
+ tools: number;
127
+ guardrails: number;
128
+ hooks: number;
129
+ other: number;
130
+ }
131
+ /**
132
+ * How quickly a turn answered and what its guardrails cost. Each value is
133
+ * absent when the trace did not record it.
134
+ */
135
+ export interface TraceLatency {
136
+ /** From the turn's start to the first text the person saw. */
137
+ firstTextMs?: number;
138
+ /** Of that, the time guardrails held finished text back before showing it. */
139
+ heldMs?: number;
140
+ /** Output tokens per second of writing, from each call's first text to its end. */
141
+ tokensPerSecond?: number;
142
+ /** Guardrail checks the trace recorded, how long they took, and how many were not a plain pass. */
143
+ guardrails?: {
144
+ checks: number;
145
+ ms: number;
146
+ flagged: number;
147
+ };
148
+ }
149
+ export declare function traceLatency(root: TraceNode): TraceLatency;
150
+ /** One row of the timeline: a span, how deep it sits, and its start and end from the turn's start. */
151
+ export interface TraceBar {
152
+ node: TraceNode;
153
+ depth: number;
154
+ from: number;
155
+ to: number;
156
+ }
157
+ export declare function traceBars(root: TraceNode): TraceBar[];
158
+ /** How busy the model and the tools were across the turn, in `buckets` equal slices (0 to 1 each). */
159
+ export declare function traceActivity(root: TraceNode, buckets: number): {
160
+ at: number;
161
+ model: number;
162
+ tools: number;
163
+ }[];