Bug Description
packages/adapter-gchat/src/markdown.ts (nodeToGChat, lines 108–116) collapses link nodes to a bare URL when the link's display text equals the URL — but only for plain http(s):// URLs. For mailto: and tel: links, the URL carries a scheme prefix (mailto:foo@bar.com) that the visible text doesn't (foo@bar.com), so the equality check linkText === node.url never fires, and the adapter emits the verbose <scheme:url|display> form even though display text adds no information.
This is most visible when an agent or upstream message contains plain email addresses. remark-gfm's autolink-literal extension converts bare emails to link nodes [foo@bar.com](mailto:foo@bar.com), and the adapter then ships them to Google Chat as <mailto:foo@bar.com|foo@bar.com>. In some Google Chat clients these render correctly as a clickable email; in others (and consistently when the message text is round-tripped via quotedMessageSnapshot.text, copied to clipboard, or read back by spaces.messages.get), the raw token surfaces in the user-facing text.
The current code at markdown.ts:108–116:
if (isLinkNode(node)) {
const linkText = getNodeChildren(node)
.map((child) => this.nodeToGChat(child))
.join('');
if (linkText === node.url) {
return node.url;
}
return `<${node.url}|${linkText}>`;
}
Steps to Reproduce
import { createGoogleChatAdapter } from '@chat-adapter/gchat';
const adapter = createGoogleChatAdapter({ /* … */ });
await adapter.postMessage('gchat:spaces/X:thread', {
markdown: 'invite sent to hello@example.com',
});
Expected Behavior
The wire-level message GChat receives should be:
invite sent to hello@example.com
(GChat's own renderer auto-links bare email addresses at display time — no <mailto:…|…> wrapping needed when display text == URL tail.)
Actual Behavior
The wire-level message is:
invite sent to <mailto:hello@example.com|hello@example.com>
Same applies for <tel:+15551234|+15551234> when the agent writes a bare phone number that GFM autolinks. The nodeToGChat bare-URL optimization at line 113 should also fire for these schemes.
Suggested Fix
Extend the bare-URL collapse to recognise that for mailto: and tel: scheme URLs, the "bare display" form is the URL with its scheme prefix stripped:
if (isLinkNode(node)) {
const linkText = getNodeChildren(node)
.map((child) => this.nodeToGChat(child))
.join('');
if (linkText === node.url) return node.url;
// mailto:/tel: autolinks: GChat renders the bare address as a clickable link.
// The display-text form is the URL minus the scheme.
const schemeMatch = node.url.match(/^(mailto|tel):(.+)$/);
if (schemeMatch && schemeMatch[2] === linkText) return linkText;
return `<${node.url}|${linkText}>`;
}
Happy to send a PR with the change + a test in markdown.test.ts if the direction looks right.
Chat SDK Version
4.26.0
Node.js Version
23.10
Platform Adapter
Google Chat
Operating System
macOS
Additional Context
Related precedent: #392 (Slack adapter mention regex mangles emails) — same general class of "plain email addresses in agent text need careful handling in adapter rendering."
Currently working around this in our codebase by monkey-patching formatConverter.renderPostable to post-process the rendered output and collapse <mailto:X|X> / <tel:X|X> / <https?://X|X> tokens where the display equals the URL tail. Happy to upstream the equivalent logic via the nodeToGChat change above so the workaround can be removed.
Bug Description
packages/adapter-gchat/src/markdown.ts(nodeToGChat, lines 108–116) collapses link nodes to a bare URL when the link's display text equals the URL — but only for plainhttp(s)://URLs. Formailto:andtel:links, the URL carries a scheme prefix (mailto:foo@bar.com) that the visible text doesn't (foo@bar.com), so the equality checklinkText === node.urlnever fires, and the adapter emits the verbose<scheme:url|display>form even though display text adds no information.This is most visible when an agent or upstream message contains plain email addresses.
remark-gfm's autolink-literal extension converts bare emails to link nodes[foo@bar.com](mailto:foo@bar.com), and the adapter then ships them to Google Chat as<mailto:foo@bar.com|foo@bar.com>. In some Google Chat clients these render correctly as a clickable email; in others (and consistently when the message text is round-tripped viaquotedMessageSnapshot.text, copied to clipboard, or read back byspaces.messages.get), the raw token surfaces in the user-facing text.The current code at
markdown.ts:108–116:Steps to Reproduce
Expected Behavior
The wire-level message GChat receives should be:
(GChat's own renderer auto-links bare email addresses at display time — no
<mailto:…|…>wrapping needed when display text == URL tail.)Actual Behavior
The wire-level message is:
Same applies for
<tel:+15551234|+15551234>when the agent writes a bare phone number that GFM autolinks. ThenodeToGChatbare-URL optimization at line 113 should also fire for these schemes.Suggested Fix
Extend the bare-URL collapse to recognise that for
mailto:andtel:scheme URLs, the "bare display" form is the URL with its scheme prefix stripped:Happy to send a PR with the change + a test in
markdown.test.tsif the direction looks right.Chat SDK Version
4.26.0
Node.js Version
23.10
Platform Adapter
Google Chat
Operating System
macOS
Additional Context
Related precedent: #392 (Slack adapter mention regex mangles emails) — same general class of "plain email addresses in agent text need careful handling in adapter rendering."
Currently working around this in our codebase by monkey-patching
formatConverter.renderPostableto post-process the rendered output and collapse<mailto:X|X>/<tel:X|X>/<https?://X|X>tokens where the display equals the URL tail. Happy to upstream the equivalent logic via thenodeToGChatchange above so the workaround can be removed.