inspect-ai 0.3.55__py3-none-any.whl → 0.3.56__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.
- inspect_ai/__init__.py +1 -0
- inspect_ai/_cli/common.py +1 -1
- inspect_ai/_cli/trace.py +33 -20
- inspect_ai/_display/core/active.py +1 -1
- inspect_ai/_display/core/display.py +1 -1
- inspect_ai/_display/core/footer.py +1 -1
- inspect_ai/_display/core/progress.py +0 -6
- inspect_ai/_display/core/rich.py +1 -1
- inspect_ai/_display/rich/display.py +2 -2
- inspect_ai/_display/textual/app.py +15 -17
- inspect_ai/_display/textual/widgets/clock.py +3 -3
- inspect_ai/_display/textual/widgets/samples.py +6 -13
- inspect_ai/_eval/context.py +9 -1
- inspect_ai/_eval/score.py +4 -10
- inspect_ai/_eval/task/results.py +5 -4
- inspect_ai/_eval/task/run.py +6 -12
- inspect_ai/_eval/task/task.py +10 -0
- inspect_ai/_util/ansi.py +31 -0
- inspect_ai/_util/format.py +7 -0
- inspect_ai/_util/logger.py +12 -12
- inspect_ai/_util/throttle.py +10 -1
- inspect_ai/_util/trace.py +43 -47
- inspect_ai/_util/transcript.py +4 -0
- inspect_ai/_util/vscode.py +51 -0
- inspect_ai/_view/notify.py +2 -1
- inspect_ai/_view/www/App.css +22 -1
- inspect_ai/_view/www/dist/assets/index.css +2374 -2
- inspect_ai/_view/www/dist/assets/index.js +29622 -24424
- inspect_ai/_view/www/log-schema.json +138 -90
- inspect_ai/_view/www/package.json +1 -0
- inspect_ai/_view/www/src/App.mjs +1 -0
- inspect_ai/_view/www/src/appearance/Icons.mjs +2 -0
- inspect_ai/_view/www/src/components/AsciiCinemaPlayer.mjs +74 -0
- inspect_ai/_view/www/src/components/CopyButton.mjs +0 -1
- inspect_ai/_view/www/src/components/HumanBaselineView.mjs +168 -0
- inspect_ai/_view/www/src/components/LightboxCarousel.mjs +217 -0
- inspect_ai/_view/www/src/components/Tools.mjs +11 -3
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.mjs +3 -2
- inspect_ai/_view/www/src/samples/transcript/TranscriptView.mjs +1 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.mjs +56 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateEventView.mjs +17 -5
- inspect_ai/_view/www/src/types/asciicinema-player.d.ts +26 -0
- inspect_ai/_view/www/src/types/log.d.ts +26 -12
- inspect_ai/_view/www/yarn.lock +44 -0
- inspect_ai/approval/_apply.py +4 -0
- inspect_ai/approval/_human/panel.py +5 -8
- inspect_ai/dataset/_dataset.py +51 -10
- inspect_ai/dataset/_util.py +31 -3
- inspect_ai/log/__init__.py +2 -0
- inspect_ai/log/_log.py +5 -2
- inspect_ai/model/_call_tools.py +4 -2
- inspect_ai/model/_chat_message.py +3 -0
- inspect_ai/model/_model.py +42 -1
- inspect_ai/model/_providers/anthropic.py +4 -0
- inspect_ai/model/_render.py +9 -2
- inspect_ai/scorer/_metric.py +12 -1
- inspect_ai/solver/__init__.py +2 -0
- inspect_ai/solver/_human_agent/agent.py +83 -0
- inspect_ai/solver/_human_agent/commands/__init__.py +36 -0
- inspect_ai/solver/_human_agent/commands/clock.py +70 -0
- inspect_ai/solver/_human_agent/commands/command.py +59 -0
- inspect_ai/solver/_human_agent/commands/instructions.py +74 -0
- inspect_ai/solver/_human_agent/commands/note.py +42 -0
- inspect_ai/solver/_human_agent/commands/score.py +80 -0
- inspect_ai/solver/_human_agent/commands/status.py +62 -0
- inspect_ai/solver/_human_agent/commands/submit.py +151 -0
- inspect_ai/solver/_human_agent/install.py +222 -0
- inspect_ai/solver/_human_agent/panel.py +252 -0
- inspect_ai/solver/_human_agent/service.py +45 -0
- inspect_ai/solver/_human_agent/state.py +55 -0
- inspect_ai/solver/_human_agent/view.py +24 -0
- inspect_ai/solver/_task_state.py +28 -2
- inspect_ai/tool/_tool.py +10 -2
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +13 -10
- inspect_ai/util/__init__.py +8 -4
- inspect_ai/{_util/display.py → util/_display.py} +6 -0
- inspect_ai/util/_panel.py +31 -9
- inspect_ai/util/_sandbox/__init__.py +0 -3
- inspect_ai/util/_sandbox/context.py +5 -1
- inspect_ai/util/_sandbox/docker/compose.py +16 -10
- inspect_ai/util/_sandbox/docker/docker.py +9 -6
- inspect_ai/util/_sandbox/docker/internal.py +1 -1
- inspect_ai/util/_sandbox/docker/util.py +2 -2
- inspect_ai/util/_sandbox/environment.py +6 -5
- inspect_ai/util/_sandbox/local.py +1 -1
- inspect_ai/util/_sandbox/service.py +22 -7
- inspect_ai/util/_store.py +5 -6
- inspect_ai/util/_store_model.py +110 -0
- inspect_ai/util/_throttle.py +32 -0
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/METADATA +1 -1
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/RECORD +95 -73
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/WHEEL +0 -0
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.55.dist-info → inspect_ai-0.3.56.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
// @ts-check
|
2
|
+
import { html } from "htm/preact";
|
3
|
+
import { useEffect } from "preact/hooks";
|
4
|
+
import { formatDateTime, formatTime } from "../utils/Format.mjs";
|
5
|
+
import { AsciiCinemaPlayer } from "./AsciiCinemaPlayer.mjs";
|
6
|
+
import { TextStyle } from "../appearance/Fonts.mjs";
|
7
|
+
import { LightboxCarousel } from "./LightboxCarousel.mjs";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* @typedef {Object} SessionLog
|
11
|
+
* @property {string} name - The name of this session
|
12
|
+
* @property {string} user - The user for this session
|
13
|
+
* @property {string} input - The input for this session
|
14
|
+
* @property {string} output - The output for this session
|
15
|
+
* @property {string} timing - The timing for this session
|
16
|
+
*/
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Renders the HumanBaselineView component.
|
20
|
+
*
|
21
|
+
* @param {Object} props - The properties passed to the component.
|
22
|
+
* @param {Date} props.started - When the baselining started
|
23
|
+
* @param {boolean} props.running - Whether the baselining is running
|
24
|
+
* @param {boolean} [props.completed] - Whether the baselining was completed
|
25
|
+
* @param {number} [props.runtime] - Duration of baselining in seconds
|
26
|
+
* @param {string} [props.answer] - The answer for the baselining
|
27
|
+
* @param {SessionLog[]} [props.sessionLogs] - The session logs for the baselining
|
28
|
+
* @returns {import("preact").JSX.Element} The component.
|
29
|
+
*/
|
30
|
+
export const HumanBaselineView = ({
|
31
|
+
started,
|
32
|
+
runtime,
|
33
|
+
answer,
|
34
|
+
completed,
|
35
|
+
running,
|
36
|
+
sessionLogs,
|
37
|
+
}) => {
|
38
|
+
const player_fns = [];
|
39
|
+
|
40
|
+
// handle creation and revoking of these URLs
|
41
|
+
const revokableUrls = [];
|
42
|
+
const revokableUrl = (data) => {
|
43
|
+
const blob = new Blob([data], { type: "text/plain" });
|
44
|
+
const url = URL.createObjectURL(blob);
|
45
|
+
revokableUrls.push(url);
|
46
|
+
return url;
|
47
|
+
};
|
48
|
+
|
49
|
+
useEffect(() => {
|
50
|
+
return () => {
|
51
|
+
revokableUrls.forEach((url) => URL.revokeObjectURL(url));
|
52
|
+
};
|
53
|
+
}, []);
|
54
|
+
|
55
|
+
// Make a player for each session log
|
56
|
+
let count = 1;
|
57
|
+
let maxCols = 0;
|
58
|
+
|
59
|
+
for (const sessionLog of sessionLogs) {
|
60
|
+
const rows = extractSize(sessionLog.output, "LINES");
|
61
|
+
const cols = extractSize(sessionLog.output, "COLUMNS");
|
62
|
+
maxCols = Math.max(maxCols, parseInt(cols));
|
63
|
+
|
64
|
+
const currentCount = count;
|
65
|
+
const title =
|
66
|
+
sessionLogs.length === 1
|
67
|
+
? "Terminal Session"
|
68
|
+
: `Terminal Session ${currentCount}`;
|
69
|
+
|
70
|
+
player_fns.push({
|
71
|
+
label: title,
|
72
|
+
render: () => html`
|
73
|
+
<${AsciiCinemaPlayer}
|
74
|
+
id=${`player-${currentCount}`}
|
75
|
+
inputUrl=${revokableUrl(sessionLog.input)}
|
76
|
+
outputUrl=${revokableUrl(sessionLog.output)}
|
77
|
+
timingUrl=${revokableUrl(sessionLog.timing)}
|
78
|
+
rows=${rows}
|
79
|
+
cols=${cols}
|
80
|
+
style=${{
|
81
|
+
maxHeight: "100vh",
|
82
|
+
maxWidth: "100vw",
|
83
|
+
height: `${parseInt(rows) * 2}em`,
|
84
|
+
width: `${parseInt(cols) * 2}em`,
|
85
|
+
}}
|
86
|
+
fit="both"
|
87
|
+
/>
|
88
|
+
`,
|
89
|
+
});
|
90
|
+
count += 1;
|
91
|
+
}
|
92
|
+
|
93
|
+
const StatusMessage = ({ completed, running, answer }) => {
|
94
|
+
if (running) {
|
95
|
+
return html`<span style=${{ ...TextStyle.label }}>Running</span>`;
|
96
|
+
} else if (completed) {
|
97
|
+
return html`<span style=${{ ...TextStyle.label, marginRight: "0.5em" }}
|
98
|
+
>Answer</span
|
99
|
+
><span>${answer}</span>`;
|
100
|
+
} else {
|
101
|
+
return "Unknown status";
|
102
|
+
}
|
103
|
+
};
|
104
|
+
|
105
|
+
return html`<div style=${{ display: "flex", justifyContent: "center" }}>
|
106
|
+
<div
|
107
|
+
style=${{
|
108
|
+
display: "grid",
|
109
|
+
gridTemplateColumns: "1fr 1fr 1fr",
|
110
|
+
gridTemplateRows: "auto auto",
|
111
|
+
width: "100%",
|
112
|
+
}}
|
113
|
+
>
|
114
|
+
<div
|
115
|
+
style=${{
|
116
|
+
justifySelf: "start",
|
117
|
+
...TextStyle.label,
|
118
|
+
}}
|
119
|
+
>
|
120
|
+
${started ? formatDateTime(started) : ""}${runtime
|
121
|
+
? ` (${formatTime(Math.floor(runtime))})`
|
122
|
+
: ""}
|
123
|
+
</div>
|
124
|
+
<div
|
125
|
+
style=${{
|
126
|
+
justifySelf: "center",
|
127
|
+
...TextStyle.label,
|
128
|
+
}}
|
129
|
+
></div>
|
130
|
+
<div
|
131
|
+
style=${{
|
132
|
+
justifySelf: "end",
|
133
|
+
}}
|
134
|
+
>
|
135
|
+
<${StatusMessage}
|
136
|
+
completed=${completed}
|
137
|
+
running=${running}
|
138
|
+
answer=${answer}
|
139
|
+
/>
|
140
|
+
</div>
|
141
|
+
<div
|
142
|
+
style=${{
|
143
|
+
gridColumn: "span 3",
|
144
|
+
width: "100%",
|
145
|
+
}}
|
146
|
+
>
|
147
|
+
<${LightboxCarousel} slides=${player_fns} />
|
148
|
+
</div>
|
149
|
+
</div>
|
150
|
+
</div>`;
|
151
|
+
};
|
152
|
+
|
153
|
+
/**
|
154
|
+
* Extracts a numeric size value from a string based on a given label.
|
155
|
+
*
|
156
|
+
* Searches the input string for a pattern matching the format `LABEL="VALUE"`,
|
157
|
+
* where `LABEL` is the provided label and `VALUE` is a numeric value.
|
158
|
+
*
|
159
|
+
* @param {string} value - The input string to search within.
|
160
|
+
* @param {string} label - The label to look for in the string.
|
161
|
+
* @returns {string | undefined} The extracted size as a string if found, otherwise `undefined`.
|
162
|
+
*/
|
163
|
+
const extractSize = (value, label) => {
|
164
|
+
const regex = new RegExp(`${label}="(\\d+)"`);
|
165
|
+
const match = value.match(regex);
|
166
|
+
const size = match ? match[1] : undefined;
|
167
|
+
return size;
|
168
|
+
};
|
@@ -0,0 +1,217 @@
|
|
1
|
+
import { html } from "htm/preact";
|
2
|
+
import { useState, useCallback, useEffect } from "preact/hooks";
|
3
|
+
import { ApplicationIcons } from "../appearance/Icons.mjs";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @typedef {Object} Slide
|
7
|
+
* @property {string} label - The label for the slide.
|
8
|
+
* @property {() => import("preact").JSX.Element} render - A function that returns another function to render the slide as a JSX element.
|
9
|
+
*/
|
10
|
+
|
11
|
+
/**
|
12
|
+
* LightboxCarousel component provides a carousel with lightbox functionality.
|
13
|
+
* @param {Object} props - Component properties.
|
14
|
+
* @property {Array<Slide>} props.slides - Array of slide render functions.
|
15
|
+
* @returns {import("preact").JSX.Element} LightboxCarousel component.
|
16
|
+
*/
|
17
|
+
export const LightboxCarousel = ({ slides }) => {
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
19
|
+
const [showOverlay, setShowOverlay] = useState(false);
|
20
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
21
|
+
|
22
|
+
const openLightbox = (index) => {
|
23
|
+
setCurrentIndex(index);
|
24
|
+
setShowOverlay(true);
|
25
|
+
|
26
|
+
// Slight delay before setting isOpen so the fade-in starts from opacity: 0
|
27
|
+
setTimeout(() => setIsOpen(true), 10);
|
28
|
+
};
|
29
|
+
|
30
|
+
const closeLightbox = () => {
|
31
|
+
setIsOpen(false);
|
32
|
+
};
|
33
|
+
|
34
|
+
// Remove the overlay from the DOM after fade-out completes
|
35
|
+
useEffect(() => {
|
36
|
+
if (!isOpen && showOverlay) {
|
37
|
+
const timer = setTimeout(() => {
|
38
|
+
setShowOverlay(false);
|
39
|
+
}, 300); // match your transition duration
|
40
|
+
return () => clearTimeout(timer);
|
41
|
+
}
|
42
|
+
}, [isOpen, showOverlay]);
|
43
|
+
|
44
|
+
const showNext = useCallback(() => {
|
45
|
+
setCurrentIndex((prev) => {
|
46
|
+
return (prev + 1) % slides.length;
|
47
|
+
});
|
48
|
+
}, [slides]);
|
49
|
+
|
50
|
+
const showPrev = useCallback(() => {
|
51
|
+
setCurrentIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
52
|
+
}, [slides]);
|
53
|
+
|
54
|
+
// Keyboard Navigation
|
55
|
+
useEffect(() => {
|
56
|
+
if (!isOpen) return;
|
57
|
+
const handleKeyUp = (e) => {
|
58
|
+
if (e.key === "Escape") {
|
59
|
+
closeLightbox();
|
60
|
+
} else if (e.key === "ArrowRight") {
|
61
|
+
showNext();
|
62
|
+
} else if (e.key === "ArrowLeft") {
|
63
|
+
showPrev();
|
64
|
+
}
|
65
|
+
e.preventDefault();
|
66
|
+
e.stopPropagation();
|
67
|
+
};
|
68
|
+
window.addEventListener("keyup", handleKeyUp, true);
|
69
|
+
return () => window.removeEventListener("keyup", handleKeyUp);
|
70
|
+
}, [isOpen, showNext, showPrev]);
|
71
|
+
|
72
|
+
// Common button style
|
73
|
+
const buttonStyle = {
|
74
|
+
position: "absolute",
|
75
|
+
top: "50%",
|
76
|
+
transform: "translateY(-50%)",
|
77
|
+
background: "none",
|
78
|
+
color: "#fff",
|
79
|
+
border: "none",
|
80
|
+
padding: "0.5em",
|
81
|
+
fontSize: "3em",
|
82
|
+
cursor: "pointer",
|
83
|
+
zIndex: "9999",
|
84
|
+
};
|
85
|
+
|
86
|
+
const prevButtonStyle = {
|
87
|
+
...buttonStyle,
|
88
|
+
left: "10px",
|
89
|
+
};
|
90
|
+
|
91
|
+
const nextButtonStyle = {
|
92
|
+
...buttonStyle,
|
93
|
+
right: "10px",
|
94
|
+
};
|
95
|
+
|
96
|
+
// Overlay style (fixed to fill the screen, flex for centering)
|
97
|
+
const overlayStyle = {
|
98
|
+
position: "fixed",
|
99
|
+
top: 0,
|
100
|
+
left: 0,
|
101
|
+
width: "100vw",
|
102
|
+
height: "100vh",
|
103
|
+
background: "rgba(0,0,0,0.8)",
|
104
|
+
display: "flex",
|
105
|
+
alignItems: "center",
|
106
|
+
justifyContent: "center",
|
107
|
+
opacity: isOpen ? "1" : "0",
|
108
|
+
visibility: isOpen ? "visible" : "hidden",
|
109
|
+
transition: "opacity 0.3s ease, visibility 0.3s ease",
|
110
|
+
zIndex: 9998,
|
111
|
+
};
|
112
|
+
|
113
|
+
// Close button style
|
114
|
+
const closeButtonWrapperStyle = {
|
115
|
+
position: "absolute",
|
116
|
+
top: "10px",
|
117
|
+
right: "10px",
|
118
|
+
};
|
119
|
+
|
120
|
+
const closeButtonStyle = {
|
121
|
+
border: "none",
|
122
|
+
background: "none",
|
123
|
+
color: "#fff",
|
124
|
+
fontSize: "3em",
|
125
|
+
fontWeight: "500",
|
126
|
+
cursor: "pointer",
|
127
|
+
paddingLeft: "1em",
|
128
|
+
paddingBottom: "1em",
|
129
|
+
zIndex: "10000",
|
130
|
+
};
|
131
|
+
|
132
|
+
// Lightbox content container style
|
133
|
+
const contentStyle = {
|
134
|
+
maxWidth: "90%",
|
135
|
+
maxHeight: "90%",
|
136
|
+
display: "flex",
|
137
|
+
alignItems: "center",
|
138
|
+
justifyContent: "center",
|
139
|
+
position: "relative",
|
140
|
+
// fade in/out
|
141
|
+
opacity: isOpen ? "1" : "0",
|
142
|
+
visibility: isOpen ? "visible" : "hidden",
|
143
|
+
transition: "opacity 0.3s ease, visibility 0.3s ease",
|
144
|
+
zIndex: 9999,
|
145
|
+
};
|
146
|
+
|
147
|
+
return html`
|
148
|
+
<div className="lightbox-carousel-container">
|
149
|
+
<!-- Thumbnails -->
|
150
|
+
<div
|
151
|
+
className="carousel-thumbs"
|
152
|
+
style=${{
|
153
|
+
display: "grid",
|
154
|
+
gridTemplateColumns: "auto auto auto auto",
|
155
|
+
}}
|
156
|
+
>
|
157
|
+
${slides.map((slide, index) => {
|
158
|
+
return html`
|
159
|
+
<div
|
160
|
+
key=${index}
|
161
|
+
className="carousel-thumb"
|
162
|
+
onClick=${() => openLightbox(index)}
|
163
|
+
style=${{
|
164
|
+
background: "black",
|
165
|
+
color: "white",
|
166
|
+
padding: "4em 0",
|
167
|
+
border: "0",
|
168
|
+
margin: "5px",
|
169
|
+
cursor: "pointer",
|
170
|
+
textAlign: "center",
|
171
|
+
}}
|
172
|
+
>
|
173
|
+
<div>${slide.label}</div>
|
174
|
+
<div>
|
175
|
+
<i
|
176
|
+
class=${ApplicationIcons.play}
|
177
|
+
style=${{ fontSize: "4em" }}
|
178
|
+
></i>
|
179
|
+
</div>
|
180
|
+
</div>
|
181
|
+
`;
|
182
|
+
})}
|
183
|
+
</div>
|
184
|
+
|
185
|
+
<!-- Lightbox Overlay -->
|
186
|
+
${showOverlay &&
|
187
|
+
html`
|
188
|
+
<div className="lightbox-overlay" style=${overlayStyle}>
|
189
|
+
<div style=${closeButtonWrapperStyle}>
|
190
|
+
<button style=${closeButtonStyle} onClick=${closeLightbox}>
|
191
|
+
<i class=${ApplicationIcons.close}></i>
|
192
|
+
</button>
|
193
|
+
</div>
|
194
|
+
|
195
|
+
${slides.length > 1
|
196
|
+
? html` <button style=${prevButtonStyle} onClick=${showPrev}>
|
197
|
+
<i class=${ApplicationIcons.previous}></i>
|
198
|
+
</button>`
|
199
|
+
: ""}
|
200
|
+
${slides.length > 1
|
201
|
+
? html` <button style=${nextButtonStyle} onClick=${showNext}>
|
202
|
+
<i class=${ApplicationIcons.next}></i>
|
203
|
+
</button>`
|
204
|
+
: ""}
|
205
|
+
|
206
|
+
<div
|
207
|
+
key=${`carousel-slide-${currentIndex}`}
|
208
|
+
className="lightbox-content"
|
209
|
+
style=${contentStyle}
|
210
|
+
>
|
211
|
+
${slides[currentIndex].render()}
|
212
|
+
</div>
|
213
|
+
</div>
|
214
|
+
`}
|
215
|
+
</div>
|
216
|
+
`;
|
217
|
+
};
|
@@ -51,7 +51,7 @@ export const resolveToolInput = (fn, toolArgs) => {
|
|
51
51
|
* @param {string | undefined } props.input - The main input for this call
|
52
52
|
* @param {string | undefined } props.inputType - The input type for this call
|
53
53
|
* @param {import("../types/log").ToolCallContent} props.view - The tool call view
|
54
|
-
* @param {string | number | boolean | (import("../types/log").ContentText | import("../types/log").ContentImage)[]} props.output - The tool output
|
54
|
+
* @param {string | number | boolean | import("../types/log").ContentText | import("../types/log").ContentImage | (import("../types/log").ContentText | import("../types/log").ContentImage)[]} props.output - The tool output
|
55
55
|
* @param { "compact" | undefined } props.mode - The display mode for this call
|
56
56
|
* @returns {import("preact").JSX.Element} The SampleTranscript component.
|
57
57
|
*/
|
@@ -63,6 +63,14 @@ export const ToolCallView = ({
|
|
63
63
|
output,
|
64
64
|
mode,
|
65
65
|
}) => {
|
66
|
+
// don't collapse if output includes an image
|
67
|
+
function isContentImage(value) {
|
68
|
+
return value && typeof value === "object" && value.type === "image";
|
69
|
+
}
|
70
|
+
const collapse = Array.isArray(output)
|
71
|
+
? output.every((item) => !isContentImage(item))
|
72
|
+
: !isContentImage(output);
|
73
|
+
|
66
74
|
return html`<div>
|
67
75
|
${mode !== "compact" && (!view || view.title)
|
68
76
|
? html`<${ToolTitle} title=${view?.title || functionCall} />`
|
@@ -77,7 +85,7 @@ export const ToolCallView = ({
|
|
77
85
|
/>
|
78
86
|
${output
|
79
87
|
? html`
|
80
|
-
<${ExpandablePanel} collapse=${
|
88
|
+
<${ExpandablePanel} collapse=${collapse} border=${true} lines=${15}>
|
81
89
|
<${MessageContent} contents=${normalizeContent(output)} />
|
82
90
|
</${ExpandablePanel}>`
|
83
91
|
: ""}
|
@@ -107,7 +115,7 @@ const ToolTitle = ({ title }) => {
|
|
107
115
|
/**
|
108
116
|
* Renders the ToolCallView component.
|
109
117
|
*
|
110
|
-
* @param {string | number | boolean | (import("../types/log").ContentText | import("../types/log").ContentImage)[]} output - The tool output
|
118
|
+
* @param {string | number | boolean | import("../types/log").ContentImage | import("../types/log").ContentText | (import("../types/log").ContentText | import("../types/log").ContentImage)[]} output - The tool output
|
111
119
|
* @returns {(import("../Types.mjs").ContentTool | import("../types/log").ContentText | import("../types/log").ContentImage)[]} The SampleTranscript component.
|
112
120
|
*/
|
113
121
|
const normalizeContent = (output) => {
|
@@ -60,10 +60,11 @@ export const ModelEventView = ({ id, event, style }) => {
|
|
60
60
|
};
|
61
61
|
|
62
62
|
// For any user messages which immediately preceded this model call, including a
|
63
|
-
// panel and display those user messages
|
63
|
+
// panel and display those user messages (exclude tool_call messages as they
|
64
|
+
// are already shown in the tool call above)
|
64
65
|
const userMessages = [];
|
65
66
|
for (const msg of event.input.slice().reverse()) {
|
66
|
-
if (msg.role === "user") {
|
67
|
+
if (msg.role === "user" && !msg.tool_call_id) {
|
67
68
|
userMessages.push(msg);
|
68
69
|
} else {
|
69
70
|
break;
|
@@ -2,6 +2,7 @@
|
|
2
2
|
import { html } from "htm/preact";
|
3
3
|
import { ChatView } from "../../../components/ChatView.mjs";
|
4
4
|
import { FontSize, TextStyle } from "../../../appearance/Fonts.mjs";
|
5
|
+
import { HumanBaselineView } from "../../../components/HumanBaselineView.mjs";
|
5
6
|
|
6
7
|
/**
|
7
8
|
* @typedef {Object} Signature
|
@@ -62,6 +63,58 @@ const add_tools = {
|
|
62
63
|
},
|
63
64
|
};
|
64
65
|
|
66
|
+
const humanAgentKey = (key) => {
|
67
|
+
return `HumanAgentState:${key}`;
|
68
|
+
};
|
69
|
+
const human_baseline_session = {
|
70
|
+
type: "human_baseline_session",
|
71
|
+
signature: {
|
72
|
+
add: ["HumanAgentState:logs"],
|
73
|
+
replace: [],
|
74
|
+
remove: [],
|
75
|
+
},
|
76
|
+
render: (changes, resolvedState) => {
|
77
|
+
// Read the session values
|
78
|
+
const started = resolvedState[humanAgentKey("started_running")];
|
79
|
+
const runtime = resolvedState[humanAgentKey("accumulated_time")];
|
80
|
+
const answer = resolvedState[humanAgentKey("answer")];
|
81
|
+
const completed = !!answer;
|
82
|
+
const running = resolvedState[humanAgentKey("running_state")];
|
83
|
+
const rawSessions = resolvedState[humanAgentKey("logs")];
|
84
|
+
|
85
|
+
// Tweak the date value
|
86
|
+
const startedDate = started ? new Date(started * 1000) : undefined;
|
87
|
+
|
88
|
+
// Convert raw sessions into session logs
|
89
|
+
const sessions = {};
|
90
|
+
if (rawSessions) {
|
91
|
+
for (const key of Object.keys(rawSessions)) {
|
92
|
+
const value = rawSessions[key];
|
93
|
+
// this pulls the key apart into
|
94
|
+
// <user>_<timestamp>.<type>
|
95
|
+
const match = key.match(/(.*)_(\d+_\d+)\.(.*)/);
|
96
|
+
if (match) {
|
97
|
+
const user = match[1];
|
98
|
+
const timestamp = match[2];
|
99
|
+
const type = match[3];
|
100
|
+
sessions[timestamp] = sessions[timestamp] || {};
|
101
|
+
sessions[timestamp][type] = value;
|
102
|
+
sessions[timestamp]["user"] = user;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
return html`<${HumanBaselineView}
|
108
|
+
started=${startedDate}
|
109
|
+
running=${running}
|
110
|
+
completed=${completed}
|
111
|
+
answer=${answer}
|
112
|
+
runtime=${runtime}
|
113
|
+
sessionLogs=${Object.values(sessions)}
|
114
|
+
/>`;
|
115
|
+
},
|
116
|
+
};
|
117
|
+
|
65
118
|
const renderTools = (changes, resolvedState) => {
|
66
119
|
// Find which tools were added in this change
|
67
120
|
const toolIndexes = [];
|
@@ -136,6 +189,9 @@ export const RenderableChangeTypes = [
|
|
136
189
|
add_tools,
|
137
190
|
];
|
138
191
|
|
192
|
+
/** @type {ChangeType[]} */
|
193
|
+
export const StoreSpecificRenderableTypes = [human_baseline_session];
|
194
|
+
|
139
195
|
/**
|
140
196
|
* @typedef {Object} ToolParameters
|
141
197
|
* @property {string} type - The type of the parameters object, typically "object".
|
@@ -2,7 +2,10 @@
|
|
2
2
|
import { html } from "htm/preact";
|
3
3
|
|
4
4
|
import { EventPanel } from "../EventPanel.mjs";
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
RenderableChangeTypes,
|
7
|
+
StoreSpecificRenderableTypes,
|
8
|
+
} from "./StateEventRenderers.mjs";
|
6
9
|
import { StateDiffView } from "./StateDiffView.mjs";
|
7
10
|
import { formatDateTime } from "../../../utils/Format.mjs";
|
8
11
|
|
@@ -12,10 +15,11 @@ import { formatDateTime } from "../../../utils/Format.mjs";
|
|
12
15
|
* @param {Object} props - The properties passed to the component.
|
13
16
|
* @param { string } props.id - The id of this event.
|
14
17
|
* @param {import("../../../types/log").StateEvent } props.event - The event object to display.
|
18
|
+
* @param { boolean } props.isStore - Whether this event view is rendering a storage (rather than a state)
|
15
19
|
* @param { Object } props.style - The style of this event.
|
16
20
|
* @returns {import("preact").JSX.Element} The component.
|
17
21
|
*/
|
18
|
-
export const StateEventView = ({ id, event, style }) => {
|
22
|
+
export const StateEventView = ({ id, event, isStore, style }) => {
|
19
23
|
const summary = summarizeChanges(event.changes);
|
20
24
|
|
21
25
|
// Synthesize objects for comparison
|
@@ -32,7 +36,11 @@ export const StateEventView = ({ id, event, style }) => {
|
|
32
36
|
// This clone is important since the state is used by preact as potential values that are rendered
|
33
37
|
// and as a result may be decorated with additional properties, etc..., resulting in DOM elements
|
34
38
|
// appearing attached to state.
|
35
|
-
const changePreview = generatePreview(
|
39
|
+
const changePreview = generatePreview(
|
40
|
+
event.changes,
|
41
|
+
structuredClone(after),
|
42
|
+
isStore,
|
43
|
+
);
|
36
44
|
if (changePreview) {
|
37
45
|
tabs.unshift(
|
38
46
|
html`<div name="Summary" style=${{ margin: "1em 0em", width: "100%" }}>
|
@@ -55,11 +63,15 @@ export const StateEventView = ({ id, event, style }) => {
|
|
55
63
|
*
|
56
64
|
* @param {import("../../../types/log").JsonChange[]} changes - The change object containing the value.
|
57
65
|
* @param {Object} resolvedState - The change object containing the value.
|
66
|
+
* @param {boolean} isStore - Is this rendering a store event
|
58
67
|
* @returns {import("preact").JSX.Element|Object|string|undefined} - The rendered HTML template if the value is an object with content and source, otherwise the value itself.
|
59
68
|
*/
|
60
|
-
const generatePreview = (changes, resolvedState) => {
|
69
|
+
const generatePreview = (changes, resolvedState, isStore) => {
|
61
70
|
const results = [];
|
62
|
-
for (const changeType of
|
71
|
+
for (const changeType of [
|
72
|
+
...RenderableChangeTypes,
|
73
|
+
...(isStore ? StoreSpecificRenderableTypes : []),
|
74
|
+
]) {
|
63
75
|
// Note that we currently only have renderers that depend upon
|
64
76
|
// add, remove, replace, but we should likely add
|
65
77
|
// move, copy, test
|
@@ -0,0 +1,26 @@
|
|
1
|
+
declare module "asciinema-player" {
|
2
|
+
export const create: (
|
3
|
+
src: string | Object,
|
4
|
+
el: HTMLElement,
|
5
|
+
opts: {
|
6
|
+
cols?: number;
|
7
|
+
rows?: number;
|
8
|
+
autoPlay?: boolean;
|
9
|
+
preload?: boolean;
|
10
|
+
loop?: boolean;
|
11
|
+
theme?: string;
|
12
|
+
startAt?: number | string;
|
13
|
+
speed?: number;
|
14
|
+
idleTimeLimit?: number;
|
15
|
+
poster?: string;
|
16
|
+
fit?: string;
|
17
|
+
controls?: boolean;
|
18
|
+
markers?: Array<number> | Array<[number, string]>;
|
19
|
+
pauseOnMarkers?: boolean;
|
20
|
+
terminalFontSize?: string;
|
21
|
+
terminalFontFamily?: string;
|
22
|
+
terminalLineHeight?: string;
|
23
|
+
logger?: Object;
|
24
|
+
},
|
25
|
+
) => any;
|
26
|
+
}
|