Merge 94517cfa7096a1fc1e156d2ae631d36a5a0fe9dd into 9fb78453e088cd7b553d7779faa0de5c83708e70
This commit is contained in:
commit
12dc8ef2c1
@ -1,12 +1,34 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
const FEISHU_DOC_ACTION_VALUES = [
|
||||
"read",
|
||||
"write",
|
||||
"append",
|
||||
"insert",
|
||||
"create",
|
||||
"list_blocks",
|
||||
"get_block",
|
||||
"update_block",
|
||||
"delete_block",
|
||||
"create_table",
|
||||
"write_table_cells",
|
||||
"create_table_with_values",
|
||||
"insert_table_row",
|
||||
"insert_table_column",
|
||||
"delete_table_rows",
|
||||
"delete_table_columns",
|
||||
"merge_table_cells",
|
||||
"upload_image",
|
||||
"upload_file",
|
||||
"color_text",
|
||||
] as const;
|
||||
|
||||
const tableCreationProperties = {
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
|
||||
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
|
||||
row_size: Type.Optional(Type.Integer({ description: "Table row count", minimum: 1 })),
|
||||
column_size: Type.Optional(Type.Integer({ description: "Table column count", minimum: 1 })),
|
||||
column_width: Type.Optional(
|
||||
Type.Array(Type.Number({ minimum: 1 }), {
|
||||
description: "Column widths in px (length should match column_size)",
|
||||
@ -14,169 +36,103 @@ const tableCreationProperties = {
|
||||
),
|
||||
};
|
||||
|
||||
export const FeishuDocSchema = Type.Union([
|
||||
Type.Object({
|
||||
action: Type.Literal("read"),
|
||||
doc_token: Type.String({ description: "Document token (extract from URL /docx/XXX)" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("write"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
content: Type.String({
|
||||
description: "Markdown content to write (replaces entire document content)",
|
||||
export const FeishuDocSchema = Type.Object(
|
||||
{
|
||||
action: Type.Unsafe<(typeof FEISHU_DOC_ACTION_VALUES)[number]>({
|
||||
type: "string",
|
||||
enum: [...FEISHU_DOC_ACTION_VALUES],
|
||||
description:
|
||||
"Document action to run: read, write, append, insert, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, insert_table_row, insert_table_column, delete_table_rows, delete_table_columns, merge_table_cells, upload_image, upload_file, color_text",
|
||||
}),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("append"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
content: Type.String({ description: "Markdown content to append to end of document" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("insert"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
content: Type.String({ description: "Markdown content to insert" }),
|
||||
after_block_id: Type.String({
|
||||
description: "Insert content after this block ID. Use list_blocks to find block IDs.",
|
||||
}),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("create"),
|
||||
title: Type.String({ description: "Document title" }),
|
||||
folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
|
||||
doc_token: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Document token. Required for all actions except create. Extract from URL /docx/XXX.",
|
||||
}),
|
||||
),
|
||||
content: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Markdown or text payload. Required for write, append, insert, update_block, and color_text.",
|
||||
}),
|
||||
),
|
||||
after_block_id: Type.Optional(
|
||||
Type.String({
|
||||
description: "Required for insert. Insert content after this block ID.",
|
||||
}),
|
||||
),
|
||||
title: Type.Optional(
|
||||
Type.String({
|
||||
description: "Required for create and rename-style content creation flows.",
|
||||
}),
|
||||
),
|
||||
folder_token: Type.Optional(Type.String({ description: "Optional target folder for create." })),
|
||||
grant_to_requester: Type.Optional(
|
||||
Type.Boolean({
|
||||
description:
|
||||
"Grant edit permission to the trusted requesting Feishu user from runtime context (default: true).",
|
||||
"For create, grant edit permission to the trusted requesting Feishu user from runtime context.",
|
||||
}),
|
||||
),
|
||||
block_id: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Block ID. Required for get_block, update_block, delete_block, table row/column operations, merge_table_cells, and color_text.",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("list_blocks"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("get_block"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("update_block"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
||||
content: Type.String({ description: "New text content" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("delete_block"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Block ID" }),
|
||||
}),
|
||||
// Table creation (explicit structure)
|
||||
Type.Object({
|
||||
action: Type.Literal("create_table"),
|
||||
...tableCreationProperties,
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("write_table_cells"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
table_block_id: Type.String({ description: "Table block ID" }),
|
||||
values: Type.Array(Type.Array(Type.String()), {
|
||||
description: "2D matrix values[row][col] to write into table cells",
|
||||
minItems: 1,
|
||||
}),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("create_table_with_values"),
|
||||
...tableCreationProperties,
|
||||
values: Type.Array(Type.Array(Type.String()), {
|
||||
description: "2D matrix values[row][col] to write into table cells",
|
||||
minItems: 1,
|
||||
}),
|
||||
}),
|
||||
// Table row/column manipulation
|
||||
Type.Object({
|
||||
action: Type.Literal("insert_table_row"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Table block ID" }),
|
||||
table_block_id: Type.Optional(
|
||||
Type.String({ description: "Table block ID. Required for write_table_cells." }),
|
||||
),
|
||||
values: Type.Optional(
|
||||
Type.Array(Type.Array(Type.String()), {
|
||||
description:
|
||||
"2D matrix values[row][col]. Required for write_table_cells and create_table_with_values.",
|
||||
minItems: 1,
|
||||
}),
|
||||
),
|
||||
row_index: Type.Optional(
|
||||
Type.Number({ description: "Row index to insert at (-1 for end, default: -1)" }),
|
||||
Type.Number({ description: "Optional row index for insert_table_row (-1 for end)." }),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("insert_table_column"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Table block ID" }),
|
||||
column_index: Type.Optional(
|
||||
Type.Number({ description: "Column index to insert at (-1 for end, default: -1)" }),
|
||||
Type.Number({
|
||||
description: "Optional column index for insert_table_column (-1 for end).",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("delete_table_rows"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Table block ID" }),
|
||||
row_start: Type.Number({ description: "Start row index (0-based)" }),
|
||||
row_count: Type.Optional(Type.Number({ description: "Number of rows to delete (default: 1)" })),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("delete_table_columns"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Table block ID" }),
|
||||
column_start: Type.Number({ description: "Start column index (0-based)" }),
|
||||
column_count: Type.Optional(
|
||||
Type.Number({ description: "Number of columns to delete (default: 1)" }),
|
||||
row_start: Type.Optional(
|
||||
Type.Number({ description: "Start row index. Required for delete/merge row actions." }),
|
||||
),
|
||||
row_count: Type.Optional(Type.Number({ description: "Rows to delete (default: 1)." })),
|
||||
row_end: Type.Optional(
|
||||
Type.Number({ description: "End row index (exclusive). Required for merge_table_cells." }),
|
||||
),
|
||||
column_start: Type.Optional(
|
||||
Type.Number({ description: "Start column index. Required for delete/merge column actions." }),
|
||||
),
|
||||
column_count: Type.Optional(Type.Number({ description: "Columns to delete (default: 1)." })),
|
||||
column_end: Type.Optional(
|
||||
Type.Number({
|
||||
description: "End column index (exclusive). Required for merge_table_cells.",
|
||||
}),
|
||||
),
|
||||
url: Type.Optional(Type.String({ description: "Remote file/image URL for upload actions." })),
|
||||
file_path: Type.Optional(
|
||||
Type.String({ description: "Local file path for upload_image or upload_file." }),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("merge_table_cells"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Table block ID" }),
|
||||
row_start: Type.Number({ description: "Start row index" }),
|
||||
row_end: Type.Number({ description: "End row index (exclusive)" }),
|
||||
column_start: Type.Number({ description: "Start column index" }),
|
||||
column_end: Type.Number({ description: "End column index (exclusive)" }),
|
||||
}),
|
||||
// Image / file upload
|
||||
Type.Object({
|
||||
action: Type.Literal("upload_image"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
url: Type.Optional(Type.String({ description: "Remote image URL (http/https)" })),
|
||||
file_path: Type.Optional(Type.String({ description: "Local image file path" })),
|
||||
image: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Image as data URI (data:image/png;base64,...) or plain base64 string. Use instead of url/file_path for DALL-E outputs, canvas screenshots, etc.",
|
||||
"Image as data URI or base64 string for upload_image when no URL/file_path is used.",
|
||||
}),
|
||||
),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
filename: Type.Optional(Type.String({ description: "Optional filename override" })),
|
||||
filename: Type.Optional(Type.String({ description: "Optional upload filename override." })),
|
||||
index: Type.Optional(
|
||||
Type.Integer({
|
||||
minimum: 0,
|
||||
description: "Insert position (0-based index among siblings). Omit to append.",
|
||||
description: "Optional insert position among siblings for upload_image.",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("upload_file"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
url: Type.Optional(Type.String({ description: "Remote file URL (http/https)" })),
|
||||
file_path: Type.Optional(Type.String({ description: "Local file path" })),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
filename: Type.Optional(Type.String({ description: "Optional filename override" })),
|
||||
}),
|
||||
// Text color / style
|
||||
Type.Object({
|
||||
action: Type.Literal("color_text"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
block_id: Type.String({ description: "Text block ID to update" }),
|
||||
content: Type.String({
|
||||
description:
|
||||
'Text with color markup. Tags: [red], [green], [blue], [orange], [yellow], [purple], [grey], [bold], [bg:yellow]. Example: "Revenue [green]+15%[/green] YoY"',
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export type FeishuDocParams = Static<typeof FeishuDocSchema>;
|
||||
|
||||
@ -67,4 +67,29 @@ describe("feishu_doc account selection", () => {
|
||||
|
||||
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-a");
|
||||
});
|
||||
|
||||
test("registers provider-friendly feishu_doc parameters for embedded sessions", () => {
|
||||
const cfg = createDocEnabledConfig();
|
||||
const { api, resolveTool } = createToolFactoryHarness(cfg);
|
||||
registerFeishuDocTools(api);
|
||||
|
||||
const docTool = resolveTool("feishu_doc", { agentAccountId: "a" });
|
||||
const schema = docTool.parameters as {
|
||||
type?: unknown;
|
||||
anyOf?: unknown;
|
||||
properties?: Record<string, { enum?: unknown; description?: unknown }>;
|
||||
required?: unknown;
|
||||
};
|
||||
|
||||
expect(schema.type).toBe("object");
|
||||
expect(schema.anyOf).toBeUndefined();
|
||||
expect(schema.required).toEqual(["action"]);
|
||||
expect(schema.properties?.action?.enum).toEqual(
|
||||
expect.arrayContaining(["read", "create", "list_blocks", "upload_image"]),
|
||||
);
|
||||
expect(schema.properties?.action?.description).toEqual(expect.stringContaining("create_table"));
|
||||
expect(schema.properties?.doc_token?.description).toEqual(
|
||||
expect.stringContaining("Required for all actions except create"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,6 +33,13 @@ function json(data: unknown) {
|
||||
};
|
||||
}
|
||||
|
||||
function requireParam(value: string | undefined, label: string): string {
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`${label} is required for this action.`);
|
||||
}
|
||||
|
||||
/** Extract image URLs from markdown content */
|
||||
function extractImageUrls(markdown: string): string[] {
|
||||
const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
|
||||
@ -1276,13 +1283,13 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
const client = getClient(p, defaultAccountId);
|
||||
switch (p.action) {
|
||||
case "read":
|
||||
return json(await readDoc(client, p.doc_token));
|
||||
return json(await readDoc(client, requireParam(p.doc_token, "doc_token")));
|
||||
case "write":
|
||||
return json(
|
||||
await writeDoc(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.content,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.content, "content"),
|
||||
getMediaMaxBytes(p, defaultAccountId),
|
||||
api.logger,
|
||||
),
|
||||
@ -1291,8 +1298,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await appendDoc(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.content,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.content, "content"),
|
||||
getMediaMaxBytes(p, defaultAccountId),
|
||||
api.logger,
|
||||
),
|
||||
@ -1301,9 +1308,9 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await insertDoc(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.content,
|
||||
p.after_block_id,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.content, "content"),
|
||||
requireParam(p.after_block_id, "after_block_id"),
|
||||
getMediaMaxBytes(p, defaultAccountId),
|
||||
api.logger,
|
||||
),
|
||||
@ -1316,18 +1323,37 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
}),
|
||||
);
|
||||
case "list_blocks":
|
||||
return json(await listBlocks(client, p.doc_token));
|
||||
return json(await listBlocks(client, requireParam(p.doc_token, "doc_token")));
|
||||
case "get_block":
|
||||
return json(await getBlock(client, p.doc_token, p.block_id));
|
||||
return json(
|
||||
await getBlock(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
),
|
||||
);
|
||||
case "update_block":
|
||||
return json(await updateBlock(client, p.doc_token, p.block_id, p.content));
|
||||
return json(
|
||||
await updateBlock(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
requireParam(p.content, "content"),
|
||||
),
|
||||
);
|
||||
case "delete_block":
|
||||
return json(await deleteBlock(client, p.doc_token, p.block_id));
|
||||
return json(
|
||||
await deleteBlock(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
),
|
||||
);
|
||||
case "create_table":
|
||||
return json(
|
||||
await createTable(
|
||||
client,
|
||||
p.doc_token,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
p.row_size,
|
||||
p.column_size,
|
||||
p.parent_block_id,
|
||||
@ -1336,13 +1362,18 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
);
|
||||
case "write_table_cells":
|
||||
return json(
|
||||
await writeTableCells(client, p.doc_token, p.table_block_id, p.values),
|
||||
await writeTableCells(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.table_block_id, "table_block_id"),
|
||||
p.values,
|
||||
),
|
||||
);
|
||||
case "create_table_with_values":
|
||||
return json(
|
||||
await createTableWithValues(
|
||||
client,
|
||||
p.doc_token,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
p.row_size,
|
||||
p.column_size,
|
||||
p.values,
|
||||
@ -1354,7 +1385,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await uploadImageBlock(
|
||||
client,
|
||||
p.doc_token,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
getMediaMaxBytes(p, defaultAccountId),
|
||||
p.url,
|
||||
p.file_path,
|
||||
@ -1368,7 +1399,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await uploadFileBlock(
|
||||
client,
|
||||
p.doc_token,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
getMediaMaxBytes(p, defaultAccountId),
|
||||
p.url,
|
||||
p.file_path,
|
||||
@ -1377,19 +1408,38 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
),
|
||||
);
|
||||
case "color_text":
|
||||
return json(await updateColorText(client, p.doc_token, p.block_id, p.content));
|
||||
return json(
|
||||
await updateColorText(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
requireParam(p.content, "content"),
|
||||
),
|
||||
);
|
||||
case "insert_table_row":
|
||||
return json(await insertTableRow(client, p.doc_token, p.block_id, p.row_index));
|
||||
return json(
|
||||
await insertTableRow(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
p.row_index,
|
||||
),
|
||||
);
|
||||
case "insert_table_column":
|
||||
return json(
|
||||
await insertTableColumn(client, p.doc_token, p.block_id, p.column_index),
|
||||
await insertTableColumn(
|
||||
client,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
p.column_index,
|
||||
),
|
||||
);
|
||||
case "delete_table_rows":
|
||||
return json(
|
||||
await deleteTableRows(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.block_id,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
p.row_start,
|
||||
p.row_count,
|
||||
),
|
||||
@ -1398,8 +1448,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await deleteTableColumns(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.block_id,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
p.column_start,
|
||||
p.column_count,
|
||||
),
|
||||
@ -1408,8 +1458,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
||||
return json(
|
||||
await mergeTableCells(
|
||||
client,
|
||||
p.doc_token,
|
||||
p.block_id,
|
||||
requireParam(p.doc_token, "doc_token"),
|
||||
requireParam(p.block_id, "block_id"),
|
||||
p.row_start,
|
||||
p.row_end,
|
||||
p.column_start,
|
||||
|
||||
@ -69,6 +69,31 @@ describe("feishu tool account routing", () => {
|
||||
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b");
|
||||
});
|
||||
|
||||
test("wiki tool exposes object parameters with explicit action enum", () => {
|
||||
const { api, resolveTool } = createToolFactoryHarness(
|
||||
createConfig({
|
||||
toolsA: { wiki: true },
|
||||
}),
|
||||
);
|
||||
registerFeishuWikiTools(api);
|
||||
|
||||
const tool = resolveTool("feishu_wiki", { agentAccountId: "a" });
|
||||
const schema = tool.parameters as {
|
||||
type?: unknown;
|
||||
anyOf?: unknown;
|
||||
properties?: Record<string, { enum?: unknown; description?: unknown }>;
|
||||
required?: unknown;
|
||||
};
|
||||
|
||||
expect(schema.type).toBe("object");
|
||||
expect(schema.anyOf).toBeUndefined();
|
||||
expect(schema.required).toEqual(["action"]);
|
||||
expect(schema.properties?.action?.enum).toEqual(
|
||||
expect.arrayContaining(["spaces", "nodes", "get", "create", "rename"]),
|
||||
);
|
||||
expect(schema.properties?.action?.description).toEqual(expect.stringContaining("search"));
|
||||
});
|
||||
|
||||
test("wiki tool prefers configured defaultAccount over inherited default account context", async () => {
|
||||
const { api, resolveTool } = createToolFactoryHarness(
|
||||
createConfig({
|
||||
|
||||
@ -8,6 +8,8 @@ type ToolFactoryLike = (ctx: ToolContextLike) => AnyAgentTool | AnyAgentTool[] |
|
||||
|
||||
export type ToolLike = {
|
||||
name: string;
|
||||
description?: string;
|
||||
parameters?: unknown;
|
||||
execute: (toolCallId: string, params: unknown) => Promise<unknown> | unknown;
|
||||
};
|
||||
|
||||
@ -30,6 +32,8 @@ function asToolLike(tool: AnyAgentTool, fallbackName?: string): ToolLike {
|
||||
}
|
||||
return {
|
||||
name,
|
||||
description: candidate.description,
|
||||
parameters: candidate.parameters,
|
||||
execute: (toolCallId, params) => execute(toolCallId, params),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,55 +1,60 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
export const FeishuWikiSchema = Type.Union([
|
||||
Type.Object({
|
||||
action: Type.Literal("spaces"),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("nodes"),
|
||||
space_id: Type.String({ description: "Knowledge space ID" }),
|
||||
parent_node_token: Type.Optional(
|
||||
Type.String({ description: "Parent node token (optional, omit for root)" }),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("get"),
|
||||
token: Type.String({ description: "Wiki node token (from URL /wiki/XXX)" }),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("search"),
|
||||
query: Type.String({ description: "Search query" }),
|
||||
space_id: Type.Optional(Type.String({ description: "Limit search to this space (optional)" })),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("create"),
|
||||
space_id: Type.String({ description: "Knowledge space ID" }),
|
||||
title: Type.String({ description: "Node title" }),
|
||||
obj_type: Type.Optional(
|
||||
Type.Union([Type.Literal("docx"), Type.Literal("sheet"), Type.Literal("bitable")], {
|
||||
description: "Object type (default: docx)",
|
||||
const FEISHU_WIKI_ACTION_VALUES = [
|
||||
"spaces",
|
||||
"nodes",
|
||||
"get",
|
||||
"search",
|
||||
"create",
|
||||
"move",
|
||||
"rename",
|
||||
] as const;
|
||||
const FEISHU_WIKI_OBJECT_TYPE_VALUES = ["docx", "sheet", "bitable"] as const;
|
||||
|
||||
export const FeishuWikiSchema = Type.Object(
|
||||
{
|
||||
action: Type.Unsafe<(typeof FEISHU_WIKI_ACTION_VALUES)[number]>({
|
||||
type: "string",
|
||||
enum: [...FEISHU_WIKI_ACTION_VALUES],
|
||||
description: "Wiki action to run: spaces, nodes, get, search, create, move, rename",
|
||||
}),
|
||||
space_id: Type.Optional(
|
||||
Type.String({
|
||||
description: "Knowledge space ID. Required for nodes, create, move, and rename.",
|
||||
}),
|
||||
),
|
||||
parent_node_token: Type.Optional(
|
||||
Type.String({ description: "Parent node token (optional, omit for root)" }),
|
||||
Type.String({
|
||||
description: "Optional parent node token for nodes/create. Omit for the root level.",
|
||||
}),
|
||||
),
|
||||
token: Type.Optional(
|
||||
Type.String({ description: "Wiki node token. Required for get." }),
|
||||
),
|
||||
query: Type.Optional(
|
||||
Type.String({ description: "Search query. Required for search." }),
|
||||
),
|
||||
title: Type.Optional(
|
||||
Type.String({ description: "Node title. Required for create and rename." }),
|
||||
),
|
||||
obj_type: Type.Optional(
|
||||
Type.Unsafe<(typeof FEISHU_WIKI_OBJECT_TYPE_VALUES)[number]>({
|
||||
type: "string",
|
||||
enum: [...FEISHU_WIKI_OBJECT_TYPE_VALUES],
|
||||
description: "Object type for create (default: docx).",
|
||||
}),
|
||||
),
|
||||
node_token: Type.Optional(
|
||||
Type.String({ description: "Node token. Required for move and rename." }),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("move"),
|
||||
space_id: Type.String({ description: "Source knowledge space ID" }),
|
||||
node_token: Type.String({ description: "Node token to move" }),
|
||||
target_space_id: Type.Optional(
|
||||
Type.String({ description: "Target space ID (optional, same space if omitted)" }),
|
||||
Type.String({ description: "Optional target space for move." }),
|
||||
),
|
||||
target_parent_token: Type.Optional(
|
||||
Type.String({ description: "Target parent node token (optional, root if omitted)" }),
|
||||
Type.String({ description: "Optional target parent node token for move." }),
|
||||
),
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("rename"),
|
||||
space_id: Type.String({ description: "Knowledge space ID" }),
|
||||
node_token: Type.String({ description: "Node token to rename" }),
|
||||
title: Type.String({ description: "New title" }),
|
||||
}),
|
||||
]);
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export type FeishuWikiParams = Static<typeof FeishuWikiSchema>;
|
||||
|
||||
@ -17,6 +17,13 @@ const WIKI_ACCESS_HINT =
|
||||
"To grant wiki access: Open wiki space → Settings → Members → Add the bot. " +
|
||||
"See: https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca";
|
||||
|
||||
function requireParam(value: string | undefined, label: string): string {
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`${label} is required for this action.`);
|
||||
}
|
||||
|
||||
async function listSpaces(client: Lark.Client) {
|
||||
const res = await client.wiki.space.list({});
|
||||
if (res.code !== 0) {
|
||||
@ -192,9 +199,15 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) {
|
||||
case "spaces":
|
||||
return jsonToolResult(await listSpaces(client));
|
||||
case "nodes":
|
||||
return jsonToolResult(await listNodes(client, p.space_id, p.parent_node_token));
|
||||
return jsonToolResult(
|
||||
await listNodes(
|
||||
client,
|
||||
requireParam(p.space_id, "space_id"),
|
||||
p.parent_node_token,
|
||||
),
|
||||
);
|
||||
case "get":
|
||||
return jsonToolResult(await getNode(client, p.token));
|
||||
return jsonToolResult(await getNode(client, requireParam(p.token, "token")));
|
||||
case "search":
|
||||
return jsonToolResult({
|
||||
error:
|
||||
@ -202,20 +215,33 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) {
|
||||
});
|
||||
case "create":
|
||||
return jsonToolResult(
|
||||
await createNode(client, p.space_id, p.title, p.obj_type, p.parent_node_token),
|
||||
await createNode(
|
||||
client,
|
||||
requireParam(p.space_id, "space_id"),
|
||||
requireParam(p.title, "title"),
|
||||
p.obj_type,
|
||||
p.parent_node_token,
|
||||
),
|
||||
);
|
||||
case "move":
|
||||
return jsonToolResult(
|
||||
await moveNode(
|
||||
client,
|
||||
p.space_id,
|
||||
p.node_token,
|
||||
requireParam(p.space_id, "space_id"),
|
||||
requireParam(p.node_token, "node_token"),
|
||||
p.target_space_id,
|
||||
p.target_parent_token,
|
||||
),
|
||||
);
|
||||
case "rename":
|
||||
return jsonToolResult(await renameNode(client, p.space_id, p.node_token, p.title));
|
||||
return jsonToolResult(
|
||||
await renameNode(
|
||||
client,
|
||||
requireParam(p.space_id, "space_id"),
|
||||
requireParam(p.node_token, "node_token"),
|
||||
requireParam(p.title, "title"),
|
||||
),
|
||||
);
|
||||
default:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
||||
return unknownToolActionResult((p as { action?: unknown }).action);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user