fix(telegram): retry failed model browser callbacks

This commit is contained in:
Vincent Koc
2026-04-13 17:42:06 +01:00
parent 1f7f8b02d0
commit 88111453cb
2 changed files with 91 additions and 15 deletions

View File

@@ -1498,7 +1498,11 @@ export const registerTelegramHandlers = ({
if (modelCallback.type === "providers" || modelCallback.type === "back") {
if (providers.length === 0) {
await editMessageWithButtons("No providers available.", []);
try {
await editMessageWithButtons("No providers available.", []);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}
const providerInfos: ProviderInfo[] = providers.map((p) => ({
@@ -1506,7 +1510,11 @@ export const registerTelegramHandlers = ({
count: byProvider.get(p)?.size ?? 0,
}));
const buttons = buildProviderKeyboard(providerInfos);
await editMessageWithButtons("Select a provider:", buttons);
try {
await editMessageWithButtons("Select a provider:", buttons);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}
@@ -1520,10 +1528,14 @@ export const registerTelegramHandlers = ({
count: byProvider.get(p)?.size ?? 0,
}));
const buttons = buildProviderKeyboard(providerInfos);
await editMessageWithButtons(
`Unknown provider: ${provider}\n\nSelect a provider:`,
buttons,
);
try {
await editMessageWithButtons(
`Unknown provider: ${provider}\n\nSelect a provider:`,
buttons,
);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}
const models = [...modelSet].toSorted();
@@ -1549,7 +1561,11 @@ export const registerTelegramHandlers = ({
agentDir: resolveAgentDir(cfg, sessionState.agentId),
sessionEntry: sessionState.sessionEntry,
});
await editMessageWithButtons(text, buttons);
try {
await editMessageWithButtons(text, buttons);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}
@@ -1565,19 +1581,27 @@ export const registerTelegramHandlers = ({
count: byProvider.get(p)?.size ?? 0,
}));
const buttons = buildProviderKeyboard(providerInfos);
await editMessageWithButtons(
`Could not resolve model "${selection.model}".\n\nSelect a provider:`,
buttons,
);
try {
await editMessageWithButtons(
`Could not resolve model "${selection.model}".\n\nSelect a provider:`,
buttons,
);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}
const modelSet = byProvider.get(selection.provider);
if (!modelSet?.has(selection.model)) {
await editMessageWithButtons(
`❌ Model "${selection.provider}/${selection.model}" is not allowed.`,
[],
);
try {
await editMessageWithButtons(
`❌ Model "${selection.provider}/${selection.model}" is not allowed.`,
[],
);
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
return;
}

View File

@@ -3104,4 +3104,56 @@ describe("createTelegramBot", () => {
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy.mock.calls[0]?.[1]).toContain("plugin bind approval");
});
it("retries model provider callbacks after a bubbled edit failure", async () => {
createTelegramBot({ token: "tok" });
const callbackHandler = getOnHandler("callback_query");
const middlewares = middlewareUseSpy.mock.calls
.map((call) => call[0])
.filter(
(fn): fn is (ctx: Record<string, unknown>, next: () => Promise<void>) => Promise<void> =>
typeof fn === "function",
);
const runMiddlewareChain = async (ctx: Record<string, unknown>) => {
let idx = -1;
const dispatch = async (i: number): Promise<void> => {
if (i <= idx) {
throw new Error("middleware dispatch called multiple times");
}
idx = i;
const fn = middlewares[i];
if (!fn) {
await callbackHandler(ctx);
return;
}
await fn(ctx, async () => dispatch(i + 1));
};
await dispatch(0);
};
const ctx = {
update: { update_id: 889 },
callbackQuery: {
id: "cbq-model-provider-retry-1",
data: "mdl_prov",
from: { id: 9, first_name: "Ada", username: "ada_bot" },
message: {
chat: { id: 1234, type: "private" },
date: 1736380800,
message_id: 23,
},
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
};
editMessageTextSpy.mockImplementationOnce(async () => {
throw new Error("edit boom");
});
await expect(runMiddlewareChain(ctx)).rejects.toThrow("edit boom");
await runMiddlewareChain(ctx);
expect(editMessageTextSpy).toHaveBeenCalledTimes(2);
expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Select a provider:");
});
});