refactor(feishu): unify account-aware tool routing and message body
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { createFeishuToolClient } from "./tool-account.js";
|
||||
|
||||
// ============ Helpers ============
|
||||
|
||||
@@ -64,10 +65,7 @@ function parseBitableUrl(url: string): { token: string; tableId?: string; isWiki
|
||||
}
|
||||
|
||||
/** Get app_token from wiki node_token */
|
||||
async function getAppTokenFromWiki(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
nodeToken: string,
|
||||
): Promise<string> {
|
||||
async function getAppTokenFromWiki(client: Lark.Client, nodeToken: string): Promise<string> {
|
||||
const res = await client.wiki.space.getNode({
|
||||
params: { token: nodeToken },
|
||||
});
|
||||
@@ -87,7 +85,7 @@ async function getAppTokenFromWiki(
|
||||
}
|
||||
|
||||
/** Get bitable metadata from URL (handles both /base/ and /wiki/ URLs) */
|
||||
async function getBitableMeta(client: ReturnType<typeof createFeishuClient>, url: string) {
|
||||
async function getBitableMeta(client: Lark.Client, url: string) {
|
||||
const parsed = parseBitableUrl(url);
|
||||
if (!parsed) {
|
||||
throw new Error("Invalid URL format. Expected /base/XXX or /wiki/XXX URL");
|
||||
@@ -134,11 +132,7 @@ async function getBitableMeta(client: ReturnType<typeof createFeishuClient>, url
|
||||
};
|
||||
}
|
||||
|
||||
async function listFields(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
) {
|
||||
async function listFields(client: Lark.Client, appToken: string, tableId: string) {
|
||||
const res = await client.bitable.appTableField.list({
|
||||
path: { app_token: appToken, table_id: tableId },
|
||||
});
|
||||
@@ -161,7 +155,7 @@ async function listFields(
|
||||
}
|
||||
|
||||
async function listRecords(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
pageSize?: number,
|
||||
@@ -186,12 +180,7 @@ async function listRecords(
|
||||
};
|
||||
}
|
||||
|
||||
async function getRecord(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
recordId: string,
|
||||
) {
|
||||
async function getRecord(client: Lark.Client, appToken: string, tableId: string, recordId: string) {
|
||||
const res = await client.bitable.appTableRecord.get({
|
||||
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
||||
});
|
||||
@@ -205,7 +194,7 @@ async function getRecord(
|
||||
}
|
||||
|
||||
async function createRecord(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
fields: Record<string, unknown>,
|
||||
@@ -235,7 +224,7 @@ const DEFAULT_CLEANUP_FIELD_TYPES = new Set([3, 5, 17]); // SingleSelect, DateTi
|
||||
|
||||
/** Clean up default placeholder rows and fields in a newly created Bitable table */
|
||||
async function cleanupNewBitable(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
tableName: string,
|
||||
@@ -334,7 +323,7 @@ async function cleanupNewBitable(
|
||||
}
|
||||
|
||||
async function createApp(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
name: string,
|
||||
folderToken?: string,
|
||||
logger?: CleanupLogger,
|
||||
@@ -389,7 +378,7 @@ async function createApp(
|
||||
}
|
||||
|
||||
async function createField(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
fieldName: string,
|
||||
@@ -417,7 +406,7 @@ async function createField(
|
||||
}
|
||||
|
||||
async function updateRecord(
|
||||
client: ReturnType<typeof createFeishuClient>,
|
||||
client: Lark.Client,
|
||||
appToken: string,
|
||||
tableId: string,
|
||||
recordId: string,
|
||||
@@ -543,203 +532,182 @@ export function registerFeishuBitableTools(api: OpenClawPluginApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstAccount = accounts[0];
|
||||
const getClient = () => createFeishuClient(firstAccount);
|
||||
type AccountAwareParams = { accountId?: string };
|
||||
|
||||
// Tool 0: feishu_bitable_get_meta (helper to parse URLs)
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_get_meta",
|
||||
label: "Feishu Bitable Get Meta",
|
||||
description:
|
||||
"Parse a Bitable URL and get app_token, table_id, and table list. Use this first when given a /wiki/ or /base/ URL.",
|
||||
parameters: GetMetaSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { url } = params as { url: string };
|
||||
try {
|
||||
const result = await getBitableMeta(getClient(), url);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
},
|
||||
{ name: "feishu_bitable_get_meta" },
|
||||
);
|
||||
const getClient = (params: AccountAwareParams | undefined, defaultAccountId?: string) =>
|
||||
createFeishuToolClient({ api, executeParams: params, defaultAccountId });
|
||||
|
||||
// Tool 1: feishu_bitable_list_fields
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_list_fields",
|
||||
label: "Feishu Bitable List Fields",
|
||||
description: "List all fields (columns) in a Bitable table with their types and properties",
|
||||
parameters: ListFieldsSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id } = params as { app_token: string; table_id: string };
|
||||
try {
|
||||
const result = await listFields(getClient(), app_token, table_id);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
},
|
||||
{ name: "feishu_bitable_list_fields" },
|
||||
);
|
||||
const registerBitableTool = <TParams extends AccountAwareParams>(params: {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
parameters: unknown;
|
||||
execute: (args: { params: TParams; defaultAccountId?: string }) => Promise<unknown>;
|
||||
}) => {
|
||||
api.registerTool(
|
||||
(ctx) => ({
|
||||
name: params.name,
|
||||
label: params.label,
|
||||
description: params.description,
|
||||
parameters: params.parameters,
|
||||
async execute(_toolCallId, rawParams) {
|
||||
try {
|
||||
return json(
|
||||
await params.execute({
|
||||
params: rawParams as TParams,
|
||||
defaultAccountId: ctx.agentAccountId,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
}),
|
||||
{ name: params.name },
|
||||
);
|
||||
};
|
||||
|
||||
// Tool 2: feishu_bitable_list_records
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_list_records",
|
||||
label: "Feishu Bitable List Records",
|
||||
description: "List records (rows) from a Bitable table with pagination support",
|
||||
parameters: ListRecordsSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id, page_size, page_token } = params as {
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
page_size?: number;
|
||||
page_token?: string;
|
||||
};
|
||||
try {
|
||||
const result = await listRecords(getClient(), app_token, table_id, page_size, page_token);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{ url: string; accountId?: string }>({
|
||||
name: "feishu_bitable_get_meta",
|
||||
label: "Feishu Bitable Get Meta",
|
||||
description:
|
||||
"Parse a Bitable URL and get app_token, table_id, and table list. Use this first when given a /wiki/ or /base/ URL.",
|
||||
parameters: GetMetaSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return getBitableMeta(getClient(params, defaultAccountId), params.url);
|
||||
},
|
||||
{ name: "feishu_bitable_list_records" },
|
||||
);
|
||||
});
|
||||
|
||||
// Tool 3: feishu_bitable_get_record
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_get_record",
|
||||
label: "Feishu Bitable Get Record",
|
||||
description: "Get a single record by ID from a Bitable table",
|
||||
parameters: GetRecordSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id, record_id } = params as {
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
record_id: string;
|
||||
};
|
||||
try {
|
||||
const result = await getRecord(getClient(), app_token, table_id, record_id);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{ app_token: string; table_id: string; accountId?: string }>({
|
||||
name: "feishu_bitable_list_fields",
|
||||
label: "Feishu Bitable List Fields",
|
||||
description: "List all fields (columns) in a Bitable table with their types and properties",
|
||||
parameters: ListFieldsSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return listFields(getClient(params, defaultAccountId), params.app_token, params.table_id);
|
||||
},
|
||||
{ name: "feishu_bitable_get_record" },
|
||||
);
|
||||
});
|
||||
|
||||
// Tool 4: feishu_bitable_create_record
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_create_record",
|
||||
label: "Feishu Bitable Create Record",
|
||||
description: "Create a new record (row) in a Bitable table",
|
||||
parameters: CreateRecordSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id, fields } = params as {
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
fields: Record<string, unknown>;
|
||||
};
|
||||
try {
|
||||
const result = await createRecord(getClient(), app_token, table_id, fields);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
page_size?: number;
|
||||
page_token?: string;
|
||||
accountId?: string;
|
||||
}>({
|
||||
name: "feishu_bitable_list_records",
|
||||
label: "Feishu Bitable List Records",
|
||||
description: "List records (rows) from a Bitable table with pagination support",
|
||||
parameters: ListRecordsSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return listRecords(
|
||||
getClient(params, defaultAccountId),
|
||||
params.app_token,
|
||||
params.table_id,
|
||||
params.page_size,
|
||||
params.page_token,
|
||||
);
|
||||
},
|
||||
{ name: "feishu_bitable_create_record" },
|
||||
);
|
||||
});
|
||||
|
||||
// Tool 5: feishu_bitable_update_record
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_update_record",
|
||||
label: "Feishu Bitable Update Record",
|
||||
description: "Update an existing record (row) in a Bitable table",
|
||||
parameters: UpdateRecordSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id, record_id, fields } = params as {
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
record_id: string;
|
||||
fields: Record<string, unknown>;
|
||||
};
|
||||
try {
|
||||
const result = await updateRecord(getClient(), app_token, table_id, record_id, fields);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
record_id: string;
|
||||
accountId?: string;
|
||||
}>({
|
||||
name: "feishu_bitable_get_record",
|
||||
label: "Feishu Bitable Get Record",
|
||||
description: "Get a single record by ID from a Bitable table",
|
||||
parameters: GetRecordSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return getRecord(
|
||||
getClient(params, defaultAccountId),
|
||||
params.app_token,
|
||||
params.table_id,
|
||||
params.record_id,
|
||||
);
|
||||
},
|
||||
{ name: "feishu_bitable_update_record" },
|
||||
);
|
||||
});
|
||||
|
||||
// Tool 6: feishu_bitable_create_app
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_create_app",
|
||||
label: "Feishu Bitable Create App",
|
||||
description: "Create a new Bitable (multidimensional table) application",
|
||||
parameters: CreateAppSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { name, folder_token } = params as { name: string; folder_token?: string };
|
||||
try {
|
||||
const result = await createApp(getClient(), name, folder_token, {
|
||||
debug: (msg) => api.logger.debug?.(msg),
|
||||
warn: (msg) => api.logger.warn?.(msg),
|
||||
});
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
fields: Record<string, unknown>;
|
||||
accountId?: string;
|
||||
}>({
|
||||
name: "feishu_bitable_create_record",
|
||||
label: "Feishu Bitable Create Record",
|
||||
description: "Create a new record (row) in a Bitable table",
|
||||
parameters: CreateRecordSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return createRecord(
|
||||
getClient(params, defaultAccountId),
|
||||
params.app_token,
|
||||
params.table_id,
|
||||
params.fields,
|
||||
);
|
||||
},
|
||||
{ name: "feishu_bitable_create_app" },
|
||||
);
|
||||
});
|
||||
|
||||
// Tool 7: feishu_bitable_create_field
|
||||
api.registerTool(
|
||||
{
|
||||
name: "feishu_bitable_create_field",
|
||||
label: "Feishu Bitable Create Field",
|
||||
description: "Create a new field (column) in a Bitable table",
|
||||
parameters: CreateFieldSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const { app_token, table_id, field_name, field_type, property } = params as {
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
field_name: string;
|
||||
field_type: number;
|
||||
property?: Record<string, unknown>;
|
||||
};
|
||||
try {
|
||||
const result = await createField(
|
||||
getClient(),
|
||||
app_token,
|
||||
table_id,
|
||||
field_name,
|
||||
field_type,
|
||||
property,
|
||||
);
|
||||
return json(result);
|
||||
} catch (err) {
|
||||
return json({ error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
},
|
||||
registerBitableTool<{
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
record_id: string;
|
||||
fields: Record<string, unknown>;
|
||||
accountId?: string;
|
||||
}>({
|
||||
name: "feishu_bitable_update_record",
|
||||
label: "Feishu Bitable Update Record",
|
||||
description: "Update an existing record (row) in a Bitable table",
|
||||
parameters: UpdateRecordSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return updateRecord(
|
||||
getClient(params, defaultAccountId),
|
||||
params.app_token,
|
||||
params.table_id,
|
||||
params.record_id,
|
||||
params.fields,
|
||||
);
|
||||
},
|
||||
{ name: "feishu_bitable_create_field" },
|
||||
);
|
||||
});
|
||||
|
||||
registerBitableTool<{ name: string; folder_token?: string; accountId?: string }>({
|
||||
name: "feishu_bitable_create_app",
|
||||
label: "Feishu Bitable Create App",
|
||||
description: "Create a new Bitable (multidimensional table) application",
|
||||
parameters: CreateAppSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return createApp(getClient(params, defaultAccountId), params.name, params.folder_token, {
|
||||
debug: (msg) => api.logger.debug?.(msg),
|
||||
warn: (msg) => api.logger.warn?.(msg),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registerBitableTool<{
|
||||
app_token: string;
|
||||
table_id: string;
|
||||
field_name: string;
|
||||
field_type: number;
|
||||
property?: Record<string, unknown>;
|
||||
accountId?: string;
|
||||
}>({
|
||||
name: "feishu_bitable_create_field",
|
||||
label: "Feishu Bitable Create Field",
|
||||
description: "Create a new field (column) in a Bitable table",
|
||||
parameters: CreateFieldSchema,
|
||||
async execute({ params, defaultAccountId }) {
|
||||
return createField(
|
||||
getClient(params, defaultAccountId),
|
||||
params.app_token,
|
||||
params.table_id,
|
||||
params.field_name,
|
||||
params.field_type,
|
||||
params.property,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
api.logger.info?.("feishu_bitable: Registered bitable tools");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user