@fased/fased 0.1.36 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/capability-catalog.json +241 -0
- package/config/dependency-ownership.json +130 -0
- package/config/official-channel-catalog.json +39 -0
- package/dist/agents/tools/web-fetch-utils.js +2 -1
- package/dist/agents/tools/web-fetch-visibility.js +3 -1
- package/dist/brand.js +1 -1
- package/dist/browser/pw-session.js +2 -1
- package/dist/browser/pw-tools-core.state.js +3 -2
- package/dist/browser/runtime-dependency.js +27 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/capabilities/catalog.js +228 -0
- package/dist/cli/components-cli.js +105 -0
- package/dist/cli/hooks-cli.js +1 -1
- package/dist/cli/plugins-cli.js +3 -3
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/update-cli/update-command.js +1 -1
- package/dist/commands/doctor-capabilities.js +26 -0
- package/dist/commands/doctor.js +2 -0
- package/dist/control-ui/assets/{app-DXYMrRJ9.js → app-JBrTSsNu.js} +1480 -1465
- package/dist/control-ui/assets/{index-iGQkJE0l.js → index-H-2DWAnH.js} +3 -3
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/method-scopes.js +1 -0
- package/dist/gateway/server-methods/plugins-marketplace.js +1 -1
- package/dist/gateway/server-methods/services.js +8 -0
- package/dist/gateway/server-methods-list.js +1 -0
- package/dist/infra/optional-runtime-dependency.js +46 -0
- package/dist/media/image-ops.js +6 -1
- package/dist/media/input-files.js +11 -2
- package/dist/media/mime.js +6 -1
- package/dist/memory/sqlite-vec.js +7 -1
- package/dist/plugin-sdk/{accounts-VqAX3HJ0.js → accounts-BwkH5bTD.js} +1 -1
- package/dist/plugin-sdk/{accounts-Zgt0NP54.js → accounts-RT6y7n3H.js} +1 -1
- package/dist/plugin-sdk/{actions-lB3PCwsz.js → actions-BrU_JMqM.js} +2 -2
- package/dist/plugin-sdk/{actions-BI5QViLk.js → actions-CUdVHzeJ.js} +2 -2
- package/dist/plugin-sdk/{active-listener-DKZiSaRA.js → active-listener-CE8t5g6l.js} +1 -1
- package/dist/plugin-sdk/{active-listener-D6_pdTMC.js → active-listener-Dx1SEGDn.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-Dx8J7XhV.js → audio-preflight-BPK8t0tI.js} +22 -22
- package/dist/plugin-sdk/{audio-preflight-CeRstduc.js → audio-preflight-kCa_KGRH.js} +22 -22
- package/dist/plugin-sdk/{audit-BImNOQvk.js → audit-DxB2Kml5.js} +11 -11
- package/dist/plugin-sdk/{audit-CKWkq0Un.js → audit-sSeRDwJb.js} +11 -11
- package/dist/plugin-sdk/channel-plugin-common.js +3 -2
- package/dist/plugin-sdk/{chunk-skAJZWCT.js → chunk-KCxbJLsV.js} +1 -1
- package/dist/plugin-sdk/{chunk-Cjw4712g.js → chunk-M2d7r-Zt.js} +1 -1
- package/dist/plugin-sdk/{chunk-items-Df5811D0.js → chunk-items-DGHUeFuI.js} +1 -1
- package/dist/plugin-sdk/command-status.js +22 -22
- package/dist/plugin-sdk/{commands-registry-5J619bQ2.js → commands-registry-CdT-nl7v.js} +2 -2
- package/dist/plugin-sdk/{commands-registry-CoLAT03J.js → commands-registry-DoWdYAJd.js} +2 -2
- package/dist/plugin-sdk/{common-CSTxewqY.js → common-Cpw-vuuM.js} +1 -1
- package/dist/plugin-sdk/{common-BiF0sTnY.js → common-DJWrErdE.js} +1 -1
- package/dist/plugin-sdk/{config-BxVjLTIv.js → config-C0qggNKQ.js} +7 -7
- package/dist/plugin-sdk/{config-TFokWwvO.js → config-CgwTaper.js} +45 -2
- package/dist/plugin-sdk/{config-DXlISPlx.js → config-DtqAN2L3.js} +45 -2
- package/dist/plugin-sdk/discord.js +2 -1
- package/dist/plugin-sdk/{fetch-guard-DPMynms8.js → fetch-guard-BLdujDnt.js} +1 -1
- package/dist/plugin-sdk/{fetch-guard-COLmW2TM.js → fetch-guard-DTKgMN9f.js} +1 -1
- package/dist/plugin-sdk/{handle-action-C-UndB8E.js → handle-action-B61D85yo.js} +12 -12
- package/dist/plugin-sdk/{handle-action-hhufKxmU.js → handle-action-CjcxGzQD.js} +12 -12
- package/dist/plugin-sdk/index.js +22 -22
- package/dist/plugin-sdk/{login-DL1oBBfL.js → login-CB_nCjX3.js} +3 -3
- package/dist/plugin-sdk/{login-Dnxas1dI.js → login-CzUuDd1v.js} +3 -3
- package/dist/plugin-sdk/{login-qr-CVQsGWKq.js → login-qr-D7MEpFza.js} +3 -3
- package/dist/plugin-sdk/{login-qr-B343JNXJ.js → login-qr-DgxfBFVN.js} +3 -3
- package/dist/plugin-sdk/{manager-BytwRIfK.js → manager-9see0IX5.js} +8 -3
- package/dist/plugin-sdk/{manager-BZjd_PCt.js → manager-D2kZ0Cps.js} +8 -3
- package/dist/plugin-sdk/{markdown-tables-krKiU4cT.js → markdown-tables-CCSD52rA.js} +1 -1
- package/dist/plugin-sdk/{markdown-tables-9rJDQ1X-.js → markdown-tables-DWUNvLrc.js} +1 -1
- package/dist/plugin-sdk/{media-D7ZkWU-A.js → media-C7YT4DeU.js} +13 -4
- package/dist/plugin-sdk/{media-C6C2c9a4.js → media-Cxw-xLGU.js} +13 -4
- package/dist/plugin-sdk/{monitor-BsLMfn3U.js → monitor-6lkdOqhQ.js} +26 -26
- package/dist/plugin-sdk/{monitor-BCxkQBoh.js → monitor-C6Ohw9BA.js} +26 -26
- package/dist/plugin-sdk/{monitor-BzDW50H6.js → monitor-o20KTHIi.js} +29 -29
- package/dist/plugin-sdk/{monitor-CVQNDfhs.js → monitor-rOs3mUol.js} +27 -27
- package/dist/plugin-sdk/{openresponses-http-BB67Z4QM.js → openresponses-http-BJONnUdy.js} +22 -22
- package/dist/plugin-sdk/{openresponses-http-NE76GGhj.js → openresponses-http-DkWqYznu.js} +22 -22
- package/dist/plugin-sdk/{ssrf-u0Ul7okY.js → optional-runtime-dependency-szS49voX.js} +46 -2
- package/dist/plugin-sdk/{outbound-PGNWU6T-.js → outbound-BCxOxdi-.js} +6 -6
- package/dist/plugin-sdk/{outbound-BHWJD9q0.js → outbound-JIwQbpRY.js} +6 -6
- package/dist/plugin-sdk/{outbound-attachment-BVzfhqnk.js → outbound-attachment-C-jSFaEB.js} +1 -1
- package/dist/plugin-sdk/{outbound-attachment-ClMvNW17.js → outbound-attachment-D6tj5Y5H.js} +1 -1
- package/dist/plugin-sdk/{pi-embedded-runner-DQ6UPx6-.js → pi-embedded-runner-DqDy0MWN.js} +72 -60
- package/dist/plugin-sdk/{pi-model-discovery-D51L5E9I.js → pi-model-discovery-DadlvsZW.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-DxoFm4Vd.js → pi-model-discovery-runtime-C5ssE6EW.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-DRuh4PNV.js → pi-model-discovery-runtime-DAaOPt64.js} +2 -2
- package/dist/plugin-sdk/{pi-model-discovery-runtime-BTWgkx4x.js → pi-model-discovery-runtime-gd77b0UF.js} +2 -2
- package/dist/plugin-sdk/{pi-model-discovery-CkH5l9X5.js → pi-model-discovery-yifTIDIn.js} +1 -1
- package/dist/plugin-sdk/{pw-ai-DZs6uYRd.js → pw-ai-Boor5vYw.js} +6 -5
- package/dist/plugin-sdk/{pw-ai-Cn-EUPZK.js → pw-ai-sH1Pi-Fh.js} +6 -5
- package/dist/plugin-sdk/{registry-B4HvWzob.js → registry-DNkJsNsz.js} +4 -3
- package/dist/plugin-sdk/{replies-jDMUBsNO.js → replies-fvzN5wnU.js} +2 -2
- package/dist/plugin-sdk/{replies-B_LW9c9f.js → replies-zebgZCHX.js} +2 -2
- package/dist/plugin-sdk/{resolve-route-_YZHbS-z.js → resolve-route-CNhTJG_2.js} +1 -1
- package/dist/plugin-sdk/{resolve-route-DlHzggnG.js → resolve-route-DO0WR4nB.js} +1 -1
- package/dist/plugin-sdk/{resolve-utils-zBhVX3Vx.js → resolve-utils-bixHYDBj.js} +1 -1
- package/dist/plugin-sdk/{retry-policy-DGLs5ebP.js → retry-policy-BLYEgIwP.js} +1 -1
- package/dist/plugin-sdk/{retry-policy-DaodShGa.js → retry-policy-sM01QZT2.js} +1 -1
- package/dist/plugin-sdk/{runtime-Dd0sswuW.js → runtime-BPyuAmka.js} +4 -3
- package/dist/plugin-sdk/{paths-CBCpJ5bu.js → runtime-dependency-Csswyv5r.js} +27 -3
- package/dist/plugin-sdk/{paths-DW-U2r7Y.js → runtime-dependency-Deg_sw2k.js} +36 -12
- package/dist/plugin-sdk/sat-runtime.js +1 -0
- package/dist/plugin-sdk/{send-8remZhsH.js → send-B09HRfHN.js} +6 -6
- package/dist/plugin-sdk/{send-DaaAscWj.js → send-BBYcgXA4.js} +5 -5
- package/dist/plugin-sdk/{send-DFTB0cnx.js → send-BQXQA81e.js} +6 -6
- package/dist/plugin-sdk/{send-CtMVvMJs.js → send-BzFdvD_i.js} +6 -6
- package/dist/plugin-sdk/{send-Di9irjS-.js → send-C3u-F_8s.js} +6 -6
- package/dist/plugin-sdk/{send-Dl88ZKq2.js → send-D3836_UN.js} +6 -6
- package/dist/plugin-sdk/{send-ifgZCHVB.js → send-D7tr-EsH.js} +5 -5
- package/dist/plugin-sdk/{send-DvUpYh0d.js → send-DRGzRdI3.js} +6 -6
- package/dist/plugin-sdk/{send-D2jlPeKH.js → send-DfkPKz_0.js} +4 -4
- package/dist/plugin-sdk/{send-QSt6nbny.js → send-DsxFXrCO.js} +4 -4
- package/dist/plugin-sdk/{session-CknrceVU.js → session-DG_bYisw.js} +2 -2
- package/dist/plugin-sdk/{session-CyRTcJ8N.js → session-EIStzK0E.js} +2 -2
- package/dist/plugin-sdk/{skill-commands-DHxkTA0s.js → skill-commands-CkgGmWBS.js} +2 -2
- package/dist/plugin-sdk/{skill-commands-CLXAG5sU.js → skill-commands-hyZA0LE3.js} +2 -2
- package/dist/plugin-sdk/{slack-Br9c6Yzs.js → slack-BErIh0uj.js} +34 -34
- package/dist/plugin-sdk/{slack-BpJXkwxF.js → slack-CGTAN4UD.js} +34 -34
- package/dist/plugin-sdk/{slack-actions-8tLGt1Ht.js → slack-actions-BQawWGTJ.js} +11 -11
- package/dist/plugin-sdk/{slack-actions-CDSSRo8Y.js → slack-actions-DDMszx56.js} +11 -11
- package/dist/plugin-sdk/slack.js +2 -1
- package/dist/plugin-sdk/src/brand.d.ts +2 -2
- package/dist/plugin-sdk/src/browser/runtime-dependency.d.ts +3 -0
- package/dist/plugin-sdk/src/infra/optional-runtime-dependency.d.ts +6 -0
- package/dist/plugin-sdk/{ssrf-fG4UHKBw.js → ssrf-Cce6LubA.js} +41 -7
- package/dist/plugin-sdk/{status-DoxnzyMC.js → status-CCGFb0f2.js} +76 -64
- package/dist/plugin-sdk/{sticker-cache-zmJ39ByC.js → sticker-cache-CKAkkCoU.js} +3 -3
- package/dist/plugin-sdk/{sticker-cache-D7WcMYsF.js → sticker-cache-wxl1Me2i.js} +3 -3
- package/dist/plugin-sdk/{tables-CkxblF0u.js → tables-BD7H5ug-.js} +1 -1
- package/dist/plugin-sdk/{tables-RBtf8G4P.js → tables-CJE8kB35.js} +1 -1
- package/dist/plugin-sdk/{telegram-actions-DAeYXSLk.js → telegram-actions-BRUNEXMr.js} +25 -25
- package/dist/plugin-sdk/{telegram-actions-BDeS3ocd.js → telegram-actions-r86ntIKR.js} +25 -25
- package/dist/plugin-sdk/telegram.js +2 -1
- package/dist/plugin-sdk/{thread-bindings.lifecycle-DsB94WbE.js → thread-bindings.lifecycle-Djem8FWF.js} +2 -2
- package/dist/plugin-sdk/{thread-bindings.lifecycle-DMhV3TsR.js → thread-bindings.lifecycle-gmHv3hc7.js} +2 -2
- package/dist/plugin-sdk/{thread-bindings.state-DYWpy7hJ.js → thread-bindings.state-CNT2vxm3.js} +2 -2
- package/dist/plugin-sdk/{thread-bindings.state-DG5cBaRn.js → thread-bindings.state-D8kN2X6H.js} +2 -2
- package/dist/plugin-sdk/{web-DJwacWQo.js → web-02I0vmdu.js} +26 -26
- package/dist/plugin-sdk/{web-IJ4NoGPJ.js → web-CXz56wyu.js} +26 -26
- package/dist/plugin-sdk/{whatsapp-actions-DAY8hWpD.js → whatsapp-actions-68Mfs2dJ.js} +12 -12
- package/dist/plugin-sdk/{whatsapp-actions-6opfo-oB.js → whatsapp-actions-DRqtzvTO.js} +12 -12
- package/dist/plugin-sdk/whatsapp.js +2 -1
- package/dist/plugin-sdk/{wsl-B2euLEc2.js → wsl-DcM2PgSS.js} +3 -3
- package/dist/plugin-sdk/{wsl-B8eyviuv.js → wsl-PXrdWitE.js} +3 -3
- package/dist/plugins/update.js +1 -1
- package/dist/tts/tts-core.js +6 -1
- package/dist/wizard/onboarding.finalize.js +9 -0
- package/extensions/acpx/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/fased-federation/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +12 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +2 -2
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +12 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +12 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/sat-mining/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +12 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +12 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +12 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +12 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +22 -15
- package/extensions/feishu/fased.plugin.json +0 -10
- package/extensions/feishu/index.ts +0 -63
- package/extensions/feishu/package.json +0 -33
- package/extensions/feishu/skills/feishu-doc/SKILL.md +0 -211
- package/extensions/feishu/skills/feishu-doc/references/block-types.md +0 -103
- package/extensions/feishu/skills/feishu-drive/SKILL.md +0 -97
- package/extensions/feishu/skills/feishu-perm/SKILL.md +0 -119
- package/extensions/feishu/skills/feishu-wiki/SKILL.md +0 -111
- package/extensions/feishu/src/accounts.ts +0 -159
- package/extensions/feishu/src/bitable.ts +0 -713
- package/extensions/feishu/src/bot.ts +0 -979
- package/extensions/feishu/src/channel.ts +0 -359
- package/extensions/feishu/src/client.ts +0 -118
- package/extensions/feishu/src/config-schema.ts +0 -226
- package/extensions/feishu/src/dedup.ts +0 -54
- package/extensions/feishu/src/directory.ts +0 -177
- package/extensions/feishu/src/doc-schema.ts +0 -122
- package/extensions/feishu/src/docx.ts +0 -998
- package/extensions/feishu/src/drive-schema.ts +0 -46
- package/extensions/feishu/src/drive.ts +0 -232
- package/extensions/feishu/src/dynamic-agent.ts +0 -131
- package/extensions/feishu/src/external-keys.ts +0 -19
- package/extensions/feishu/src/media.ts +0 -447
- package/extensions/feishu/src/mention.ts +0 -133
- package/extensions/feishu/src/monitor.ts +0 -434
- package/extensions/feishu/src/onboarding.ts +0 -430
- package/extensions/feishu/src/outbound.ts +0 -56
- package/extensions/feishu/src/perm-schema.ts +0 -52
- package/extensions/feishu/src/perm.ts +0 -180
- package/extensions/feishu/src/policy.ts +0 -116
- package/extensions/feishu/src/probe.ts +0 -44
- package/extensions/feishu/src/reactions.ts +0 -160
- package/extensions/feishu/src/reply-dispatcher.ts +0 -239
- package/extensions/feishu/src/runtime.ts +0 -14
- package/extensions/feishu/src/send-result.ts +0 -29
- package/extensions/feishu/src/send-target.ts +0 -25
- package/extensions/feishu/src/send.ts +0 -313
- package/extensions/feishu/src/streaming-card.ts +0 -218
- package/extensions/feishu/src/targets.ts +0 -78
- package/extensions/feishu/src/tool-account.ts +0 -58
- package/extensions/feishu/src/tools-config.ts +0 -21
- package/extensions/feishu/src/types.ts +0 -81
- package/extensions/feishu/src/typing.ts +0 -80
- package/extensions/feishu/src/wiki-schema.ts +0 -55
- package/extensions/feishu/src/wiki.ts +0 -237
- package/extensions/googlechat/fased.plugin.json +0 -9
- package/extensions/googlechat/index.ts +0 -19
- package/extensions/googlechat/package.json +0 -37
- package/extensions/googlechat/src/accounts.ts +0 -147
- package/extensions/googlechat/src/actions.ts +0 -176
- package/extensions/googlechat/src/api.ts +0 -282
- package/extensions/googlechat/src/auth.ts +0 -137
- package/extensions/googlechat/src/channel.ts +0 -591
- package/extensions/googlechat/src/monitor.ts +0 -996
- package/extensions/googlechat/src/onboarding.ts +0 -344
- package/extensions/googlechat/src/runtime.ts +0 -14
- package/extensions/googlechat/src/targets.ts +0 -65
- package/extensions/googlechat/src/types.config.ts +0 -3
- package/extensions/googlechat/src/types.ts +0 -73
|
@@ -1,998 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import { basename } from "node:path";
|
|
3
|
-
import { Readable } from "stream";
|
|
4
|
-
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
5
|
-
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import type { FasedAgentPluginApi } from "fased/plugin-sdk";
|
|
7
|
-
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
8
|
-
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
|
9
|
-
import { getFeishuRuntime } from "./runtime.js";
|
|
10
|
-
import {
|
|
11
|
-
createFeishuToolClient,
|
|
12
|
-
resolveAnyEnabledFeishuToolsConfig,
|
|
13
|
-
resolveFeishuToolAccount,
|
|
14
|
-
} from "./tool-account.js";
|
|
15
|
-
|
|
16
|
-
// ============ Helpers ============
|
|
17
|
-
|
|
18
|
-
function json(data: unknown) {
|
|
19
|
-
return {
|
|
20
|
-
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
21
|
-
details: data,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Extract image URLs from markdown content */
|
|
26
|
-
function extractImageUrls(markdown: string): string[] {
|
|
27
|
-
const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
28
|
-
const urls: string[] = [];
|
|
29
|
-
let match;
|
|
30
|
-
while ((match = regex.exec(markdown)) !== null) {
|
|
31
|
-
const url = match[1].trim();
|
|
32
|
-
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
33
|
-
urls.push(url);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return urls;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const BLOCK_TYPE_NAMES: Record<number, string> = {
|
|
40
|
-
1: "Page",
|
|
41
|
-
2: "Text",
|
|
42
|
-
3: "Heading1",
|
|
43
|
-
4: "Heading2",
|
|
44
|
-
5: "Heading3",
|
|
45
|
-
12: "Bullet",
|
|
46
|
-
13: "Ordered",
|
|
47
|
-
14: "Code",
|
|
48
|
-
15: "Quote",
|
|
49
|
-
17: "Todo",
|
|
50
|
-
18: "Bitable",
|
|
51
|
-
21: "Diagram",
|
|
52
|
-
22: "Divider",
|
|
53
|
-
23: "File",
|
|
54
|
-
27: "Image",
|
|
55
|
-
30: "Sheet",
|
|
56
|
-
31: "Table",
|
|
57
|
-
32: "TableCell",
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Block types that cannot be created via documentBlockChildren.create API
|
|
61
|
-
const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
|
|
62
|
-
|
|
63
|
-
/** Clean blocks for insertion (remove unsupported types and read-only fields) */
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block types
|
|
65
|
-
function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[] } {
|
|
66
|
-
const skipped: string[] = [];
|
|
67
|
-
const cleaned = blocks
|
|
68
|
-
.filter((block) => {
|
|
69
|
-
if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
|
|
70
|
-
const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
|
|
71
|
-
skipped.push(typeName);
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
})
|
|
76
|
-
.map((block) => {
|
|
77
|
-
if (block.block_type === 31 && block.table?.merge_info) {
|
|
78
|
-
const { merge_info: _merge_info, ...tableRest } = block.table;
|
|
79
|
-
return { ...block, table: tableRest };
|
|
80
|
-
}
|
|
81
|
-
return block;
|
|
82
|
-
});
|
|
83
|
-
return { cleaned, skipped };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ============ Core Functions ============
|
|
87
|
-
|
|
88
|
-
async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|
89
|
-
const res = await client.docx.document.convert({
|
|
90
|
-
data: { content_type: "markdown", content: markdown },
|
|
91
|
-
});
|
|
92
|
-
if (res.code !== 0) {
|
|
93
|
-
throw new Error(res.msg);
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
blocks: res.data?.blocks ?? [],
|
|
97
|
-
firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function sortBlocksByFirstLevel(blocks: any[], firstLevelIds: string[]): any[] {
|
|
102
|
-
if (!firstLevelIds || firstLevelIds.length === 0) return blocks;
|
|
103
|
-
const sorted = firstLevelIds.map((id) => blocks.find((b) => b.block_id === id)).filter(Boolean);
|
|
104
|
-
const sortedIds = new Set(firstLevelIds);
|
|
105
|
-
const remaining = blocks.filter((b) => !sortedIds.has(b.block_id));
|
|
106
|
-
return [...sorted, ...remaining];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
|
110
|
-
async function insertBlocks(
|
|
111
|
-
client: Lark.Client,
|
|
112
|
-
docToken: string,
|
|
113
|
-
blocks: any[],
|
|
114
|
-
parentBlockId?: string,
|
|
115
|
-
index?: number,
|
|
116
|
-
): Promise<{ children: any[]; skipped: string[] }> {
|
|
117
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
118
|
-
const { cleaned, skipped } = cleanBlocksForInsert(blocks);
|
|
119
|
-
const blockId = parentBlockId ?? docToken;
|
|
120
|
-
|
|
121
|
-
if (cleaned.length === 0) {
|
|
122
|
-
return { children: [], skipped };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const res = await client.docx.documentBlockChildren.create({
|
|
126
|
-
path: { document_id: docToken, block_id: blockId },
|
|
127
|
-
data: {
|
|
128
|
-
children: cleaned,
|
|
129
|
-
...(index !== undefined && { index }),
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
if (res.code !== 0) {
|
|
133
|
-
throw new Error(res.msg);
|
|
134
|
-
}
|
|
135
|
-
return { children: res.data?.children ?? [], skipped };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function clearDocumentContent(client: Lark.Client, docToken: string) {
|
|
139
|
-
const existing = await client.docx.documentBlock.list({
|
|
140
|
-
path: { document_id: docToken },
|
|
141
|
-
});
|
|
142
|
-
if (existing.code !== 0) {
|
|
143
|
-
throw new Error(existing.msg);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const childIds =
|
|
147
|
-
existing.data?.items
|
|
148
|
-
?.filter((b) => b.parent_id === docToken && b.block_type !== 1)
|
|
149
|
-
.map((b) => b.block_id) ?? [];
|
|
150
|
-
|
|
151
|
-
if (childIds.length > 0) {
|
|
152
|
-
const res = await client.docx.documentBlockChildren.batchDelete({
|
|
153
|
-
path: { document_id: docToken, block_id: docToken },
|
|
154
|
-
data: { start_index: 0, end_index: childIds.length },
|
|
155
|
-
});
|
|
156
|
-
if (res.code !== 0) {
|
|
157
|
-
throw new Error(res.msg);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return childIds.length;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function uploadImageToDocx(
|
|
165
|
-
client: Lark.Client,
|
|
166
|
-
blockId: string,
|
|
167
|
-
imageBuffer: Buffer,
|
|
168
|
-
fileName: string,
|
|
169
|
-
): Promise<string> {
|
|
170
|
-
const res = await client.drive.media.uploadAll({
|
|
171
|
-
data: {
|
|
172
|
-
file_name: fileName,
|
|
173
|
-
parent_type: "docx_image",
|
|
174
|
-
parent_node: blockId,
|
|
175
|
-
size: imageBuffer.length,
|
|
176
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK stream type
|
|
177
|
-
file: Readable.from(imageBuffer) as any,
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const fileToken = res?.file_token;
|
|
182
|
-
if (!fileToken) {
|
|
183
|
-
throw new Error("Image upload failed: no file_token returned");
|
|
184
|
-
}
|
|
185
|
-
return fileToken;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function downloadImage(url: string, maxBytes: number): Promise<Buffer> {
|
|
189
|
-
const fetched = await getFeishuRuntime().channel.media.fetchRemoteMedia({ url, maxBytes });
|
|
190
|
-
return fetched.buffer;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function resolveUploadInput(
|
|
194
|
-
url: string | undefined,
|
|
195
|
-
filePath: string | undefined,
|
|
196
|
-
maxBytes: number,
|
|
197
|
-
explicitFileName?: string,
|
|
198
|
-
): Promise<{ buffer: Buffer; fileName: string }> {
|
|
199
|
-
if (!url && !filePath) {
|
|
200
|
-
throw new Error("Either url or file_path is required");
|
|
201
|
-
}
|
|
202
|
-
if (url && filePath) {
|
|
203
|
-
throw new Error("Provide only one of url or file_path");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (url) {
|
|
207
|
-
const fetched = await getFeishuRuntime().channel.media.fetchRemoteMedia({ url, maxBytes });
|
|
208
|
-
const urlPath = new URL(url).pathname;
|
|
209
|
-
const guessed = urlPath.split("/").pop() || "upload.bin";
|
|
210
|
-
return {
|
|
211
|
-
buffer: fetched.buffer,
|
|
212
|
-
fileName: explicitFileName || guessed,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const buffer = await fs.readFile(filePath!);
|
|
217
|
-
if (buffer.length > maxBytes) {
|
|
218
|
-
throw new Error(`Local file exceeds limit: ${buffer.length} bytes > ${maxBytes} bytes`);
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
buffer,
|
|
222
|
-
fileName: explicitFileName || basename(filePath!),
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
|
227
|
-
async function processImages(
|
|
228
|
-
client: Lark.Client,
|
|
229
|
-
docToken: string,
|
|
230
|
-
markdown: string,
|
|
231
|
-
insertedBlocks: any[],
|
|
232
|
-
maxBytes: number,
|
|
233
|
-
): Promise<number> {
|
|
234
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
235
|
-
const imageUrls = extractImageUrls(markdown);
|
|
236
|
-
if (imageUrls.length === 0) {
|
|
237
|
-
return 0;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27);
|
|
241
|
-
|
|
242
|
-
let processed = 0;
|
|
243
|
-
for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
|
|
244
|
-
const url = imageUrls[i];
|
|
245
|
-
const blockId = imageBlocks[i].block_id;
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const buffer = await downloadImage(url, maxBytes);
|
|
249
|
-
const urlPath = new URL(url).pathname;
|
|
250
|
-
const fileName = urlPath.split("/").pop() || `image_${i}.png`;
|
|
251
|
-
const fileToken = await uploadImageToDocx(client, blockId, buffer, fileName);
|
|
252
|
-
|
|
253
|
-
await client.docx.documentBlock.patch({
|
|
254
|
-
path: { document_id: docToken, block_id: blockId },
|
|
255
|
-
data: {
|
|
256
|
-
replace_image: { token: fileToken },
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
processed++;
|
|
261
|
-
} catch (err) {
|
|
262
|
-
console.error(`Failed to process image ${url}:`, err);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return processed;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async function uploadImageBlock(
|
|
270
|
-
client: Lark.Client,
|
|
271
|
-
docToken: string,
|
|
272
|
-
maxBytes: number,
|
|
273
|
-
url?: string,
|
|
274
|
-
filePath?: string,
|
|
275
|
-
parentBlockId?: string,
|
|
276
|
-
filename?: string,
|
|
277
|
-
index?: number,
|
|
278
|
-
) {
|
|
279
|
-
const blockId = parentBlockId ?? docToken;
|
|
280
|
-
|
|
281
|
-
// Feishu API does not allow creating empty image blocks (block_type 27).
|
|
282
|
-
// Workaround: use markdown conversion to create a placeholder image block,
|
|
283
|
-
// then upload the real image and patch the block.
|
|
284
|
-
const placeholderMd = "";
|
|
285
|
-
const converted = await convertMarkdown(client, placeholderMd);
|
|
286
|
-
const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
|
|
287
|
-
const { children: inserted } = await insertBlocks(client, docToken, sorted, blockId, index);
|
|
288
|
-
|
|
289
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape
|
|
290
|
-
const imageBlock = inserted.find((b: any) => b.block_type === 27);
|
|
291
|
-
const imageBlockId = imageBlock?.block_id;
|
|
292
|
-
if (!imageBlockId) {
|
|
293
|
-
throw new Error("Failed to create image block via markdown placeholder");
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const upload = await resolveUploadInput(url, filePath, maxBytes, filename);
|
|
297
|
-
const fileToken = await uploadImageToDocx(client, imageBlockId, upload.buffer, upload.fileName);
|
|
298
|
-
|
|
299
|
-
const patchRes = await client.docx.documentBlock.patch({
|
|
300
|
-
path: { document_id: docToken, block_id: imageBlockId },
|
|
301
|
-
data: {
|
|
302
|
-
replace_image: { token: fileToken },
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
if (patchRes.code !== 0) {
|
|
306
|
-
throw new Error(patchRes.msg);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
success: true,
|
|
311
|
-
block_id: imageBlockId,
|
|
312
|
-
file_token: fileToken,
|
|
313
|
-
file_name: upload.fileName,
|
|
314
|
-
size: upload.buffer.length,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async function uploadFileBlock(
|
|
319
|
-
client: Lark.Client,
|
|
320
|
-
docToken: string,
|
|
321
|
-
maxBytes: number,
|
|
322
|
-
url?: string,
|
|
323
|
-
filePath?: string,
|
|
324
|
-
parentBlockId?: string,
|
|
325
|
-
filename?: string,
|
|
326
|
-
) {
|
|
327
|
-
const blockId = parentBlockId ?? docToken;
|
|
328
|
-
|
|
329
|
-
// Feishu API does not allow creating empty file blocks (block_type 23).
|
|
330
|
-
// Workaround: create a placeholder text block, then replace it with file content.
|
|
331
|
-
// Actually, file blocks need a different approach: use markdown link as placeholder.
|
|
332
|
-
const upload = await resolveUploadInput(url, filePath, maxBytes, filename);
|
|
333
|
-
|
|
334
|
-
// Create a placeholder text block first
|
|
335
|
-
const placeholderMd = `[${upload.fileName}](https://example.com/placeholder)`;
|
|
336
|
-
const converted = await convertMarkdown(client, placeholderMd);
|
|
337
|
-
const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
|
|
338
|
-
const { children: inserted } = await insertBlocks(client, docToken, sorted, blockId);
|
|
339
|
-
|
|
340
|
-
// Get the first inserted block - we'll delete it and create the file in its place
|
|
341
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape
|
|
342
|
-
const placeholderBlock = inserted[0];
|
|
343
|
-
if (!placeholderBlock?.block_id) {
|
|
344
|
-
throw new Error("Failed to create placeholder block for file upload");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Delete the placeholder
|
|
348
|
-
const parentId = placeholderBlock.parent_id ?? blockId;
|
|
349
|
-
const childrenRes = await client.docx.documentBlockChildren.get({
|
|
350
|
-
path: { document_id: docToken, block_id: parentId },
|
|
351
|
-
});
|
|
352
|
-
if (childrenRes.code !== 0) {
|
|
353
|
-
throw new Error(childrenRes.msg);
|
|
354
|
-
}
|
|
355
|
-
const items = childrenRes.data?.items ?? [];
|
|
356
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
|
357
|
-
const placeholderIdx = items.findIndex(
|
|
358
|
-
(item: any) => item.block_id === placeholderBlock.block_id,
|
|
359
|
-
);
|
|
360
|
-
if (placeholderIdx >= 0) {
|
|
361
|
-
const deleteRes = await client.docx.documentBlockChildren.batchDelete({
|
|
362
|
-
path: { document_id: docToken, block_id: parentId },
|
|
363
|
-
data: { start_index: placeholderIdx, end_index: placeholderIdx + 1 },
|
|
364
|
-
});
|
|
365
|
-
if (deleteRes.code !== 0) {
|
|
366
|
-
throw new Error(deleteRes.msg);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Upload file to Feishu drive
|
|
371
|
-
const fileRes = await client.drive.media.uploadAll({
|
|
372
|
-
data: {
|
|
373
|
-
file_name: upload.fileName,
|
|
374
|
-
parent_type: "docx_file",
|
|
375
|
-
parent_node: docToken,
|
|
376
|
-
size: upload.buffer.length,
|
|
377
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK stream type
|
|
378
|
-
file: Readable.from(upload.buffer) as any,
|
|
379
|
-
},
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const fileToken = fileRes?.file_token;
|
|
383
|
-
if (!fileToken) {
|
|
384
|
-
throw new Error("File upload failed: no file_token returned");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
success: true,
|
|
389
|
-
file_token: fileToken,
|
|
390
|
-
file_name: upload.fileName,
|
|
391
|
-
size: upload.buffer.length,
|
|
392
|
-
note: "File uploaded to drive. Use the file_token to reference it. Direct file block creation is not supported by the Feishu API.",
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ============ Actions ============
|
|
397
|
-
|
|
398
|
-
const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
|
|
399
|
-
|
|
400
|
-
async function readDoc(client: Lark.Client, docToken: string) {
|
|
401
|
-
const [contentRes, infoRes, blocksRes] = await Promise.all([
|
|
402
|
-
client.docx.document.rawContent({ path: { document_id: docToken } }),
|
|
403
|
-
client.docx.document.get({ path: { document_id: docToken } }),
|
|
404
|
-
client.docx.documentBlock.list({ path: { document_id: docToken } }),
|
|
405
|
-
]);
|
|
406
|
-
|
|
407
|
-
if (contentRes.code !== 0) {
|
|
408
|
-
throw new Error(contentRes.msg);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const blocks = blocksRes.data?.items ?? [];
|
|
412
|
-
const blockCounts: Record<string, number> = {};
|
|
413
|
-
const structuredTypes: string[] = [];
|
|
414
|
-
|
|
415
|
-
for (const b of blocks) {
|
|
416
|
-
const type = b.block_type ?? 0;
|
|
417
|
-
const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
|
|
418
|
-
blockCounts[name] = (blockCounts[name] || 0) + 1;
|
|
419
|
-
|
|
420
|
-
if (STRUCTURED_BLOCK_TYPES.has(type) && !structuredTypes.includes(name)) {
|
|
421
|
-
structuredTypes.push(name);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
let hint: string | undefined;
|
|
426
|
-
if (structuredTypes.length > 0) {
|
|
427
|
-
hint = `This document contains ${structuredTypes.join(", ")} which are NOT included in the plain text above. Use feishu_doc with action: "list_blocks" to get full content.`;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return {
|
|
431
|
-
title: infoRes.data?.document?.title,
|
|
432
|
-
content: contentRes.data?.content,
|
|
433
|
-
revision_id: infoRes.data?.document?.revision_id,
|
|
434
|
-
block_count: blocks.length,
|
|
435
|
-
block_types: blockCounts,
|
|
436
|
-
...(hint && { hint }),
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
async function createDoc(
|
|
441
|
-
client: Lark.Client,
|
|
442
|
-
title: string,
|
|
443
|
-
folderToken?: string,
|
|
444
|
-
ownerOpenId?: string,
|
|
445
|
-
ownerPermType: "view" | "edit" | "full_access" = "full_access",
|
|
446
|
-
) {
|
|
447
|
-
const res = await client.docx.document.create({
|
|
448
|
-
data: { title, folder_token: folderToken },
|
|
449
|
-
});
|
|
450
|
-
if (res.code !== 0) {
|
|
451
|
-
throw new Error(res.msg);
|
|
452
|
-
}
|
|
453
|
-
const doc = res.data?.document;
|
|
454
|
-
const docToken = doc?.document_id;
|
|
455
|
-
if (!docToken) {
|
|
456
|
-
throw new Error("Document creation succeeded but no document_id was returned");
|
|
457
|
-
}
|
|
458
|
-
let ownerPermissionAdded = false;
|
|
459
|
-
|
|
460
|
-
// Auto add owner permission if ownerOpenId is provided
|
|
461
|
-
if (docToken && ownerOpenId) {
|
|
462
|
-
try {
|
|
463
|
-
await client.drive.permissionMember.create({
|
|
464
|
-
path: { token: docToken },
|
|
465
|
-
params: { type: "docx", need_notification: false },
|
|
466
|
-
data: {
|
|
467
|
-
member_type: "openid",
|
|
468
|
-
member_id: ownerOpenId,
|
|
469
|
-
perm: ownerPermType,
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
ownerPermissionAdded = true;
|
|
473
|
-
} catch (err) {
|
|
474
|
-
console.warn("Failed to add owner permission (non-critical):", err);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
document_id: docToken,
|
|
480
|
-
title: doc?.title,
|
|
481
|
-
url: `https://feishu.cn/docx/${docToken}`,
|
|
482
|
-
...(ownerOpenId &&
|
|
483
|
-
ownerPermissionAdded && {
|
|
484
|
-
owner_permission_added: true,
|
|
485
|
-
owner_open_id: ownerOpenId,
|
|
486
|
-
owner_perm_type: ownerPermType,
|
|
487
|
-
}),
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function writeDoc(client: Lark.Client, docToken: string, markdown: string, maxBytes: number) {
|
|
492
|
-
const deleted = await clearDocumentContent(client, docToken);
|
|
493
|
-
|
|
494
|
-
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
|
495
|
-
if (blocks.length === 0) {
|
|
496
|
-
return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0 };
|
|
497
|
-
}
|
|
498
|
-
const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
|
|
499
|
-
|
|
500
|
-
const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
|
|
501
|
-
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
|
502
|
-
|
|
503
|
-
return {
|
|
504
|
-
success: true,
|
|
505
|
-
blocks_deleted: deleted,
|
|
506
|
-
blocks_added: inserted.length,
|
|
507
|
-
images_processed: imagesProcessed,
|
|
508
|
-
...(skipped.length > 0 && {
|
|
509
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
|
|
510
|
-
}),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
async function appendDoc(
|
|
515
|
-
client: Lark.Client,
|
|
516
|
-
docToken: string,
|
|
517
|
-
markdown: string,
|
|
518
|
-
maxBytes: number,
|
|
519
|
-
) {
|
|
520
|
-
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
|
521
|
-
if (blocks.length === 0) {
|
|
522
|
-
throw new Error("Content is empty");
|
|
523
|
-
}
|
|
524
|
-
const sortedBlocks = sortBlocksByFirstLevel(blocks, firstLevelBlockIds);
|
|
525
|
-
|
|
526
|
-
const { children: inserted, skipped } = await insertBlocks(client, docToken, sortedBlocks);
|
|
527
|
-
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
|
528
|
-
|
|
529
|
-
return {
|
|
530
|
-
success: true,
|
|
531
|
-
blocks_added: inserted.length,
|
|
532
|
-
images_processed: imagesProcessed,
|
|
533
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
|
534
|
-
block_ids: inserted.map((b: any) => b.block_id),
|
|
535
|
-
...(skipped.length > 0 && {
|
|
536
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
|
|
537
|
-
}),
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
async function createTable(
|
|
542
|
-
client: Lark.Client,
|
|
543
|
-
docToken: string,
|
|
544
|
-
rowSize: number,
|
|
545
|
-
columnSize: number,
|
|
546
|
-
parentBlockId?: string,
|
|
547
|
-
columnWidth?: number[],
|
|
548
|
-
) {
|
|
549
|
-
if (columnWidth && columnWidth.length !== columnSize) {
|
|
550
|
-
throw new Error("column_width length must equal column_size");
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const blockId = parentBlockId ?? docToken;
|
|
554
|
-
const res = await client.docx.documentBlockChildren.create({
|
|
555
|
-
path: { document_id: docToken, block_id: blockId },
|
|
556
|
-
data: {
|
|
557
|
-
children: [
|
|
558
|
-
{
|
|
559
|
-
block_type: 31,
|
|
560
|
-
table: {
|
|
561
|
-
property: {
|
|
562
|
-
row_size: rowSize,
|
|
563
|
-
column_size: columnSize,
|
|
564
|
-
...(columnWidth && columnWidth.length > 0 ? { column_width: columnWidth } : {}),
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
],
|
|
569
|
-
},
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
if (res.code !== 0) {
|
|
573
|
-
throw new Error(res.msg);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type
|
|
577
|
-
const tableBlock = (res.data?.children as any[] | undefined)?.find((b) => b.block_type === 31);
|
|
578
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape may vary by version
|
|
579
|
-
const cells = (tableBlock?.children as any[] | undefined) ?? [];
|
|
580
|
-
|
|
581
|
-
return {
|
|
582
|
-
success: true,
|
|
583
|
-
table_block_id: tableBlock?.block_id,
|
|
584
|
-
row_size: rowSize,
|
|
585
|
-
column_size: columnSize,
|
|
586
|
-
// row-major cell ids, if API returns them directly
|
|
587
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type
|
|
588
|
-
table_cell_block_ids: cells.map((c: any) => c.block_id).filter(Boolean),
|
|
589
|
-
raw_children_count: res.data?.children?.length ?? 0,
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
async function writeTableCells(
|
|
594
|
-
client: Lark.Client,
|
|
595
|
-
docToken: string,
|
|
596
|
-
tableBlockId: string,
|
|
597
|
-
values: string[][],
|
|
598
|
-
) {
|
|
599
|
-
if (!values.length || !values[0]?.length) {
|
|
600
|
-
throw new Error("values must be a non-empty 2D array");
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const tableRes = await client.docx.documentBlock.get({
|
|
604
|
-
path: { document_id: docToken, block_id: tableBlockId },
|
|
605
|
-
});
|
|
606
|
-
if (tableRes.code !== 0) {
|
|
607
|
-
throw new Error(tableRes.msg);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const tableBlock = tableRes.data?.block;
|
|
611
|
-
if (tableBlock?.block_type !== 31) {
|
|
612
|
-
throw new Error("table_block_id is not a table block");
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// SDK types are loose here across versions
|
|
616
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block payload
|
|
617
|
-
const tableData = (tableBlock as any).table;
|
|
618
|
-
const rows = tableData?.property?.row_size as number | undefined;
|
|
619
|
-
const cols = tableData?.property?.column_size as number | undefined;
|
|
620
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block payload
|
|
621
|
-
const cellIds = (tableData?.cells as any[] | undefined) ?? [];
|
|
622
|
-
|
|
623
|
-
if (!rows || !cols || !cellIds.length) {
|
|
624
|
-
throw new Error(
|
|
625
|
-
"Table cell IDs unavailable from table block. Use list_blocks/get_block and pass explicit cell block IDs if needed.",
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const writeRows = Math.min(values.length, rows);
|
|
630
|
-
let written = 0;
|
|
631
|
-
|
|
632
|
-
for (let r = 0; r < writeRows; r++) {
|
|
633
|
-
const rowValues = values[r] ?? [];
|
|
634
|
-
const writeCols = Math.min(rowValues.length, cols);
|
|
635
|
-
|
|
636
|
-
for (let c = 0; c < writeCols; c++) {
|
|
637
|
-
const cellId = cellIds[r * cols + c];
|
|
638
|
-
if (!cellId) continue;
|
|
639
|
-
|
|
640
|
-
// table cell is a container block: clear existing children, then create text child blocks
|
|
641
|
-
const childrenRes = await client.docx.documentBlockChildren.get({
|
|
642
|
-
path: { document_id: docToken, block_id: cellId },
|
|
643
|
-
});
|
|
644
|
-
if (childrenRes.code !== 0) {
|
|
645
|
-
throw new Error(childrenRes.msg);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const existingChildren = childrenRes.data?.items ?? [];
|
|
649
|
-
if (existingChildren.length > 0) {
|
|
650
|
-
const delRes = await client.docx.documentBlockChildren.batchDelete({
|
|
651
|
-
path: { document_id: docToken, block_id: cellId },
|
|
652
|
-
data: { start_index: 0, end_index: existingChildren.length },
|
|
653
|
-
});
|
|
654
|
-
if (delRes.code !== 0) {
|
|
655
|
-
throw new Error(delRes.msg);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const text = rowValues[c] ?? "";
|
|
660
|
-
const converted = await convertMarkdown(client, text);
|
|
661
|
-
const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds);
|
|
662
|
-
|
|
663
|
-
if (sorted.length > 0) {
|
|
664
|
-
await insertBlocks(client, docToken, sorted, cellId);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
written++;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
return {
|
|
672
|
-
success: true,
|
|
673
|
-
table_block_id: tableBlockId,
|
|
674
|
-
cells_written: written,
|
|
675
|
-
table_size: { rows, cols },
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async function createTableWithValues(
|
|
680
|
-
client: Lark.Client,
|
|
681
|
-
docToken: string,
|
|
682
|
-
rowSize: number,
|
|
683
|
-
columnSize: number,
|
|
684
|
-
values: string[][],
|
|
685
|
-
parentBlockId?: string,
|
|
686
|
-
columnWidth?: number[],
|
|
687
|
-
) {
|
|
688
|
-
const created = await createTable(
|
|
689
|
-
client,
|
|
690
|
-
docToken,
|
|
691
|
-
rowSize,
|
|
692
|
-
columnSize,
|
|
693
|
-
parentBlockId,
|
|
694
|
-
columnWidth,
|
|
695
|
-
);
|
|
696
|
-
|
|
697
|
-
const tableBlockId = created.table_block_id;
|
|
698
|
-
if (!tableBlockId) {
|
|
699
|
-
throw new Error("create_table succeeded but table_block_id is missing");
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const written = await writeTableCells(client, docToken, tableBlockId, values);
|
|
703
|
-
return {
|
|
704
|
-
success: true,
|
|
705
|
-
table_block_id: tableBlockId,
|
|
706
|
-
row_size: rowSize,
|
|
707
|
-
column_size: columnSize,
|
|
708
|
-
cells_written: written.cells_written,
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
async function updateBlock(
|
|
713
|
-
client: Lark.Client,
|
|
714
|
-
docToken: string,
|
|
715
|
-
blockId: string,
|
|
716
|
-
content: string,
|
|
717
|
-
) {
|
|
718
|
-
const blockInfo = await client.docx.documentBlock.get({
|
|
719
|
-
path: { document_id: docToken, block_id: blockId },
|
|
720
|
-
});
|
|
721
|
-
if (blockInfo.code !== 0) {
|
|
722
|
-
throw new Error(blockInfo.msg);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
const res = await client.docx.documentBlock.patch({
|
|
726
|
-
path: { document_id: docToken, block_id: blockId },
|
|
727
|
-
data: {
|
|
728
|
-
update_text_elements: {
|
|
729
|
-
elements: [{ text_run: { content } }],
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
});
|
|
733
|
-
if (res.code !== 0) {
|
|
734
|
-
throw new Error(res.msg);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return { success: true, block_id: blockId };
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
async function deleteBlock(client: Lark.Client, docToken: string, blockId: string) {
|
|
741
|
-
const blockInfo = await client.docx.documentBlock.get({
|
|
742
|
-
path: { document_id: docToken, block_id: blockId },
|
|
743
|
-
});
|
|
744
|
-
if (blockInfo.code !== 0) {
|
|
745
|
-
throw new Error(blockInfo.msg);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const parentId = blockInfo.data?.block?.parent_id ?? docToken;
|
|
749
|
-
|
|
750
|
-
const children = await client.docx.documentBlockChildren.get({
|
|
751
|
-
path: { document_id: docToken, block_id: parentId },
|
|
752
|
-
});
|
|
753
|
-
if (children.code !== 0) {
|
|
754
|
-
throw new Error(children.msg);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const items = children.data?.items ?? [];
|
|
758
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type
|
|
759
|
-
const index = items.findIndex((item: any) => item.block_id === blockId);
|
|
760
|
-
if (index === -1) {
|
|
761
|
-
throw new Error("Block not found");
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const res = await client.docx.documentBlockChildren.batchDelete({
|
|
765
|
-
path: { document_id: docToken, block_id: parentId },
|
|
766
|
-
data: { start_index: index, end_index: index + 1 },
|
|
767
|
-
});
|
|
768
|
-
if (res.code !== 0) {
|
|
769
|
-
throw new Error(res.msg);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
return { success: true, deleted_block_id: blockId };
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
async function listBlocks(client: Lark.Client, docToken: string) {
|
|
776
|
-
const res = await client.docx.documentBlock.list({
|
|
777
|
-
path: { document_id: docToken },
|
|
778
|
-
});
|
|
779
|
-
if (res.code !== 0) {
|
|
780
|
-
throw new Error(res.msg);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
return {
|
|
784
|
-
blocks: res.data?.items ?? [],
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
async function getBlock(client: Lark.Client, docToken: string, blockId: string) {
|
|
789
|
-
const res = await client.docx.documentBlock.get({
|
|
790
|
-
path: { document_id: docToken, block_id: blockId },
|
|
791
|
-
});
|
|
792
|
-
if (res.code !== 0) {
|
|
793
|
-
throw new Error(res.msg);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
return {
|
|
797
|
-
block: res.data?.block,
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
async function listAppScopes(client: Lark.Client) {
|
|
802
|
-
const res = await client.application.scope.list({});
|
|
803
|
-
if (res.code !== 0) {
|
|
804
|
-
throw new Error(res.msg);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const scopes = res.data?.scopes ?? [];
|
|
808
|
-
const granted = scopes.filter((s) => s.grant_status === 1);
|
|
809
|
-
const pending = scopes.filter((s) => s.grant_status !== 1);
|
|
810
|
-
|
|
811
|
-
return {
|
|
812
|
-
granted: granted.map((s) => ({ name: s.scope_name, type: s.scope_type })),
|
|
813
|
-
pending: pending.map((s) => ({ name: s.scope_name, type: s.scope_type })),
|
|
814
|
-
summary: `${granted.length} granted, ${pending.length} pending`,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// ============ Tool Registration ============
|
|
819
|
-
|
|
820
|
-
export function registerFeishuDocTools(api: FasedAgentPluginApi) {
|
|
821
|
-
if (!api.config) {
|
|
822
|
-
api.logger.debug?.("feishu_doc: No config available, skipping doc tools");
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Check if any account is configured
|
|
827
|
-
const accounts = listEnabledFeishuAccounts(api.config);
|
|
828
|
-
if (accounts.length === 0) {
|
|
829
|
-
api.logger.debug?.("feishu_doc: No Feishu accounts configured, skipping doc tools");
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// Register if enabled on any account; account routing is resolved per execution.
|
|
834
|
-
const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
|
|
835
|
-
|
|
836
|
-
const registered: string[] = [];
|
|
837
|
-
type FeishuDocExecuteParams = FeishuDocParams & { accountId?: string };
|
|
838
|
-
|
|
839
|
-
const getClient = (params: { accountId?: string } | undefined, defaultAccountId?: string) =>
|
|
840
|
-
createFeishuToolClient({ api, executeParams: params, defaultAccountId });
|
|
841
|
-
|
|
842
|
-
const getMediaMaxBytes = (
|
|
843
|
-
params: { accountId?: string } | undefined,
|
|
844
|
-
defaultAccountId?: string,
|
|
845
|
-
) =>
|
|
846
|
-
(resolveFeishuToolAccount({ api, executeParams: params, defaultAccountId }).config
|
|
847
|
-
?.mediaMaxMb ?? 30) *
|
|
848
|
-
1024 *
|
|
849
|
-
1024;
|
|
850
|
-
|
|
851
|
-
// Main document tool with action-based dispatch
|
|
852
|
-
if (toolsCfg.doc) {
|
|
853
|
-
api.registerTool(
|
|
854
|
-
(ctx) => {
|
|
855
|
-
const defaultAccountId = ctx.agentAccountId;
|
|
856
|
-
return {
|
|
857
|
-
name: "feishu_doc",
|
|
858
|
-
label: "Feishu Doc",
|
|
859
|
-
description:
|
|
860
|
-
"Feishu document operations. Actions: read, write, append, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, upload_image, upload_file",
|
|
861
|
-
parameters: FeishuDocSchema,
|
|
862
|
-
async execute(_toolCallId, params) {
|
|
863
|
-
const p = params as FeishuDocExecuteParams;
|
|
864
|
-
try {
|
|
865
|
-
const client = getClient(p, defaultAccountId);
|
|
866
|
-
switch (p.action) {
|
|
867
|
-
case "read":
|
|
868
|
-
return json(await readDoc(client, p.doc_token));
|
|
869
|
-
case "write":
|
|
870
|
-
return json(
|
|
871
|
-
await writeDoc(
|
|
872
|
-
client,
|
|
873
|
-
p.doc_token,
|
|
874
|
-
p.content,
|
|
875
|
-
getMediaMaxBytes(p, defaultAccountId),
|
|
876
|
-
),
|
|
877
|
-
);
|
|
878
|
-
case "append":
|
|
879
|
-
return json(
|
|
880
|
-
await appendDoc(
|
|
881
|
-
client,
|
|
882
|
-
p.doc_token,
|
|
883
|
-
p.content,
|
|
884
|
-
getMediaMaxBytes(p, defaultAccountId),
|
|
885
|
-
),
|
|
886
|
-
);
|
|
887
|
-
case "create":
|
|
888
|
-
return json(
|
|
889
|
-
await createDoc(
|
|
890
|
-
client,
|
|
891
|
-
p.title,
|
|
892
|
-
p.folder_token,
|
|
893
|
-
p.owner_open_id,
|
|
894
|
-
p.owner_perm_type,
|
|
895
|
-
),
|
|
896
|
-
);
|
|
897
|
-
case "list_blocks":
|
|
898
|
-
return json(await listBlocks(client, p.doc_token));
|
|
899
|
-
case "get_block":
|
|
900
|
-
return json(await getBlock(client, p.doc_token, p.block_id));
|
|
901
|
-
case "update_block":
|
|
902
|
-
return json(await updateBlock(client, p.doc_token, p.block_id, p.content));
|
|
903
|
-
case "delete_block":
|
|
904
|
-
return json(await deleteBlock(client, p.doc_token, p.block_id));
|
|
905
|
-
case "create_table":
|
|
906
|
-
return json(
|
|
907
|
-
await createTable(
|
|
908
|
-
client,
|
|
909
|
-
p.doc_token,
|
|
910
|
-
p.row_size,
|
|
911
|
-
p.column_size,
|
|
912
|
-
p.parent_block_id,
|
|
913
|
-
p.column_width,
|
|
914
|
-
),
|
|
915
|
-
);
|
|
916
|
-
case "write_table_cells":
|
|
917
|
-
return json(
|
|
918
|
-
await writeTableCells(client, p.doc_token, p.table_block_id, p.values),
|
|
919
|
-
);
|
|
920
|
-
case "create_table_with_values":
|
|
921
|
-
return json(
|
|
922
|
-
await createTableWithValues(
|
|
923
|
-
client,
|
|
924
|
-
p.doc_token,
|
|
925
|
-
p.row_size,
|
|
926
|
-
p.column_size,
|
|
927
|
-
p.values,
|
|
928
|
-
p.parent_block_id,
|
|
929
|
-
p.column_width,
|
|
930
|
-
),
|
|
931
|
-
);
|
|
932
|
-
case "upload_image":
|
|
933
|
-
return json(
|
|
934
|
-
await uploadImageBlock(
|
|
935
|
-
client,
|
|
936
|
-
p.doc_token,
|
|
937
|
-
getMediaMaxBytes(p, defaultAccountId),
|
|
938
|
-
p.url,
|
|
939
|
-
p.file_path,
|
|
940
|
-
p.parent_block_id,
|
|
941
|
-
p.filename,
|
|
942
|
-
p.index,
|
|
943
|
-
),
|
|
944
|
-
);
|
|
945
|
-
case "upload_file":
|
|
946
|
-
return json(
|
|
947
|
-
await uploadFileBlock(
|
|
948
|
-
client,
|
|
949
|
-
p.doc_token,
|
|
950
|
-
getMediaMaxBytes(p, defaultAccountId),
|
|
951
|
-
p.url,
|
|
952
|
-
p.file_path,
|
|
953
|
-
p.parent_block_id,
|
|
954
|
-
p.filename,
|
|
955
|
-
),
|
|
956
|
-
);
|
|
957
|
-
default:
|
|
958
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
|
|
959
|
-
return json({ error: `Unknown action: ${(p as any).action}` });
|
|
960
|
-
}
|
|
961
|
-
} catch (err) {
|
|
962
|
-
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
963
|
-
}
|
|
964
|
-
},
|
|
965
|
-
};
|
|
966
|
-
},
|
|
967
|
-
{ name: "feishu_doc" },
|
|
968
|
-
);
|
|
969
|
-
registered.push("feishu_doc");
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Keep feishu_app_scopes as independent tool
|
|
973
|
-
if (toolsCfg.scopes) {
|
|
974
|
-
api.registerTool(
|
|
975
|
-
(ctx) => ({
|
|
976
|
-
name: "feishu_app_scopes",
|
|
977
|
-
label: "Feishu App Scopes",
|
|
978
|
-
description:
|
|
979
|
-
"List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
|
|
980
|
-
parameters: Type.Object({}),
|
|
981
|
-
async execute() {
|
|
982
|
-
try {
|
|
983
|
-
const result = await listAppScopes(getClient(undefined, ctx.agentAccountId));
|
|
984
|
-
return json(result);
|
|
985
|
-
} catch (err) {
|
|
986
|
-
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
987
|
-
}
|
|
988
|
-
},
|
|
989
|
-
}),
|
|
990
|
-
{ name: "feishu_app_scopes" },
|
|
991
|
-
);
|
|
992
|
-
registered.push("feishu_app_scopes");
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
if (registered.length > 0) {
|
|
996
|
-
api.logger.debug?.(`feishu_doc: Registered ${registered.join(", ")}`);
|
|
997
|
-
}
|
|
998
|
-
}
|