mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
Render inline visualization links in the TUI (#33925)
## 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
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::history_cell::PlainHistoryCell;
|
||||
use crate::history_cell::ReasoningSummaryCell;
|
||||
use crate::history_cell::UserHistoryCell;
|
||||
use crate::history_cell::split_reasoning_summary_parts;
|
||||
use crate::inline_visualization::InlineVisualizationContext;
|
||||
use crate::multi_agents::sub_agent_activity_summary;
|
||||
use codex_app_server_protocol::Thread;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
@@ -31,6 +32,7 @@ pub(crate) async fn load_session_transcript(
|
||||
app_server: &mut AppServerSession,
|
||||
thread_id: ThreadId,
|
||||
raw_reasoning_visibility: RawReasoningVisibility,
|
||||
codex_home: Option<&std::path::Path>,
|
||||
) -> std::io::Result<TranscriptCells> {
|
||||
let thread = app_server
|
||||
.thread_read(thread_id, /*include_turns*/ true)
|
||||
@@ -39,14 +41,21 @@ pub(crate) async fn load_session_transcript(
|
||||
Ok(thread_to_transcript_cells(
|
||||
&thread,
|
||||
raw_reasoning_visibility,
|
||||
codex_home,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn thread_to_transcript_cells(
|
||||
thread: &Thread,
|
||||
raw_reasoning_visibility: RawReasoningVisibility,
|
||||
codex_home: Option<&std::path::Path>,
|
||||
) -> TranscriptCells {
|
||||
let cwd = thread.cwd.as_path();
|
||||
let inline_visualization_context = codex_home.and_then(|codex_home| {
|
||||
ThreadId::from_string(&thread.id)
|
||||
.ok()
|
||||
.and_then(|thread_id| InlineVisualizationContext::new(codex_home, thread_id))
|
||||
});
|
||||
let mut cells: TranscriptCells = Vec::new();
|
||||
for item in thread.turns.iter().flat_map(|turn| turn.items.iter()) {
|
||||
match item {
|
||||
@@ -85,9 +94,10 @@ pub(crate) fn thread_to_transcript_cells(
|
||||
ThreadItem::AgentMessage { text, .. } => {
|
||||
let parsed = parse_assistant_markdown(text, cwd);
|
||||
if !parsed.visible_markdown.trim().is_empty() {
|
||||
cells.push(Arc::new(AgentMarkdownCell::new(
|
||||
cells.push(Arc::new(AgentMarkdownCell::new_with_inline_visualizations(
|
||||
parsed.visible_markdown,
|
||||
cwd,
|
||||
inline_visualization_context.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user