LogoRobo.js

Hooks

Intercept and customize the AI pipeline with chat and reply hooks.

Hooks let you intercept the AI pipeline at two points: before messages reach the engine (chat hook) and after the response is generated (reply hook). Use them for content filtering, response augmentation, analytics, or custom post-processing.

Registration

Two ways to register hooks.

Via plugin config

config/plugins/robojs/ai.ts
export default {
	hooks: {
		chat: async (ctx) => {
			// Preprocess messages
		},
		reply: (ctx) => {
			// Postprocess response
		}
	}
}
config/plugins/robojs/ai.js
export default {
	hooks: {
		chat: async (ctx) => {
			// Preprocess messages
		},
		reply: (ctx) => {
			// Postprocess response
		}
	}
}

Via engine instance

config/plugins/robojs/ai.ts
import { OpenAiEngine } from '@robojs/ai/engines/openai'

const engine = new OpenAiEngine({ /* ... */ })

engine.on('chat', async (ctx) => {
	// Preprocess messages
})
engine.on('reply', (ctx) => {
	// Postprocess response
})

export default { engine }
config/plugins/robojs/ai.js
import { OpenAiEngine } from '@robojs/ai/engines/openai'

const engine = new OpenAiEngine({ /* ... */ })

engine.on('chat', async (ctx) => {
	// Preprocess messages
})
engine.on('reply', (ctx) => {
	// Postprocess response
})

export default { engine }

Unregister a hook with engine.off('chat', handler) or engine.off('reply', handler).

Chat hook

Runs before messages reach the engine. Use it to filter, modify, or inject messages.

Signature: (context: ChatHookContext) => Promise<void | ChatMessage[]> | void | ChatMessage[]

ChatHookContext

Prop

Type

You can either mutate ctx.messages in place or return a new ChatMessage[] array to replace it entirely.

Example -- content filtering

config/plugins/robojs/ai.ts
export default {
	hooks: {
		chat: async (ctx) => {
			ctx.messages = ctx.messages.filter((m) => {
				if (typeof m.content === 'string') {
					return !m.content.includes('BLOCKED_WORD')
				}
				return true
			})
		}
	}
}
config/plugins/robojs/ai.js
export default {
	hooks: {
		chat: async (ctx) => {
			ctx.messages = ctx.messages.filter((m) => {
				if (typeof m.content === 'string') {
					return !m.content.includes('BLOCKED_WORD')
				}
				return true
			})
		}
	}
}

Reply hook

Runs after the engine generates a response. Use it to modify, replace, or augment the reply before it's sent to Discord.

Signature: (context: ReplyHookContext) => Promise<ChatReply | void> | ChatReply | void

ReplyHookContext

Prop

Type

Return a ChatReply to override the response. Return nothing to keep the default.

ChatReply

Prop

Type

config/plugins/robojs/ai.ts
import type { ReplyHookContext, ChatReply } from '@robojs/ai'

export default {
	hooks: {
		reply: (ctx: ReplyHookContext): ChatReply | void => {
			const content = ctx.response.message?.content
			if (typeof content === 'string') {
				return { text: content + '\n\n*Powered by Sage*' }
			}
		}
	}
}
config/plugins/robojs/ai.mjs
export default {
	hooks: {
		reply: (ctx) => {
			const content = ctx.response.message?.content
			if (typeof content === 'string') {
				return { text: content + '\n\n*Powered by Sage*' }
			}
		}
	}
}

Example -- MCP degradation handling

config/plugins/robojs/ai.ts
export default {
	hooks: {
		reply: (ctx) => {
			if (ctx.degradedMcpServers?.length) {
				console.warn('Degraded MCP servers:', ctx.degradedMcpServers)
				const text = ctx.response.message?.content ?? ''
				return { text: text + '\n\nSome external tools were temporarily unavailable.' }
			}
		}
	}
}
config/plugins/robojs/ai.js
export default {
	hooks: {
		reply: (ctx) => {
			if (ctx.degradedMcpServers?.length) {
				console.warn('Degraded MCP servers:', ctx.degradedMcpServers)
				const text = ctx.response.message?.content ?? ''
				return { text: text + '\n\nSome external tools were temporarily unavailable.' }
			}
		}
	}
}

Execution order

Hooks run sequentially in registration order. All chat hooks run before the engine call. All reply hooks run after. If multiple hooks are registered for the same event, they execute one at a time in the order they were added.

For reply hooks, the first hook that returns a ChatReply wins. Subsequent reply hooks are skipped once a reply is returned.

Next steps

On this page