mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 06:41:44 +00:00
fix: mark card field as optional in message tool schema
The `createMessageToolCardSchema()` helper returned a bare `Type.Object()`
which TypeBox treats as required when merged into the parent tool schema via
`Type.Object({ card: ... })`. This caused schema validation to reject
media-only sends on Feishu and MSTeams with "must have required property
card", even though the implementation correctly treats card as optional.
Wrap the return value in `Type.Optional()` so the card field is excluded
from the JSON Schema `required` array. Fixes the catch-22 where omitting
card fails validation and including an empty card triggers the runtime
"does not support card with media" guard.
Closes #53697
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
eaad4ad1be
commit
ca578a9183
@@ -576,6 +576,45 @@ describe("feishuPlugin actions", () => {
|
||||
).rejects.toThrow("Feishu thread-reply requires messageId.");
|
||||
});
|
||||
|
||||
it("declares card as optional in the tool schema", () => {
|
||||
const discovery = feishuPlugin.actions?.describeMessageTool?.({ cfg });
|
||||
const schema = Array.isArray(discovery?.schema) ? discovery.schema[0] : discovery?.schema;
|
||||
const cardSchema = schema?.properties?.card;
|
||||
expect(cardSchema).toBeDefined();
|
||||
// TypeBox marks Optional schemas with Symbol(TypeBox.Optional) = "Optional".
|
||||
expect(
|
||||
(cardSchema as unknown as Record<symbol, unknown>)?.[Symbol.for("TypeBox.Optional")],
|
||||
).toBe("Optional");
|
||||
});
|
||||
|
||||
it("sends media-only messages without requiring card", async () => {
|
||||
feishuOutboundSendMediaMock.mockResolvedValueOnce({
|
||||
channel: "feishu",
|
||||
messageId: "om_media_only",
|
||||
details: { messageId: "om_media_only", chatId: "oc_group_1" },
|
||||
});
|
||||
|
||||
const result = await feishuPlugin.actions?.handleAction?.({
|
||||
action: "send",
|
||||
params: {
|
||||
to: "chat:oc_group_1",
|
||||
media: "https://example.com/image.png",
|
||||
},
|
||||
cfg,
|
||||
accountId: undefined,
|
||||
toolContext: {},
|
||||
mediaLocalRoots: [],
|
||||
} as never);
|
||||
|
||||
expect(feishuOutboundSendMediaMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: "chat:oc_group_1",
|
||||
mediaUrl: "https://example.com/image.png",
|
||||
}),
|
||||
);
|
||||
expect(result?.details).toMatchObject({ messageId: "om_media_only" });
|
||||
});
|
||||
|
||||
it("fails for unsupported action names", async () => {
|
||||
await expect(
|
||||
feishuPlugin.actions?.handleAction?.({
|
||||
|
||||
@@ -25,11 +25,13 @@ export function createMessageToolButtonsSchema(): TSchema {
|
||||
|
||||
/** Schema helper for channels that accept provider-native card payloads. */
|
||||
export function createMessageToolCardSchema(): TSchema {
|
||||
return Type.Object(
|
||||
{},
|
||||
{
|
||||
additionalProperties: true,
|
||||
description: "Structured card payload for channels that support card-style messages.",
|
||||
},
|
||||
return Type.Optional(
|
||||
Type.Object(
|
||||
{},
|
||||
{
|
||||
additionalProperties: true,
|
||||
description: "Structured card payload for channels that support card-style messages.",
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user