@opengeni/react 0.48.0 → 0.52.1

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 (284) hide show
  1. package/README.md +90 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +170 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/chunk-6BBWWOOU.js +82 -0
  15. package/dist/chunk-6BBWWOOU.js.map +1 -0
  16. package/dist/{chunk-FWXGSNXT.js → chunk-724BW4TW.js} +585 -34
  17. package/dist/chunk-724BW4TW.js.map +1 -0
  18. package/dist/chunk-7I2KB3OK.js +3895 -0
  19. package/dist/chunk-7I2KB3OK.js.map +1 -0
  20. package/dist/chunk-7WCLAEFV.js +2828 -0
  21. package/dist/chunk-7WCLAEFV.js.map +1 -0
  22. package/dist/{chunk-I4MPFDRP.js → chunk-FWS3KC3Z.js} +3 -3
  23. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  24. package/dist/chunk-HDBG7XGO.js.map +1 -0
  25. package/dist/chunk-HUIKIDM5.js +2148 -0
  26. package/dist/chunk-HUIKIDM5.js.map +1 -0
  27. package/dist/{chunk-XCNTXWBB.js → chunk-JAGDUCZQ.js} +854 -340
  28. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  29. package/dist/{chunk-LHLYWG5R.js → chunk-JR3QFY6B.js} +413 -37
  30. package/dist/chunk-JR3QFY6B.js.map +1 -0
  31. package/dist/chunk-JVYQRRC4.js +53 -0
  32. package/dist/chunk-JVYQRRC4.js.map +1 -0
  33. package/dist/chunk-KCGXPP5V.js +157 -0
  34. package/dist/chunk-KCGXPP5V.js.map +1 -0
  35. package/dist/chunk-PEI6RANI.js +3234 -0
  36. package/dist/chunk-PEI6RANI.js.map +1 -0
  37. package/dist/{chunk-NJ7M2B6Z.js → chunk-ROIRTM37.js} +18 -10
  38. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-ROIRTM37.js.map} +1 -1
  39. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  40. package/dist/chunk-WSK7G5LE.js.map +1 -0
  41. package/dist/{chunk-OL6QVXGF.js → chunk-X4S56Y2H.js} +27 -7
  42. package/dist/{chunk-OL6QVXGF.js.map → chunk-X4S56Y2H.js.map} +1 -1
  43. package/dist/client.d.ts +7 -1
  44. package/dist/clipboard.d.ts +1 -0
  45. package/dist/clipboard.js +7 -0
  46. package/dist/clipboard.js.map +1 -0
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/registry.d.ts +1 -1
  49. package/dist/commands/types.d.ts +1 -1
  50. package/dist/components/approval-surface.d.ts +2 -2
  51. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  52. package/dist/components/artifacts/document-editor.d.ts +172 -0
  53. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  54. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  55. package/dist/components/artifacts/editable-document.d.ts +24 -0
  56. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  57. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  58. package/dist/components/artifacts/index.d.ts +8 -0
  59. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  60. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  61. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  62. package/dist/components/browser-viewer.d.ts +29 -0
  63. package/dist/components/chat-composer.d.ts +8 -8
  64. package/dist/components/code-editor.d.ts +25 -3
  65. package/dist/components/command-palette.d.ts +2 -2
  66. package/dist/components/composer-transcription-control.d.ts +2 -2
  67. package/dist/components/composer.d.ts +17 -17
  68. package/dist/components/computer-viewer.d.ts +23 -0
  69. package/dist/components/copy-button.d.ts +2 -2
  70. package/dist/components/desktop-viewer.d.ts +25 -21
  71. package/dist/components/diff-view.d.ts +1 -1
  72. package/dist/components/enrollment-consent.d.ts +1 -1
  73. package/dist/components/enrollment-device-flow.d.ts +1 -1
  74. package/dist/components/file-browser.d.ts +2 -2
  75. package/dist/components/fleet-tile.d.ts +1 -1
  76. package/dist/components/generated-video-player.d.ts +10 -0
  77. package/dist/components/human-input-form.d.ts +1 -1
  78. package/dist/components/human-input-surface.d.ts +2 -2
  79. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  80. package/dist/components/machine-card.d.ts +2 -2
  81. package/dist/components/machine-dock-bar.d.ts +3 -3
  82. package/dist/components/machine-health-pill.d.ts +2 -2
  83. package/dist/components/machine-input-display.d.ts +1 -1
  84. package/dist/components/machine-metrics.d.ts +2 -2
  85. package/dist/components/machine-status-pill.d.ts +4 -4
  86. package/dist/components/machines/health.d.ts +1 -1
  87. package/dist/components/machines/machine-detail.d.ts +3 -3
  88. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  89. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  90. package/dist/components/machines/series.d.ts +2 -2
  91. package/dist/components/machines-dashboard.d.ts +2 -2
  92. package/dist/components/markdown.d.ts +1 -1
  93. package/dist/components/message-timeline.d.ts +12 -5
  94. package/dist/components/model-picker.d.ts +2 -2
  95. package/dist/components/model-policy-picker.d.ts +7 -7
  96. package/dist/components/pierre-diff.d.ts +1 -1
  97. package/dist/components/pierre-file.d.ts +1 -1
  98. package/dist/components/queue-draft-policy.d.ts +1 -1
  99. package/dist/components/queue-surface-implementation.d.ts +3 -3
  100. package/dist/components/queue-surface-state.d.ts +4 -4
  101. package/dist/components/queue-surface.d.ts +5 -5
  102. package/dist/components/sandbox-files.d.ts +3 -3
  103. package/dist/components/sandbox-terminal.d.ts +21 -4
  104. package/dist/components/sandbox-workspace.d.ts +25 -9
  105. package/dist/components/session-chrome.d.ts +4 -4
  106. package/dist/components/session-status.d.ts +2 -2
  107. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  108. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  109. package/dist/components/timeline-annotations.d.ts +13 -0
  110. package/dist/components/tooltip.d.ts +4 -4
  111. package/dist/components/user-message-body.d.ts +2 -2
  112. package/dist/components/workbench-changes.d.ts +2 -2
  113. package/dist/components/workspace-dock.d.ts +1 -1
  114. package/dist/composer.d.ts +11 -11
  115. package/dist/composer.js +5 -5
  116. package/dist/hooks/internal.d.ts +1 -1
  117. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  118. package/dist/hooks/use-available-models.d.ts +2 -2
  119. package/dist/hooks/use-billing-usage.d.ts +1 -1
  120. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  121. package/dist/hooks/use-browser-identities.d.ts +26 -0
  122. package/dist/hooks/use-browser-session.d.ts +28 -0
  123. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  124. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  125. package/dist/hooks/use-composer.d.ts +36 -9
  126. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  127. package/dist/hooks/use-computer-session.d.ts +28 -0
  128. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  129. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  130. package/dist/hooks/use-environments.d.ts +1 -1
  131. package/dist/hooks/use-file-attachments.d.ts +1 -1
  132. package/dist/hooks/use-goal.d.ts +2 -2
  133. package/dist/hooks/use-human-input.d.ts +2 -2
  134. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  135. package/dist/hooks/use-machine-chip.d.ts +2 -2
  136. package/dist/hooks/use-machines.d.ts +4 -4
  137. package/dist/hooks/use-packs.d.ts +7 -2
  138. package/dist/hooks/use-rigs.d.ts +1 -1
  139. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  140. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  141. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  142. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  143. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  144. package/dist/hooks/use-session-control.d.ts +1 -1
  145. package/dist/hooks/use-session-events.d.ts +2 -2
  146. package/dist/hooks/use-session-lineage.d.ts +1 -1
  147. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  148. package/dist/hooks/use-session.d.ts +2 -2
  149. package/dist/hooks/use-slash-commands.d.ts +1 -1
  150. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  151. package/dist/hooks/use-turn-queue.d.ts +2 -2
  152. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  153. package/dist/hooks/use-voice-input.d.ts +1 -1
  154. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  155. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  156. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  157. package/dist/hooks/use-workspaces.d.ts +1 -1
  158. package/dist/index.d.ts +158 -155
  159. package/dist/index.js +906 -462
  160. package/dist/index.js.map +1 -1
  161. package/dist/interaction.d.ts +23 -0
  162. package/dist/interaction.js +36 -0
  163. package/dist/interaction.js.map +1 -0
  164. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  165. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  166. package/dist/lib/relay-wire.d.ts +4 -0
  167. package/dist/lib/sandbox-liveness.d.ts +9 -0
  168. package/dist/lib/terminal-capability.d.ts +5 -0
  169. package/dist/lib/xterm-theme.d.ts +1 -1
  170. package/dist/machines.d.ts +30 -30
  171. package/dist/machines.js +3 -3
  172. package/dist/provider.d.ts +4 -4
  173. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  174. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  175. package/dist/realtime/dropdown-menu.d.ts +8 -8
  176. package/dist/realtime/realtime-control.d.ts +6 -6
  177. package/dist/realtime.d.ts +4 -4
  178. package/dist/realtime.js +3 -3
  179. package/dist/session-context.d.ts +19 -1
  180. package/dist/session-ui.d.ts +14 -14
  181. package/dist/session-ui.js +5 -4
  182. package/dist/session.d.ts +28 -28
  183. package/dist/session.js +5 -5
  184. package/dist/timeline/activity-rail.d.ts +5 -3
  185. package/dist/timeline/disclosure-context.d.ts +1 -1
  186. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  187. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  188. package/dist/timeline/index.d.ts +19 -19
  189. package/dist/timeline/parsers.d.ts +4 -1
  190. package/dist/timeline/projection.d.ts +3 -3
  191. package/dist/timeline/registry.d.ts +8 -2
  192. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  193. package/dist/timeline/shared.d.ts +21 -10
  194. package/dist/timeline/tool-diff.d.ts +2 -2
  195. package/dist/timeline/tool-renderers.d.ts +1 -1
  196. package/dist/timeline/turn-summary.d.ts +3 -3
  197. package/dist/timeline/types.d.ts +39 -3
  198. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  199. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  200. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  201. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  202. package/package.json +36 -7
  203. package/src/artifacts-document.ts +31 -0
  204. package/src/artifacts-presentation.ts +36 -0
  205. package/src/artifacts-spreadsheet.ts +21 -0
  206. package/src/artifacts.ts +86 -0
  207. package/src/client.ts +87 -1
  208. package/src/clipboard.ts +2 -0
  209. package/src/components/artifacts/artifact-surface.tsx +93 -0
  210. package/src/components/artifacts/document-editor.tsx +2255 -0
  211. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  212. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  213. package/src/components/artifacts/editable-document.tsx +657 -0
  214. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  215. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  216. package/src/components/artifacts/index.ts +99 -0
  217. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  218. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  219. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  220. package/src/components/browser-viewer.tsx +1592 -0
  221. package/src/components/chat-composer.tsx +13 -0
  222. package/src/components/code-editor.tsx +89 -30
  223. package/src/components/composer-transcription-control.tsx +10 -6
  224. package/src/components/computer-viewer.tsx +1139 -0
  225. package/src/components/desktop-viewer.tsx +101 -73
  226. package/src/components/generated-video-player.tsx +89 -0
  227. package/src/components/interactive-workbench-viewers.ts +2 -0
  228. package/src/components/machine-input-display.ts +3 -0
  229. package/src/components/message-timeline.tsx +289 -32
  230. package/src/components/queue-surface-implementation.tsx +116 -60
  231. package/src/components/sandbox-files.tsx +2 -2
  232. package/src/components/sandbox-terminal.tsx +106 -41
  233. package/src/components/sandbox-workspace.tsx +215 -87
  234. package/src/components/session-chrome.tsx +53 -13
  235. package/src/components/timeline-annotation-selection.tsx +221 -0
  236. package/src/components/timeline-annotations-dialog.tsx +213 -0
  237. package/src/components/timeline-annotations.tsx +84 -0
  238. package/src/components/workbench-changes.tsx +188 -31
  239. package/src/hooks/use-attached-browsers.ts +152 -0
  240. package/src/hooks/use-browser-frame-stream.ts +316 -0
  241. package/src/hooks/use-browser-identities.ts +225 -0
  242. package/src/hooks/use-browser-session.ts +476 -0
  243. package/src/hooks/use-browser-sessions.ts +281 -0
  244. package/src/hooks/use-composer.ts +769 -24
  245. package/src/hooks/use-computer-frame-stream.ts +311 -0
  246. package/src/hooks/use-computer-session.ts +442 -0
  247. package/src/hooks/use-computer-sessions.ts +245 -0
  248. package/src/hooks/use-machine-chip.ts +5 -3
  249. package/src/hooks/use-machines.ts +33 -3
  250. package/src/hooks/use-packs.ts +51 -0
  251. package/src/hooks/use-sandbox-files.ts +98 -55
  252. package/src/hooks/use-sandbox-git.ts +93 -38
  253. package/src/hooks/use-sandbox-terminal.ts +3 -2
  254. package/src/hooks/use-session-capabilities.ts +59 -46
  255. package/src/hooks/use-terminal-stream.ts +102 -23
  256. package/src/hooks/use-video-artifact-playback.ts +28 -0
  257. package/src/hooks/use-workspace-edit.ts +2 -1
  258. package/src/index.ts +10 -0
  259. package/src/interaction.ts +71 -0
  260. package/src/lib/relay-wire.ts +4 -0
  261. package/src/lib/sandbox-liveness.ts +11 -0
  262. package/src/lib/terminal-capability.ts +10 -0
  263. package/src/session-context.ts +90 -0
  264. package/src/timeline/activity-rail.tsx +19 -3
  265. package/src/timeline/index.ts +3 -0
  266. package/src/timeline/parsers.ts +14 -1
  267. package/src/timeline/projection.ts +211 -14
  268. package/src/timeline/registry.ts +13 -1
  269. package/src/timeline/screenshot-lightbox.tsx +19 -9
  270. package/src/timeline/shared.tsx +56 -8
  271. package/src/timeline/tool-renderers.tsx +303 -50
  272. package/src/timeline/types.ts +55 -3
  273. package/styles/compiled.css +248 -15
  274. package/styles/index.css +15 -0
  275. package/styles/responsive.css +13 -1
  276. package/dist/chunk-EKZH6IDG.js.map +0 -1
  277. package/dist/chunk-FWXGSNXT.js.map +0 -1
  278. package/dist/chunk-LHLYWG5R.js.map +0 -1
  279. package/dist/chunk-O4VXOBGF.js.map +0 -1
  280. package/dist/chunk-XCNTXWBB.js.map +0 -1
  281. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  282. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  283. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
  284. /package/dist/{chunk-I4MPFDRP.js.map → chunk-FWS3KC3Z.js.map} +0 -0
@@ -1,9 +1,10 @@
1
1
  import { CameraIcon, CameraOffIcon, ChevronRightIcon } from "lucide-react";
2
- import { useState, type ReactNode } from "react";
2
+ import { createContext, useContext, useState, type ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { stringifyPayload } from "../lib/format";
5
5
  import { useForcedDefaultOpen } from "./disclosure-context";
6
6
  import { useLightboxOptional } from "./screenshot-lightbox";
7
+ import type { ToolCallTruncation } from "./types";
7
8
 
8
9
  /* ----------------------------------------------------------------------------
9
10
  Shared timeline primitives
@@ -37,6 +38,22 @@ export type DisclosureChip = {
37
38
  text: string;
38
39
  };
39
40
 
41
+ const ToolCallTruncationContext = createContext<ToolCallTruncation | null>(null);
42
+
43
+ export function ToolCallTruncationProvider({
44
+ value,
45
+ children,
46
+ }: {
47
+ value: ToolCallTruncation | null;
48
+ children: ReactNode;
49
+ }) {
50
+ return (
51
+ <ToolCallTruncationContext.Provider value={value}>
52
+ {children}
53
+ </ToolCallTruncationContext.Provider>
54
+ );
55
+ }
56
+
40
57
  export type ActivityDisclosureProps = {
41
58
  icon: ReactNode;
42
59
  /** Icon tint. Defaults to the muted foreground; renderers pass accent/failed. */
@@ -81,6 +98,8 @@ export type ActivityDisclosureProps = {
81
98
  cancelled?: boolean | undefined;
82
99
  /** When false the row is a static line (no expand affordance). */
83
100
  expandable?: boolean | undefined;
101
+ /** Seed this individual disclosure open; user interaction still owns it afterwards. */
102
+ defaultOpen?: boolean | undefined;
84
103
  children?: ReactNode | undefined;
85
104
  };
86
105
 
@@ -109,6 +128,7 @@ export function ActivityDisclosure({
109
128
  failed,
110
129
  cancelled,
111
130
  expandable = true,
131
+ defaultOpen,
112
132
  children,
113
133
  }: ActivityDisclosureProps) {
114
134
  // `failed` takes precedence over `cancelled` when both are set (shouldn't happen, but be safe).
@@ -128,8 +148,9 @@ export function ActivityDisclosure({
128
148
  // An ancestor may seed the initial open state (screenshot instrumentation);
129
149
  // absent in normal app usage, where the row starts collapsed.
130
150
  const forcedDefaultOpen = useForcedDefaultOpen();
131
- const [open, setOpen] = useState(forcedDefaultOpen ?? false);
132
- const hasBody = expandable && children != null;
151
+ const [open, setOpen] = useState(defaultOpen ?? forcedDefaultOpen ?? false);
152
+ const truncation = useContext(ToolCallTruncationContext);
153
+ const hasBody = expandable && (children != null || truncation != null);
133
154
 
134
155
  // The preview is detail-on-demand: it is suppressed once the row is open so a
135
156
  // path/stat shown in the body never also sits in the collapsed row.
@@ -184,7 +205,7 @@ export function ActivityDisclosure({
184
205
  )}
185
206
  {/* The right gutter carries at most ONE signal: the media thumbnail, else a
186
207
  terse settle chip (hidden once expanded — the body owns the detail). */}
187
- {media ? (
208
+ {media && !open ? (
188
209
  <span className="ml-auto flex shrink-0 items-center gap-2 pl-2">{media}</span>
189
210
  ) : chip && !open ? (
190
211
  <span className="ml-auto shrink-0 pl-2">
@@ -235,12 +256,31 @@ export function ActivityDisclosure({
235
256
  {open ? (
236
257
  <div className="mb-2 ml-7 mt-1.5 flex flex-col gap-2 overflow-hidden animate-og-expand">
237
258
  {children}
259
+ {truncation ? <ToolCallTruncationNotice truncation={truncation} /> : null}
238
260
  </div>
239
261
  ) : null}
240
262
  </div>
241
263
  );
242
264
  }
243
265
 
266
+ function ToolCallTruncationNotice({ truncation }: { truncation: ToolCallTruncation }) {
267
+ const fullEvidence = truncation.fullEvidence.available
268
+ ? "retained"
269
+ : (truncation.fullEvidence.reason ?? "unavailable");
270
+ return (
271
+ <div data-og-tool-output-truncation="" className="text-og-sm leading-5 text-og-fg-subtle">
272
+ Output bounded at <code className="font-og-mono">{truncation.surface}</code>
273
+ {truncation.omittedBytes != null && truncation.omittedBytes > 0
274
+ ? ` · ${truncation.omittedBytes.toLocaleString()} bytes omitted`
275
+ : null}
276
+ {" · reason "}
277
+ <code className="font-og-mono">{truncation.reason}</code>
278
+ {" · full evidence "}
279
+ <code className="font-og-mono">{fullEvidence}</code>
280
+ </div>
281
+ );
282
+ }
283
+
244
284
  /**
245
285
  * The right-gutter settle chip. Color is spent only on the exception: a failure
246
286
  * is a red dot + red text (the one colored token in a healthy run); success and
@@ -467,10 +507,14 @@ export function Thumbnail({
467
507
  src,
468
508
  caption,
469
509
  alt = "screenshot",
510
+ expandLabel = "Expand screenshot",
511
+ lightboxLabel = "Screenshot",
470
512
  }: {
471
513
  src: string;
472
514
  caption?: string | undefined;
473
515
  alt?: string;
516
+ expandLabel?: string | undefined;
517
+ lightboxLabel?: string | undefined;
474
518
  }) {
475
519
  const lightbox = useLightboxOptional();
476
520
  const [failed, setFailed] = useState(false);
@@ -498,7 +542,7 @@ export function Thumbnail({
498
542
  type="button"
499
543
  onClick={(event) => {
500
544
  event.stopPropagation();
501
- lightbox.open(src, caption, event.currentTarget);
545
+ lightbox.open(src, caption, event.currentTarget, lightboxLabel);
502
546
  }}
503
547
  onKeyDown={(event) => {
504
548
  if (event.key === "Enter" || event.key === " ") {
@@ -510,7 +554,7 @@ export function Thumbnail({
510
554
  "group/thumb relative inline-flex overflow-hidden bg-og-bg outline-hidden",
511
555
  "focus-visible:ring-2 focus-visible:ring-og-accent",
512
556
  )}
513
- aria-label="Expand screenshot"
557
+ aria-label={expandLabel}
514
558
  >
515
559
  {img}
516
560
  </button>
@@ -527,10 +571,14 @@ export function ScreenshotFigure({
527
571
  src,
528
572
  caption,
529
573
  alt = "screenshot",
574
+ expandLabel = "Expand screenshot",
575
+ lightboxLabel = "Screenshot",
530
576
  }: {
531
577
  src: string;
532
578
  caption?: string | undefined;
533
579
  alt?: string;
580
+ expandLabel?: string | undefined;
581
+ lightboxLabel?: string | undefined;
534
582
  }) {
535
583
  const lightbox = useLightboxOptional();
536
584
  const [failed, setFailed] = useState(false);
@@ -554,9 +602,9 @@ export function ScreenshotFigure({
554
602
  ) : lightbox ? (
555
603
  <button
556
604
  type="button"
557
- onClick={(event) => lightbox.open(src, caption, event.currentTarget)}
605
+ onClick={(event) => lightbox.open(src, caption, event.currentTarget, lightboxLabel)}
558
606
  className={surface}
559
- aria-label="Expand screenshot"
607
+ aria-label={expandLabel}
560
608
  >
561
609
  {img}
562
610
  </button>
@@ -26,15 +26,17 @@ import {
26
26
  Share2Icon,
27
27
  TargetIcon,
28
28
  TerminalIcon,
29
+ VideoIcon,
29
30
  WrenchIcon,
30
31
  } from "lucide-react";
31
- import { useEffect, useState, type ReactNode } from "react";
32
+ import { useEffect, useRef, useState, type ReactNode } from "react";
32
33
  import { stringifyPayload, tryParseJson } from "../lib/format";
33
34
  import { useTimelineComputeLabel } from "./compute-label";
34
35
  import {
35
36
  applyPatchOpsFromToolItem,
36
37
  controlCaret,
37
38
  execTruncated,
39
+ generatedImageReceipt,
38
40
  isExecSessionLostBanner,
39
41
  looksBinary,
40
42
  mediaPreviewFact,
@@ -664,11 +666,14 @@ function ComputerCallRenderer({ item, loadRetainedScreenshot }: ToolRendererProp
664
666
  );
665
667
  }
666
668
  return (
667
- <RetainedScreenshotDisclosure
669
+ <RetainedSessionImageDisclosure
668
670
  artifact={retained}
669
671
  load={loadRetainedScreenshot}
670
- verb={verb}
671
- countSuffix={countSuffix}
672
+ title={`${verb}${countSuffix}`}
673
+ caption={`${verb}${countSuffix}`}
674
+ noun="screenshot"
675
+ icon={<CameraIcon className={ICON_SIZE} />}
676
+ lightboxLabel="Screenshot"
672
677
  batched={batched}
673
678
  failed={isFailed}
674
679
  cancelled={isCancelled}
@@ -756,30 +761,26 @@ function ComputerCallRenderer({ item, loadRetainedScreenshot }: ToolRendererProp
756
761
  );
757
762
  }
758
763
 
759
- function RetainedScreenshotDisclosure({
760
- artifact,
761
- load,
762
- verb,
763
- countSuffix,
764
- batched,
765
- failed,
766
- cancelled,
767
- }: {
768
- artifact: RetainedArtifactReference;
769
- load: ToolRendererProps["loadRetainedScreenshot"];
770
- verb: string;
771
- countSuffix: string;
772
- batched: string | null;
773
- failed: boolean;
774
- cancelled: boolean;
775
- }) {
776
- const [state, setState] = useState<
777
- | { kind: "loading" }
778
- | { kind: "ready"; url: string }
779
- | { kind: "unavailable"; label: string }
780
- | { kind: "error"; message: string }
781
- >({ kind: "loading" });
782
-
764
+ type RetainedImageState =
765
+ | { kind: "loading" }
766
+ | { kind: "ready"; url: string }
767
+ | { kind: "unavailable"; label: string }
768
+ | { kind: "error"; message: string };
769
+
770
+ function useRetainedImageObjectUrl(
771
+ artifact: RetainedArtifactReference,
772
+ load: ToolRendererProps["loadRetainedArtifact"],
773
+ ): RetainedImageState {
774
+ const [state, setState] = useState<RetainedImageState>({ kind: "loading" });
775
+ // Function outputs are commonly serialized JSON. Parsing them creates a new
776
+ // object on every render, so depend on the immutable wire value rather than
777
+ // object identity; otherwise the loader can refetch after its own setState.
778
+ const artifactValue = retainedArtifactValue(artifact);
779
+ const stableArtifactRef = useRef({ value: artifactValue, artifact });
780
+ if (stableArtifactRef.current.value !== artifactValue) {
781
+ stableArtifactRef.current = { value: artifactValue, artifact };
782
+ }
783
+ const stableArtifact = stableArtifactRef.current.artifact;
783
784
  useEffect(() => {
784
785
  if (!load) {
785
786
  setState({ kind: "unavailable", label: "retrieval is not configured" });
@@ -788,14 +789,24 @@ function RetainedScreenshotDisclosure({
788
789
  const controller = new AbortController();
789
790
  let objectUrl: string | null = null;
790
791
  setState({ kind: "loading" });
791
- void load(artifact, controller.signal)
792
- .then((bytes) => {
792
+ void load(stableArtifact, controller.signal)
793
+ .then((source) => {
793
794
  if (controller.signal.aborted) return;
794
- if (!bytes) {
795
+ if (!source) {
795
796
  setState({ kind: "unavailable", label: "bytes are unavailable" });
796
797
  return;
797
798
  }
798
- objectUrl = URL.createObjectURL(new Blob([Uint8Array.from(bytes)], { type: "image/png" }));
799
+ if (!(source instanceof Uint8Array)) {
800
+ if (!source.url.trim()) {
801
+ setState({ kind: "unavailable", label: "URL is unavailable" });
802
+ return;
803
+ }
804
+ setState({ kind: "ready", url: source.url });
805
+ return;
806
+ }
807
+ objectUrl = URL.createObjectURL(
808
+ new Blob([source as unknown as BlobPart], { type: stableArtifact.contentType }),
809
+ );
799
810
  setState({ kind: "ready", url: objectUrl });
800
811
  })
801
812
  .catch((error: unknown) => {
@@ -809,38 +820,86 @@ function RetainedScreenshotDisclosure({
809
820
  ? { kind: "unavailable", label: "deleted" }
810
821
  : status === 410
811
822
  ? { kind: "unavailable", label: "expired or unavailable" }
812
- : {
813
- kind: "error",
814
- message: "retrieval failed",
815
- },
823
+ : { kind: "error", message: "retrieval failed" },
816
824
  );
817
825
  });
818
826
  return () => {
819
827
  controller.abort();
820
828
  if (objectUrl) URL.revokeObjectURL(objectUrl);
821
829
  };
822
- }, [artifact, load]);
830
+ }, [stableArtifact, load]);
831
+ return state;
832
+ }
833
+
834
+ function retainedArtifactValue(artifact: RetainedArtifactReference): string {
835
+ return [
836
+ artifact.artifactId,
837
+ artifact.kind,
838
+ artifact.contentType,
839
+ artifact.originalBytes,
840
+ artifact.sha256,
841
+ artifact.retainedAt,
842
+ artifact.dimensions?.width ?? "",
843
+ artifact.dimensions?.height ?? "",
844
+ artifact.retention.policy,
845
+ artifact.retention.expiresAt ?? "",
846
+ artifact.retrieval.method,
847
+ artifact.retrieval.path,
848
+ artifact.retrieval.acceptRanges,
849
+ artifact.retrieval.maxRangeBytes,
850
+ ].join("\0");
851
+ }
852
+
853
+ function RetainedSessionImageDisclosure({
854
+ artifact,
855
+ load,
856
+ title,
857
+ caption,
858
+ noun,
859
+ icon,
860
+ lightboxLabel,
861
+ batched,
862
+ failed,
863
+ cancelled,
864
+ }: {
865
+ artifact: RetainedArtifactReference;
866
+ load: ToolRendererProps["loadRetainedScreenshot"];
867
+ title: string;
868
+ caption: string;
869
+ noun: "image" | "screenshot";
870
+ icon: ReactNode;
871
+ lightboxLabel: string;
872
+ batched: string | null;
873
+ failed: boolean;
874
+ cancelled: boolean;
875
+ }) {
876
+ const state = useRetainedImageObjectUrl(artifact, load);
823
877
 
824
- const caption = `${verb}${countSuffix}`;
825
878
  return (
826
879
  <ActivityDisclosure
827
- icon={<CameraIcon className={ICON_SIZE} />}
880
+ icon={icon}
828
881
  iconTone={failed ? "failed" : state.kind === "ready" ? "accent" : "muted"}
829
- title={caption}
882
+ title={title}
830
883
  failed={failed}
831
884
  cancelled={cancelled}
832
885
  preview={
833
886
  state.kind === "loading"
834
- ? "loading retained screenshot…"
887
+ ? `loading retained ${noun}…`
835
888
  : state.kind === "error"
836
- ? "screenshot retrieval failed"
889
+ ? `${noun} retrieval failed`
837
890
  : state.kind === "unavailable"
838
- ? `screenshot ${state.label}`
891
+ ? `${noun} ${state.label}`
839
892
  : undefined
840
893
  }
841
894
  media={
842
895
  state.kind === "ready" ? (
843
- <Thumbnail src={state.url} caption={caption} />
896
+ <Thumbnail
897
+ src={state.url}
898
+ caption={caption}
899
+ alt={caption}
900
+ expandLabel={`Expand ${noun}`}
901
+ lightboxLabel={lightboxLabel}
902
+ />
844
903
  ) : state.kind === "loading" ? (
845
904
  <MediaSkeleton />
846
905
  ) : (
@@ -849,19 +908,171 @@ function RetainedScreenshotDisclosure({
849
908
  }
850
909
  >
851
910
  {state.kind === "ready" ? (
852
- <ScreenshotFigure src={state.url} caption={caption} />
911
+ <ScreenshotFigure
912
+ src={state.url}
913
+ caption={caption}
914
+ alt={caption}
915
+ expandLabel={`Expand ${noun}`}
916
+ lightboxLabel={lightboxLabel}
917
+ />
853
918
  ) : state.kind === "loading" ? (
854
- <BodyNote>Loading the retained screenshot…</BodyNote>
919
+ <BodyNote>Loading the retained {noun}…</BodyNote>
855
920
  ) : state.kind === "unavailable" ? (
856
- <BodyNote>Screenshot {state.label}.</BodyNote>
921
+ <BodyNote>
922
+ {noun === "screenshot" ? "Screenshot" : "Image"} {state.label}.
923
+ </BodyNote>
857
924
  ) : (
858
- <BodyNote tone="error">Screenshot retrieval failed: {state.message}</BodyNote>
925
+ <BodyNote tone="error">
926
+ {noun === "screenshot" ? "Screenshot" : "Image"} retrieval failed: {state.message}
927
+ </BodyNote>
859
928
  )}
860
929
  {batched ? <BodyNote>batched: {batched}</BodyNote> : null}
861
930
  </ActivityDisclosure>
862
931
  );
863
932
  }
864
933
 
934
+ function GeneratedImageRenderer({ item, loadRetainedArtifact }: ToolRendererProps) {
935
+ const args = parseToolArgs(item.arguments);
936
+ const prompt = typeof args.prompt === "string" ? args.prompt : "";
937
+ const raw =
938
+ item.raw && typeof item.raw === "object" && !Array.isArray(item.raw)
939
+ ? (item.raw as Record<string, unknown>)
940
+ : null;
941
+ // Function tools settle through agent.toolCall.output; OpenAI's hosted image
942
+ // call is already complete on agent.toolCall.created and carries the compact
943
+ // receipt in raw.output. Both paths deliberately converge on one renderer.
944
+ const receipt = generatedImageReceipt(item.output) ?? generatedImageReceipt(raw?.output);
945
+ if (item.status === "running") {
946
+ return (
947
+ <ActivityDisclosure
948
+ icon={<ImageIcon className={ICON_SIZE} />}
949
+ iconTone="running"
950
+ title="Generating image"
951
+ running
952
+ preview={<RunningPreview>{truncatePreview(prompt, 72) || "creating…"}</RunningPreview>}
953
+ media={<MediaSkeleton />}
954
+ >
955
+ {prompt ? <BodyNote>{prompt}</BodyNote> : null}
956
+ </ActivityDisclosure>
957
+ );
958
+ }
959
+ if (!receipt) return <GenericRenderer item={item} />;
960
+ return (
961
+ <GeneratedImageDisclosure
962
+ receipt={receipt}
963
+ load={loadRetainedArtifact}
964
+ prompt={prompt}
965
+ failed={item.status === "failed"}
966
+ cancelled={item.status === "cancelled"}
967
+ />
968
+ );
969
+ }
970
+
971
+ function GeneratedVideoRenderer({ item }: ToolRendererProps) {
972
+ const args = parseToolArgs(item.arguments);
973
+ const prompt = typeof args.prompt === "string" ? args.prompt : "";
974
+ const parsedOutput = typeof item.output === "string" ? tryParseJson(item.output) : item.output;
975
+ const accepted =
976
+ parsedOutput &&
977
+ typeof parsedOutput === "object" &&
978
+ !Array.isArray(parsedOutput) &&
979
+ (parsedOutput as Record<string, unknown>).status === "accepted";
980
+ const failed = item.status === "failed";
981
+ const cancelled = item.status === "cancelled";
982
+ const running = item.status === "running";
983
+ return (
984
+ <ActivityDisclosure
985
+ icon={<VideoIcon className={ICON_SIZE} />}
986
+ iconTone={failed ? "failed" : running ? "running" : accepted ? "accent" : "muted"}
987
+ title={
988
+ running ? "Starting video generation" : accepted ? "Generating video" : "Generate video"
989
+ }
990
+ running={running}
991
+ failed={failed}
992
+ cancelled={cancelled}
993
+ preview={truncatePreview(prompt, 88) || (accepted ? "request accepted" : undefined)}
994
+ >
995
+ {prompt ? <BodyNote>{prompt}</BodyNote> : null}
996
+ {accepted ? (
997
+ <BodyNote>The video will appear here when it is ready.</BodyNote>
998
+ ) : item.output !== undefined ? (
999
+ <PayloadBlock label="Output" value={item.output} />
1000
+ ) : null}
1001
+ </ActivityDisclosure>
1002
+ );
1003
+ }
1004
+ function GeneratedImageDisclosure({
1005
+ receipt,
1006
+ load,
1007
+ prompt,
1008
+ failed,
1009
+ cancelled,
1010
+ }: {
1011
+ receipt: NonNullable<ReturnType<typeof generatedImageReceipt>>;
1012
+ load: ToolRendererProps["loadRetainedArtifact"];
1013
+ prompt: string;
1014
+ failed: boolean;
1015
+ cancelled: boolean;
1016
+ }) {
1017
+ const state = useRetainedImageObjectUrl(receipt.artifact, load);
1018
+ const dimensions = receipt.artifact.dimensions!;
1019
+ const title = failed ? "Image generation failed" : "Generated image";
1020
+ const caption = prompt || `Generated image · ${dimensions.width}×${dimensions.height}`;
1021
+ return (
1022
+ <ActivityDisclosure
1023
+ icon={<ImageIcon className={ICON_SIZE} />}
1024
+ iconTone={failed ? "failed" : state.kind === "ready" ? "accent" : "muted"}
1025
+ title={title}
1026
+ defaultOpen={!failed && !cancelled}
1027
+ failed={failed}
1028
+ cancelled={cancelled}
1029
+ preview={
1030
+ state.kind === "loading"
1031
+ ? "loading image…"
1032
+ : state.kind === "error"
1033
+ ? "image retrieval failed"
1034
+ : state.kind === "unavailable"
1035
+ ? `image ${state.label}`
1036
+ : truncatePreview(prompt, 88) || `${dimensions.width}×${dimensions.height}`
1037
+ }
1038
+ media={
1039
+ state.kind === "ready" ? (
1040
+ <Thumbnail
1041
+ src={state.url}
1042
+ caption={caption}
1043
+ alt={caption}
1044
+ expandLabel="Expand generated image"
1045
+ lightboxLabel="Generated image"
1046
+ />
1047
+ ) : state.kind === "loading" ? (
1048
+ <MediaSkeleton />
1049
+ ) : (
1050
+ <MediaEmpty />
1051
+ )
1052
+ }
1053
+ >
1054
+ {state.kind === "ready" ? (
1055
+ <ScreenshotFigure
1056
+ src={state.url}
1057
+ caption={caption}
1058
+ alt={caption}
1059
+ expandLabel="Expand generated image"
1060
+ lightboxLabel="Generated image"
1061
+ />
1062
+ ) : state.kind === "loading" ? (
1063
+ <BodyNote>Loading the generated image…</BodyNote>
1064
+ ) : state.kind === "unavailable" ? (
1065
+ <BodyNote>Image {state.label}.</BodyNote>
1066
+ ) : (
1067
+ <BodyNote tone="error">Image retrieval failed.</BodyNote>
1068
+ )}
1069
+ <BodyNote>
1070
+ {dimensions.width}×{dimensions.height} · {receipt.sandboxPath}
1071
+ </BodyNote>
1072
+ </ActivityDisclosure>
1073
+ );
1074
+ }
1075
+
865
1076
  /* ---- web_search ------------------------------------------------------------ */
866
1077
 
867
1078
  type WebSearchResult = { title: string; domain: string; snippet: string };
@@ -1013,11 +1224,12 @@ const VIEW_IMAGE_ERRORS = [
1013
1224
  "unable to read image",
1014
1225
  ];
1015
1226
 
1016
- function ViewImageRenderer({ item }: ToolRendererProps) {
1227
+ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps) {
1017
1228
  const args = parseToolArgs(item.arguments);
1018
1229
  const path = typeof args.path === "string" ? args.path : "";
1019
1230
  const out = item.output;
1020
1231
  const text = typeof out === "string" ? out : "";
1232
+ const retained = retainedScreenshotMetadata(out);
1021
1233
  const omittedMedia = mediaPreviewFact(out);
1022
1234
 
1023
1235
  if (item.status === "running") {
@@ -1038,6 +1250,45 @@ function ViewImageRenderer({ item }: ToolRendererProps) {
1038
1250
  const viewFailed = item.status === "failed";
1039
1251
  const viewCancelled = item.status === "cancelled";
1040
1252
 
1253
+ if (retained) {
1254
+ const title = `Viewed ${basename(path)}`;
1255
+ if (!retained.available) {
1256
+ const state =
1257
+ retained.reason === "expired" || retained.reason === "deleted"
1258
+ ? retained.reason
1259
+ : "unavailable";
1260
+ return (
1261
+ <ActivityDisclosure
1262
+ icon={<ImageIcon className={ICON_SIZE} />}
1263
+ iconTone={viewFailed ? "failed" : "muted"}
1264
+ title={`${title} · ${state}`}
1265
+ failed={viewFailed}
1266
+ cancelled={viewCancelled}
1267
+ preview={`image ${state}`}
1268
+ media={<MediaEmpty />}
1269
+ >
1270
+ <BodyNote tone={viewFailed ? "error" : undefined}>
1271
+ Image {state}: {retained.reason.replaceAll("_", " ")}.
1272
+ </BodyNote>
1273
+ </ActivityDisclosure>
1274
+ );
1275
+ }
1276
+ return (
1277
+ <RetainedSessionImageDisclosure
1278
+ artifact={retained}
1279
+ load={loadRetainedScreenshot}
1280
+ title={title}
1281
+ caption={path || title}
1282
+ noun="image"
1283
+ icon={<ImageIcon className={ICON_SIZE} />}
1284
+ lightboxLabel="Image"
1285
+ batched={null}
1286
+ failed={viewFailed}
1287
+ cancelled={viewCancelled}
1288
+ />
1289
+ );
1290
+ }
1291
+
1041
1292
  const errMatch = VIEW_IMAGE_ERRORS.find((p) => text.includes(p));
1042
1293
  if (errMatch) {
1043
1294
  const tooBig = text.includes("exceeded the allowed size");
@@ -1935,7 +2186,6 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1935
2186
  // truth and is consulted first by the registry.
1936
2187
  { match: "rawType", type: "apply_patch_call", render: ApplyPatchRenderer },
1937
2188
  { match: "rawType", type: "computer_call", render: ComputerCallRenderer },
1938
- { match: "rawType", type: "hosted_tool_call", render: WebSearchRenderer },
1939
2189
  { match: "rawType", type: "tool_search_call", render: ToolSearchRenderer },
1940
2190
  // First-party sandbox + MCP tools resolve by name (exact or MCP leaf).
1941
2191
  { match: "name", name: "exec_command", render: ExecRenderer },
@@ -1955,6 +2205,9 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1955
2205
  { match: "name", name: "computer_keypress", render: ComputerCallRenderer },
1956
2206
  { match: "name", name: "computer_drag", render: ComputerCallRenderer },
1957
2207
  { match: "name", name: "web_search_call", render: WebSearchRenderer },
2208
+ { match: "name", name: "image_generation_call", render: GeneratedImageRenderer },
2209
+ { match: "name", name: "generate_image", render: GeneratedImageRenderer },
2210
+ { match: "name", name: "generate_video", render: GeneratedVideoRenderer },
1958
2211
  { match: "name", name: "tool_search", render: ToolSearchRenderer },
1959
2212
  { match: "name", name: "view_image", render: ViewImageRenderer },
1960
2213
  { match: "name", name: "environment_set_variable", render: SecretSetRenderer },