opencode-feishu 0.6.1 → 0.7.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.
package/dist/index.js CHANGED
@@ -99176,76 +99176,27 @@ async function updateMessage(client, messageId, text2) {
99176
99176
  }
99177
99177
  }
99178
99178
 
99179
- // src/handler/event.ts
99180
- var pendingBySession = /* @__PURE__ */ new Map();
99181
- var sessionErrors = /* @__PURE__ */ new Map();
99182
- function trackSessionError(sessionId, error) {
99183
- if (error.includes("ModelNotFound") || error.includes("ProviderModelNotFound")) {
99184
- sessionErrors.set(sessionId, error);
99179
+ // src/session.ts
99180
+ var SESSION_KEY_PREFIX = "feishu";
99181
+ var TITLE_PREFIX = "Feishu";
99182
+ var sessionCache = /* @__PURE__ */ new Map();
99183
+ var sessionIdToKeyCache = /* @__PURE__ */ new Map();
99184
+ function setCachedSession(sessionKey, session) {
99185
+ const oldSession = sessionCache.get(sessionKey);
99186
+ if (oldSession && oldSession.id !== session.id) {
99187
+ sessionIdToKeyCache.delete(oldSession.id);
99185
99188
  }
99189
+ sessionCache.set(sessionKey, session);
99190
+ sessionIdToKeyCache.set(session.id, sessionKey);
99186
99191
  }
99187
- function consumeSessionError(sessionId) {
99188
- const err = sessionErrors.get(sessionId);
99189
- if (err) sessionErrors.delete(sessionId);
99190
- return err;
99191
- }
99192
- function registerPending(sessionId, payload) {
99193
- pendingBySession.set(sessionId, { ...payload, textBuffer: "" });
99194
- }
99195
- function unregisterPending(sessionId) {
99196
- pendingBySession.delete(sessionId);
99197
- }
99198
- async function handleEvent(event) {
99199
- switch (event.type) {
99200
- case "message.part.updated": {
99201
- const part = event.properties.part;
99202
- if (!part) break;
99203
- const sessionId = part.sessionID;
99204
- if (!sessionId) break;
99205
- const payload = pendingBySession.get(sessionId);
99206
- if (!payload) break;
99207
- const delta = event.properties.delta;
99208
- if (delta) {
99209
- payload.textBuffer += delta;
99210
- } else {
99211
- const fullText = extractPartText(part);
99212
- if (fullText) {
99213
- payload.textBuffer = fullText;
99214
- }
99215
- }
99216
- if (payload.textBuffer) {
99217
- const res = await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
99218
- if (!res.ok) ;
99219
- }
99220
- break;
99221
- }
99222
- case "session.error": {
99223
- const props = event.properties;
99224
- const sessionId = props.sessionID;
99225
- if (!sessionId) break;
99226
- const errMsg = props.error?.message ?? String(props.error);
99227
- trackSessionError(sessionId, errMsg);
99228
- const payload = pendingBySession.get(sessionId);
99229
- if (!payload) break;
99230
- const updateRes = await updateMessage(payload.feishuClient, payload.placeholderId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
99231
- if (!updateRes.ok) {
99232
- await sendTextMessage(payload.feishuClient, payload.chatId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
99233
- }
99234
- break;
99235
- }
99192
+ function invalidateCachedSession(sessionId) {
99193
+ const sessionKey = sessionIdToKeyCache.get(sessionId);
99194
+ if (sessionKey) {
99195
+ sessionCache.delete(sessionKey);
99196
+ sessionIdToKeyCache.delete(sessionId);
99236
99197
  }
99198
+ return sessionKey;
99237
99199
  }
99238
- function extractPartText(part) {
99239
- if (part.type === "text") return part.text ?? "";
99240
- if (part.type === "reasoning" && part.text) return `\u{1F914} \u601D\u8003: ${part.text}
99241
-
99242
- `;
99243
- return "";
99244
- }
99245
-
99246
- // src/session.ts
99247
- var SESSION_KEY_PREFIX = "feishu";
99248
- var TITLE_PREFIX = "Feishu";
99249
99200
  function buildSessionKey(chatType, id) {
99250
99201
  return `${SESSION_KEY_PREFIX}-${chatType}-${id}`;
99251
99202
  }
@@ -99253,6 +99204,8 @@ function generateSessionTitle(sessionKey) {
99253
99204
  return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
99254
99205
  }
99255
99206
  async function getOrCreateSession(client, sessionKey, directory) {
99207
+ const cached = sessionCache.get(sessionKey);
99208
+ if (cached) return cached;
99256
99209
  const titlePrefix = `${TITLE_PREFIX}-${sessionKey}-`;
99257
99210
  const query = directory ? { directory } : void 0;
99258
99211
  const { data: sessions } = await client.session.list({ query });
@@ -99267,7 +99220,11 @@ async function getOrCreateSession(client, sessionKey, directory) {
99267
99220
  return cb - ca;
99268
99221
  });
99269
99222
  const best = candidates[0];
99270
- if (best?.id) return { id: best.id, title: best.title };
99223
+ if (best?.id) {
99224
+ const session2 = { id: best.id, title: best.title };
99225
+ setCachedSession(sessionKey, session2);
99226
+ return session2;
99227
+ }
99271
99228
  }
99272
99229
  }
99273
99230
  const title = generateSessionTitle(sessionKey);
@@ -99278,7 +99235,9 @@ async function getOrCreateSession(client, sessionKey, directory) {
99278
99235
  `\u521B\u5EFA OpenCode \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99279
99236
  );
99280
99237
  }
99281
- return { id: createResp.data.id, title: createResp.data.title };
99238
+ const session = { id: createResp.data.id, title: createResp.data.title };
99239
+ setCachedSession(sessionKey, session);
99240
+ return session;
99282
99241
  }
99283
99242
  async function forkSession(client, oldSessionId, sessionKey, directory) {
99284
99243
  const query = directory ? { directory } : void 0;
@@ -99308,6 +99267,92 @@ async function forkSession(client, oldSessionId, sessionKey, directory) {
99308
99267
  return { id: resp.data.id, title };
99309
99268
  }
99310
99269
 
99270
+ // src/handler/event.ts
99271
+ var pendingBySession = /* @__PURE__ */ new Map();
99272
+ function registerPending(sessionId, payload) {
99273
+ pendingBySession.set(sessionId, { ...payload, textBuffer: "" });
99274
+ }
99275
+ function unregisterPending(sessionId) {
99276
+ pendingBySession.delete(sessionId);
99277
+ }
99278
+ function migratePending(oldSessionId, newSessionId) {
99279
+ const payload = pendingBySession.get(oldSessionId);
99280
+ if (payload) {
99281
+ pendingBySession.delete(oldSessionId);
99282
+ pendingBySession.set(newSessionId, payload);
99283
+ }
99284
+ }
99285
+ function isModelError(errMsg) {
99286
+ return errMsg.includes("ModelNotFound") || errMsg.includes("ProviderModelNotFound");
99287
+ }
99288
+ async function handleEvent(event, deps) {
99289
+ switch (event.type) {
99290
+ case "message.part.updated": {
99291
+ const part = event.properties.part;
99292
+ if (!part) break;
99293
+ const sessionId = part.sessionID;
99294
+ if (!sessionId) break;
99295
+ const payload = pendingBySession.get(sessionId);
99296
+ if (!payload) break;
99297
+ const delta = event.properties.delta;
99298
+ if (delta) {
99299
+ payload.textBuffer += delta;
99300
+ } else {
99301
+ const fullText = extractPartText(part);
99302
+ if (fullText) {
99303
+ payload.textBuffer = fullText;
99304
+ }
99305
+ }
99306
+ if (payload.textBuffer) {
99307
+ const res = await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
99308
+ if (!res.ok) ;
99309
+ }
99310
+ break;
99311
+ }
99312
+ case "session.error": {
99313
+ const props = event.properties;
99314
+ const sessionId = props.sessionID;
99315
+ if (!sessionId) break;
99316
+ const errMsg = props.error?.message ?? String(props.error);
99317
+ if (isModelError(errMsg)) {
99318
+ const sessionKey = invalidateCachedSession(sessionId);
99319
+ if (sessionKey) {
99320
+ try {
99321
+ const newSession = await forkSession(deps.client, sessionId, sessionKey, deps.directory);
99322
+ setCachedSession(sessionKey, newSession);
99323
+ deps.log("warn", "\u6A21\u578B\u4E0D\u517C\u5BB9\uFF0C\u5DF2\u4E3B\u52A8 fork \u4F1A\u8BDD", {
99324
+ oldSessionId: sessionId,
99325
+ newSessionId: newSession.id,
99326
+ sessionKey
99327
+ });
99328
+ migratePending(sessionId, newSession.id);
99329
+ } catch (forkErr) {
99330
+ deps.log("error", "\u4E3B\u52A8 fork \u5931\u8D25", {
99331
+ sessionId,
99332
+ sessionKey,
99333
+ error: forkErr instanceof Error ? forkErr.message : String(forkErr)
99334
+ });
99335
+ }
99336
+ }
99337
+ }
99338
+ const payload = pendingBySession.get(sessionId);
99339
+ if (!payload) break;
99340
+ const updateRes = await updateMessage(payload.feishuClient, payload.placeholderId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
99341
+ if (!updateRes.ok) {
99342
+ await sendTextMessage(payload.feishuClient, payload.chatId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
99343
+ }
99344
+ break;
99345
+ }
99346
+ }
99347
+ }
99348
+ function extractPartText(part) {
99349
+ if (part.type === "text") return part.text ?? "";
99350
+ if (part.type === "reasoning" && part.text) return `\u{1F914} \u601D\u8003: ${part.text}
99351
+
99352
+ `;
99353
+ return "";
99354
+ }
99355
+
99311
99356
  // src/handler/chat.ts
99312
99357
  var activeAutoPrompts = /* @__PURE__ */ new Map();
99313
99358
  async function handleChat(ctx, deps) {
@@ -99322,18 +99367,14 @@ async function handleChat(ctx, deps) {
99322
99367
  activeAutoPrompts.delete(sessionKey);
99323
99368
  log("info", "\u7528\u6237\u4ECB\u5165\uFF0C\u81EA\u52A8\u63D0\u793A\u5DF2\u4E2D\u65AD", { sessionKey });
99324
99369
  }
99325
- let session = await getOrCreateSession(client, sessionKey, directory);
99370
+ const session = await getOrCreateSession(client, sessionKey, directory);
99326
99371
  const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
99327
99372
  if (!parts.length) return;
99328
99373
  if (!shouldReply) {
99329
99374
  try {
99330
- session = await promptWithForkRecovery({
99331
- client,
99332
- session,
99333
- sessionKey,
99334
- directory,
99375
+ await client.session.prompt({
99376
+ path: { id: session.id },
99335
99377
  query,
99336
- log,
99337
99378
  body: { parts, noReply: true }
99338
99379
  });
99339
99380
  } catch (err) {
@@ -99349,7 +99390,6 @@ async function handleChat(ctx, deps) {
99349
99390
  const stablePolls = config.stablePolls;
99350
99391
  let placeholderId = "";
99351
99392
  let done = false;
99352
- let oldSessionId = session.id;
99353
99393
  const timer = thinkingDelay > 0 ? setTimeout(async () => {
99354
99394
  if (done) return;
99355
99395
  try {
@@ -99367,21 +99407,11 @@ async function handleChat(ctx, deps) {
99367
99407
  }
99368
99408
  }, thinkingDelay) : null;
99369
99409
  try {
99370
- session = await promptWithForkRecovery({
99371
- client,
99372
- session,
99373
- sessionKey,
99374
- directory,
99410
+ await client.session.prompt({
99411
+ path: { id: session.id },
99375
99412
  query,
99376
- log,
99377
99413
  body: { parts }
99378
99414
  });
99379
- if (session.id !== oldSessionId) {
99380
- unregisterPending(oldSessionId);
99381
- if (placeholderId) {
99382
- registerPending(session.id, { chatId, placeholderId, feishuClient });
99383
- }
99384
- }
99385
99415
  const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
99386
99416
  await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
99387
99417
  const { autoPrompt } = config;
@@ -99427,7 +99457,6 @@ async function handleChat(ctx, deps) {
99427
99457
  done = true;
99428
99458
  if (timer) clearTimeout(timer);
99429
99459
  unregisterPending(session.id);
99430
- if (session.id !== oldSessionId) unregisterPending(oldSessionId);
99431
99460
  }
99432
99461
  }
99433
99462
  async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
@@ -99499,26 +99528,6 @@ function abortableSleep(ms, signal) {
99499
99528
  signal.addEventListener("abort", onAbort, { once: true });
99500
99529
  });
99501
99530
  }
99502
- function isModelNotFoundError(err, sessionId) {
99503
- const msg = err instanceof Error ? err.message : String(err);
99504
- const tracked = consumeSessionError(sessionId);
99505
- if (msg.includes("ProviderModelNotFound") || msg.includes("ModelNotFound")) return true;
99506
- return !!tracked;
99507
- }
99508
- async function promptWithForkRecovery(opts) {
99509
- const { client, sessionKey, directory, query, log, body } = opts;
99510
- let { session } = opts;
99511
- const doPrompt = (s) => client.session.prompt({ path: { id: s.id }, query, body });
99512
- try {
99513
- await doPrompt(session);
99514
- } catch (err) {
99515
- if (!isModelNotFoundError(err, session.id)) throw err;
99516
- log("warn", "\u4F1A\u8BDD\u6A21\u578B\u4E0D\u517C\u5BB9\uFF0Cfork \u65E7\u4F1A\u8BDD\u91CD\u8BD5", { oldSessionId: session.id });
99517
- session = await forkSession(client, session.id, sessionKey, directory);
99518
- await doPrompt(session);
99519
- }
99520
- return session;
99521
- }
99522
99531
  function extractLastAssistantText(messages) {
99523
99532
  const assistant = messages.filter((m) => m.info?.role === "assistant").pop();
99524
99533
  const parts = assistant?.parts ?? [];
@@ -99721,7 +99730,7 @@ var FeishuPlugin = async (ctx) => {
99721
99730
  const hooks = {
99722
99731
  event: async ({ event }) => {
99723
99732
  if (!gateway) return;
99724
- await handleEvent(event);
99733
+ await handleEvent(event, { client, log, directory: resolvedConfig.directory });
99725
99734
  }
99726
99735
  };
99727
99736
  return hooks;