Files
clawdbot/src/auto-reply/reply/directive-handling.model-picker.ts
Jake 634a429c50 fix(model-picker): list each provider/model combo separately (#970)
* fix(model-picker): list each provider/model combo separately

Previously, /model grouped models by name and showed all providers
that offer the same model (e.g. 'claude-sonnet-4-5 — anthropic, google-antigravity').
This was confusing because:
1. Users couldn't tell which provider would be used when selecting by number
2. The display implied choice between providers but selection was automatic

Now each provider/model combination is listed separately so users
can explicitly select the exact provider they want.

- Remove model grouping in buildModelPickerItems
- Display format changed from 'model — providers' to 'provider/model'
- pickProviderForModel now returns the single provider directly
- Updated tests to reflect new behavior

* fix: simplify model picker entries (#970) (thanks @mcinteerj)

---------

Co-authored-by: Keith the Silly Goose <keith@42bolton.macnet.nz>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-01-15 22:20:11 +00:00

86 lines
2.2 KiB
TypeScript

import { type ModelRef, normalizeProviderId } from "../../agents/model-selection.js";
import type { ClawdbotConfig } from "../../config/config.js";
export type ModelPickerCatalogEntry = {
provider: string;
id: string;
name?: string;
};
export type ModelPickerItem = ModelRef;
const MODEL_PICK_PROVIDER_PREFERENCE = [
"anthropic",
"openai",
"openai-codex",
"minimax",
"synthetic",
"google",
"zai",
"openrouter",
"opencode",
"github-copilot",
"groq",
"cerebras",
"mistral",
"xai",
"lmstudio",
] as const;
const PROVIDER_RANK = new Map<string, number>(
MODEL_PICK_PROVIDER_PREFERENCE.map((provider, idx) => [provider, idx]),
);
function compareProvidersForPicker(a: string, b: string): number {
const pa = PROVIDER_RANK.get(a);
const pb = PROVIDER_RANK.get(b);
if (pa !== undefined && pb !== undefined) return pa - pb;
if (pa !== undefined) return -1;
if (pb !== undefined) return 1;
return a.localeCompare(b);
}
export function buildModelPickerItems(catalog: ModelPickerCatalogEntry[]): ModelPickerItem[] {
const seen = new Set<string>();
const out: ModelPickerItem[] = [];
for (const entry of catalog) {
const provider = normalizeProviderId(entry.provider);
const model = entry.id?.trim();
if (!provider || !model) continue;
const key = `${provider}/${model}`;
if (seen.has(key)) continue;
seen.add(key);
out.push({ model, provider });
}
// Sort by provider preference first, then by model name
out.sort((a, b) => {
const providerOrder = compareProvidersForPicker(a.provider, b.provider);
if (providerOrder !== 0) return providerOrder;
return a.model.toLowerCase().localeCompare(b.model.toLowerCase());
});
return out;
}
export function resolveProviderEndpointLabel(
provider: string,
cfg: ClawdbotConfig,
): { endpoint?: string; api?: string } {
const normalized = normalizeProviderId(provider);
const providers = (cfg.models?.providers ?? {}) as Record<
string,
{ baseUrl?: string; api?: string } | undefined
>;
const entry = providers[normalized];
const endpoint = entry?.baseUrl?.trim();
const api = entry?.api?.trim();
return {
endpoint: endpoint || undefined,
api: api || undefined,
};
}