opencode-feishu 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -112525,6 +112525,7 @@ var FeishuConfigSchema = external_exports.object({
112525
112525
  pollInterval: external_exports.number().int().positive().default(1e3),
112526
112526
  stablePolls: external_exports.number().int().positive().default(3),
112527
112527
  dedupTtl: external_exports.number().int().positive().default(10 * 60 * 1e3),
112528
+ maxResourceSize: external_exports.number().int().positive().max(500 * 1024 * 1024).default(500 * 1024 * 1024),
112528
112529
  directory: external_exports.string().optional(),
112529
112530
  autoPrompt: AutoPromptSchema.default(() => AutoPromptSchema.parse({}))
112530
112531
  });
@@ -112602,6 +112603,29 @@ var CardKitClient = class {
112602
112603
  });
112603
112604
  }
112604
112605
  }
112606
+ /**
112607
+ * 在卡片末尾追加新组件(用于流式卡片动态添加元素)
112608
+ */
112609
+ async addElement(cardId, elements, sequence) {
112610
+ const res = await this.larkClient.cardkit.v1.cardElement.create({
112611
+ data: {
112612
+ type: "append",
112613
+ elements: JSON.stringify(elements),
112614
+ sequence
112615
+ },
112616
+ path: {
112617
+ card_id: cardId
112618
+ }
112619
+ });
112620
+ if (res?.code !== 0) {
112621
+ this.log?.("warn", "CardKit addElement \u5931\u8D25", {
112622
+ cardId,
112623
+ code: res?.code,
112624
+ msg: res?.msg
112625
+ });
112626
+ throw new Error(`CardKit addElement \u5931\u8D25: ${res?.msg ?? "unknown"} (code: ${res?.code})`);
112627
+ }
112628
+ }
112605
112629
  /**
112606
112630
  * 关闭卡片流式模式
112607
112631
  */
@@ -113062,8 +113086,7 @@ function isDuplicate(messageId) {
113062
113086
  }
113063
113087
 
113064
113088
  // src/feishu/resource.ts
113065
- var MAX_RESOURCE_SIZE = 10 * 1024 * 1024;
113066
- async function downloadMessageResource(client, messageId, fileKey, type, log) {
113089
+ async function downloadMessageResource(client, messageId, fileKey, type, log, maxSize) {
113067
113090
  try {
113068
113091
  const res = await client.im.messageResource.get({
113069
113092
  path: { message_id: messageId, file_key: fileKey },
@@ -113071,7 +113094,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113071
113094
  });
113072
113095
  if (!res) {
113073
113096
  log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u8FD4\u56DE\u7A7A\u6570\u636E", { messageId, fileKey, type });
113074
- return null;
113097
+ return { resource: null, reason: "error" };
113075
113098
  }
113076
113099
  const stream4 = res.getReadableStream();
113077
113100
  const chunks = [];
@@ -113079,10 +113102,10 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113079
113102
  for await (const chunk of stream4) {
113080
113103
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
113081
113104
  totalSize += buf.length;
113082
- if (totalSize > MAX_RESOURCE_SIZE) {
113083
- log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize });
113105
+ if (totalSize > maxSize) {
113106
+ log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize, maxSize });
113084
113107
  stream4.destroy();
113085
- return null;
113108
+ return { resource: null, reason: "too_large", totalSize };
113086
113109
  }
113087
113110
  chunks.push(buf);
113088
113111
  }
@@ -113091,7 +113114,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113091
113114
  const contentType = headers?.["content-type"] ?? guessMimeByType(type);
113092
113115
  const base643 = buffer.toString("base64");
113093
113116
  const dataUrl = `data:${contentType};base64,${base643}`;
113094
- return { dataUrl, mime: contentType };
113117
+ return { resource: { dataUrl, mime: contentType }, reason: "ok" };
113095
113118
  } catch (err) {
113096
113119
  log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u5931\u8D25", {
113097
113120
  messageId,
@@ -113099,7 +113122,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113099
113122
  type,
113100
113123
  error: err instanceof Error ? err.message : String(err)
113101
113124
  });
113102
- return null;
113125
+ return { resource: null, reason: "error" };
113103
113126
  }
113104
113127
  }
113105
113128
  function guessMimeByType(type) {
@@ -113139,19 +113162,19 @@ function guessMimeByFilename(filename) {
113139
113162
  }
113140
113163
 
113141
113164
  // src/feishu/content-extractor.ts
113142
- async function extractParts(feishuClient, messageId, messageType, rawContent, log) {
113165
+ async function extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize) {
113143
113166
  try {
113144
113167
  switch (messageType) {
113145
113168
  case "text":
113146
113169
  return extractText(rawContent);
113147
113170
  case "image":
113148
- return await extractImage(feishuClient, messageId, rawContent, log);
113171
+ return await extractImage(feishuClient, messageId, rawContent, log, maxResourceSize);
113149
113172
  case "post":
113150
113173
  return extractPost(rawContent);
113151
113174
  case "file":
113152
- return await extractFile(feishuClient, messageId, rawContent, log);
113175
+ return await extractFile(feishuClient, messageId, rawContent, log, maxResourceSize);
113153
113176
  case "audio":
113154
- return await extractAudio(feishuClient, messageId, rawContent, log);
113177
+ return await extractAudio(feishuClient, messageId, rawContent, log, maxResourceSize);
113155
113178
  case "media":
113156
113179
  return extractMediaFallback();
113157
113180
  case "sticker":
@@ -113222,13 +113245,15 @@ function extractText(rawContent) {
113222
113245
  if (!text) return [];
113223
113246
  return [{ type: "text", text }];
113224
113247
  }
113225
- async function extractImage(client, messageId, rawContent, log) {
113248
+ async function extractImage(client, messageId, rawContent, log, maxResourceSize) {
113226
113249
  const parsed = JSON.parse(rawContent);
113227
113250
  const imageKey = parsed.image_key;
113228
113251
  if (!imageKey) return [{ type: "text", text: "[\u56FE\u7247: \u65E0\u6CD5\u83B7\u53D6]" }];
113229
- const resource = await downloadMessageResource(client, messageId, imageKey, "image", log);
113230
- if (!resource) return [{ type: "text", text: "[\u56FE\u7247: \u4E0B\u8F7D\u5931\u8D25]" }];
113231
- return [{ type: "file", mime: resource.mime, url: resource.dataUrl }];
113252
+ const result = await downloadMessageResource(client, messageId, imageKey, "image", log, maxResourceSize);
113253
+ if (!result.resource) {
113254
+ return [{ type: "text", text: formatDownloadFailure("\u56FE\u7247", result, maxResourceSize) }];
113255
+ }
113256
+ return [{ type: "file", mime: result.resource.mime, url: result.resource.dataUrl }];
113232
113257
  }
113233
113258
  function extractPost(rawContent) {
113234
113259
  const text = extractPostText(rawContent);
@@ -113263,27 +113288,39 @@ function extractPostText(rawContent) {
113263
113288
  return "";
113264
113289
  }
113265
113290
  }
113266
- async function extractFile(client, messageId, rawContent, log) {
113291
+ async function extractFile(client, messageId, rawContent, log, maxResourceSize) {
113267
113292
  const parsed = JSON.parse(rawContent);
113268
113293
  const fileKey = parsed.file_key;
113269
113294
  const fileName = parsed.file_name ?? "\u672A\u77E5\u6587\u4EF6";
113270
113295
  if (!fileKey) return [{ type: "text", text: `[\u6587\u4EF6: ${fileName}]` }];
113271
113296
  const mime = guessMimeByFilename(fileName);
113272
- const resource = await downloadMessageResource(client, messageId, fileKey, "file", log);
113273
- if (!resource) return [{ type: "text", text: `[\u6587\u4EF6\u4E0B\u8F7D\u5931\u8D25: ${fileName}]` }];
113274
- return [{ type: "file", mime: resource.mime || mime, url: resource.dataUrl, filename: fileName }];
113297
+ const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
113298
+ if (!result.resource) {
113299
+ return [{ type: "text", text: formatDownloadFailure(fileName, result, maxResourceSize) }];
113300
+ }
113301
+ return [{ type: "file", mime: result.resource.mime || mime, url: result.resource.dataUrl, filename: fileName }];
113275
113302
  }
113276
- async function extractAudio(client, messageId, rawContent, log) {
113303
+ async function extractAudio(client, messageId, rawContent, log, maxResourceSize) {
113277
113304
  const parsed = JSON.parse(rawContent);
113278
113305
  const fileKey = parsed.file_key;
113279
113306
  if (!fileKey) return [{ type: "text", text: "[\u8BED\u97F3: \u65E0\u6CD5\u83B7\u53D6]" }];
113280
- const resource = await downloadMessageResource(client, messageId, fileKey, "file", log);
113281
- if (!resource) return [{ type: "text", text: "[\u8BED\u97F3: \u4E0B\u8F7D\u5931\u8D25]" }];
113282
- return [{ type: "file", mime: resource.mime || "audio/opus", url: resource.dataUrl }];
113307
+ const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
113308
+ if (!result.resource) {
113309
+ return [{ type: "text", text: formatDownloadFailure("\u8BED\u97F3", result, maxResourceSize) }];
113310
+ }
113311
+ return [{ type: "file", mime: result.resource.mime || "audio/opus", url: result.resource.dataUrl }];
113283
113312
  }
113284
113313
  function extractMediaFallback() {
113285
113314
  return [{ type: "text", text: "[\u89C6\u9891\u6D88\u606F]" }];
113286
113315
  }
113316
+ function formatDownloadFailure(label, result, maxSize) {
113317
+ if (result.reason === "too_large" && result.totalSize) {
113318
+ const sizeMB = (result.totalSize / (1024 * 1024)).toFixed(1);
113319
+ const limitMB = (maxSize / (1024 * 1024)).toFixed(0);
113320
+ return `[\u6587\u4EF6\u8FC7\u5927: ${label}, \u5DF2\u4E0B\u8F7D ${sizeMB}MB \u65F6\u8D85\u51FA ${limitMB}MB \u9650\u5236]`;
113321
+ }
113322
+ return `[\u4E0B\u8F7D\u5931\u8D25: ${label}]`;
113323
+ }
113287
113324
  function extractInteractive(rawContent) {
113288
113325
  try {
113289
113326
  const parsed = JSON.parse(rawContent);
@@ -113905,6 +113942,7 @@ var StreamingCard = class {
113905
113942
  textBuffer = "";
113906
113943
  toolStates = /* @__PURE__ */ new Map();
113907
113944
  closed = false;
113945
+ toolsElementAdded = false;
113908
113946
  /**
113909
113947
  * 创建卡片 + 发送 interactive 消息 → messageId
113910
113948
  */
@@ -113912,15 +113950,14 @@ var StreamingCard = class {
113912
113950
  const schema = {
113913
113951
  data: {
113914
113952
  schema: "2.0",
113915
- config: { streaming_mode: true, summary: { content: "\u6B63\u5728\u601D\u8003..." } },
113953
+ config: { streaming_mode: true },
113916
113954
  header: {
113917
113955
  title: { tag: "plain_text", content: "AI \u56DE\u590D" },
113918
113956
  template: "blue"
113919
113957
  },
113920
113958
  body: {
113921
113959
  elements: [
113922
- { tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." },
113923
- { tag: "markdown", element_id: "tools", content: "" }
113960
+ { tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." }
113924
113961
  ]
113925
113962
  }
113926
113963
  }
@@ -114010,12 +114047,22 @@ var StreamingCard = class {
114010
114047
  const icon = ts.state === "completed" ? "\u2705" : ts.state === "error" ? "\u274C" : "\u{1F504}";
114011
114048
  lines.push(`${icon} ${ts.tool}`);
114012
114049
  }
114013
- await this.cardkit.updateElement(
114014
- this.cardId,
114015
- "tools",
114016
- lines.join("\n"),
114017
- ++this.seq
114018
- );
114050
+ const content = lines.join("\n");
114051
+ if (!this.toolsElementAdded) {
114052
+ this.toolsElementAdded = true;
114053
+ await this.cardkit.addElement(
114054
+ this.cardId,
114055
+ [{ tag: "markdown", element_id: "tools", content }],
114056
+ ++this.seq
114057
+ );
114058
+ } else {
114059
+ await this.cardkit.updateElement(
114060
+ this.cardId,
114061
+ "tools",
114062
+ content,
114063
+ ++this.seq
114064
+ );
114065
+ }
114019
114066
  }
114020
114067
  };
114021
114068
 
@@ -114043,7 +114090,7 @@ async function handleChat(ctx, deps, signal) {
114043
114090
  const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
114044
114091
  const session = await getOrCreateSession(client, sessionKey, directory);
114045
114092
  registerSessionChat(session.id, chatId, chatType);
114046
- const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
114093
+ const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log, config2.maxResourceSize);
114047
114094
  if (!parts.length) return void 0;
114048
114095
  log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
114049
114096
  sessionKey,
@@ -114217,7 +114264,7 @@ async function handleChat(ctx, deps, signal) {
114217
114264
  unregisterPending(activeSessionId);
114218
114265
  }
114219
114266
  }
114220
- async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
114267
+ async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log, maxResourceSize) {
114221
114268
  if (messageType === "text") {
114222
114269
  let promptText = textContent;
114223
114270
  if (chatType === "group" && senderId) {
@@ -114225,7 +114272,7 @@ async function buildPromptParts(feishuClient, messageId, messageType, rawContent
114225
114272
  }
114226
114273
  return [{ type: "text", text: promptText }];
114227
114274
  }
114228
- const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log);
114275
+ const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize);
114229
114276
  if (chatType === "group" && senderId && parts.length > 0) {
114230
114277
  return [{ type: "text", text: `[${senderId}]:` }, ...parts];
114231
114278
  }