mirror of https://github.com/openclaw/openclaw.git
Telegram: support compact model callback fallback
This commit is contained in:
parent
c582a54554
commit
54eb13893f
|
|
@ -1261,11 +1261,29 @@ export const registerTelegramHandlers = ({
|
|||
|
||||
if (modelCallback.type === "select") {
|
||||
const { provider, model } = modelCallback;
|
||||
let resolvedProvider = provider;
|
||||
if (!resolvedProvider) {
|
||||
const matchingProviders = providers.filter((id) => byProvider.get(id)?.has(model));
|
||||
if (matchingProviders.length === 1) {
|
||||
resolvedProvider = matchingProviders[0];
|
||||
} else {
|
||||
const providerInfos: ProviderInfo[] = providers.map((p) => ({
|
||||
id: p,
|
||||
count: byProvider.get(p)?.size ?? 0,
|
||||
}));
|
||||
const buttons = buildProviderKeyboard(providerInfos);
|
||||
await editMessageWithButtons(
|
||||
`Could not resolve model "${model}".\n\nSelect a provider:`,
|
||||
buttons,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Process model selection as a synthetic message with /model command
|
||||
const syntheticMessage = buildSyntheticTextMessage({
|
||||
base: callbackMessage,
|
||||
from: callback.from,
|
||||
text: `/model ${provider}/${model}`,
|
||||
text: `/model ${resolvedProvider}/${model}`,
|
||||
});
|
||||
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
|
||||
forceWasMentioned: true,
|
||||
|
|
|
|||
|
|
@ -319,6 +319,107 @@ describe("createTelegramBot", () => {
|
|||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-4");
|
||||
});
|
||||
|
||||
it("routes compact model callbacks by inferring provider", async () => {
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
const modelId = "us.anthropic.claude-3-5-sonnet-20240620-v1:0";
|
||||
|
||||
createTelegramBot({
|
||||
token: "tok",
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: `bedrock/${modelId}`,
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
expect(callbackHandler).toBeDefined();
|
||||
|
||||
await callbackHandler({
|
||||
callbackQuery: {
|
||||
id: "cbq-model-compact-1",
|
||||
data: `mdl_sel/${modelId}`,
|
||||
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
date: 1736380800,
|
||||
message_id: 14,
|
||||
},
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0]?.[0];
|
||||
expect(payload?.Body).toContain(`/model amazon-bedrock/${modelId}`);
|
||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-compact-1");
|
||||
});
|
||||
|
||||
it("rejects ambiguous compact model callbacks and returns provider list", async () => {
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
editMessageTextSpy.mockClear();
|
||||
|
||||
createTelegramBot({
|
||||
token: "tok",
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/shared-model",
|
||||
models: {
|
||||
"anthropic/shared-model": {},
|
||||
"openai/shared-model": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
expect(callbackHandler).toBeDefined();
|
||||
|
||||
await callbackHandler({
|
||||
callbackQuery: {
|
||||
id: "cbq-model-compact-2",
|
||||
data: "mdl_sel/shared-model",
|
||||
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
date: 1736380800,
|
||||
message_id: 15,
|
||||
},
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
expect(editMessageTextSpy).toHaveBeenCalledTimes(1);
|
||||
expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain(
|
||||
'Could not resolve model "shared-model".',
|
||||
);
|
||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-compact-2");
|
||||
});
|
||||
|
||||
it("includes sender identity in group envelope headers", async () => {
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ describe("parseModelCallbackData", () => {
|
|||
{ type: "select", provider: "anthropic", model: "claude-sonnet-4-5" },
|
||||
],
|
||||
["mdl_sel_openai/gpt-4/turbo", { type: "select", provider: "openai", model: "gpt-4/turbo" }],
|
||||
[
|
||||
"mdl_sel/us.anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
{ type: "select", model: "us.anthropic.claude-3-5-sonnet-20240620-v1:0" },
|
||||
],
|
||||
[
|
||||
"mdl_sel/anthropic/claude-3-7-sonnet",
|
||||
{ type: "select", model: "anthropic/claude-3-7-sonnet" },
|
||||
],
|
||||
[" mdl_prov ", { type: "providers" }],
|
||||
] as const;
|
||||
for (const [input, expected] of cases) {
|
||||
|
|
@ -36,6 +44,7 @@ describe("parseModelCallbackData", () => {
|
|||
"mdl_invalid",
|
||||
"mdl_list_",
|
||||
"mdl_sel_noslash",
|
||||
"mdl_sel/",
|
||||
];
|
||||
for (const input of invalid) {
|
||||
expect(parseModelCallbackData(input), input).toBeNull();
|
||||
|
|
@ -209,6 +218,18 @@ describe("buildModelsKeyboard", () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("uses compact selection callback when provider/model callback exceeds 64 bytes", () => {
|
||||
const model = "us.anthropic.claude-3-5-sonnet-20240620-v1:0";
|
||||
const result = buildModelsKeyboard({
|
||||
provider: "amazon-bedrock",
|
||||
models: [model],
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
});
|
||||
|
||||
expect(result[0]?.[0]?.callback_data).toBe(`mdl_sel/${model}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildBrowseProvidersButton", () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
* Callback data patterns (max 64 bytes for Telegram):
|
||||
* - mdl_prov - show providers list
|
||||
* - mdl_list_{prov}_{pg} - show models for provider (page N, 1-indexed)
|
||||
* - mdl_sel_{provider/id} - select model
|
||||
* - mdl_sel_{provider/id} - select model (standard)
|
||||
* - mdl_sel/{model} - select model (compact fallback when standard is >64 bytes)
|
||||
* - mdl_back - back to providers list
|
||||
*/
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ export type ButtonRow = Array<{ text: string; callback_data: string }>;
|
|||
export type ParsedModelCallback =
|
||||
| { type: "providers" }
|
||||
| { type: "list"; provider: string; page: number }
|
||||
| { type: "select"; provider: string; model: string }
|
||||
| { type: "select"; provider?: string; model: string }
|
||||
| { type: "back" };
|
||||
|
||||
export type ProviderInfo = {
|
||||
|
|
@ -57,6 +58,18 @@ export function parseModelCallbackData(data: string): ParsedModelCallback | null
|
|||
}
|
||||
}
|
||||
|
||||
// mdl_sel/{model} (compact fallback)
|
||||
const compactSelMatch = trimmed.match(/^mdl_sel\/(.+)$/);
|
||||
if (compactSelMatch) {
|
||||
const modelRef = compactSelMatch[1];
|
||||
if (modelRef) {
|
||||
return {
|
||||
type: "select",
|
||||
model: modelRef,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// mdl_sel_{provider/model}
|
||||
const selMatch = trimmed.match(/^mdl_sel_(.+)$/);
|
||||
if (selMatch) {
|
||||
|
|
@ -133,8 +146,12 @@ export function buildModelsKeyboard(params: ModelsKeyboardParams): ButtonRow[] {
|
|||
: currentModel;
|
||||
|
||||
for (const model of pageModels) {
|
||||
const callbackData = `mdl_sel_${provider}/${model}`;
|
||||
// Skip models that would exceed Telegram's callback_data limit
|
||||
const fullCallbackData = `mdl_sel_${provider}/${model}`;
|
||||
const callbackData =
|
||||
Buffer.byteLength(fullCallbackData, "utf8") <= MAX_CALLBACK_DATA_BYTES
|
||||
? fullCallbackData
|
||||
: `mdl_sel/${model}`;
|
||||
// Skip models that still exceed Telegram's callback_data limit
|
||||
if (Buffer.byteLength(callbackData, "utf8") > MAX_CALLBACK_DATA_BYTES) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue