import type { ModelCapabilities, ModelSelection, ServerConfig as T3ServerConfig, } from "@t3tools/contracts"; import type { MenuAction } from "@react-native-menu/menu"; import { buildProviderOptionSelectionsFromDescriptors, getProviderOptionDescriptors, } from "@t3tools/shared/model"; export type ModelOption = { readonly key: string; readonly label: string; readonly subtitle: string; readonly providerKey: string; readonly providerLabel: string; readonly providerDriver: string; readonly isDefault: boolean; readonly isLegacy: boolean; readonly capabilities: ModelCapabilities | null; readonly selection: ModelSelection; }; export type ProviderGroup = { readonly providerKey: string; readonly providerLabel: string; readonly models: ReadonlyArray; }; function providerDisplayLabel(provider: { readonly displayName?: string | undefined; readonly driver: string; readonly instanceId: string; }): string { if (provider.displayName) return provider.displayName; if (provider.driver === "codex") return "Codex"; if (provider.driver === "claudeAgent") return "Claude"; return provider.instanceId; } function normalizeSelectionOptions( selection: ModelSelection, capabilities: ModelCapabilities | null, ): ModelSelection { if (!capabilities) { return selection; } const options = buildProviderOptionSelectionsFromDescriptors( getProviderOptionDescriptors({ caps: capabilities, selections: selection.options, }), ); return options ? { ...selection, options } : { instanceId: selection.instanceId, model: selection.model, }; } /** * A stored model selection is only usable when its provider instance is * currently enabled, installed, and authenticated on the server. Returns the * selection unchanged when usable, otherwise `null` so callers fall through to * the server's default model. A missing config (environment offline) cannot be * validated, so stored selections pass through untouched. */ export function resolveSelectableModelSelection( config: T3ServerConfig | null | undefined, selection: ModelSelection | null, ): ModelSelection | null { if (!selection || !config) { return selection; } const provider = config.providers.find( (candidate) => candidate.instanceId === selection.instanceId, ); return provider && provider.enabled && provider.installed && provider.auth.status !== "unauthenticated" ? selection : null; } export function buildModelOptions( config: T3ServerConfig | null | undefined, fallbackModelSelection: ModelSelection | null, ): ReadonlyArray { const options = new Map(); for (const provider of config?.providers ?? []) { if (!provider.enabled || !provider.installed || provider.auth.status === "unauthenticated") { continue; } const providerLabel = providerDisplayLabel(provider); for (const model of provider.models) { const key = `${provider.instanceId}:${model.slug}`; options.set(key, { key, label: model.name, subtitle: providerLabel, providerKey: provider.instanceId, providerLabel, providerDriver: provider.driver, isDefault: model.isDefault === true, isLegacy: model.isLegacy === true, capabilities: model.capabilities, selection: normalizeSelectionOptions( { instanceId: provider.instanceId, model: model.slug, }, model.capabilities, ), }); } } if (fallbackModelSelection) { const key = `${fallbackModelSelection.instanceId}:${fallbackModelSelection.model}`; const existing = options.get(key); if (existing) { options.set(key, { ...existing, selection: normalizeSelectionOptions(fallbackModelSelection, existing.capabilities), }); } else { const providerLabel = fallbackModelSelection.instanceId; options.set(key, { key, label: fallbackModelSelection.model, subtitle: providerLabel, providerKey: fallbackModelSelection.instanceId, providerLabel, providerDriver: fallbackModelSelection.instanceId, isDefault: false, isLegacy: false, capabilities: null, selection: fallbackModelSelection, }); } } return [...options.values()]; } export function groupByProvider(options: ReadonlyArray): ReadonlyArray { const groups = new Map(); for (const option of options) { const existing = groups.get(option.providerKey); if (existing) { existing.models.push(option); } else { groups.set(option.providerKey, { providerLabel: option.providerLabel, models: [option], }); } } return [...groups.entries()].map(([providerKey, group]) => ({ providerKey, providerLabel: group.providerLabel, models: group.models, })); } function modelMenuAction(option: ModelOption, selectedModel: ModelSelection | null): MenuAction { return { id: `model:${option.key}`, title: option.label, state: option.selection.instanceId === selectedModel?.instanceId && option.selection.model === selectedModel.model ? "on" : undefined, }; } export function buildModelMenuActions( groups: ReadonlyArray, selectedModel: ModelSelection | null, ): MenuAction[] { return groups.flatMap((group) => { const currentModels = group.models.filter((model) => !model.isLegacy); const legacyModels = group.models.filter((model) => model.isLegacy); const selected = group.models.find( (model) => model.selection.instanceId === selectedModel?.instanceId && model.selection.model === selectedModel.model, ); return [ ...(currentModels.length > 0 ? [ { id: `provider:${group.providerKey}`, title: group.providerLabel, subtitle: selected && !selected.isLegacy ? selected.label : undefined, subactions: currentModels.map((option) => modelMenuAction(option, selectedModel)), }, ] : []), ...(legacyModels.length > 0 ? [ { id: `legacy-models:${group.providerKey}`, title: `${group.providerLabel} legacy models`, subtitle: selected?.isLegacy ? selected.label : undefined, subactions: legacyModels.map((option) => modelMenuAction(option, selectedModel)), }, ] : []), ]; }); }