mirror of
https://github.com/openai/codex.git
synced 2026-11-09 16:41:37 +00:00
## Why
Assistant-authored inline visualizations need a terminal fallback that lets users open generated artifacts in a browser.
## What changed
- Recognize `::codex-inline-vis{file="..."}` directives in assistant Markdown and replace them with browser links during streaming, finalized history rendering, and local transcript previews.
- Materialize visualization fragments in sandboxed viewer documents with a content security policy, while restricting artifacts to HTML files within the thread's visualization directory and limiting their size.
- Show an explicit unavailable message for invalid or missing artifacts, hide incomplete streaming directives, and preserve directive text in code blocks and user Markdown.
- Add a trusted file-link path for generated viewers without enabling arbitrary `file:` links in Markdown.
## Testing
Add coverage for directive rewriting, streaming and replay, artifact validation, sandboxed viewer generation, trusted terminal hyperlinks, and literal preservation.
GitOrigin-RevId: a666b4ebc82bb5798ad45bd137fd815d538e1de2
100 lines
3.7 KiB
Rust
100 lines
3.7 KiB
Rust
//! Transcript consolidation for finalized streaming agent messages.
|
|
//!
|
|
//! During streaming, the chat widget emits transient `AgentMessageCell`s so it
|
|
//! can animate stable lines into scrollback while keeping the active mutable
|
|
//! tail in the bottom pane. Once the answer finishes, the app replaces that
|
|
//! trailing run with a single source-backed `AgentMarkdownCell`. This makes the
|
|
//! transcript the canonical owner of the raw markdown source used for future
|
|
//! resize re-renders.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use color_eyre::eyre::Result;
|
|
|
|
use super::App;
|
|
use super::resize_reflow::trailing_run_start;
|
|
use crate::app_event::ConsolidationScrollbackReflow;
|
|
use crate::history_cell;
|
|
use crate::history_cell::HistoryCell;
|
|
use crate::inline_visualization::InlineVisualizationContext;
|
|
use crate::pager_overlay::Overlay;
|
|
use crate::tui;
|
|
|
|
impl App {
|
|
pub(super) fn handle_consolidate_agent_message(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
source: String,
|
|
cwd: PathBuf,
|
|
inline_visualization_context: Option<InlineVisualizationContext>,
|
|
scrollback_reflow: ConsolidationScrollbackReflow,
|
|
deferred_history_cell: Option<Box<dyn HistoryCell>>,
|
|
) -> Result<()> {
|
|
// Some finalize paths must preserve a last provisional stream cell long
|
|
// enough for queue ordering, then fold it into the canonical
|
|
// source-backed cell during consolidation.
|
|
if let Some(cell) = deferred_history_cell {
|
|
let cell: Arc<dyn HistoryCell> = cell.into();
|
|
if let Some(Overlay::Transcript(t)) = &mut self.overlay {
|
|
t.insert_cell(cell.clone());
|
|
}
|
|
self.transcript_cells.push(cell);
|
|
}
|
|
|
|
// Walk backward to find the contiguous run of streaming AgentMessageCells that
|
|
// belong to the just-finalized stream.
|
|
let end = self.transcript_cells.len();
|
|
tracing::debug!(
|
|
"ConsolidateAgentMessage: transcript_cells.len()={end}, source_len={}",
|
|
source.len()
|
|
);
|
|
let start = trailing_run_start::<history_cell::AgentMessageCell>(&self.transcript_cells);
|
|
if start < end {
|
|
tracing::debug!(
|
|
"ConsolidateAgentMessage: replacing cells [{start}..{end}] with AgentMarkdownCell"
|
|
);
|
|
let consolidated: Arc<dyn HistoryCell> = Arc::new(
|
|
history_cell::AgentMarkdownCell::new_with_inline_visualizations(
|
|
source,
|
|
&cwd,
|
|
inline_visualization_context,
|
|
),
|
|
);
|
|
self.transcript_cells
|
|
.splice(start..end, std::iter::once(consolidated.clone()));
|
|
|
|
if let Some(Overlay::Transcript(t)) = &mut self.overlay {
|
|
t.consolidate_cells(start..end, consolidated.clone());
|
|
tui.frame_requester().schedule_frame();
|
|
}
|
|
|
|
self.finish_agent_message_consolidation(tui, scrollback_reflow)?;
|
|
} else {
|
|
tracing::debug!(
|
|
"ConsolidateAgentMessage: no cells to consolidate(start={start}, end={end})",
|
|
);
|
|
self.maybe_finish_stream_reflow(tui)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn finish_agent_message_consolidation(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
scrollback_reflow: ConsolidationScrollbackReflow,
|
|
) -> Result<()> {
|
|
match scrollback_reflow {
|
|
ConsolidationScrollbackReflow::IfResizeReflowRan => {
|
|
self.maybe_finish_stream_reflow(tui)?;
|
|
}
|
|
ConsolidationScrollbackReflow::Required => {
|
|
self.finish_required_stream_reflow(tui)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|