inspect-ai 0.3.96__py3-none-any.whl → 0.3.97__py3-none-any.whl

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 (133) hide show
  1. inspect_ai/_eval/eval.py +10 -2
  2. inspect_ai/_eval/task/util.py +32 -3
  3. inspect_ai/_util/registry.py +7 -0
  4. inspect_ai/_util/timer.py +13 -0
  5. inspect_ai/_view/www/dist/assets/index.css +275 -195
  6. inspect_ai/_view/www/dist/assets/index.js +8568 -7376
  7. inspect_ai/_view/www/src/app/App.css +1 -0
  8. inspect_ai/_view/www/src/app/App.tsx +27 -10
  9. inspect_ai/_view/www/src/app/appearance/icons.ts +5 -0
  10. inspect_ai/_view/www/src/app/content/RecordTree.module.css +22 -0
  11. inspect_ai/_view/www/src/app/content/RecordTree.tsx +370 -0
  12. inspect_ai/_view/www/src/app/content/RenderedContent.module.css +5 -0
  13. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +32 -19
  14. inspect_ai/_view/www/src/app/content/record_processors/store.ts +101 -0
  15. inspect_ai/_view/www/src/app/content/record_processors/types.ts +3 -0
  16. inspect_ai/_view/www/src/app/content/types.ts +5 -0
  17. inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -0
  18. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +35 -28
  19. inspect_ai/_view/www/src/app/log-view/LogViewLayout.tsx +1 -8
  20. inspect_ai/_view/www/src/app/log-view/navbar/PrimaryBar.tsx +2 -4
  21. inspect_ai/_view/www/src/app/log-view/navbar/ResultsPanel.tsx +13 -3
  22. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.module.css +15 -0
  23. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.tsx +14 -10
  24. inspect_ai/_view/www/src/app/log-view/tabs/InfoTab.tsx +9 -3
  25. inspect_ai/_view/www/src/app/log-view/tabs/JsonTab.tsx +1 -3
  26. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +8 -2
  27. inspect_ai/_view/www/src/app/log-view/types.ts +1 -0
  28. inspect_ai/_view/www/src/app/plan/ModelCard.module.css +7 -0
  29. inspect_ai/_view/www/src/app/plan/ModelCard.tsx +5 -2
  30. inspect_ai/_view/www/src/app/plan/PlanCard.tsx +13 -8
  31. inspect_ai/_view/www/src/app/routing/navigationHooks.ts +63 -8
  32. inspect_ai/_view/www/src/app/routing/url.ts +45 -0
  33. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +2 -1
  34. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.tsx +15 -8
  35. inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +3 -0
  36. inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +16 -5
  37. inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +9 -1
  38. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +68 -31
  39. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.module.css +12 -7
  40. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +17 -5
  41. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.module.css +9 -0
  42. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +48 -18
  43. inspect_ai/_view/www/src/app/samples/chat/ChatView.tsx +0 -1
  44. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.module.css +4 -0
  45. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +41 -1
  46. inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -0
  47. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.module.css +0 -3
  48. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -1
  49. inspect_ai/_view/www/src/app/samples/chat/tools/ToolInput.module.css +1 -1
  50. inspect_ai/_view/www/src/app/samples/chat/tools/ToolOutput.module.css +1 -1
  51. inspect_ai/_view/www/src/app/samples/descriptor/score/NumericScoreDescriptor.tsx +5 -1
  52. inspect_ai/_view/www/src/app/samples/descriptor/score/PassFailScoreDescriptor.tsx +11 -6
  53. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +7 -0
  54. inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +5 -18
  55. inspect_ai/_view/www/src/app/samples/sample-tools/SortFilter.tsx +1 -1
  56. inspect_ai/_view/www/src/app/samples/scores/SampleScoresGrid.tsx +18 -5
  57. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.module.css +0 -6
  58. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.tsx +4 -1
  59. inspect_ai/_view/www/src/app/samples/transcript/ApprovalEventView.tsx +4 -2
  60. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +6 -4
  61. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.module.css +1 -1
  62. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +13 -6
  63. inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +6 -4
  64. inspect_ai/_view/www/src/app/samples/transcript/LoggerEventView.tsx +4 -2
  65. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +11 -8
  66. inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +14 -8
  67. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +13 -8
  68. inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +25 -16
  69. inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +7 -5
  70. inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +11 -28
  71. inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +12 -20
  72. inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +12 -31
  73. inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +25 -29
  74. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +297 -0
  75. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +0 -8
  76. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +43 -25
  77. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +43 -0
  78. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +109 -43
  79. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +19 -8
  80. inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +128 -60
  81. inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +14 -4
  82. inspect_ai/_view/www/src/app/samples/transcript/types.ts +6 -4
  83. inspect_ai/_view/www/src/app/types.ts +12 -1
  84. inspect_ai/_view/www/src/components/Card.css +6 -3
  85. inspect_ai/_view/www/src/components/Card.tsx +15 -2
  86. inspect_ai/_view/www/src/components/CopyButton.tsx +4 -6
  87. inspect_ai/_view/www/src/components/ExpandablePanel.module.css +20 -14
  88. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +17 -22
  89. inspect_ai/_view/www/src/components/LargeModal.tsx +5 -1
  90. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +25 -1
  91. inspect_ai/_view/www/src/components/MarkdownDiv.css +4 -0
  92. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +2 -2
  93. inspect_ai/_view/www/src/components/TabSet.module.css +6 -1
  94. inspect_ai/_view/www/src/components/TabSet.tsx +8 -2
  95. inspect_ai/_view/www/src/state/hooks.ts +83 -13
  96. inspect_ai/_view/www/src/state/logPolling.ts +2 -2
  97. inspect_ai/_view/www/src/state/logSlice.ts +1 -2
  98. inspect_ai/_view/www/src/state/logsSlice.ts +9 -9
  99. inspect_ai/_view/www/src/state/samplePolling.ts +1 -1
  100. inspect_ai/_view/www/src/state/sampleSlice.ts +134 -7
  101. inspect_ai/_view/www/src/state/scoring.ts +1 -1
  102. inspect_ai/_view/www/src/state/scrolling.ts +39 -6
  103. inspect_ai/_view/www/src/state/store.ts +5 -0
  104. inspect_ai/_view/www/src/state/store_filter.ts +47 -44
  105. inspect_ai/_view/www/src/utils/debugging.ts +95 -0
  106. inspect_ai/_view/www/src/utils/format.ts +2 -2
  107. inspect_ai/_view/www/src/utils/json.ts +29 -0
  108. inspect_ai/agent/__init__.py +2 -1
  109. inspect_ai/agent/_agent.py +12 -0
  110. inspect_ai/agent/_react.py +184 -48
  111. inspect_ai/agent/_types.py +14 -1
  112. inspect_ai/analysis/beta/__init__.py +0 -2
  113. inspect_ai/analysis/beta/_dataframe/columns.py +11 -16
  114. inspect_ai/analysis/beta/_dataframe/evals/table.py +65 -40
  115. inspect_ai/analysis/beta/_dataframe/events/table.py +24 -36
  116. inspect_ai/analysis/beta/_dataframe/messages/table.py +24 -15
  117. inspect_ai/analysis/beta/_dataframe/progress.py +35 -5
  118. inspect_ai/analysis/beta/_dataframe/record.py +13 -9
  119. inspect_ai/analysis/beta/_dataframe/samples/columns.py +1 -1
  120. inspect_ai/analysis/beta/_dataframe/samples/table.py +156 -46
  121. inspect_ai/analysis/beta/_dataframe/util.py +14 -12
  122. inspect_ai/model/_call_tools.py +1 -1
  123. inspect_ai/model/_providers/anthropic.py +18 -5
  124. inspect_ai/model/_providers/azureai.py +7 -2
  125. inspect_ai/model/_providers/util/llama31.py +3 -3
  126. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/METADATA +1 -1
  127. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/RECORD +131 -126
  128. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/WHEEL +1 -1
  129. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.module.css +0 -48
  130. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.tsx +0 -276
  131. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/entry_points.txt +0 -0
  132. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/licenses/LICENSE +0 -0
  133. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/top_level.txt +0 -0
@@ -228,6 +228,7 @@ body[class^="vscode-"] .accordion-button::after {
228
228
  mask-image: var(--bs-accordion-btn-icon);
229
229
  }
230
230
 
231
+ .copy-button i.bi,
231
232
  body[class^="vscode-"] .navbar-text i.bi {
232
233
  color: var(--vscode-editor-foreground);
233
234
  }
@@ -14,7 +14,7 @@ import "./App.css";
14
14
 
15
15
  import ClipboardJS from "clipboard";
16
16
  import { FC, useCallback, useEffect } from "react";
17
- import { RouterProvider } from "react-router-dom";
17
+ import { RouterProvider, useParams } from "react-router-dom";
18
18
  import { ClientAPI, HostMessage } from "../client/api/types.ts";
19
19
  import { useStore } from "../state/store.ts";
20
20
  import { AppRouter } from "./routing/AppRouter.tsx";
@@ -27,20 +27,33 @@ interface AppProps {
27
27
  * Renders the Main Application
28
28
  */
29
29
  export const App: FC<AppProps> = ({ api }) => {
30
+ // Whether the app was rehydrated
31
+ const rehydrated = useStore((state) => state.app.rehydrated);
32
+
33
+ const logs = useStore((state) => state.logs.logs);
34
+ const selectedLogFile = useStore((state) => state.logs.selectedLogFile);
35
+
36
+ const loadedLogFile = useStore((state) => state.log.loadedLog);
37
+ const selectedLogSummary = useStore((state) => state.log.selectedLogSummary);
38
+
39
+ const setIntialState = useStore((state) => state.appActions.setInitialState);
30
40
  const setAppStatus = useStore((state) => state.appActions.setStatus);
41
+
42
+ const refreshLogs = useStore((state) => state.logsActions.refreshLogs);
31
43
  const setLogs = useStore((state) => state.logsActions.setLogs);
32
44
  const selectLogFile = useStore((state) => state.logsActions.selectLogFile);
33
- const setIntialState = useStore((state) => state.appActions.setInitialState);
34
- const rehydrated = useStore((state) => state.app.rehydrated);
35
- const refreshLogs = useStore((state) => state.logsActions.refreshLogs);
45
+
36
46
  const loadLog = useStore((state) => state.logActions.loadLog);
37
47
  const pollLog = useStore((state) => state.logActions.pollLog);
38
- const loadedLogFile = useStore((state) => state.log.loadedLog);
39
- const selectedLogFile = useStore((state) =>
40
- state.logsActions.getSelectedLogFile(),
41
- );
42
- const selectedLogSummary = useStore((state) => state.log.selectedLogSummary);
43
- const logs = useStore((state) => state.logs.logs);
48
+
49
+ const { sampleId } = useParams<{
50
+ logPath?: string;
51
+ tabId?: string;
52
+ sampleId?: string;
53
+ epoch?: string;
54
+ sampleTabId?: string;
55
+ }>();
56
+ const selectSample = useStore((state) => state.logActions.selectSample);
44
57
 
45
58
  // Load a specific log
46
59
  useEffect(() => {
@@ -53,6 +66,10 @@ export const App: FC<AppProps> = ({ api }) => {
53
66
  // Then load the log
54
67
  await loadLog(selectedLogFile);
55
68
 
69
+ if (!sampleId) {
70
+ selectSample(0);
71
+ }
72
+
56
73
  // Finally set loading to false
57
74
  setAppStatus({ loading: false, error: undefined });
58
75
  } catch (e) {
@@ -62,6 +62,7 @@ export const ApplicationIcons = {
62
62
  time: "bi bi-clock",
63
63
  execution: "bi bi-stopwatch",
64
64
  },
65
+ link: "bi bi-link-45deg",
65
66
  logging: loggingIcons,
66
67
  menu: "bi bi-list",
67
68
  messages: "bi bi-chat-right-text",
@@ -101,5 +102,9 @@ export const ApplicationIcons = {
101
102
  step: "bi bi-fast-forward-btn",
102
103
  subtask: "bi bi-subtract",
103
104
  transcript: "bi bi-list-columns-reverse",
105
+ tree: {
106
+ open: "bi bi-caret-down-fill",
107
+ closed: "bi bi-caret-right-fill",
108
+ },
104
109
  usage: "bi bi-stopwatch",
105
110
  };
@@ -0,0 +1,22 @@
1
+ .keyPairContainer {
2
+ display: grid;
3
+ grid-template-columns: max-content auto;
4
+ column-gap: 0.5em;
5
+ padding-top: 4px;
6
+ padding-bottom: 4px;
7
+ border-bottom: solid 1px var(--bs-border-color);
8
+ }
9
+
10
+ .key {
11
+ display: grid;
12
+ grid-template-columns: 1em auto;
13
+ cursor: pointer;
14
+ }
15
+
16
+ .pre {
17
+ margin-bottom: 0;
18
+ }
19
+
20
+ .treeIcon {
21
+ margin-top: -3px;
22
+ }
@@ -0,0 +1,370 @@
1
+ import clsx from "clsx";
2
+ import {
3
+ FC,
4
+ KeyboardEvent,
5
+ RefObject,
6
+ useCallback,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ } from "react";
11
+ import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
12
+ import { RenderedContent } from "./RenderedContent";
13
+
14
+ import { useCollapsibleIds } from "../../state/hooks";
15
+ import { useVirtuosoState } from "../../state/scrolling";
16
+ import { useStore } from "../../state/store";
17
+ import { ApplicationIcons } from "../appearance/icons";
18
+ import styles from "./RecordTree.module.css";
19
+ import { resolveStoreKeys } from "./record_processors/store";
20
+ import { RecordProcessor } from "./record_processors/types";
21
+
22
+ const kRecordTreeKey = "record-tree-key";
23
+
24
+ interface RecordTreeProps {
25
+ id: string;
26
+ record: Record<string, unknown>;
27
+ className?: string | string[];
28
+ scrollRef?: RefObject<HTMLDivElement | null>;
29
+ defaultExpandLevel?: number;
30
+ processStore?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Renders the MetaDataView component.
35
+ */
36
+ export const RecordTree: FC<RecordTreeProps> = ({
37
+ id,
38
+ record,
39
+ className,
40
+ scrollRef,
41
+ defaultExpandLevel = 1,
42
+ processStore = false,
43
+ }) => {
44
+ // The virtual list handle and state
45
+ const listHandle = useRef<VirtuosoHandle | null>(null);
46
+ const { getRestoreState } = useVirtuosoState(
47
+ listHandle,
48
+ `metadata-grid-${id}`,
49
+ );
50
+
51
+ // Collapse state
52
+ const [collapsedIds, setCollapsed, clearIds] = useCollapsibleIds(id);
53
+ const setCollapsedIds = useStore(
54
+ (state) => state.sampleActions.setCollapsedIds,
55
+ );
56
+
57
+ // Clear the collapsed ids when the component unmounts
58
+ useEffect(() => {
59
+ return () => {
60
+ clearIds();
61
+ };
62
+ }, [clearIds, id]);
63
+
64
+ // Tree-ify the record (creates a flat lsit of items with depth property)
65
+ const items = useMemo(() => {
66
+ return toTreeItems(
67
+ record,
68
+ collapsedIds || {},
69
+ processStore ? [resolveStoreKeys] : [],
70
+ );
71
+ }, [record, collapsedIds, processStore]);
72
+
73
+ // If collapsedIds is not set, we need to set it to the default state
74
+ useEffect(() => {
75
+ if (collapsedIds) {
76
+ return;
77
+ }
78
+
79
+ const defaultCollapsedIds = items.reduce((prev, item) => {
80
+ if (item.depth >= defaultExpandLevel && item.hasChildren) {
81
+ return {
82
+ ...prev,
83
+ [item.id]: true,
84
+ };
85
+ }
86
+ return prev;
87
+ }, {});
88
+ setCollapsedIds(id, defaultCollapsedIds);
89
+ }, [collapsedIds, items]);
90
+
91
+ // Keyboard handling for tree
92
+ const keyUpHandler = useCallback(
93
+ (itemId: string, index: number) => {
94
+ return (event: KeyboardEvent) => {
95
+ switch (event.key) {
96
+ case "Enter":
97
+ event.preventDefault();
98
+ event.stopPropagation();
99
+ setCollapsed(itemId, !collapsedIds?.[id]);
100
+ break;
101
+ case "ArrowDown": {
102
+ event.preventDefault();
103
+ event.stopPropagation();
104
+ // focus next
105
+ if (index === items.length - 1) {
106
+ return;
107
+ }
108
+ const treeRoot = document.getElementById(id);
109
+ const nextEl = treeRoot?.querySelector(
110
+ `.${kRecordTreeKey}[data-index="${index + 1}"]`,
111
+ );
112
+ if (nextEl) {
113
+ (nextEl as HTMLElement).focus();
114
+ }
115
+ break;
116
+ }
117
+ case "ArrowUp": {
118
+ event.preventDefault();
119
+ event.stopPropagation();
120
+ // focus previous
121
+ if (index === 0) {
122
+ return;
123
+ }
124
+ const treeRoot = document.getElementById(id);
125
+ const prevEl = treeRoot?.querySelector(
126
+ `.${kRecordTreeKey}[data-index="${index - 1}"]`,
127
+ );
128
+ if (prevEl) {
129
+ (prevEl as HTMLElement).focus();
130
+ }
131
+ break;
132
+ }
133
+ case "ArrowRight":
134
+ event.preventDefault();
135
+ event.stopPropagation();
136
+ setCollapsed(itemId, false);
137
+ break;
138
+ case "ArrowLeft":
139
+ event.preventDefault();
140
+ event.stopPropagation();
141
+ setCollapsed(itemId, true);
142
+ break;
143
+ }
144
+ };
145
+ },
146
+ [collapsedIds, items],
147
+ );
148
+
149
+ const renderRow = (index: number) => {
150
+ const item = items[index];
151
+
152
+ return (
153
+ <div
154
+ key={item.id}
155
+ className={clsx(styles.keyPairContainer, "text-size-small")}
156
+ style={{
157
+ paddingLeft: `${item.depth * 20}px`,
158
+ }}
159
+ >
160
+ <div
161
+ data-index={index}
162
+ className={clsx(
163
+ kRecordTreeKey,
164
+ styles.key,
165
+ "font-monospace",
166
+ "text-style-secondary",
167
+ )}
168
+ onKeyUp={keyUpHandler(item.id, index)}
169
+ tabIndex={0}
170
+ onClick={() => {
171
+ setCollapsed(item.id, !collapsedIds?.[item.id]);
172
+ }}
173
+ >
174
+ <div>
175
+ {item.hasChildren ? (
176
+ <pre className={clsx(styles.pre)}>
177
+ <i
178
+ className={clsx(
179
+ collapsedIds && collapsedIds[item.id]
180
+ ? ApplicationIcons.tree.closed
181
+ : ApplicationIcons.tree.open,
182
+ styles.treeIcon,
183
+ )}
184
+ />
185
+ </pre>
186
+ ) : undefined}
187
+ </div>
188
+ <pre className={clsx(styles.pre)}>{item.key}:</pre>
189
+ </div>
190
+ <div>
191
+ {item.value !== null &&
192
+ (!item.hasChildren || collapsedIds?.[item.id]) ? (
193
+ <RenderedContent
194
+ id={`${id}-value-${item.id}`}
195
+ entry={{
196
+ name: item.key,
197
+ value: item.value,
198
+ }}
199
+ renderOptions={{ renderString: "pre" }}
200
+ />
201
+ ) : undefined}
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ return (
208
+ <Virtuoso
209
+ ref={listHandle}
210
+ customScrollParent={scrollRef?.current ? scrollRef.current : undefined}
211
+ id={id}
212
+ style={{ width: "100%", height: "100%" }}
213
+ data={items}
214
+ defaultItemHeight={50}
215
+ itemContent={renderRow}
216
+ atBottomThreshold={30}
217
+ increaseViewportBy={{ top: 300, bottom: 300 }}
218
+ overscan={{
219
+ main: 10,
220
+ reverse: 10,
221
+ }}
222
+ className={clsx(className, "samples-list")}
223
+ skipAnimationFrameInResizeObserver={true}
224
+ restoreStateFrom={getRestoreState()}
225
+ tabIndex={0}
226
+ />
227
+ );
228
+ };
229
+
230
+ interface MetadataItem {
231
+ id: string;
232
+ key: string;
233
+ value: string | number | boolean | null;
234
+ depth: number;
235
+ hasChildren: boolean;
236
+ }
237
+
238
+ export const toTreeItems = (
239
+ record: Record<string, unknown>,
240
+ collapsedIds: Record<string, boolean>,
241
+ recordProcessors: RecordProcessor[] = [],
242
+ currentDepth = 0,
243
+ currentPath: string[] = [],
244
+ ): MetadataItem[] => {
245
+ if (!record) {
246
+ return [];
247
+ }
248
+
249
+ // Apply any record processors
250
+ if (recordProcessors.length > 0) {
251
+ for (const processor of recordProcessors) {
252
+ record = processor(record);
253
+ }
254
+ }
255
+
256
+ const result: MetadataItem[] = [];
257
+
258
+ Object.entries(record).forEach(([key, value], index) => {
259
+ const itemSegment = index.toString();
260
+ result.push(
261
+ ...processNodeRecursive(
262
+ key,
263
+ value,
264
+ currentDepth,
265
+ currentPath,
266
+ itemSegment,
267
+ collapsedIds,
268
+ ),
269
+ );
270
+ });
271
+
272
+ return result;
273
+ };
274
+
275
+ const processNodeRecursive = (
276
+ key: string,
277
+ value: unknown,
278
+ depth: number,
279
+ parentPath: string[],
280
+ thisPath: string,
281
+ collapsedIds: Record<string, boolean>,
282
+ ): MetadataItem[] => {
283
+ const items: MetadataItem[] = [];
284
+ const currentItemPath = [...parentPath, thisPath];
285
+ const id = `${depth}.${currentItemPath.join(".")}`;
286
+
287
+ if (isPrimitiveOrNull(value)) {
288
+ items.push({
289
+ id,
290
+ key,
291
+ value: value === undefined ? null : value,
292
+ depth,
293
+ hasChildren: false,
294
+ });
295
+ return items;
296
+ }
297
+
298
+ // For non-primitives (objects, arrays, functions, etc.)
299
+ let displayValue: string | number | boolean | null = null;
300
+ let processChildren = false;
301
+
302
+ if (Array.isArray(value)) {
303
+ processChildren = true;
304
+ displayValue = `Array(${value.length})`;
305
+ } else if (typeof value === "object" && value !== null) {
306
+ processChildren = true;
307
+ displayValue = `Object(${Object.keys(value).length})`;
308
+ } else {
309
+ // Other types like functions, symbols. These are treated as leaf nodes.
310
+ displayValue = String(value);
311
+ processChildren = false;
312
+ }
313
+
314
+ // Add the item
315
+ items.push({ id, key, value: displayValue, depth, hasChildren: true });
316
+
317
+ // Process children
318
+ if (processChildren && !collapsedIds[id]) {
319
+ const childDepth = depth + 1;
320
+ if (Array.isArray(value)) {
321
+ if (value.length > 0) {
322
+ value.forEach((element, index) => {
323
+ const elementKey = `[${index}]`;
324
+ const elementIdentifier = `[${index}]`;
325
+ items.push(
326
+ ...processNodeRecursive(
327
+ elementKey,
328
+ element,
329
+ childDepth,
330
+ currentItemPath,
331
+ elementIdentifier,
332
+ collapsedIds,
333
+ ),
334
+ );
335
+ });
336
+ }
337
+ } else if (typeof value === "object" && value !== null) {
338
+ // Process object properties
339
+ Object.entries(value as Record<string, unknown>).forEach(
340
+ ([childKey, childValue], index) => {
341
+ const childIdentifier = index.toString();
342
+ items.push(
343
+ ...processNodeRecursive(
344
+ childKey,
345
+ childValue,
346
+ childDepth,
347
+ currentItemPath,
348
+ childIdentifier,
349
+ collapsedIds,
350
+ ),
351
+ );
352
+ },
353
+ );
354
+ }
355
+ }
356
+
357
+ return items;
358
+ };
359
+
360
+ const isPrimitiveOrNull = (
361
+ value: unknown,
362
+ ): value is string | number | boolean | null | undefined => {
363
+ return (
364
+ value === null ||
365
+ value === undefined ||
366
+ typeof value === "string" ||
367
+ typeof value === "number" ||
368
+ typeof value === "boolean"
369
+ );
370
+ };
@@ -9,4 +9,9 @@
9
9
 
10
10
  .preWrap {
11
11
  white-space: pre-wrap;
12
+ word-break: break-word;
13
+ }
14
+
15
+ .preCompact {
16
+ margin-bottom: 0;
12
17
  }
@@ -11,11 +11,12 @@ import { FC, Fragment, isValidElement, JSX, ReactNode } from "react";
11
11
  import JSONPanel from "../../components/JsonPanel";
12
12
  import { isJson } from "../../utils/json";
13
13
  import styles from "./RenderedContent.module.css";
14
- import { Buckets, ContentRenderer } from "./types";
14
+ import { Buckets, ContentRenderer, RenderOptions } from "./types";
15
15
 
16
16
  interface RenderedContentProps {
17
17
  id: string;
18
18
  entry: { name: string; value: unknown };
19
+ renderOptions?: RenderOptions;
19
20
  }
20
21
 
21
22
  /**
@@ -24,6 +25,7 @@ interface RenderedContentProps {
24
25
  export const RenderedContent: FC<RenderedContentProps> = ({
25
26
  id,
26
27
  entry,
28
+ renderOptions = { renderString: "markdown" },
27
29
  }): JSX.Element => {
28
30
  // Explicitly specify return type
29
31
  if (entry.value === null) {
@@ -42,7 +44,7 @@ export const RenderedContent: FC<RenderedContentProps> = ({
42
44
  });
43
45
 
44
46
  if (renderer) {
45
- const { rendered } = renderer.render(id, entry);
47
+ const { rendered } = renderer.render(id, entry, renderOptions);
46
48
  if (rendered !== undefined && isValidElement(rendered)) {
47
49
  return rendered;
48
50
  }
@@ -75,7 +77,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
75
77
  typeof entry.value === "string" && entry.value.indexOf("\u001b") > -1
76
78
  );
77
79
  },
78
- render: (_id, entry) => {
80
+ render: (_id, entry, _options) => {
79
81
  return {
80
82
  rendered: <ANSIDisplay output={entry.value} />,
81
83
  };
@@ -90,17 +92,18 @@ const contentRenderers: Record<string, ContentRenderer> = {
90
92
  }
91
93
  return false;
92
94
  },
93
- render: (_id, entry) => {
95
+ render: (_id, entry, _options) => {
94
96
  const obj = JSON5.parse(entry.value);
95
97
  return { rendered: <JSONPanel data={obj as Record<string, unknown>} /> };
96
98
  },
97
99
  },
100
+
98
101
  Model: {
99
102
  bucket: Buckets.intermediate,
100
103
  canRender: (entry) => {
101
104
  return typeof entry.value === "object" && entry.value._model;
102
105
  },
103
- render: (_id, entry) => {
106
+ render: (_id, entry, _options) => {
104
107
  return {
105
108
  rendered: (
106
109
  <Fragment>
@@ -115,9 +118,9 @@ const contentRenderers: Record<string, ContentRenderer> = {
115
118
  canRender: (entry) => {
116
119
  return typeof entry.value === "boolean";
117
120
  },
118
- render: (id, entry) => {
121
+ render: (id, entry, options) => {
119
122
  entry.value = entry.value.toString();
120
- return contentRenderers.String.render(id, entry);
123
+ return contentRenderers.String.render(id, entry, options);
121
124
  },
122
125
  },
123
126
  Number: {
@@ -125,9 +128,9 @@ const contentRenderers: Record<string, ContentRenderer> = {
125
128
  canRender: (entry) => {
126
129
  return typeof entry.value === "number";
127
130
  },
128
- render: (id, entry) => {
131
+ render: (id, entry, options) => {
129
132
  entry.value = formatNumber(entry.value);
130
- return contentRenderers.String.render(id, entry);
133
+ return contentRenderers.String.render(id, entry, options);
131
134
  },
132
135
  },
133
136
  String: {
@@ -135,11 +138,21 @@ const contentRenderers: Record<string, ContentRenderer> = {
135
138
  canRender: (entry) => {
136
139
  return typeof entry.value === "string";
137
140
  },
138
- render: (_id, entry) => {
141
+ render: (_id, entry, options) => {
139
142
  const rendered = entry.value.trim();
140
- return {
141
- rendered,
142
- };
143
+ if (options.renderString === "markdown") {
144
+ return {
145
+ rendered: rendered,
146
+ };
147
+ } else {
148
+ return {
149
+ rendered: (
150
+ <pre className={clsx(styles.preWrap, styles.preCompact)}>
151
+ {rendered}
152
+ </pre>
153
+ ),
154
+ };
155
+ }
143
156
  },
144
157
  },
145
158
  Array: {
@@ -162,7 +175,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
162
175
  return false;
163
176
  }
164
177
  },
165
- render: (id, entry) => {
178
+ render: (id, entry, _options) => {
166
179
  const arrayMap: Record<string, unknown> = {};
167
180
  entry.value.forEach((e: unknown, index: number) => {
168
181
  arrayMap[`[${index}]`] = e;
@@ -186,7 +199,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
186
199
  canRender: (entry) => {
187
200
  return typeof entry.value === "object" && entry.name === "web_search";
188
201
  },
189
- render: (_id, entry) => {
202
+ render: (_id, entry, _options) => {
190
203
  const results: ReactNode[] = [];
191
204
  results.push(
192
205
  <div className={styles.query}>
@@ -219,7 +232,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
219
232
  typeof entry.value === "string" && entry.name?.startsWith("web_browser")
220
233
  );
221
234
  },
222
- render: (_id, entry) => {
235
+ render: (_id, entry, _options) => {
223
236
  return {
224
237
  rendered: <pre className={styles.preWrap}>{entry.value}</pre>,
225
238
  };
@@ -230,7 +243,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
230
243
  canRender: (entry) => {
231
244
  return typeof entry.value === "object" && entry.value._html;
232
245
  },
233
- render: (_id, entry) => {
246
+ render: (_id, entry, _options) => {
234
247
  return {
235
248
  rendered: entry.value._html,
236
249
  };
@@ -243,7 +256,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
243
256
  typeof entry.value === "string" && entry.value.startsWith("data:image/")
244
257
  );
245
258
  },
246
- render: (_id, entry) => {
259
+ render: (_id, entry, _options) => {
247
260
  return {
248
261
  rendered: <img src={entry.value} />,
249
262
  };
@@ -254,7 +267,7 @@ const contentRenderers: Record<string, ContentRenderer> = {
254
267
  canRender: (entry) => {
255
268
  return typeof entry.value === "object";
256
269
  },
257
- render: (id, entry) => {
270
+ render: (id, entry, _options) => {
258
271
  return {
259
272
  rendered: (
260
273
  <MetaDataView