Skip to content

refactor(api-service): consolidate agent protocol types to single source of truth#10754

Merged
ChmaraX merged 1 commit intonextfrom
refactor/consolidate-agent-protocol-types
Apr 16, 2026
Merged

refactor(api-service): consolidate agent protocol types to single source of truth#10754
ChmaraX merged 1 commit intonextfrom
refactor/consolidate-agent-protocol-types

Conversation

@ChmaraX
Copy link
Copy Markdown
Contributor

@ChmaraX ChmaraX commented Apr 16, 2026

Summary

  • Eliminate duplicate type definitionsBridgeMessage, BridgeConversation, BridgeSubscriber, BridgeHistoryEntry, BridgeAction, BridgePlatformContext, and the local AgentBridgeRequest interface in the API were structurally identical to types already defined in packages/framework. Replaced them all with imports from @novu/framework.
  • Export all agent protocol types from frameworkAgentBridgeRequest, AgentMessage, AgentConversation, AgentSubscriber, AgentHistoryEntry, AgentAction, AgentReaction, AgentPlatformContext, Signal, MetadataSignal, TriggerSignal, ReplyContent, and AgentEventEnum are now part of the public API of @novu/framework.
  • Add discriminated union validation for signals — New IsValidSignal custom validator ensures metadata signals have key + value and trigger signals have workflowId, enforced at the DTO layer.
  • Remove debug console.logs from enterprise PopulateConversationParticipants service.

Architecture

graph TD
    subgraph "Source of Truth"
        FW["packages/framework<br/>Agent protocol types"]
    end
    subgraph "Cross-cutting"
        SH["packages/shared<br/>Feature flags, permissions, enums"]
    end
    subgraph "Persistence"
        DAL["libs/dal<br/>Entities + DB enums"]
    end
    subgraph "API Layer"
        API["apps/api<br/>DTOs, commands, services"]
    end

    FW -->|"protocol types<br/>(import)"| API
    SH -->|"flags, permissions"| API
    DAL -->|"entities"| API
    FW -->|"SDK types"| USER["User Code"]
    SH -->|"enums"| DASH["apps/dashboard"]
Loading

Enterprise PR

https://github.com/novuhq/packages-enterprise/pull/445 — merge first

Test plan

  • pnpm build passes for packages/framework and apps/api
  • Existing agent e2e tests pass unchanged
  • Signal validation rejects malformed payloads (e.g. metadata signal without key)

Made with Cursor

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 16, 2026

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This PR consolidates agent-related types and enums by migrating local definitions to the @novu/framework package and updating dependent code. Changes include re-exporting AgentEventEnum, FileRef, and Signal types from the framework, implementing a new signal validator, updating service method signatures to use framework types, expanding framework's public exports, and adding Dashboard as a conversational provider.

Changes

Cohort / File(s) Summary
Agent DTOs & Exports
apps/api/src/app/agents/dtos/agent-event.enum.ts, apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts, apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.command.ts
Replaced local type definitions (AgentEventEnum, FileRef, Signal) with re-exports from @novu/framework. Added IsValidSignal validator to enforce type-specific signal constraints (metadata requires key/value; trigger requires workflowId).
Agent Services
apps/api/src/app/agents/services/agent-inbound-handler.service.ts, apps/api/src/app/agents/services/bridge-executor.service.ts
Updated method signatures and parameter types to use framework types: handleAction now accepts AgentAction instead of BridgeAction; BridgeExecutorParams uses AgentPlatformContext and AgentAction; payload mapping functions return framework types (AgentMessage, AgentConversation, AgentSubscriber, AgentReaction, AgentHistoryEntry).
Framework Exports
packages/framework/src/index.ts, packages/framework/src/resources/agent/index.ts
Expanded framework's public exports to include agent-related types: AgentAction, AgentAttachment, AgentBridgeRequest, AgentConversation, AgentHistoryEntry, AgentMessage, AgentMessageAuthor, AgentPlatformContext, AgentReplyPayload, AgentSubscriber, MetadataSignal, ReplyContent, Signal, TriggerSignal, and AgentEventEnum.
Shared Configuration
packages/shared/src/consts/providers/conversational-providers.ts
Added Dashboard provider entry to CONVERSATIONAL_PROVIDERS array with InAppProviderIdEnum.Novu providerId.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #10733 — Modifies same agent-related types and code paths including AgentEventEnum, bridge-executor, and agent-inbound-handler services.
  • #10751 — Updates agent/bridge type surface and payload mapping functions (AgentMessage, AgentHistoryEntry) in BridgeExecutorService.
  • #10727 — Also replaces local agent protocol/type definitions with framework exports including AgentEventEnum, AgentAction, and signal types.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with valid type 'refactor', valid scope 'api-service', and a clear lowercase imperative description that accurately summarizes the main objective of consolidating duplicate agent protocol types into a single source of truth from @novu/framework.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: refactor(api-service): consolidate agent protocol types to single source of truth

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts (1)

24-35: Consider enforcing strict discriminator exclusivity for SignalDto.

Current validation enforces required fields per type, but it still accepts mixed-shape payloads. If you want strict discriminated-union behavior, reject incompatible fields for each branch.

Optional hardening diff
   validate(signal: SignalDto): boolean {
     if (!signal?.type) return false;
 
     if (signal.type === 'metadata') {
-      return typeof signal.key === 'string' && signal.key.length > 0 && signal.value !== undefined;
+      return (
+        typeof signal.key === 'string' &&
+        signal.key.trim().length > 0 &&
+        signal.value !== undefined &&
+        signal.workflowId === undefined
+      );
     }
 
     if (signal.type === 'trigger') {
-      return typeof signal.workflowId === 'string' && signal.workflowId.length > 0;
+      return (
+        typeof signal.workflowId === 'string' &&
+        signal.workflowId.trim().length > 0 &&
+        signal.key === undefined &&
+        signal.value === undefined
+      );
     }
 
     return false;
   }

Also applies to: 166-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts` around lines 24 -
35, The validate method for SignalDto currently checks required fields for each
signal type but allows extra incompatible fields; update validate(signal:
SignalDto) to enforce strict discriminator exclusivity by rejecting signals that
contain fields not allowed for their type (e.g., when signal.type === 'metadata'
ensure workflowId is undefined or absent and when signal.type === 'trigger'
ensure key/value are undefined or absent), keep the existing checks for required
fields (key/value present for 'metadata', workflowId present for 'trigger') and
return false if any incompatible fields are present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.source:
- Line 1: The .source submodule pointer was advanced to commit
de44134b129e41f24dc931e96347a303f4ea5260 which does not exist on the enterprise
repo's next branch; ensure the enterprise PR novuhq/packages-enterprise#445 (or
that specific commit) is merged into the packages-enterprise/next branch before
merging this PR, or else update the .source pointer back to the current
packages-enterprise/next commit; locate the .source entry in the repo, either
merge/rebase the enterprise branch so the SHA is present or reset the submodule
pointer to the matching next-branch commit to resolve the blocking dependency.

---

Nitpick comments:
In `@apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts`:
- Around line 24-35: The validate method for SignalDto currently checks required
fields for each signal type but allows extra incompatible fields; update
validate(signal: SignalDto) to enforce strict discriminator exclusivity by
rejecting signals that contain fields not allowed for their type (e.g., when
signal.type === 'metadata' ensure workflowId is undefined or absent and when
signal.type === 'trigger' ensure key/value are undefined or absent), keep the
existing checks for required fields (key/value present for 'metadata',
workflowId present for 'trigger') and return false if any incompatible fields
are present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 185399af-28e6-4d29-8cac-1ec4ad33b3e8

📥 Commits

Reviewing files that changed from the base of the PR and between 0874ab8 and 70f6bed.

📒 Files selected for processing (9)
  • .source
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.command.ts
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/index.ts
  • packages/shared/src/consts/providers/conversational-providers.ts

Comment thread .source Outdated
@@ -1 +1 @@
Subproject commit b7ead722c175bc6d14d5186b58b8cae86df829d6
Subproject commit de44134b129e41f24dc931e96347a303f4ea5260
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ENTERPRISE_REPO="novuhq/packages-enterprise"
ENTERPRISE_PR="445"
PINNED_SHA="de44134b129e41f24dc931e96347a303f4ea5260"

echo "1) Check enterprise PR state/base branch"
gh pr view "$ENTERPRISE_PR" --repo "$ENTERPRISE_REPO" \
  --json number,state,mergedAt,baseRefName,headRefName,mergeCommit,url \
  | jq .

echo "2) Check pinned SHA exists in remote"
git ls-remote "https://github.com/${ENTERPRISE_REPO}.git" "$PINNED_SHA"

echo "3) Check pinned SHA is ancestor of 'next'"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT

git -C "$tmp_dir" init -q
git -C "$tmp_dir" remote add origin "https://github.com/${ENTERPRISE_REPO}.git"
git -C "$tmp_dir" fetch --depth=300 origin next "$PINNED_SHA"

NEXT_COMMIT="$(git -C "$tmp_dir" rev-parse FETCH_HEAD~1 2>/dev/null || true)"
# Re-fetch to ensure local refs are explicit
git -C "$tmp_dir" fetch --depth=300 origin next:refs/remotes/origin/next "$PINNED_SHA:$PINNED_SHA" >/dev/null 2>&1 || true

if git -C "$tmp_dir" merge-base --is-ancestor "$PINNED_SHA" refs/remotes/origin/next; then
  echo "OK: pinned SHA is contained in origin/next"
else
  echo "FAIL: pinned SHA is NOT contained in origin/next"
  exit 1
fi

Repository: novuhq/novu

Length of output: 212


🏁 Script executed:

rg "packages-enterprise" --type md --type txt --type json

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Check git configuration or any CI files that reference the enterprise repo
fd -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.json" -o -name "*.md" \) | xargs rg -l "enterprise" | head -20

Repository: novuhq/novu

Length of output: 1206


🏁 Script executed:

# Check if .gitmodules or similar config exists
find . -maxdepth 2 -name ".gitmodules" -o -name ".sourcemap" -o -name ".source*"

Repository: novuhq/novu

Length of output: 78


🏁 Script executed:

cat .gitmodules

Repository: novuhq/novu

Length of output: 148


🏁 Script executed:

cat .source

Repository: novuhq/novu

Length of output: 83


🏁 Script executed:

# Check the git history of .source to understand this change
git log --oneline -10 .source 2>/dev/null || echo "No git history available"

Repository: novuhq/novu

Length of output: 935


🏁 Script executed:

# The .source is a git submodule pointing to packages-enterprise
# Try to verify the SHA using git commands directly (no GitHub API)

PINNED_SHA="de44134b129e41f24dc931e96347a303f4ea5260"
ENTERPRISE_REPO_URL="git@github.com:novuhq/packages-enterprise.git"

echo "Attempting to verify SHA exists in enterprise repo..."

# Create a temporary directory for checking
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT

# Try SSH first (standard for this repo)
if git -C "$tmp_dir" clone --bare --depth=500 "$ENTERPRISE_REPO_URL" enterprise.git 2>&1 | head -20; then
  echo "--- Checking if SHA exists in cloned repo ---"
  git -C "$tmp_dir/enterprise.git" cat-file -t "$PINNED_SHA" 2>&1 || echo "SHA not found in shallow clone"
  
  echo "--- Checking branches ---"
  git -C "$tmp_dir/enterprise.git" branch -r | head -10
else
  echo "Clone failed, trying HTTPS fallback..."
  HTTPS_URL="https://github.com/novuhq/packages-enterprise.git"
  if git -C "$tmp_dir" clone --bare --depth=500 "$HTTPS_URL" enterprise.git 2>&1 | head -20; then
    echo "--- Checking if SHA exists ---"
    git -C "$tmp_dir/enterprise.git" cat-file -t "$PINNED_SHA" 2>&1 || echo "SHA not found"
  fi
fi

Repository: novuhq/novu

Length of output: 672


🏁 Script executed:

# Also check if we can use ls-remote to verify the SHA exists
git ls-remote --heads --tags "https://github.com/novuhq/packages-enterprise.git" | grep -E "(next|de44134b)" || echo "Could not verify via ls-remote"

Repository: novuhq/novu

Length of output: 213


🏁 Script executed:

cd .source && git log --oneline next | grep de44134b || echo "SHA not in next"

Repository: novuhq/novu

Length of output: 70


Subproject SHA must be merged into packages-enterprise/next before this PR can merge.

Line 1 updates the .source submodule pointer to de44134b129e41f24dc931e96347a303f4ea5260. This commit is not present in the next branch of the enterprise repository, but your PR explicitly states that novuhq/packages-enterprise#445 must merge first. This is a blocking dependency: ensure that commit is merged into next before proceeding with this merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.source at line 1, The .source submodule pointer was advanced to commit
de44134b129e41f24dc931e96347a303f4ea5260 which does not exist on the enterprise
repo's next branch; ensure the enterprise PR novuhq/packages-enterprise#445 (or
that specific commit) is merged into the packages-enterprise/next branch before
merging this PR, or else update the .source pointer back to the current
packages-enterprise/next commit; locate the .source entry in the repo, either
merge/rebase the enterprise branch so the SHA is present or reset the submodule
pointer to the matching next-branch commit to resolve the blocking dependency.

…rce of truth

Eliminate duplicate type definitions across packages/framework and apps/api.
The framework package now owns all agent protocol types (AgentBridgeRequest,
AgentMessage, AgentConversation, Signal, etc.) and the API imports from it
instead of maintaining local copies. Adds discriminated union validation for
SignalDto and exports all protocol types from the framework's public API.

Made-with: Cursor
@ChmaraX ChmaraX force-pushed the refactor/consolidate-agent-protocol-types branch from 70f6bed to a9ecde0 Compare April 16, 2026 14:35
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/src/app/agents/services/bridge-executor.service.ts (2)

186-203: ⚠️ Potential issue | 🟡 Minor

Insert the required blank line before these returns.

The new/updated returns in these methods don't follow the repo rule.

As per coding guidelines "Include a blank line before every return statement".

Also applies to: 232-240, 244-245, 248-257, 261-266, 270-278

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/bridge-executor.service.ts` around lines 186
- 203, Insert a blank line immediately before the return statement that
constructs and returns the event object (the one returning an object with keys
version, timestamp, deliveryId, event, agentId, replyUrl, conversationId,
integrationIdentifier, message mapped via mapMessage, conversation via
mapConversation, subscriber via mapSubscriber, history via mapHistory, platform,
platformContext, action, reaction via mapReaction) so it follows the repo rule
to have a blank line before every return; apply the same fix to the other return
statements in this file that build similar response objects (the other methods
that call mapMessage/mapConversation/mapSubscriber/mapHistory/mapReaction and
return platform/action/reaction payloads) to ensure each return is preceded by a
blank line.

269-278: ⚠️ Potential issue | 🟠 Major

Validate signalData before forwarding to framework.

mapHistory forwards persisted signalData directly without validation. The DTO validator (IsValidSignal) only protects inbound payloads; legacy database rows or other code paths can store malformed signals without validation. The database schema allows Mixed types, providing no enforcement. Additionally, the codebase stores resolve-type signals that are not part of the framework's Signal union (which defines only metadata and trigger). Revalidate or normalize signalData here to ensure outbound history payloads match the expected contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/bridge-executor.service.ts` around lines 269
- 278, The mapHistory function currently forwards persisted signalData directly;
update mapHistory to revalidate and normalize activity.signalData before
assigning it to AgentHistoryEntry.signalData so outbound history matches the
framework Signal union. Implement or call a validator/normalizer (e.g.,
isValidSignal or normalizeSignal) inside mapHistory: if signalData matches the
framework Signal shape (only metadata or trigger) include it, if it's a legacy
"resolve" or contains extra keys then transform it into an allowed Signal form
(or drop/return undefined) and ensure any invalid shapes are omitted; reference
the mapHistory function, AgentHistoryEntry.signalData, the existing
IsValidSignal DTO/validator, and the framework Signal union when locating where
to add this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/api/src/app/agents/services/bridge-executor.service.ts`:
- Around line 186-203: Insert a blank line immediately before the return
statement that constructs and returns the event object (the one returning an
object with keys version, timestamp, deliveryId, event, agentId, replyUrl,
conversationId, integrationIdentifier, message mapped via mapMessage,
conversation via mapConversation, subscriber via mapSubscriber, history via
mapHistory, platform, platformContext, action, reaction via mapReaction) so it
follows the repo rule to have a blank line before every return; apply the same
fix to the other return statements in this file that build similar response
objects (the other methods that call
mapMessage/mapConversation/mapSubscriber/mapHistory/mapReaction and return
platform/action/reaction payloads) to ensure each return is preceded by a blank
line.
- Around line 269-278: The mapHistory function currently forwards persisted
signalData directly; update mapHistory to revalidate and normalize
activity.signalData before assigning it to AgentHistoryEntry.signalData so
outbound history matches the framework Signal union. Implement or call a
validator/normalizer (e.g., isValidSignal or normalizeSignal) inside mapHistory:
if signalData matches the framework Signal shape (only metadata or trigger)
include it, if it's a legacy "resolve" or contains extra keys then transform it
into an allowed Signal form (or drop/return undefined) and ensure any invalid
shapes are omitted; reference the mapHistory function,
AgentHistoryEntry.signalData, the existing IsValidSignal DTO/validator, and the
framework Signal union when locating where to add this check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dda9e14-7aeb-4c9c-89a7-a5526cddea17

📥 Commits

Reviewing files that changed from the base of the PR and between 70f6bed and a9ecde0.

📒 Files selected for processing (8)
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.command.ts
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/index.ts
  • packages/shared/src/consts/providers/conversational-providers.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.command.ts
  • packages/shared/src/consts/providers/conversational-providers.ts
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts

@ChmaraX ChmaraX merged commit a6f9a49 into next Apr 16, 2026
37 checks passed
@ChmaraX ChmaraX deleted the refactor/consolidate-agent-protocol-types branch April 16, 2026 14:44
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