From 310f9166759845efbebba8745d6100b8772be4b7 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Wed, 21 Jan 2026 12:54:02 -0800 Subject: [PATCH] fix(models): handle out-of-range pages --- src/auto-reply/reply/commands-models.test.ts | 9 ++++++ src/auto-reply/reply/commands-models.ts | 33 ++++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/auto-reply/reply/commands-models.test.ts b/src/auto-reply/reply/commands-models.test.ts index 0dd321399..d225db991 100644 --- a/src/auto-reply/reply/commands-models.test.ts +++ b/src/auto-reply/reply/commands-models.test.ts @@ -74,11 +74,20 @@ describe("/models command", () => { const result = await handleCommands(params); expect(result.shouldContinue).toBe(false); expect(result.reply?.text).toContain("Models (anthropic)"); + expect(result.reply?.text).toContain("page 1/"); expect(result.reply?.text).toContain("anthropic/claude-opus-4-5"); expect(result.reply?.text).toContain("Switch: /model "); expect(result.reply?.text).toContain("All: /models anthropic all"); }); + it("errors on out-of-range pages", async () => { + const params = buildParams("/models anthropic 4", cfg); + const result = await handleCommands(params); + expect(result.shouldContinue).toBe(false); + expect(result.reply?.text).toContain("Page out of range"); + expect(result.reply?.text).toContain("valid: 1-"); + }); + it("handles unknown providers", async () => { const params = buildParams("/models not-a-provider", cfg); const result = await handleCommands(params); diff --git a/src/auto-reply/reply/commands-models.ts b/src/auto-reply/reply/commands-models.ts index fbc5560f5..3bb545746 100644 --- a/src/auto-reply/reply/commands-models.ts +++ b/src/auto-reply/reply/commands-models.ts @@ -143,23 +143,44 @@ export const handleModelsCommand: CommandHandler = async (params, allowTextComma const models = [...(byProvider.get(provider) ?? new Set())].sort(); const total = models.length; + if (total === 0) { + const lines: string[] = [ + `Models (${provider}) — none`, + "", + "Browse: /models", + "Switch: /model ", + ]; + return { reply: { text: lines.join("\n") }, shouldContinue: false }; + } + const effectivePageSize = all ? total : pageSize; - const startIndex = (page - 1) * effectivePageSize; + const pageCount = effectivePageSize > 0 ? Math.ceil(total / effectivePageSize) : 1; + const safePage = all ? 1 : Math.max(1, Math.min(page, pageCount)); + + if (!all && page !== safePage) { + const lines: string[] = [ + `Page out of range: ${page} (valid: 1-${pageCount})`, + "", + `Try: /models ${provider} ${safePage}`, + `All: /models ${provider} all`, + ]; + return { reply: { text: lines.join("\n") }, shouldContinue: false }; + } + + const startIndex = (safePage - 1) * effectivePageSize; const endIndexExclusive = Math.min(total, startIndex + effectivePageSize); const pageModels = models.slice(startIndex, endIndexExclusive); - const header = `Models (${provider}) — showing ${startIndex + 1}-${endIndexExclusive} of ${total}`; + const header = `Models (${provider}) — showing ${startIndex + 1}-${endIndexExclusive} of ${total} (page ${safePage}/${pageCount})`; const lines: string[] = [header]; for (const id of pageModels) { lines.push(`- ${provider}/${id}`); } - const pageCount = effectivePageSize > 0 ? Math.ceil(total / effectivePageSize) : 1; - lines.push("", "Switch: /model "); - if (!all && page < pageCount) { - lines.push(`More: /models ${provider} ${page + 1}`); + if (!all && safePage < pageCount) { + lines.push(`More: /models ${provider} ${safePage + 1}`); } if (!all) { lines.push(`All: /models ${provider} all`);