Render assistant file citations as local links (#42650)

## What changed

- Convert `codex-file-citation` directives in assistant Markdown into local-file links while preserving paths with Markdown-significant characters, Unicode, Windows separators, and location suffixes.
- Apply citation rendering consistently to streaming output, finalized messages and plans, and resume-picker transcript previews.
- Keep directives literal in code, HTML, existing links, reference definitions, escaped text, and generic Markdown rendering.
- Bound repeated parsing work for malformed directive candidates without limiting valid citations.

## Testing

- Add coverage for citation parsing and rendering across finalized, streaming, and resume-preview output, including malformed input and path edge cases.

GitOrigin-RevId: 5a0bfb3fc7ac21b7e8846fd170a349fb2d2d5e85
This commit is contained in:
Ian MacLeod
2026-09-04 01:46:55 +00:00
committed by copyberry
parent 956aa3f637
commit f46671b14a
13 changed files with 635 additions and 22 deletions
@@ -109,6 +109,26 @@ fn incremental_render_keeps_final_block_mutable_and_matches_full_render() {
assert_debug_snapshot!("incremental_render_representative_stream", render.lines);
}
#[test]
fn incremental_file_citations_preserve_metadata_unicode_and_markdown() {
let cwd = test_cwd();
let rendered_cases = [
(
"Quarterly Report.xlsx",
"- :codex-file-citation{artifact_kind=\"workbook\" ",
),
("Résumé *final* ✨.xlsx", "- :codex-file-citation{"),
]
.map(|(filename, prefix)| {
let tail = format!("path=\"{}\"}}\n", cwd.join(filename).display());
let chunks = ["# Output\n\n", prefix, &tail, "\n", "Continue.\n"];
let (_, render) = assert_rich_stream_matches_full_render(&chunks, Some(80));
render.lines
});
assert_debug_snapshot!("incremental_file_citations", rendered_cases);
}
#[test]
fn growing_single_top_level_blocks_render_and_scan_in_one_pass() {
let streams: &[&[&str]] = &[
@@ -0,0 +1,62 @@
---
source: tui/src/streaming/render_tests.rs
expression: rendered_cases
---
[
[
HyperlinkLine {
line: Line::from_iter([
Span::from("# ").bold().underlined(),
Span::from("Output").bold().underlined(),
]),
hyperlinks: [],
},
HyperlinkLine {
line: Line::default(),
hyperlinks: [],
},
HyperlinkLine {
line: Line::from_iter([
Span::from("- "),
Span::from("Quarterly Report.xlsx").cyan(),
]),
hyperlinks: [],
},
HyperlinkLine {
line: Line::default(),
hyperlinks: [],
},
HyperlinkLine {
line: Line::from("Continue."),
hyperlinks: [],
},
],
[
HyperlinkLine {
line: Line::from_iter([
Span::from("# ").bold().underlined(),
Span::from("Output").bold().underlined(),
]),
hyperlinks: [],
},
HyperlinkLine {
line: Line::default(),
hyperlinks: [],
},
HyperlinkLine {
line: Line::from_iter([
Span::from("- "),
Span::from("Résumé *final* ✨.xlsx").cyan(),
]),
hyperlinks: [],
},
HyperlinkLine {
line: Line::default(),
hyperlinks: [],
},
HyperlinkLine {
line: Line::from("Continue."),
hyperlinks: [],
},
],
]