fix: validate feishu chat page size

This commit is contained in:
Peter Steinberger
2026-05-28 21:45:20 -04:00
parent d2fbc8c0e7
commit d33c2eefce
3 changed files with 69 additions and 2 deletions

View File

@@ -1,3 +1,4 @@
import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
import { Type, type Static } from "typebox";
const CHAT_ACTION_VALUES = ["members", "info", "member_info"] as const;
@@ -11,7 +12,10 @@ export const FeishuChatSchema = Type.Object({
}),
chat_id: Type.Optional(Type.String({ description: "Chat ID (from URL or event payload)" })),
member_id: Type.Optional(Type.String({ description: "Member ID for member_info lookups" })),
page_size: Type.Optional(Type.Number({ description: "Page size (1-100, default 50)" })),
page_size: optionalPositiveIntegerSchema({
maximum: 100,
description: "Page size (1-100, default 50)",
}),
page_token: Type.Optional(Type.String({ description: "Pagination token" })),
member_id_type: Type.Optional(
Type.Unsafe<(typeof MEMBER_ID_TYPE_VALUES)[number]>({

View File

@@ -167,6 +167,60 @@ describe("registerFeishuChatTools", () => {
});
});
it("advertises and validates member page_size as a positive integer", async () => {
const registerTool = vi.fn();
registerFeishuChatTools(
createChatToolApi({
config: {
channels: {
feishu: {
enabled: true,
appId: "app_id",
appSecret: "app_secret", // pragma: allowlist secret
tools: { chat: true },
},
},
},
registerTool,
}),
);
const tool = registerTool.mock.calls[0]?.[0];
expect(tool?.parameters.properties.page_size).toMatchObject({
type: "integer",
minimum: 1,
maximum: 100,
});
chatMembersGetMock.mockResolvedValueOnce({
code: 0,
data: { has_more: false, items: [] },
});
await tool.execute("tc_page_size_string", {
action: "members",
chat_id: "oc_1",
page_size: "25",
});
expect(chatMembersGetMock).toHaveBeenLastCalledWith({
path: { chat_id: "oc_1" },
params: {
page_size: 25,
page_token: undefined,
member_id_type: "open_id",
},
});
const invalidResult = await tool.execute("tc_page_size_invalid", {
action: "members",
chat_id: "oc_1",
page_size: 0,
});
expect(invalidResult.details.error).toContain(
"page_size must be a positive integer between 1 and 100",
);
expect(chatMembersGetMock).toHaveBeenCalledTimes(1);
});
it("skips registration when chat tool is disabled", () => {
const registerTool = vi.fn();
registerFeishuChatTools(

View File

@@ -1,4 +1,5 @@
import type * as Lark from "@larksuiteoapi/node-sdk";
import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccounts } from "./accounts.js";
import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js";
@@ -13,6 +14,13 @@ function json(data: unknown) {
};
}
function readChatPageSize(params: Record<string, unknown>): number | undefined {
return readPositiveIntegerParam(params, "page_size", {
max: 100,
message: "page_size must be a positive integer between 1 and 100",
});
}
export async function getChatInfo(client: Lark.Client, chatId: string) {
const res = await client.im.chat.get({ path: { chat_id: chatId } });
if (res.code !== 0) {
@@ -146,6 +154,7 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) {
description: "Feishu chat operations. Actions: members, info, member_info",
parameters: FeishuChatSchema,
async execute(_toolCallId, params) {
const rawParams = params as Record<string, unknown>;
const p = params as FeishuChatParams;
try {
const client = getClient();
@@ -158,7 +167,7 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) {
await getChatMembers(
client,
p.chat_id,
p.page_size,
readChatPageSize(rawParams),
p.page_token,
p.member_id_type,
),