@milerliu/feishu 0.1.17 → 0.2.0

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/send.ts +105 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milerliu/feishu",
3
- "version": "0.1.17",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
package/src/send.ts CHANGED
@@ -18,8 +18,8 @@ export type FeishuMessageInfo = {
18
18
 
19
19
  /**
20
20
  * Send text with streaming effect using Feishu streaming update card.
21
- * This creates an initial card and then streams updates to it.
22
21
  * Uses the official Feishu streaming update card API.
22
+ * Reference: https://open.feishu.cn/document/cardkit-v1/streaming-updates-openapi-overview
23
23
  */
24
24
  export async function sendStreamingMessageFeishu(params: {
25
25
  cfg: ClawdbotConfig;
@@ -48,34 +48,76 @@ export async function sendStreamingMessageFeishu(params: {
48
48
  rawText = buildMentionedMessage(mentions, rawText);
49
49
  }
50
50
 
51
- // Create interactive card with streaming_update enabled
52
- const card = {
53
- header: {
54
- title: {
55
- tag: "plain_text",
56
- content: "AI 回复",
51
+ // Create streaming card according to official docs
52
+ // Reference: https://open.feishu.cn/document/cardkit-v1/streaming-updates-openapi-overview
53
+ const streamingCard = {
54
+ schema: "2.0",
55
+ config: {
56
+ streaming_mode: true,
57
+ streaming_config: {
58
+ print_frequency_ms: {
59
+ default: 30,
60
+ },
61
+ print_step: {
62
+ default: 2,
63
+ },
64
+ print_strategy: "fast",
65
+ },
66
+ summary: {
67
+ content: "AI 正在生成内容...",
57
68
  },
58
69
  },
59
- config: {
60
- wide_screen_mode: true,
61
- streaming_update: true,
70
+ body: {
71
+ elements: [
72
+ {
73
+ tag: "markdown",
74
+ content: "",
75
+ element_id: "ai_response",
76
+ },
77
+ ],
62
78
  },
63
- elements: [
64
- {
65
- tag: "markdown",
66
- content: rawText,
67
- },
68
- ],
69
79
  };
70
80
 
71
- const cardContent = JSON.stringify(card);
81
+ const cardContent = JSON.stringify(streamingCard);
82
+
83
+ // Step 1: Create card instance
84
+ let cardResponse;
85
+ try {
86
+ cardResponse = await (client as any).cardkit.v1.card.create({
87
+ data: {
88
+ type: "adaptive",
89
+ data: cardContent,
90
+ },
91
+ });
92
+ } catch (error) {
93
+ console.error("Failed to create streaming card:", error);
94
+ // Fallback to regular card
95
+ const fallbackCard = buildMarkdownCard(rawText);
96
+ return sendCardFeishu({ cfg, to, card: fallbackCard });
97
+ }
98
+
99
+ if (cardResponse.code !== 0) {
100
+ console.error(`Card creation failed: ${cardResponse.msg}`);
101
+ // Fallback to regular card
102
+ const fallbackCard = buildMarkdownCard(rawText);
103
+ return sendCardFeishu({ cfg, to, card: fallbackCard });
104
+ }
72
105
 
73
- // Create the card message
106
+ const cardId = cardResponse.data?.card_id;
107
+ if (!cardId) {
108
+ console.error("No card_id returned");
109
+ // Fallback to regular card
110
+ const fallbackCard = buildMarkdownCard(rawText);
111
+ return sendCardFeishu({ cfg, to, card: fallbackCard });
112
+ }
113
+
114
+ // Step 2: Send card to chat
115
+ const messageContent = JSON.stringify({ card_id: cardId });
74
116
  const messageResponse = await client.im.message.create({
75
117
  params: { receive_id_type: receiveIdType },
76
118
  data: {
77
119
  receive_id: receiveId,
78
- content: cardContent,
120
+ content: messageContent,
79
121
  msg_type: "interactive",
80
122
  },
81
123
  });
@@ -86,17 +128,56 @@ export async function sendStreamingMessageFeishu(params: {
86
128
 
87
129
  const messageId = messageResponse.data?.message_id ?? "unknown";
88
130
 
89
- // Now update the message multiple times for streaming effect
90
- // Use im.v1.message.update API
91
- const chunkSize = 500;
131
+ // Step 3: Stream updates using batch_update
132
+ // Split text into chunks for streaming effect
133
+ const chunkSize = 10; // Small chunks for streaming effect
92
134
  const chunks: string[] = [];
93
135
  for (let i = 0; i < rawText.length; i += chunkSize) {
94
136
  chunks.push(rawText.slice(i, i + chunkSize));
95
137
  }
96
138
 
97
- // Send updates with delay
139
+ let cumulativeContent = "";
140
+ let sequence = 0;
141
+
98
142
  for (let i = 0; i < chunks.length; i++) {
99
- const updateCard = {
143
+ cumulativeContent += chunks[i];
144
+ sequence = i;
145
+
146
+ const actions = JSON.stringify([
147
+ {
148
+ action_name: "update_content",
149
+ action_param: {
150
+ tag: "markdown",
151
+ content: cumulativeContent,
152
+ element_id: "ai_response",
153
+ },
154
+ },
155
+ ]);
156
+
157
+ try {
158
+ await (client as any).cardkit.v1.card.batch_update({
159
+ path: { card_id: cardId },
160
+ data: {
161
+ uuid: `stream-${Date.now()}-${i}`,
162
+ sequence,
163
+ actions,
164
+ },
165
+ });
166
+
167
+ // Small delay between updates for streaming effect
168
+ if (i < chunks.length - 1) {
169
+ await new Promise((resolve) => setTimeout(resolve, 50));
170
+ }
171
+ } catch (error) {
172
+ console.error(`Streaming update failed at chunk ${i}: ${error}`);
173
+ }
174
+ }
175
+
176
+ return {
177
+ messageId,
178
+ chatId: receiveId,
179
+ };
180
+ }
100
181
  header: {
101
182
  title: {
102
183
  tag: "plain_text",