fix(feishu): preserve per-chat model state
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
parseList,
|
||||
parseApprovalDecisionArgs,
|
||||
parseTextContent,
|
||||
preservedChatStateFields,
|
||||
splitMessage,
|
||||
stripGroupPrefix
|
||||
} from "./lib.mjs";
|
||||
@@ -266,6 +267,7 @@ async function ensureThread(chatId, { forceNew = false } = {}) {
|
||||
});
|
||||
|
||||
const state = {
|
||||
...preservedChatStateFields(existing),
|
||||
threadId: thread.id,
|
||||
lastSeq: 0,
|
||||
activeTurnId: null,
|
||||
@@ -505,7 +507,9 @@ async function resumeThread(chatId, args) {
|
||||
return;
|
||||
}
|
||||
const detail = await runtimeJson(`/v1/threads/${encodeURIComponent(threadId)}`);
|
||||
const existing = await threadStore.getChat(chatId);
|
||||
await threadStore.setChat(chatId, {
|
||||
...preservedChatStateFields(existing),
|
||||
threadId,
|
||||
lastSeq: Number(detail.latest_seq || 0),
|
||||
activeTurnId: null,
|
||||
@@ -601,22 +605,28 @@ async function sendText(chatId, text) {
|
||||
throw new Error("Lark SDK client does not expose im message create API");
|
||||
}
|
||||
|
||||
let canReply = Boolean(replyMessage);
|
||||
for (const chunk of splitMessage(text, config.maxReplyChars)) {
|
||||
const body = {
|
||||
msg_type: "text",
|
||||
content: JSON.stringify({ text: chunk })
|
||||
};
|
||||
if (replyMessage) {
|
||||
await replyMessage({
|
||||
path: { message_id: replyToMessageId },
|
||||
data: body
|
||||
});
|
||||
} else {
|
||||
await createMessage({
|
||||
params: { receive_id_type: "chat_id" },
|
||||
data: { ...body, receive_id: chatId }
|
||||
});
|
||||
if (canReply) {
|
||||
try {
|
||||
await replyMessage({
|
||||
path: { message_id: replyToMessageId },
|
||||
data: body
|
||||
});
|
||||
continue;
|
||||
} catch (error) {
|
||||
canReply = false;
|
||||
console.warn("Feishu reply API failed; falling back to message create", error);
|
||||
}
|
||||
}
|
||||
await createMessage({
|
||||
params: { receive_id_type: "chat_id" },
|
||||
data: { ...body, receive_id: chatId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,17 @@ export function commandAction(command) {
|
||||
}
|
||||
}
|
||||
|
||||
export function preservedChatStateFields(state = {}) {
|
||||
const preserved = {};
|
||||
if (Object.prototype.hasOwnProperty.call(state || {}, "model")) {
|
||||
preserved.model = state.model || null;
|
||||
}
|
||||
if (state?.replyToMessageId) {
|
||||
preserved.replyToMessageId = state.replyToMessageId;
|
||||
}
|
||||
return preserved;
|
||||
}
|
||||
|
||||
export function splitMessage(text, maxChars = 3500) {
|
||||
const value = String(text || "");
|
||||
const chars = Array.from(value);
|
||||
@@ -346,6 +357,7 @@ export function helpText() {
|
||||
"/threads - recent runtime threads",
|
||||
"/new - create a new thread for this chat",
|
||||
"/resume <thread_id> - bind this chat to an existing thread",
|
||||
"/model <name|default> - set or reset this chat's model",
|
||||
"/interrupt - interrupt the active turn",
|
||||
"/compact - compact the current thread",
|
||||
"/allow <approval_id> [remember] - approve a pending tool call",
|
||||
|
||||
@@ -12,8 +12,10 @@ import {
|
||||
parseCommand,
|
||||
parseList,
|
||||
parseTextContent,
|
||||
preservedChatStateFields,
|
||||
splitMessage,
|
||||
stripGroupPrefix,
|
||||
helpText,
|
||||
validateBridgeConfig
|
||||
} from "../src/lib.mjs";
|
||||
|
||||
@@ -89,12 +91,36 @@ test("commandAction maps bridge commands and falls back to prompts", () => {
|
||||
kind: "resume",
|
||||
threadId: "thread-1"
|
||||
});
|
||||
assert.deepEqual(commandAction(parseCommand("/model deepseek-v4-pro")), {
|
||||
kind: "set_model",
|
||||
modelName: "deepseek-v4-pro"
|
||||
});
|
||||
assert.deepEqual(commandAction(parseCommand("/unknown value")), {
|
||||
kind: "prompt",
|
||||
prompt: "/unknown value"
|
||||
});
|
||||
});
|
||||
|
||||
test("helpText documents per-chat model switching", () => {
|
||||
assert.match(helpText(), /\/model <name\|default>/);
|
||||
});
|
||||
|
||||
test("preservedChatStateFields carries model across state replacement", () => {
|
||||
assert.deepEqual(
|
||||
preservedChatStateFields({
|
||||
threadId: "old-thread",
|
||||
model: "deepseek-v4-flash",
|
||||
replyToMessageId: "om_123",
|
||||
activeTurnId: "turn-1"
|
||||
}),
|
||||
{
|
||||
model: "deepseek-v4-flash",
|
||||
replyToMessageId: "om_123"
|
||||
}
|
||||
);
|
||||
assert.deepEqual(preservedChatStateFields({ model: null }), { model: null });
|
||||
});
|
||||
|
||||
test("parseApprovalDecisionArgs extracts remember flag", () => {
|
||||
assert.deepEqual(parseApprovalDecisionArgs("ap_123 remember"), {
|
||||
approvalId: "ap_123",
|
||||
|
||||
Reference in New Issue
Block a user