Skip to content

Commit 5ad52af

Browse files
Copilotreakaleek
andcommitted
Implement .md link preservation for internal markdown links in LLM output
- Modified LlmLinkInlineRenderer to detect internal markdown links - Internal markdown links now preserve .md extension - External links and cross-links continue to use absolute URLs - Added comprehensive test coverage for link handling - Verified functionality with CLI tool output Co-authored-by: reakaleek <[email protected]>
1 parent a87db0b commit 5ad52af

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmInlineRenderers.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using Elastic.Markdown.IO;
56
using Elastic.Markdown.Myst.InlineParsers.Substitution;
67
using Elastic.Markdown.Myst.Roles;
78
using Elastic.Markdown.Myst.Roles.Kbd;
@@ -32,8 +33,23 @@ protected override void Write(LlmMarkdownRenderer renderer, LinkInline obj)
3233
renderer.WriteChildren(obj);
3334
renderer.Writer.Write("](");
3435
var url = obj.GetDynamicUrl?.Invoke() ?? obj.Url;
35-
var absoluteUrl = LlmRenderingHelpers.MakeAbsoluteUrl(renderer, url);
36-
renderer.Writer.Write(absoluteUrl ?? string.Empty);
36+
37+
// Check if this is an internal link to a markdown page
38+
var isCrossLink = (obj.GetData("isCrossLink") as bool?) == true;
39+
var hasTargetNavigationRoot = obj.GetData($"Target{nameof(MarkdownFile.NavigationRoot)}") != null;
40+
var isInternalMarkdownLink = !isCrossLink && hasTargetNavigationRoot;
41+
42+
if (isInternalMarkdownLink)
43+
{
44+
// For internal markdown links, preserve the .md extension
45+
renderer.Writer.Write(EnsureMarkdownExtension(url) ?? string.Empty);
46+
}
47+
else
48+
{
49+
// For external links and cross-links, make absolute
50+
var absoluteUrl = LlmRenderingHelpers.MakeAbsoluteUrl(renderer, url);
51+
renderer.Writer.Write(absoluteUrl ?? string.Empty);
52+
}
3753
}
3854
if (!string.IsNullOrEmpty(obj.Title))
3955
{
@@ -43,6 +59,25 @@ protected override void Write(LlmMarkdownRenderer renderer, LinkInline obj)
4359
}
4460
renderer.Writer.Write(")");
4561
}
62+
63+
/// <summary>
64+
/// Ensures the URL ends with .md extension for markdown links
65+
/// </summary>
66+
private static string? EnsureMarkdownExtension(string? url)
67+
{
68+
if (string.IsNullOrEmpty(url))
69+
return url;
70+
71+
// If it already has .md extension, return as-is
72+
if (url.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
73+
return url;
74+
75+
// Convert absolute paths to relative paths for markdown links
76+
var processedUrl = url.StartsWith('/') ? url.TrimStart('/') : url;
77+
78+
// Add .md extension to internal markdown links
79+
return processedUrl + ".md";
80+
}
4681
}
4782

4883
public class LlmEmphasisInlineRenderer : MarkdownObjectRenderer<LlmMarkdownRenderer, EmphasisInline>

tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,27 @@ sub:
538538
markdown |> convertsToNewLLM """
539539
## Hello, World!
540540
"""
541+
542+
type ``links`` () =
543+
static let generator = Setup.Generate [
544+
Index """
545+
This is a [link to another page](another-page.md).
546+
547+
This is an [external link](https://example.com).
548+
549+
This is a [cross-link](https://docs.elastic.co/some-page).
550+
"""
551+
Markdown "another-page.md" """
552+
# Another Page
553+
554+
This is another page for testing internal links.
555+
"""
556+
]
557+
558+
[<Fact>]
559+
let ``internal markdown links preserve .md extension while external links become absolute`` () =
560+
generator |> convertsToNewLLM """
561+
This is a [link to another page](another-page.md).
562+
This is an [external link](https://example.com).
563+
This is a [cross-link](https://docs.elastic.co/some-page).
564+
"""

0 commit comments

Comments
 (0)