Skip to content

feat(site): rewrite localhost URLs in agent chat to port-forward links#22891

Merged
kylecarbs merged 2 commits intomainfrom
port-forward-chat-links
Mar 10, 2026
Merged

feat(site): rewrite localhost URLs in agent chat to port-forward links#22891
kylecarbs merged 2 commits intomainfrom
port-forward-chat-links

Conversation

@kylecarbs
Copy link
Copy Markdown
Member

Uses streamdown's built-in urlTransform prop to intercept http://localhost:PORT URLs in agent chat messages and rewrite them to port-forwarded workspace URLs.

When the agent outputs a bare URL like http://localhost:3000 or a markdown link like [app](http://localhost:8080/path), the URL is rewritten to the workspace's port-forward subdomain (e.g. https://3000--agent--workspace--user.wildcard.host). This makes links clickable directly from the chat without manual port-forwarding.

How it works

The transform is built in AgentDetail where workspace and proxy context are available, then threaded as an optional prop through the component tree:

AgentDetail → AgentDetailView → AgentDetailTimeline → ConversationTimeline → Response → Streamdown
  • Uses streamdown's first-class urlTransform API — no monkey-patching or rehype plugins
  • Reuses the existing portForwardURL() utility from utils/portForward
  • Matches the same localhost detection as the terminal page (localhost, 127.0.0.1, 0.0.0.0)
  • Preserves pathname and search params
  • Gracefully degrades: when any required context is missing (no workspace, no wildcard proxy host), URLs pass through unchanged

What gets transformed

Markdown input Transformed?
http://localhost:8080 (bare URL, auto-linked by remark-gfm) Yes
[my app](http://localhost:3000/path) (explicit link) Yes
\http://localhost:8080\`` (inline code) No (correct — code spans are literal)
https://example.com (non-localhost) No

Uses streamdown's built-in urlTransform prop to intercept
http://localhost:PORT URLs in agent chat messages and rewrite
them to port-forwarded workspace URLs.

When the agent outputs a bare URL like http://localhost:3000 or a
markdown link like [app](http://localhost:8080/path), the URL is
rewritten to the workspace's port-forward subdomain (e.g.
https://3000--agent--workspace--user.wildcard.host). This makes
links clickable directly from the chat without manual
port-forwarding.

The transform is built in AgentDetail where workspace and proxy
context are available, then threaded as an optional prop through
AgentDetailView -> AgentDetailTimeline -> ConversationTimeline ->
Response -> Streamdown. When any required context is missing
(no workspace, no wildcard proxy host), URLs pass through
unchanged.

Only real links are transformed — URLs inside backtick code spans
remain literal text, which is correct since streamdown's
urlTransform operates on HAST element attributes (href, src),
not text node content.
Copy link
Copy Markdown
Contributor

@DanielleMaywood DanielleMaywood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming you've tested this to work, LGTM

The AgentDetail component now calls useProxy(), which requires
a ProxyProvider in the tree. The storybook stories were missing
this provider, causing test failures.
@kylecarbs kylecarbs enabled auto-merge (squash) March 10, 2026 12:57
@kylecarbs kylecarbs merged commit b898e45 into main Mar 10, 2026
24 of 25 checks passed
@kylecarbs kylecarbs deleted the port-forward-chat-links branch March 10, 2026 12:58
@github-actions github-actions bot locked and limited conversation to collaborators Mar 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants