mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 03:04:45 +00:00
fix(discord): defer model picker interactions
This commit is contained in:
@@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram: keep no-response DM turns quiet instead of rewriting them into visible silent-reply chatter. Fixes #78188. (#78228) Thanks @Beandon13.
|
||||
- Telegram: handle managed select button callbacks before the raw callback fallback while preserving delimiter-containing option values such as `env|prod`. (#79816) Thanks @moeedahmed.
|
||||
- OpenAI-compatible models: handle JSON chat-completion bodies returned to streaming requests, preserving reasoning fields and visible text instead of completing an empty agent turn. Fixes #77870.
|
||||
- Discord/models: defer model picker component interactions before loading route, model, and preference data, preventing "This interaction failed" timeouts under gateway load. Fixes #77283. Thanks @colin-chang.
|
||||
- xAI: expose `/think low|medium|high` for reasoning-capable Grok models and keep `reasoning.effort` on native Responses payloads while preserving off-only behavior for non-reasoning routes. Fixes #79210. Thanks @colinmcintosh.
|
||||
- CLI/media: let explicit image description model refs use bundled static provider catalogs and generic model-backed image hooks, so `openclaw infer image describe --model zai/glm-4.6v` works like direct model runs and Anthropic auth probes avoid stale Claude 3 Haiku catalog entries.
|
||||
- Models/Anthropic: add `anthropic/claude-haiku-4-5` to Anthropic API-key agent allowlist defaults when an Anthropic default model is configured, so cron model overrides can select the current Haiku alias. Fixes #78000.
|
||||
|
||||
@@ -147,6 +147,17 @@ export async function handleDiscordModelPickerInteraction(params: {
|
||||
return;
|
||||
}
|
||||
|
||||
let deferredUpdate = interaction.acknowledged;
|
||||
if (!deferredUpdate) {
|
||||
const deferred = await params.safeInteractionCall("model picker defer", () =>
|
||||
interaction.acknowledge(),
|
||||
);
|
||||
if (deferred === null) {
|
||||
return;
|
||||
}
|
||||
deferredUpdate = true;
|
||||
}
|
||||
|
||||
const route = await resolveDiscordModelPickerRoute({
|
||||
interaction,
|
||||
cfg: ctx.cfg,
|
||||
@@ -175,7 +186,9 @@ export async function handleDiscordModelPickerInteraction(params: {
|
||||
limit: 5,
|
||||
});
|
||||
const updatePicker = async (payload: MessagePayload) =>
|
||||
await params.safeInteractionCall("model picker update", () => interaction.update(payload));
|
||||
await params.safeInteractionCall("model picker update", () =>
|
||||
deferredUpdate ? interaction.editReply(payload) : interaction.update(payload),
|
||||
);
|
||||
const showNotice = async (message: string) =>
|
||||
await updatePicker(buildDiscordModelPickerNoticePayload(message));
|
||||
|
||||
|
||||
@@ -44,7 +44,9 @@ type MockInteraction = {
|
||||
reply: ReturnType<typeof vi.fn>;
|
||||
followUp: ReturnType<typeof vi.fn>;
|
||||
update: ReturnType<typeof vi.fn>;
|
||||
editReply: ReturnType<typeof vi.fn>;
|
||||
acknowledge: ReturnType<typeof vi.fn>;
|
||||
acknowledged: boolean;
|
||||
client: object;
|
||||
};
|
||||
|
||||
@@ -96,7 +98,7 @@ function createModelPickerContext(): ModelPickerContext {
|
||||
|
||||
function createInteraction(params?: { userId?: string; values?: string[] }): MockInteraction {
|
||||
const userId = params?.userId ?? "owner";
|
||||
return {
|
||||
const interaction = {
|
||||
user: {
|
||||
id: userId,
|
||||
username: "tester",
|
||||
@@ -115,9 +117,16 @@ function createInteraction(params?: { userId?: string; values?: string[] }): Moc
|
||||
reply: vi.fn().mockResolvedValue({ ok: true }),
|
||||
followUp: vi.fn().mockResolvedValue({ ok: true }),
|
||||
update: vi.fn().mockResolvedValue({ ok: true }),
|
||||
acknowledge: vi.fn().mockResolvedValue({ ok: true }),
|
||||
editReply: vi.fn().mockResolvedValue({ ok: true }),
|
||||
acknowledge: vi.fn(),
|
||||
acknowledged: false,
|
||||
client: {},
|
||||
};
|
||||
interaction.acknowledge.mockImplementation(async () => {
|
||||
interaction.acknowledged = true;
|
||||
return { ok: true };
|
||||
});
|
||||
return interaction;
|
||||
}
|
||||
|
||||
function createDefaultModelPickerData(): ModelsProviderData {
|
||||
@@ -323,6 +332,28 @@ describe("Discord model picker interactions", () => {
|
||||
expect(loadSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("defers owner picker interactions before loading model data", async () => {
|
||||
const context = createModelPickerContext();
|
||||
const pickerData = createDefaultModelPickerData();
|
||||
const loadSpy = vi
|
||||
.spyOn(modelPickerModule, "loadDiscordModelPickerData")
|
||||
.mockImplementation(async () => {
|
||||
expect(interaction.acknowledge).toHaveBeenCalledTimes(1);
|
||||
return pickerData;
|
||||
});
|
||||
const select = createModelPickerFallbackSelect(context);
|
||||
const interaction = createInteraction({ userId: "owner", values: ["gpt-4o"] });
|
||||
|
||||
await select.run(
|
||||
interaction as unknown as PickerSelectInteraction,
|
||||
createModelsViewSelectData(),
|
||||
);
|
||||
|
||||
expect(loadSpy).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.editReply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires submit click before routing selected model through /model pipeline", async () => {
|
||||
const context = createModelPickerContext();
|
||||
const pickerData = createDefaultModelPickerData();
|
||||
@@ -338,7 +369,7 @@ describe("Discord model picker interactions", () => {
|
||||
dispatchCommandInteraction: dispatchSpy,
|
||||
});
|
||||
|
||||
expect(selectInteraction.update).toHaveBeenCalledTimes(1);
|
||||
expect(selectInteraction.editReply).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchSpy).not.toHaveBeenCalled();
|
||||
|
||||
const submitInteraction = await runSubmitButton({
|
||||
@@ -347,7 +378,7 @@ describe("Discord model picker interactions", () => {
|
||||
dispatchCommandInteraction: dispatchSpy,
|
||||
});
|
||||
|
||||
expect(submitInteraction.update).toHaveBeenCalledTimes(1);
|
||||
expect(submitInteraction.editReply).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchSpy).toHaveBeenCalledTimes(1);
|
||||
expectDispatchedModelSelection({
|
||||
dispatchSpy,
|
||||
@@ -547,8 +578,8 @@ describe("Discord model picker interactions", () => {
|
||||
|
||||
await button.run(interaction as unknown as PickerButtonInteraction, data);
|
||||
|
||||
expect(interaction.update).toHaveBeenCalledTimes(1);
|
||||
const updatePayload = interaction.update.mock.calls[0]?.[0];
|
||||
expect(interaction.editReply).toHaveBeenCalledTimes(1);
|
||||
const updatePayload = interaction.editReply.mock.calls[0]?.[0];
|
||||
if (!updatePayload) {
|
||||
throw new Error("recents button did not emit an update payload");
|
||||
}
|
||||
@@ -585,7 +616,7 @@ describe("Discord model picker interactions", () => {
|
||||
dispatchCommandInteraction: dispatchSpy,
|
||||
});
|
||||
|
||||
expect(submitInteraction.update).toHaveBeenCalledTimes(1);
|
||||
expect(submitInteraction.editReply).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchSpy).toHaveBeenCalledTimes(1);
|
||||
expectDispatchedModelSelection({ dispatchSpy, model: "openai/gpt-4o" });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user