fix: preserve custom provider prefixes in think mode model switching (#451)
When using custom providers with model ID prefixes (e.g., vertex_ai/claude-sonnet-4-5), the think mode switcher was stripping the prefix when mapping to high variants, causing routing failures in custom LLM proxies. Changes: - Add extractModelPrefix() to parse and preserve prefixes like vertex_ai/, openai/, etc. - Update getHighVariant() to preserve prefix when mapping to -high variants - Update isAlreadyHighVariant() to check base model name (without prefix) - Update getThinkingConfig() to check capability using base model name - Add comprehensive tests for custom provider prefix scenarios This fix ensures backward compatibility while supporting custom providers that use prefixed model IDs for routing. Fixes issue where think mode would break custom providers with prefixed models by stripping the routing prefix during model variant switching.
This commit is contained in:
@@ -16,6 +16,26 @@
|
||||
* inconsistencies defensively while maintaining backwards compatibility.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracts provider-specific prefix from model ID (if present).
|
||||
* Custom providers may use prefixes for routing (e.g., vertex_ai/, openai/).
|
||||
*
|
||||
* @example
|
||||
* extractModelPrefix("vertex_ai/claude-sonnet-4-5") // { prefix: "vertex_ai/", base: "claude-sonnet-4-5" }
|
||||
* extractModelPrefix("claude-sonnet-4-5") // { prefix: "", base: "claude-sonnet-4-5" }
|
||||
* extractModelPrefix("openai/gpt-5.2") // { prefix: "openai/", base: "gpt-5.2" }
|
||||
*/
|
||||
function extractModelPrefix(modelID: string): { prefix: string; base: string } {
|
||||
const slashIndex = modelID.indexOf("/")
|
||||
if (slashIndex === -1) {
|
||||
return { prefix: "", base: modelID }
|
||||
}
|
||||
return {
|
||||
prefix: modelID.slice(0, slashIndex + 1),
|
||||
base: modelID.slice(slashIndex + 1),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes model IDs to use consistent hyphen formatting.
|
||||
* GitHub Copilot may use dots (claude-opus-4.5) but our maps use hyphens (claude-opus-4-5).
|
||||
@@ -25,6 +45,7 @@
|
||||
* normalizeModelID("claude-opus-4.5") // "claude-opus-4-5"
|
||||
* normalizeModelID("gemini-3.5-pro") // "gemini-3-5-pro"
|
||||
* normalizeModelID("gpt-5.2") // "gpt-5-2"
|
||||
* normalizeModelID("vertex_ai/claude-opus-4.5") // "vertex_ai/claude-opus-4-5"
|
||||
*/
|
||||
function normalizeModelID(modelID: string): string {
|
||||
// Replace dots with hyphens when followed by a digit
|
||||
@@ -142,16 +163,27 @@ const THINKING_CAPABLE_MODELS = {
|
||||
|
||||
export function getHighVariant(modelID: string): string | null {
|
||||
const normalized = normalizeModelID(modelID)
|
||||
const { prefix, base } = extractModelPrefix(normalized)
|
||||
|
||||
if (ALREADY_HIGH.has(normalized)) {
|
||||
// Check if already high variant (with or without prefix)
|
||||
if (ALREADY_HIGH.has(base) || base.endsWith("-high")) {
|
||||
return null
|
||||
}
|
||||
return HIGH_VARIANT_MAP[normalized] ?? null
|
||||
|
||||
// Look up high variant for base model
|
||||
const highBase = HIGH_VARIANT_MAP[base]
|
||||
if (!highBase) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Preserve prefix in the high variant
|
||||
return prefix + highBase
|
||||
}
|
||||
|
||||
export function isAlreadyHighVariant(modelID: string): boolean {
|
||||
const normalized = normalizeModelID(modelID)
|
||||
return ALREADY_HIGH.has(normalized) || normalized.endsWith("-high")
|
||||
const { base } = extractModelPrefix(normalized)
|
||||
return ALREADY_HIGH.has(base) || base.endsWith("-high")
|
||||
}
|
||||
|
||||
type ThinkingProvider = keyof typeof THINKING_CONFIGS
|
||||
@@ -165,6 +197,7 @@ export function getThinkingConfig(
|
||||
modelID: string
|
||||
): Record<string, unknown> | null {
|
||||
const normalized = normalizeModelID(modelID)
|
||||
const { base } = extractModelPrefix(normalized)
|
||||
|
||||
if (isAlreadyHighVariant(normalized)) {
|
||||
return null
|
||||
@@ -179,9 +212,10 @@ export function getThinkingConfig(
|
||||
const config = THINKING_CONFIGS[resolvedProvider]
|
||||
const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider]
|
||||
|
||||
const modelLower = normalized.toLowerCase()
|
||||
// Check capability using base model name (without prefix)
|
||||
const baseLower = base.toLowerCase()
|
||||
const isCapable = capablePatterns.some((pattern) =>
|
||||
modelLower.includes(pattern.toLowerCase())
|
||||
baseLower.includes(pattern.toLowerCase())
|
||||
)
|
||||
|
||||
return isCapable ? config : null
|
||||
|
||||
Reference in New Issue
Block a user