inspect-ai 0.3.81__py3-none-any.whl → 0.3.83__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 (297) hide show
  1. inspect_ai/__init__.py +2 -1
  2. inspect_ai/_cli/eval.py +35 -2
  3. inspect_ai/_cli/util.py +44 -1
  4. inspect_ai/_display/core/config.py +1 -1
  5. inspect_ai/_display/core/display.py +13 -4
  6. inspect_ai/_display/core/results.py +1 -1
  7. inspect_ai/_display/textual/app.py +14 -3
  8. inspect_ai/_display/textual/display.py +4 -0
  9. inspect_ai/_display/textual/widgets/samples.py +9 -3
  10. inspect_ai/_display/textual/widgets/task_detail.py +8 -8
  11. inspect_ai/_display/textual/widgets/tasks.py +17 -1
  12. inspect_ai/_display/textual/widgets/vscode.py +44 -0
  13. inspect_ai/_eval/eval.py +74 -25
  14. inspect_ai/_eval/evalset.py +22 -18
  15. inspect_ai/_eval/loader.py +34 -11
  16. inspect_ai/_eval/run.py +13 -15
  17. inspect_ai/_eval/score.py +13 -3
  18. inspect_ai/_eval/task/generate.py +8 -9
  19. inspect_ai/_eval/task/log.py +55 -6
  20. inspect_ai/_eval/task/run.py +51 -10
  21. inspect_ai/_eval/task/task.py +23 -9
  22. inspect_ai/_util/constants.py +2 -0
  23. inspect_ai/_util/file.py +30 -1
  24. inspect_ai/_util/json.py +37 -1
  25. inspect_ai/_util/registry.py +1 -0
  26. inspect_ai/_util/vscode.py +37 -0
  27. inspect_ai/_view/server.py +113 -1
  28. inspect_ai/_view/www/App.css +7 -1
  29. inspect_ai/_view/www/dist/assets/index.css +813 -415
  30. inspect_ai/_view/www/dist/assets/index.js +54475 -32003
  31. inspect_ai/_view/www/eslint.config.mjs +1 -1
  32. inspect_ai/_view/www/log-schema.json +137 -31
  33. inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
  34. inspect_ai/_view/www/package.json +11 -2
  35. inspect_ai/_view/www/src/App.tsx +161 -853
  36. inspect_ai/_view/www/src/api/api-browser.ts +176 -5
  37. inspect_ai/_view/www/src/api/api-vscode.ts +75 -1
  38. inspect_ai/_view/www/src/api/client-api.ts +66 -10
  39. inspect_ai/_view/www/src/api/jsonrpc.ts +2 -0
  40. inspect_ai/_view/www/src/api/types.ts +107 -2
  41. inspect_ai/_view/www/src/appearance/icons.ts +2 -0
  42. inspect_ai/_view/www/src/components/AsciinemaPlayer.tsx +3 -3
  43. inspect_ai/_view/www/src/components/Card.tsx +6 -4
  44. inspect_ai/_view/www/src/components/DownloadPanel.tsx +2 -2
  45. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +56 -61
  46. inspect_ai/_view/www/src/components/FindBand.tsx +17 -9
  47. inspect_ai/_view/www/src/components/HumanBaselineView.tsx +1 -1
  48. inspect_ai/_view/www/src/components/JsonPanel.tsx +14 -24
  49. inspect_ai/_view/www/src/components/LargeModal.tsx +2 -35
  50. inspect_ai/_view/www/src/components/LightboxCarousel.tsx +27 -11
  51. inspect_ai/_view/www/src/components/LinkButton.module.css +16 -0
  52. inspect_ai/_view/www/src/components/LinkButton.tsx +33 -0
  53. inspect_ai/_view/www/src/components/LiveVirtualList.module.css +11 -0
  54. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +177 -0
  55. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +116 -26
  56. inspect_ai/_view/www/src/components/MessageBand.tsx +14 -9
  57. inspect_ai/_view/www/src/components/Modal.module.css +38 -0
  58. inspect_ai/_view/www/src/components/Modal.tsx +77 -0
  59. inspect_ai/_view/www/src/components/MorePopOver.tsx +3 -3
  60. inspect_ai/_view/www/src/components/NavPills.tsx +20 -8
  61. inspect_ai/_view/www/src/components/NoContentsPanel.module.css +12 -0
  62. inspect_ai/_view/www/src/components/NoContentsPanel.tsx +20 -0
  63. inspect_ai/_view/www/src/components/ProgressBar.module.css +5 -4
  64. inspect_ai/_view/www/src/components/ProgressBar.tsx +3 -2
  65. inspect_ai/_view/www/src/components/PulsingDots.module.css +81 -0
  66. inspect_ai/_view/www/src/components/PulsingDots.tsx +45 -0
  67. inspect_ai/_view/www/src/components/TabSet.tsx +4 -37
  68. inspect_ai/_view/www/src/components/ToolButton.tsx +3 -4
  69. inspect_ai/_view/www/src/index.tsx +26 -94
  70. inspect_ai/_view/www/src/logfile/remoteLogFile.ts +9 -1
  71. inspect_ai/_view/www/src/logfile/remoteZipFile.ts +30 -4
  72. inspect_ai/_view/www/src/metadata/RenderedContent.tsx +4 -6
  73. inspect_ai/_view/www/src/plan/DetailStep.module.css +4 -0
  74. inspect_ai/_view/www/src/plan/DetailStep.tsx +6 -3
  75. inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +1 -1
  76. inspect_ai/_view/www/src/plan/SolverDetailView.module.css +2 -1
  77. inspect_ai/_view/www/src/samples/InlineSampleDisplay.module.css +9 -1
  78. inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +74 -28
  79. inspect_ai/_view/www/src/samples/SampleDialog.tsx +58 -22
  80. inspect_ai/_view/www/src/samples/SampleDisplay.module.css +4 -0
  81. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +135 -104
  82. inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +10 -0
  83. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +83 -36
  84. inspect_ai/_view/www/src/samples/SamplesTools.tsx +35 -30
  85. inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +2 -1
  86. inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +1 -1
  87. inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +45 -53
  88. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +6 -1
  89. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +5 -0
  90. inspect_ai/_view/www/src/samples/chat/messages.ts +36 -0
  91. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.module.css +3 -0
  92. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +11 -1
  93. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +22 -46
  94. inspect_ai/_view/www/src/samples/descriptor/samplesDescriptor.tsx +34 -20
  95. inspect_ai/_view/www/src/samples/descriptor/score/BooleanScoreDescriptor.module.css +3 -3
  96. inspect_ai/_view/www/src/samples/descriptor/score/BooleanScoreDescriptor.tsx +1 -1
  97. inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.module.css +4 -4
  98. inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.tsx +10 -10
  99. inspect_ai/_view/www/src/samples/descriptor/types.ts +6 -5
  100. inspect_ai/_view/www/src/samples/list/SampleFooter.module.css +22 -3
  101. inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +27 -2
  102. inspect_ai/_view/www/src/samples/list/SampleList.tsx +122 -85
  103. inspect_ai/_view/www/src/samples/list/SampleRow.module.css +6 -0
  104. inspect_ai/_view/www/src/samples/list/SampleRow.tsx +28 -15
  105. inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +29 -18
  106. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +28 -28
  107. inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +19 -9
  108. inspect_ai/_view/www/src/samples/sampleDataAdapter.ts +33 -0
  109. inspect_ai/_view/www/src/samples/sampleLimit.ts +2 -2
  110. inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +12 -27
  111. inspect_ai/_view/www/src/samples/scores/SampleScoresGrid.module.css +38 -0
  112. inspect_ai/_view/www/src/samples/scores/SampleScoresGrid.tsx +118 -0
  113. inspect_ai/_view/www/src/samples/scores/{SampleScoreView.module.css → SampleScoresView.module.css} +10 -1
  114. inspect_ai/_view/www/src/samples/scores/SampleScoresView.tsx +78 -0
  115. inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +0 -13
  116. inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +0 -13
  117. inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +0 -13
  118. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +4 -0
  119. inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +10 -24
  120. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +0 -13
  121. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +4 -22
  122. inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +15 -24
  123. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +0 -13
  124. inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +6 -28
  125. inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +24 -34
  126. inspect_ai/_view/www/src/samples/transcript/ToolEventView.module.css +4 -0
  127. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +33 -17
  128. inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +197 -338
  129. inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.module.css +16 -0
  130. inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.tsx +44 -0
  131. inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +7 -4
  132. inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +81 -60
  133. inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.module.css +23 -0
  134. inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.tsx +27 -0
  135. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +29 -1
  136. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +102 -72
  137. inspect_ai/_view/www/src/scoring/utils.ts +87 -0
  138. inspect_ai/_view/www/src/state/appSlice.ts +244 -0
  139. inspect_ai/_view/www/src/state/hooks.ts +399 -0
  140. inspect_ai/_view/www/src/state/logPolling.ts +200 -0
  141. inspect_ai/_view/www/src/state/logSlice.ts +224 -0
  142. inspect_ai/_view/www/src/state/logsPolling.ts +118 -0
  143. inspect_ai/_view/www/src/state/logsSlice.ts +181 -0
  144. inspect_ai/_view/www/src/state/samplePolling.ts +314 -0
  145. inspect_ai/_view/www/src/state/sampleSlice.ts +140 -0
  146. inspect_ai/_view/www/src/state/sampleUtils.ts +21 -0
  147. inspect_ai/_view/www/src/state/scrolling.ts +206 -0
  148. inspect_ai/_view/www/src/state/store.ts +168 -0
  149. inspect_ai/_view/www/src/state/store_filter.ts +84 -0
  150. inspect_ai/_view/www/src/state/utils.ts +23 -0
  151. inspect_ai/_view/www/src/storage/index.ts +26 -0
  152. inspect_ai/_view/www/src/types/log.d.ts +36 -26
  153. inspect_ai/_view/www/src/types/markdown-it-katex.d.ts +21 -0
  154. inspect_ai/_view/www/src/types.ts +94 -32
  155. inspect_ai/_view/www/src/utils/attachments.ts +58 -23
  156. inspect_ai/_view/www/src/utils/json-worker.ts +79 -12
  157. inspect_ai/_view/www/src/utils/logger.ts +52 -0
  158. inspect_ai/_view/www/src/utils/polling.ts +100 -0
  159. inspect_ai/_view/www/src/utils/react.ts +30 -0
  160. inspect_ai/_view/www/src/utils/vscode.ts +1 -1
  161. inspect_ai/_view/www/src/workspace/WorkSpace.tsx +184 -217
  162. inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +11 -53
  163. inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +8 -18
  164. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.module.css +1 -0
  165. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +40 -22
  166. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.module.css +16 -1
  167. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +159 -103
  168. inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.module.css +32 -0
  169. inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.tsx +32 -0
  170. inspect_ai/_view/www/src/workspace/navbar/ScoreGrid.module.css +35 -0
  171. inspect_ai/_view/www/src/workspace/navbar/ScoreGrid.tsx +117 -0
  172. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +12 -14
  173. inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +6 -2
  174. inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +4 -4
  175. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.module.css +3 -2
  176. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +28 -13
  177. inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +5 -10
  178. inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +4 -4
  179. inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.module.css +22 -0
  180. inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.tsx +19 -0
  181. inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +128 -115
  182. inspect_ai/_view/www/src/workspace/tabs/grouping.ts +37 -5
  183. inspect_ai/_view/www/src/workspace/tabs/types.ts +4 -0
  184. inspect_ai/_view/www/src/workspace/types.ts +4 -3
  185. inspect_ai/_view/www/src/workspace/utils.ts +4 -4
  186. inspect_ai/_view/www/vite.config.js +6 -0
  187. inspect_ai/_view/www/yarn.lock +464 -355
  188. inspect_ai/agent/__init__.py +36 -0
  189. inspect_ai/agent/_agent.py +268 -0
  190. inspect_ai/agent/_as_solver.py +72 -0
  191. inspect_ai/agent/_as_tool.py +122 -0
  192. inspect_ai/{solver → agent}/_bridge/bridge.py +23 -37
  193. inspect_ai/{solver → agent}/_bridge/patch.py +9 -8
  194. inspect_ai/agent/_filter.py +46 -0
  195. inspect_ai/agent/_handoff.py +93 -0
  196. inspect_ai/{solver/_human_agent → agent/_human}/agent.py +11 -12
  197. inspect_ai/{solver/_human_agent → agent/_human}/commands/__init__.py +2 -3
  198. inspect_ai/{solver/_human_agent → agent/_human}/commands/clock.py +3 -1
  199. inspect_ai/{solver/_human_agent → agent/_human}/commands/score.py +5 -5
  200. inspect_ai/{solver/_human_agent → agent/_human}/install.py +6 -3
  201. inspect_ai/{solver/_human_agent → agent/_human}/service.py +7 -3
  202. inspect_ai/{solver/_human_agent → agent/_human}/state.py +5 -5
  203. inspect_ai/agent/_react.py +241 -0
  204. inspect_ai/agent/_run.py +36 -0
  205. inspect_ai/agent/_types.py +81 -0
  206. inspect_ai/log/_condense.py +26 -0
  207. inspect_ai/log/_log.py +17 -5
  208. inspect_ai/log/_recorders/buffer/__init__.py +14 -0
  209. inspect_ai/log/_recorders/buffer/buffer.py +30 -0
  210. inspect_ai/log/_recorders/buffer/database.py +685 -0
  211. inspect_ai/log/_recorders/buffer/filestore.py +259 -0
  212. inspect_ai/log/_recorders/buffer/types.py +84 -0
  213. inspect_ai/log/_recorders/eval.py +2 -11
  214. inspect_ai/log/_recorders/types.py +30 -0
  215. inspect_ai/log/_transcript.py +32 -2
  216. inspect_ai/model/__init__.py +7 -1
  217. inspect_ai/model/_call_tools.py +257 -52
  218. inspect_ai/model/_chat_message.py +7 -4
  219. inspect_ai/model/_conversation.py +13 -62
  220. inspect_ai/model/_display.py +85 -0
  221. inspect_ai/model/_generate_config.py +2 -2
  222. inspect_ai/model/_model.py +114 -14
  223. inspect_ai/model/_model_output.py +14 -9
  224. inspect_ai/model/_openai.py +16 -4
  225. inspect_ai/model/_openai_computer_use.py +162 -0
  226. inspect_ai/model/_openai_responses.py +319 -165
  227. inspect_ai/model/_providers/anthropic.py +20 -21
  228. inspect_ai/model/_providers/azureai.py +24 -13
  229. inspect_ai/model/_providers/bedrock.py +1 -7
  230. inspect_ai/model/_providers/cloudflare.py +3 -3
  231. inspect_ai/model/_providers/goodfire.py +2 -6
  232. inspect_ai/model/_providers/google.py +11 -10
  233. inspect_ai/model/_providers/groq.py +6 -3
  234. inspect_ai/model/_providers/hf.py +7 -3
  235. inspect_ai/model/_providers/mistral.py +7 -10
  236. inspect_ai/model/_providers/openai.py +47 -17
  237. inspect_ai/model/_providers/openai_o1.py +11 -4
  238. inspect_ai/model/_providers/openai_responses.py +12 -14
  239. inspect_ai/model/_providers/providers.py +2 -2
  240. inspect_ai/model/_providers/together.py +12 -2
  241. inspect_ai/model/_providers/util/chatapi.py +7 -2
  242. inspect_ai/model/_providers/util/hf_handler.py +4 -2
  243. inspect_ai/model/_providers/util/llama31.py +4 -2
  244. inspect_ai/model/_providers/vertex.py +11 -9
  245. inspect_ai/model/_providers/vllm.py +4 -4
  246. inspect_ai/scorer/__init__.py +2 -0
  247. inspect_ai/scorer/_metrics/__init__.py +2 -0
  248. inspect_ai/scorer/_metrics/grouped.py +84 -0
  249. inspect_ai/scorer/_score.py +26 -6
  250. inspect_ai/solver/__init__.py +2 -2
  251. inspect_ai/solver/_basic_agent.py +22 -9
  252. inspect_ai/solver/_bridge.py +31 -0
  253. inspect_ai/solver/_chain.py +20 -12
  254. inspect_ai/solver/_fork.py +5 -1
  255. inspect_ai/solver/_human_agent.py +52 -0
  256. inspect_ai/solver/_prompt.py +3 -1
  257. inspect_ai/solver/_run.py +59 -0
  258. inspect_ai/solver/_solver.py +14 -4
  259. inspect_ai/solver/_task_state.py +5 -3
  260. inspect_ai/tool/_tool_call.py +15 -8
  261. inspect_ai/tool/_tool_def.py +17 -12
  262. inspect_ai/tool/_tool_support_helpers.py +4 -4
  263. inspect_ai/tool/_tool_with.py +14 -11
  264. inspect_ai/tool/_tools/_bash_session.py +11 -2
  265. inspect_ai/tool/_tools/_computer/_common.py +18 -2
  266. inspect_ai/tool/_tools/_computer/_computer.py +18 -2
  267. inspect_ai/tool/_tools/_computer/_resources/tool/_constants.py +2 -0
  268. inspect_ai/tool/_tools/_computer/_resources/tool/_x11_client.py +17 -0
  269. inspect_ai/tool/_tools/_think.py +1 -1
  270. inspect_ai/tool/_tools/_web_browser/_web_browser.py +103 -62
  271. inspect_ai/util/__init__.py +2 -0
  272. inspect_ai/util/_anyio.py +27 -0
  273. inspect_ai/util/_sandbox/__init__.py +2 -1
  274. inspect_ai/util/_sandbox/context.py +32 -7
  275. inspect_ai/util/_sandbox/docker/cleanup.py +4 -0
  276. inspect_ai/util/_sandbox/docker/compose.py +2 -2
  277. inspect_ai/util/_sandbox/docker/docker.py +12 -1
  278. inspect_ai/util/_store_model.py +30 -7
  279. inspect_ai/util/_subprocess.py +13 -3
  280. inspect_ai/util/_subtask.py +1 -0
  281. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/METADATA +1 -1
  282. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/RECORD +295 -229
  283. inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +0 -169
  284. inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +0 -22
  285. /inspect_ai/{solver → agent}/_bridge/__init__.py +0 -0
  286. /inspect_ai/{solver/_human_agent → agent/_human}/__init__.py +0 -0
  287. /inspect_ai/{solver/_human_agent → agent/_human}/commands/command.py +0 -0
  288. /inspect_ai/{solver/_human_agent → agent/_human}/commands/instructions.py +0 -0
  289. /inspect_ai/{solver/_human_agent → agent/_human}/commands/note.py +0 -0
  290. /inspect_ai/{solver/_human_agent → agent/_human}/commands/status.py +0 -0
  291. /inspect_ai/{solver/_human_agent → agent/_human}/commands/submit.py +0 -0
  292. /inspect_ai/{solver/_human_agent → agent/_human}/panel.py +0 -0
  293. /inspect_ai/{solver/_human_agent → agent/_human}/view.py +0 -0
  294. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/WHEEL +0 -0
  295. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/entry_points.txt +0 -0
  296. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/licenses/LICENSE +0 -0
  297. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.83.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
1
+ import { RefObject, useCallback, useEffect, useRef } from "react";
2
+ import { StateCallback, StateSnapshot, VirtuosoHandle } from "react-virtuoso";
3
+ import { createLogger } from "../utils/logger";
4
+ import { debounce } from "../utils/sync";
5
+ import { useStore } from "./store";
6
+
7
+ const log = createLogger("scrolling");
8
+
9
+ export function useStatefulScrollPosition<
10
+ T extends HTMLElement = HTMLDivElement,
11
+ >(
12
+ elementRef: RefObject<T | null>,
13
+ elementKey: string,
14
+ delay = 500,
15
+ scrollable = true,
16
+ ) {
17
+ const getScrollPosition = useStore(
18
+ (state) => state.appActions.getScrollPosition,
19
+ );
20
+ const setScrollPosition = useStore(
21
+ (state) => state.appActions.setScrollPosition,
22
+ );
23
+
24
+ // Create debounced scroll handler
25
+ const handleScroll = useCallback(
26
+ debounce((e: Event) => {
27
+ const target = e.target as HTMLElement;
28
+ const position = target.scrollTop;
29
+ log.debug(`Storing scroll position`, elementKey, position);
30
+ setScrollPosition(elementKey, position);
31
+ }, delay),
32
+ [elementKey, setScrollPosition, delay],
33
+ );
34
+
35
+ // Function to manually restore scroll position
36
+ const restoreScrollPosition = useCallback(() => {
37
+ const element = elementRef.current;
38
+ const savedPosition = getScrollPosition(elementKey);
39
+
40
+ if (element && savedPosition !== undefined) {
41
+ requestAnimationFrame(() => {
42
+ element.scrollTop = savedPosition;
43
+
44
+ requestAnimationFrame(() => {
45
+ if (element.scrollTop !== savedPosition) {
46
+ element.scrollTop = savedPosition;
47
+ }
48
+ });
49
+ });
50
+ }
51
+ }, [elementKey, getScrollPosition, elementRef]);
52
+
53
+ // Set up scroll listener and restore position on mount
54
+ useEffect(() => {
55
+ const element = elementRef.current;
56
+ if (!element || !scrollable) {
57
+ return;
58
+ }
59
+ log.debug(`Restore Scroll Hook`, elementKey);
60
+
61
+ // Restore scroll position on mount
62
+ const savedPosition = getScrollPosition(elementKey);
63
+ if (savedPosition !== undefined) {
64
+ log.debug(`Restoring scroll position`, savedPosition);
65
+ // Ensure the element has fully rendered
66
+ requestAnimationFrame(() => {
67
+ if (element.scrollTop !== savedPosition) {
68
+ element.scrollTop = savedPosition;
69
+ }
70
+ });
71
+ }
72
+
73
+ // Set up scroll listener
74
+ if (element.addEventListener) {
75
+ element.addEventListener("scroll", handleScroll);
76
+ } else {
77
+ log.warn("Element has no way to add event listener", element);
78
+ }
79
+
80
+ // Clean up
81
+ return () => {
82
+ if (element.removeEventListener) {
83
+ element.removeEventListener("scroll", handleScroll);
84
+ } else {
85
+ log.warn("Element has no way to remove event listener", element);
86
+ }
87
+ };
88
+ }, [elementKey, elementRef, handleScroll]);
89
+
90
+ return { restoreScrollPosition };
91
+ }
92
+
93
+ // Define a type for the debounced function that includes the cancel method
94
+ type DebouncedFunction<T extends (...args: any[]) => any> = T & {
95
+ cancel: () => void;
96
+ flush: () => void;
97
+ };
98
+
99
+ export const useVirtuosoState = (
100
+ virtuosoRef: RefObject<VirtuosoHandle | null>,
101
+ elementKey: string,
102
+ delay = 1000,
103
+ ) => {
104
+ // Use useCallback to stabilize the selectors
105
+ const restoreState = useStore(
106
+ useCallback((state) => state.app.listPositions[elementKey], [elementKey]),
107
+ );
108
+
109
+ const setListPosition = useStore(
110
+ useCallback((state) => state.appActions.setListPosition, []),
111
+ );
112
+
113
+ const clearListPosition = useStore(
114
+ useCallback((state) => state.appActions.clearListPosition, []),
115
+ );
116
+
117
+ // Properly type the debounced function ref
118
+ const debouncedFnRef = useRef<DebouncedFunction<
119
+ (isScrolling: boolean) => void
120
+ > | null>(null);
121
+
122
+ // Create the state change handler
123
+ const handleStateChange: StateCallback = useCallback(
124
+ (state: StateSnapshot) => {
125
+ log.debug(`Storing list state: [${elementKey}]`, state);
126
+ setListPosition(elementKey, state);
127
+ },
128
+ [elementKey, setListPosition],
129
+ );
130
+
131
+ // Setup the debounced function once
132
+ useEffect(() => {
133
+ debouncedFnRef.current = debounce((isScrolling: boolean) => {
134
+ log.debug("List scroll", isScrolling);
135
+ const element = virtuosoRef.current;
136
+ if (!element) {
137
+ return;
138
+ }
139
+ element.getState(handleStateChange);
140
+ }, delay) as DebouncedFunction<(isScrolling: boolean) => void>;
141
+
142
+ return () => {
143
+ // Clear the stored position when component unmounts
144
+ clearListPosition(elementKey);
145
+ };
146
+ }, [delay, elementKey, handleStateChange, clearListPosition, virtuosoRef]);
147
+
148
+ // Return a stable function reference that uses the ref internally
149
+ const isScrolling = useCallback((scrolling: boolean) => {
150
+ if (!scrolling) {
151
+ return;
152
+ }
153
+
154
+ if (debouncedFnRef.current) {
155
+ debouncedFnRef.current(scrolling);
156
+ }
157
+ }, []);
158
+
159
+ // Use a state to prevent re-rendering just because the restore
160
+ // state changes
161
+ const stateRef = useRef(restoreState);
162
+ useEffect(() => {
163
+ stateRef.current = restoreState;
164
+ }, [restoreState]);
165
+
166
+ const getRestoreState = useCallback(() => stateRef.current, []);
167
+
168
+ return { getRestoreState, isScrolling };
169
+ };
170
+
171
+ export function useRafThrottle<T extends (...args: any[]) => any>(
172
+ callback: T,
173
+ dependencies: any[] = [],
174
+ ): (...args: Parameters<T>) => void {
175
+ const rafRef = useRef<number | null>(null);
176
+ const callbackRef = useRef<T>(callback);
177
+
178
+ // Update the callback ref when the callback changes
179
+ useEffect(() => {
180
+ callbackRef.current = callback;
181
+ }, [callback, ...dependencies]);
182
+
183
+ const throttledCallback = useCallback((...args: Parameters<T>) => {
184
+ // Skip if we already have a frame queued
185
+ if (rafRef.current) {
186
+ return;
187
+ }
188
+
189
+ rafRef.current = requestAnimationFrame(() => {
190
+ callbackRef.current(...args);
191
+ rafRef.current = null;
192
+ });
193
+ }, []);
194
+
195
+ // Clean up any pending animation frame on unmount
196
+ useEffect(() => {
197
+ return () => {
198
+ if (rafRef.current) {
199
+ cancelAnimationFrame(rafRef.current);
200
+ rafRef.current = null;
201
+ }
202
+ };
203
+ }, []);
204
+
205
+ return throttledCallback;
206
+ }
@@ -0,0 +1,168 @@
1
+ import { create, StoreApi, UseBoundStore } from "zustand";
2
+ import { devtools, persist } from "zustand/middleware";
3
+ import { immer } from "zustand/middleware/immer";
4
+ import { Capabilities, ClientAPI, ClientStorage } from "../api/types";
5
+ import { createLogger } from "../utils/logger";
6
+ import { AppSlice, createAppSlice, initializeAppSlice } from "./appSlice";
7
+ import { createLogSlice, initalializeLogSlice, LogSlice } from "./logSlice";
8
+ import { createLogsSlice, initializeLogsSlice, LogsSlice } from "./logsSlice";
9
+ import {
10
+ createSampleSlice,
11
+ initializeSampleSlice,
12
+ SampleSlice,
13
+ } from "./sampleSlice";
14
+ import { filterState } from "./store_filter";
15
+
16
+ const log = createLogger("store");
17
+
18
+ export interface StoreState extends AppSlice, LogsSlice, LogSlice, SampleSlice {
19
+ // The shared api
20
+ api?: ClientAPI | null;
21
+
22
+ // Global actions
23
+ initialize: (api: ClientAPI, capabilities: Capabilities) => void;
24
+ cleanup: () => void;
25
+ }
26
+
27
+ // The data that will actually be persisted
28
+ export type PersistedState = {
29
+ app: AppSlice["app"];
30
+ log: LogSlice["log"];
31
+ logs: LogsSlice["logs"];
32
+ sample: SampleSlice["sample"];
33
+ };
34
+
35
+ // The store implementation (this will be set when the store is initialized)
36
+ let storeImplementation: UseBoundStore<StoreApi<StoreState>> | null = null;
37
+
38
+ // Create a proxy store that forwards calls to the real store once initialized
39
+ export const useStore = ((selector?: any) => {
40
+ if (!storeImplementation) {
41
+ throw new Error(
42
+ "Store accessed before initialization. Call initializeStore first.",
43
+ );
44
+ }
45
+ return selector ? storeImplementation(selector) : storeImplementation();
46
+ }) as UseBoundStore<StoreApi<StoreState>>;
47
+
48
+ // Initialize the store
49
+ export const initializeStore = (
50
+ api: ClientAPI,
51
+ capabilities: Capabilities,
52
+ storage?: ClientStorage,
53
+ ) => {
54
+ // Create the storage implementation
55
+ const storageImplementation = {
56
+ getItem: <T>(name: string): T | null => {
57
+ return storage ? (storage.getItem(name) as T) : null;
58
+ },
59
+ setItem: <T>(name: string, value: T): void => {
60
+ if (storage) {
61
+ storage.setItem(name, value);
62
+ }
63
+ },
64
+ removeItem: (name: string): void => {
65
+ if (storage) {
66
+ storage.removeItem(name);
67
+ }
68
+ },
69
+ };
70
+
71
+ // Create the actual store
72
+ const store = create<StoreState>()(
73
+ devtools(
74
+ persist(
75
+ immer((set, get, store) => {
76
+ const [appSlice, appCleanup] = createAppSlice(
77
+ set as (fn: (state: StoreState) => void) => void,
78
+ get,
79
+ store,
80
+ );
81
+ const [logsSlice, logsCleanup] = createLogsSlice(
82
+ set as (fn: (state: StoreState) => void) => void,
83
+ get,
84
+ store,
85
+ );
86
+ const [logSlice, logCleanup] = createLogSlice(
87
+ set as (fn: (state: StoreState) => void) => void,
88
+ get,
89
+ store,
90
+ );
91
+ const [sampleSlice, sampleCleanup] = createSampleSlice(
92
+ set as (fn: (state: StoreState) => void) => void,
93
+ get,
94
+ store,
95
+ );
96
+
97
+ return {
98
+ // Shared state
99
+ api: null,
100
+
101
+ // Initialize
102
+ initialize: (api, capabilities) => {
103
+ set((state) => {
104
+ state.api = api;
105
+ });
106
+
107
+ // Initialize application slices
108
+ initializeAppSlice(
109
+ set as (fn: (state: StoreState) => void) => void,
110
+ capabilities,
111
+ );
112
+ initializeLogsSlice(
113
+ set as (fn: (state: StoreState) => void) => void,
114
+ );
115
+ initalializeLogSlice(
116
+ set as (fn: (state: StoreState) => void) => void,
117
+ );
118
+ initializeSampleSlice(
119
+ set as (fn: (state: StoreState) => void) => void,
120
+ );
121
+ },
122
+
123
+ // Create the slices and merge them in
124
+ ...appSlice,
125
+ ...logsSlice,
126
+ ...logSlice,
127
+ ...sampleSlice,
128
+
129
+ cleanup: () => {
130
+ appCleanup();
131
+ logsCleanup();
132
+ logCleanup();
133
+ sampleCleanup();
134
+ },
135
+ };
136
+ }),
137
+ {
138
+ name: "app-storage",
139
+ storage: storageImplementation,
140
+ partialize: (state) => {
141
+ const persisted: PersistedState = filterState({
142
+ app: state.app,
143
+ log: state.log,
144
+ logs: state.logs,
145
+ sample: state.sample,
146
+ });
147
+ return persisted as unknown as StoreState;
148
+ },
149
+ version: 1,
150
+ onRehydrateStorage: (state: StoreState) => {
151
+ return (hydrationState, error) => {
152
+ log.debug("REHYDRATING STATE");
153
+ if (error) {
154
+ log.debug("ERROR", { error });
155
+ } else {
156
+ log.debug("STATE", { state, hydrationState });
157
+ }
158
+ };
159
+ },
160
+ },
161
+ ),
162
+ ),
163
+ );
164
+
165
+ // Set the implementation and initialize it
166
+ storeImplementation = store as UseBoundStore<StoreApi<StoreState>>;
167
+ store.getState().initialize(api, capabilities);
168
+ };
@@ -0,0 +1,84 @@
1
+ import { PersistedState } from "./store";
2
+
3
+ export function filterState(state: PersistedState) {
4
+ if (!state) {
5
+ return state;
6
+ }
7
+
8
+ // When saving state, we can't store vast amounts of data (like a large sample)
9
+ const filters = [filterLargeSample, filterLargeLogSummary];
10
+ return filters.reduce(
11
+ (filteredState, filter) => filter(filteredState),
12
+ state,
13
+ );
14
+ }
15
+
16
+ // Filters the selected Sample if it is large
17
+ function filterLargeSample(state: PersistedState): PersistedState {
18
+ if (!state || !state.sample || !state.sample.selectedSample) {
19
+ return state;
20
+ }
21
+
22
+ const estimatedTotalSize = estimateSize(state.sample.selectedSample.messages);
23
+ if (estimatedTotalSize > 250000) {
24
+ return {
25
+ ...state,
26
+ sample: {
27
+ ...state.sample,
28
+ selectedSample: undefined,
29
+ },
30
+ };
31
+ } else {
32
+ return state;
33
+ }
34
+ }
35
+
36
+ // Filters the selectedlog if it is too large
37
+ function filterLargeLogSummary(state: PersistedState): PersistedState {
38
+ if (!state || !state.log || !state.log.selectedLogSummary) {
39
+ return state;
40
+ }
41
+
42
+ const estimatedSize = estimateSize(
43
+ state.log.selectedLogSummary.sampleSummaries,
44
+ );
45
+ if (estimatedSize > 250000) {
46
+ return {
47
+ ...state,
48
+ log: {
49
+ ...state.log,
50
+ selectedLogSummary: undefined,
51
+ },
52
+ };
53
+ } else {
54
+ return state;
55
+ }
56
+ }
57
+
58
+ function estimateSize(list: unknown[], frequency = 0.2) {
59
+ if (!list || list.length === 0) {
60
+ return 0;
61
+ }
62
+
63
+ // Total number of samples
64
+ const sampleSize = Math.ceil(list.length * frequency);
65
+
66
+ // Get a proper random sample without duplicates
67
+ const messageIndices = new Set<number>();
68
+ while (
69
+ messageIndices.size < sampleSize &&
70
+ messageIndices.size < list.length
71
+ ) {
72
+ const randomIndex = Math.floor(Math.random() * list.length);
73
+ messageIndices.add(randomIndex);
74
+ }
75
+
76
+ // Calculate size from sampled messages
77
+ const totalSize = Array.from(messageIndices).reduce((size, index) => {
78
+ return size + JSON.stringify(list[index]).length;
79
+ }, 0);
80
+
81
+ // Estimate total size based on sample
82
+ const estimatedTotalSize = (totalSize / sampleSize) * list.length;
83
+ return estimatedTotalSize;
84
+ }
@@ -0,0 +1,23 @@
1
+ import { SampleSummary } from "../api/types";
2
+
3
+ // Function to merge log samples with pending samples
4
+ export const mergeSampleSummaries = (
5
+ logSamples: SampleSummary[],
6
+ pendingSamples: SampleSummary[],
7
+ ) => {
8
+ // Create a map of existing sample IDs to avoid duplicates
9
+ const existingSampleIds = new Set(
10
+ logSamples.map((sample) => `${sample.id}-${sample.epoch}`),
11
+ );
12
+
13
+ // Filter out any pending samples that already exist in the log
14
+ const uniquePendingSamples = pendingSamples
15
+ .filter((sample) => !existingSampleIds.has(`${sample.id}-${sample.epoch}`))
16
+ .map((sample) => {
17
+ // Always mark pending items as incomplete to be sure we trigger polling
18
+ return { ...sample, completed: false };
19
+ });
20
+
21
+ // Combine and return all samples
22
+ return [...logSamples, ...uniquePendingSamples];
23
+ };
@@ -0,0 +1,26 @@
1
+ import { ClientStorage } from "../api/types";
2
+ import { PersistedState } from "../state/store";
3
+ import { getVscodeApi } from "../utils/vscode";
4
+
5
+ const resolveStorage = (): ClientStorage | undefined => {
6
+ const vscodeApi = getVscodeApi();
7
+ if (vscodeApi) {
8
+ return {
9
+ getItem: (_name: string) => {
10
+ const state = vscodeApi.getState() as PersistedState;
11
+ return state;
12
+ },
13
+ setItem: (_name: string, value: unknown) => {
14
+ // TODO: This is pretty gnarly type hijinks
15
+ const valObj = value as { state: PersistedState; version: number };
16
+ vscodeApi.setState(valObj);
17
+ },
18
+ removeItem: (_name: string) => {
19
+ vscodeApi.setState(null);
20
+ },
21
+ };
22
+ }
23
+ return undefined;
24
+ };
25
+
26
+ export default resolveStorage();