Unified · Split
| @@ -2,7 +2,6 @@ import { · 1 unmodified line | ||
| 2 | 2 | type ClaudeSettings, |
| 3 | 3 | type ModelCapabilities, |
| 4 | 4 | type ModelSelection, |
| 5 | − ProviderDriverKind, | |
| 6 | 5 | type ServerProviderModel, |
| 7 | 6 | type ServerProviderSlashCommand, |
| 8 | 7 | } from "@t3tools/contracts"; |
| @@ -44,7 +43,6 @@ const DEFAULT_CLAUDE_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabili · 35 unmodified lines | ||
| 44 | 43 | optionDescriptors: [], |
| 45 | 44 | }); |
| 46 | 45 | |
| 47 | −const PROVIDER = ProviderDriverKind.make("claudeAgent"); | |
| 48 | 46 | const CLAUDE_PRESENTATION = { |
| 49 | 47 | displayName: "Claude", |
| 50 | 48 | showInteractionModeToggle: true, |
| @@ -691,7 +689,6 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( · 640 unmodified lines | ||
| 691 | 689 | const checkedAt = DateTime.formatIso(yield* DateTime.now); |
| 692 | 690 | const allModels = providerModelsFromSettings( |
| 693 | 691 | BUILT_IN_MODELS, |
| 694 | − PROVIDER, | |
| 695 | 692 | claudeSettings.customModels, |
| 696 | 693 | DEFAULT_CLAUDE_MODEL_CAPABILITIES, |
| 697 | 694 | ); |
| @@ -782,7 +779,6 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( · 84 unmodified lines | ||
| 782 | 779 | |
| 783 | 780 | const models = providerModelsFromSettings( |
| 784 | 781 | getBuiltInClaudeModelsForVersion(parsedVersion), |
| 785 | − PROVIDER, | |
| 786 | 782 | claudeSettings.customModels, |
| 787 | 783 | DEFAULT_CLAUDE_MODEL_CAPABILITIES, |
| 788 | 784 | ); |
| @@ -851,7 +847,6 @@ export const makePendingClaudeProvider = ( · 62 unmodified lines | ||
| 851 | 847 | const checkedAt = yield* nowIso; |
| 852 | 848 | const models = providerModelsFromSettings( |
| 853 | 849 | BUILT_IN_MODELS, |
| 854 | − PROVIDER, | |
| 855 | 850 | claudeSettings.customModels, |
| 856 | 851 | DEFAULT_CLAUDE_MODEL_CAPABILITIES, |
| 857 | 852 | ); |
| @@ -8,7 +8,6 @@ import type { · 7 unmodified lines | ||
| 8 | 8 | ServerProviderModel, |
| 9 | 9 | ServerProviderState, |
| 10 | 10 | } from "@t3tools/contracts"; |
| 11 | −import { ProviderDriverKind } from "@t3tools/contracts"; | |
| 12 | 11 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 13 | 12 | import { causeErrorTag } from "@t3tools/shared/observability"; |
| 14 | 13 | import * as Crypto from "effect/Crypto"; |
| @@ -51,7 +50,6 @@ import { CursorListAvailableModelsResponse } from "../acp/CursorAcpExtension.ts" · 36 unmodified lines | ||
| 51 | 50 | const decodeCursorListAvailableModelsResponse = Schema.decodeUnknownEffect( |
| 52 | 51 | CursorListAvailableModelsResponse, |
| 53 | 52 | ); |
| 54 | −const PROVIDER = ProviderDriverKind.make("cursor"); | |
| 55 | 53 | const CURSOR_PRESENTATION = { |
| 56 | 54 | displayName: "Cursor", |
| 57 | 55 | badgeLabel: "Early Access", |
| @@ -576,7 +574,7 @@ export const discoverCursorModelsViaAcp = ( · 518 unmodified lines | ||
| 576 | 574 | export function getCursorFallbackModels( |
| 577 | 575 | cursorSettings: Pick<CursorSettings, "customModels">, |
| 578 | 576 | ): ReadonlyArray<ServerProviderModel> { |
| 579 | − return providerModelsFromSettings([], PROVIDER, cursorSettings.customModels, EMPTY_CAPABILITIES); | |
| 577 | + return providerModelsFromSettings([], cursorSettings.customModels, EMPTY_CAPABILITIES); | |
| 580 | 578 | } |
| 581 | 579 | |
| 582 | 580 | /** Timeout for `agent about` — it's slower than a simple `--version` probe. */ |
| @@ -638,7 +636,6 @@ export function buildCursorProviderSnapshot(input: { · 55 unmodified lines | ||
| 638 | 636 | checkedAt: input.checkedAt, |
| 639 | 637 | models: providerModelsFromSettings( |
| 640 | 638 | input.discoveredModels ?? [], |
| 641 | − PROVIDER, | |
| 642 | 639 | input.cursorSettings.customModels, |
| 643 | 640 | EMPTY_CAPABILITIES, |
| 644 | 641 | ), |
| @@ -1,7 +1,6 @@ | ||
| 1 | 1 | import { |
| 2 | 2 | type GrokSettings, |
| 3 | 3 | type ModelCapabilities, |
| 4 | − ProviderDriverKind, | |
| 5 | 4 | type ServerProvider, |
| 6 | 5 | type ServerProviderModel, |
| 7 | 6 | } from "@t3tools/contracts"; |
| @@ -38,7 +37,6 @@ const GROK_PRESENTATION = { · 30 unmodified lines | ||
| 38 | 37 | showInteractionModeToggle: false, |
| 39 | 38 | requiresNewThreadForModelChange: true, |
| 40 | 39 | } as const; |
| 41 | −const PROVIDER = ProviderDriverKind.make("grok"); | |
| 42 | 40 | const EMPTY_CAPABILITIES: ModelCapabilities = createModelCapabilities({ |
| 43 | 41 | optionDescriptors: [], |
| 44 | 42 | }); |
| @@ -98,12 +96,7 @@ function grokModelsFromSettings( · 53 unmodified lines | ||
| 98 | 96 | customModels: ReadonlyArray<string> | undefined, |
| 99 | 97 | builtInModels: ReadonlyArray<ServerProviderModel> = GROK_BUILT_IN_MODELS, |
| 100 | 98 | ): ReadonlyArray<ServerProviderModel> { |
| 101 | − return providerModelsFromSettings( | |
| 102 | − builtInModels, | |
| 103 | − PROVIDER, | |
| 104 | − customModels ?? [], | |
| 105 | − EMPTY_CAPABILITIES, | |
| 106 | − ); | |
| 99 | + return providerModelsFromSettings(builtInModels, customModels ?? [], EMPTY_CAPABILITIES); | |
| 107 | 100 | } |
| 108 | 101 | |
| 109 | 102 | function buildGrokDiscoveredModelsFromSessionModelState( |
| @@ -1,5 +1,4 @@ | ||
| 1 | 1 | import { |
| 2 | − ProviderDriverKind, | |
| 3 | 2 | type ModelCapabilities, |
| 4 | 3 | type OpenCodeSettings, |
| 5 | 4 | type ServerProviderModel, |
| @@ -25,7 +24,6 @@ import { · 19 unmodified lines | ||
| 25 | 24 | } from "../opencodeRuntime.ts"; |
| 26 | 25 | import type { Agent, ProviderListResponse } from "@opencode-ai/sdk/v2"; |
| 27 | 26 | |
| 28 | −const PROVIDER = ProviderDriverKind.make("opencode"); | |
| 29 | 27 | const OPENCODE_PRESENTATION = { |
| 30 | 28 | displayName: "OpenCode", |
| 31 | 29 | showInteractionModeToggle: false, |
| @@ -259,7 +257,6 @@ export const makePendingOpenCodeProvider = ( · 227 unmodified lines | ||
| 259 | 257 | const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); |
| 260 | 258 | const models = providerModelsFromSettings( |
| 261 | 259 | [], |
| 262 | − PROVIDER, | |
| 263 | 260 | openCodeSettings.customModels, |
| 264 | 261 | DEFAULT_OPENCODE_MODEL_CAPABILITIES, |
| 265 | 262 | ); |
| @@ -319,12 +316,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu · 53 unmodified lines | ||
| 319 | 316 | presentation: OPENCODE_PRESENTATION, |
| 320 | 317 | enabled: openCodeSettings.enabled, |
| 321 | 318 | checkedAt, |
| 322 | − models: providerModelsFromSettings( | |
| 323 | − [], | |
| 324 | − PROVIDER, | |
| 325 | − customModels, | |
| 326 | − DEFAULT_OPENCODE_MODEL_CAPABILITIES, | |
| 327 | − ), | |
| 319 | + models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), | |
| 328 | 320 | probe: { |
| 329 | 321 | installed: failure.installed, |
| 330 | 322 | version, |
| @@ -340,12 +332,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu · 9 unmodified lines | ||
| 340 | 332 | presentation: OPENCODE_PRESENTATION, |
| 341 | 333 | enabled: false, |
| 342 | 334 | checkedAt, |
| 343 | − models: providerModelsFromSettings( | |
| 344 | − [], | |
| 345 | − PROVIDER, | |
| 346 | − customModels, | |
| 347 | − DEFAULT_OPENCODE_MODEL_CAPABILITIES, | |
| 348 | − ), | |
| 335 | + models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), | |
| 349 | 336 | probe: { |
| 350 | 337 | installed: false, |
| 351 | 338 | version: null, |
| @@ -391,12 +378,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu · 39 unmodified lines | ||
| 391 | 378 | presentation: OPENCODE_PRESENTATION, |
| 392 | 379 | enabled: openCodeSettings.enabled, |
| 393 | 380 | checkedAt, |
| 394 | − models: providerModelsFromSettings( | |
| 395 | − [], | |
| 396 | − PROVIDER, | |
| 397 | − customModels, | |
| 398 | − DEFAULT_OPENCODE_MODEL_CAPABILITIES, | |
| 399 | − ), | |
| 381 | + models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), | |
| 400 | 382 | probe: { |
| 401 | 383 | installed: true, |
| 402 | 384 | version, |
| @@ -444,7 +426,6 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu · 41 unmodified lines | ||
| 444 | 426 | |
| 445 | 427 | const models = providerModelsFromSettings( |
| 446 | 428 | flattenOpenCodeModels(inventoryExit.value), |
| 447 | − PROVIDER, | |
| 448 | 429 | customModels, |
| 449 | 430 | DEFAULT_OPENCODE_MODEL_CAPABILITIES, |
| 450 | 431 | ); |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | −import { ProviderDriverKind, type ModelCapabilities } from "@t3tools/contracts"; | |
| 2 | +import type { ModelCapabilities } from "@t3tools/contracts"; | |
| 3 | 3 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 4 | 4 | import { createModelCapabilities } from "@t3tools/shared/model"; |
| 5 | 5 | import * as Effect from "effect/Effect"; |
| @@ -38,7 +38,6 @@ describe("providerModelsFromSettings", () => { · 32 unmodified lines | ||
| 38 | 38 | it("applies the provided capabilities to custom models", () => { |
| 39 | 39 | const models = providerModelsFromSettings( |
| 40 | 40 | [], |
| 41 | − ProviderDriverKind.make("opencode"), | |
| 42 | 41 | ["openai/gpt-5"], |
| 43 | 42 | OPENCODE_CUSTOM_MODEL_CAPABILITIES, |
| 44 | 43 | ); |
| @@ -52,6 +51,25 @@ describe("providerModelsFromSettings", () => { · 7 unmodified lines | ||
| 52 | 51 | }, |
| 53 | 52 | ]); |
| 54 | 53 | }); |
| 54 | + | |
| 55 | + it("preserves a custom slug that collides with a provider alias", () => { | |
| 56 | + const capabilities = createModelCapabilities({ optionDescriptors: [] }); | |
| 57 | + const models = providerModelsFromSettings( | |
| 58 | + [ | |
| 59 | + { | |
| 60 | + slug: "claude-opus-4-8", | |
| 61 | + name: "Claude Opus 4.8", | |
| 62 | + isCustom: false, | |
| 63 | + capabilities, | |
| 64 | + }, | |
| 65 | + ], | |
| 66 | + [" opus "], | |
| 67 | + capabilities, | |
| 68 | + ); | |
| 69 | + | |
| 70 | + expect(models.map((model) => model.slug)).toEqual(["claude-opus-4-8", "opus"]); | |
| 71 | + expect(models[1]?.isCustom).toBe(true); | |
| 72 | + }); | |
| 55 | 73 | }); |
| 56 | 74 | |
| 57 | 75 | describe("ProviderCommandNotFoundError", () => { |
| @@ -13,7 +13,7 @@ import * as PlatformError from "effect/PlatformError"; · 12 unmodified lines | ||
| 13 | 13 | import * as Schema from "effect/Schema"; |
| 14 | 14 | import * as Stream from "effect/Stream"; |
| 15 | 15 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 16 | −import { normalizeModelSlug } from "@t3tools/shared/model"; | |
| 16 | +import { normalizeCustomModelSlug } from "@t3tools/shared/model"; | |
| 17 | 17 | import { isWindowsCommandNotFound } from "../processRunner.ts"; |
| 18 | 18 | import { createProviderVersionAdvisory } from "./providerMaintenance.ts"; |
| 19 | 19 | import { collectUint8StreamText } from "../stream/collectUint8StreamText.ts"; |
| @@ -140,7 +140,6 @@ export function parseGenericCliVersion(output: string): string | null { · 120 unmodified lines | ||
| 140 | 140 | |
| 141 | 141 | export function providerModelsFromSettings( |
| 142 | 142 | builtInModels: ReadonlyArray<ServerProviderModel>, |
| 143 | − provider: ProviderDriverKind, | |
| 144 | 143 | customModels: ReadonlyArray<string>, |
| 145 | 144 | customModelCapabilities: ModelCapabilities, |
| 146 | 145 | ): ReadonlyArray<ServerProviderModel> { |
| @@ -149,7 +148,7 @@ export function providerModelsFromSettings( · 2 unmodified lines | ||
| 149 | 148 | const customEntries: ServerProviderModel[] = []; |
| 150 | 149 | |
| 151 | 150 | for (const candidate of customModels) { |
| 152 | − const normalized = normalizeModelSlug(candidate, provider); | |
| 151 | + const normalized = normalizeCustomModelSlug(candidate); | |
| 153 | 152 | if (!normalized || seen.has(normalized)) { |
| 154 | 153 | continue; |
| 155 | 154 | } |
| @@ -16,7 +16,7 @@ import { · 15 unmodified lines | ||
| 16 | 16 | type ProviderInstanceId, |
| 17 | 17 | type ServerProviderModel, |
| 18 | 18 | } from "@t3tools/contracts"; |
| 19 | −import { normalizeModelSlug } from "@t3tools/shared/model"; | |
| 19 | +import { normalizeCustomModelSlug } from "@t3tools/shared/model"; | |
| 20 | 20 | |
| 21 | 21 | import { cn } from "../../lib/utils"; |
| 22 | 22 | import { sortModelsForProviderInstance } from "../../modelOrdering"; |
| @@ -111,7 +111,7 @@ export function ProviderModelsSection({ · 88 unmodified lines | ||
| 111 | 111 | }, [favoriteModelSet, modelOrder, models]); |
| 112 | 112 | |
| 113 | 113 | const handleAdd = () => { |
| 114 | − const normalized = driverKind ? normalizeModelSlug(input, driverKind) : input.trim() || null; | |
| 114 | + const normalized = normalizeCustomModelSlug(input); | |
| 115 | 115 | if (!normalized) { |
| 116 | 116 | setError("Enter a model slug."); |
| 117 | 117 | return; |
| @@ -101,6 +101,39 @@ describe("instance-scoped model selection", () => { · 100 unmodified lines | ||
| 101 | 101 | ).toBe("openai/gpt-5.5"); |
| 102 | 102 | }); |
| 103 | 103 | |
| 104 | + it("preserves a custom slug that collides with a provider alias", () => { | |
| 105 | + const providers = [ | |
| 106 | + provider({ | |
| 107 | + provider: ProviderDriverKind.make("claudeAgent"), | |
| 108 | + instanceId: "claude_openrouter", | |
| 109 | + models: ["claude-opus-4-8"], | |
| 110 | + }), | |
| 111 | + ]; | |
| 112 | + const settings: UnifiedSettings = { | |
| 113 | + ...settingsWithProviderInstances(), | |
| 114 | + providerInstances: { | |
| 115 | + ...settingsWithProviderInstances().providerInstances, | |
| 116 | + [ProviderInstanceId.make("claude_openrouter")]: { | |
| 117 | + driver: ProviderDriverKind.make("claudeAgent"), | |
| 118 | + config: { customModels: ["opus"] }, | |
| 119 | + }, | |
| 120 | + }, | |
| 121 | + }; | |
| 122 | + const openrouter = deriveProviderInstanceEntries(providers)[0]!; | |
| 123 | + | |
| 124 | + expect( | |
| 125 | + getAppModelOptionsForInstance(settings, openrouter).map((option) => option.slug), | |
| 126 | + ).toEqual(["claude-opus-4-8", "opus"]); | |
| 127 | + expect( | |
| 128 | + resolveAppModelSelectionForInstance( | |
| 129 | + ProviderInstanceId.make("claude_openrouter"), | |
| 130 | + settings, | |
| 131 | + providers, | |
| 132 | + "opus", | |
| 133 | + ), | |
| 134 | + ).toBe("opus"); | |
| 135 | + }); | |
| 136 | + | |
| 104 | 137 | it("includes Grok custom models from the selected provider instance", () => { |
| 105 | 138 | const providers = [provider({ provider: ProviderDriverKind.make("grok"), instanceId: "grok" })]; |
| 106 | 139 | const settings: UnifiedSettings = { |
| @@ -9,7 +9,7 @@ import { · 8 unmodified lines | ||
| 9 | 9 | } from "@t3tools/contracts"; |
| 10 | 10 | import { |
| 11 | 11 | createModelSelection, |
| 12 | − normalizeModelSlug, | |
| 12 | + normalizeCustomModelSlug, | |
| 13 | 13 | resolveSelectableModel, |
| 14 | 14 | } from "@t3tools/shared/model"; |
| 15 | 15 | import { getComposerProviderState } from "./components/chat/composerProviderState"; |
| @@ -117,13 +117,12 @@ function applyInstanceModelPreferences( · 101 unmodified lines | ||
| 117 | 117 | export function normalizeCustomModelSlugs( |
| 118 | 118 | models: Iterable<string | null | undefined>, |
| 119 | 119 | builtInModelSlugs: ReadonlySet<string>, |
| 120 | − provider: ProviderDriverKind = ProviderDriverKind.make("codex"), | |
| 121 | 120 | ): string[] { |
| 122 | 121 | const normalizedModels: string[] = []; |
| 123 | 122 | const seen = new Set<string>(); |
| 124 | 123 | |
| 125 | 124 | for (const candidate of models) { |
| 126 | − const normalized = normalizeModelSlug(candidate, provider); | |
| 125 | + const normalized = normalizeCustomModelSlug(candidate); | |
| 127 | 126 | if ( |
| 128 | 127 | !normalized || |
| 129 | 128 | normalized.length > MAX_CUSTOM_MODEL_LENGTH || |
| @@ -163,7 +162,7 @@ export function getAppModelOptions( · 33 unmodified lines | ||
| 163 | 162 | // see the user's authored custom models. |
| 164 | 163 | const defaultInstanceId = defaultInstanceIdForDriver(provider); |
| 165 | 164 | const customModels = readInstanceCustomModels(settings, defaultInstanceId, provider); |
| 166 | − for (const slug of normalizeCustomModelSlugs(customModels, builtInModelSlugs, provider)) { | |
| 165 | + for (const slug of normalizeCustomModelSlugs(customModels, builtInModelSlugs)) { | |
| 167 | 166 | if (seen.has(slug)) { |
| 168 | 167 | continue; |
| 169 | 168 | } |
| @@ -206,8 +205,7 @@ export function getAppModelOptionsForInstance( · 36 unmodified lines | ||
| 206 | 205 | ); |
| 207 | 206 | |
| 208 | 207 | const customModels = readInstanceCustomModels(settings, entry.instanceId, entry.driverKind); |
| 209 | − const normalizer = entry.driverKind; | |
| 210 | − for (const slug of normalizeCustomModelSlugs(customModels, builtInModelSlugs, normalizer)) { | |
| 208 | + for (const slug of normalizeCustomModelSlugs(customModels, builtInModelSlugs)) { | |
| 211 | 209 | if (seen.has(slug)) { |
| 212 | 210 | continue; |
| 213 | 211 | } |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | −import { ProviderInstanceId, type ModelCapabilities } from "@t3tools/contracts"; | |
| 2 | +import { ProviderDriverKind, ProviderInstanceId, type ModelCapabilities } from "@t3tools/contracts"; | |
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | buildProviderOptionSelectionsFromDescriptors, |
| @@ -10,6 +10,8 @@ import { · 4 unmodified lines | ||
| 10 | 10 | getProviderOptionDescriptors, |
| 11 | 11 | getProviderOptionBooleanSelectionValue, |
| 12 | 12 | getProviderOptionStringSelectionValue, |
| 13 | + normalizeCustomModelSlug, | |
| 14 | + normalizeModelSlug, | |
| 13 | 15 | } from "./model.ts"; |
| 14 | 16 | |
| 15 | 17 | const codexCaps: ModelCapabilities = createModelCapabilities({ |
| @@ -144,3 +146,12 @@ describe("descriptor helpers", () => { · 128 unmodified lines | ||
| 144 | 146 | expect(getModelSelectionBooleanOptionValue(selection, "fastMode")).toBe(true); |
| 145 | 147 | }); |
| 146 | 148 | }); |
| 149 | + | |
| 150 | +describe("model slug normalization", () => { | |
| 151 | + it("preserves exact custom slugs instead of expanding provider aliases", () => { | |
| 152 | + const claude = ProviderDriverKind.make("claudeAgent"); | |
| 153 | + | |
| 154 | + expect(normalizeModelSlug("opus", claude)).toBe("claude-opus-4-8"); | |
| 155 | + expect(normalizeCustomModelSlug(" opus ")).toBe("opus"); | |
| 156 | + }); | |
| 157 | +}); | |
| @@ -236,11 +236,7 @@ export function normalizeModelSlug( · 235 unmodified lines | ||
| 236 | 236 | model: string | null | undefined, |
| 237 | 237 | provider: ProviderDriverKind = DEFAULT_PROVIDER_DRIVER_KIND, |
| 238 | 238 | ): string | null { |
| 239 | − if (typeof model !== "string") { | |
| 240 | − return null; | |
| 241 | − } | |
| 242 | − | |
| 243 | − const trimmed = model.trim(); | |
| 239 | + const trimmed = normalizeCustomModelSlug(model); | |
| 244 | 240 | if (!trimmed) { |
| 245 | 241 | return null; |
| 246 | 242 | } |
| @@ -252,6 +248,15 @@ export function normalizeModelSlug( · 5 unmodified lines | ||
| 252 | 248 | return typeof aliased === "string" ? aliased : trimmed; |
| 253 | 249 | } |
| 254 | 250 | |
| 251 | +/** Custom model identifiers are provider-owned, so only trim them; never expand aliases. */ | |
| 252 | +export function normalizeCustomModelSlug(model: string | null | undefined): string | null { | |
| 253 | + if (typeof model !== "string") { | |
| 254 | + return null; | |
| 255 | + } | |
| 256 | + | |
| 257 | + return model.trim() || null; | |
| 258 | +} | |
| 259 | + | |
| 255 | 260 | export function resolveSelectableModel( |
| 256 | 261 | provider: ProviderDriverKind, |
| 257 | 262 | value: string | null | undefined, |