Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/framework/src/resources/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,12 @@ describe('agent dispatch via NovuRequestHandler', () => {

it('should provide read-only context properties from bridge payload', async () => {
let capturedCtx: any;
let capturedMessage: any;

const testBot = agent('test-bot', {
onMessage: async ({ ctx }) => {
onMessage: async ({ message, ctx }) => {
capturedCtx = ctx;
capturedMessage = message;
await ctx.reply('ok');
},
});
Expand All @@ -401,7 +403,7 @@ describe('agent dispatch via NovuRequestHandler', () => {
await vi.waitFor(() => expect(capturedCtx).toBeDefined());

expect(capturedCtx.event).toBe('onMessage');
expect(capturedCtx.message?.text).toBe('Hello bot!');
expect(capturedMessage.text).toBe('Hello bot!');
expect(capturedCtx.conversation.identifier).toBe('conv-456');
expect(capturedCtx.subscriber?.subscriberId).toBe('sub-001');
expect(capturedCtx.platform).toBe('slack');
Expand Down Expand Up @@ -1033,7 +1035,6 @@ describe('agent dispatch via NovuRequestHandler', () => {

expect(capturedCtx.event).toBe('onAction');
expect(capturedCtx.action).toEqual({ actionId: 'confirm', value: 'yes', sourceMessageId: 'msg-card-001' });
expect(capturedCtx.message).toBeNull();

const replyCall = fetchMock.mock.calls.find(
(call: any[]) => call[0] === 'https://api.novu.co/v1/agents/test-bot/reply'
Expand Down
6 changes: 2 additions & 4 deletions packages/framework/src/resources/agent/agent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,6 @@ interface AgentContextBase {
/** Context passed to the `onMessage` handler. */
export interface AgentMessageContext extends AgentContextBase {
readonly event: 'onMessage';
/** The incoming message that triggered this handler. */
readonly message: AgentMessage;
}

/** Context passed to the `onAction` handler. */
Expand Down Expand Up @@ -302,8 +300,8 @@ export type AgentContext = AgentMessageContext | AgentActionContext | AgentReact
export interface AgentHandlers {
/**
* Fires on every text message the user sends.
* `message` is the incoming message. `ctx` provides conversation history, subscriber,
* metadata, and reply/trigger methods.
* `payload.message` is the incoming message. `payload.ctx` provides conversation history,
* subscriber, metadata, and reply/trigger methods.
* Return a string or JSX card to reply, or call `ctx.reply()` directly
* for more control (e.g. editing a message in place).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ app/

## Agent API

Your agent handler receives a context object with:
Your `onMessage` handler receives `{ message, ctx }`:

- **`message`** — The inbound message (text, author, timestamp)

| Method / Property | Description |
|---|---|
| `ctx.message` | The inbound message (text, author, timestamp) |
| `ctx.conversation` | Current conversation state and metadata |
| `ctx.history` | Recent conversation history |
| `ctx.subscriber` | Resolved subscriber info |
Expand All @@ -48,13 +49,13 @@ Your agent handler receives a context object with:
Replace the demo handler in `app/novu/agents/support-agent.tsx` with your LLM call:

```typescript
onMessage: async (ctx) => {
onMessage: async ({ message, ctx }) => {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{ role: 'system', content: 'You are a helpful support agent.' },
...ctx.history.map((h) => ({ role: h.role, content: h.content })),
{ role: 'user', content: ctx.message?.text ?? '' },
{ role: 'user', content: message.text },
],
});

Expand Down
Loading