Mattermost: add interactive model picker (#38767)

Merged via squash.

Prepared head SHA: 0883654e88
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
This commit is contained in:
Muhammed Mukhthar CM
2026-03-07 21:45:29 +05:30
committed by GitHub
parent 33e7394861
commit 4f08dcccfd
23 changed files with 1867 additions and 290 deletions

View File

@@ -1,12 +1,11 @@
import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
import { resolveModelAuthLabel } from "../../agents/model-auth-label.js";
import { loadModelCatalog } from "../../agents/model-catalog.js";
import {
buildAllowedModelSet,
buildModelAliasIndex,
normalizeProviderId,
resolveConfiguredModelRef,
resolveDefaultModelForAgent,
resolveModelRefFromString,
} from "../../agents/model-selection.js";
import type { OpenClawConfig } from "../../config/config.js";
@@ -35,11 +34,13 @@ export type ModelsProviderData = {
* Build provider/model data from config and catalog.
* Exported for reuse by callback handlers.
*/
export async function buildModelsProviderData(cfg: OpenClawConfig): Promise<ModelsProviderData> {
const resolvedDefault = resolveConfiguredModelRef({
export async function buildModelsProviderData(
cfg: OpenClawConfig,
agentId?: string,
): Promise<ModelsProviderData> {
const resolvedDefault = resolveDefaultModelForAgent({
cfg,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL,
agentId,
});
const catalog = await loadModelCatalog({ config: cfg });
@@ -220,6 +221,7 @@ export async function resolveModelsCommandReply(params: {
commandBodyNormalized: string;
surface?: string;
currentModel?: string;
agentId?: string;
agentDir?: string;
sessionEntry?: SessionEntry;
}): Promise<ReplyPayload | null> {
@@ -231,7 +233,7 @@ export async function resolveModelsCommandReply(params: {
const argText = body.replace(/^\/models\b/i, "").trim();
const { provider, page, pageSize, all } = parseModelsArgs(argText);
const { byProvider, providers } = await buildModelsProviderData(params.cfg);
const { byProvider, providers } = await buildModelsProviderData(params.cfg, params.agentId);
const isTelegram = params.surface === "telegram";
// Provider list (no provider specified)
@@ -386,6 +388,7 @@ export const handleModelsCommand: CommandHandler = async (params, allowTextComma
commandBodyNormalized,
surface: params.ctx.Surface,
currentModel: params.model ? `${params.provider}/${params.model}` : undefined,
agentId: modelsAgentId,
agentDir: modelsAgentDir,
sessionEntry: params.sessionEntry,
});

View File

@@ -907,6 +907,28 @@ describe("/models command", () => {
expect(result.reply?.text).toContain("localai/ultra-chat");
expect(result.reply?.text).not.toContain("Unknown provider");
});
it("threads the routed agent through /models replies", async () => {
const scopedCfg = {
commands: { text: true },
agents: {
defaults: { model: { primary: "anthropic/claude-opus-4-5" } },
list: [{ id: "support", model: "localai/ultra-chat" }],
},
} as unknown as OpenClawConfig;
const params = buildPolicyParams("/models", scopedCfg, {
Provider: "discord",
Surface: "discord",
});
const result = await handleCommands({
...params,
agentId: "support",
sessionKey: "agent:support:main",
});
expect(result.reply?.text).toContain("localai");
});
});
describe("handleCommands plugin commands", () => {