Skip to content

fix(framework): unify text and markdown making markdown the default fixes NV-7392#10880

Merged
ChmaraX merged 6 commits intonextfrom
fix/unify-markdown-text-NV-7392
Apr 27, 2026
Merged

fix(framework): unify text and markdown making markdown the default fixes NV-7392#10880
ChmaraX merged 6 commits intonextfrom
fix/unify-markdown-text-NV-7392

Conversation

@ChmaraX
Copy link
Copy Markdown
Contributor

@ChmaraX ChmaraX commented Apr 27, 2026

Summary

  • Removes the text content type from the agent reply system — plain strings passed to ctx.reply() now serialize as { markdown } instead of { text }
  • ReplyContent.text field removed from the wire format; ReplyContentDto.text removed from the API DTO
  • ChatSdkService post/edit branches simplified: non-card content always goes through thread.post({ markdown }), which routes through the chat SDK's platform conversion layer (Slack mrkdwn, Teams HTML, etc.) rather than raw passthrough

Why

string{ text } was a raw passthrough to the platform — asterisks stayed as asterisks. string{ markdown } lets the chat SDK handle platform-specific formatting conversion, which is the correct behavior. Since markdown is a superset of plain text, there is no regression for callers passing plain strings.

Breaking change

ReplyContent.text is removed. This is a backwards-incompatible change intentionally made while the agents feature is pre-release.

// Before
ctx.reply('hello')  →  { text: 'hello' }

// After
ctx.reply('hello')  →  { markdown: 'hello' }

Test plan

  • pnpm --filter @novu/framework test — 289 tests pass, no type errors

Made with Cursor

What changed

Unifies the agent reply content model by removing the text field and making markdown the default. Plain strings passed to ctx.reply() now serialize as { markdown } instead of { text }, allowing the chat SDK to handle platform-specific formatting conversion (e.g., Slack mrkdwn, Teams HTML) rather than treating text as a separate passthrough format.

Affected areas

  • api: Removed text field from ReplyContentDto DTO; ChatSdkService now always routes non-card content through thread.post({ markdown }) instead of branching on text versus markdown; text fallback extraction logic updated to only consider markdown.
  • framework: ReplyContent interface no longer includes optional text field; agent context serializes plain string input as { markdown } instead of { text }.

Key technical decisions

  • Breaking change: ReplyContent.text removed from the wire format and API contract (intentional while agents are pre-release).
  • Markdown is treated as a superset of plain text, ensuring callers passing plain strings are not regressed.
  • Chat SDK platform conversion layer now handles formatting for both markdown and text input through unified routing.

Testing

Existing unit tests (289 total) pass without modification. E2E tests updated to use markdown field instead of text in request payloads and assertions across all reply and edit scenarios.

ChmaraX added 4 commits April 27, 2026 10:40
…es NV-7410

Wrap all three provider delivery boundaries (thread.post, adapter.editMessage,
handler.send) in chat-sdk.service.ts with .catch(toDeliveryError), converting
any unhandled provider error into a BadGatewayException (502) that preserves
the original error message instead of swallowing it as a generic 500.

Add AgentDeliveryError to @novu/framework so developers can instanceof-check
delivery failures in their agent handlers and react to them explicitly.

Old framework versions automatically get better error messages from the 502
body without any code changes — the new typed error is additive.

Made-with: Cursor
…dpoint fixes NV-7410

Same unguarded handler.send() pattern as the delivery path. Wrap it with
.catch() so provider errors (invalid API key, 401, etc.) are returned as
BadGatewayException (502) with the original message, making the dashboard
toast actionable instead of showing a generic internal server error.

Made-with: Cursor
… delivery

Extract nested error detail from common REST API error shapes (errors[0].message
or body.message) so the surfaced message is actionable rather than just the
HTTP status text e.g. "Unauthorized: The provided authorization grant is invalid"
instead of just "Unauthorized".

Made-with: Cursor
…ixes NV-7392

Remove the separate `text` content type from the agent reply system.
Plain strings passed to `ctx.reply()` now serialize as `{ markdown }`
instead of `{ text }`, routing them through the chat SDK's platform
conversion layer (mrkdwn for Slack, HTML for Teams, etc.) rather than
raw passthrough.

Breaking change: `ReplyContent.text` is removed. Any agent code relying
on the `text` wire field should update to `markdown`.

Made-with: Cursor
@linear
Copy link
Copy Markdown

linear Bot commented Apr 27, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

This PR removes the text field from agent reply content schemas and replaces it with markdown throughout the codebase. The changes span DTOs, validation rules, service handlers, type definitions, serialization logic, and corresponding test assertions across both the API and framework packages.

Changes

Cohort / File(s) Summary
DTO & Validation
apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
Removed text field from ReplyContentDto and updated validation to enforce exactly one of markdown or card is present, removing text from mutual-exclusion logic.
E2E Tests
apps/api/src/app/agents/e2e/agent-reply.e2e.ts
Updated all test request payloads to use markdown field instead of text across reply, edit, signals, and failure scenarios.
Service Layer
apps/api/src/app/agents/services/chat-sdk.service.ts
Modified postToConversation and editInConversation to use markdown: content.markdown ?? '' instead of branching on presence or falling back to content.text.
Usecase Layer
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
Updated extractTextFallback to prioritize markdown over text and removed reliance on content.text field.
Framework Types
packages/framework/src/resources/agent/agent.types.ts
Removed optional text?: string field from ReplyContent interface; updated documentation to clarify plain strings are converted to markdown by the chat SDK.
Framework Serialization
packages/framework/src/resources/agent/agent.context.ts
Updated serializeContent to serialize plain string MessageContent as { markdown: ... } instead of { text: ... }.
Framework Tests
packages/framework/src/resources/agent/agent.test.ts
Updated assertions to validate reply.markdown (and edit.content.markdown) instead of reply.text; removed assertions expecting text to be undefined.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • novuhq/novu#10877: Modifies agent messaging flow through postToConversation/editInConversation and serialization in agent.context.ts, directly related to this PR's service and framework changes.
  • novuhq/novu#10754: Modifies agent reply payload DTOs and ReplyContent schema definition, overlapping with this PR's DTO and type signature changes.
  • novuhq/novu#10709: Affects agent reply e2e tests and reply payload shape handling, related to this PR's test updates and reply serialization logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title follows Conventional Commits format with valid type 'fix' and scope 'framework', uses imperative mood lowercase description, and ends with the Linear ticket reference 'fixes NV-7392' as required.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Base automatically changed from fix/agents-surface-provider-delivery-errors-NV-7410 to next April 27, 2026 09:50
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 27, 2026

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit d92b4aa
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69ef33050a4e48000822365f

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/framework/src/resources/agent/agent.types.ts (1)

96-103: Breaking removal of ReplyContent.text on a public package export.

Removing text from ReplyContent is a breaking change to a @novu/framework exported type and will cause TS2353 / TS2322 for any consumer that referenced it (object literals, intermediate variables typed as ReplyContent). The PR description notes this is intentional while agents are pre-release, which is fine — just confirm:

  1. The package version is bumped according to semver (major bump, or pre-release tag) and the change is called out in the changelog/migration notes.
  2. There are no remaining internal/external consumers reading ReplyContent.text.

A softer alternative — if you'd like to preserve the @deprecated step expected by the package guideline — is to keep the field as @deprecated text?: string for one release before deletion. Otherwise, an explicit "BREAKING CHANGE" note in the release entry is sufficient.

As per coding guidelines: "Deprecate symbols with @deprecated JSDoc before removing them from packages" and "Treat all exported symbols in packages as public API; follow semver conventions with breaking changes requiring major bumps".

#!/bin/bash
# Find any remaining references to ReplyContent.text in the monorepo (typed access patterns).
rg -nP --type=ts -C2 '\bReplyContent\b' 
echo "---"
# Look for direct .text property access on values typed as ReplyContent / agent reply content
rg -nP --type=ts -C2 '(reply|content)\.text\b' apps packages 2>/dev/null | rg -v '\.test\.|\.e2e\.|message\.text|params\.text' | head -200
echo "---"
# Confirm framework package version handling
fd -t f 'package.json' packages/framework -x cat {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/resources/agent/agent.types.ts` around lines 96 - 103,
Restore a nullable deprecated text property on the exported ReplyContent type to
avoid breaking consumers: add " /** `@deprecated` text will be removed in next
major release */ text?: string" to the ReplyContent interface (keep
markdown/card/files as-is), then run the repository search suggested in the
comment to confirm there are no remaining internal consumers reading text (check
for ReplyContent references and .text access), and finally ensure the package
version/changelog is updated for a breaking change if you intend to remove text
immediately — otherwise bump a minor and remove text only after one release with
a clear deprecation note in the changelog.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/framework/src/resources/agent/agent.types.ts`:
- Around line 96-103: Restore a nullable deprecated text property on the
exported ReplyContent type to avoid breaking consumers: add " /** `@deprecated`
text will be removed in next major release */ text?: string" to the ReplyContent
interface (keep markdown/card/files as-is), then run the repository search
suggested in the comment to confirm there are no remaining internal consumers
reading text (check for ReplyContent references and .text access), and finally
ensure the package version/changelog is updated for a breaking change if you
intend to remove text immediately — otherwise bump a minor and remove text only
after one release with a clear deprecation note in the changelog.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c77df1f3-341b-4a2c-b942-9b671e93b756

📥 Commits

Reviewing files that changed from the base of the PR and between 6e22999 and d92b4aa.

📒 Files selected for processing (7)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/e2e/agent-reply.e2e.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
💤 Files with no reviewable changes (1)
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts

@ChmaraX ChmaraX merged commit d47ad61 into next Apr 27, 2026
37 checks passed
@ChmaraX ChmaraX deleted the fix/unify-markdown-text-NV-7392 branch April 27, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant