opencode-feishu 0.7.13 → 0.7.15
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 +184 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99175,6 +99175,12 @@ async function updateMessage(client, messageId, text2) {
|
|
|
99175
99175
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
99176
99176
|
}
|
|
99177
99177
|
}
|
|
99178
|
+
async function deleteMessage(client, messageId) {
|
|
99179
|
+
try {
|
|
99180
|
+
await client.im.message.delete({ path: { message_id: messageId } });
|
|
99181
|
+
} catch {
|
|
99182
|
+
}
|
|
99183
|
+
}
|
|
99178
99184
|
|
|
99179
99185
|
// src/handler/event.ts
|
|
99180
99186
|
var pendingBySession = /* @__PURE__ */ new Map();
|
|
@@ -99230,7 +99236,7 @@ function setSessionError(sessionId, message, fields) {
|
|
|
99230
99236
|
sessionErrorTimeouts.set(sessionId, timeoutId);
|
|
99231
99237
|
}
|
|
99232
99238
|
function registerPending(sessionId, payload) {
|
|
99233
|
-
pendingBySession.set(sessionId, { ...payload, textBuffer: "" });
|
|
99239
|
+
pendingBySession.set(sessionId, { ...payload, textBuffer: "", expectedMessageId: void 0 });
|
|
99234
99240
|
}
|
|
99235
99241
|
function unregisterPending(sessionId) {
|
|
99236
99242
|
pendingBySession.delete(sessionId);
|
|
@@ -99238,30 +99244,41 @@ function unregisterPending(sessionId) {
|
|
|
99238
99244
|
function extractErrorFields(error) {
|
|
99239
99245
|
if (typeof error === "string") return [error];
|
|
99240
99246
|
if (error && typeof error === "object") {
|
|
99241
|
-
const
|
|
99242
|
-
|
|
99243
|
-
const enumerable = Object.values(e);
|
|
99244
|
-
const fields = [...explicit, ...enumerable].filter((v) => typeof v === "string" && v.length > 0);
|
|
99245
|
-
if (e.data && typeof e.data === "object" && "message" in e.data) {
|
|
99246
|
-
const dataMsg = e.data.message;
|
|
99247
|
-
if (typeof dataMsg === "string" && dataMsg.length > 0) fields.push(dataMsg);
|
|
99248
|
-
}
|
|
99249
|
-
if (e.data && typeof e.data === "object" && "error" in e.data) {
|
|
99250
|
-
const dataErr = e.data.error;
|
|
99251
|
-
if (dataErr && typeof dataErr === "object") {
|
|
99252
|
-
const errStrings = Object.values(dataErr).filter((v) => typeof v === "string" && v.length > 0);
|
|
99253
|
-
fields.push(...errStrings);
|
|
99254
|
-
}
|
|
99255
|
-
}
|
|
99247
|
+
const fields = [];
|
|
99248
|
+
collectStrings(error, fields, 3);
|
|
99256
99249
|
return [...new Set(fields)];
|
|
99257
99250
|
}
|
|
99258
99251
|
return [String(error)];
|
|
99259
99252
|
}
|
|
99253
|
+
function collectStrings(obj, out, maxDepth) {
|
|
99254
|
+
if (maxDepth <= 0 || !obj || typeof obj !== "object") return;
|
|
99255
|
+
const e = obj;
|
|
99256
|
+
for (const key of ["message", "type", "name"]) {
|
|
99257
|
+
const v = e[key];
|
|
99258
|
+
if (typeof v === "string" && v.length > 0) out.push(v);
|
|
99259
|
+
}
|
|
99260
|
+
for (const v of Object.values(e)) {
|
|
99261
|
+
if (typeof v === "string" && v.length > 0) out.push(v);
|
|
99262
|
+
else if (Array.isArray(v)) {
|
|
99263
|
+
for (const item of v) collectStrings(item, out, maxDepth - 1);
|
|
99264
|
+
} else if (v && typeof v === "object") collectStrings(v, out, maxDepth - 1);
|
|
99265
|
+
}
|
|
99266
|
+
}
|
|
99260
99267
|
function isModelError(fields) {
|
|
99261
|
-
const
|
|
99268
|
+
const exactPatterns = [
|
|
99269
|
+
"model not found",
|
|
99270
|
+
"modelnotfound",
|
|
99271
|
+
"model_not_found",
|
|
99272
|
+
"model not supported",
|
|
99273
|
+
"model_not_supported",
|
|
99274
|
+
"model is not supported"
|
|
99275
|
+
];
|
|
99276
|
+
const negativeWords = ["not", "unsupported", "invalid", "unavailable", "unknown", "does not", "doesn't", "cannot", "\u4E0D\u652F\u6301", "\u4E0D\u5B58\u5728", "\u65E0\u6548"];
|
|
99262
99277
|
return fields.some((f) => {
|
|
99263
99278
|
const l = f.toLowerCase();
|
|
99264
|
-
|
|
99279
|
+
if (exactPatterns.some((p) => l.includes(p))) return true;
|
|
99280
|
+
if (l.includes("model") && negativeWords.some((w) => l.includes(w))) return true;
|
|
99281
|
+
return false;
|
|
99265
99282
|
});
|
|
99266
99283
|
}
|
|
99267
99284
|
async function handleEvent(event, deps) {
|
|
@@ -99273,6 +99290,16 @@ async function handleEvent(event, deps) {
|
|
|
99273
99290
|
if (!sessionId) break;
|
|
99274
99291
|
const payload = pendingBySession.get(sessionId);
|
|
99275
99292
|
if (!payload) break;
|
|
99293
|
+
const messageId = part.messageID;
|
|
99294
|
+
if (messageId) {
|
|
99295
|
+
if (!payload.expectedMessageId) {
|
|
99296
|
+
payload.expectedMessageId = messageId;
|
|
99297
|
+
} else if (payload.expectedMessageId !== messageId) {
|
|
99298
|
+
break;
|
|
99299
|
+
}
|
|
99300
|
+
} else if (payload.expectedMessageId) {
|
|
99301
|
+
break;
|
|
99302
|
+
}
|
|
99276
99303
|
const delta = event.properties.delta;
|
|
99277
99304
|
if (delta) {
|
|
99278
99305
|
payload.textBuffer += delta;
|
|
@@ -99332,6 +99359,9 @@ function buildSessionKey(chatType, id) {
|
|
|
99332
99359
|
function generateSessionTitle(sessionKey) {
|
|
99333
99360
|
return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
|
|
99334
99361
|
}
|
|
99362
|
+
function getCachedSession(sessionKey) {
|
|
99363
|
+
return sessionCache.get(sessionKey);
|
|
99364
|
+
}
|
|
99335
99365
|
async function getOrCreateSession(client, sessionKey, directory) {
|
|
99336
99366
|
const cached = sessionCache.get(sessionKey);
|
|
99337
99367
|
if (cached) return cached;
|
|
@@ -99370,9 +99400,8 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
99370
99400
|
}
|
|
99371
99401
|
|
|
99372
99402
|
// src/handler/chat.ts
|
|
99373
|
-
var SSE_RACE_WAIT_MS = 100;
|
|
99374
99403
|
var activeAutoPrompts = /* @__PURE__ */ new Map();
|
|
99375
|
-
async function handleChat(ctx, deps) {
|
|
99404
|
+
async function handleChat(ctx, deps, signal) {
|
|
99376
99405
|
const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
|
|
99377
99406
|
if (!content.trim() && messageType === "text") return;
|
|
99378
99407
|
const { config, client, feishuClient, log, directory } = deps;
|
|
@@ -99400,7 +99429,7 @@ async function handleChat(ctx, deps) {
|
|
|
99400
99429
|
const baseBody = { parts };
|
|
99401
99430
|
if (!shouldReply) {
|
|
99402
99431
|
try {
|
|
99403
|
-
await client.session.
|
|
99432
|
+
await client.session.promptAsync({
|
|
99404
99433
|
path: { id: session.id },
|
|
99405
99434
|
query,
|
|
99406
99435
|
body: { ...baseBody, noReply: true }
|
|
@@ -99477,12 +99506,12 @@ async function handleChat(ctx, deps) {
|
|
|
99477
99506
|
}
|
|
99478
99507
|
try {
|
|
99479
99508
|
clearSessionError(session.id);
|
|
99480
|
-
await client.session.
|
|
99509
|
+
await client.session.promptAsync({
|
|
99481
99510
|
path: { id: session.id },
|
|
99482
99511
|
query,
|
|
99483
99512
|
body: baseBody
|
|
99484
99513
|
});
|
|
99485
|
-
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
|
|
99514
|
+
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal });
|
|
99486
99515
|
log("info", "\u6A21\u578B\u54CD\u5E94\u5B8C\u6210", {
|
|
99487
99516
|
sessionKey,
|
|
99488
99517
|
sessionId: session.id,
|
|
@@ -99492,21 +99521,21 @@ async function handleChat(ctx, deps) {
|
|
|
99492
99521
|
await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
|
|
99493
99522
|
await runAutoPromptLoop(session.id);
|
|
99494
99523
|
} catch (err) {
|
|
99524
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
99525
|
+
log("info", "\u5904\u7406\u88AB\u4E2D\u65AD", { sessionKey, sessionId: session.id });
|
|
99526
|
+
if (placeholderId) {
|
|
99527
|
+
await deleteMessage(feishuClient, placeholderId).catch(() => {
|
|
99528
|
+
});
|
|
99529
|
+
}
|
|
99530
|
+
return;
|
|
99531
|
+
}
|
|
99495
99532
|
let sessionError;
|
|
99496
99533
|
if (err instanceof SessionErrorDetected) {
|
|
99497
99534
|
sessionError = err.sessionError;
|
|
99498
99535
|
clearSessionError(session.id);
|
|
99499
99536
|
} else {
|
|
99500
|
-
await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
|
|
99501
99537
|
sessionError = getSessionError(session.id);
|
|
99502
99538
|
clearSessionError(session.id);
|
|
99503
|
-
if (!sessionError) {
|
|
99504
|
-
const thrownFields = extractErrorFields(err);
|
|
99505
|
-
if (isModelError(thrownFields)) {
|
|
99506
|
-
const thrownMsg = err instanceof Error ? err.message : String(err);
|
|
99507
|
-
sessionError = { message: thrownMsg, fields: thrownFields };
|
|
99508
|
-
}
|
|
99509
|
-
}
|
|
99510
99539
|
}
|
|
99511
99540
|
if (sessionError) {
|
|
99512
99541
|
log("info", "\u9519\u8BEF\u5B57\u6BB5\u68C0\u67E5", {
|
|
@@ -99538,12 +99567,12 @@ async function handleChat(ctx, deps) {
|
|
|
99538
99567
|
modelID: modelOverride.modelID
|
|
99539
99568
|
});
|
|
99540
99569
|
clearSessionError(session.id);
|
|
99541
|
-
await client.session.
|
|
99570
|
+
await client.session.promptAsync({
|
|
99542
99571
|
path: { id: session.id },
|
|
99543
99572
|
query,
|
|
99544
99573
|
body: { ...baseBody, model: modelOverride }
|
|
99545
99574
|
});
|
|
99546
|
-
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
|
|
99575
|
+
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal });
|
|
99547
99576
|
log("info", "\u6A21\u578B\u6062\u590D\u540E\u54CD\u5E94\u5B8C\u6210", {
|
|
99548
99577
|
sessionKey,
|
|
99549
99578
|
sessionId: session.id,
|
|
@@ -99561,19 +99590,25 @@ async function handleChat(ctx, deps) {
|
|
|
99561
99590
|
return;
|
|
99562
99591
|
}
|
|
99563
99592
|
} catch (recoveryErr) {
|
|
99593
|
+
if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
|
|
99594
|
+
log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
|
|
99595
|
+
if (placeholderId) {
|
|
99596
|
+
await deleteMessage(feishuClient, placeholderId).catch(() => {
|
|
99597
|
+
});
|
|
99598
|
+
}
|
|
99599
|
+
return;
|
|
99600
|
+
}
|
|
99564
99601
|
const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
|
|
99565
99602
|
if (recoveryErr instanceof SessionErrorDetected) {
|
|
99566
99603
|
sessionError = recoveryErr.sessionError;
|
|
99567
99604
|
clearSessionError(session.id);
|
|
99568
99605
|
} else {
|
|
99569
|
-
await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
|
|
99570
99606
|
const sseError = getSessionError(session.id);
|
|
99571
99607
|
if (sseError) {
|
|
99572
99608
|
sessionError = sseError;
|
|
99573
99609
|
clearSessionError(session.id);
|
|
99574
99610
|
} else {
|
|
99575
|
-
|
|
99576
|
-
sessionError = { message: errMsg, fields: thrownFields };
|
|
99611
|
+
sessionError = { message: errMsg, fields: [] };
|
|
99577
99612
|
}
|
|
99578
99613
|
}
|
|
99579
99614
|
log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", {
|
|
@@ -99596,7 +99631,7 @@ async function handleChat(ctx, deps) {
|
|
|
99596
99631
|
sessionKey,
|
|
99597
99632
|
chatType,
|
|
99598
99633
|
error: thrownError,
|
|
99599
|
-
...sessionError ? { sessionError: sessionError.message } : {
|
|
99634
|
+
...sessionError ? { sessionError: sessionError.message } : {}
|
|
99600
99635
|
});
|
|
99601
99636
|
const msg = "\u274C " + errorMessage;
|
|
99602
99637
|
await replyOrUpdate(feishuClient, chatId, placeholderId, msg);
|
|
@@ -99707,6 +99742,113 @@ function extractLastAssistantText(messages) {
|
|
|
99707
99742
|
return parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join("\n").trim();
|
|
99708
99743
|
}
|
|
99709
99744
|
|
|
99745
|
+
// src/handler/session-queue.ts
|
|
99746
|
+
var states = /* @__PURE__ */ new Map();
|
|
99747
|
+
function getOrCreateState(sessionKey) {
|
|
99748
|
+
const existing = states.get(sessionKey);
|
|
99749
|
+
if (existing) return existing;
|
|
99750
|
+
const state = {
|
|
99751
|
+
controller: null,
|
|
99752
|
+
currentTask: null,
|
|
99753
|
+
queue: [],
|
|
99754
|
+
processing: false
|
|
99755
|
+
};
|
|
99756
|
+
states.set(sessionKey, state);
|
|
99757
|
+
return state;
|
|
99758
|
+
}
|
|
99759
|
+
function cleanupStateIfIdle(sessionKey, state) {
|
|
99760
|
+
if (!state.processing && state.queue.length === 0) {
|
|
99761
|
+
states.delete(sessionKey);
|
|
99762
|
+
}
|
|
99763
|
+
}
|
|
99764
|
+
async function enqueueMessage(ctx, deps) {
|
|
99765
|
+
if (!ctx.shouldReply) {
|
|
99766
|
+
await handleChat(ctx, deps);
|
|
99767
|
+
return;
|
|
99768
|
+
}
|
|
99769
|
+
const sessionKey = buildSessionKey(
|
|
99770
|
+
ctx.chatType,
|
|
99771
|
+
ctx.chatType === "p2p" ? ctx.senderId : ctx.chatId
|
|
99772
|
+
);
|
|
99773
|
+
if (ctx.chatType === "p2p") {
|
|
99774
|
+
await handleP2PMessage(sessionKey, ctx, deps);
|
|
99775
|
+
} else {
|
|
99776
|
+
await handleGroupMessage(sessionKey, ctx, deps);
|
|
99777
|
+
}
|
|
99778
|
+
}
|
|
99779
|
+
async function handleP2PMessage(sessionKey, ctx, deps) {
|
|
99780
|
+
const state = getOrCreateState(sessionKey);
|
|
99781
|
+
while (state.processing) {
|
|
99782
|
+
if (state.controller) {
|
|
99783
|
+
state.controller.abort();
|
|
99784
|
+
await abortServerSession(sessionKey, deps);
|
|
99785
|
+
}
|
|
99786
|
+
if (state.currentTask) {
|
|
99787
|
+
await state.currentTask.catch(() => {
|
|
99788
|
+
});
|
|
99789
|
+
}
|
|
99790
|
+
}
|
|
99791
|
+
const controller = new AbortController();
|
|
99792
|
+
state.controller = controller;
|
|
99793
|
+
state.processing = true;
|
|
99794
|
+
const task = processMessage(ctx, deps, controller.signal).finally(() => {
|
|
99795
|
+
state.processing = false;
|
|
99796
|
+
state.controller = null;
|
|
99797
|
+
state.currentTask = null;
|
|
99798
|
+
cleanupStateIfIdle(sessionKey, state);
|
|
99799
|
+
});
|
|
99800
|
+
state.currentTask = task;
|
|
99801
|
+
await task;
|
|
99802
|
+
}
|
|
99803
|
+
async function handleGroupMessage(sessionKey, ctx, deps) {
|
|
99804
|
+
const state = getOrCreateState(sessionKey);
|
|
99805
|
+
state.queue.push({ ctx, deps });
|
|
99806
|
+
if (state.processing) return;
|
|
99807
|
+
await drainLoop(sessionKey, state);
|
|
99808
|
+
}
|
|
99809
|
+
async function drainLoop(sessionKey, state) {
|
|
99810
|
+
state.processing = true;
|
|
99811
|
+
try {
|
|
99812
|
+
while (state.queue.length > 0) {
|
|
99813
|
+
const item = state.queue.shift();
|
|
99814
|
+
if (!item) break;
|
|
99815
|
+
const controller = new AbortController();
|
|
99816
|
+
state.controller = controller;
|
|
99817
|
+
try {
|
|
99818
|
+
await processMessage(item.ctx, item.deps, controller.signal);
|
|
99819
|
+
} catch (err) {
|
|
99820
|
+
item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
|
|
99821
|
+
sessionKey,
|
|
99822
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99823
|
+
});
|
|
99824
|
+
} finally {
|
|
99825
|
+
state.controller = null;
|
|
99826
|
+
}
|
|
99827
|
+
}
|
|
99828
|
+
} finally {
|
|
99829
|
+
state.processing = false;
|
|
99830
|
+
cleanupStateIfIdle(sessionKey, state);
|
|
99831
|
+
}
|
|
99832
|
+
}
|
|
99833
|
+
async function processMessage(ctx, deps, signal) {
|
|
99834
|
+
await handleChat(ctx, deps, signal);
|
|
99835
|
+
}
|
|
99836
|
+
async function abortServerSession(sessionKey, deps) {
|
|
99837
|
+
const { client, log, directory } = deps;
|
|
99838
|
+
const query = directory ? { directory } : void 0;
|
|
99839
|
+
const cached = getCachedSession(sessionKey);
|
|
99840
|
+
if (!cached) return;
|
|
99841
|
+
try {
|
|
99842
|
+
await client.session.abort({ path: { id: cached.id }, query });
|
|
99843
|
+
log("info", "\u5DF2\u4E2D\u65AD\u5F53\u524D\u4F1A\u8BDD\u5904\u7406", { sessionKey, sessionId: cached.id });
|
|
99844
|
+
} catch (err) {
|
|
99845
|
+
log("warn", "\u4E2D\u65AD\u4F1A\u8BDD\u5931\u8D25", {
|
|
99846
|
+
sessionKey,
|
|
99847
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99848
|
+
});
|
|
99849
|
+
}
|
|
99850
|
+
}
|
|
99851
|
+
|
|
99710
99852
|
// src/feishu/history.ts
|
|
99711
99853
|
var DEFAULT_PAGE_SIZE = 50;
|
|
99712
99854
|
async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options) {
|
|
@@ -99791,6 +99933,7 @@ ${body}`;
|
|
|
99791
99933
|
|
|
99792
99934
|
// src/index.ts
|
|
99793
99935
|
var SERVICE_NAME = "opencode-feishu";
|
|
99936
|
+
var LOG_PREFIX = "[feishu]";
|
|
99794
99937
|
var isDebug = !!process.env.FEISHU_DEBUG;
|
|
99795
99938
|
var DEFAULT_CONFIG = {
|
|
99796
99939
|
timeout: 12e4,
|
|
@@ -99812,14 +99955,15 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99812
99955
|
const { client } = ctx;
|
|
99813
99956
|
let gateway = null;
|
|
99814
99957
|
const log = (level, message, extra) => {
|
|
99958
|
+
const prefixed = `${LOG_PREFIX} ${message}`;
|
|
99815
99959
|
if (isDebug) {
|
|
99816
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), service: SERVICE_NAME, level, message, ...extra }));
|
|
99960
|
+
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), service: SERVICE_NAME, level, message: prefixed, ...extra }));
|
|
99817
99961
|
}
|
|
99818
99962
|
client.app.log({
|
|
99819
99963
|
body: {
|
|
99820
99964
|
service: SERVICE_NAME,
|
|
99821
99965
|
level,
|
|
99822
|
-
message,
|
|
99966
|
+
message: prefixed,
|
|
99823
99967
|
extra
|
|
99824
99968
|
}
|
|
99825
99969
|
}).catch(() => {
|
|
@@ -99873,7 +100017,7 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99873
100017
|
botOpenId,
|
|
99874
100018
|
onMessage: async (msgCtx) => {
|
|
99875
100019
|
if (!msgCtx.content.trim() || !gateway) return;
|
|
99876
|
-
await
|
|
100020
|
+
await enqueueMessage(msgCtx, {
|
|
99877
100021
|
config: resolvedConfig,
|
|
99878
100022
|
client,
|
|
99879
100023
|
feishuClient: gateway.client,
|