@kodelyth/feishu 2026.5.39 → 2026.5.42

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.
Files changed (238) hide show
  1. package/api.ts +32 -0
  2. package/channel-entry.ts +20 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/contract-api.ts +16 -0
  5. package/dist/accounts-D0ow-lRb.js +429 -0
  6. package/dist/api.js +2308 -0
  7. package/dist/app-registration-DBSnysKJ.js +184 -0
  8. package/dist/audio-preflight.runtime-Dpjbn-7r.js +7 -0
  9. package/dist/channel-13WQvQ0u.js +2115 -0
  10. package/dist/channel-entry.js +22 -0
  11. package/dist/channel-plugin-api.js +2 -0
  12. package/dist/channel.runtime-JMJonrJ4.js +729 -0
  13. package/dist/client-D1pzbBGo.js +157 -0
  14. package/dist/contract-api.js +9 -0
  15. package/dist/conversation-id-_58ecqlx.js +139 -0
  16. package/dist/drive-CgHOluXx.js +883 -0
  17. package/dist/index.js +68 -0
  18. package/dist/monitor-oWptK0zL.js +60 -0
  19. package/dist/monitor.account-DHaWlslg.js +5207 -0
  20. package/dist/monitor.state-C211a4tX.js +100 -0
  21. package/dist/probe-CF4duEpK.js +149 -0
  22. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  23. package/dist/runtime-DSh5rL_d.js +8 -0
  24. package/dist/runtime-api.js +14 -0
  25. package/dist/secret-contract-NSee-WzN.js +119 -0
  26. package/dist/secret-contract-api.js +2 -0
  27. package/dist/security-audit-DWVC0vSK.js +11 -0
  28. package/dist/security-audit-shared-Dpcwxeft.js +38 -0
  29. package/dist/security-contract-api.js +2 -0
  30. package/dist/send-DfZuV4Fi.js +1212 -0
  31. package/dist/session-conversation-Duaukbnl.js +27 -0
  32. package/dist/session-key-api.js +2 -0
  33. package/dist/setup-api.js +2 -0
  34. package/dist/setup-entry.js +15 -0
  35. package/dist/subagent-hooks-Dtegs0kh.js +235 -0
  36. package/dist/subagent-hooks-api.js +23 -0
  37. package/dist/targets-DFskxX4p.js +48 -0
  38. package/dist/thread-bindings-DI7lVSOE.js +222 -0
  39. package/index.ts +82 -0
  40. package/klaw.plugin.json +47 -1712
  41. package/package.json +4 -4
  42. package/runtime-api.ts +52 -0
  43. package/secret-contract-api.ts +5 -0
  44. package/security-contract-api.ts +1 -0
  45. package/session-key-api.ts +1 -0
  46. package/setup-api.ts +3 -0
  47. package/setup-entry.test.ts +19 -0
  48. package/setup-entry.ts +13 -0
  49. package/src/accounts.test.ts +480 -0
  50. package/src/accounts.ts +333 -0
  51. package/src/agent-config.ts +21 -0
  52. package/src/app-registration.ts +331 -0
  53. package/src/approval-auth.test.ts +24 -0
  54. package/src/approval-auth.ts +25 -0
  55. package/src/async.test.ts +35 -0
  56. package/src/async.ts +104 -0
  57. package/src/audio-preflight.runtime.ts +9 -0
  58. package/src/bitable.test.ts +136 -0
  59. package/src/bitable.ts +762 -0
  60. package/src/bot-content.ts +485 -0
  61. package/src/bot-group-name.test.ts +116 -0
  62. package/src/bot-runtime-api.ts +12 -0
  63. package/src/bot-sender-name.ts +125 -0
  64. package/src/bot.broadcast.test.ts +523 -0
  65. package/src/bot.card-action.test.ts +552 -0
  66. package/src/bot.checkBotMentioned.test.ts +265 -0
  67. package/src/bot.helpers.test.ts +135 -0
  68. package/src/bot.stripBotMention.test.ts +126 -0
  69. package/src/bot.test.ts +3671 -0
  70. package/src/bot.ts +1703 -0
  71. package/src/card-action.ts +447 -0
  72. package/src/card-interaction.test.ts +131 -0
  73. package/src/card-interaction.ts +159 -0
  74. package/src/card-test-helpers.ts +54 -0
  75. package/src/card-ux-approval.ts +65 -0
  76. package/src/card-ux-launcher.test.ts +106 -0
  77. package/src/card-ux-launcher.ts +121 -0
  78. package/src/card-ux-shared.ts +33 -0
  79. package/src/channel-runtime-api.ts +16 -0
  80. package/src/channel.runtime.ts +47 -0
  81. package/src/channel.test.ts +1151 -0
  82. package/src/channel.ts +1423 -0
  83. package/src/chat-schema.ts +25 -0
  84. package/src/chat.test.ts +240 -0
  85. package/src/chat.ts +188 -0
  86. package/src/client-timeout.ts +42 -0
  87. package/src/client.test.ts +447 -0
  88. package/src/client.ts +262 -0
  89. package/src/comment-dispatcher-runtime-api.ts +6 -0
  90. package/src/comment-dispatcher.test.ts +185 -0
  91. package/src/comment-dispatcher.ts +107 -0
  92. package/src/comment-handler-runtime-api.ts +3 -0
  93. package/src/comment-handler.test.ts +592 -0
  94. package/src/comment-handler.ts +303 -0
  95. package/src/comment-reaction.test.ts +138 -0
  96. package/src/comment-reaction.ts +259 -0
  97. package/src/comment-shared.test.ts +183 -0
  98. package/src/comment-shared.ts +406 -0
  99. package/src/comment-target.ts +44 -0
  100. package/src/config-schema.test.ts +326 -0
  101. package/src/config-schema.ts +335 -0
  102. package/src/conversation-id.test.ts +18 -0
  103. package/src/conversation-id.ts +199 -0
  104. package/src/dedup-runtime-api.ts +1 -0
  105. package/src/dedup.ts +141 -0
  106. package/src/dedupe-key.ts +72 -0
  107. package/src/directory.static.ts +61 -0
  108. package/src/directory.test.ts +141 -0
  109. package/src/directory.ts +124 -0
  110. package/src/doc-schema.ts +182 -0
  111. package/src/docx-batch-insert.test.ts +116 -0
  112. package/src/docx-batch-insert.ts +223 -0
  113. package/src/docx-color-text.ts +154 -0
  114. package/src/docx-table-ops.test.ts +53 -0
  115. package/src/docx-table-ops.ts +316 -0
  116. package/src/docx-types.ts +38 -0
  117. package/src/docx.account-selection.test.ts +95 -0
  118. package/src/docx.test.ts +701 -0
  119. package/src/docx.ts +1596 -0
  120. package/src/drive-schema.ts +92 -0
  121. package/src/drive.test.ts +1237 -0
  122. package/src/drive.ts +829 -0
  123. package/src/dynamic-agent.test.ts +155 -0
  124. package/src/dynamic-agent.ts +143 -0
  125. package/src/event-types.ts +45 -0
  126. package/src/external-keys.test.ts +20 -0
  127. package/src/external-keys.ts +19 -0
  128. package/src/lifecycle.test-support.ts +220 -0
  129. package/src/media.test.ts +955 -0
  130. package/src/media.ts +1105 -0
  131. package/src/mention-target.types.ts +5 -0
  132. package/src/mention.ts +114 -0
  133. package/src/message-action-contract.ts +13 -0
  134. package/src/monitor-state-runtime-api.ts +7 -0
  135. package/src/monitor-transport-runtime-api.ts +10 -0
  136. package/src/monitor.account.ts +492 -0
  137. package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
  138. package/src/monitor.bot-identity.ts +86 -0
  139. package/src/monitor.bot-menu-handler.ts +165 -0
  140. package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
  141. package/src/monitor.bot-menu.test.ts +188 -0
  142. package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
  143. package/src/monitor.card-action.lifecycle.test-support.ts +421 -0
  144. package/src/monitor.cleanup.test.ts +383 -0
  145. package/src/monitor.comment-notice-handler.ts +105 -0
  146. package/src/monitor.comment.test.ts +967 -0
  147. package/src/monitor.comment.ts +1386 -0
  148. package/src/monitor.lifecycle.test.ts +4 -0
  149. package/src/monitor.message-handler.ts +350 -0
  150. package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
  151. package/src/monitor.reaction.test.ts +739 -0
  152. package/src/monitor.startup.test.ts +213 -0
  153. package/src/monitor.startup.ts +74 -0
  154. package/src/monitor.state.defaults.test.ts +46 -0
  155. package/src/monitor.state.ts +170 -0
  156. package/src/monitor.synthetic-error.ts +18 -0
  157. package/src/monitor.test-mocks.ts +46 -0
  158. package/src/monitor.transport.ts +451 -0
  159. package/src/monitor.ts +100 -0
  160. package/src/monitor.webhook-e2e.test.ts +279 -0
  161. package/src/monitor.webhook-security.test.ts +389 -0
  162. package/src/monitor.webhook.test-helpers.ts +116 -0
  163. package/src/outbound-runtime-api.ts +1 -0
  164. package/src/outbound.test.ts +1118 -0
  165. package/src/outbound.ts +785 -0
  166. package/src/perm-schema.ts +52 -0
  167. package/src/perm.ts +170 -0
  168. package/src/pins.ts +108 -0
  169. package/src/policy.test.ts +223 -0
  170. package/src/policy.ts +318 -0
  171. package/src/post.test.ts +105 -0
  172. package/src/post.ts +275 -0
  173. package/src/probe.test.ts +283 -0
  174. package/src/probe.ts +166 -0
  175. package/src/processing-claims.ts +59 -0
  176. package/src/qr-terminal.ts +1 -0
  177. package/src/reactions.ts +123 -0
  178. package/src/reasoning-preview.test.ts +113 -0
  179. package/src/reasoning-preview.ts +28 -0
  180. package/src/reply-dispatcher-runtime-api.ts +7 -0
  181. package/src/reply-dispatcher.test.ts +1513 -0
  182. package/src/reply-dispatcher.ts +748 -0
  183. package/src/runtime.ts +9 -0
  184. package/src/secret-contract.ts +145 -0
  185. package/src/secret-input.ts +1 -0
  186. package/src/security-audit-shared.ts +69 -0
  187. package/src/security-audit.test.ts +59 -0
  188. package/src/security-audit.ts +1 -0
  189. package/src/send-result.ts +80 -0
  190. package/src/send-target.test.ts +86 -0
  191. package/src/send-target.ts +35 -0
  192. package/src/send.reply-fallback.test.ts +417 -0
  193. package/src/send.test.ts +621 -0
  194. package/src/send.ts +861 -0
  195. package/src/sequential-key.test.ts +72 -0
  196. package/src/sequential-key.ts +25 -0
  197. package/src/sequential-queue.test.ts +165 -0
  198. package/src/sequential-queue.ts +86 -0
  199. package/src/session-conversation.ts +42 -0
  200. package/src/session-route.ts +48 -0
  201. package/src/setup-core.ts +51 -0
  202. package/src/setup-surface.test.ts +484 -0
  203. package/src/setup-surface.ts +618 -0
  204. package/src/streaming-card.test.ts +397 -0
  205. package/src/streaming-card.ts +571 -0
  206. package/src/subagent-hooks.test.ts +627 -0
  207. package/src/subagent-hooks.ts +413 -0
  208. package/src/targets.ts +97 -0
  209. package/src/test-support/lifecycle-test-support.ts +454 -0
  210. package/src/thread-bindings.test.ts +180 -0
  211. package/src/thread-bindings.ts +331 -0
  212. package/src/tool-account-routing.test.ts +250 -0
  213. package/src/tool-account.test.ts +44 -0
  214. package/src/tool-account.ts +93 -0
  215. package/src/tool-factory-test-harness.ts +79 -0
  216. package/src/tool-result.test.ts +32 -0
  217. package/src/tool-result.ts +16 -0
  218. package/src/tools-config.test.ts +21 -0
  219. package/src/tools-config.ts +22 -0
  220. package/src/types.ts +106 -0
  221. package/src/typing.test.ts +144 -0
  222. package/src/typing.ts +214 -0
  223. package/src/wiki-schema.ts +69 -0
  224. package/src/wiki.ts +270 -0
  225. package/subagent-hooks-api.ts +31 -0
  226. package/tsconfig.json +16 -0
  227. package/api.js +0 -7
  228. package/channel-entry.js +0 -7
  229. package/channel-plugin-api.js +0 -7
  230. package/contract-api.js +0 -7
  231. package/index.js +0 -7
  232. package/runtime-api.js +0 -7
  233. package/secret-contract-api.js +0 -7
  234. package/security-contract-api.js +0 -7
  235. package/session-key-api.js +0 -7
  236. package/setup-api.js +0 -7
  237. package/setup-entry.js +0 -7
  238. package/subagent-hooks-api.js +0 -7
package/dist/api.js ADDED
@@ -0,0 +1,2308 @@
1
+ import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-_58ecqlx.js";
2
+ import { n as getFeishuThreadBindingManager, r as testing, t as createFeishuThreadBindingManager } from "./thread-bindings-DI7lVSOE.js";
3
+ import { n as handleFeishuSubagentEnded, r as handleFeishuSubagentSpawning, t as handleFeishuSubagentDeliveryTarget } from "./subagent-hooks-Dtegs0kh.js";
4
+ import { r as listEnabledFeishuAccounts } from "./accounts-D0ow-lRb.js";
5
+ import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-13WQvQ0u.js";
6
+ import { t as getFeishuRuntime } from "./runtime-DSh5rL_d.js";
7
+ import { a as jsonToolResult, d as registerFeishuChatTools, f as createFeishuToolClient, m as resolveFeishuToolAccount, n as registerFeishuDriveTools, o as toolExecutionErrorResult, p as resolveAnyEnabledFeishuToolsConfig, s as unknownToolActionResult } from "./drive-CgHOluXx.js";
8
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue } from "klaw/plugin-sdk/string-coerce-runtime";
9
+ import { existsSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { basename, isAbsolute, resolve } from "node:path";
12
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
13
+ import { extensionForMime } from "klaw/plugin-sdk/media-mime";
14
+ import { Type } from "typebox";
15
+ import { createClackPrompter } from "klaw/plugin-sdk/setup-runtime";
16
+ //#region extensions/feishu/src/doc-schema.ts
17
+ const tableCreationProperties = {
18
+ doc_token: Type.String({ description: "Document token" }),
19
+ parent_block_id: Type.Optional(Type.String({ description: "Parent block ID (default: document root)" })),
20
+ row_size: Type.Integer({
21
+ description: "Table row count",
22
+ minimum: 1
23
+ }),
24
+ column_size: Type.Integer({
25
+ description: "Table column count",
26
+ minimum: 1
27
+ }),
28
+ column_width: Type.Optional(Type.Array(Type.Number({ minimum: 1 }), { description: "Column widths in px (length should match column_size)" }))
29
+ };
30
+ const FeishuDocSchema = Type.Union([
31
+ Type.Object({
32
+ action: Type.Literal("read"),
33
+ doc_token: Type.String({ description: "Document token (extract from URL /docx/XXX)" })
34
+ }),
35
+ Type.Object({
36
+ action: Type.Literal("write"),
37
+ doc_token: Type.String({ description: "Document token" }),
38
+ content: Type.String({ description: "Markdown content to write (replaces entire document content)" })
39
+ }),
40
+ Type.Object({
41
+ action: Type.Literal("append"),
42
+ doc_token: Type.String({ description: "Document token" }),
43
+ content: Type.String({ description: "Markdown content to append to end of document" })
44
+ }),
45
+ Type.Object({
46
+ action: Type.Literal("insert"),
47
+ doc_token: Type.String({ description: "Document token" }),
48
+ content: Type.String({ description: "Markdown content to insert" }),
49
+ after_block_id: Type.String({ description: "Insert content after this block ID. Use list_blocks to find block IDs." })
50
+ }),
51
+ Type.Object({
52
+ action: Type.Literal("create"),
53
+ title: Type.String({ description: "Document title" }),
54
+ folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
55
+ grant_to_requester: Type.Optional(Type.Boolean({ description: "Grant edit permission to the trusted requesting Feishu user from runtime context (default: true)." }))
56
+ }),
57
+ Type.Object({
58
+ action: Type.Literal("list_blocks"),
59
+ doc_token: Type.String({ description: "Document token" })
60
+ }),
61
+ Type.Object({
62
+ action: Type.Literal("get_block"),
63
+ doc_token: Type.String({ description: "Document token" }),
64
+ block_id: Type.String({ description: "Block ID (from list_blocks)" })
65
+ }),
66
+ Type.Object({
67
+ action: Type.Literal("update_block"),
68
+ doc_token: Type.String({ description: "Document token" }),
69
+ block_id: Type.String({ description: "Block ID (from list_blocks)" }),
70
+ content: Type.String({ description: "New text content" })
71
+ }),
72
+ Type.Object({
73
+ action: Type.Literal("delete_block"),
74
+ doc_token: Type.String({ description: "Document token" }),
75
+ block_id: Type.String({ description: "Block ID" })
76
+ }),
77
+ Type.Object({
78
+ action: Type.Literal("create_table"),
79
+ ...tableCreationProperties
80
+ }),
81
+ Type.Object({
82
+ action: Type.Literal("write_table_cells"),
83
+ doc_token: Type.String({ description: "Document token" }),
84
+ table_block_id: Type.String({ description: "Table block ID" }),
85
+ values: Type.Array(Type.Array(Type.String()), {
86
+ description: "2D matrix values[row][col] to write into table cells",
87
+ minItems: 1
88
+ })
89
+ }),
90
+ Type.Object({
91
+ action: Type.Literal("create_table_with_values"),
92
+ ...tableCreationProperties,
93
+ values: Type.Array(Type.Array(Type.String()), {
94
+ description: "2D matrix values[row][col] to write into table cells",
95
+ minItems: 1
96
+ })
97
+ }),
98
+ Type.Object({
99
+ action: Type.Literal("insert_table_row"),
100
+ doc_token: Type.String({ description: "Document token" }),
101
+ block_id: Type.String({ description: "Table block ID" }),
102
+ row_index: Type.Optional(Type.Number({ description: "Row index to insert at (-1 for end, default: -1)" }))
103
+ }),
104
+ Type.Object({
105
+ action: Type.Literal("insert_table_column"),
106
+ doc_token: Type.String({ description: "Document token" }),
107
+ block_id: Type.String({ description: "Table block ID" }),
108
+ column_index: Type.Optional(Type.Number({ description: "Column index to insert at (-1 for end, default: -1)" }))
109
+ }),
110
+ Type.Object({
111
+ action: Type.Literal("delete_table_rows"),
112
+ doc_token: Type.String({ description: "Document token" }),
113
+ block_id: Type.String({ description: "Table block ID" }),
114
+ row_start: Type.Number({ description: "Start row index (0-based)" }),
115
+ row_count: Type.Optional(Type.Number({ description: "Number of rows to delete (default: 1)" }))
116
+ }),
117
+ Type.Object({
118
+ action: Type.Literal("delete_table_columns"),
119
+ doc_token: Type.String({ description: "Document token" }),
120
+ block_id: Type.String({ description: "Table block ID" }),
121
+ column_start: Type.Number({ description: "Start column index (0-based)" }),
122
+ column_count: Type.Optional(Type.Number({ description: "Number of columns to delete (default: 1)" }))
123
+ }),
124
+ Type.Object({
125
+ action: Type.Literal("merge_table_cells"),
126
+ doc_token: Type.String({ description: "Document token" }),
127
+ block_id: Type.String({ description: "Table block ID" }),
128
+ row_start: Type.Number({ description: "Start row index" }),
129
+ row_end: Type.Number({ description: "End row index (exclusive)" }),
130
+ column_start: Type.Number({ description: "Start column index" }),
131
+ column_end: Type.Number({ description: "End column index (exclusive)" })
132
+ }),
133
+ Type.Object({
134
+ action: Type.Literal("upload_image"),
135
+ doc_token: Type.String({ description: "Document token" }),
136
+ url: Type.Optional(Type.String({ description: "Remote image URL (http/https)" })),
137
+ file_path: Type.Optional(Type.String({ description: "Local image file path" })),
138
+ 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." })),
139
+ parent_block_id: Type.Optional(Type.String({ description: "Parent block ID (default: document root)" })),
140
+ filename: Type.Optional(Type.String({ description: "Optional filename override" })),
141
+ index: Type.Optional(Type.Integer({
142
+ minimum: 0,
143
+ description: "Insert position (0-based index among siblings). Omit to append."
144
+ }))
145
+ }),
146
+ Type.Object({
147
+ action: Type.Literal("upload_file"),
148
+ doc_token: Type.String({ description: "Document token" }),
149
+ url: Type.Optional(Type.String({ description: "Remote file URL (http/https)" })),
150
+ file_path: Type.Optional(Type.String({ description: "Local file path" })),
151
+ parent_block_id: Type.Optional(Type.String({ description: "Parent block ID (default: document root)" })),
152
+ filename: Type.Optional(Type.String({ description: "Optional filename override" }))
153
+ }),
154
+ Type.Object({
155
+ action: Type.Literal("color_text"),
156
+ doc_token: Type.String({ description: "Document token" }),
157
+ block_id: Type.String({ description: "Text block ID to update" }),
158
+ 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\"" })
159
+ })
160
+ ]);
161
+ //#endregion
162
+ //#region extensions/feishu/src/docx-table-ops.ts
163
+ const MIN_COLUMN_WIDTH = 50;
164
+ const MAX_COLUMN_WIDTH = 400;
165
+ const DEFAULT_TABLE_WIDTH = 730;
166
+ /**
167
+ * Calculate adaptive column widths based on cell content length.
168
+ *
169
+ * Algorithm:
170
+ * 1. For each column, find the max content length across all rows
171
+ * 2. Weight CJK characters as 2x width (they render wider)
172
+ * 3. Calculate proportional widths based on content length
173
+ * 4. Apply min/max constraints
174
+ * 5. Redistribute remaining space to fill total table width
175
+ *
176
+ * Total width is derived from the original column_width values returned
177
+ * by the Convert API, ensuring tables match Feishu's expected dimensions.
178
+ *
179
+ * @param blocks - Array of blocks from Convert API
180
+ * @param tableBlockId - The block_id of the table block
181
+ * @returns Array of column widths in pixels
182
+ */
183
+ function normalizeChildBlockIds(children) {
184
+ if (Array.isArray(children)) return children;
185
+ return typeof children === "string" ? [children] : [];
186
+ }
187
+ function omitParentId(block) {
188
+ const cleanBlock = { ...block };
189
+ delete cleanBlock.parent_id;
190
+ return cleanBlock;
191
+ }
192
+ function createDescendantTable(table, adaptiveWidths) {
193
+ const { row_size, column_size } = table.property || {};
194
+ return { property: {
195
+ row_size,
196
+ column_size,
197
+ ...adaptiveWidths?.length ? { column_width: adaptiveWidths } : {}
198
+ } };
199
+ }
200
+ function calculateAdaptiveColumnWidths(blocks, tableBlockId) {
201
+ const tableBlock = blocks.find((b) => b.block_id === tableBlockId && b.block_type === 31);
202
+ if (!tableBlock?.table?.property) return [];
203
+ const { row_size, column_size, column_width: originalWidths } = tableBlock.table.property;
204
+ if (!row_size || !column_size) return [];
205
+ const totalWidth = originalWidths && originalWidths.length > 0 ? originalWidths.reduce((a, b) => a + b, 0) : DEFAULT_TABLE_WIDTH;
206
+ const cellIds = normalizeChildBlockIds(tableBlock.children);
207
+ const blockMap = /* @__PURE__ */ new Map();
208
+ for (const block of blocks) if (block.block_id) blockMap.set(block.block_id, block);
209
+ function getCellText(cellId) {
210
+ const cell = blockMap.get(cellId);
211
+ let text = "";
212
+ const childIds = normalizeChildBlockIds(cell?.children);
213
+ for (const childId of childIds) {
214
+ const child = blockMap.get(childId);
215
+ if (child?.text?.elements) {
216
+ for (const elem of child.text.elements) if (elem.text_run?.content) text += elem.text_run.content;
217
+ }
218
+ }
219
+ return text;
220
+ }
221
+ function getWeightedLength(text) {
222
+ return Array.from(text).reduce((sum, char) => {
223
+ return sum + (char.charCodeAt(0) > 255 ? 2 : 1);
224
+ }, 0);
225
+ }
226
+ const maxLengths = Array.from({ length: column_size }, () => 0);
227
+ for (let row = 0; row < row_size; row++) for (let col = 0; col < column_size; col++) {
228
+ const cellId = cellIds[row * column_size + col];
229
+ if (cellId) {
230
+ const length = getWeightedLength(getCellText(cellId));
231
+ maxLengths[col] = Math.max(maxLengths[col], length);
232
+ }
233
+ }
234
+ const totalLength = maxLengths.reduce((a, b) => a + b, 0);
235
+ if (totalLength === 0) {
236
+ const equalWidth = Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, Math.floor(totalWidth / column_size)));
237
+ return Array.from({ length: column_size }, () => equalWidth);
238
+ }
239
+ let widths = maxLengths.map((len) => {
240
+ const proportion = len / totalLength;
241
+ return Math.round(proportion * totalWidth);
242
+ });
243
+ widths = widths.map((w) => Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, w)));
244
+ let remaining = totalWidth - widths.reduce((a, b) => a + b, 0);
245
+ while (remaining > 0) {
246
+ const growable = widths.map((w, i) => w < MAX_COLUMN_WIDTH ? i : -1).filter((i) => i >= 0);
247
+ if (growable.length === 0) break;
248
+ const perColumn = Math.floor(remaining / growable.length);
249
+ if (perColumn === 0) break;
250
+ for (const i of growable) {
251
+ const add = Math.min(perColumn, MAX_COLUMN_WIDTH - widths[i]);
252
+ widths[i] += add;
253
+ remaining -= add;
254
+ }
255
+ }
256
+ return widths;
257
+ }
258
+ /**
259
+ * Clean blocks for Descendant API with adaptive column widths.
260
+ *
261
+ * - Removes parent_id from all blocks
262
+ * - Fixes children type (string → array) for TableCell blocks
263
+ * - Removes merge_info (read-only, causes API error)
264
+ * - Calculates and applies adaptive column_width for tables
265
+ *
266
+ * @param blocks - Array of blocks from Convert API
267
+ * @returns Cleaned blocks ready for Descendant API
268
+ */
269
+ function cleanBlocksForDescendant(blocks) {
270
+ const tableWidths = /* @__PURE__ */ new Map();
271
+ for (const block of blocks) if (block.block_type === 31 && block.block_id) {
272
+ const widths = calculateAdaptiveColumnWidths(blocks, block.block_id);
273
+ tableWidths.set(block.block_id, widths);
274
+ }
275
+ return blocks.map((block) => {
276
+ const cleanBlock = omitParentId(block);
277
+ if (cleanBlock.block_type === 32 && typeof cleanBlock.children === "string") cleanBlock.children = [cleanBlock.children];
278
+ if (cleanBlock.block_type === 31 && cleanBlock.table) {
279
+ const adaptiveWidths = block.block_id ? tableWidths.get(block.block_id) : void 0;
280
+ cleanBlock.table = createDescendantTable(cleanBlock.table, adaptiveWidths);
281
+ }
282
+ return cleanBlock;
283
+ });
284
+ }
285
+ async function insertTableRow(client, docToken, blockId, rowIndex = -1) {
286
+ const res = await client.docx.documentBlock.patch({
287
+ path: {
288
+ document_id: docToken,
289
+ block_id: blockId
290
+ },
291
+ data: { insert_table_row: { row_index: rowIndex } }
292
+ });
293
+ if (res.code !== 0) throw new Error(res.msg);
294
+ return {
295
+ success: true,
296
+ block: res.data?.block
297
+ };
298
+ }
299
+ async function insertTableColumn(client, docToken, blockId, columnIndex = -1) {
300
+ const res = await client.docx.documentBlock.patch({
301
+ path: {
302
+ document_id: docToken,
303
+ block_id: blockId
304
+ },
305
+ data: { insert_table_column: { column_index: columnIndex } }
306
+ });
307
+ if (res.code !== 0) throw new Error(res.msg);
308
+ return {
309
+ success: true,
310
+ block: res.data?.block
311
+ };
312
+ }
313
+ async function deleteTableRows(client, docToken, blockId, rowStart, rowCount = 1) {
314
+ const res = await client.docx.documentBlock.patch({
315
+ path: {
316
+ document_id: docToken,
317
+ block_id: blockId
318
+ },
319
+ data: { delete_table_rows: {
320
+ row_start_index: rowStart,
321
+ row_end_index: rowStart + rowCount
322
+ } }
323
+ });
324
+ if (res.code !== 0) throw new Error(res.msg);
325
+ return {
326
+ success: true,
327
+ rows_deleted: rowCount,
328
+ block: res.data?.block
329
+ };
330
+ }
331
+ async function deleteTableColumns(client, docToken, blockId, columnStart, columnCount = 1) {
332
+ const res = await client.docx.documentBlock.patch({
333
+ path: {
334
+ document_id: docToken,
335
+ block_id: blockId
336
+ },
337
+ data: { delete_table_columns: {
338
+ column_start_index: columnStart,
339
+ column_end_index: columnStart + columnCount
340
+ } }
341
+ });
342
+ if (res.code !== 0) throw new Error(res.msg);
343
+ return {
344
+ success: true,
345
+ columns_deleted: columnCount,
346
+ block: res.data?.block
347
+ };
348
+ }
349
+ async function mergeTableCells(client, docToken, blockId, rowStart, rowEnd, columnStart, columnEnd) {
350
+ const res = await client.docx.documentBlock.patch({
351
+ path: {
352
+ document_id: docToken,
353
+ block_id: blockId
354
+ },
355
+ data: { merge_table_cells: {
356
+ row_start_index: rowStart,
357
+ row_end_index: rowEnd,
358
+ column_start_index: columnStart,
359
+ column_end_index: columnEnd
360
+ } }
361
+ });
362
+ if (res.code !== 0) throw new Error(res.msg);
363
+ return {
364
+ success: true,
365
+ block: res.data?.block
366
+ };
367
+ }
368
+ //#endregion
369
+ //#region extensions/feishu/src/docx-batch-insert.ts
370
+ const BATCH_SIZE = 1e3;
371
+ function normalizeChildIds$1(children) {
372
+ if (Array.isArray(children)) return children;
373
+ const child = readStringValue(children);
374
+ return child ? [child] : void 0;
375
+ }
376
+ function toDescendantBlock$1(block) {
377
+ const children = normalizeChildIds$1(block.children);
378
+ return {
379
+ ...block,
380
+ ...children ? { children } : {}
381
+ };
382
+ }
383
+ /**
384
+ * Collect all descendant blocks for a given first-level block ID.
385
+ * Recursively traverses the block tree to gather all children.
386
+ */
387
+ function collectDescendants(blockMap, rootId) {
388
+ const result = [];
389
+ const visited = /* @__PURE__ */ new Set();
390
+ function collect(blockId) {
391
+ if (visited.has(blockId)) return;
392
+ visited.add(blockId);
393
+ const block = blockMap.get(blockId);
394
+ if (!block) return;
395
+ result.push(block);
396
+ const children = block.children;
397
+ if (Array.isArray(children)) for (const childId of children) collect(childId);
398
+ else if (typeof children === "string") collect(children);
399
+ }
400
+ collect(rootId);
401
+ return result;
402
+ }
403
+ /**
404
+ * Insert a single batch of blocks using Descendant API.
405
+ *
406
+ * @param parentBlockId - Parent block to insert into (defaults to docToken)
407
+ * @param index - Position within parent's children (-1 = end)
408
+ */
409
+ async function insertBatch(client, docToken, blocks, firstLevelBlockIds, parentBlockId = docToken, index = -1) {
410
+ const descendants = cleanBlocksForDescendant(blocks);
411
+ if (descendants.length === 0) return [];
412
+ const res = await client.docx.documentBlockDescendant.create({
413
+ path: {
414
+ document_id: docToken,
415
+ block_id: parentBlockId
416
+ },
417
+ data: {
418
+ children_id: firstLevelBlockIds,
419
+ descendants: descendants.map(toDescendantBlock$1),
420
+ index
421
+ }
422
+ });
423
+ if (res.code !== 0) throw new Error(`${res.msg} (code: ${res.code})`);
424
+ return res.data?.children ?? [];
425
+ }
426
+ /**
427
+ * Insert blocks in batches for large documents (>1000 blocks).
428
+ *
429
+ * Batches are split to ensure BOTH children_id AND descendants
430
+ * arrays stay under the 1000 block API limit.
431
+ *
432
+ * @param client - Feishu API client
433
+ * @param docToken - Document ID
434
+ * @param blocks - All blocks from Convert API
435
+ * @param firstLevelBlockIds - IDs of top-level blocks to insert
436
+ * @param logger - Optional logger for progress updates
437
+ * @param parentBlockId - Parent block to insert into (defaults to docToken = document root)
438
+ * @param startIndex - Starting position within parent (-1 = end). For multi-batch inserts,
439
+ * each batch advances this by the number of first-level IDs inserted so far.
440
+ * @returns Inserted children blocks and any skipped block IDs
441
+ */
442
+ async function insertBlocksInBatches(client, docToken, blocks, firstLevelBlockIds, logger, parentBlockId = docToken, startIndex = -1) {
443
+ const allChildren = [];
444
+ const batches = [];
445
+ let currentBatch = {
446
+ firstLevelIds: [],
447
+ blocks: []
448
+ };
449
+ const usedBlockIds = /* @__PURE__ */ new Set();
450
+ const blockMap = /* @__PURE__ */ new Map();
451
+ for (const block of blocks) if (block.block_id) blockMap.set(block.block_id, block);
452
+ for (const firstLevelId of firstLevelBlockIds) {
453
+ const newBlocks = collectDescendants(blockMap, firstLevelId).filter((b) => b.block_id && !usedBlockIds.has(b.block_id));
454
+ if (newBlocks.length > 1e3) throw new Error(`Block "${firstLevelId}" has ${newBlocks.length} descendants, which exceeds the Feishu API limit of ${BATCH_SIZE} blocks per request. Please split the content into smaller sections.`);
455
+ if (currentBatch.blocks.length + newBlocks.length > 1e3 && currentBatch.blocks.length > 0) {
456
+ batches.push(currentBatch);
457
+ currentBatch = {
458
+ firstLevelIds: [],
459
+ blocks: []
460
+ };
461
+ }
462
+ currentBatch.firstLevelIds.push(firstLevelId);
463
+ for (const block of newBlocks) {
464
+ currentBatch.blocks.push(block);
465
+ if (block.block_id) usedBlockIds.add(block.block_id);
466
+ }
467
+ }
468
+ if (currentBatch.blocks.length > 0) batches.push(currentBatch);
469
+ let currentIndex = startIndex;
470
+ for (let i = 0; i < batches.length; i++) {
471
+ const batch = batches[i];
472
+ logger?.info?.(`feishu_doc: Inserting batch ${i + 1}/${batches.length} (${batch.blocks.length} blocks)...`);
473
+ const children = await insertBatch(client, docToken, batch.blocks, batch.firstLevelIds, parentBlockId, currentIndex);
474
+ allChildren.push(...children);
475
+ if (currentIndex !== -1) currentIndex += batch.firstLevelIds.length;
476
+ }
477
+ return {
478
+ children: allChildren,
479
+ skipped: []
480
+ };
481
+ }
482
+ //#endregion
483
+ //#region extensions/feishu/src/docx-color-text.ts
484
+ const TEXT_COLOR = {
485
+ red: 1,
486
+ orange: 2,
487
+ yellow: 3,
488
+ green: 4,
489
+ blue: 5,
490
+ purple: 6,
491
+ grey: 7,
492
+ gray: 7
493
+ };
494
+ const BACKGROUND_COLOR = {
495
+ red: 1,
496
+ orange: 2,
497
+ yellow: 3,
498
+ green: 4,
499
+ blue: 5,
500
+ purple: 6,
501
+ grey: 7,
502
+ gray: 7
503
+ };
504
+ /**
505
+ * Parse color markup into segments.
506
+ *
507
+ * Supports:
508
+ * [red]text[/red] → red text
509
+ * [bg:yellow]text[/bg] → yellow background
510
+ * [bold]text[/bold] → bold
511
+ * [green bold]text[/green] → green + bold
512
+ */
513
+ function parseColorMarkup(content) {
514
+ const segments = [];
515
+ const KNOWN = "(?:bg:[a-z]+|bold|red|orange|yellow|green|blue|purple|gr[ae]y)";
516
+ const tagPattern = new RegExp(`\\[(${KNOWN}(?:\\s+${KNOWN})*)\\](.*?)\\[\\/(?:[^\\]]+)\\]|([^[]+|\\[)`, "gis");
517
+ let match;
518
+ while ((match = tagPattern.exec(content)) !== null) if (match[3] !== void 0) {
519
+ if (match[3]) segments.push({ text: match[3] });
520
+ } else {
521
+ const tagStr = normalizeLowercaseStringOrEmpty(match[1]);
522
+ const text = match[2];
523
+ const tags = tagStr.split(/\s+/);
524
+ const segment = { text };
525
+ for (const tag of tags) if (tag.startsWith("bg:")) {
526
+ const color = tag.slice(3);
527
+ if (BACKGROUND_COLOR[color]) segment.bgColor = BACKGROUND_COLOR[color];
528
+ } else if (tag === "bold") segment.bold = true;
529
+ else if (TEXT_COLOR[tag]) segment.textColor = TEXT_COLOR[tag];
530
+ if (text) segments.push(segment);
531
+ }
532
+ return segments;
533
+ }
534
+ /**
535
+ * Update a text block with colored segments.
536
+ */
537
+ async function updateColorText(client, docToken, blockId, content) {
538
+ const segments = parseColorMarkup(content);
539
+ const elements = segments.map((seg) => ({ text_run: {
540
+ content: seg.text,
541
+ text_element_style: {
542
+ ...seg.textColor && { text_color: seg.textColor },
543
+ ...seg.bgColor && { background_color: seg.bgColor },
544
+ ...seg.bold && { bold: true }
545
+ }
546
+ } }));
547
+ const res = await client.docx.documentBlock.patch({
548
+ path: {
549
+ document_id: docToken,
550
+ block_id: blockId
551
+ },
552
+ data: { update_text_elements: { elements } }
553
+ });
554
+ if (res.code !== 0) throw new Error(res.msg);
555
+ return {
556
+ success: true,
557
+ segments: segments.length,
558
+ block: res.data?.block
559
+ };
560
+ }
561
+ //#endregion
562
+ //#region extensions/feishu/src/docx.ts
563
+ function json$1(data) {
564
+ return {
565
+ content: [{
566
+ type: "text",
567
+ text: JSON.stringify(data, null, 2)
568
+ }],
569
+ details: data
570
+ };
571
+ }
572
+ function resolveDocToolLocalRoots(ctx) {
573
+ if (ctx.fsPolicy?.workspaceOnly !== true) return;
574
+ const workspaceDir = ctx.workspaceDir?.trim();
575
+ if (!workspaceDir) return [];
576
+ return [resolve(workspaceDir)];
577
+ }
578
+ /** Extract image URLs from markdown content */
579
+ function extractImageUrls(markdown) {
580
+ const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
581
+ const urls = [];
582
+ let match;
583
+ while ((match = regex.exec(markdown)) !== null) {
584
+ const url = match[1].trim();
585
+ if (url.startsWith("http://") || url.startsWith("https://")) urls.push(url);
586
+ }
587
+ return urls;
588
+ }
589
+ const BLOCK_TYPE_NAMES = {
590
+ 1: "Page",
591
+ 2: "Text",
592
+ 3: "Heading1",
593
+ 4: "Heading2",
594
+ 5: "Heading3",
595
+ 12: "Bullet",
596
+ 13: "Ordered",
597
+ 14: "Code",
598
+ 15: "Quote",
599
+ 17: "Todo",
600
+ 18: "Bitable",
601
+ 21: "Diagram",
602
+ 22: "Divider",
603
+ 23: "File",
604
+ 27: "Image",
605
+ 30: "Sheet",
606
+ 31: "Table",
607
+ 32: "TableCell"
608
+ };
609
+ const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
610
+ /** Clean blocks for insertion (remove unsupported types and read-only fields) */
611
+ function cleanBlocksForInsert(blocks) {
612
+ const skipped = [];
613
+ return {
614
+ cleaned: blocks.filter((block) => {
615
+ if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
616
+ const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
617
+ skipped.push(typeName);
618
+ return false;
619
+ }
620
+ return true;
621
+ }).map((block) => {
622
+ if (block.block_type === 31 && block.table?.merge_info) {
623
+ const { merge_info: _merge_info, ...tableRest } = block.table;
624
+ return Object.assign({}, block, { table: tableRest });
625
+ }
626
+ return block;
627
+ }),
628
+ skipped
629
+ };
630
+ }
631
+ /** Max blocks per documentBlockChildren.create request */
632
+ const MAX_CONVERT_RETRY_DEPTH = 8;
633
+ async function convertMarkdown(client, markdown) {
634
+ const res = await client.docx.document.convert({ data: {
635
+ content_type: "markdown",
636
+ content: markdown
637
+ } });
638
+ if (res.code !== 0) throw new Error(res.msg);
639
+ return {
640
+ blocks: res.data?.blocks ?? [],
641
+ firstLevelBlockIds: res.data?.first_level_block_ids ?? []
642
+ };
643
+ }
644
+ function normalizeChildIds(children) {
645
+ if (Array.isArray(children)) return children.filter((child) => typeof child === "string");
646
+ if (typeof children === "string") return [children];
647
+ return [];
648
+ }
649
+ function toCreateChildBlock(block) {
650
+ return block;
651
+ }
652
+ function toDescendantBlock(block) {
653
+ const children = normalizeChildIds(block.children);
654
+ return {
655
+ ...block.block_id ? { block_id: block.block_id } : {},
656
+ ...children.length > 0 ? { children } : {},
657
+ ...block
658
+ };
659
+ }
660
+ function normalizeInsertedChildBlocks(children) {
661
+ if (!Array.isArray(children)) return [];
662
+ return children.filter((child) => typeof child === "object" && child !== null);
663
+ }
664
+ function normalizeConvertedBlockTree(blocks, firstLevelIds) {
665
+ if (blocks.length <= 1) return {
666
+ orderedBlocks: blocks,
667
+ rootIds: blocks.length === 1 && typeof blocks[0]?.block_id === "string" ? [blocks[0].block_id] : []
668
+ };
669
+ const byId = /* @__PURE__ */ new Map();
670
+ const originalOrder = /* @__PURE__ */ new Map();
671
+ for (const [index, block] of blocks.entries()) if (typeof block?.block_id === "string") {
672
+ byId.set(block.block_id, block);
673
+ originalOrder.set(block.block_id, index);
674
+ }
675
+ const childIds = /* @__PURE__ */ new Set();
676
+ for (const block of blocks) for (const childId of normalizeChildIds(block?.children)) childIds.add(childId);
677
+ const inferredTopLevelIds = blocks.filter((block) => {
678
+ const blockId = block?.block_id;
679
+ if (typeof blockId !== "string") return false;
680
+ const parentId = typeof block?.parent_id === "string" ? block.parent_id : "";
681
+ return !childIds.has(blockId) && (!parentId || !byId.has(parentId));
682
+ }).toSorted((a, b) => (originalOrder.get(a.block_id ?? "__missing__") ?? 0) - (originalOrder.get(b.block_id ?? "__missing__") ?? 0)).map((block) => block.block_id).filter((blockId) => typeof blockId === "string");
683
+ const rootIds = (firstLevelIds && firstLevelIds.length > 0 ? firstLevelIds : inferredTopLevelIds).filter((id, index, arr) => typeof id === "string" && byId.has(id) && arr.indexOf(id) === index);
684
+ const orderedBlocks = [];
685
+ const visited = /* @__PURE__ */ new Set();
686
+ const visit = (blockId) => {
687
+ if (!byId.has(blockId) || visited.has(blockId)) return;
688
+ visited.add(blockId);
689
+ const block = byId.get(blockId);
690
+ if (!block) return;
691
+ orderedBlocks.push(block);
692
+ for (const childId of normalizeChildIds(block?.children)) visit(childId);
693
+ };
694
+ for (const rootId of rootIds) visit(rootId);
695
+ for (const block of blocks) if (typeof block?.block_id === "string") visit(block.block_id);
696
+ else orderedBlocks.push(block);
697
+ return {
698
+ orderedBlocks,
699
+ rootIds: rootIds.filter((id) => typeof id === "string")
700
+ };
701
+ }
702
+ async function insertBlocks(client, docToken, blocks, parentBlockId, index) {
703
+ const { cleaned, skipped } = cleanBlocksForInsert(blocks);
704
+ const blockId = parentBlockId ?? docToken;
705
+ if (cleaned.length === 0) return {
706
+ children: [],
707
+ skipped
708
+ };
709
+ const allInserted = [];
710
+ for (const [offset, block] of cleaned.entries()) {
711
+ const res = await client.docx.documentBlockChildren.create({
712
+ path: {
713
+ document_id: docToken,
714
+ block_id: blockId
715
+ },
716
+ data: {
717
+ children: [toCreateChildBlock(block)],
718
+ ...index !== void 0 ? { index: index + offset } : {}
719
+ }
720
+ });
721
+ if (res.code !== 0) throw new Error(res.msg);
722
+ allInserted.push(...res.data?.children ?? []);
723
+ }
724
+ return {
725
+ children: allInserted,
726
+ skipped
727
+ };
728
+ }
729
+ /** Split markdown into chunks at top-level headings (# or ##) to stay within API content limits */
730
+ function splitMarkdownByHeadings(markdown) {
731
+ const lines = markdown.split("\n");
732
+ const chunks = [];
733
+ let current = [];
734
+ let inFencedBlock = false;
735
+ for (const line of lines) {
736
+ if (/^(`{3,}|~{3,})/.test(line)) inFencedBlock = !inFencedBlock;
737
+ if (!inFencedBlock && /^#{1,2}\s/.test(line) && current.length > 0) {
738
+ chunks.push(current.join("\n"));
739
+ current = [];
740
+ }
741
+ current.push(line);
742
+ }
743
+ if (current.length > 0) chunks.push(current.join("\n"));
744
+ return chunks;
745
+ }
746
+ /** Split markdown by size, preferring to break outside fenced code blocks when possible */
747
+ function splitMarkdownBySize(markdown, maxChars) {
748
+ if (markdown.length <= maxChars) return [markdown];
749
+ const lines = markdown.split("\n");
750
+ const chunks = [];
751
+ let current = [];
752
+ let currentLength = 0;
753
+ let inFencedBlock = false;
754
+ for (const line of lines) {
755
+ if (/^(`{3,}|~{3,})/.test(line)) inFencedBlock = !inFencedBlock;
756
+ const lineLength = line.length + 1;
757
+ const wouldExceed = currentLength + lineLength > maxChars;
758
+ if (current.length > 0 && wouldExceed && !inFencedBlock) {
759
+ chunks.push(current.join("\n"));
760
+ current = [];
761
+ currentLength = 0;
762
+ }
763
+ current.push(line);
764
+ currentLength += lineLength;
765
+ }
766
+ if (current.length > 0) chunks.push(current.join("\n"));
767
+ if (chunks.length > 1) return chunks;
768
+ const midpoint = Math.floor(lines.length / 2);
769
+ if (midpoint <= 0 || midpoint >= lines.length) return [markdown];
770
+ return [lines.slice(0, midpoint).join("\n"), lines.slice(midpoint).join("\n")];
771
+ }
772
+ async function convertMarkdownWithFallback(client, markdown, depth = 0) {
773
+ try {
774
+ return await convertMarkdown(client, markdown);
775
+ } catch (error) {
776
+ if (depth >= MAX_CONVERT_RETRY_DEPTH || markdown.length < 2) throw error;
777
+ const chunks = splitMarkdownBySize(markdown, Math.max(256, Math.floor(markdown.length / 2)));
778
+ if (chunks.length <= 1) throw error;
779
+ const blocks = [];
780
+ const firstLevelBlockIds = [];
781
+ for (const chunk of chunks) {
782
+ const converted = await convertMarkdownWithFallback(client, chunk, depth + 1);
783
+ blocks.push(...converted.blocks);
784
+ firstLevelBlockIds.push(...converted.firstLevelBlockIds);
785
+ }
786
+ return {
787
+ blocks,
788
+ firstLevelBlockIds
789
+ };
790
+ }
791
+ }
792
+ /** Convert markdown in chunks to avoid document.convert content size limits */
793
+ async function chunkedConvertMarkdown(client, markdown) {
794
+ const chunks = splitMarkdownByHeadings(markdown);
795
+ const allBlocks = [];
796
+ const allRootIds = [];
797
+ for (const chunk of chunks) {
798
+ const { blocks, firstLevelBlockIds } = await convertMarkdownWithFallback(client, chunk);
799
+ const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
800
+ allBlocks.push(...orderedBlocks);
801
+ allRootIds.push(...rootIds);
802
+ }
803
+ return {
804
+ blocks: allBlocks,
805
+ firstLevelBlockIds: allRootIds
806
+ };
807
+ }
808
+ /**
809
+ * Insert blocks using the Descendant API (supports tables, nested lists, large docs).
810
+ * Unlike the Children API, this supports block_type 31/32 (Table/TableCell).
811
+ *
812
+ * @param parentBlockId - Parent block to insert into (defaults to docToken = document root)
813
+ * @param index - Position within parent's children (-1 = end, 0 = first)
814
+ */
815
+ async function insertBlocksWithDescendant(client, docToken, blocks, firstLevelBlockIds, { parentBlockId = docToken, index = -1 } = {}) {
816
+ const descendants = cleanBlocksForDescendant(blocks);
817
+ if (descendants.length === 0) return { children: [] };
818
+ const res = await client.docx.documentBlockDescendant.create({
819
+ path: {
820
+ document_id: docToken,
821
+ block_id: parentBlockId
822
+ },
823
+ data: {
824
+ children_id: firstLevelBlockIds,
825
+ descendants: descendants.map(toDescendantBlock),
826
+ index
827
+ }
828
+ });
829
+ if (res.code !== 0) throw new Error(`${res.msg} (code: ${res.code})`);
830
+ return { children: res.data?.children ?? [] };
831
+ }
832
+ async function clearDocumentContent(client, docToken) {
833
+ const existing = await client.docx.documentBlock.list({ path: { document_id: docToken } });
834
+ if (existing.code !== 0) throw new Error(existing.msg);
835
+ const childIds = existing.data?.items?.filter((b) => b.parent_id === docToken && b.block_type !== 1).map((b) => b.block_id) ?? [];
836
+ if (childIds.length > 0) {
837
+ const res = await client.docx.documentBlockChildren.batchDelete({
838
+ path: {
839
+ document_id: docToken,
840
+ block_id: docToken
841
+ },
842
+ data: {
843
+ start_index: 0,
844
+ end_index: childIds.length
845
+ }
846
+ });
847
+ if (res.code !== 0) throw new Error(res.msg);
848
+ }
849
+ return childIds.length;
850
+ }
851
+ async function uploadImageToDocx(client, blockId, imageBuffer, fileName, docToken) {
852
+ const fileToken = (await client.drive.media.uploadAll({ data: {
853
+ file_name: fileName,
854
+ parent_type: "docx_image",
855
+ parent_node: blockId,
856
+ size: imageBuffer.length,
857
+ file: imageBuffer,
858
+ ...docToken ? { extra: JSON.stringify({ drive_route_token: docToken }) } : {}
859
+ } }))?.file_token;
860
+ if (!fileToken) throw new Error("Image upload failed: no file_token returned");
861
+ return fileToken;
862
+ }
863
+ async function downloadImage(url, maxBytes) {
864
+ return (await getFeishuRuntime().channel.media.readRemoteMediaBuffer({
865
+ url,
866
+ maxBytes
867
+ })).buffer;
868
+ }
869
+ async function resolveUploadInput(url, filePath, maxBytes, localRoots, explicitFileName, imageInput) {
870
+ const inputSources = [
871
+ url ? "url" : null,
872
+ filePath ? "file_path" : null,
873
+ imageInput ? "image" : null
874
+ ].filter(Boolean);
875
+ if (inputSources.length > 1) throw new Error(`Provide only one image source; got: ${inputSources.join(", ")}`);
876
+ if (imageInput?.startsWith("data:")) {
877
+ const commaIdx = imageInput.indexOf(",");
878
+ if (commaIdx === -1) throw new Error("Invalid data URI: missing comma separator.");
879
+ const header = imageInput.slice(0, commaIdx);
880
+ const data = imageInput.slice(commaIdx + 1);
881
+ if (!header.includes(";base64")) throw new Error("Invalid data URI: missing ';base64' marker. Expected format: data:image/png;base64,<base64data>");
882
+ const trimmedData = data.trim();
883
+ if (trimmedData.length === 0 || !/^[A-Za-z0-9+/]+=*$/.test(trimmedData)) throw new Error(`Invalid data URI: base64 payload contains characters outside the standard alphabet.`);
884
+ const ext = extensionForMime(header.match(/data:([^;]+)/)?.[1])?.slice(1) ?? "png";
885
+ const estimatedBytes = Math.ceil(trimmedData.length * 3 / 4);
886
+ if (estimatedBytes > maxBytes) throw new Error(`Image data URI exceeds limit: estimated ${estimatedBytes} bytes > ${maxBytes} bytes`);
887
+ return {
888
+ buffer: Buffer.from(trimmedData, "base64"),
889
+ fileName: explicitFileName ?? `image.${ext}`
890
+ };
891
+ }
892
+ if (imageInput) {
893
+ const candidate = imageInput.startsWith("~") ? imageInput.replace(/^~/, homedir()) : imageInput;
894
+ const unambiguousPath = imageInput.startsWith("~") || imageInput.startsWith("./") || imageInput.startsWith("../");
895
+ const absolutePath = isAbsolute(imageInput);
896
+ if (unambiguousPath || absolutePath && existsSync(candidate)) {
897
+ const resolvedPath = resolve(candidate);
898
+ return {
899
+ buffer: (await getFeishuRuntime().media.loadWebMedia(resolvedPath, {
900
+ maxBytes,
901
+ optimizeImages: false,
902
+ localRoots
903
+ })).buffer,
904
+ fileName: explicitFileName ?? basename(candidate)
905
+ };
906
+ }
907
+ if (absolutePath && !existsSync(candidate)) throw new Error(`File not found: "${candidate}". If you intended to pass image binary data, use a data URI instead: data:image/jpeg;base64,...`);
908
+ }
909
+ if (imageInput) {
910
+ const trimmed = imageInput.trim();
911
+ if (trimmed.length === 0 || !/^[A-Za-z0-9+/]+=*$/.test(trimmed)) throw new Error("Invalid base64: image input contains characters outside the standard base64 alphabet. Use a data URI (data:image/png;base64,...) or a local file path instead.");
912
+ const estimatedBytes = Math.ceil(trimmed.length * 3 / 4);
913
+ if (estimatedBytes > maxBytes) throw new Error(`Base64 image exceeds limit: estimated ${estimatedBytes} bytes > ${maxBytes} bytes`);
914
+ const buffer = Buffer.from(trimmed, "base64");
915
+ if (buffer.length === 0) throw new Error("Base64 image decoded to empty buffer; check the input.");
916
+ return {
917
+ buffer,
918
+ fileName: explicitFileName ?? "image.png"
919
+ };
920
+ }
921
+ if (!url && !filePath) throw new Error("Either url, file_path, or image (base64/data URI) must be provided");
922
+ if (url && filePath) throw new Error("Provide only one of url or file_path");
923
+ if (url) {
924
+ const fetched = await getFeishuRuntime().channel.media.readRemoteMediaBuffer({
925
+ url,
926
+ maxBytes
927
+ });
928
+ const guessed = new URL(url).pathname.split("/").pop() || "upload.bin";
929
+ return {
930
+ buffer: fetched.buffer,
931
+ fileName: explicitFileName || guessed
932
+ };
933
+ }
934
+ const resolvedFilePath = resolve(filePath);
935
+ return {
936
+ buffer: (await getFeishuRuntime().media.loadWebMedia(resolvedFilePath, {
937
+ maxBytes,
938
+ optimizeImages: false,
939
+ localRoots
940
+ })).buffer,
941
+ fileName: explicitFileName || basename(filePath)
942
+ };
943
+ }
944
+ async function processImages(client, docToken, markdown, insertedBlocks, maxBytes) {
945
+ const imageUrls = extractImageUrls(markdown);
946
+ if (imageUrls.length === 0) return 0;
947
+ const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27);
948
+ let processed = 0;
949
+ for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
950
+ const url = imageUrls[i];
951
+ const blockId = imageBlocks[i]?.block_id;
952
+ if (!blockId) continue;
953
+ try {
954
+ const fileToken = await uploadImageToDocx(client, blockId, await downloadImage(url, maxBytes), new URL(url).pathname.split("/").pop() || `image_${i}.png`, docToken);
955
+ await client.docx.documentBlock.patch({
956
+ path: {
957
+ document_id: docToken,
958
+ block_id: blockId
959
+ },
960
+ data: { replace_image: { token: fileToken } }
961
+ });
962
+ processed++;
963
+ } catch (err) {
964
+ console.error(`Failed to process image ${url}:`, err);
965
+ }
966
+ }
967
+ return processed;
968
+ }
969
+ async function uploadImageBlock(client, docToken, maxBytes, localRoots, url, filePath, parentBlockId, filename, index, imageInput) {
970
+ const insertRes = await client.docx.documentBlockChildren.create({
971
+ path: {
972
+ document_id: docToken,
973
+ block_id: parentBlockId ?? docToken
974
+ },
975
+ params: { document_revision_id: -1 },
976
+ data: {
977
+ children: [{
978
+ block_type: 27,
979
+ image: {}
980
+ }],
981
+ index: index ?? -1
982
+ }
983
+ });
984
+ if (insertRes.code !== 0) throw new Error(`Failed to create image block: ${insertRes.msg}`);
985
+ const imageBlockId = insertRes.data?.children?.find((b) => b.block_type === 27)?.block_id;
986
+ if (!imageBlockId) throw new Error("Failed to create image block");
987
+ const upload = await resolveUploadInput(url, filePath, maxBytes, localRoots, filename, imageInput);
988
+ const fileToken = await uploadImageToDocx(client, imageBlockId, upload.buffer, upload.fileName, docToken);
989
+ const patchRes = await client.docx.documentBlock.patch({
990
+ path: {
991
+ document_id: docToken,
992
+ block_id: imageBlockId
993
+ },
994
+ data: { replace_image: { token: fileToken } }
995
+ });
996
+ if (patchRes.code !== 0) throw new Error(patchRes.msg);
997
+ return {
998
+ success: true,
999
+ block_id: imageBlockId,
1000
+ file_token: fileToken,
1001
+ file_name: upload.fileName,
1002
+ size: upload.buffer.length
1003
+ };
1004
+ }
1005
+ async function uploadFileBlock(client, docToken, maxBytes, localRoots, url, filePath, parentBlockId, filename) {
1006
+ const blockId = parentBlockId ?? docToken;
1007
+ const upload = await resolveUploadInput(url, filePath, maxBytes, localRoots, filename);
1008
+ const converted = await convertMarkdown(client, `[${upload.fileName}](https://example.com/placeholder)`);
1009
+ const { orderedBlocks } = normalizeConvertedBlockTree(converted.blocks, converted.firstLevelBlockIds);
1010
+ const { children: inserted } = await insertBlocks(client, docToken, orderedBlocks, blockId);
1011
+ const placeholderBlock = inserted[0];
1012
+ if (!placeholderBlock?.block_id) throw new Error("Failed to create placeholder block for file upload");
1013
+ const parentId = placeholderBlock.parent_id ?? blockId;
1014
+ const childrenRes = await client.docx.documentBlockChildren.get({ path: {
1015
+ document_id: docToken,
1016
+ block_id: parentId
1017
+ } });
1018
+ if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1019
+ const placeholderIdx = (childrenRes.data?.items ?? []).findIndex((item) => item.block_id === placeholderBlock.block_id);
1020
+ if (placeholderIdx >= 0) {
1021
+ const deleteRes = await client.docx.documentBlockChildren.batchDelete({
1022
+ path: {
1023
+ document_id: docToken,
1024
+ block_id: parentId
1025
+ },
1026
+ data: {
1027
+ start_index: placeholderIdx,
1028
+ end_index: placeholderIdx + 1
1029
+ }
1030
+ });
1031
+ if (deleteRes.code !== 0) throw new Error(deleteRes.msg);
1032
+ }
1033
+ const fileToken = (await client.drive.media.uploadAll({ data: {
1034
+ file_name: upload.fileName,
1035
+ parent_type: "docx_file",
1036
+ parent_node: docToken,
1037
+ size: upload.buffer.length,
1038
+ file: upload.buffer
1039
+ } }))?.file_token;
1040
+ if (!fileToken) throw new Error("File upload failed: no file_token returned");
1041
+ return {
1042
+ success: true,
1043
+ file_token: fileToken,
1044
+ file_name: upload.fileName,
1045
+ size: upload.buffer.length,
1046
+ note: "File uploaded to drive. Use the file_token to reference it. Direct file block creation is not supported by the Feishu API."
1047
+ };
1048
+ }
1049
+ const STRUCTURED_BLOCK_TYPES = new Set([
1050
+ 14,
1051
+ 18,
1052
+ 21,
1053
+ 23,
1054
+ 27,
1055
+ 30,
1056
+ 31,
1057
+ 32
1058
+ ]);
1059
+ async function readDoc(client, docToken) {
1060
+ const [contentRes, infoRes, blocksRes] = await Promise.all([
1061
+ client.docx.document.rawContent({ path: { document_id: docToken } }),
1062
+ client.docx.document.get({ path: { document_id: docToken } }),
1063
+ client.docx.documentBlock.list({ path: { document_id: docToken } })
1064
+ ]);
1065
+ if (contentRes.code !== 0) throw new Error(contentRes.msg);
1066
+ const blocks = blocksRes.data?.items ?? [];
1067
+ const blockCounts = {};
1068
+ const structuredTypes = [];
1069
+ for (const b of blocks) {
1070
+ const type = b.block_type ?? 0;
1071
+ const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
1072
+ blockCounts[name] = (blockCounts[name] || 0) + 1;
1073
+ if (STRUCTURED_BLOCK_TYPES.has(type) && !structuredTypes.includes(name)) structuredTypes.push(name);
1074
+ }
1075
+ let hint;
1076
+ if (structuredTypes.length > 0) 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.`;
1077
+ return {
1078
+ title: infoRes.data?.document?.title,
1079
+ content: contentRes.data?.content,
1080
+ revision_id: infoRes.data?.document?.revision_id,
1081
+ block_count: blocks.length,
1082
+ block_types: blockCounts,
1083
+ ...hint && { hint }
1084
+ };
1085
+ }
1086
+ async function createDoc(client, title, folderToken, options) {
1087
+ const res = await client.docx.document.create({ data: {
1088
+ title,
1089
+ folder_token: folderToken
1090
+ } });
1091
+ if (res.code !== 0) throw new Error(res.msg);
1092
+ const doc = res.data?.document;
1093
+ const docToken = doc?.document_id;
1094
+ if (!docToken) throw new Error("Document creation succeeded but no document_id was returned");
1095
+ const shouldGrantToRequester = options?.grantToRequester !== false;
1096
+ const requesterOpenId = options?.requesterOpenId?.trim();
1097
+ const requesterPermType = "edit";
1098
+ let requesterPermissionAdded = false;
1099
+ let requesterPermissionSkippedReason;
1100
+ let requesterPermissionError;
1101
+ if (shouldGrantToRequester) if (!requesterOpenId) requesterPermissionSkippedReason = "trusted requester identity unavailable";
1102
+ else try {
1103
+ await client.drive.permissionMember.create({
1104
+ path: { token: docToken },
1105
+ params: {
1106
+ type: "docx",
1107
+ need_notification: false
1108
+ },
1109
+ data: {
1110
+ member_type: "openid",
1111
+ member_id: requesterOpenId,
1112
+ perm: requesterPermType
1113
+ }
1114
+ });
1115
+ requesterPermissionAdded = true;
1116
+ } catch (err) {
1117
+ requesterPermissionError = formatErrorMessage(err);
1118
+ }
1119
+ return {
1120
+ document_id: docToken,
1121
+ title: doc?.title,
1122
+ url: `https://feishu.cn/docx/${docToken}`,
1123
+ ...shouldGrantToRequester && {
1124
+ requester_permission_added: requesterPermissionAdded,
1125
+ ...requesterOpenId && { requester_open_id: requesterOpenId },
1126
+ requester_perm_type: requesterPermType,
1127
+ ...requesterPermissionSkippedReason && { requester_permission_skipped_reason: requesterPermissionSkippedReason },
1128
+ ...requesterPermissionError && { requester_permission_error: requesterPermissionError }
1129
+ }
1130
+ };
1131
+ }
1132
+ async function writeDoc(client, docToken, markdown, maxBytes, logger) {
1133
+ const deleted = await clearDocumentContent(client, docToken);
1134
+ logger?.info?.("feishu_doc: Converting markdown...");
1135
+ const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
1136
+ if (blocks.length === 0) return {
1137
+ success: true,
1138
+ blocks_deleted: deleted,
1139
+ blocks_added: 0,
1140
+ images_processed: 0
1141
+ };
1142
+ logger?.info?.(`feishu_doc: Converted to ${blocks.length} blocks, inserting...`);
1143
+ const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
1144
+ const { children: inserted } = blocks.length > 1e3 ? await insertBlocksInBatches(client, docToken, orderedBlocks, rootIds, logger) : await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds);
1145
+ const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
1146
+ logger?.info?.(`feishu_doc: Done (${blocks.length} blocks, ${imagesProcessed} images)`);
1147
+ return {
1148
+ success: true,
1149
+ blocks_deleted: deleted,
1150
+ blocks_added: blocks.length,
1151
+ images_processed: imagesProcessed
1152
+ };
1153
+ }
1154
+ async function appendDoc(client, docToken, markdown, maxBytes, logger) {
1155
+ logger?.info?.("feishu_doc: Converting markdown...");
1156
+ const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
1157
+ if (blocks.length === 0) throw new Error("Content is empty");
1158
+ logger?.info?.(`feishu_doc: Converted to ${blocks.length} blocks, inserting...`);
1159
+ const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
1160
+ const { children: inserted } = blocks.length > 1e3 ? await insertBlocksInBatches(client, docToken, orderedBlocks, rootIds, logger) : await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds);
1161
+ const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
1162
+ logger?.info?.(`feishu_doc: Done (${blocks.length} blocks, ${imagesProcessed} images)`);
1163
+ return {
1164
+ success: true,
1165
+ blocks_added: blocks.length,
1166
+ images_processed: imagesProcessed,
1167
+ block_ids: inserted.map((b) => b.block_id)
1168
+ };
1169
+ }
1170
+ async function insertDoc(client, docToken, markdown, afterBlockId, maxBytes, logger) {
1171
+ const blockInfo = await client.docx.documentBlock.get({ path: {
1172
+ document_id: docToken,
1173
+ block_id: afterBlockId
1174
+ } });
1175
+ if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1176
+ const parentId = blockInfo.data?.block?.parent_id ?? docToken;
1177
+ const items = [];
1178
+ let pageToken;
1179
+ do {
1180
+ const childrenRes = await client.docx.documentBlockChildren.get({
1181
+ path: {
1182
+ document_id: docToken,
1183
+ block_id: parentId
1184
+ },
1185
+ params: pageToken ? { page_token: pageToken } : {}
1186
+ });
1187
+ if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1188
+ items.push(...childrenRes.data?.items ?? []);
1189
+ pageToken = childrenRes.data?.page_token ?? void 0;
1190
+ } while (pageToken);
1191
+ const blockIndex = items.findIndex((item) => item.block_id === afterBlockId);
1192
+ if (blockIndex === -1) throw new Error(`after_block_id "${afterBlockId}" was not found among the children of parent block "${parentId}". Use list_blocks to verify the block ID.`);
1193
+ const insertIndex = blockIndex + 1;
1194
+ logger?.info?.("feishu_doc: Converting markdown...");
1195
+ const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
1196
+ if (blocks.length === 0) throw new Error("Content is empty");
1197
+ const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
1198
+ logger?.info?.(`feishu_doc: Converted to ${blocks.length} blocks, inserting at index ${insertIndex}...`);
1199
+ const { children: inserted } = blocks.length > 1e3 ? await insertBlocksInBatches(client, docToken, orderedBlocks, rootIds, logger, parentId, insertIndex) : await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds, {
1200
+ parentBlockId: parentId,
1201
+ index: insertIndex
1202
+ });
1203
+ const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
1204
+ logger?.info?.(`feishu_doc: Done (${blocks.length} blocks, ${imagesProcessed} images)`);
1205
+ return {
1206
+ success: true,
1207
+ blocks_added: blocks.length,
1208
+ images_processed: imagesProcessed,
1209
+ block_ids: inserted.map((b) => b.block_id)
1210
+ };
1211
+ }
1212
+ async function createTable(client, docToken, rowSize, columnSize, parentBlockId, columnWidth) {
1213
+ if (columnWidth && columnWidth.length !== columnSize) throw new Error("column_width length must equal column_size");
1214
+ const blockId = parentBlockId ?? docToken;
1215
+ const res = await client.docx.documentBlockChildren.create({
1216
+ path: {
1217
+ document_id: docToken,
1218
+ block_id: blockId
1219
+ },
1220
+ data: { children: [{
1221
+ block_type: 31,
1222
+ table: { property: {
1223
+ row_size: rowSize,
1224
+ column_size: columnSize,
1225
+ ...columnWidth && columnWidth.length > 0 ? { column_width: columnWidth } : {}
1226
+ } }
1227
+ }] }
1228
+ });
1229
+ if (res.code !== 0) throw new Error(res.msg);
1230
+ const tableBlock = res.data?.children?.find((b) => b.block_type === 31);
1231
+ const cells = normalizeInsertedChildBlocks(tableBlock?.children);
1232
+ return {
1233
+ success: true,
1234
+ table_block_id: tableBlock?.block_id,
1235
+ row_size: rowSize,
1236
+ column_size: columnSize,
1237
+ table_cell_block_ids: cells.map((c) => c.block_id).filter(Boolean),
1238
+ raw_children_count: res.data?.children?.length ?? 0
1239
+ };
1240
+ }
1241
+ async function writeTableCells(client, docToken, tableBlockId, values) {
1242
+ if (!values.length || !values[0]?.length) throw new Error("values must be a non-empty 2D array");
1243
+ const tableRes = await client.docx.documentBlock.get({ path: {
1244
+ document_id: docToken,
1245
+ block_id: tableBlockId
1246
+ } });
1247
+ if (tableRes.code !== 0) throw new Error(tableRes.msg);
1248
+ const tableBlock = tableRes.data?.block;
1249
+ if (tableBlock?.block_type !== 31) throw new Error("table_block_id is not a table block");
1250
+ const tableData = tableBlock.table;
1251
+ const rows = tableData?.property?.row_size;
1252
+ const cols = tableData?.property?.column_size;
1253
+ const cellIds = tableData?.cells ?? [];
1254
+ if (!rows || !cols || !cellIds.length) throw new Error("Table cell IDs unavailable from table block. Use list_blocks/get_block and pass explicit cell block IDs if needed.");
1255
+ const writeRows = Math.min(values.length, rows);
1256
+ let written = 0;
1257
+ for (let r = 0; r < writeRows; r++) {
1258
+ const rowValues = values[r] ?? [];
1259
+ const writeCols = Math.min(rowValues.length, cols);
1260
+ for (let c = 0; c < writeCols; c++) {
1261
+ const cellId = cellIds[r * cols + c];
1262
+ if (!cellId) continue;
1263
+ const childrenRes = await client.docx.documentBlockChildren.get({ path: {
1264
+ document_id: docToken,
1265
+ block_id: cellId
1266
+ } });
1267
+ if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1268
+ const existingChildren = childrenRes.data?.items ?? [];
1269
+ if (existingChildren.length > 0) {
1270
+ const delRes = await client.docx.documentBlockChildren.batchDelete({
1271
+ path: {
1272
+ document_id: docToken,
1273
+ block_id: cellId
1274
+ },
1275
+ data: {
1276
+ start_index: 0,
1277
+ end_index: existingChildren.length
1278
+ }
1279
+ });
1280
+ if (delRes.code !== 0) throw new Error(delRes.msg);
1281
+ }
1282
+ const converted = await convertMarkdown(client, rowValues[c] ?? "");
1283
+ const { orderedBlocks } = normalizeConvertedBlockTree(converted.blocks, converted.firstLevelBlockIds);
1284
+ if (orderedBlocks.length > 0) await insertBlocks(client, docToken, orderedBlocks, cellId);
1285
+ written++;
1286
+ }
1287
+ }
1288
+ return {
1289
+ success: true,
1290
+ table_block_id: tableBlockId,
1291
+ cells_written: written,
1292
+ table_size: {
1293
+ rows,
1294
+ cols
1295
+ }
1296
+ };
1297
+ }
1298
+ async function createTableWithValues(client, docToken, rowSize, columnSize, values, parentBlockId, columnWidth) {
1299
+ const tableBlockId = (await createTable(client, docToken, rowSize, columnSize, parentBlockId, columnWidth)).table_block_id;
1300
+ if (!tableBlockId) throw new Error("create_table succeeded but table_block_id is missing");
1301
+ return {
1302
+ success: true,
1303
+ table_block_id: tableBlockId,
1304
+ row_size: rowSize,
1305
+ column_size: columnSize,
1306
+ cells_written: (await writeTableCells(client, docToken, tableBlockId, values)).cells_written
1307
+ };
1308
+ }
1309
+ async function updateBlock(client, docToken, blockId, content) {
1310
+ const blockInfo = await client.docx.documentBlock.get({ path: {
1311
+ document_id: docToken,
1312
+ block_id: blockId
1313
+ } });
1314
+ if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1315
+ const res = await client.docx.documentBlock.patch({
1316
+ path: {
1317
+ document_id: docToken,
1318
+ block_id: blockId
1319
+ },
1320
+ data: { update_text_elements: { elements: [{ text_run: { content } }] } }
1321
+ });
1322
+ if (res.code !== 0) throw new Error(res.msg);
1323
+ return {
1324
+ success: true,
1325
+ block_id: blockId
1326
+ };
1327
+ }
1328
+ async function deleteBlock(client, docToken, blockId) {
1329
+ const blockInfo = await client.docx.documentBlock.get({ path: {
1330
+ document_id: docToken,
1331
+ block_id: blockId
1332
+ } });
1333
+ if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1334
+ const parentId = blockInfo.data?.block?.parent_id ?? docToken;
1335
+ const children = await client.docx.documentBlockChildren.get({ path: {
1336
+ document_id: docToken,
1337
+ block_id: parentId
1338
+ } });
1339
+ if (children.code !== 0) throw new Error(children.msg);
1340
+ const index = (children.data?.items ?? []).findIndex((item) => item.block_id === blockId);
1341
+ if (index === -1) throw new Error("Block not found");
1342
+ const res = await client.docx.documentBlockChildren.batchDelete({
1343
+ path: {
1344
+ document_id: docToken,
1345
+ block_id: parentId
1346
+ },
1347
+ data: {
1348
+ start_index: index,
1349
+ end_index: index + 1
1350
+ }
1351
+ });
1352
+ if (res.code !== 0) throw new Error(res.msg);
1353
+ return {
1354
+ success: true,
1355
+ deleted_block_id: blockId
1356
+ };
1357
+ }
1358
+ async function listBlocks(client, docToken) {
1359
+ const res = await client.docx.documentBlock.list({ path: { document_id: docToken } });
1360
+ if (res.code !== 0) throw new Error(res.msg);
1361
+ return { blocks: res.data?.items ?? [] };
1362
+ }
1363
+ async function getBlock(client, docToken, blockId) {
1364
+ const res = await client.docx.documentBlock.get({ path: {
1365
+ document_id: docToken,
1366
+ block_id: blockId
1367
+ } });
1368
+ if (res.code !== 0) throw new Error(res.msg);
1369
+ return { block: res.data?.block };
1370
+ }
1371
+ async function listAppScopes(client) {
1372
+ const res = await client.application.scope.list({});
1373
+ if (res.code !== 0) throw new Error(res.msg);
1374
+ const scopes = res.data?.scopes ?? [];
1375
+ const granted = scopes.filter((s) => s.grant_status === 1);
1376
+ const pending = scopes.filter((s) => s.grant_status !== 1);
1377
+ return {
1378
+ granted: granted.map((s) => ({
1379
+ name: s.scope_name,
1380
+ type: s.scope_type
1381
+ })),
1382
+ pending: pending.map((s) => ({
1383
+ name: s.scope_name,
1384
+ type: s.scope_type
1385
+ })),
1386
+ summary: `${granted.length} granted, ${pending.length} pending`
1387
+ };
1388
+ }
1389
+ function registerFeishuDocTools(api) {
1390
+ if (!api.config) return;
1391
+ const accounts = listEnabledFeishuAccounts(api.config);
1392
+ if (accounts.length === 0) return;
1393
+ const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
1394
+ const registered = [];
1395
+ const getClient = (params, defaultAccountId) => createFeishuToolClient({
1396
+ api,
1397
+ executeParams: params,
1398
+ defaultAccountId
1399
+ });
1400
+ const getMediaMaxBytes = (params, defaultAccountId) => (resolveFeishuToolAccount({
1401
+ api,
1402
+ executeParams: params,
1403
+ defaultAccountId
1404
+ }).config?.mediaMaxMb ?? 30) * 1024 * 1024;
1405
+ if (toolsCfg.doc) {
1406
+ api.registerTool((ctx) => {
1407
+ const defaultAccountId = ctx.agentAccountId;
1408
+ const mediaLocalRoots = resolveDocToolLocalRoots(ctx);
1409
+ const trustedRequesterOpenId = ctx.messageChannel === "feishu" ? normalizeOptionalString(ctx.requesterSenderId) : void 0;
1410
+ return {
1411
+ name: "feishu_doc",
1412
+ label: "Feishu Doc",
1413
+ description: "Feishu document operations. Actions: 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",
1414
+ parameters: FeishuDocSchema,
1415
+ async execute(_toolCallId, params) {
1416
+ const p = params;
1417
+ try {
1418
+ const client = getClient(p, defaultAccountId);
1419
+ switch (p.action) {
1420
+ case "read": return json$1(await readDoc(client, p.doc_token));
1421
+ case "write": return json$1(await writeDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), api.logger));
1422
+ case "append": return json$1(await appendDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), api.logger));
1423
+ case "insert": return json$1(await insertDoc(client, p.doc_token, p.content, p.after_block_id, getMediaMaxBytes(p, defaultAccountId), api.logger));
1424
+ case "create": return json$1(await createDoc(client, p.title, p.folder_token, {
1425
+ grantToRequester: p.grant_to_requester,
1426
+ requesterOpenId: trustedRequesterOpenId
1427
+ }));
1428
+ case "list_blocks": return json$1(await listBlocks(client, p.doc_token));
1429
+ case "get_block": return json$1(await getBlock(client, p.doc_token, p.block_id));
1430
+ case "update_block": return json$1(await updateBlock(client, p.doc_token, p.block_id, p.content));
1431
+ case "delete_block": return json$1(await deleteBlock(client, p.doc_token, p.block_id));
1432
+ case "create_table": return json$1(await createTable(client, p.doc_token, p.row_size, p.column_size, p.parent_block_id, p.column_width));
1433
+ case "write_table_cells": return json$1(await writeTableCells(client, p.doc_token, p.table_block_id, p.values));
1434
+ case "create_table_with_values": return json$1(await createTableWithValues(client, p.doc_token, p.row_size, p.column_size, p.values, p.parent_block_id, p.column_width));
1435
+ case "upload_image": return json$1(await uploadImageBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename, p.index, p.image));
1436
+ case "upload_file": return json$1(await uploadFileBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename));
1437
+ case "color_text": return json$1(await updateColorText(client, p.doc_token, p.block_id, p.content));
1438
+ case "insert_table_row": return json$1(await insertTableRow(client, p.doc_token, p.block_id, p.row_index));
1439
+ case "insert_table_column": return json$1(await insertTableColumn(client, p.doc_token, p.block_id, p.column_index));
1440
+ case "delete_table_rows": return json$1(await deleteTableRows(client, p.doc_token, p.block_id, p.row_start, p.row_count));
1441
+ case "delete_table_columns": return json$1(await deleteTableColumns(client, p.doc_token, p.block_id, p.column_start, p.column_count));
1442
+ case "merge_table_cells": return json$1(await mergeTableCells(client, p.doc_token, p.block_id, p.row_start, p.row_end, p.column_start, p.column_end));
1443
+ default: return json$1({ error: "Unknown action" });
1444
+ }
1445
+ } catch (err) {
1446
+ return json$1({ error: formatErrorMessage(err) });
1447
+ }
1448
+ }
1449
+ };
1450
+ }, { name: "feishu_doc" });
1451
+ registered.push("feishu_doc");
1452
+ }
1453
+ if (toolsCfg.scopes) {
1454
+ api.registerTool((ctx) => ({
1455
+ name: "feishu_app_scopes",
1456
+ label: "Feishu App Scopes",
1457
+ description: "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
1458
+ parameters: Type.Object({}),
1459
+ async execute() {
1460
+ try {
1461
+ return json$1(await listAppScopes(getClient(void 0, ctx.agentAccountId)));
1462
+ } catch (err) {
1463
+ return json$1({ error: formatErrorMessage(err) });
1464
+ }
1465
+ }
1466
+ }), { name: "feishu_app_scopes" });
1467
+ registered.push("feishu_app_scopes");
1468
+ }
1469
+ }
1470
+ //#endregion
1471
+ //#region extensions/feishu/src/wiki-schema.ts
1472
+ const WIKI_SPACE_ID_DESCRIPTION = "Knowledge space ID. Treat as an opaque string and keep it quoted; never pass numeric-looking IDs as numbers.";
1473
+ const FeishuWikiSchema = Type.Union([
1474
+ Type.Object({ action: Type.Literal("spaces") }),
1475
+ Type.Object({
1476
+ action: Type.Literal("nodes"),
1477
+ space_id: Type.String({ description: WIKI_SPACE_ID_DESCRIPTION }),
1478
+ parent_node_token: Type.Optional(Type.String({ description: "Parent node token (optional, omit for root)" }))
1479
+ }),
1480
+ Type.Object({
1481
+ action: Type.Literal("get"),
1482
+ token: Type.String({ description: "Wiki node token (from URL /wiki/XXX)" })
1483
+ }),
1484
+ Type.Object({
1485
+ action: Type.Literal("search"),
1486
+ query: Type.String({ description: "Search query" }),
1487
+ space_id: Type.Optional(Type.String({ description: "Limit search to this knowledge space. Treat as an opaque string and keep it quoted; never pass numeric-looking IDs as numbers." }))
1488
+ }),
1489
+ Type.Object({
1490
+ action: Type.Literal("create"),
1491
+ space_id: Type.String({ description: WIKI_SPACE_ID_DESCRIPTION }),
1492
+ title: Type.String({ description: "Node title" }),
1493
+ obj_type: Type.Optional(Type.Union([
1494
+ Type.Literal("docx"),
1495
+ Type.Literal("sheet"),
1496
+ Type.Literal("bitable")
1497
+ ], { description: "Object type (default: docx)" })),
1498
+ parent_node_token: Type.Optional(Type.String({ description: "Parent node token (optional, omit for root)" }))
1499
+ }),
1500
+ Type.Object({
1501
+ action: Type.Literal("move"),
1502
+ space_id: Type.String({ description: "Source knowledge space ID. Treat as an opaque string and keep it quoted; never pass numeric-looking IDs as numbers." }),
1503
+ node_token: Type.String({ description: "Node token to move" }),
1504
+ target_space_id: Type.Optional(Type.String({ description: "Target knowledge space ID (optional, same space if omitted). Treat as an opaque string and keep it quoted; never pass numeric-looking IDs as numbers." })),
1505
+ target_parent_token: Type.Optional(Type.String({ description: "Target parent node token (optional, root if omitted)" }))
1506
+ }),
1507
+ Type.Object({
1508
+ action: Type.Literal("rename"),
1509
+ space_id: Type.String({ description: WIKI_SPACE_ID_DESCRIPTION }),
1510
+ node_token: Type.String({ description: "Node token to rename" }),
1511
+ title: Type.String({ description: "New title" })
1512
+ })
1513
+ ]);
1514
+ //#endregion
1515
+ //#region extensions/feishu/src/wiki.ts
1516
+ 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";
1517
+ function requireWikiSpaceId(value, fieldName) {
1518
+ if (typeof value !== "string") throw new Error(`${fieldName} must be a string. Feishu wiki space IDs are opaque identifiers; pass them quoted to avoid JavaScript number precision loss.`);
1519
+ const trimmed = value.trim();
1520
+ if (!trimmed) throw new Error(`${fieldName} must not be empty.`);
1521
+ return trimmed;
1522
+ }
1523
+ function optionalWikiSpaceId(value, fieldName) {
1524
+ if (value === void 0 || value === null || value === "") return;
1525
+ return requireWikiSpaceId(value, fieldName);
1526
+ }
1527
+ async function listSpaces(client) {
1528
+ const res = await client.wiki.space.list({});
1529
+ if (res.code !== 0) throw new Error(res.msg);
1530
+ const spaces = res.data?.items?.map((s) => ({
1531
+ space_id: s.space_id,
1532
+ name: s.name,
1533
+ description: s.description,
1534
+ visibility: s.visibility
1535
+ })) ?? [];
1536
+ return {
1537
+ spaces,
1538
+ ...spaces.length === 0 && { hint: WIKI_ACCESS_HINT }
1539
+ };
1540
+ }
1541
+ async function listNodes(client, spaceId, parentNodeToken) {
1542
+ const res = await client.wiki.spaceNode.list({
1543
+ path: { space_id: spaceId },
1544
+ params: { parent_node_token: parentNodeToken }
1545
+ });
1546
+ if (res.code !== 0) throw new Error(res.msg);
1547
+ return { nodes: res.data?.items?.map((n) => ({
1548
+ node_token: n.node_token,
1549
+ obj_token: n.obj_token,
1550
+ obj_type: n.obj_type,
1551
+ title: n.title,
1552
+ has_child: n.has_child
1553
+ })) ?? [] };
1554
+ }
1555
+ async function getNode(client, token) {
1556
+ const res = await client.wiki.space.getNode({ params: { token } });
1557
+ if (res.code !== 0) throw new Error(res.msg);
1558
+ const node = res.data?.node;
1559
+ return {
1560
+ node_token: node?.node_token,
1561
+ space_id: node?.space_id,
1562
+ obj_token: node?.obj_token,
1563
+ obj_type: node?.obj_type,
1564
+ title: node?.title,
1565
+ parent_node_token: node?.parent_node_token,
1566
+ has_child: node?.has_child,
1567
+ creator: node?.creator,
1568
+ create_time: node?.node_create_time
1569
+ };
1570
+ }
1571
+ async function createNode(client, spaceId, title, objType, parentNodeToken) {
1572
+ const res = await client.wiki.spaceNode.create({
1573
+ path: { space_id: spaceId },
1574
+ data: {
1575
+ obj_type: objType || "docx",
1576
+ node_type: "origin",
1577
+ title,
1578
+ parent_node_token: parentNodeToken
1579
+ }
1580
+ });
1581
+ if (res.code !== 0) throw new Error(res.msg);
1582
+ const node = res.data?.node;
1583
+ return {
1584
+ node_token: node?.node_token,
1585
+ obj_token: node?.obj_token,
1586
+ obj_type: node?.obj_type,
1587
+ title: node?.title
1588
+ };
1589
+ }
1590
+ async function moveNode(client, spaceId, nodeToken, targetSpaceId, targetParentToken) {
1591
+ const res = await client.wiki.spaceNode.move({
1592
+ path: {
1593
+ space_id: spaceId,
1594
+ node_token: nodeToken
1595
+ },
1596
+ data: {
1597
+ target_space_id: targetSpaceId || spaceId,
1598
+ target_parent_token: targetParentToken
1599
+ }
1600
+ });
1601
+ if (res.code !== 0) throw new Error(res.msg);
1602
+ return {
1603
+ success: true,
1604
+ node_token: res.data?.node?.node_token
1605
+ };
1606
+ }
1607
+ async function renameNode(client, spaceId, nodeToken, title) {
1608
+ const res = await client.wiki.spaceNode.updateTitle({
1609
+ path: {
1610
+ space_id: spaceId,
1611
+ node_token: nodeToken
1612
+ },
1613
+ data: { title }
1614
+ });
1615
+ if (res.code !== 0) throw new Error(res.msg);
1616
+ return {
1617
+ success: true,
1618
+ node_token: nodeToken,
1619
+ title
1620
+ };
1621
+ }
1622
+ function registerFeishuWikiTools(api) {
1623
+ if (!api.config) return;
1624
+ const accounts = listEnabledFeishuAccounts(api.config);
1625
+ if (accounts.length === 0) return;
1626
+ if (!resolveAnyEnabledFeishuToolsConfig(accounts).wiki) return;
1627
+ api.registerTool((ctx) => {
1628
+ const defaultAccountId = ctx.agentAccountId;
1629
+ return {
1630
+ name: "feishu_wiki",
1631
+ label: "Feishu Wiki",
1632
+ description: "Feishu knowledge base operations. Actions: spaces, nodes, get, create, move, rename",
1633
+ parameters: FeishuWikiSchema,
1634
+ async execute(_toolCallId, params) {
1635
+ const p = params;
1636
+ try {
1637
+ const createClient = () => createFeishuToolClient({
1638
+ api,
1639
+ executeParams: p,
1640
+ defaultAccountId
1641
+ });
1642
+ switch (p.action) {
1643
+ case "spaces": return jsonToolResult(await listSpaces(createClient()));
1644
+ case "nodes": {
1645
+ const spaceId = requireWikiSpaceId(p.space_id, "space_id");
1646
+ return jsonToolResult(await listNodes(createClient(), spaceId, p.parent_node_token));
1647
+ }
1648
+ case "get": return jsonToolResult(await getNode(createClient(), p.token));
1649
+ case "search":
1650
+ optionalWikiSpaceId(p.space_id, "space_id");
1651
+ createClient();
1652
+ return jsonToolResult({ error: "Search is not available. Use feishu_wiki with action: 'nodes' to browse or action: 'get' to lookup by token." });
1653
+ case "create": {
1654
+ const spaceId = requireWikiSpaceId(p.space_id, "space_id");
1655
+ return jsonToolResult(await createNode(createClient(), spaceId, p.title, p.obj_type, p.parent_node_token));
1656
+ }
1657
+ case "move": {
1658
+ const spaceId = requireWikiSpaceId(p.space_id, "space_id");
1659
+ return jsonToolResult(await moveNode(createClient(), spaceId, p.node_token, optionalWikiSpaceId(p.target_space_id, "target_space_id"), p.target_parent_token));
1660
+ }
1661
+ case "rename": {
1662
+ const spaceId = requireWikiSpaceId(p.space_id, "space_id");
1663
+ return jsonToolResult(await renameNode(createClient(), spaceId, p.node_token, p.title));
1664
+ }
1665
+ default: return unknownToolActionResult(p.action);
1666
+ }
1667
+ } catch (err) {
1668
+ return toolExecutionErrorResult(err);
1669
+ }
1670
+ }
1671
+ };
1672
+ }, { name: "feishu_wiki" });
1673
+ }
1674
+ //#endregion
1675
+ //#region extensions/feishu/src/perm-schema.ts
1676
+ const TokenType = Type.Union([
1677
+ Type.Literal("doc"),
1678
+ Type.Literal("docx"),
1679
+ Type.Literal("sheet"),
1680
+ Type.Literal("bitable"),
1681
+ Type.Literal("folder"),
1682
+ Type.Literal("file"),
1683
+ Type.Literal("wiki"),
1684
+ Type.Literal("mindnote")
1685
+ ]);
1686
+ const MemberType = Type.Union([
1687
+ Type.Literal("email"),
1688
+ Type.Literal("openid"),
1689
+ Type.Literal("userid"),
1690
+ Type.Literal("unionid"),
1691
+ Type.Literal("openchat"),
1692
+ Type.Literal("opendepartmentid")
1693
+ ]);
1694
+ const Permission = Type.Union([
1695
+ Type.Literal("view"),
1696
+ Type.Literal("edit"),
1697
+ Type.Literal("full_access")
1698
+ ]);
1699
+ const FeishuPermSchema = Type.Union([
1700
+ Type.Object({
1701
+ action: Type.Literal("list"),
1702
+ token: Type.String({ description: "File token" }),
1703
+ type: TokenType
1704
+ }),
1705
+ Type.Object({
1706
+ action: Type.Literal("add"),
1707
+ token: Type.String({ description: "File token" }),
1708
+ type: TokenType,
1709
+ member_type: MemberType,
1710
+ member_id: Type.String({ description: "Member ID (email, open_id, user_id, etc.)" }),
1711
+ perm: Permission
1712
+ }),
1713
+ Type.Object({
1714
+ action: Type.Literal("remove"),
1715
+ token: Type.String({ description: "File token" }),
1716
+ type: TokenType,
1717
+ member_type: MemberType,
1718
+ member_id: Type.String({ description: "Member ID to remove" })
1719
+ })
1720
+ ]);
1721
+ //#endregion
1722
+ //#region extensions/feishu/src/perm.ts
1723
+ async function listMembers(client, token, type) {
1724
+ const res = await client.drive.permissionMember.list({
1725
+ path: { token },
1726
+ params: { type }
1727
+ });
1728
+ if (res.code !== 0) throw new Error(res.msg);
1729
+ return { members: res.data?.items?.map((m) => ({
1730
+ member_type: m.member_type,
1731
+ member_id: m.member_id,
1732
+ perm: m.perm,
1733
+ name: m.name
1734
+ })) ?? [] };
1735
+ }
1736
+ async function addMember(client, token, type, memberType, memberId, perm) {
1737
+ const res = await client.drive.permissionMember.create({
1738
+ path: { token },
1739
+ params: {
1740
+ type,
1741
+ need_notification: false
1742
+ },
1743
+ data: {
1744
+ member_type: memberType,
1745
+ member_id: memberId,
1746
+ perm
1747
+ }
1748
+ });
1749
+ if (res.code !== 0) throw new Error(res.msg);
1750
+ return {
1751
+ success: true,
1752
+ member: res.data?.member
1753
+ };
1754
+ }
1755
+ async function removeMember(client, token, type, memberType, memberId) {
1756
+ const res = await client.drive.permissionMember.delete({
1757
+ path: {
1758
+ token,
1759
+ member_id: memberId
1760
+ },
1761
+ params: {
1762
+ type,
1763
+ member_type: memberType
1764
+ }
1765
+ });
1766
+ if (res.code !== 0) throw new Error(res.msg);
1767
+ return { success: true };
1768
+ }
1769
+ function registerFeishuPermTools(api) {
1770
+ if (!api.config) return;
1771
+ const accounts = listEnabledFeishuAccounts(api.config);
1772
+ if (accounts.length === 0) return;
1773
+ if (!resolveAnyEnabledFeishuToolsConfig(accounts).perm) return;
1774
+ api.registerTool((ctx) => {
1775
+ const defaultAccountId = ctx.agentAccountId;
1776
+ return {
1777
+ name: "feishu_perm",
1778
+ label: "Feishu Perm",
1779
+ description: "Feishu permission management. Actions: list, add, remove",
1780
+ parameters: FeishuPermSchema,
1781
+ async execute(_toolCallId, params) {
1782
+ const p = params;
1783
+ try {
1784
+ const client = createFeishuToolClient({
1785
+ api,
1786
+ executeParams: p,
1787
+ defaultAccountId
1788
+ });
1789
+ switch (p.action) {
1790
+ case "list": return jsonToolResult(await listMembers(client, p.token, p.type));
1791
+ case "add": return jsonToolResult(await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm));
1792
+ case "remove": return jsonToolResult(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
1793
+ default: return unknownToolActionResult(p.action);
1794
+ }
1795
+ } catch (err) {
1796
+ return toolExecutionErrorResult(err);
1797
+ }
1798
+ }
1799
+ };
1800
+ }, { name: "feishu_perm" });
1801
+ }
1802
+ //#endregion
1803
+ //#region extensions/feishu/src/bitable.ts
1804
+ function json(data) {
1805
+ return {
1806
+ content: [{
1807
+ type: "text",
1808
+ text: JSON.stringify(data, null, 2)
1809
+ }],
1810
+ details: data
1811
+ };
1812
+ }
1813
+ var LarkApiError = class extends Error {
1814
+ constructor(code, message, api, context) {
1815
+ super(`[${api}] code=${code} message=${message}`);
1816
+ this.name = "LarkApiError";
1817
+ this.code = code;
1818
+ this.api = api;
1819
+ this.context = context;
1820
+ }
1821
+ };
1822
+ function ensureLarkSuccess(res, api, context) {
1823
+ if (res.code !== 0) throw new LarkApiError(res.code ?? -1, res.msg ?? "unknown error", api, context);
1824
+ }
1825
+ /** Field type ID to human-readable name */
1826
+ const FIELD_TYPE_NAMES = {
1827
+ 1: "Text",
1828
+ 2: "Number",
1829
+ 3: "SingleSelect",
1830
+ 4: "MultiSelect",
1831
+ 5: "DateTime",
1832
+ 7: "Checkbox",
1833
+ 11: "User",
1834
+ 13: "Phone",
1835
+ 15: "URL",
1836
+ 17: "Attachment",
1837
+ 18: "SingleLink",
1838
+ 19: "Lookup",
1839
+ 20: "Formula",
1840
+ 21: "DuplexLink",
1841
+ 22: "Location",
1842
+ 23: "GroupChat",
1843
+ 1001: "CreatedTime",
1844
+ 1002: "ModifiedTime",
1845
+ 1003: "CreatedUser",
1846
+ 1004: "ModifiedUser",
1847
+ 1005: "AutoNumber"
1848
+ };
1849
+ /** Parse bitable URL and extract tokens */
1850
+ function parseBitableUrl(url) {
1851
+ try {
1852
+ const u = new URL(url);
1853
+ const tableId = u.searchParams.get("table") ?? void 0;
1854
+ const wikiMatch = u.pathname.match(/\/wiki\/([A-Za-z0-9]+)/);
1855
+ if (wikiMatch) return {
1856
+ token: wikiMatch[1],
1857
+ tableId,
1858
+ isWiki: true
1859
+ };
1860
+ const baseMatch = u.pathname.match(/\/base\/([A-Za-z0-9]+)/);
1861
+ if (baseMatch) return {
1862
+ token: baseMatch[1],
1863
+ tableId,
1864
+ isWiki: false
1865
+ };
1866
+ return null;
1867
+ } catch {
1868
+ return null;
1869
+ }
1870
+ }
1871
+ /** Get app_token from wiki node_token */
1872
+ async function getAppTokenFromWiki(client, nodeToken) {
1873
+ const res = await client.wiki.space.getNode({ params: { token: nodeToken } });
1874
+ ensureLarkSuccess(res, "wiki.space.getNode", { nodeToken });
1875
+ const node = res.data?.node;
1876
+ if (!node) throw new Error("Node not found");
1877
+ if (node.obj_type !== "bitable") throw new Error(`Node is not a bitable (type: ${node.obj_type})`);
1878
+ return node.obj_token;
1879
+ }
1880
+ /** Get bitable metadata from URL (handles both /base/ and /wiki/ URLs) */
1881
+ async function getBitableMeta(client, url) {
1882
+ const parsed = parseBitableUrl(url);
1883
+ if (!parsed) throw new Error("Invalid URL format. Expected /base/XXX or /wiki/XXX URL");
1884
+ let appToken;
1885
+ if (parsed.isWiki) appToken = await getAppTokenFromWiki(client, parsed.token);
1886
+ else appToken = parsed.token;
1887
+ const res = await client.bitable.app.get({ path: { app_token: appToken } });
1888
+ ensureLarkSuccess(res, "bitable.app.get", { appToken });
1889
+ let tables = [];
1890
+ if (!parsed.tableId) {
1891
+ const tablesRes = await client.bitable.appTable.list({ path: { app_token: appToken } });
1892
+ if (tablesRes.code === 0) tables = (tablesRes.data?.items ?? []).map((t) => ({
1893
+ table_id: t.table_id,
1894
+ name: t.name
1895
+ }));
1896
+ }
1897
+ return {
1898
+ app_token: appToken,
1899
+ table_id: parsed.tableId,
1900
+ name: res.data?.app?.name,
1901
+ url_type: parsed.isWiki ? "wiki" : "base",
1902
+ ...tables.length > 0 && { tables },
1903
+ hint: parsed.tableId ? `Use app_token="${appToken}" and table_id="${parsed.tableId}" for other bitable tools` : `Use app_token="${appToken}" for other bitable tools. Select a table_id from the tables list.`
1904
+ };
1905
+ }
1906
+ async function listFields(client, appToken, tableId) {
1907
+ const res = await client.bitable.appTableField.list({ path: {
1908
+ app_token: appToken,
1909
+ table_id: tableId
1910
+ } });
1911
+ ensureLarkSuccess(res, "bitable.appTableField.list", {
1912
+ appToken,
1913
+ tableId
1914
+ });
1915
+ const fields = res.data?.items ?? [];
1916
+ return {
1917
+ fields: fields.map((f) => ({
1918
+ field_id: f.field_id,
1919
+ field_name: f.field_name,
1920
+ type: f.type,
1921
+ type_name: FIELD_TYPE_NAMES[f.type ?? 0] || `type_${f.type}`,
1922
+ is_primary: f.is_primary,
1923
+ ...f.property && { property: f.property }
1924
+ })),
1925
+ total: fields.length
1926
+ };
1927
+ }
1928
+ async function listRecords(client, appToken, tableId, pageSize, pageToken) {
1929
+ const res = await client.bitable.appTableRecord.list({
1930
+ path: {
1931
+ app_token: appToken,
1932
+ table_id: tableId
1933
+ },
1934
+ params: {
1935
+ page_size: pageSize ?? 100,
1936
+ ...pageToken && { page_token: pageToken }
1937
+ }
1938
+ });
1939
+ ensureLarkSuccess(res, "bitable.appTableRecord.list", {
1940
+ appToken,
1941
+ tableId,
1942
+ pageSize
1943
+ });
1944
+ return {
1945
+ records: res.data?.items ?? [],
1946
+ has_more: res.data?.has_more ?? false,
1947
+ page_token: res.data?.page_token,
1948
+ total: res.data?.total
1949
+ };
1950
+ }
1951
+ async function getRecord(client, appToken, tableId, recordId) {
1952
+ const res = await client.bitable.appTableRecord.get({ path: {
1953
+ app_token: appToken,
1954
+ table_id: tableId,
1955
+ record_id: recordId
1956
+ } });
1957
+ ensureLarkSuccess(res, "bitable.appTableRecord.get", {
1958
+ appToken,
1959
+ tableId,
1960
+ recordId
1961
+ });
1962
+ return { record: res.data?.record };
1963
+ }
1964
+ async function createRecord(client, appToken, tableId, fields) {
1965
+ const res = await client.bitable.appTableRecord.create({
1966
+ path: {
1967
+ app_token: appToken,
1968
+ table_id: tableId
1969
+ },
1970
+ data: { fields }
1971
+ });
1972
+ ensureLarkSuccess(res, "bitable.appTableRecord.create", {
1973
+ appToken,
1974
+ tableId
1975
+ });
1976
+ return { record: res.data?.record };
1977
+ }
1978
+ /** Default field types created for new Bitable tables (to be cleaned up) */
1979
+ const DEFAULT_CLEANUP_FIELD_TYPES = new Set([
1980
+ 3,
1981
+ 5,
1982
+ 17
1983
+ ]);
1984
+ function isDefaultEmptyBitableFieldValue(value) {
1985
+ if (value === void 0 || value === null || value === "") return true;
1986
+ if (Array.isArray(value)) return value.every(isDefaultEmptyBitableFieldValue);
1987
+ if (typeof value === "object") {
1988
+ const record = value;
1989
+ const keys = Object.keys(record);
1990
+ if (keys.length === 0) return true;
1991
+ if ("text" in record && keys.every((key) => key === "text" || key === "type")) return record.text === void 0 || record.text === null || record.text === "";
1992
+ return Object.values(record).every(isDefaultEmptyBitableFieldValue);
1993
+ }
1994
+ return false;
1995
+ }
1996
+ function isPlaceholderBitableRecord(fields) {
1997
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) return true;
1998
+ return Object.values(fields).every(isDefaultEmptyBitableFieldValue);
1999
+ }
2000
+ /** Clean up default placeholder rows and fields in a newly created Bitable table */
2001
+ async function cleanupNewBitable(client, appToken, tableId, tableName, logger) {
2002
+ let cleanedRows = 0;
2003
+ let cleanedFields = 0;
2004
+ const fieldsRes = await client.bitable.appTableField.list({ path: {
2005
+ app_token: appToken,
2006
+ table_id: tableId
2007
+ } });
2008
+ if (fieldsRes.code === 0 && fieldsRes.data?.items) {
2009
+ const primaryField = fieldsRes.data.items.find((f) => f.is_primary);
2010
+ if (primaryField?.field_id) try {
2011
+ const newFieldName = tableName.length <= 20 ? tableName : "Name";
2012
+ await client.bitable.appTableField.update({
2013
+ path: {
2014
+ app_token: appToken,
2015
+ table_id: tableId,
2016
+ field_id: primaryField.field_id
2017
+ },
2018
+ data: {
2019
+ field_name: newFieldName,
2020
+ type: 1
2021
+ }
2022
+ });
2023
+ cleanedFields++;
2024
+ } catch (err) {
2025
+ logger.debug(`Failed to rename primary field: ${String(err)}`);
2026
+ }
2027
+ const defaultFieldsToDelete = fieldsRes.data.items.filter((f) => !f.is_primary && DEFAULT_CLEANUP_FIELD_TYPES.has(f.type ?? 0));
2028
+ for (const field of defaultFieldsToDelete) if (field.field_id) try {
2029
+ await client.bitable.appTableField.delete({ path: {
2030
+ app_token: appToken,
2031
+ table_id: tableId,
2032
+ field_id: field.field_id
2033
+ } });
2034
+ cleanedFields++;
2035
+ } catch (err) {
2036
+ logger.debug(`Failed to delete default field ${field.field_name}: ${String(err)}`);
2037
+ }
2038
+ }
2039
+ const recordsRes = await client.bitable.appTableRecord.list({
2040
+ path: {
2041
+ app_token: appToken,
2042
+ table_id: tableId
2043
+ },
2044
+ params: { page_size: 100 }
2045
+ });
2046
+ if (recordsRes.code === 0 && recordsRes.data?.items) {
2047
+ const emptyRecordIds = recordsRes.data.items.filter((r) => isPlaceholderBitableRecord(r.fields)).map((r) => r.record_id).filter((id) => Boolean(id));
2048
+ if (emptyRecordIds.length > 0) try {
2049
+ await client.bitable.appTableRecord.batchDelete({
2050
+ path: {
2051
+ app_token: appToken,
2052
+ table_id: tableId
2053
+ },
2054
+ data: { records: emptyRecordIds }
2055
+ });
2056
+ cleanedRows = emptyRecordIds.length;
2057
+ } catch {
2058
+ for (const recordId of emptyRecordIds) try {
2059
+ await client.bitable.appTableRecord.delete({ path: {
2060
+ app_token: appToken,
2061
+ table_id: tableId,
2062
+ record_id: recordId
2063
+ } });
2064
+ cleanedRows++;
2065
+ } catch (err) {
2066
+ logger.debug(`Failed to delete empty row ${recordId}: ${String(err)}`);
2067
+ }
2068
+ }
2069
+ }
2070
+ return {
2071
+ cleanedRows,
2072
+ cleanedFields
2073
+ };
2074
+ }
2075
+ async function createApp(client, name, folderToken, logger) {
2076
+ const res = await client.bitable.app.create({ data: {
2077
+ name,
2078
+ ...folderToken && { folder_token: folderToken }
2079
+ } });
2080
+ ensureLarkSuccess(res, "bitable.app.create", {
2081
+ name,
2082
+ folderToken
2083
+ });
2084
+ const appToken = res.data?.app?.app_token;
2085
+ if (!appToken) throw new Error("Failed to create Bitable: no app_token returned");
2086
+ const log = logger ?? {
2087
+ debug: () => {},
2088
+ warn: () => {}
2089
+ };
2090
+ let tableId;
2091
+ let cleanedRows = 0;
2092
+ let cleanedFields = 0;
2093
+ try {
2094
+ const tablesRes = await client.bitable.appTable.list({ path: { app_token: appToken } });
2095
+ if (tablesRes.code === 0 && tablesRes.data?.items && tablesRes.data.items.length > 0) {
2096
+ tableId = tablesRes.data.items[0].table_id ?? void 0;
2097
+ if (tableId) {
2098
+ const cleanup = await cleanupNewBitable(client, appToken, tableId, name, log);
2099
+ cleanedRows = cleanup.cleanedRows;
2100
+ cleanedFields = cleanup.cleanedFields;
2101
+ }
2102
+ }
2103
+ } catch (err) {
2104
+ log.debug(`Cleanup failed (non-critical): ${String(err)}`);
2105
+ }
2106
+ return {
2107
+ app_token: appToken,
2108
+ table_id: tableId,
2109
+ name: res.data?.app?.name,
2110
+ url: res.data?.app?.url,
2111
+ cleaned_placeholder_rows: cleanedRows,
2112
+ cleaned_default_fields: cleanedFields,
2113
+ hint: tableId ? `Table created. Use app_token="${appToken}" and table_id="${tableId}" for other bitable tools.` : "Table created. Use feishu_bitable_get_meta to get table_id and field details."
2114
+ };
2115
+ }
2116
+ async function createField(client, appToken, tableId, fieldName, fieldType, property) {
2117
+ const res = await client.bitable.appTableField.create({
2118
+ path: {
2119
+ app_token: appToken,
2120
+ table_id: tableId
2121
+ },
2122
+ data: {
2123
+ field_name: fieldName,
2124
+ type: fieldType,
2125
+ ...property && { property }
2126
+ }
2127
+ });
2128
+ ensureLarkSuccess(res, "bitable.appTableField.create", {
2129
+ appToken,
2130
+ tableId,
2131
+ fieldName,
2132
+ fieldType
2133
+ });
2134
+ return {
2135
+ field_id: res.data?.field?.field_id,
2136
+ field_name: res.data?.field?.field_name,
2137
+ type: res.data?.field?.type,
2138
+ type_name: FIELD_TYPE_NAMES[res.data?.field?.type ?? 0] || `type_${res.data?.field?.type}`
2139
+ };
2140
+ }
2141
+ async function updateRecord(client, appToken, tableId, recordId, fields) {
2142
+ const res = await client.bitable.appTableRecord.update({
2143
+ path: {
2144
+ app_token: appToken,
2145
+ table_id: tableId,
2146
+ record_id: recordId
2147
+ },
2148
+ data: { fields }
2149
+ });
2150
+ ensureLarkSuccess(res, "bitable.appTableRecord.update", {
2151
+ appToken,
2152
+ tableId,
2153
+ recordId
2154
+ });
2155
+ return { record: res.data?.record };
2156
+ }
2157
+ const GetMetaSchema = Type.Object({ url: Type.String({ description: "Bitable URL. Supports both formats: /base/XXX?table=YYY or /wiki/XXX?table=YYY" }) });
2158
+ const ListFieldsSchema = Type.Object({
2159
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2160
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" })
2161
+ });
2162
+ const ListRecordsSchema = Type.Object({
2163
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2164
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2165
+ page_size: Type.Optional(Type.Number({
2166
+ description: "Number of records per page (1-500, default 100)",
2167
+ minimum: 1,
2168
+ maximum: 500
2169
+ })),
2170
+ page_token: Type.Optional(Type.String({ description: "Pagination token from previous response" }))
2171
+ });
2172
+ const GetRecordSchema = Type.Object({
2173
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2174
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2175
+ record_id: Type.String({ description: "Record ID to retrieve" })
2176
+ });
2177
+ const CreateRecordSchema = Type.Object({
2178
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2179
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2180
+ fields: Type.Record(Type.String(), Type.Any(), { description: "Field values keyed by field name. Format by type: Text='string', Number=123, SingleSelect='Option', MultiSelect=['A','B'], DateTime=timestamp_ms, User=[{id:'ou_xxx'}], URL={text:'Display',link:'https://...'}" })
2181
+ });
2182
+ const CreateAppSchema = Type.Object({
2183
+ name: Type.String({ description: "Name for the new Bitable application" }),
2184
+ folder_token: Type.Optional(Type.String({ description: "Optional folder token to place the Bitable in a specific folder" }))
2185
+ });
2186
+ const CreateFieldSchema = Type.Object({
2187
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL, or feishu_bitable_create_app to create new)" }),
2188
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2189
+ field_name: Type.String({ description: "Name for the new field" }),
2190
+ field_type: Type.Number({
2191
+ description: "Field type ID: 1=Text, 2=Number, 3=SingleSelect, 4=MultiSelect, 5=DateTime, 7=Checkbox, 11=User, 13=Phone, 15=URL, 17=Attachment, 18=SingleLink, 19=Lookup, 20=Formula, 21=DuplexLink, 22=Location, 23=GroupChat, 1001=CreatedTime, 1002=ModifiedTime, 1003=CreatedUser, 1004=ModifiedUser, 1005=AutoNumber",
2192
+ minimum: 1
2193
+ }),
2194
+ property: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "Field-specific properties (e.g., options for SingleSelect, format for Number)" }))
2195
+ });
2196
+ const UpdateRecordSchema = Type.Object({
2197
+ app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2198
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2199
+ record_id: Type.String({ description: "Record ID to update" }),
2200
+ fields: Type.Record(Type.String(), Type.Any(), { description: "Field values to update (same format as create_record)" })
2201
+ });
2202
+ function registerFeishuBitableTools(api) {
2203
+ if (!api.config) return;
2204
+ if (listEnabledFeishuAccounts(api.config).length === 0) return;
2205
+ const getClient = (params, defaultAccountId) => createFeishuToolClient({
2206
+ api,
2207
+ executeParams: params,
2208
+ defaultAccountId
2209
+ });
2210
+ const registerBitableTool = (params) => {
2211
+ api.registerTool((ctx) => ({
2212
+ name: params.name,
2213
+ label: params.label,
2214
+ description: params.description,
2215
+ parameters: params.parameters,
2216
+ async execute(_toolCallId, rawParams) {
2217
+ try {
2218
+ return json(await params.execute({
2219
+ params: rawParams,
2220
+ defaultAccountId: ctx.agentAccountId
2221
+ }));
2222
+ } catch (err) {
2223
+ return json({ error: formatErrorMessage(err) });
2224
+ }
2225
+ }
2226
+ }), { name: params.name });
2227
+ };
2228
+ registerBitableTool({
2229
+ name: "feishu_bitable_get_meta",
2230
+ label: "Feishu Bitable Get Meta",
2231
+ description: "Parse a Bitable URL and get app_token, table_id, and table list. Use this first when given a /wiki/ or /base/ URL.",
2232
+ parameters: GetMetaSchema,
2233
+ async execute({ params, defaultAccountId }) {
2234
+ return getBitableMeta(getClient(params, defaultAccountId), params.url);
2235
+ }
2236
+ });
2237
+ registerBitableTool({
2238
+ name: "feishu_bitable_list_fields",
2239
+ label: "Feishu Bitable List Fields",
2240
+ description: "List all fields (columns) in a Bitable table with their types and properties",
2241
+ parameters: ListFieldsSchema,
2242
+ async execute({ params, defaultAccountId }) {
2243
+ return listFields(getClient(params, defaultAccountId), params.app_token, params.table_id);
2244
+ }
2245
+ });
2246
+ registerBitableTool({
2247
+ name: "feishu_bitable_list_records",
2248
+ label: "Feishu Bitable List Records",
2249
+ description: "List records (rows) from a Bitable table with pagination support",
2250
+ parameters: ListRecordsSchema,
2251
+ async execute({ params, defaultAccountId }) {
2252
+ return listRecords(getClient(params, defaultAccountId), params.app_token, params.table_id, params.page_size, params.page_token);
2253
+ }
2254
+ });
2255
+ registerBitableTool({
2256
+ name: "feishu_bitable_get_record",
2257
+ label: "Feishu Bitable Get Record",
2258
+ description: "Get a single record by ID from a Bitable table",
2259
+ parameters: GetRecordSchema,
2260
+ async execute({ params, defaultAccountId }) {
2261
+ return getRecord(getClient(params, defaultAccountId), params.app_token, params.table_id, params.record_id);
2262
+ }
2263
+ });
2264
+ registerBitableTool({
2265
+ name: "feishu_bitable_create_record",
2266
+ label: "Feishu Bitable Create Record",
2267
+ description: "Create a new record (row) in a Bitable table",
2268
+ parameters: CreateRecordSchema,
2269
+ async execute({ params, defaultAccountId }) {
2270
+ return createRecord(getClient(params, defaultAccountId), params.app_token, params.table_id, params.fields);
2271
+ }
2272
+ });
2273
+ registerBitableTool({
2274
+ name: "feishu_bitable_update_record",
2275
+ label: "Feishu Bitable Update Record",
2276
+ description: "Update an existing record (row) in a Bitable table",
2277
+ parameters: UpdateRecordSchema,
2278
+ async execute({ params, defaultAccountId }) {
2279
+ return updateRecord(getClient(params, defaultAccountId), params.app_token, params.table_id, params.record_id, params.fields);
2280
+ }
2281
+ });
2282
+ registerBitableTool({
2283
+ name: "feishu_bitable_create_app",
2284
+ label: "Feishu Bitable Create App",
2285
+ description: "Create a new Bitable (multidimensional table) application",
2286
+ parameters: CreateAppSchema,
2287
+ async execute({ params, defaultAccountId }) {
2288
+ return createApp(getClient(params, defaultAccountId), params.name, params.folder_token, {
2289
+ debug: (msg) => api.logger.debug?.(msg),
2290
+ warn: (msg) => api.logger.warn?.(msg)
2291
+ });
2292
+ }
2293
+ });
2294
+ registerBitableTool({
2295
+ name: "feishu_bitable_create_field",
2296
+ label: "Feishu Bitable Create Field",
2297
+ description: "Create a new field (column) in a Bitable table",
2298
+ parameters: CreateFieldSchema,
2299
+ async execute({ params, defaultAccountId }) {
2300
+ return createField(getClient(params, defaultAccountId), params.app_token, params.table_id, params.field_name, params.field_type, params.property);
2301
+ }
2302
+ });
2303
+ }
2304
+ //#endregion
2305
+ //#region extensions/feishu/api.ts
2306
+ const feishuSessionBindingAdapterChannels = ["feishu"];
2307
+ //#endregion
2308
+ export { testing as __testing, testing as feishuThreadBindingTesting, testing, buildFeishuConversationId, buildFeishuModelOverrideParentCandidates, createClackPrompter, createFeishuThreadBindingManager, feishuPlugin, feishuSessionBindingAdapterChannels, feishuSetupAdapter, feishuSetupWizard, getFeishuThreadBindingManager, handleFeishuSubagentDeliveryTarget, handleFeishuSubagentEnded, handleFeishuSubagentSpawning, parseFeishuConversationId, parseFeishuDirectConversationId, parseFeishuTargetId, registerFeishuBitableTools, registerFeishuChatTools, registerFeishuDocTools, registerFeishuDriveTools, registerFeishuPermTools, registerFeishuWikiTools, runFeishuLogin, setFeishuNamedAccountEnabled };