opencode-feishu 1.3.0 → 1.3.2

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
  */
@@ -112818,6 +112842,10 @@ function createSendCardTool(deps) {
112818
112842
  async execute(args, context) {
112819
112843
  const chatId = getChatIdBySession(context.sessionID);
112820
112844
  if (!chatId) {
112845
+ deps.log("warn", "Agent \u5361\u7247\u53D1\u9001\u8DF3\u8FC7\uFF1AsessionID \u65E0\u98DE\u4E66\u804A\u5929\u6620\u5C04", {
112846
+ sessionId: context.sessionID,
112847
+ title: args.title
112848
+ });
112821
112849
  return "\u9519\u8BEF\uFF1A\u5F53\u524D\u4F1A\u8BDD\u4E0D\u5173\u8054\u98DE\u4E66\u804A\u5929\uFF0C\u65E0\u6CD5\u53D1\u9001\u5361\u7247";
112822
112850
  }
112823
112851
  const chatInfo = getChatInfoBySession(context.sessionID);
@@ -113062,8 +113090,7 @@ function isDuplicate(messageId) {
113062
113090
  }
113063
113091
 
113064
113092
  // src/feishu/resource.ts
113065
- var MAX_RESOURCE_SIZE = 10 * 1024 * 1024;
113066
- async function downloadMessageResource(client, messageId, fileKey, type, log) {
113093
+ async function downloadMessageResource(client, messageId, fileKey, type, log, maxSize) {
113067
113094
  try {
113068
113095
  const res = await client.im.messageResource.get({
113069
113096
  path: { message_id: messageId, file_key: fileKey },
@@ -113071,7 +113098,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113071
113098
  });
113072
113099
  if (!res) {
113073
113100
  log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u8FD4\u56DE\u7A7A\u6570\u636E", { messageId, fileKey, type });
113074
- return null;
113101
+ return { resource: null, reason: "error" };
113075
113102
  }
113076
113103
  const stream4 = res.getReadableStream();
113077
113104
  const chunks = [];
@@ -113079,10 +113106,10 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113079
113106
  for await (const chunk of stream4) {
113080
113107
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
113081
113108
  totalSize += buf.length;
113082
- if (totalSize > MAX_RESOURCE_SIZE) {
113083
- log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize });
113109
+ if (totalSize > maxSize) {
113110
+ log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize, maxSize });
113084
113111
  stream4.destroy();
113085
- return null;
113112
+ return { resource: null, reason: "too_large", totalSize };
113086
113113
  }
113087
113114
  chunks.push(buf);
113088
113115
  }
@@ -113091,7 +113118,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113091
113118
  const contentType = headers?.["content-type"] ?? guessMimeByType(type);
113092
113119
  const base643 = buffer.toString("base64");
113093
113120
  const dataUrl = `data:${contentType};base64,${base643}`;
113094
- return { dataUrl, mime: contentType };
113121
+ return { resource: { dataUrl, mime: contentType }, reason: "ok" };
113095
113122
  } catch (err) {
113096
113123
  log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u5931\u8D25", {
113097
113124
  messageId,
@@ -113099,7 +113126,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
113099
113126
  type,
113100
113127
  error: err instanceof Error ? err.message : String(err)
113101
113128
  });
113102
- return null;
113129
+ return { resource: null, reason: "error" };
113103
113130
  }
113104
113131
  }
113105
113132
  function guessMimeByType(type) {
@@ -113139,19 +113166,19 @@ function guessMimeByFilename(filename) {
113139
113166
  }
113140
113167
 
113141
113168
  // src/feishu/content-extractor.ts
113142
- async function extractParts(feishuClient, messageId, messageType, rawContent, log) {
113169
+ async function extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize) {
113143
113170
  try {
113144
113171
  switch (messageType) {
113145
113172
  case "text":
113146
113173
  return extractText(rawContent);
113147
113174
  case "image":
113148
- return await extractImage(feishuClient, messageId, rawContent, log);
113175
+ return await extractImage(feishuClient, messageId, rawContent, log, maxResourceSize);
113149
113176
  case "post":
113150
113177
  return extractPost(rawContent);
113151
113178
  case "file":
113152
- return await extractFile(feishuClient, messageId, rawContent, log);
113179
+ return await extractFile(feishuClient, messageId, rawContent, log, maxResourceSize);
113153
113180
  case "audio":
113154
- return await extractAudio(feishuClient, messageId, rawContent, log);
113181
+ return await extractAudio(feishuClient, messageId, rawContent, log, maxResourceSize);
113155
113182
  case "media":
113156
113183
  return extractMediaFallback();
113157
113184
  case "sticker":
@@ -113222,13 +113249,15 @@ function extractText(rawContent) {
113222
113249
  if (!text) return [];
113223
113250
  return [{ type: "text", text }];
113224
113251
  }
113225
- async function extractImage(client, messageId, rawContent, log) {
113252
+ async function extractImage(client, messageId, rawContent, log, maxResourceSize) {
113226
113253
  const parsed = JSON.parse(rawContent);
113227
113254
  const imageKey = parsed.image_key;
113228
113255
  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 }];
113256
+ const result = await downloadMessageResource(client, messageId, imageKey, "image", log, maxResourceSize);
113257
+ if (!result.resource) {
113258
+ return [{ type: "text", text: formatDownloadFailure("\u56FE\u7247", result, maxResourceSize) }];
113259
+ }
113260
+ return [{ type: "file", mime: result.resource.mime, url: result.resource.dataUrl }];
113232
113261
  }
113233
113262
  function extractPost(rawContent) {
113234
113263
  const text = extractPostText(rawContent);
@@ -113263,27 +113292,39 @@ function extractPostText(rawContent) {
113263
113292
  return "";
113264
113293
  }
113265
113294
  }
113266
- async function extractFile(client, messageId, rawContent, log) {
113295
+ async function extractFile(client, messageId, rawContent, log, maxResourceSize) {
113267
113296
  const parsed = JSON.parse(rawContent);
113268
113297
  const fileKey = parsed.file_key;
113269
113298
  const fileName = parsed.file_name ?? "\u672A\u77E5\u6587\u4EF6";
113270
113299
  if (!fileKey) return [{ type: "text", text: `[\u6587\u4EF6: ${fileName}]` }];
113271
113300
  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 }];
113301
+ const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
113302
+ if (!result.resource) {
113303
+ return [{ type: "text", text: formatDownloadFailure(fileName, result, maxResourceSize) }];
113304
+ }
113305
+ return [{ type: "file", mime: result.resource.mime || mime, url: result.resource.dataUrl, filename: fileName }];
113275
113306
  }
113276
- async function extractAudio(client, messageId, rawContent, log) {
113307
+ async function extractAudio(client, messageId, rawContent, log, maxResourceSize) {
113277
113308
  const parsed = JSON.parse(rawContent);
113278
113309
  const fileKey = parsed.file_key;
113279
113310
  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 }];
113311
+ const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
113312
+ if (!result.resource) {
113313
+ return [{ type: "text", text: formatDownloadFailure("\u8BED\u97F3", result, maxResourceSize) }];
113314
+ }
113315
+ return [{ type: "file", mime: result.resource.mime || "audio/opus", url: result.resource.dataUrl }];
113283
113316
  }
113284
113317
  function extractMediaFallback() {
113285
113318
  return [{ type: "text", text: "[\u89C6\u9891\u6D88\u606F]" }];
113286
113319
  }
113320
+ function formatDownloadFailure(label, result, maxSize) {
113321
+ if (result.reason === "too_large" && result.totalSize) {
113322
+ const sizeMB = (result.totalSize / (1024 * 1024)).toFixed(1);
113323
+ const limitMB = (maxSize / (1024 * 1024)).toFixed(0);
113324
+ return `[\u6587\u4EF6\u8FC7\u5927: ${label}, \u5DF2\u4E0B\u8F7D ${sizeMB}MB \u65F6\u8D85\u51FA ${limitMB}MB \u9650\u5236]`;
113325
+ }
113326
+ return `[\u4E0B\u8F7D\u5931\u8D25: ${label}]`;
113327
+ }
113287
113328
  function extractInteractive(rawContent) {
113288
113329
  try {
113289
113330
  const parsed = JSON.parse(rawContent);
@@ -113905,6 +113946,7 @@ var StreamingCard = class {
113905
113946
  textBuffer = "";
113906
113947
  toolStates = /* @__PURE__ */ new Map();
113907
113948
  closed = false;
113949
+ toolsElementAdded = false;
113908
113950
  /**
113909
113951
  * 创建卡片 + 发送 interactive 消息 → messageId
113910
113952
  */
@@ -113912,15 +113954,14 @@ var StreamingCard = class {
113912
113954
  const schema = {
113913
113955
  data: {
113914
113956
  schema: "2.0",
113915
- config: { streaming_mode: true, summary: { content: "\u6B63\u5728\u601D\u8003..." } },
113957
+ config: { streaming_mode: true },
113916
113958
  header: {
113917
113959
  title: { tag: "plain_text", content: "AI \u56DE\u590D" },
113918
113960
  template: "blue"
113919
113961
  },
113920
113962
  body: {
113921
113963
  elements: [
113922
- { tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." },
113923
- { tag: "markdown", element_id: "tools", content: "" }
113964
+ { tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." }
113924
113965
  ]
113925
113966
  }
113926
113967
  }
@@ -114010,12 +114051,22 @@ var StreamingCard = class {
114010
114051
  const icon = ts.state === "completed" ? "\u2705" : ts.state === "error" ? "\u274C" : "\u{1F504}";
114011
114052
  lines.push(`${icon} ${ts.tool}`);
114012
114053
  }
114013
- await this.cardkit.updateElement(
114014
- this.cardId,
114015
- "tools",
114016
- lines.join("\n"),
114017
- ++this.seq
114018
- );
114054
+ const content = lines.join("\n");
114055
+ if (!this.toolsElementAdded) {
114056
+ this.toolsElementAdded = true;
114057
+ await this.cardkit.addElement(
114058
+ this.cardId,
114059
+ [{ tag: "markdown", element_id: "tools", content }],
114060
+ ++this.seq
114061
+ );
114062
+ } else {
114063
+ await this.cardkit.updateElement(
114064
+ this.cardId,
114065
+ "tools",
114066
+ content,
114067
+ ++this.seq
114068
+ );
114069
+ }
114019
114070
  }
114020
114071
  };
114021
114072
 
@@ -114043,7 +114094,7 @@ async function handleChat(ctx, deps, signal) {
114043
114094
  const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
114044
114095
  const session = await getOrCreateSession(client, sessionKey, directory);
114045
114096
  registerSessionChat(session.id, chatId, chatType);
114046
- const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
114097
+ const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log, config2.maxResourceSize);
114047
114098
  if (!parts.length) return void 0;
114048
114099
  log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
114049
114100
  sessionKey,
@@ -114217,7 +114268,7 @@ async function handleChat(ctx, deps, signal) {
114217
114268
  unregisterPending(activeSessionId);
114218
114269
  }
114219
114270
  }
114220
- async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
114271
+ async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log, maxResourceSize) {
114221
114272
  if (messageType === "text") {
114222
114273
  let promptText = textContent;
114223
114274
  if (chatType === "group" && senderId) {
@@ -114225,7 +114276,7 @@ async function buildPromptParts(feishuClient, messageId, messageType, rawContent
114225
114276
  }
114226
114277
  return [{ type: "text", text: promptText }];
114227
114278
  }
114228
- const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log);
114279
+ const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize);
114229
114280
  if (chatType === "group" && senderId && parts.length > 0) {
114230
114281
  return [{ type: "text", text: `[${senderId}]:` }, ...parts];
114231
114282
  }