fix(telegram): fix model button selection to actually change model

The synthetic message approach used for Telegram model button callbacks
was silently failing - the callback was answered but the model never
actually changed. This replaces the broken synthetic /model command
with direct session store updates.

Changes:
- Replace processMessage() synthetic approach with updateSessionStore()
- Add applyModelOverrideToSessionEntry() to directly set model override
- Update resolveTelegramSessionState() to return sessionKey
- Add visual feedback with / emojis and bold text
- Remove inline keyboard after successful selection
- Fix isDefault detection: selecting default model now clears override
  instead of pinning it, matching /model command behavior
- Auth checks already applied before callback handler (lines 1108-1157)
- Properly resolve agent-specific default model from agents.list or
  agents.defaults to handle shorthand refs like 'gpt-4o'
- Add allowlist validation: verify model is in byProvider before
  persisting to prevent stale buttons from bypassing policy changes

Fixes the bug where clicking model buttons appeared to work but
didn't actually change the active model.

Test: Click /models → select provider → select model → verify
message shows 'Model changed to X' and next message uses that model.
This commit is contained in:
MAG-4 2026-03-10 01:52:19 +00:00 committed by Radek Sienkiewicz
parent 65572c2a2b
commit e62e95b87b
No known key found for this signature in database
GPG Key ID: F76BB7FE39C6D0C7
1 changed files with 69 additions and 10 deletions

View File

@ -20,6 +20,7 @@ import {
loadSessionStore,
resolveSessionStoreEntry,
resolveStorePath,
updateSessionStore,
} from "../config/sessions.js";
import type { DmPolicy } from "../config/types.base.js";
import type {
@ -33,6 +34,7 @@ import { MediaFetchError } from "../media/fetch.js";
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
import { resolveAgentRoute } from "../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../routing/session-key.js";
import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import {
isSenderAllowed,
@ -300,6 +302,7 @@ export const registerTelegramHandlers = ({
}): {
agentId: string;
sessionEntry: ReturnType<typeof loadSessionStore>[string] | undefined;
sessionKey: string;
model?: string;
} => {
const resolvedThreadId =
@ -339,6 +342,7 @@ export const registerTelegramHandlers = ({
return {
agentId: route.agentId,
sessionEntry: entry,
sessionKey,
model: storedOverride.provider
? `${storedOverride.provider}/${storedOverride.model}`
: storedOverride.model,
@ -350,6 +354,7 @@ export const registerTelegramHandlers = ({
return {
agentId: route.agentId,
sessionEntry: entry,
sessionKey,
model: `${provider}/${model}`,
};
}
@ -357,6 +362,7 @@ export const registerTelegramHandlers = ({
return {
agentId: route.agentId,
sessionEntry: entry,
sessionKey,
model: typeof modelCfg === "string" ? modelCfg : modelCfg?.primary,
};
};
@ -1374,16 +1380,69 @@ export const registerTelegramHandlers = ({
);
return;
}
// Process model selection as a synthetic message with /model command
const syntheticMessage = buildSyntheticTextMessage({
base: callbackMessage,
from: callback.from,
text: `/model ${selection.provider}/${selection.model}`,
});
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
forceWasMentioned: true,
messageIdOverride: callback.id,
});
const modelSet = byProvider.get(selection.provider);
if (!modelSet?.has(selection.model)) {
await editMessageWithButtons(
`❌ Model "${selection.provider}/${selection.model}" is not allowed.`,
[],
);
return;
}
// Directly set model override in session
try {
// Get session store path
const storePath = resolveStorePath(cfg.session?.store, {
agentId: sessionState.agentId,
});
const agentConfig = cfg.agents?.list?.find((a) => a.id === sessionState.agentId);
const agentModelConfig = agentConfig?.model ?? cfg.agents?.defaults?.model;
const rawModelRef =
typeof agentModelConfig === "string" ? agentModelConfig : agentModelConfig?.primary;
let resolvedDefaultRef = sessionState.model;
if (rawModelRef) {
const trimmed = rawModelRef.trim();
if (trimmed.includes("/")) {
resolvedDefaultRef = trimmed;
} else {
const currentProvider = sessionState.model?.split("/")[0];
resolvedDefaultRef = currentProvider
? `${currentProvider}/${trimmed}`
: sessionState.model;
}
}
const isDefaultSelection =
`${selection.provider}/${selection.model}` === resolvedDefaultRef;
await updateSessionStore(storePath, (store) => {
const sessionKey = sessionState.sessionKey;
const entry = store[sessionKey] ?? {};
store[sessionKey] = entry;
applyModelOverrideToSessionEntry({
entry,
selection: {
provider: selection.provider,
model: selection.model,
isDefault: isDefaultSelection,
},
});
});
// Update message to show success with visual feedback
const actionText = isDefaultSelection
? "reset to default"
: `changed to **${selection.provider}/${selection.model}**`;
await editMessageWithButtons(
`✅ Model ${actionText}\n\nThis model will be used for your next message.`,
[], // Empty buttons = remove inline keyboard
);
} catch (err) {
await editMessageWithButtons(`❌ Failed to change model: ${String(err)}`, []);
}
return;
}