237 lines
6.9 KiB
Go
237 lines
6.9 KiB
Go
package openai
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
|
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
)
|
|
|
|
func websocketUpstreamSupportsIncrementalInput(attributes map[string]string, metadata map[string]any) bool {
|
|
if len(attributes) > 0 {
|
|
if raw := strings.TrimSpace(attributes["websockets"]); raw != "" {
|
|
parsed, errParse := strconv.ParseBool(raw)
|
|
if errParse == nil {
|
|
return parsed
|
|
}
|
|
}
|
|
}
|
|
if len(metadata) == 0 {
|
|
return false
|
|
}
|
|
raw, ok := metadata["websockets"]
|
|
if !ok || raw == nil {
|
|
return false
|
|
}
|
|
switch value := raw.(type) {
|
|
case bool:
|
|
return value
|
|
case string:
|
|
parsed, errParse := strconv.ParseBool(strings.TrimSpace(value))
|
|
if errParse == nil {
|
|
return parsed
|
|
}
|
|
default:
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *OpenAIResponsesAPIHandler) websocketUpstreamSupportsIncrementalInputForModel(modelName string) bool {
|
|
auths, _ := h.responsesWebsocketAvailableAuthsForModel(modelName)
|
|
for _, auth := range auths {
|
|
if responsesWebsocketAuthSupportsIncrementalInput(auth) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *OpenAIResponsesAPIHandler) websocketUpstreamSupportsCompactionReplayForModel(modelName string) bool {
|
|
auths, _ := h.responsesWebsocketAvailableAuthsForModel(modelName)
|
|
if len(auths) == 0 {
|
|
return false
|
|
}
|
|
for _, auth := range auths {
|
|
if !responsesWebsocketAuthSupportsCompactionReplay(auth) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (h *OpenAIResponsesAPIHandler) responsesWebsocketAvailableAuthsForModel(modelName string) ([]*coreauth.Auth, string) {
|
|
if h == nil || h.AuthManager == nil {
|
|
return nil, ""
|
|
}
|
|
resolvedModelName := responsesWebsocketResolvedModelName(modelName)
|
|
providerSet, modelKey := responsesWebsocketProviderSetForModel(resolvedModelName)
|
|
if len(providerSet) == 0 {
|
|
return nil, modelKey
|
|
}
|
|
|
|
registryRef := registry.GetGlobalRegistry()
|
|
now := time.Now()
|
|
auths := h.AuthManager.List()
|
|
available := make([]*coreauth.Auth, 0, len(auths))
|
|
for _, auth := range auths {
|
|
if !responsesWebsocketAuthMatchesModel(auth, providerSet, modelKey, registryRef, now) {
|
|
continue
|
|
}
|
|
available = append(available, auth)
|
|
}
|
|
return available, modelKey
|
|
}
|
|
|
|
func (h *OpenAIResponsesAPIHandler) responsesWebsocketUsesCodexWebsocketPassthrough(modelName string) bool {
|
|
return h.responsesWebsocketUsesUpstreamWebsocketPassthrough(modelName)
|
|
}
|
|
|
|
func (h *OpenAIResponsesAPIHandler) responsesWebsocketUsesUpstreamWebsocketPassthrough(modelName string) bool {
|
|
modelName = strings.TrimSpace(modelName)
|
|
if h == nil || h.AuthManager == nil || modelName == "" {
|
|
return false
|
|
}
|
|
auths, _ := h.responsesWebsocketAvailableAuthsForModel(modelName)
|
|
if len(auths) == 0 {
|
|
return false
|
|
}
|
|
provider := ""
|
|
for _, auth := range auths {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
authProvider := strings.ToLower(strings.TrimSpace(auth.Provider))
|
|
if authProvider != "codex" && authProvider != "xai" {
|
|
return false
|
|
}
|
|
if provider == "" {
|
|
provider = authProvider
|
|
if _, ok := h.AuthManager.Executor(provider); !ok {
|
|
return false
|
|
}
|
|
} else if authProvider != provider {
|
|
return false
|
|
}
|
|
if !websocketUpstreamSupportsIncrementalInput(auth.Attributes, auth.Metadata) {
|
|
return false
|
|
}
|
|
}
|
|
return provider != ""
|
|
}
|
|
|
|
func responsesWebsocketAuthSupportsIncrementalInput(auth *coreauth.Auth) bool {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
return websocketUpstreamSupportsIncrementalInput(auth.Attributes, auth.Metadata)
|
|
}
|
|
|
|
func responsesWebsocketPinnedAuthMatchesModel(auth *coreauth.Auth, modelName string, pinnedModelKey string, homeRuntime bool) bool {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
providerSet, modelKey := responsesWebsocketProviderSetForModel(responsesWebsocketResolvedModelName(modelName))
|
|
providerKey := strings.ToLower(strings.TrimSpace(auth.Provider))
|
|
if _, ok := providerSet[providerKey]; !ok {
|
|
return false
|
|
}
|
|
if !responsesWebsocketAuthAvailableForModel(auth, modelKey, time.Now()) {
|
|
return false
|
|
}
|
|
|
|
if homeRuntime {
|
|
return strings.EqualFold(strings.TrimSpace(pinnedModelKey), strings.TrimSpace(modelKey))
|
|
}
|
|
return registry.GetGlobalRegistry().ClientSupportsModel(auth.ID, modelKey)
|
|
}
|
|
|
|
func responsesWebsocketResolvedModelName(modelName string) string {
|
|
initialSuffix := thinking.ParseSuffix(modelName)
|
|
if initialSuffix.ModelName == "auto" {
|
|
resolvedBase := util.ResolveAutoModel(initialSuffix.ModelName)
|
|
if initialSuffix.HasSuffix {
|
|
return fmt.Sprintf("%s(%s)", resolvedBase, initialSuffix.RawSuffix)
|
|
}
|
|
return resolvedBase
|
|
}
|
|
return util.ResolveAutoModel(modelName)
|
|
}
|
|
|
|
func responsesWebsocketProviderSetForModel(resolvedModelName string) (map[string]struct{}, string) {
|
|
parsed := thinking.ParseSuffix(resolvedModelName)
|
|
baseModel := strings.TrimSpace(parsed.ModelName)
|
|
providers := util.GetProviderName(baseModel)
|
|
if len(providers) == 0 && baseModel != resolvedModelName {
|
|
providers = util.GetProviderName(resolvedModelName)
|
|
}
|
|
providerSet := make(map[string]struct{}, len(providers))
|
|
for _, provider := range providers {
|
|
providerKey := strings.TrimSpace(strings.ToLower(provider))
|
|
if providerKey == "" {
|
|
continue
|
|
}
|
|
providerSet[providerKey] = struct{}{}
|
|
}
|
|
modelKey := baseModel
|
|
if modelKey == "" {
|
|
modelKey = strings.TrimSpace(resolvedModelName)
|
|
}
|
|
return providerSet, modelKey
|
|
}
|
|
|
|
func responsesWebsocketAuthMatchesModel(auth *coreauth.Auth, providerSet map[string]struct{}, modelKey string, registryRef *registry.ModelRegistry, now time.Time) bool {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
providerKey := strings.TrimSpace(strings.ToLower(auth.Provider))
|
|
if _, ok := providerSet[providerKey]; !ok {
|
|
return false
|
|
}
|
|
if modelKey != "" && registryRef != nil && !registryRef.ClientSupportsModel(auth.ID, modelKey) {
|
|
return false
|
|
}
|
|
return responsesWebsocketAuthAvailableForModel(auth, modelKey, now)
|
|
}
|
|
|
|
func responsesWebsocketAuthSupportsCompactionReplay(auth *coreauth.Auth) bool {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
return strings.EqualFold(strings.TrimSpace(auth.Provider), "codex")
|
|
}
|
|
|
|
func responsesWebsocketAuthAvailableForModel(auth *coreauth.Auth, modelName string, now time.Time) bool {
|
|
if auth == nil {
|
|
return false
|
|
}
|
|
if auth.Disabled || auth.Status == coreauth.StatusDisabled {
|
|
return false
|
|
}
|
|
if modelName != "" && len(auth.ModelStates) > 0 {
|
|
state, ok := auth.ModelStates[modelName]
|
|
if (!ok || state == nil) && modelName != "" {
|
|
baseModel := strings.TrimSpace(thinking.ParseSuffix(modelName).ModelName)
|
|
if baseModel != "" && baseModel != modelName {
|
|
state, ok = auth.ModelStates[baseModel]
|
|
}
|
|
}
|
|
if ok && state != nil {
|
|
if state.Status == coreauth.StatusDisabled {
|
|
return false
|
|
}
|
|
if state.Unavailable && !state.NextRetryAfter.IsZero() && state.NextRetryAfter.After(now) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
if auth.Unavailable && !auth.NextRetryAfter.IsZero() && auth.NextRetryAfter.After(now) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|