| |
| |
| |
|
|
| import type { ThinkingLevel } from "@earendil-works/pi-agent-core"; |
| import { |
| type Api, |
| type AuthOperationOptions, |
| type KnownProvider, |
| type Model, |
| modelsAreEqual, |
| } from "@earendil-works/pi-ai"; |
| import chalk from "chalk"; |
| import { minimatch } from "minimatch"; |
| import { isValidThinkingLevel } from "../cli/args.ts"; |
| import { DEFAULT_THINKING_LEVEL } from "./defaults.ts"; |
| import type { ModelRuntime } from "./model-runtime.ts"; |
|
|
| |
| export const defaultModelPerProvider: Record<KnownProvider, string> = { |
| "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1", |
| "ant-ling": "Ring-2.6-1T", |
| anthropic: "claude-opus-4-8", |
| openai: "gpt-5.5", |
| "azure-openai-responses": "gpt-5.4", |
| "openai-codex": "gpt-5.5", |
| radius: "balanced", |
| nvidia: "nvidia/nemotron-3-super-120b-a12b", |
| deepseek: "deepseek-v4-pro", |
| google: "gemini-3.1-pro-preview", |
| "google-vertex": "gemini-3.1-pro-preview", |
| "github-copilot": "gpt-5.4", |
| openrouter: "moonshotai/kimi-k2.6", |
| "vercel-ai-gateway": "zai/glm-5.1", |
| xai: "grok-4.6", |
| groq: "openai/gpt-oss-120b", |
| cerebras: "gpt-oss-120b", |
| zai: "glm-5.3", |
| "zai-coding-cn": "glm-5.3", |
| mistral: "devstral-medium-latest", |
| minimax: "MiniMax-M2.7", |
| "minimax-cn": "MiniMax-M2.7", |
| moonshotai: "kimi-k2.6", |
| "moonshotai-cn": "kimi-k2.6", |
| huggingface: "moonshotai/Kimi-K2.6", |
| fireworks: "accounts/fireworks/models/kimi-k2p6", |
| together: "moonshotai/Kimi-K2.6", |
| baseten: "zai-org/GLM-5.2", |
| opencode: "kimi-k2.6", |
| "opencode-go": "kimi-k2.6", |
| "kimi-coding": "kimi-for-coding", |
| "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6", |
| "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6", |
| "qwen-token-plan": "qwen3.7-max", |
| "qwen-token-plan-cn": "qwen3.7-max", |
| "qwen-token-plan-individual": "qwen3.8-max", |
| xiaomi: "mimo-v2.5-pro", |
| "xiaomi-token-plan-cn": "mimo-v2.5-pro", |
| "xiaomi-token-plan-ams": "mimo-v2.5-pro", |
| "xiaomi-token-plan-sgp": "mimo-v2.5-pro", |
| }; |
|
|
| export interface ScopedModel { |
| model: Model<Api>; |
| |
| thinkingLevel?: ThinkingLevel; |
| } |
|
|
| |
| |
| |
| |
| function isAlias(id: string): boolean { |
| |
| if (id.endsWith("-latest")) return true; |
|
|
| |
| const datePattern = /-\d{8}$/; |
| return !datePattern.test(id); |
| } |
|
|
| |
| |
| |
| |
| |
| export function findExactModelReferenceMatch( |
| modelReference: string, |
| availableModels: Model<Api>[], |
| ): Model<Api> | undefined { |
| const trimmedReference = modelReference.trim(); |
| if (!trimmedReference) { |
| return undefined; |
| } |
|
|
| const normalizedReference = trimmedReference.toLowerCase(); |
|
|
| const canonicalMatches = availableModels.filter( |
| (model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference, |
| ); |
| if (canonicalMatches.length === 1) { |
| return canonicalMatches[0]; |
| } |
| if (canonicalMatches.length > 1) { |
| return undefined; |
| } |
|
|
| const slashIndex = trimmedReference.indexOf("/"); |
| if (slashIndex !== -1) { |
| const provider = trimmedReference.substring(0, slashIndex).trim(); |
| const modelId = trimmedReference.substring(slashIndex + 1).trim(); |
| if (provider && modelId) { |
| const providerMatches = availableModels.filter( |
| (model) => |
| model.provider.toLowerCase() === provider.toLowerCase() && |
| model.id.toLowerCase() === modelId.toLowerCase(), |
| ); |
| if (providerMatches.length === 1) { |
| return providerMatches[0]; |
| } |
| if (providerMatches.length > 1) { |
| return undefined; |
| } |
| } |
| } |
|
|
| const idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference); |
| return idMatches.length === 1 ? idMatches[0] : undefined; |
| } |
|
|
| |
| |
| |
| |
| function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Model<Api> | undefined { |
| const exactMatch = findExactModelReferenceMatch(modelPattern, availableModels); |
| if (exactMatch) { |
| return exactMatch; |
| } |
|
|
| |
| const matches = availableModels.filter( |
| (m) => |
| m.id.toLowerCase().includes(modelPattern.toLowerCase()) || |
| m.name?.toLowerCase().includes(modelPattern.toLowerCase()), |
| ); |
|
|
| if (matches.length === 0) { |
| return undefined; |
| } |
|
|
| |
| const aliases = matches.filter((m) => isAlias(m.id)); |
| const datedVersions = matches.filter((m) => !isAlias(m.id)); |
|
|
| if (aliases.length > 0) { |
| |
| aliases.sort((a, b) => b.id.localeCompare(a.id)); |
| return aliases[0]; |
| } else { |
| |
| datedVersions.sort((a, b) => b.id.localeCompare(a.id)); |
| return datedVersions[0]; |
| } |
| } |
|
|
| export interface ParsedModelResult { |
| model: Model<Api> | undefined; |
| |
| thinkingLevel?: ThinkingLevel; |
| warning: string | undefined; |
| } |
|
|
| function buildFallbackModel(provider: string, modelId: string, availableModels: Model<Api>[]): Model<Api> | undefined { |
| const providerModels = availableModels.filter((m) => m.provider === provider); |
| if (providerModels.length === 0) return undefined; |
|
|
| const defaultId = defaultModelPerProvider[provider as KnownProvider]; |
| const baseModel = defaultId |
| ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0]) |
| : providerModels[0]; |
|
|
| return { |
| ...baseModel, |
| id: modelId, |
| name: modelId, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseModelPattern( |
| pattern: string, |
| availableModels: Model<Api>[], |
| options?: { allowInvalidThinkingLevelFallback?: boolean }, |
| ): ParsedModelResult { |
| |
| const exactMatch = tryMatchModel(pattern, availableModels); |
| if (exactMatch) { |
| return { model: exactMatch, thinkingLevel: undefined, warning: undefined }; |
| } |
|
|
| |
| const lastColonIndex = pattern.lastIndexOf(":"); |
| if (lastColonIndex === -1) { |
| |
| return { model: undefined, thinkingLevel: undefined, warning: undefined }; |
| } |
|
|
| const prefix = pattern.substring(0, lastColonIndex); |
| const suffix = pattern.substring(lastColonIndex + 1); |
|
|
| if (isValidThinkingLevel(suffix)) { |
| |
| const result = parseModelPattern(prefix, availableModels, options); |
| if (result.model) { |
| |
| return { |
| model: result.model, |
| thinkingLevel: result.warning ? undefined : suffix, |
| warning: result.warning, |
| }; |
| } |
| return result; |
| } else { |
| |
| const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true; |
| if (!allowFallback) { |
| |
| |
| return { model: undefined, thinkingLevel: undefined, warning: undefined }; |
| } |
|
|
| |
| const result = parseModelPattern(prefix, availableModels, options); |
| if (result.model) { |
| return { |
| model: result.model, |
| thinkingLevel: undefined, |
| warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using default instead.`, |
| }; |
| } |
| return result; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export interface ModelScopeDiagnostic { |
| type: "warning"; |
| code: "no-match" | "invalid-thinking-level"; |
| message: string; |
| pattern: string; |
| } |
|
|
| export interface ResolveModelScopeResult { |
| scopedModels: ScopedModel[]; |
| diagnostics: ModelScopeDiagnostic[]; |
| } |
|
|
| export function resolveModelScopeFromModels( |
| patterns: string[], |
| models: readonly Model<Api>[], |
| ): ResolveModelScopeResult { |
| const availableModels = [...models]; |
| const scopedModels: ScopedModel[] = []; |
| const diagnostics: ModelScopeDiagnostic[] = []; |
|
|
| for (const pattern of patterns) { |
| |
| if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) { |
| |
| const colonIdx = pattern.lastIndexOf(":"); |
| let globPattern = pattern; |
| let thinkingLevel: ThinkingLevel | undefined; |
|
|
| if (colonIdx !== -1) { |
| const suffix = pattern.substring(colonIdx + 1); |
| if (isValidThinkingLevel(suffix)) { |
| thinkingLevel = suffix; |
| globPattern = pattern.substring(0, colonIdx); |
| } |
| } |
|
|
| const exactMatch = findExactModelReferenceMatch(globPattern, availableModels); |
| if (exactMatch) { |
| if (!scopedModels.find((sm) => modelsAreEqual(sm.model, exactMatch))) { |
| scopedModels.push({ model: exactMatch, thinkingLevel }); |
| } |
| continue; |
| } |
|
|
| |
| |
| const matchingModels = availableModels.filter((m) => { |
| const fullId = `${m.provider}/${m.id}`; |
| return minimatch(fullId, globPattern, { nocase: true }) || minimatch(m.id, globPattern, { nocase: true }); |
| }); |
|
|
| if (matchingModels.length === 0) { |
| diagnostics.push({ |
| type: "warning", |
| code: "no-match", |
| message: `No models match pattern "${pattern}"`, |
| pattern, |
| }); |
| continue; |
| } |
|
|
| for (const model of matchingModels) { |
| if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) { |
| scopedModels.push({ model, thinkingLevel }); |
| } |
| } |
| continue; |
| } |
|
|
| const { model, thinkingLevel, warning } = parseModelPattern(pattern, availableModels); |
|
|
| if (warning) { |
| diagnostics.push({ type: "warning", code: "invalid-thinking-level", message: warning, pattern }); |
| } |
|
|
| if (!model) { |
| diagnostics.push({ |
| type: "warning", |
| code: "no-match", |
| message: `No models match pattern "${pattern}"`, |
| pattern, |
| }); |
| continue; |
| } |
|
|
| |
| if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) { |
| scopedModels.push({ model, thinkingLevel }); |
| } |
| } |
|
|
| return { scopedModels, diagnostics }; |
| } |
|
|
| export async function resolveModelScopeWithDiagnostics( |
| patterns: string[], |
| modelRuntime: ModelRuntime, |
| options?: AuthOperationOptions, |
| ): Promise<ResolveModelScopeResult> { |
| return resolveModelScopeFromModels(patterns, await modelRuntime.getAvailable(undefined, options)); |
| } |
|
|
| export async function resolveModelScope( |
| patterns: string[], |
| modelRuntime: ModelRuntime, |
| options?: AuthOperationOptions, |
| ): Promise<ScopedModel[]> { |
| const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(patterns, modelRuntime, options); |
| for (const diagnostic of diagnostics) { |
| console.warn(chalk.yellow(`Warning: ${diagnostic.message}`)); |
| } |
| return scopedModels; |
| } |
|
|
| export interface ResolveCliModelResult { |
| model: Model<Api> | undefined; |
| thinkingLevel?: ThinkingLevel; |
| warning: string | undefined; |
| |
| |
| |
| |
| error: string | undefined; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function resolveCliModel(options: { |
| cliProvider?: string; |
| cliModel?: string; |
| cliThinking?: ThinkingLevel; |
| modelRuntime: ModelRuntime; |
| }): ResolveCliModelResult { |
| const { cliProvider, cliModel, cliThinking, modelRuntime } = options; |
|
|
| if (!cliModel) { |
| return { model: undefined, warning: undefined, error: undefined }; |
| } |
|
|
| |
| |
| const availableModels = [...modelRuntime.getModels()]; |
| if (availableModels.length === 0) { |
| return { |
| model: undefined, |
| warning: undefined, |
| error: "No models available. Check your installation or add models to models.json.", |
| }; |
| } |
|
|
| |
| const providerMap = new Map<string, string>(); |
| for (const m of availableModels) { |
| providerMap.set(m.provider.toLowerCase(), m.provider); |
| } |
|
|
| let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined; |
| if (cliProvider && !provider) { |
| return { |
| model: undefined, |
| warning: undefined, |
| error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| let pattern = cliModel; |
| let inferredProvider = false; |
|
|
| if (!provider) { |
| const slashIndex = cliModel.indexOf("/"); |
| if (slashIndex !== -1) { |
| const maybeProvider = cliModel.substring(0, slashIndex); |
| const canonical = providerMap.get(maybeProvider.toLowerCase()); |
| if (canonical) { |
| provider = canonical; |
| pattern = cliModel.substring(slashIndex + 1); |
| inferredProvider = true; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| if (!provider) { |
| const lower = cliModel.toLowerCase(); |
| const exactMatches = availableModels.filter( |
| (m) => m.id.toLowerCase() === lower || `${m.provider}/${m.id}`.toLowerCase() === lower, |
| ); |
| if (exactMatches.length === 1) { |
| return { model: exactMatches[0], warning: undefined, thinkingLevel: undefined, error: undefined }; |
| } |
| if (exactMatches.length > 1) { |
| const authenticatedExactMatches = exactMatches.filter((m) => modelRuntime.hasConfiguredAuth(m.provider)); |
| if (authenticatedExactMatches.length === 1) { |
| return { |
| model: authenticatedExactMatches[0], |
| warning: undefined, |
| thinkingLevel: undefined, |
| error: undefined, |
| }; |
| } |
|
|
| const matches = exactMatches |
| .map((m) => `${m.provider}/${m.id}`) |
| .sort((a, b) => a.localeCompare(b)) |
| .join(", "); |
| const authHint = |
| authenticatedExactMatches.length === 0 |
| ? "No matching provider is authenticated." |
| : "More than one matching provider is authenticated."; |
| return { |
| model: undefined, |
| warning: undefined, |
| thinkingLevel: undefined, |
| error: `Model "${cliModel}" is ambiguous across providers: ${matches}. ${authHint} Use --provider or provider/model.`, |
| }; |
| } |
| } |
|
|
| if (cliProvider && provider) { |
| |
| const prefix = `${provider}/`; |
| if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) { |
| pattern = cliModel.substring(prefix.length); |
| } |
| } |
|
|
| const candidates = provider ? availableModels.filter((m) => m.provider === provider) : availableModels; |
| const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, { |
| allowInvalidThinkingLevelFallback: false, |
| }); |
|
|
| if (model) { |
| |
| |
| |
| |
| |
| if (inferredProvider) { |
| const rawExactMatches = availableModels.filter( |
| (m) => m.id.toLowerCase() === cliModel.toLowerCase() && !modelsAreEqual(m, model), |
| ); |
| if (rawExactMatches.length > 0 && !modelRuntime.hasConfiguredAuth(model.provider)) { |
| const authenticatedRawMatches = rawExactMatches.filter((m) => modelRuntime.hasConfiguredAuth(m.provider)); |
| if (authenticatedRawMatches.length === 1) { |
| return { |
| model: authenticatedRawMatches[0], |
| thinkingLevel: undefined, |
| warning: undefined, |
| error: undefined, |
| }; |
| } |
| } |
| } |
| return { model, thinkingLevel, warning, error: undefined }; |
| } |
|
|
| |
| |
| |
| |
| if (inferredProvider) { |
| const lower = cliModel.toLowerCase(); |
| const exact = availableModels.find( |
| (m) => m.id.toLowerCase() === lower || `${m.provider}/${m.id}`.toLowerCase() === lower, |
| ); |
| if (exact) { |
| return { model: exact, warning: undefined, thinkingLevel: undefined, error: undefined }; |
| } |
| |
| const fallback = parseModelPattern(cliModel, availableModels, { |
| allowInvalidThinkingLevelFallback: false, |
| }); |
| if (fallback.model) { |
| return { |
| model: fallback.model, |
| thinkingLevel: fallback.thinkingLevel, |
| warning: fallback.warning, |
| error: undefined, |
| }; |
| } |
| } |
|
|
| if (provider) { |
| |
| |
| |
| let fallbackPattern = pattern; |
| let fallbackThinking: ThinkingLevel | undefined; |
| if (!cliThinking) { |
| const lastColon = pattern.lastIndexOf(":"); |
| if (lastColon !== -1) { |
| const suffix = pattern.substring(lastColon + 1); |
| if (isValidThinkingLevel(suffix)) { |
| fallbackPattern = pattern.substring(0, lastColon); |
| fallbackThinking = suffix; |
| } |
| } |
| } |
|
|
| const fallbackModel = buildFallbackModel(provider, fallbackPattern, availableModels); |
| if (fallbackModel) { |
| const requestedThinking = cliThinking ?? fallbackThinking; |
| const model = |
| requestedThinking && requestedThinking !== "off" ? { ...fallbackModel, reasoning: true } : fallbackModel; |
| const fallbackWarning = warning |
| ? `${warning} Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.` |
| : `Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`; |
| return { model, thinkingLevel: fallbackThinking, warning: fallbackWarning, error: undefined }; |
| } |
| } |
|
|
| const display = provider ? `${provider}/${pattern}` : cliModel; |
| return { |
| model: undefined, |
| thinkingLevel: undefined, |
| warning, |
| error: `Model "${display}" not found. Use --list-models to see available models.`, |
| }; |
| } |
|
|
| export interface InitialModelResult { |
| model: Model<Api> | undefined; |
| thinkingLevel: ThinkingLevel; |
| fallbackMessage: string | undefined; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function findInitialModel(options: { |
| cliProvider?: string; |
| cliModel?: string; |
| scopedModels: ScopedModel[]; |
| isContinuing: boolean; |
| defaultProvider?: string; |
| defaultModelId?: string; |
| defaultThinkingLevel?: ThinkingLevel; |
| modelThinkingLevels?: Record<string, ThinkingLevel>; |
| modelRuntime: ModelRuntime; |
| }): Promise<InitialModelResult> { |
| const { |
| cliProvider, |
| cliModel, |
| scopedModels, |
| isContinuing, |
| defaultProvider, |
| defaultModelId, |
| defaultThinkingLevel, |
| modelThinkingLevels, |
| modelRuntime, |
| } = options; |
|
|
| let model: Model<Api> | undefined; |
| let thinkingLevel: ThinkingLevel = DEFAULT_THINKING_LEVEL; |
|
|
| |
| if (cliProvider && cliModel) { |
| const resolved = resolveCliModel({ |
| cliProvider, |
| cliModel, |
| modelRuntime, |
| }); |
| if (resolved.error) { |
| console.error(chalk.red(resolved.error)); |
| process.exit(1); |
| } |
| if (resolved.model) { |
| return { model: resolved.model, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined }; |
| } |
| } |
|
|
| |
| if (scopedModels.length > 0 && !isContinuing) { |
| const scopedModel = scopedModels[0]; |
| const perModel = modelThinkingLevels?.[`${scopedModel.model.provider}/${scopedModel.model.id}`]; |
| return { |
| model: scopedModel.model, |
| thinkingLevel: scopedModel.thinkingLevel ?? perModel ?? defaultThinkingLevel ?? DEFAULT_THINKING_LEVEL, |
| fallbackMessage: undefined, |
| }; |
| } |
|
|
| |
| if (defaultProvider && defaultModelId) { |
| const found = modelRuntime.getModel(defaultProvider, defaultModelId); |
| if (found && modelRuntime.hasConfiguredAuth(found.provider)) { |
| model = found; |
| const perModel = modelThinkingLevels?.[`${defaultProvider}/${defaultModelId}`]; |
| if (perModel) { |
| thinkingLevel = perModel; |
| } else if (defaultThinkingLevel) { |
| thinkingLevel = defaultThinkingLevel; |
| } |
| return { model, thinkingLevel, fallbackMessage: undefined }; |
| } |
| } |
|
|
| |
| const availableModels = [...modelRuntime.getAvailableSnapshot()]; |
|
|
| if (availableModels.length > 0) { |
| |
| for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) { |
| const defaultId = defaultModelPerProvider[provider]; |
| const match = availableModels.find((m) => m.provider === provider && m.id === defaultId); |
| if (match) { |
| return { model: match, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined }; |
| } |
| } |
|
|
| |
| return { model: availableModels[0], thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined }; |
| } |
|
|
| |
| return { model: undefined, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined }; |
| } |
|
|
| |
| |
| |
| export async function restoreModelFromSession( |
| savedProvider: string, |
| savedModelId: string, |
| currentModel: Model<Api> | undefined, |
| shouldPrintMessages: boolean, |
| modelRuntime: ModelRuntime, |
| ): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> { |
| const restoredModel = modelRuntime.getModel(savedProvider, savedModelId); |
|
|
| |
| const hasConfiguredAuth = restoredModel ? modelRuntime.hasConfiguredAuth(restoredModel.provider) : false; |
|
|
| if (restoredModel && hasConfiguredAuth) { |
| if (shouldPrintMessages) { |
| console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`)); |
| } |
| return { model: restoredModel, fallbackMessage: undefined }; |
| } |
|
|
| |
| const reason = !restoredModel ? "model no longer exists" : "no auth configured"; |
|
|
| if (shouldPrintMessages) { |
| console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`)); |
| } |
|
|
| |
| if (currentModel) { |
| if (shouldPrintMessages) { |
| console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`)); |
| } |
| return { |
| model: currentModel, |
| fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`, |
| }; |
| } |
|
|
| |
| const availableModels = [...modelRuntime.getAvailableSnapshot()]; |
|
|
| if (availableModels.length > 0) { |
| |
| let fallbackModel: Model<Api> | undefined; |
| for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) { |
| const defaultId = defaultModelPerProvider[provider]; |
| const match = availableModels.find((m) => m.provider === provider && m.id === defaultId); |
| if (match) { |
| fallbackModel = match; |
| break; |
| } |
| } |
|
|
| |
| if (!fallbackModel) { |
| fallbackModel = availableModels[0]; |
| } |
|
|
| if (shouldPrintMessages) { |
| console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`)); |
| } |
|
|
| return { |
| model: fallbackModel, |
| fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`, |
| }; |
| } |
|
|
| |
| return { model: undefined, fallbackMessage: undefined }; |
| } |
|
|