feat: Add bot API endpoint for reading room messages

Adds a new GET endpoint at /rooms/:room_id/:bot_key/messages that allows
bots to read messages from rooms they are members of.

The endpoint returns JSON with:
- Room info (id, name)
- Messages array with body (plain/html), created_at, and creator info
- Pagination info (oldest_id, newest_id, has_more)

Supports pagination via ?before=:id and ?after=:id query parameters,
consistent with the existing pagination in the messages controller.

This enables AI bots and other automated agents to understand conversation
context when responding to messages, rather than only receiving the single
message that triggered the webhook.

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
John-Mason Shackelford
2026-04-08 13:53:17 -04:00
parent 3fada3d997
commit d4a56784e0
3 changed files with 90 additions and 3 deletions
+43 -1
View File
@@ -1,5 +1,11 @@
class Messages::ByBotsController < MessagesController
allow_bot_access only: :create
allow_bot_access only: %i[ index create ]
def index
set_room
@messages = find_paged_messages
render json: messages_as_json(@messages)
end
def create
super
@@ -7,6 +13,42 @@ class Messages::ByBotsController < MessagesController
end
private
def messages_as_json(messages)
{
room: {
id: @room.id,
name: @room.name
},
messages: messages.map { |m| message_as_json(m) },
pagination: pagination_info(messages)
}
end
def message_as_json(message)
{
id: message.id,
body: {
plain: message.plain_text_body,
html: message.body&.body&.to_s
},
created_at: message.created_at.iso8601,
creator: {
id: message.creator.id,
name: message.creator.name,
is_bot: message.creator.role == "bot"
}
}
end
def pagination_info(messages)
return {} if messages.empty?
{
oldest_id: messages.last.id,
newest_id: messages.first.id,
has_more: messages.size == Message::PAGE_SIZE
}
end
def message_params
if params[:attachment]
params.permit(:attachment)
+2
View File
@@ -62,6 +62,8 @@ Rails.application.routes.draw do
resources :rooms do
resources :messages
# Bot API endpoints - authenticated via bot_key in URL
get ":bot_key/messages", to: "messages/by_bots#index", as: :bot_messages_index
post ":bot_key/messages", to: "messages/by_bots#create", as: :bot_messages
scope module: "rooms" do
@@ -51,8 +51,51 @@ class Messages::ByBotsControlleTest < ActionDispatch::IntegrationTest
assert_response :redirect
end
test "denied index" do
get room_messages_url(@room, bot_key: users(:bender).bot_key, format: :json)
test "index returns messages as JSON" do
get room_bot_messages_index_url(@room, users(:bender).bot_key)
assert_response :success
json = JSON.parse(response.body)
assert json["room"]["id"].present?
assert json["room"]["name"].present?
assert json["messages"].is_a?(Array)
assert json["pagination"].present?
end
test "index includes message details" do
# Create a message in the room first
post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Test message for index"
get room_bot_messages_index_url(@room, users(:bender).bot_key)
assert_response :success
json = JSON.parse(response.body)
message = json["messages"].find { |m| m["body"]["plain"] == "Test message for index" }
assert message.present?, "Expected to find the test message"
assert message["id"].present?
assert message["created_at"].present?
assert message["creator"]["id"].present?
assert message["creator"]["name"].present?
end
test "index supports pagination with before parameter" do
get room_bot_messages_index_url(@room, users(:bender).bot_key, before: Message.last.id)
assert_response :success
end
test "index supports pagination with after parameter" do
get room_bot_messages_index_url(@room, users(:bender).bot_key, after: Message.first.id)
assert_response :success
end
test "index requires valid bot key" do
get room_bot_messages_index_url(@room, "invalid-bot-key")
assert_response :redirect # Redirects to login
end
test "regular messages index still denied for bots" do
# The standard messages endpoint (not the bot-specific one) should still be forbidden
get room_messages_url(@room, bot_key: users(:bender).bot_key)
assert_response :forbidden
end
end