opencode-feishu 0.6.1 → 0.7.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 +119 -110
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99176,76 +99176,27 @@ async function updateMessage(client, messageId, text2) {
|
|
|
99176
99176
|
}
|
|
99177
99177
|
}
|
|
99178
99178
|
|
|
99179
|
-
// src/
|
|
99180
|
-
var
|
|
99181
|
-
var
|
|
99182
|
-
|
|
99183
|
-
|
|
99184
|
-
|
|
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
|
|
99188
|
-
const
|
|
99189
|
-
if (
|
|
99190
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99331
|
-
|
|
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
|
-
|
|
99371
|
-
|
|
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;
|