Skip to content

feat: native Discord embeds via [EMBED] marker - #75945

Open
Jonesyj83 wants to merge 4 commits into
NousResearch:mainfrom
Jonesyj83:feat/discord-embed-marker
Open

feat: native Discord embeds via [EMBED] marker#75945
Jonesyj83 wants to merge 4 commits into
NousResearch:mainfrom
Jonesyj83:feat/discord-embed-marker

Conversation

@Jonesyj83

Copy link
Copy Markdown

Summary

Agents can now emit native Discord embeds by including an [EMBED]{json}[/EMBED] block in a message. The gateway parses the JSON and sends it as a real discord.Embed — title, description, colour, inline fields (columns/rows), footer — while keeping any surrounding text as the plain message content.

This follows the established MEDIA: text-marker convention already in the adapter (agent → marker → platform feature).

Example

[EMBED]
{"title": "⚡ POWER WATCH", "color": "#3fb950", "fields": [{"name": "Import", "value": "12.8 kWh", "inline": true}, ...]}
[/EMBED]

Implementation

  • _extract_embed(): parses the JSON block into a discord.Embed
    • json.loads(strict=False) — literal newlines inside strings work, so the model can write readable multi-line JSON
    • colour accepts int or #hex; footer accepts str or {"text": ...}
    • fields capped at Discord's 25
  • send(): attaches the embed to the first chunk; marker stripped from content
  • edit_message(): on streaming finalize, swaps the marker for the embed; mid-stream edits show a ⏳ rendering embed… placeholder so the raw JSON never flashes on screen
  • Zero impact on normal messages — extraction is marker-gated

Tests

11 new unit tests in tests/gateway/test_discord_adapter_embed.py (parse, preamble/tail preservation, invalid-JSON fallback, newline handling, colour/footer forms, 25-field cap, send-path wiring, marker-gated no-op). Existing Discord adapter tests still pass.

Agents can now emit real Discord embeds by including an [EMBED]{json}[/EMBED]
block in a message. The gateway parses the JSON and sends it as a native
discord.Embed (title, description, color, inline fields, footer), keeping
any surrounding text as the plain message content.

- _extract_embed(): JSON block -> discord.Embed (strict=False so literal
  newlines work; color accepts int or #hex; footer accepts str or dict;
  fields capped at Discord's 25)
- send(): attaches the embed to the first chunk, marker stripped
- edit_message(): streaming finalize swaps the marker for the embed;
  mid-stream edits show a placeholder instead of raw JSON
- Normal messages are completely unaffected (marker-gated)
- 11 unit tests covering parse, fallback, colors, footer, caps, send wiring
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/discord Discord bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 1, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for adding a small, platform-scoped native Discord capability. Static review found two delivery gaps.

Problems

  • Forum sends return through _send_to_forum(channel, content) before the new normal-send extraction path (plugins/platforms/discord/adapter.py:2938-2948). _send_to_forum() creates its starter with raw content (plugins/platforms/discord/adapter.py:3050-3061), so forum posts will show [EMBED] JSON rather than receive an embed.
  • Finalized streaming content over 2,000 characters returns to _edit_overflow_split() before the new extraction code (plugins/platforms/discord/adapter.py:3218-3222). That helper chunks the raw content (plugins/platforms/discord/adapter.py:3317-3347), so a large marker is exposed instead of rendered.

Suggested changes

  • Parse before forum routing and pass the embed through the forum-thread creation path, with a forum regression test.
  • Parse finalized complete markers before overflow handling, then size-check only the remaining text; add a >2,000-character finalized-stream test.

Automated hermes-sweeper review.

Comment thread plugins/platforms/discord/adapter.py Outdated
# Format and split message if needed
formatted = self.format_message(content)
embed = None
if isinstance(formatted, str) and "[EMBED]" in formatted:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Forum parents return at the earlier _send_to_forum(channel, content) branch, before this normal-send extraction runs. Parse before that branch and carry the embed into _send_to_forum()/create_thread(); otherwise forum posts expose the marker as text.

Comment thread plugins/platforms/discord/adapter.py Outdated
try:
await msg.edit(content=formatted)
embed = None
edit_content = formatted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This runs after the finalized >2,000-character branch has already returned to _edit_overflow_split(), which chunks the raw marker content. Extract a complete finalized marker before overflow handling, then apply the text limit to the remaining content.

Addresses review feedback (delivery gaps):
- send(): embed extraction now runs BEFORE forum routing, and
  _send_to_forum() passes the parsed embed through to create_thread()
  so forum starters render the embed instead of raw JSON
- edit_message(): finalized oversized streams parse the marker before
  the overflow pre-flight; _edit_overflow_split() carries the embed on
  the first chunk and only size-checks the remaining text
- 4 new regression tests: forum send, oversized finalized stream,
  normal finalize edit, mid-stream placeholder
@Jonesyj83

Copy link
Copy Markdown
Author

Thanks for the review @teknium1 — both gaps are fixed.

1. Forum routing: embed extraction now runs before the forum branch in send(), and _send_to_forum() accepts an embed param that rides on the create_thread() starter, so forum posts render the embed instead of raw JSON. Added test_forum_send_gets_embed.

2. Overflow splitting: edit_message() now parses the marker before the overflow pre-flight — the remaining text is what gets size-checked, and _edit_overflow_split() carries the embed on the first chunk. Added test_finalize_oversized_embed_parses_before_split (>2,000-char finalized stream).

Also added tests for the normal finalize edit path and the mid-stream placeholder. 15 tests in the file, 45 Discord tests total — all green.

Models generating the [EMBED] block occasionally inject (1/2)-style
segment markers or trailing commas, which broke json.loads and caused
the raw marker to be sent. _clean_embed_json() strips both artifacts
outside string literals before parsing; string contents are preserved.

- 2 new tests: (1/2) + trailing comma regression, string-preservation
Cron deliveries and the send_message tool route through
_standalone_send (REST), never adapter.send() — so [EMBED] markers
arrived as raw text. _split_embed_from_message() + _embed_payload_from_block()
extract the marker into a native embeds[] payload on the plain-text POST,
the forum-thread POST, and the caption/media starter.

- 3 new tests: standalone split, no-marker passthrough, bad-JSON fallback
@Jonesyj83

Copy link
Copy Markdown
Author

Verified working end-to-end in production: the [EMBED] marker now renders as a native embed through ALL delivery paths — interactive streaming (edit_message finalize), normal send(), and the standalone REST path used by cron jobs and the send_message tool. Also tolerates LLM artifacts in the JSON block ((1/2) segment markers, trailing commas). 20 unit tests green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/discord Discord bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants