| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- package service
- import (
- "strings"
- "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
- )
- func NormalizeOpenAICompatRequestedModel(model string) string {
- trimmed := strings.TrimSpace(model)
- if trimmed == "" {
- return ""
- }
- normalized, _, ok := splitOpenAICompatReasoningModel(trimmed)
- if !ok || normalized == "" {
- return trimmed
- }
- return normalized
- }
- func applyOpenAICompatModelNormalization(req *apicompat.AnthropicRequest) {
- if req == nil {
- return
- }
- originalModel := strings.TrimSpace(req.Model)
- if originalModel == "" {
- return
- }
- normalizedModel, derivedEffort, hasReasoningSuffix := splitOpenAICompatReasoningModel(originalModel)
- if hasReasoningSuffix && normalizedModel != "" {
- req.Model = normalizedModel
- }
- if req.OutputConfig != nil && strings.TrimSpace(req.OutputConfig.Effort) != "" {
- return
- }
- claudeEffort := openAIReasoningEffortToClaudeOutputEffort(derivedEffort)
- if claudeEffort == "" {
- return
- }
- if req.OutputConfig == nil {
- req.OutputConfig = &apicompat.AnthropicOutputConfig{}
- }
- req.OutputConfig.Effort = claudeEffort
- }
- func splitOpenAICompatReasoningModel(model string) (normalizedModel string, reasoningEffort string, ok bool) {
- trimmed := strings.TrimSpace(model)
- if trimmed == "" {
- return "", "", false
- }
- modelID := trimmed
- if strings.Contains(modelID, "/") {
- parts := strings.Split(modelID, "/")
- modelID = parts[len(parts)-1]
- }
- modelID = strings.TrimSpace(modelID)
- if !strings.HasPrefix(strings.ToLower(modelID), "gpt-") {
- return trimmed, "", false
- }
- parts := strings.FieldsFunc(strings.ToLower(modelID), func(r rune) bool {
- switch r {
- case '-', '_', ' ':
- return true
- default:
- return false
- }
- })
- if len(parts) == 0 {
- return trimmed, "", false
- }
- last := strings.NewReplacer("-", "", "_", "", " ", "").Replace(parts[len(parts)-1])
- switch last {
- case "none", "minimal":
- case "low", "medium", "high":
- reasoningEffort = last
- case "xhigh", "extrahigh":
- reasoningEffort = "xhigh"
- default:
- return trimmed, "", false
- }
- return normalizeCodexModel(modelID), reasoningEffort, true
- }
- func openAIReasoningEffortToClaudeOutputEffort(effort string) string {
- switch strings.TrimSpace(effort) {
- case "low", "medium", "high":
- return effort
- case "xhigh":
- return "max"
- default:
- return ""
- }
- }
- // openAICompatAnthropicReasoningEffort resolves the effort emitted by the
- // Anthropic bridge after the final upstream model is known. Anthropic's max is
- // normally translated to OpenAI xhigh, but GPT-5.6 accepts the original max
- // value on Responses and Chat Completions.
- func openAICompatAnthropicReasoningEffort(req *apicompat.AnthropicRequest, upstreamModel, convertedEffort string) string {
- if req == nil || req.OutputConfig == nil || !strings.EqualFold(strings.TrimSpace(req.OutputConfig.Effort), "max") {
- return convertedEffort
- }
- if normalized := normalizeOpenAIReasoningEffortForModel(req.OutputConfig.Effort, upstreamModel); normalized != "" {
- return normalized
- }
- return convertedEffort
- }
|