inspect-ai 0.3.63__py3-none-any.whl → 0.3.64__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 (178) hide show
  1. inspect_ai/_cli/cache.py +8 -7
  2. inspect_ai/_cli/common.py +0 -12
  3. inspect_ai/_cli/eval.py +32 -4
  4. inspect_ai/_cli/info.py +1 -0
  5. inspect_ai/_cli/list.py +1 -1
  6. inspect_ai/_cli/log.py +2 -0
  7. inspect_ai/_cli/sandbox.py +4 -1
  8. inspect_ai/_cli/score.py +181 -32
  9. inspect_ai/_cli/trace.py +2 -0
  10. inspect_ai/_cli/view.py +4 -2
  11. inspect_ai/_display/core/config.py +7 -1
  12. inspect_ai/_display/textual/widgets/samples.py +4 -3
  13. inspect_ai/_display/textual/widgets/sandbox.py +6 -0
  14. inspect_ai/_eval/__init__.py +0 -0
  15. inspect_ai/_eval/eval.py +100 -97
  16. inspect_ai/_eval/evalset.py +69 -69
  17. inspect_ai/_eval/loader.py +122 -12
  18. inspect_ai/_eval/registry.py +1 -1
  19. inspect_ai/_eval/run.py +14 -0
  20. inspect_ai/_eval/score.py +125 -36
  21. inspect_ai/_eval/task/log.py +105 -4
  22. inspect_ai/_eval/task/results.py +92 -38
  23. inspect_ai/_eval/task/run.py +6 -2
  24. inspect_ai/_eval/task/sandbox.py +35 -2
  25. inspect_ai/_eval/task/task.py +49 -46
  26. inspect_ai/_util/__init__.py +0 -0
  27. inspect_ai/_util/constants.py +1 -1
  28. inspect_ai/_util/content.py +8 -0
  29. inspect_ai/_util/error.py +2 -0
  30. inspect_ai/_util/file.py +15 -1
  31. inspect_ai/_util/logger.py +4 -2
  32. inspect_ai/_util/registry.py +7 -1
  33. inspect_ai/_view/view.py +1 -2
  34. inspect_ai/_view/www/App.css +5 -0
  35. inspect_ai/_view/www/README.md +1 -1
  36. inspect_ai/_view/www/dist/assets/index.css +39 -11
  37. inspect_ai/_view/www/dist/assets/index.js +504 -501
  38. inspect_ai/_view/www/log-schema.json +86 -73
  39. inspect_ai/_view/www/package.json +1 -1
  40. inspect_ai/_view/www/src/App.tsx +1 -0
  41. inspect_ai/_view/www/src/components/AnsiDisplay.tsx +1 -1
  42. inspect_ai/_view/www/src/components/JsonPanel.tsx +1 -1
  43. inspect_ai/_view/www/src/components/LargeModal.tsx +39 -49
  44. inspect_ai/_view/www/src/components/NavPills.tsx +3 -1
  45. inspect_ai/_view/www/src/components/TabSet.tsx +19 -4
  46. inspect_ai/_view/www/src/logfile/remoteLogFile.ts +0 -1
  47. inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +1 -1
  48. inspect_ai/_view/www/src/metadata/MetaDataView.tsx +1 -1
  49. inspect_ai/_view/www/src/plan/PlanDetailView.tsx +17 -2
  50. inspect_ai/_view/www/src/plan/SolverDetailView.tsx +1 -1
  51. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +9 -4
  52. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +4 -2
  53. inspect_ai/_view/www/src/samples/SamplesTools.tsx +16 -24
  54. inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +1 -1
  55. inspect_ai/_view/www/src/samples/chat/ChatView.tsx +1 -0
  56. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +27 -13
  57. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +19 -17
  58. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +12 -10
  59. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +56 -66
  60. inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +12 -5
  61. inspect_ai/_view/www/src/samples/chat/tools/tool.ts +21 -36
  62. inspect_ai/_view/www/src/samples/descriptor/samplesDescriptor.tsx +3 -1
  63. inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +27 -25
  64. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +5 -1
  65. inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +1 -1
  66. inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +2 -2
  67. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +9 -5
  68. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +1 -1
  69. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -4
  70. inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +1 -0
  71. inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +1 -0
  72. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +17 -6
  73. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +14 -19
  74. inspect_ai/_view/www/src/types/log.d.ts +107 -19
  75. inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +7 -1
  76. inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +5 -3
  77. inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +25 -27
  78. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +12 -11
  79. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.module.css +25 -2
  80. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +60 -36
  81. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +4 -0
  82. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +6 -4
  83. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +16 -14
  84. inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +9 -19
  85. inspect_ai/_view/www/src/workspace/utils.ts +34 -0
  86. inspect_ai/approval/_approval.py +2 -0
  87. inspect_ai/approval/_approver.py +4 -4
  88. inspect_ai/approval/_auto.py +1 -1
  89. inspect_ai/approval/_human/approver.py +3 -0
  90. inspect_ai/approval/_policy.py +5 -0
  91. inspect_ai/approval/_registry.py +2 -2
  92. inspect_ai/dataset/_dataset.py +36 -45
  93. inspect_ai/dataset/_sources/__init__.py +0 -0
  94. inspect_ai/dataset/_sources/csv.py +13 -13
  95. inspect_ai/dataset/_sources/hf.py +29 -29
  96. inspect_ai/dataset/_sources/json.py +10 -10
  97. inspect_ai/log/__init__.py +2 -0
  98. inspect_ai/log/_convert.py +3 -3
  99. inspect_ai/log/_file.py +24 -9
  100. inspect_ai/log/_log.py +98 -7
  101. inspect_ai/log/_message.py +3 -1
  102. inspect_ai/log/_recorders/file.py +4 -0
  103. inspect_ai/log/_recorders/recorder.py +3 -0
  104. inspect_ai/log/_transcript.py +19 -8
  105. inspect_ai/model/__init__.py +2 -0
  106. inspect_ai/model/_cache.py +39 -21
  107. inspect_ai/model/_call_tools.py +2 -2
  108. inspect_ai/model/_chat_message.py +14 -4
  109. inspect_ai/model/_generate_config.py +1 -1
  110. inspect_ai/model/_model.py +31 -24
  111. inspect_ai/model/_model_output.py +14 -1
  112. inspect_ai/model/_openai.py +10 -18
  113. inspect_ai/model/_providers/google.py +9 -5
  114. inspect_ai/model/_providers/openai.py +5 -9
  115. inspect_ai/model/_providers/openrouter.py +1 -1
  116. inspect_ai/scorer/__init__.py +6 -1
  117. inspect_ai/scorer/_answer.py +1 -1
  118. inspect_ai/scorer/_classification.py +4 -0
  119. inspect_ai/scorer/_match.py +4 -5
  120. inspect_ai/scorer/_metric.py +87 -28
  121. inspect_ai/scorer/_metrics/__init__.py +3 -3
  122. inspect_ai/scorer/_metrics/accuracy.py +8 -10
  123. inspect_ai/scorer/_metrics/mean.py +3 -17
  124. inspect_ai/scorer/_metrics/std.py +111 -30
  125. inspect_ai/scorer/_model.py +12 -12
  126. inspect_ai/scorer/_pattern.py +3 -3
  127. inspect_ai/scorer/_reducer/reducer.py +36 -21
  128. inspect_ai/scorer/_reducer/registry.py +2 -2
  129. inspect_ai/scorer/_reducer/types.py +7 -1
  130. inspect_ai/scorer/_score.py +11 -1
  131. inspect_ai/scorer/_scorer.py +110 -16
  132. inspect_ai/solver/__init__.py +1 -1
  133. inspect_ai/solver/_basic_agent.py +19 -22
  134. inspect_ai/solver/_bridge/__init__.py +0 -3
  135. inspect_ai/solver/_bridge/bridge.py +3 -3
  136. inspect_ai/solver/_chain.py +1 -2
  137. inspect_ai/solver/_critique.py +3 -3
  138. inspect_ai/solver/_fork.py +2 -2
  139. inspect_ai/solver/_human_agent/__init__.py +0 -0
  140. inspect_ai/solver/_human_agent/agent.py +5 -8
  141. inspect_ai/solver/_human_agent/commands/clock.py +14 -10
  142. inspect_ai/solver/_human_agent/commands/note.py +1 -1
  143. inspect_ai/solver/_human_agent/commands/score.py +0 -11
  144. inspect_ai/solver/_multiple_choice.py +15 -18
  145. inspect_ai/solver/_prompt.py +7 -7
  146. inspect_ai/solver/_solver.py +53 -52
  147. inspect_ai/solver/_task_state.py +80 -69
  148. inspect_ai/solver/_use_tools.py +9 -9
  149. inspect_ai/tool/__init__.py +2 -1
  150. inspect_ai/tool/_tool.py +43 -14
  151. inspect_ai/tool/_tool_call.py +6 -2
  152. inspect_ai/tool/_tool_choice.py +3 -1
  153. inspect_ai/tool/_tool_def.py +10 -8
  154. inspect_ai/tool/_tool_params.py +24 -0
  155. inspect_ai/tool/_tool_with.py +7 -7
  156. inspect_ai/tool/_tools/__init__.py +0 -0
  157. inspect_ai/tool/_tools/_computer/_common.py +2 -2
  158. inspect_ai/tool/_tools/_computer/_computer.py +11 -0
  159. inspect_ai/tool/_tools/_execute.py +15 -9
  160. inspect_ai/tool/_tools/_web_browser/_resources/README.md +2 -2
  161. inspect_ai/tool/_tools/_web_browser/_web_browser.py +5 -3
  162. inspect_ai/tool/_tools/_web_search.py +7 -5
  163. inspect_ai/util/_concurrency.py +3 -3
  164. inspect_ai/util/_panel.py +2 -0
  165. inspect_ai/util/_resource.py +12 -12
  166. inspect_ai/util/_sandbox/docker/compose.py +23 -20
  167. inspect_ai/util/_sandbox/docker/config.py +2 -1
  168. inspect_ai/util/_sandbox/docker/docker.py +10 -1
  169. inspect_ai/util/_sandbox/docker/service.py +100 -0
  170. inspect_ai/util/_sandbox/environment.py +99 -96
  171. inspect_ai/util/_subprocess.py +5 -3
  172. inspect_ai/util/_subtask.py +15 -16
  173. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/LICENSE +1 -1
  174. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/METADATA +10 -6
  175. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/RECORD +178 -171
  176. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/WHEEL +0 -0
  177. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/entry_points.txt +0 -0
  178. {inspect_ai-0.3.63.dist-info → inspect_ai-0.3.64.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ import { Arguments } from "../../../types/log";
7
7
  export interface ToolCallResult {
8
8
  functionCall: string;
9
9
  input?: string;
10
- inputType?: string;
10
+ highlightLanguage?: string;
11
11
  }
12
12
 
13
13
  /**
@@ -19,24 +19,18 @@ export const resolveToolInput = (
19
19
  ): ToolCallResult => {
20
20
  const toolName = fn;
21
21
 
22
- const [inputKey, inputType] = extractInputMetadata(toolName);
23
- if (inputKey) {
24
- const { input, args } = extractInput(
25
- inputKey,
26
- toolArgs as Record<string, unknown>,
27
- );
28
- const functionCall =
29
- args.length > 0 ? `${toolName}(${args.join(",")})` : toolName;
30
- return {
31
- functionCall,
32
- input,
33
- inputType,
34
- };
35
- } else {
36
- return {
37
- functionCall: toolName,
38
- };
39
- }
22
+ const [inputKey, highlightLanguage] = extractInputMetadata(toolName);
23
+ const { input, args } = extractInput(
24
+ toolArgs as Record<string, unknown>,
25
+ inputKey,
26
+ );
27
+ const functionCall =
28
+ args.length > 0 ? `${toolName}(${args.join(", ")})` : toolName;
29
+ return {
30
+ functionCall,
31
+ input,
32
+ highlightLanguage,
33
+ };
40
34
  };
41
35
 
42
36
  const extractInputMetadata = (
@@ -54,29 +48,20 @@ const extractInputMetadata = (
54
48
  };
55
49
 
56
50
  const extractInput = (
57
- inputKey: string,
58
51
  args: Record<string, unknown>,
52
+ inputKey?: string,
59
53
  ): { input?: string; args: string[] } => {
60
54
  const formatArg = (key: string, value: unknown) => {
61
- const quotedValue = typeof value === "string" ? `"${value}"` : value;
55
+ const quotedValue =
56
+ typeof value === "string"
57
+ ? `"${value}"`
58
+ : typeof value === "object" || Array.isArray(value)
59
+ ? JSON.stringify(value, undefined, 2)
60
+ : String(value);
62
61
  return `${key}: ${quotedValue}`;
63
62
  };
64
63
  if (args) {
65
- if (Object.keys(args).length === 1) {
66
- const inputRaw = args[Object.keys(args)[0]];
67
-
68
- let input;
69
- if (Array.isArray(inputRaw) || typeof inputRaw === "object") {
70
- input = JSON.stringify(inputRaw, undefined, 2);
71
- } else {
72
- input = String(inputRaw);
73
- }
74
-
75
- return {
76
- input: input,
77
- args: [],
78
- };
79
- } else if (args[inputKey]) {
64
+ if (inputKey && args[inputKey]) {
80
65
  const input = args[inputKey];
81
66
  const filteredArgs = Object.keys(args)
82
67
  .filter((key) => {
@@ -158,8 +158,10 @@ export const createEvalDescriptor = (
158
158
  ): ReactNode => {
159
159
  const descriptor = scoreDescriptor(scoreLabel);
160
160
  const score = scoreValue(sample, scoreLabel);
161
- if (score === null || score === "undefined") {
161
+ if (score === null) {
162
162
  return "null";
163
+ } else if (score === undefined) {
164
+ return "";
163
165
  } else if (score && descriptor && descriptor.render) {
164
166
  return descriptor.render(score);
165
167
  } else {
@@ -56,28 +56,6 @@ export const SelectScorer: React.FC<SelectScorerProps> = ({
56
56
  return score && sc.scorer === score.scorer;
57
57
  });
58
58
 
59
- const selectors = [
60
- <ScorerSelector
61
- scorers={scorers}
62
- selectedIndex={scorerIndex(scorers, score)}
63
- setSelectedIndex={(index: number) => {
64
- setScore(scorers[index]);
65
- }}
66
- />,
67
- ];
68
- if (scorerScores.length > 1) {
69
- selectors.push(
70
- <ScoreSelector
71
- className={clsx(styles.secondSel)}
72
- scores={scorerScores}
73
- selectedIndex={scoreIndex(scorerScores, score)}
74
- setSelectedIndex={(index: number) => {
75
- setScore(scorerScores[index]);
76
- }}
77
- />,
78
- );
79
- }
80
-
81
59
  // There are multiple scorers, so show a scorer selector and a r
82
60
  return (
83
61
  <div className={styles.flex}>
@@ -93,7 +71,23 @@ export const SelectScorer: React.FC<SelectScorerProps> = ({
93
71
  >
94
72
  Scorer:
95
73
  </span>
96
- {selectors}
74
+ <ScorerSelector
75
+ scorers={scorers}
76
+ selectedIndex={scorerIndex(scorers, score)}
77
+ setSelectedIndex={(index: number) => {
78
+ setScore(scorers[index]);
79
+ }}
80
+ />
81
+ {scorerScores.length > 1 ? (
82
+ <ScoreSelector
83
+ className={clsx(styles.secondSel)}
84
+ scores={scorerScores}
85
+ selectedIndex={scoreIndex(scorerScores, score)}
86
+ setSelectedIndex={(index: number) => {
87
+ setScore(scorerScores[index]);
88
+ }}
89
+ />
90
+ ) : undefined}
97
91
  </div>
98
92
  );
99
93
  }
@@ -128,7 +122,11 @@ const ScoreSelector: React.FC<ScoreSelectorProps> = ({
128
122
  }}
129
123
  >
130
124
  {scores.map((score) => {
131
- return <option value={score.name}>{score.name}</option>;
125
+ return (
126
+ <option key={score.name} value={score.name}>
127
+ {score.name}
128
+ </option>
129
+ );
132
130
  })}
133
131
  </select>
134
132
  );
@@ -156,7 +154,11 @@ const ScorerSelector: React.FC<ScorerSelectorProps> = ({
156
154
  }}
157
155
  >
158
156
  {scorers.map((scorer) => {
159
- return <option value={scorer.scorer}>{scorer.scorer}</option>;
157
+ return (
158
+ <option key={scorer.scorer} value={scorer.scorer}>
159
+ {scorer.scorer}
160
+ </option>
161
+ );
160
162
  })}
161
163
  </select>
162
164
  );
@@ -68,7 +68,11 @@ export const SortFilter: React.FC<SortFilterProps> = ({
68
68
  }}
69
69
  >
70
70
  {options.map((option) => {
71
- return <option value={option.val}>{option.label}</option>;
71
+ return (
72
+ <option key={option.val} value={option.val}>
73
+ {option.label}
74
+ </option>
75
+ );
72
76
  })}
73
77
  </select>
74
78
  </div>
@@ -35,7 +35,7 @@ export const InfoEventView: React.FC<InfoEventViewProps> = ({
35
35
  return (
36
36
  <EventPanel
37
37
  id={id}
38
- title="Info"
38
+ title={"Info" + (event.source ? ": " + event.source : "")}
39
39
  className={className}
40
40
  subTitle={formatDateTime(new Date(event.timestamp))}
41
41
  icon={ApplicationIcons.info}
@@ -208,9 +208,9 @@ interface ToolConfigProps {
208
208
  }
209
209
 
210
210
  const ToolsConfig: React.FC<ToolConfigProps> = ({ tools }) => {
211
- const toolEls = tools.map((tool) => {
211
+ const toolEls = tools.map((tool, idx) => {
212
212
  return (
213
- <Fragment>
213
+ <Fragment key={`${tool.name}-${idx}`}>
214
214
  <div className={clsx("text-style-label", "text-style-secondary")}>
215
215
  {tool.name}
216
216
  </div>
@@ -35,9 +35,13 @@ export const SampleInitEventView: React.FC<SampleInitEventViewProps> = ({
35
35
 
36
36
  if (event.sample.files && Object.keys(event.sample.files).length > 0) {
37
37
  sections.push(
38
- <EventSection title="Files">
38
+ <EventSection title="Files" key={`sample-${id}-init-files`}>
39
39
  {Object.keys(event.sample.files).map((file) => {
40
- return <pre className={styles.noMargin}>{file}</pre>;
40
+ return (
41
+ <pre key={`sample-init-file-${file}`} className={styles.noMargin}>
42
+ {file}
43
+ </pre>
44
+ );
41
45
  })}
42
46
  </EventSection>,
43
47
  );
@@ -45,7 +49,7 @@ export const SampleInitEventView: React.FC<SampleInitEventViewProps> = ({
45
49
 
46
50
  if (event.sample.setup) {
47
51
  sections.push(
48
- <EventSection title="Setup">
52
+ <EventSection title="Setup" key={`sample-${id}-init-setup`}>
49
53
  <pre className={styles.code}>
50
54
  <code className="sourceCode">{event.sample.setup}</code>
51
55
  </pre>
@@ -75,7 +79,7 @@ export const SampleInitEventView: React.FC<SampleInitEventViewProps> = ({
75
79
  {event.sample.choices
76
80
  ? event.sample.choices.map((choice, index) => {
77
81
  return (
78
- <div>
82
+ <div key={`$choice-{choice}`}>
79
83
  {String.fromCharCode(65 + index)}) {choice}
80
84
  </div>
81
85
  );
@@ -88,7 +92,7 @@ export const SampleInitEventView: React.FC<SampleInitEventViewProps> = ({
88
92
  )}
89
93
  <EventSection title="Target">
90
94
  {toArray(event.sample.target).map((target) => {
91
- return <div>{target}</div>;
95
+ return <div key={target}>{target}</div>;
92
96
  })}
93
97
  </EventSection>
94
98
  </div>
@@ -37,7 +37,7 @@ export const ScoreEventView: React.FC<ScoreEventViewProps> = ({
37
37
  return (
38
38
  <EventPanel
39
39
  id={id}
40
- title="Score"
40
+ title={(event.intermediate ? "Intermediate " : "") + "Score"}
41
41
  className={clsx(className, "text-size-small")}
42
42
  subTitle={formatDateTime(new Date(event.timestamp))}
43
43
  icon={ApplicationIcons.scorer}
@@ -8,6 +8,7 @@ import { EventPanel } from "./event/EventPanel";
8
8
  import { TranscriptView } from "./TranscriptView";
9
9
  import { TranscriptEventState } from "./types";
10
10
 
11
+ import { useMemo } from "react";
11
12
  import styles from "./ToolEventView.module.css";
12
13
 
13
14
  interface ToolEventViewProps {
@@ -31,9 +32,9 @@ export const ToolEventView: React.FC<ToolEventViewProps> = ({
31
32
  className,
32
33
  }) => {
33
34
  // Extract tool input
34
- const { input, functionCall, inputType } = resolveToolInput(
35
- event.function,
36
- event.arguments,
35
+ const { input, functionCall, highlightLanguage } = useMemo(
36
+ () => resolveToolInput(event.function, event.arguments),
37
+ [event.function, event.arguments],
37
38
  );
38
39
 
39
40
  // Find an approval if there is one
@@ -62,7 +63,7 @@ export const ToolEventView: React.FC<ToolEventViewProps> = ({
62
63
  <ToolCallView
63
64
  functionCall={functionCall}
64
65
  input={input}
65
- inputType={inputType}
66
+ highlightLanguage={highlightLanguage}
66
67
  output={event.error?.message || event.result}
67
68
  mode="compact"
68
69
  view={event.view ? event.view : undefined}
@@ -26,6 +26,7 @@ export const EventNavs: React.FC<EventNavsProps> = ({
26
26
  {navs.map((nav) => {
27
27
  return (
28
28
  <EventNav
29
+ key={nav.title}
29
30
  target={nav.target}
30
31
  title={nav.title}
31
32
  selectedNav={selectedNav}
@@ -176,6 +176,7 @@ export const EventPanel: React.FC<EventPanelProps> = ({
176
176
 
177
177
  return (
178
178
  <div
179
+ key={`children-${id}-${index}`}
179
180
  id={id}
180
181
  className={clsx("tab-pane", "show", isSelected ? "active" : "")}
181
182
  >
@@ -1,5 +1,5 @@
1
1
  import clsx from "clsx";
2
- import { Fragment, ReactNode } from "react";
2
+ import { Fragment, JSX, ReactNode } from "react";
3
3
  import {
4
4
  HumanBaselineView,
5
5
  SessionLog,
@@ -18,7 +18,10 @@ interface Signature {
18
18
  interface ChangeType {
19
19
  type: string;
20
20
  signature: Signature;
21
- render: (changes: JsonChange[], state: Record<string, unknown>) => ReactNode;
21
+ render: (
22
+ changes: JsonChange[],
23
+ state: Record<string, unknown>,
24
+ ) => JSX.Element;
22
25
  }
23
26
 
24
27
  const system_msg_added_sig: ChangeType = {
@@ -33,6 +36,7 @@ const system_msg_added_sig: ChangeType = {
33
36
  const message = messages[0];
34
37
  return (
35
38
  <ChatView
39
+ key="system_msg_event_preview"
36
40
  id="system_msg_event_preview"
37
41
  messages={[message] as Messages}
38
42
  />
@@ -123,6 +127,7 @@ const human_baseline_session: ChangeType = {
123
127
 
124
128
  return (
125
129
  <HumanBaselineView
130
+ key="human_baseline_view"
126
131
  started={startedDate}
127
132
  running={running}
128
133
  completed={completed}
@@ -185,10 +190,10 @@ const renderTools = (
185
190
  }
186
191
 
187
192
  return (
188
- <div className={clsx(styles.tools)}>
193
+ <div key={"state-diff-tools"} className={clsx(styles.tools)}>
189
194
  {Object.keys(toolsInfo).map((key) => {
190
195
  return (
191
- <Fragment>
196
+ <Fragment key={key}>
192
197
  <div
193
198
  className={clsx(
194
199
  "text-size-smaller",
@@ -242,12 +247,18 @@ interface ToolsProps {
242
247
  * Renders a list of tool components based on the provided tool definitions.
243
248
  */
244
249
  export const Tools: React.FC<ToolsProps> = ({ toolDefinitions }) => {
245
- return toolDefinitions.map((toolDefinition) => {
250
+ return toolDefinitions.map((toolDefinition, idx) => {
246
251
  const toolName = toolDefinition.name;
247
252
  const toolArgs = toolDefinition.parameters?.properties
248
253
  ? Object.keys(toolDefinition.parameters.properties)
249
254
  : [];
250
- return <Tool toolName={toolName} toolArgs={toolArgs} />;
255
+ return (
256
+ <Tool
257
+ key={`${toolName}-${idx}`}
258
+ toolName={toolName}
259
+ toolArgs={toolArgs}
260
+ />
261
+ );
251
262
  });
252
263
  };
253
264
 
@@ -44,14 +44,6 @@ export const StateEventView: React.FC<StateEventViewProps> = ({
44
44
  // Synthesize objects for comparison
45
45
  const [before, after] = synthesizeComparable(event.changes);
46
46
 
47
- const tabs = [
48
- <StateDiffView
49
- before={before}
50
- after={after}
51
- data-name="Diff"
52
- className={clsx(styles.diff)}
53
- />,
54
- ];
55
47
  // This clone is important since the state is used by react as potential values that are rendered
56
48
  // and as a result may be decorated with additional properties, etc..., resulting in DOM elements
57
49
  // appearing attached to state.
@@ -60,14 +52,6 @@ export const StateEventView: React.FC<StateEventViewProps> = ({
60
52
  structuredClone(after),
61
53
  isStore,
62
54
  );
63
- if (changePreview) {
64
- tabs.unshift(
65
- <div data-name="Summary" className={clsx(styles.summary)}>
66
- {changePreview}
67
- </div>,
68
- );
69
- }
70
-
71
55
  // Compute the title
72
56
  const title = event.event === "state" ? "State Updated" : "Store Updated";
73
57
 
@@ -77,7 +61,7 @@ export const StateEventView: React.FC<StateEventViewProps> = ({
77
61
  title={title}
78
62
  className={className}
79
63
  subTitle={formatDateTime(new Date(event.timestamp))}
80
- text={tabs.length === 1 ? summary : undefined}
64
+ text={!changePreview ? summary : undefined}
81
65
  collapse={changePreview === undefined ? true : undefined}
82
66
  selectedNav={eventState.selectedNav || ""}
83
67
  setSelectedNav={(selectedNav) => {
@@ -88,7 +72,17 @@ export const StateEventView: React.FC<StateEventViewProps> = ({
88
72
  setEventState({ ...eventState, collapsed });
89
73
  }}
90
74
  >
91
- {tabs}
75
+ {changePreview ? (
76
+ <div data-name="Summary" className={clsx(styles.summary)}>
77
+ {changePreview}
78
+ </div>
79
+ ) : undefined}
80
+ <StateDiffView
81
+ before={before}
82
+ after={after}
83
+ data-name="Diff"
84
+ className={clsx(styles.diff)}
85
+ />
92
86
  </EventPanel>
93
87
  );
94
88
  };
@@ -153,7 +147,8 @@ const generatePreview = (
153
147
  }
154
148
  }
155
149
  if (matchingOps === requiredMatchCount) {
156
- results.push(changeType.render(changes, resolvedState));
150
+ const el = changeType.render(changes, resolvedState);
151
+ results.push(el);
157
152
  // Only one renderer can process a change
158
153
  // TODO: consider changing this to allow many handlers to render (though then we sort of need
159
154
  // to match the renderer to the key (e.g. a rendered for `tool_choice` a renderer for `tools` etc..))