opencode-feishu 0.7.6 → 0.7.10

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,157 +99176,35 @@ async function updateMessage(client, messageId, text2) {
99176
99176
  }
99177
99177
  }
99178
99178
 
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);
99188
- }
99189
- sessionCache.set(sessionKey, session);
99190
- sessionIdToKeyCache.set(session.id, sessionKey);
99191
- }
99192
- function invalidateCachedSession(sessionId) {
99193
- const sessionKey = sessionIdToKeyCache.get(sessionId);
99194
- if (sessionKey) {
99195
- sessionCache.delete(sessionKey);
99196
- sessionIdToKeyCache.delete(sessionId);
99197
- }
99198
- return sessionKey;
99199
- }
99200
- function buildSessionKey(chatType, id) {
99201
- return `${SESSION_KEY_PREFIX}-${chatType}-${id}`;
99202
- }
99203
- function generateSessionTitle(sessionKey) {
99204
- return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
99205
- }
99206
- async function getOrCreateSession(client, sessionKey, directory) {
99207
- const cached = sessionCache.get(sessionKey);
99208
- if (cached) return cached;
99209
- const titlePrefix = `${TITLE_PREFIX}-${sessionKey}-`;
99210
- const query = directory ? { directory } : void 0;
99211
- const { data: sessions } = await client.session.list({ query });
99212
- if (Array.isArray(sessions)) {
99213
- const candidates = sessions.filter(
99214
- (s) => s.title && s.title.startsWith(titlePrefix)
99215
- );
99216
- if (candidates.length > 0) {
99217
- candidates.sort((a, b) => {
99218
- const ca = a.time?.created ?? 0;
99219
- const cb = b.time?.created ?? 0;
99220
- return cb - ca;
99221
- });
99222
- const best = candidates[0];
99223
- if (best?.id) {
99224
- const session2 = { id: best.id, title: best.title };
99225
- setCachedSession(sessionKey, session2);
99226
- return session2;
99227
- }
99228
- }
99229
- }
99230
- const title = generateSessionTitle(sessionKey);
99231
- const createResp = await client.session.create({ query, body: { title } });
99232
- if (!createResp?.data?.id) {
99233
- const err = createResp?.error;
99234
- throw new Error(
99235
- `\u521B\u5EFA OpenCode \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99236
- );
99237
- }
99238
- const session = { id: createResp.data.id, title: createResp.data.title };
99239
- setCachedSession(sessionKey, session);
99240
- return session;
99241
- }
99242
- async function forkSession(client, oldSessionId, sessionKey, directory) {
99243
- const query = directory ? { directory } : void 0;
99244
- const resp = await client.session.fork({
99245
- path: { id: oldSessionId },
99246
- query,
99247
- body: {}
99248
- });
99249
- if (!resp?.data?.id) {
99250
- const err = resp?.error;
99251
- throw new Error(
99252
- `Fork \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99253
- );
99254
- }
99255
- const title = generateSessionTitle(sessionKey);
99256
- const updateResp = await client.session.update({
99257
- path: { id: resp.data.id },
99258
- query,
99259
- body: { title }
99260
- });
99261
- if (!updateResp?.data?.id) {
99262
- const err = updateResp?.error;
99263
- throw new Error(
99264
- `\u66F4\u65B0 forked session \u6807\u9898\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99265
- );
99266
- }
99267
- return { id: resp.data.id, title };
99268
- }
99269
- async function createFreshSession(client, sessionKey, directory) {
99270
- const query = directory ? { directory } : void 0;
99271
- const title = generateSessionTitle(sessionKey);
99272
- const resp = await client.session.create({ query, body: { title } });
99273
- if (!resp?.data?.id) {
99274
- const err = resp?.error;
99275
- throw new Error(
99276
- `\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99277
- );
99278
- }
99279
- return { id: resp.data.id, title: resp.data.title };
99280
- }
99281
- async function forkOrCreateSession(client, oldSessionId, sessionKey, directory, log) {
99282
- try {
99283
- return await forkSession(client, oldSessionId, sessionKey, directory);
99284
- } catch (forkErr) {
99285
- log?.("warn", "Fork \u5931\u8D25\uFF0C\u56DE\u9000\u5230\u521B\u5EFA\u65B0\u4F1A\u8BDD", {
99286
- oldSessionId,
99287
- sessionKey,
99288
- error: forkErr instanceof Error ? forkErr.message : String(forkErr)
99289
- });
99290
- return await createFreshSession(client, sessionKey, directory);
99291
- }
99292
- }
99293
-
99294
99179
  // src/handler/event.ts
99295
- function maskKey(sessionKey) {
99296
- return sessionKey.replace(/-[^-]+$/, "-***");
99297
- }
99298
99180
  var pendingBySession = /* @__PURE__ */ new Map();
99299
99181
  var sessionErrors = /* @__PURE__ */ new Map();
99300
99182
  var sessionErrorTimeouts = /* @__PURE__ */ new Map();
99301
99183
  var SESSION_ERROR_TTL_MS = 3e4;
99302
- var modelOverrides = /* @__PURE__ */ new Map();
99303
- var forkAttempts = /* @__PURE__ */ new Map();
99304
- var forkAttemptTimeouts = /* @__PURE__ */ new Map();
99305
- var MAX_FORK_ATTEMPTS = 2;
99306
- var FORK_ATTEMPTS_TTL_MS = 36e5;
99307
- function clearForkAttempts(sessionKey) {
99308
- forkAttempts.delete(sessionKey);
99309
- const timer = forkAttemptTimeouts.get(sessionKey);
99184
+ var retryAttempts = /* @__PURE__ */ new Map();
99185
+ var retryAttemptTimeouts = /* @__PURE__ */ new Map();
99186
+ var MAX_RETRY_ATTEMPTS = 2;
99187
+ var RETRY_ATTEMPTS_TTL_MS = 36e5;
99188
+ function clearRetryAttempts(sessionKey) {
99189
+ retryAttempts.delete(sessionKey);
99190
+ const timer = retryAttemptTimeouts.get(sessionKey);
99310
99191
  if (timer) {
99311
99192
  clearTimeout(timer);
99312
- forkAttemptTimeouts.delete(sessionKey);
99193
+ retryAttemptTimeouts.delete(sessionKey);
99313
99194
  }
99314
99195
  }
99315
- function setForkAttempts(sessionKey, count) {
99316
- forkAttempts.set(sessionKey, count);
99317
- const existing = forkAttemptTimeouts.get(sessionKey);
99196
+ function getRetryAttempts(sessionKey) {
99197
+ return retryAttempts.get(sessionKey) ?? 0;
99198
+ }
99199
+ function setRetryAttempts(sessionKey, count) {
99200
+ retryAttempts.set(sessionKey, count);
99201
+ const existing = retryAttemptTimeouts.get(sessionKey);
99318
99202
  if (existing) clearTimeout(existing);
99319
99203
  const timeoutId = setTimeout(() => {
99320
- forkAttempts.delete(sessionKey);
99321
- forkAttemptTimeouts.delete(sessionKey);
99322
- }, FORK_ATTEMPTS_TTL_MS);
99323
- forkAttemptTimeouts.set(sessionKey, timeoutId);
99324
- }
99325
- function getModelOverride(sessionKey) {
99326
- return modelOverrides.get(sessionKey);
99327
- }
99328
- function clearModelOverride(sessionKey) {
99329
- modelOverrides.delete(sessionKey);
99204
+ retryAttempts.delete(sessionKey);
99205
+ retryAttemptTimeouts.delete(sessionKey);
99206
+ }, RETRY_ATTEMPTS_TTL_MS);
99207
+ retryAttemptTimeouts.set(sessionKey, timeoutId);
99330
99208
  }
99331
99209
  function getSessionError(sessionId) {
99332
99210
  return sessionErrors.get(sessionId);
@@ -99339,12 +99217,12 @@ function clearSessionError(sessionId) {
99339
99217
  }
99340
99218
  sessionErrors.delete(sessionId);
99341
99219
  }
99342
- function setSessionError(sessionId, errMsg) {
99220
+ function setSessionError(sessionId, message, fields) {
99343
99221
  const existing = sessionErrorTimeouts.get(sessionId);
99344
99222
  if (existing) {
99345
99223
  clearTimeout(existing);
99346
99224
  }
99347
- sessionErrors.set(sessionId, errMsg);
99225
+ sessionErrors.set(sessionId, { message, fields });
99348
99226
  const timeoutId = setTimeout(() => {
99349
99227
  sessionErrors.delete(sessionId);
99350
99228
  sessionErrorTimeouts.delete(sessionId);
@@ -99357,13 +99235,6 @@ function registerPending(sessionId, payload) {
99357
99235
  function unregisterPending(sessionId) {
99358
99236
  pendingBySession.delete(sessionId);
99359
99237
  }
99360
- function migratePending(oldSessionId, newSessionId) {
99361
- const payload = pendingBySession.get(oldSessionId);
99362
- if (payload) {
99363
- pendingBySession.delete(oldSessionId);
99364
- pendingBySession.set(newSessionId, payload);
99365
- }
99366
- }
99367
99238
  function extractErrorFields(error) {
99368
99239
  if (typeof error === "string") return [error];
99369
99240
  if (error && typeof error === "object") {
@@ -99377,14 +99248,12 @@ function extractErrorFields(error) {
99377
99248
  }
99378
99249
  return [String(error)];
99379
99250
  }
99380
- function isModelError(errMsg, rawError) {
99251
+ function isModelError(fields) {
99381
99252
  const check = (s) => {
99382
99253
  const l = s.toLowerCase();
99383
99254
  return l.includes("model not found") || l.includes("modelnotfound");
99384
99255
  };
99385
- if (check(errMsg)) return true;
99386
- if (rawError) return extractErrorFields(rawError).some(check);
99387
- return false;
99256
+ return fields.some(check);
99388
99257
  }
99389
99258
  async function handleEvent(event, deps) {
99390
99259
  switch (event.type) {
@@ -99426,77 +99295,13 @@ async function handleEvent(event, deps) {
99426
99295
  } else {
99427
99296
  errMsg = String(error);
99428
99297
  }
99298
+ const fields = extractErrorFields(error);
99429
99299
  deps.log("warn", "\u6536\u5230 session.error \u4E8B\u4EF6", { sessionId, errMsg });
99430
- setSessionError(sessionId, errMsg);
99431
- if (isModelError(errMsg, props.error)) {
99432
- const sessionKey = invalidateCachedSession(sessionId);
99433
- if (sessionKey) {
99434
- const attempts = forkAttempts.get(sessionKey) ?? 0;
99435
- if (attempts >= MAX_FORK_ATTEMPTS) {
99436
- deps.log("warn", "\u5DF2\u8FBE fork \u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey: maskKey(sessionKey), attempts });
99437
- } else {
99438
- setForkAttempts(sessionKey, attempts + 1);
99439
- try {
99440
- const newSession = await forkOrCreateSession(deps.client, sessionId, sessionKey, deps.directory, deps.log);
99441
- setCachedSession(sessionKey, newSession);
99442
- modelOverrides.delete(sessionKey);
99443
- try {
99444
- const fallbackModel = await resolveLatestModel(deps.client, props.error ?? errMsg, deps.directory);
99445
- if (fallbackModel) {
99446
- modelOverrides.set(sessionKey, fallbackModel);
99447
- deps.log("info", "\u5DF2\u89E3\u6790\u964D\u7EA7\u6A21\u578B", {
99448
- sessionKey: maskKey(sessionKey),
99449
- providerID: fallbackModel.providerID,
99450
- modelID: fallbackModel.modelID
99451
- });
99452
- }
99453
- } catch (modelErr) {
99454
- deps.log("warn", "\u89E3\u6790\u964D\u7EA7\u6A21\u578B\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u9ED8\u8BA4\u6A21\u578B", {
99455
- sessionKey: maskKey(sessionKey),
99456
- error: modelErr instanceof Error ? modelErr.message : String(modelErr)
99457
- });
99458
- }
99459
- deps.log("warn", "\u6A21\u578B\u4E0D\u517C\u5BB9\uFF0C\u5DF2\u6062\u590D\u4F1A\u8BDD", {
99460
- oldSessionId: sessionId,
99461
- newSessionId: newSession.id,
99462
- sessionKey: maskKey(sessionKey),
99463
- forkAttempt: attempts + 1
99464
- });
99465
- migratePending(sessionId, newSession.id);
99466
- } catch (recoverErr) {
99467
- deps.log("error", "\u4F1A\u8BDD\u6062\u590D\u5931\u8D25", {
99468
- sessionId,
99469
- sessionKey: maskKey(sessionKey),
99470
- error: recoverErr instanceof Error ? recoverErr.message : String(recoverErr)
99471
- });
99472
- }
99473
- }
99474
- }
99475
- }
99300
+ setSessionError(sessionId, errMsg, fields);
99476
99301
  break;
99477
99302
  }
99478
99303
  }
99479
99304
  }
99480
- async function resolveLatestModel(client, rawError, directory) {
99481
- const pattern = /model not found:?\s*(\w[\w-]*)\//i;
99482
- const fields = extractErrorFields(rawError);
99483
- const rawProviderID = fields.map((f) => pattern.exec(f)?.[1]).find(Boolean);
99484
- if (!rawProviderID) return void 0;
99485
- const providerID = rawProviderID.toLowerCase();
99486
- const query = directory ? { directory } : void 0;
99487
- const { data } = await client.provider.list({ query });
99488
- if (!data) return void 0;
99489
- const defaultModelID = data.default?.[providerID];
99490
- if (defaultModelID) {
99491
- return { providerID, modelID: defaultModelID };
99492
- }
99493
- const provider = data.all?.find((p) => p.id === providerID);
99494
- if (!provider?.models) return void 0;
99495
- const sortedModels = Object.values(provider.models).filter((m) => m.status !== "deprecated").sort((a, b) => b.release_date.localeCompare(a.release_date));
99496
- if (sortedModels.length === 0) return void 0;
99497
- const best = sortedModels.find((m) => m.tool_call) ?? sortedModels[0];
99498
- return { providerID, modelID: best.id };
99499
- }
99500
99305
  function extractPartText(part) {
99501
99306
  if (part.type === "text") return part.text ?? "";
99502
99307
  if (part.type === "reasoning" && part.text) return `\u{1F914} \u601D\u8003: ${part.text}
@@ -99505,6 +99310,56 @@ function extractPartText(part) {
99505
99310
  return "";
99506
99311
  }
99507
99312
 
99313
+ // src/session.ts
99314
+ var SESSION_KEY_PREFIX = "feishu";
99315
+ var TITLE_PREFIX = "Feishu";
99316
+ var sessionCache = /* @__PURE__ */ new Map();
99317
+ function setCachedSession(sessionKey, session) {
99318
+ sessionCache.set(sessionKey, session);
99319
+ }
99320
+ function buildSessionKey(chatType, id) {
99321
+ return `${SESSION_KEY_PREFIX}-${chatType}-${id}`;
99322
+ }
99323
+ function generateSessionTitle(sessionKey) {
99324
+ return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
99325
+ }
99326
+ async function getOrCreateSession(client, sessionKey, directory) {
99327
+ const cached = sessionCache.get(sessionKey);
99328
+ if (cached) return cached;
99329
+ const titlePrefix = `${TITLE_PREFIX}-${sessionKey}-`;
99330
+ const query = directory ? { directory } : void 0;
99331
+ const { data: sessions } = await client.session.list({ query });
99332
+ if (Array.isArray(sessions)) {
99333
+ const candidates = sessions.filter(
99334
+ (s) => s.title && s.title.startsWith(titlePrefix)
99335
+ );
99336
+ if (candidates.length > 0) {
99337
+ candidates.sort((a, b) => {
99338
+ const ca = a.time?.created ?? 0;
99339
+ const cb = b.time?.created ?? 0;
99340
+ return cb - ca;
99341
+ });
99342
+ const best = candidates[0];
99343
+ if (best?.id) {
99344
+ const session2 = { id: best.id, title: best.title };
99345
+ setCachedSession(sessionKey, session2);
99346
+ return session2;
99347
+ }
99348
+ }
99349
+ }
99350
+ const title = generateSessionTitle(sessionKey);
99351
+ const createResp = await client.session.create({ query, body: { title } });
99352
+ if (!createResp?.data?.id) {
99353
+ const err = createResp?.error;
99354
+ throw new Error(
99355
+ `\u521B\u5EFA OpenCode \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99356
+ );
99357
+ }
99358
+ const session = { id: createResp.data.id, title: createResp.data.title };
99359
+ setCachedSession(sessionKey, session);
99360
+ return session;
99361
+ }
99362
+
99508
99363
  // src/handler/chat.ts
99509
99364
  var SSE_RACE_WAIT_MS = 100;
99510
99365
  var activeAutoPrompts = /* @__PURE__ */ new Map();
@@ -99530,10 +99385,10 @@ async function handleChat(ctx, deps) {
99530
99385
  senderId,
99531
99386
  messageType,
99532
99387
  shouldReply,
99388
+ content,
99533
99389
  parts
99534
99390
  });
99535
- const modelOverride = getModelOverride(sessionKey);
99536
- const baseBody = { parts, ...modelOverride ? { model: modelOverride } : {} };
99391
+ const baseBody = { parts };
99537
99392
  if (!shouldReply) {
99538
99393
  try {
99539
99394
  await client.session.prompt({
@@ -99554,6 +99409,7 @@ async function handleChat(ctx, deps) {
99554
99409
  const stablePolls = config.stablePolls;
99555
99410
  let placeholderId = "";
99556
99411
  let done = false;
99412
+ let activeSessionId = session.id;
99557
99413
  const timer = thinkingDelay > 0 ? setTimeout(async () => {
99558
99414
  if (done) return;
99559
99415
  try {
@@ -99561,7 +99417,7 @@ async function handleChat(ctx, deps) {
99561
99417
  if (done) return;
99562
99418
  if (res.ok && res.messageId) {
99563
99419
  placeholderId = res.messageId;
99564
- registerPending(session.id, { chatId, placeholderId, feishuClient });
99420
+ registerPending(activeSessionId, { chatId, placeholderId, feishuClient });
99565
99421
  }
99566
99422
  } catch (err) {
99567
99423
  log("warn", "\u53D1\u9001\u5360\u4F4D\u6D88\u606F\u5931\u8D25", {
@@ -99570,6 +99426,45 @@ async function handleChat(ctx, deps) {
99570
99426
  });
99571
99427
  }
99572
99428
  }, thinkingDelay) : null;
99429
+ async function runAutoPromptLoop(activeId) {
99430
+ const { autoPrompt } = config;
99431
+ if (!autoPrompt.enabled || !shouldReply) return;
99432
+ const ac = new AbortController();
99433
+ activeAutoPrompts.set(sessionKey, ac);
99434
+ log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
99435
+ try {
99436
+ for (let i = 0; i < autoPrompt.maxIterations; i++) {
99437
+ await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
99438
+ log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
99439
+ await client.session.prompt({
99440
+ path: { id: activeId },
99441
+ query,
99442
+ body: { parts: [{ type: "text", text: autoPrompt.message }] }
99443
+ });
99444
+ const text2 = await pollForResponse(client, activeId, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
99445
+ if (text2) {
99446
+ log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", {
99447
+ sessionKey,
99448
+ iteration: i + 1,
99449
+ output: text2
99450
+ });
99451
+ await sendTextMessage(feishuClient, chatId, text2);
99452
+ }
99453
+ }
99454
+ log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
99455
+ } catch (loopErr) {
99456
+ if (loopErr.name === "AbortError") {
99457
+ log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
99458
+ } else {
99459
+ log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
99460
+ sessionKey,
99461
+ error: loopErr instanceof Error ? loopErr.message : String(loopErr)
99462
+ });
99463
+ }
99464
+ } finally {
99465
+ activeAutoPrompts.delete(sessionKey);
99466
+ }
99467
+ }
99573
99468
  try {
99574
99469
  await client.session.prompt({
99575
99470
  path: { id: session.id },
@@ -99577,62 +99472,122 @@ async function handleChat(ctx, deps) {
99577
99472
  body: baseBody
99578
99473
  });
99579
99474
  const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
99580
- clearForkAttempts(sessionKey);
99581
- clearModelOverride(sessionKey);
99475
+ log("info", "\u6A21\u578B\u54CD\u5E94\u5B8C\u6210", {
99476
+ sessionKey,
99477
+ sessionId: session.id,
99478
+ output: finalText || "(empty)"
99479
+ });
99480
+ clearRetryAttempts(sessionKey);
99582
99481
  await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
99583
- const { autoPrompt } = config;
99584
- if (autoPrompt.enabled && shouldReply) {
99585
- const ac = new AbortController();
99586
- activeAutoPrompts.set(sessionKey, ac);
99587
- log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
99588
- try {
99589
- for (let i = 0; i < autoPrompt.maxIterations; i++) {
99590
- await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
99591
- log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
99592
- await client.session.prompt({
99593
- path: { id: session.id },
99594
- query,
99595
- body: { parts: [{ type: "text", text: autoPrompt.message }] }
99596
- });
99597
- const text2 = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
99598
- if (text2) {
99599
- await sendTextMessage(feishuClient, chatId, text2);
99482
+ await runAutoPromptLoop(session.id);
99483
+ } catch (err) {
99484
+ await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
99485
+ let sessionError = getSessionError(session.id);
99486
+ clearSessionError(session.id);
99487
+ if (!sessionError) {
99488
+ const thrownFields = extractErrorFields(err);
99489
+ if (isModelError(thrownFields)) {
99490
+ const thrownMsg = err instanceof Error ? err.message : String(err);
99491
+ sessionError = { message: thrownMsg, fields: thrownFields };
99492
+ }
99493
+ }
99494
+ if (sessionError && isModelError(sessionError.fields)) {
99495
+ const attempts = getRetryAttempts(sessionKey);
99496
+ if (attempts < MAX_RETRY_ATTEMPTS) {
99497
+ try {
99498
+ const modelOverride = await resolveLatestModel(client, sessionError.fields, directory);
99499
+ if (!modelOverride) {
99500
+ log("warn", "\u65E0\u4EFB\u4F55\u5DF2\u8FDE\u63A5 provider \u6709\u53EF\u7528\u6A21\u578B\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey });
99501
+ } else {
99502
+ setRetryAttempts(sessionKey, attempts + 1);
99503
+ log("info", "\u5DF2\u89E3\u6790\u53EF\u7528\u6A21\u578B\uFF0C\u5728\u540C\u4E00 session \u4E0A\u91CD\u8BD5", {
99504
+ sessionKey,
99505
+ providerID: modelOverride.providerID,
99506
+ modelID: modelOverride.modelID
99507
+ });
99508
+ await client.session.prompt({
99509
+ path: { id: session.id },
99510
+ query,
99511
+ body: { ...baseBody, model: modelOverride }
99512
+ });
99513
+ const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
99514
+ log("info", "\u6A21\u578B\u6062\u590D\u540E\u54CD\u5E94\u5B8C\u6210", {
99515
+ sessionKey,
99516
+ sessionId: session.id,
99517
+ output: finalText || "(empty)"
99518
+ });
99519
+ clearRetryAttempts(sessionKey);
99520
+ await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
99521
+ log("info", "\u6A21\u578B\u4E0D\u517C\u5BB9\u6062\u590D\u6210\u529F", {
99522
+ sessionId: session.id,
99523
+ sessionKey,
99524
+ model: `${modelOverride.providerID}/${modelOverride.modelID}`,
99525
+ attempt: attempts + 1
99526
+ });
99527
+ await runAutoPromptLoop(session.id);
99528
+ return;
99600
99529
  }
99601
- }
99602
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
99603
- } catch (err) {
99604
- if (err.name === "AbortError") {
99605
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
99606
- } else {
99607
- log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
99530
+ } catch (recoveryErr) {
99531
+ const recoveryErrMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
99532
+ const retryError = getSessionError(session.id);
99533
+ if (retryError) clearSessionError(session.id);
99534
+ sessionError = retryError ?? { message: recoveryErrMsg, fields: [] };
99535
+ log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", {
99536
+ sessionId: session.id,
99608
99537
  sessionKey,
99609
- error: err instanceof Error ? err.message : String(err)
99538
+ error: recoveryErrMsg
99610
99539
  });
99611
99540
  }
99612
- } finally {
99613
- activeAutoPrompts.delete(sessionKey);
99541
+ } else {
99542
+ log("warn", "\u5DF2\u8FBE\u91CD\u8BD5\u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", {
99543
+ sessionKey,
99544
+ attempts
99545
+ });
99614
99546
  }
99615
99547
  }
99616
- } catch (err) {
99617
- await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
99618
- const sessionError = getSessionError(session.id);
99619
- clearSessionError(session.id);
99620
99548
  const thrownError = err instanceof Error ? err.message : String(err);
99621
- const errorMessage = sessionError || thrownError;
99549
+ const errorMessage = sessionError?.message || thrownError;
99622
99550
  log("error", "\u5BF9\u8BDD\u5904\u7406\u5931\u8D25", {
99623
99551
  sessionId: session.id,
99624
- sessionKey: sessionKey.replace(/-[^-]+$/, "-***"),
99552
+ sessionKey,
99625
99553
  chatType,
99626
99554
  error: thrownError,
99627
- ...sessionError ? { sessionError } : { sseRaceMiss: true }
99555
+ ...sessionError ? { sessionError: sessionError.message } : { sseRaceMiss: true }
99628
99556
  });
99629
99557
  const msg = "\u274C " + errorMessage;
99630
99558
  await replyOrUpdate(feishuClient, chatId, placeholderId, msg);
99631
99559
  } finally {
99632
99560
  done = true;
99633
99561
  if (timer) clearTimeout(timer);
99634
- unregisterPending(session.id);
99562
+ unregisterPending(activeSessionId);
99563
+ }
99564
+ }
99565
+ async function resolveLatestModel(client, errorFields, directory) {
99566
+ const pattern = /model\s*not\s*found:?\s*(\w[\w-]*)\/(\S+)/i;
99567
+ const match = errorFields.map((f) => pattern.exec(f)).find(Boolean);
99568
+ const failedProviderID = match?.[1]?.toLowerCase();
99569
+ const failedModelID = match?.[2]?.replace(/\.$/, "");
99570
+ const query = directory ? { directory } : void 0;
99571
+ const { data } = await client.provider.list({ query });
99572
+ if (!data) return void 0;
99573
+ const connectedProviders = data.connected ?? [];
99574
+ if (connectedProviders.length === 0) return void 0;
99575
+ for (const pid of connectedProviders) {
99576
+ const defaultModelID = data.default?.[pid];
99577
+ if (defaultModelID && !(pid === failedProviderID && defaultModelID === failedModelID)) {
99578
+ return { providerID: pid, modelID: defaultModelID };
99579
+ }
99580
+ }
99581
+ for (const pid of connectedProviders) {
99582
+ const provider = data.all?.find((p) => p.id === pid);
99583
+ if (!provider?.models) continue;
99584
+ const candidates = Object.values(provider.models).filter((m) => m.status !== "deprecated" && !(pid === failedProviderID && m.id === failedModelID)).sort((a, b) => b.release_date.localeCompare(a.release_date));
99585
+ if (candidates.length > 0) {
99586
+ const best = candidates.find((m) => m.tool_call) ?? candidates[0];
99587
+ return { providerID: pid, modelID: best.id };
99588
+ }
99635
99589
  }
99590
+ return void 0;
99636
99591
  }
99637
99592
  async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
99638
99593
  if (messageType === "text") {
@@ -99905,7 +99860,7 @@ var FeishuPlugin = async (ctx) => {
99905
99860
  const hooks = {
99906
99861
  event: async ({ event }) => {
99907
99862
  if (!gateway) return;
99908
- await handleEvent(event, { client, log, directory: resolvedConfig.directory });
99863
+ await handleEvent(event, { log, directory: resolvedConfig.directory });
99909
99864
  }
99910
99865
  };
99911
99866
  return hooks;