diff --git a/src/auto-reply/reply.triggers.test.ts b/src/auto-reply/reply.triggers.test.ts index 0c9582ea9..41614632e 100644 --- a/src/auto-reply/reply.triggers.test.ts +++ b/src/auto-reply/reply.triggers.test.ts @@ -314,11 +314,90 @@ describe("trigger handling", () => { "1) claude-opus-4-5 — anthropic, openrouter", ); expect(normalized).toContain("3) gpt-5.2 — openai, openai-codex"); + expect(normalized).toContain("More: /model status"); expect(normalized).not.toContain("reasoning"); expect(normalized).not.toContain("image"); }); }); + it("rejects invalid /model <#> selections", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + const sessionKey = "telegram:slash:111"; + + const res = await getReplyFromConfig( + { + Body: "/model 99", + From: "telegram:111", + To: "telegram:111", + ChatType: "direct", + Provider: "telegram", + Surface: "telegram", + SessionKey: sessionKey, + }, + {}, + cfg, + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(normalizeTestText(text ?? "")).toContain( + 'Invalid model selection "99". Use /model to list.', + ); + + const store = loadSessionStore(cfg.session.store); + expect(store[sessionKey]?.providerOverride).toBeUndefined(); + expect(store[sessionKey]?.modelOverride).toBeUndefined(); + }); + }); + + it("prefers the current provider when selecting /model <#>", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + const sessionKey = "telegram:slash:111"; + + await fs.writeFile( + cfg.session.store, + JSON.stringify( + { + [sessionKey]: { + sessionId: "session-openrouter", + updatedAt: Date.now(), + providerOverride: "openrouter", + modelOverride: "anthropic/claude-opus-4-5", + }, + }, + null, + 2, + ), + ); + + const res = await getReplyFromConfig( + { + Body: "/model 1", + From: "telegram:111", + To: "telegram:111", + ChatType: "direct", + Provider: "telegram", + Surface: "telegram", + SessionKey: sessionKey, + }, + {}, + cfg, + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(normalizeTestText(text ?? "")).toContain( + "Model set to openrouter/anthropic/claude-opus-4-5", + ); + + const store = loadSessionStore(cfg.session.store); + expect(store[sessionKey]?.providerOverride).toBe("openrouter"); + expect(store[sessionKey]?.modelOverride).toBe( + "anthropic/claude-opus-4-5", + ); + }); + }); + it("selects a model by index via /model <#>", async () => { await withTempHome(async (home) => { const cfg = makeCfg(home); @@ -349,6 +428,28 @@ describe("trigger handling", () => { }); }); + it("shows endpoint default in /model status when not configured", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + const res = await getReplyFromConfig( + { + Body: "/model status", + From: "telegram:111", + To: "telegram:111", + ChatType: "direct", + Provider: "telegram", + Surface: "telegram", + SessionKey: "telegram:slash:111", + }, + {}, + cfg, + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(normalizeTestText(text ?? "")).toContain("endpoint: default"); + }); + }); + it("includes endpoint details in /model status when configured", async () => { await withTempHome(async (home) => { const cfg = { diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index e4ccd7646..5f3a736e5 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -505,15 +505,14 @@ export async function getReplyFromConfig( }; } } - const hasDirective = + const hasInlineDirective = parsedDirectives.hasThinkDirective || parsedDirectives.hasVerboseDirective || parsedDirectives.hasReasoningDirective || parsedDirectives.hasElevatedDirective || - parsedDirectives.hasStatusDirective || parsedDirectives.hasModelDirective || parsedDirectives.hasQueueDirective; - if (hasDirective) { + if (hasInlineDirective) { const stripped = stripStructuralPrefixes(parsedDirectives.cleaned); const noMentions = isGroup ? stripMentions(stripped, ctx, cfg, agentId) @@ -698,6 +697,9 @@ export async function getReplyFromConfig( : directives.rawModelDirective; if (!command.isAuthorizedSender) { + cleanedBody = existingBody; + sessionCtx.Body = cleanedBody; + sessionCtx.BodyStripped = cleanedBody; directives = { ...directives, hasThinkDirective: false,