refactor(model-core): remove src back-imports via core utilities and adapter
- move fuzzyMatchModel and transformModelForProvider into model-core - replace connected-providers re-export with adapter contract - route shared wrappers through model-core exports Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,5 +1,38 @@
|
|||||||
export {
|
import type { ModelMetadata } from "./provider-cache"
|
||||||
findProviderModelMetadata,
|
|
||||||
readConnectedProvidersCache,
|
export interface ProviderModelsCache {
|
||||||
readProviderModelsCache,
|
readonly models: Record<string, readonly string[] | readonly ModelMetadata[]>
|
||||||
} from "../../../src/shared/connected-providers-cache"
|
readonly connected: readonly string[]
|
||||||
|
readonly updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConnectedProvidersAdapter {
|
||||||
|
readConnectedProvidersCache(): string[] | null
|
||||||
|
findProviderModelMetadata(providerID: string, modelID: string): ModelMetadata | undefined
|
||||||
|
readProviderModelsCache(): ProviderModelsCache | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readConnectedProvidersCache(): string[] | null {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProviderModelMetadata(
|
||||||
|
_providerID: string,
|
||||||
|
_modelID: string,
|
||||||
|
): ModelMetadata | undefined {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readProviderModelsCache(): ProviderModelsCache | null {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const connectedProvidersAdapter: ConnectedProvidersAdapter = {
|
||||||
|
readConnectedProvidersCache,
|
||||||
|
findProviderModelMetadata,
|
||||||
|
readProviderModelsCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const disconnectedConnectedProvidersAdapter: ConnectedProvidersAdapter = {
|
||||||
|
...connectedProvidersAdapter,
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ export * from "./model-format-normalizer"
|
|||||||
export * from "./model-normalization"
|
export * from "./model-normalization"
|
||||||
export * from "./model-string-parser"
|
export * from "./model-string-parser"
|
||||||
export * from "./model-sanitizer"
|
export * from "./model-sanitizer"
|
||||||
|
export {
|
||||||
|
fuzzyMatchModel,
|
||||||
|
isModelAvailable,
|
||||||
|
} from "./model-availability"
|
||||||
|
export { transformModelForProvider } from "./provider-model-id-transform"
|
||||||
export * from "./fallback-chain-from-models"
|
export * from "./fallback-chain-from-models"
|
||||||
export * from "./known-variants"
|
export * from "./known-variants"
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
function normalizeModelName(name: string): string {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/claude-(opus|sonnet|haiku)-(\d+)[.-](\d+)/g, "claude-$1-$2.$3")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fuzzyMatchModel(
|
||||||
|
target: string,
|
||||||
|
available: Set<string>,
|
||||||
|
providers?: string[],
|
||||||
|
): string | null {
|
||||||
|
if (available.size === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNormalized = normalizeModelName(target)
|
||||||
|
|
||||||
|
let candidates = Array.from(available)
|
||||||
|
if (providers && providers.length > 0) {
|
||||||
|
const providerSet = new Set(providers)
|
||||||
|
candidates = candidates.filter((model) => {
|
||||||
|
const [provider] = model.split("/")
|
||||||
|
return providerSet.has(provider)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = candidates.filter((model) =>
|
||||||
|
normalizeModelName(model).includes(targetNormalized),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (matches.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const exactMatch = matches.find((model) => normalizeModelName(model) === targetNormalized)
|
||||||
|
if (exactMatch) {
|
||||||
|
return exactMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
const exactModelIdMatches = matches.filter((model) => {
|
||||||
|
const modelId = model.split("/").slice(1).join("/")
|
||||||
|
return normalizeModelName(modelId) === targetNormalized
|
||||||
|
})
|
||||||
|
if (exactModelIdMatches.length > 0) {
|
||||||
|
return exactModelIdMatches.reduce((shortest, current) =>
|
||||||
|
current.length < shortest.length ? current : shortest,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.reduce((shortest, current) =>
|
||||||
|
current.length < shortest.length ? current : shortest,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isModelAvailable(
|
||||||
|
targetModel: string,
|
||||||
|
availableModels: Set<string>,
|
||||||
|
): boolean {
|
||||||
|
return fuzzyMatchModel(targetModel, availableModels) !== null
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { fuzzyMatchModel } from "../../../src/shared/model-availability"
|
import { fuzzyMatchModel } from "./model-availability"
|
||||||
import type { FallbackEntry } from "./model-requirements"
|
import type { FallbackEntry } from "./model-requirements"
|
||||||
import { transformModelForProvider } from "../../../src/shared/provider-model-id-transform"
|
import { transformModelForProvider } from "./provider-model-id-transform"
|
||||||
import { normalizeModel } from "./model-normalization"
|
import { normalizeModel } from "./model-normalization"
|
||||||
import type { ProviderCache } from "./provider-cache"
|
import type { ProviderCache } from "./provider-cache"
|
||||||
|
|
||||||
@@ -57,6 +57,20 @@ export type ModelResolutionResult = {
|
|||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelResolutionDeps = {
|
||||||
|
fuzzyMatchModel: (
|
||||||
|
target: string,
|
||||||
|
available: Set<string>,
|
||||||
|
providers?: string[],
|
||||||
|
) => string | null
|
||||||
|
transformModelForProvider: (provider: string, model: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MODEL_RESOLUTION_DEPS: ModelResolutionDeps = {
|
||||||
|
fuzzyMatchModel,
|
||||||
|
transformModelForProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function resolveModelPipeline(
|
export function resolveModelPipeline(
|
||||||
request: ModelResolutionRequest,
|
request: ModelResolutionRequest,
|
||||||
@@ -64,6 +78,7 @@ export function resolveModelPipeline(
|
|||||||
readConnectedProvidersCache: () => null,
|
readConnectedProvidersCache: () => null,
|
||||||
findProviderModelMetadata: () => undefined,
|
findProviderModelMetadata: () => undefined,
|
||||||
},
|
},
|
||||||
|
deps: ModelResolutionDeps = DEFAULT_MODEL_RESOLUTION_DEPS,
|
||||||
): ModelResolutionResult | undefined {
|
): ModelResolutionResult | undefined {
|
||||||
const attempted: string[] = []
|
const attempted: string[] = []
|
||||||
const { intent, constraints, policy } = request
|
const { intent, constraints, policy } = request
|
||||||
@@ -89,7 +104,7 @@ export function resolveModelPipeline(
|
|||||||
if (availableModels.size > 0) {
|
if (availableModels.size > 0) {
|
||||||
const parts = normalizedCategoryDefault.split("/")
|
const parts = normalizedCategoryDefault.split("/")
|
||||||
const providerHint = parts.length >= 2 ? [parts[0]] : undefined
|
const providerHint = parts.length >= 2 ? [parts[0]] : undefined
|
||||||
const match = fuzzyMatchModel(normalizedCategoryDefault, availableModels, providerHint)
|
const match = deps.fuzzyMatchModel(normalizedCategoryDefault, availableModels, providerHint)
|
||||||
if (match) {
|
if (match) {
|
||||||
log("Model resolved via category default (fuzzy matched)", {
|
log("Model resolved via category default (fuzzy matched)", {
|
||||||
original: normalizedCategoryDefault,
|
original: normalizedCategoryDefault,
|
||||||
@@ -110,7 +125,7 @@ export function resolveModelPipeline(
|
|||||||
const provider = parts[0]
|
const provider = parts[0]
|
||||||
if (connectedProviders.includes(provider)) {
|
if (connectedProviders.includes(provider)) {
|
||||||
const modelName = parts.slice(1).join("/")
|
const modelName = parts.slice(1).join("/")
|
||||||
const transformedModel = `${provider}/${transformModelForProvider(provider, modelName)}`
|
const transformedModel = `${provider}/${deps.transformModelForProvider(provider, modelName)}`
|
||||||
log("Model resolved via category default (connected provider)", {
|
log("Model resolved via category default (connected provider)", {
|
||||||
model: transformedModel,
|
model: transformedModel,
|
||||||
original: normalizedCategoryDefault,
|
original: normalizedCategoryDefault,
|
||||||
@@ -139,7 +154,7 @@ export function resolveModelPipeline(
|
|||||||
const provider = parts[0]
|
const provider = parts[0]
|
||||||
if (connectedSet.has(provider)) {
|
if (connectedSet.has(provider)) {
|
||||||
const modelName = parts.slice(1).join("/")
|
const modelName = parts.slice(1).join("/")
|
||||||
const transformedModel = `${provider}/${transformModelForProvider(provider, modelName)}`
|
const transformedModel = `${provider}/${deps.transformModelForProvider(provider, modelName)}`
|
||||||
log("Model resolved via user fallback_models (connected provider)", { model: transformedModel, original: model })
|
log("Model resolved via user fallback_models (connected provider)", { model: transformedModel, original: model })
|
||||||
return { model: transformedModel, provenance: "provider-fallback", attempted }
|
return { model: transformedModel, provenance: "provider-fallback", attempted }
|
||||||
}
|
}
|
||||||
@@ -152,7 +167,7 @@ export function resolveModelPipeline(
|
|||||||
attempted.push(model)
|
attempted.push(model)
|
||||||
const parts = model.split("/")
|
const parts = model.split("/")
|
||||||
const providerHint = parts.length >= 2 ? [parts[0]] : undefined
|
const providerHint = parts.length >= 2 ? [parts[0]] : undefined
|
||||||
const match = fuzzyMatchModel(model, availableModels, providerHint)
|
const match = deps.fuzzyMatchModel(model, availableModels, providerHint)
|
||||||
if (match) {
|
if (match) {
|
||||||
log("Model resolved via user fallback_models (availability confirmed)", { model: model, match })
|
log("Model resolved via user fallback_models (availability confirmed)", { model: model, match })
|
||||||
return { model: match, provenance: "provider-fallback", attempted }
|
return { model: match, provenance: "provider-fallback", attempted }
|
||||||
@@ -173,7 +188,7 @@ export function resolveModelPipeline(
|
|||||||
for (const entry of fallbackChain) {
|
for (const entry of fallbackChain) {
|
||||||
for (const provider of entry.providers) {
|
for (const provider of entry.providers) {
|
||||||
if (connectedSet.has(provider)) {
|
if (connectedSet.has(provider)) {
|
||||||
const transformedModelId = transformModelForProvider(provider, entry.model)
|
const transformedModelId = deps.transformModelForProvider(provider, entry.model)
|
||||||
const model = `${provider}/${transformedModelId}`
|
const model = `${provider}/${transformedModelId}`
|
||||||
log("Model resolved via fallback chain (connected provider)", {
|
log("Model resolved via fallback chain (connected provider)", {
|
||||||
provider,
|
provider,
|
||||||
@@ -195,7 +210,7 @@ export function resolveModelPipeline(
|
|||||||
for (const entry of fallbackChain) {
|
for (const entry of fallbackChain) {
|
||||||
for (const provider of entry.providers) {
|
for (const provider of entry.providers) {
|
||||||
const fullModel = `${provider}/${entry.model}`
|
const fullModel = `${provider}/${entry.model}`
|
||||||
const match = fuzzyMatchModel(fullModel, availableModels, [provider])
|
const match = deps.fuzzyMatchModel(fullModel, availableModels, [provider])
|
||||||
if (match) {
|
if (match) {
|
||||||
log("Model resolved via fallback chain (availability confirmed)", {
|
log("Model resolved via fallback chain (availability confirmed)", {
|
||||||
provider,
|
provider,
|
||||||
@@ -212,7 +227,7 @@ export function resolveModelPipeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const crossProviderMatch = fuzzyMatchModel(entry.model, availableModels)
|
const crossProviderMatch = deps.fuzzyMatchModel(entry.model, availableModels)
|
||||||
if (crossProviderMatch) {
|
if (crossProviderMatch) {
|
||||||
log("Model resolved via fallback chain (cross-provider fuzzy match)", {
|
log("Model resolved via fallback chain (cross-provider fuzzy match)", {
|
||||||
model: entry.model,
|
model: entry.model,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { FallbackModelObject } from "./fallback-model-object"
|
|||||||
import { normalizeModel } from "./model-normalization"
|
import { normalizeModel } from "./model-normalization"
|
||||||
import { resolveModelPipeline } from "./model-resolution-pipeline"
|
import { resolveModelPipeline } from "./model-resolution-pipeline"
|
||||||
import { KNOWN_VARIANTS } from "./known-variants"
|
import { KNOWN_VARIANTS } from "./known-variants"
|
||||||
|
import type { ConnectedProvidersAdapter } from "./connected-providers-cache"
|
||||||
import * as connectedProvidersCache from "./connected-providers-cache"
|
import * as connectedProvidersCache from "./connected-providers-cache"
|
||||||
|
|
||||||
export type ModelResolutionInput = {
|
export type ModelResolutionInput = {
|
||||||
@@ -44,13 +45,14 @@ export function resolveModel(input: ModelResolutionInput): string | undefined {
|
|||||||
|
|
||||||
export function resolveModelWithFallback(
|
export function resolveModelWithFallback(
|
||||||
input: ExtendedModelResolutionInput,
|
input: ExtendedModelResolutionInput,
|
||||||
|
connectedProvidersAdapter: ConnectedProvidersAdapter = connectedProvidersCache,
|
||||||
): ModelResolutionResult | undefined {
|
): ModelResolutionResult | undefined {
|
||||||
const { uiSelectedModel, userModel, userFallbackModels, categoryDefaultModel, fallbackChain, availableModels, systemDefaultModel } = input
|
const { uiSelectedModel, userModel, userFallbackModels, categoryDefaultModel, fallbackChain, availableModels, systemDefaultModel } = input
|
||||||
const resolved = resolveModelPipeline({
|
const resolved = resolveModelPipeline({
|
||||||
intent: { uiSelectedModel, userModel, userFallbackModels, categoryDefaultModel },
|
intent: { uiSelectedModel, userModel, userFallbackModels, categoryDefaultModel },
|
||||||
constraints: { availableModels },
|
constraints: { availableModels },
|
||||||
policy: { fallbackChain, systemDefaultModel },
|
policy: { fallbackChain, systemDefaultModel },
|
||||||
}, connectedProvidersCache)
|
}, connectedProvidersAdapter)
|
||||||
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
function inferSubProvider(model: string): string | undefined {
|
||||||
|
if (model.startsWith("claude-")) return "anthropic"
|
||||||
|
if (model.startsWith("gpt-")) return "openai"
|
||||||
|
if (model.startsWith("gemini-")) return "google"
|
||||||
|
if (model.startsWith("grok-")) return "xai"
|
||||||
|
if (model.startsWith("minimax-")) return "minimax"
|
||||||
|
if (model.startsWith("kimi-")) return "moonshotai"
|
||||||
|
if (model.startsWith("glm-")) return "zai"
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLAUDE_VERSION_DOT = /claude-(\w+)-(\d+)-(\d+)/g
|
||||||
|
const GEMINI_31_PRO_PREVIEW = /gemini-3\.1-pro(?!-)/g
|
||||||
|
const GEMINI_3_FLASH_PREVIEW = /gemini-3-flash(?!-)/g
|
||||||
|
|
||||||
|
function claudeVersionDot(model: string): string {
|
||||||
|
return model.replace(CLAUDE_VERSION_DOT, "claude-$1-$2.$3")
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyGatewayTransforms(model: string): string {
|
||||||
|
return claudeVersionDot(model).replace(
|
||||||
|
GEMINI_31_PRO_PREVIEW,
|
||||||
|
"gemini-3.1-pro-preview",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformModelForProvider(provider: string, model: string): string {
|
||||||
|
if (provider === "vercel") {
|
||||||
|
const slashIndex = model.indexOf("/")
|
||||||
|
if (slashIndex !== -1) {
|
||||||
|
const subProvider = model.substring(0, slashIndex)
|
||||||
|
const subModel = model.substring(slashIndex + 1)
|
||||||
|
return `${subProvider}/${applyGatewayTransforms(subModel)}`
|
||||||
|
}
|
||||||
|
const subProvider = inferSubProvider(model)
|
||||||
|
if (subProvider) {
|
||||||
|
return `${subProvider}/${applyGatewayTransforms(model)}`
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
if (provider === "github-copilot") {
|
||||||
|
return claudeVersionDot(model)
|
||||||
|
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
|
||||||
|
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
|
||||||
|
}
|
||||||
|
if (provider === "google") {
|
||||||
|
return model
|
||||||
|
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
|
||||||
|
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
|
||||||
|
}
|
||||||
|
if (provider === "anthropic") {
|
||||||
|
return claudeVersionDot(model)
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
}
|
||||||
@@ -5,6 +5,12 @@ import { getOpenCodeCacheDir } from "./data-path"
|
|||||||
import * as connectedProvidersCache from "./connected-providers-cache"
|
import * as connectedProvidersCache from "./connected-providers-cache"
|
||||||
import { normalizeSDKResponse } from "./normalize-sdk-response"
|
import { normalizeSDKResponse } from "./normalize-sdk-response"
|
||||||
|
|
||||||
|
function normalizeModelName(name: string): string {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/claude-(opus|sonnet|haiku)-(\d+)[.-](\d+)/g, "claude-$1-$2.$3")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fuzzy match a target model name against available models
|
* Fuzzy match a target model name against available models
|
||||||
*
|
*
|
||||||
@@ -25,12 +31,6 @@ import { normalizeSDKResponse } from "./normalize-sdk-response"
|
|||||||
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
|
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
|
||||||
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
||||||
*/
|
*/
|
||||||
function normalizeModelName(name: string): string {
|
|
||||||
return name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/claude-(opus|sonnet|haiku)-(\d+)[.-](\d+)/g, "claude-$1-$2.$3")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fuzzyMatchModel(
|
export function fuzzyMatchModel(
|
||||||
target: string,
|
target: string,
|
||||||
available: Set<string>,
|
available: Set<string>,
|
||||||
@@ -45,7 +45,6 @@ export function fuzzyMatchModel(
|
|||||||
|
|
||||||
const targetNormalized = normalizeModelName(target)
|
const targetNormalized = normalizeModelName(target)
|
||||||
|
|
||||||
// Filter by providers if specified
|
|
||||||
let candidates = Array.from(available)
|
let candidates = Array.from(available)
|
||||||
if (providers && providers.length > 0) {
|
if (providers && providers.length > 0) {
|
||||||
const providerSet = new Set(providers)
|
const providerSet = new Set(providers)
|
||||||
@@ -61,7 +60,6 @@ export function fuzzyMatchModel(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all matches (case-insensitive substring match with normalization)
|
|
||||||
const matches = candidates.filter((model) =>
|
const matches = candidates.filter((model) =>
|
||||||
normalizeModelName(model).includes(targetNormalized),
|
normalizeModelName(model).includes(targetNormalized),
|
||||||
)
|
)
|
||||||
@@ -73,16 +71,12 @@ export function fuzzyMatchModel(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 1: Exact match (normalized full model string)
|
|
||||||
const exactMatch = matches.find((model) => normalizeModelName(model) === targetNormalized)
|
const exactMatch = matches.find((model) => normalizeModelName(model) === targetNormalized)
|
||||||
if (exactMatch) {
|
if (exactMatch) {
|
||||||
log("[fuzzyMatchModel] exact match found", { exactMatch })
|
log("[fuzzyMatchModel] exact match found", { exactMatch })
|
||||||
return exactMatch
|
return exactMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 2: Exact model ID match (part after provider/)
|
|
||||||
// This ensures "big-pickle" matches "zai-coding-plan/big-pickle" over "zai-coding-plan/glm-5"
|
|
||||||
// Use filter + shortest to handle multi-provider cases (e.g., openai/gpt-5.4 + opencode/gpt-5.4)
|
|
||||||
const exactModelIdMatches = matches.filter((model) => {
|
const exactModelIdMatches = matches.filter((model) => {
|
||||||
const modelId = model.split("/").slice(1).join("/")
|
const modelId = model.split("/").slice(1).join("/")
|
||||||
return normalizeModelName(modelId) === targetNormalized
|
return normalizeModelName(modelId) === targetNormalized
|
||||||
@@ -95,7 +89,6 @@ export function fuzzyMatchModel(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 3: Shorter model name (more specific, fallback for partial matches)
|
|
||||||
const result = matches.reduce((shortest, current) =>
|
const result = matches.reduce((shortest, current) =>
|
||||||
current.length < shortest.length ? current : shortest,
|
current.length < shortest.length ? current : shortest,
|
||||||
)
|
)
|
||||||
@@ -103,13 +96,6 @@ export function fuzzyMatchModel(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a target model is available (fuzzy match by model name, no provider filtering)
|
|
||||||
*
|
|
||||||
* @param targetModel - Model name to check (e.g., "gpt-5.3-codex")
|
|
||||||
* @param availableModels - Set of available models in "provider/model" format
|
|
||||||
* @returns true if model is available, false otherwise
|
|
||||||
*/
|
|
||||||
export function isModelAvailable(
|
export function isModelAvailable(
|
||||||
targetModel: string,
|
targetModel: string,
|
||||||
availableModels: Set<string>,
|
availableModels: Set<string>,
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
|
import {
|
||||||
|
resolveModel,
|
||||||
|
resolveModelWithFallback as resolveModelWithFallbackFromCore,
|
||||||
|
normalizeFallbackModels,
|
||||||
|
flattenToFallbackModelStrings,
|
||||||
|
} from "@oh-my-opencode/model-core"
|
||||||
|
import type {
|
||||||
|
ModelResolutionInput,
|
||||||
|
ExtendedModelResolutionInput,
|
||||||
|
} from "@oh-my-opencode/model-core"
|
||||||
|
import * as connectedProvidersCache from "./connected-providers-cache"
|
||||||
|
|
||||||
|
export { resolveModel, normalizeFallbackModels, flattenToFallbackModelStrings }
|
||||||
|
|
||||||
|
type CoreModelResolutionResult = ReturnType<typeof resolveModelWithFallbackFromCore>
|
||||||
|
export type ModelResolutionResult = Exclude<CoreModelResolutionResult, undefined>
|
||||||
|
export type ModelSource = ModelResolutionResult["source"]
|
||||||
|
|
||||||
|
export function resolveModelWithFallback(
|
||||||
|
input: ExtendedModelResolutionInput,
|
||||||
|
): CoreModelResolutionResult {
|
||||||
|
return resolveModelWithFallbackFromCore(input, connectedProvidersCache)
|
||||||
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
ModelResolutionInput,
|
ModelResolutionInput,
|
||||||
ModelSource,
|
ExtendedModelResolutionInput,
|
||||||
ModelResolutionResult,
|
}
|
||||||
ExtendedModelResolutionInput,
|
|
||||||
} from "@oh-my-opencode/model-core"
|
|
||||||
export {
|
|
||||||
resolveModel,
|
|
||||||
resolveModelWithFallback,
|
|
||||||
normalizeFallbackModels,
|
|
||||||
flattenToFallbackModelStrings,
|
|
||||||
} from "@oh-my-opencode/model-core"
|
|
||||||
|
|||||||
@@ -23,18 +23,13 @@ function applyGatewayTransforms(model: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function transformModelForProvider(provider: string, model: string): string {
|
export function transformModelForProvider(provider: string, model: string): string {
|
||||||
// Vercel AI Gateway expects <sub-provider>/<model> (e.g. anthropic/claude-opus-4.7).
|
|
||||||
// Canonical names in model-requirements.ts may be bare (claude-opus-4-7) or
|
|
||||||
// already prefixed (anthropic/claude-opus-4-7). Both need gateway-specific transforms.
|
|
||||||
if (provider === "vercel") {
|
if (provider === "vercel") {
|
||||||
// Already prefixed — transform only the model part
|
|
||||||
const slashIndex = model.indexOf("/")
|
const slashIndex = model.indexOf("/")
|
||||||
if (slashIndex !== -1) {
|
if (slashIndex !== -1) {
|
||||||
const subProvider = model.substring(0, slashIndex)
|
const subProvider = model.substring(0, slashIndex)
|
||||||
const subModel = model.substring(slashIndex + 1)
|
const subModel = model.substring(slashIndex + 1)
|
||||||
return `${subProvider}/${applyGatewayTransforms(subModel)}`
|
return `${subProvider}/${applyGatewayTransforms(subModel)}`
|
||||||
}
|
}
|
||||||
// Bare name — infer sub-provider from model prefix (claude- → anthropic, etc.)
|
|
||||||
const subProvider = inferSubProvider(model)
|
const subProvider = inferSubProvider(model)
|
||||||
if (subProvider) {
|
if (subProvider) {
|
||||||
return `${subProvider}/${applyGatewayTransforms(model)}`
|
return `${subProvider}/${applyGatewayTransforms(model)}`
|
||||||
|
|||||||
Reference in New Issue
Block a user