| author | |
| committer | |
| log | fa69f05b69d05003a6994194c12edd1d8aee080c |
| tree | 879aa3622f9b3790869c873c86955073fbd804eb |
| parent | c710167bde19e665c8517b606718bfbd406f2dd1 |
| signature | Signed by PGP key B5690EEEBB952194 |
11 files changed, 88 insertions(+), 58 deletions(-)
apps/server/src/provider/Layers/ClaudeProvider.ts-5| ... | ... | @@ -2,7 +2,6 @@ import { |
| 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 |
| 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")( |
| 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")( |
| 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 = ( |
| 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 | ); |
apps/server/src/provider/Layers/CursorProvider.ts+1-4| ... | ... | @@ -8,7 +8,6 @@ import type { |
| 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" |
| 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 = ( |
| 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: { |
| 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 | ), |
apps/server/src/provider/Layers/GrokProvider.ts+1-8| ... | ... | @@ -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 = { |
| 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( |
| 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( |
apps/server/src/provider/Layers/OpenCodeProvider.ts+3-22| ... | ... | @@ -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 { |
| 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 = ( |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 | ); |
apps/server/src/provider/providerSnapshot.test.ts+20-2| ... | ... | @@ -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", () => { |
| 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", () => { |
| 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", () => { |
apps/server/src/provider/providerSnapshot.ts+2-3| ... | ... | @@ -13,7 +13,7 @@ import * as PlatformError from "effect/PlatformError"; |
| 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 { |
| 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( |
| 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 | } |
apps/web/src/components/settings/ProviderModelsSection.tsx+2-2| ... | ... | @@ -16,7 +16,7 @@ import { |
| 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({ |
| 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; |
apps/web/src/modelSelection.test.ts+33| ... | ... | @@ -101,6 +101,39 @@ describe("instance-scoped model selection", () => { |
| 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 = { |
apps/web/src/modelSelection.ts+4-6| ... | ... | @@ -9,7 +9,7 @@ import { |
| 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( |
| 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( |
| 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( |
| 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 | } |
packages/shared/src/model.test.ts+12-1| ... | ... | @@ -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 { |
| 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", () => { |
| 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 | }); |
packages/shared/src/model.ts+10-5| ... | ... | @@ -236,11 +236,7 @@ export function normalizeModelSlug( |
| 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( |
| 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, |