feat(proxy): Markdown for Agents — Accept: text/markdown content negotiation#12
Merged
feat(proxy): Markdown for Agents — Accept: text/markdown content negotiation#12
Conversation
Implements Markdown for Agents content negotiation in the Pingora proxy, compatible with Cloudflare's emerging standard for AI-native content delivery. When an HTTP client sends 'Accept: text/markdown', the proxy detects the preference in early_request_filter and, if the upstream returns text/html, buffers the response body and converts it to Markdown via htmd before forwarding it to the client. Key behaviours: - Detection: Accept header parsed in early_request_filter; compression disabled for markdown requests to receive raw HTML bytes - Gating: upstream_response_filter cancels conversion for non-HTML content types, SSE streams, and WebSocket upgrades; adds Vary: Accept - Conversion: response_body_filter accumulates chunks and converts the full body on end_of_stream using htmd - Size guard: responses larger than 2 MB fall back to passthrough, mirroring Cloudflare's limit - Headers: Content-Type rewritten to text/markdown; charset=utf-8, Content-Length and Content-Encoding removed, X-Markdown-Tokens set as a best-effort placeholder - Token estimation: word-count heuristic (words * 4 / 3) matching the rough estimate in Cloudflare's x-markdown-tokens header 18 unit tests cover: Accept header parsing, content-type gating, SSE/WebSocket passthrough safety, multi-chunk accumulation, size guard, HTML-to-Markdown conversion, and token estimation.
…d update changelog
Without pre-extraction, htmd converted the entire page document including inlined <script> and <style> blocks, nav, sidebars and footers — producing output 12x larger than Cloudflare's equivalent (115 KB vs 9.5 KB). Now uses scraper to find the first <main> element (document order / shallowest depth) before passing to htmd, with fallback to <body> and then the raw HTML. This matches Cloudflare's Markdown for Agents approach: only the article content node is converted, not the full browser-rendered document shell. Result on the Cloudflare docs reference page: Before: 115,020 bytes (full page + CSS/JS noise) After: 15,658 bytes (article content only) 5 new extraction unit tests added covering: <main> preferred over surrounding nav/footer, <body> fallback, first-of-multiple-main wins, script/style outside <main> excluded, and fragment passthrough.
Extract gate and header-rewrite logic into free functions (apply_markdown_upstream_gate, apply_markdown_response_headers) so they can be tested without a live Pingora session. Gate now cancels conversion for: non-2xx status codes (4xx/5xx/3xx), missing Content-Type, non-HTML content types, uppercase Content-Type (TEXT/HTML), SSE, and WebSocket. 23 new pipeline tests cover every edge case end-to-end (gate → header rewrite → body filter).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Markdown for Agents content negotiation in the Pingora reverse proxy, compatible with Cloudflare's emerging standard for AI-native content delivery.
When an AI agent or HTTP client sends
Accept: text/markdown, Temps now converts the upstream HTML response to Markdown on the fly before delivering it — reducing token waste and improving response quality for AI systems consuming deployed apps.How it works
Filter chain
early_request_filterAccept: text/markdown; setsctx.wants_markdown = true; disables upstream compressionupstream_response_filtertext/html(not SSE/WS); addsVary: Accept; cancels conversion otherwiseresponse_filterContent-Type → text/markdown; charset=utf-8; removesContent-LengthandContent-Encoding; addsX-Markdown-Tokens: 0placeholderresponse_body_filterhtmdonend_of_stream; falls back to passthrough on error or size limit exceededSafety guards
ctx.is_sseorctx.is_websocketis set — streaming responses always pass through unchangedtext/htmlupstream responses are converted; JSON, images, etc. pass through as-ishtmdfails, the original HTML bytes are returned so the client always gets somethingTests
18 unit tests added in
proxy::markdown_tests:Dependencies
htmd0.5— pure Rust HTML→Markdown converter, no C dependencies