diff --git a/internal/registry/model_registry.go b/internal/registry/model_registry.go index 8f56c43d01..588355827b 100644 --- a/internal/registry/model_registry.go +++ b/internal/registry/model_registry.go @@ -742,9 +742,33 @@ func (r *ModelRegistry) ClientSupportsModel(clientID, modelID string) bool { } } + // Fallback: strip known model variant suffixes (e.g. "-customtools"). + // These variants share the same provider as the base model. + if stripped := stripModelVariantSuffix(modelID); stripped != modelID { + for _, id := range models { + if strings.EqualFold(strings.TrimSpace(id), stripped) { + return true + } + } + } + return false } +// knownModelVariantSuffixes lists suffixes that Google appends to model names +// for specialised fine-tune variants (e.g. "-customtools" for tool-calling). +// These variants share the same provider/executor as the base model. +var knownModelVariantSuffixes = []string{"-customtools"} + +func stripModelVariantSuffix(model string) string { + for _, suffix := range knownModelVariantSuffixes { + if strings.HasSuffix(model, suffix) { + return strings.TrimSuffix(model, suffix) + } + } + return model +} + // GetAvailableModels returns all models that have at least one available client // Parameters: // - handlerType: The handler type to filter models for (e.g., "openai", "claude", "gemini") diff --git a/sdk/api/handlers/handlers.go b/sdk/api/handlers/handlers.go index 0e490e3202..a3991ee126 100644 --- a/sdk/api/handlers/handlers.go +++ b/sdk/api/handlers/handlers.go @@ -801,6 +801,19 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string providers = util.GetProviderName(resolvedModelName) } + // Fallback: strip known model variant suffixes (e.g. "-customtools") for + // provider lookup. The suffix is also stripped from resolvedModelName + // because CPA routes gemini-cli through cloudcode-pa/v1internal (OAuth) + // which does not accept variant suffixes (returns 404). + // The behavioral difference is minor: -customtools only changes tool + // selection priority (prefers custom tools over bash), not model capabilities. + if len(providers) == 0 { + if stripped := stripModelVariantSuffix(baseModel); stripped != baseModel { + providers = util.GetProviderName(stripped) + resolvedModelName = stripModelVariantSuffix(resolvedModelName) + } + } + if len(providers) == 0 { return nil, "", &interfaces.ErrorMessage{StatusCode: http.StatusBadGateway, Error: fmt.Errorf("unknown provider for model %s", modelName)} } @@ -810,6 +823,20 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string return providers, resolvedModelName, nil } +// knownModelVariantSuffixes lists suffixes that Google appends to model names +// for specialised fine-tune variants (e.g. "-customtools" for tool-calling). +// These variants share the same provider/executor as the base model. +var knownModelVariantSuffixes = []string{"-customtools"} + +func stripModelVariantSuffix(model string) string { + for _, suffix := range knownModelVariantSuffixes { + if strings.HasSuffix(model, suffix) { + return strings.TrimSuffix(model, suffix) + } + } + return model +} + func cloneBytes(src []byte) []byte { if len(src) == 0 { return nil