package openai import ( edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" "strings" ) // routeUsesProviderTunnel reports whether the resolved dispatch targets an // OpenAI-compatible provider that serves raw tunnel passthrough. Provider-pool // catalog routes and openai_compat/vllm type routes qualify; other // legacy adapters keep the normalized RunEvent path. func routeUsesProviderTunnel(d routeDispatch) bool { if d.ProviderPool { return true } switch strings.TrimSpace(d.Adapter) { case "openai_compat", "vllm": return true default: return false } } func routeSupportsNativeToolCalls(dispatch routeDispatch) bool { if dispatch.ProviderPool { return true } switch strings.TrimSpace(dispatch.Adapter) { case "openai_compat", "vllm", "ollama": return true default: return false } } func (s *Server) resolveAdapter() string { if s.cfg.Adapter != "" { return s.cfg.Adapter } return "ollama" } func (s *Server) resolveTarget(model string) string { if s.cfg.Target != "" { return s.cfg.Target } return strings.TrimSpace(model) } // routeDispatch holds fully-resolved dispatch parameters for a single request. type routeDispatch struct { NodeRef string ProviderID string UsageAttribution string Adapter string Target string SessionID string TimeoutSec int MaxQueue int QueueTimeoutMS int // ProviderPool is true when the request model matched a provider-pool // catalog entry. Adapter and Target are empty; the service layer resolves // them per-candidate and rewrites Target after admission. ProviderPool bool Managed bool ModelGroupKey string RouteID string CredentialSlotRef string ProfileID string UpstreamModel string ResourceSelector string RouteRevision uint64 CredentialRevision uint64 PrincipalRef string ProjectionGeneration uint64 ManagedPredicate edgeservice.ProviderPoolCandidatePredicate } func (d routeDispatch) credentialBinding() *edgeservice.CredentialBinding { if !d.Managed { return nil } return &edgeservice.CredentialBinding{ PrincipalRef: d.PrincipalRef, CredentialSlotRef: d.CredentialSlotRef, RouteID: d.RouteID, ProfileID: d.ProfileID, CredentialRevision: d.CredentialRevision, RouteRevision: d.RouteRevision, ProjectionGeneration: d.ProjectionGeneration, } } func (d routeDispatch) CandidatePredicate() edgeservice.ProviderPoolCandidatePredicate { return d.ManagedPredicate } func (d routeDispatch) effectiveModelGroupKey(requestModel string) string { if key := strings.TrimSpace(d.ModelGroupKey); key != "" { return key } return strings.TrimSpace(requestModel) } // resolveRoute returns the first catalog entry whose Model matches model. // Entries with an empty Target are skipped. func (s *Server) resolveRoute(model string) *config.OpenAIRouteEntry { model = strings.TrimSpace(model) if model == "" { return nil } for i := range s.cfg.ModelRoutes { r := &s.cfg.ModelRoutes[i] if strings.TrimSpace(r.Model) == model && r.Target != "" { return r } } return nil } // findProviderPoolEntry returns the catalog entry matching model, or nil. func (s *Server) findProviderPoolEntry(model string) *config.ModelCatalogEntry { model = strings.TrimSpace(model) if model == "" { return nil } modelCatalog := s.modelCatalogSnapshot() for i := range modelCatalog { if modelCatalog[i].ID == model { entry := modelCatalog[i] return &entry } } return nil } // resolveRouteDispatch returns fully-resolved dispatch params for model. // Priority: provider-pool catalog → legacy model_routes → single-target fallback. // Returns (dispatch, true) on success; (zero, false) when no target can be resolved. func (s *Server) resolveRouteDispatch(model string) (routeDispatch, bool) { // Provider-pool catalog takes highest priority. if catalogEntry := s.findProviderPoolEntry(model); catalogEntry != nil { return routeDispatch{ UsageAttribution: catalogEntry.EffectiveUsageAttribution(), SessionID: s.resolveSessionID(), TimeoutSec: s.resolveTimeoutSec(), ProviderPool: true, }, true } if route := s.resolveRoute(model); route != nil { providerID := strings.TrimSpace(route.ProviderID) if providerID == "" { providerID = strings.TrimSpace(s.cfg.ProviderID) } adapter := route.Adapter if adapter == "" { adapter = s.resolveAdapter() } nodeRef := route.NodeRef if nodeRef == "" { nodeRef = s.cfg.NodeRef } sessionID := route.SessionID if sessionID == "" { sessionID = s.resolveSessionID() } timeoutSec := route.TimeoutSec if timeoutSec <= 0 { timeoutSec = s.resolveTimeoutSec() } return routeDispatch{ NodeRef: nodeRef, ProviderID: providerID, UsageAttribution: config.UsageAttributionProvider, Adapter: adapter, Target: route.Target, SessionID: sessionID, TimeoutSec: timeoutSec, MaxQueue: route.MaxQueue, QueueTimeoutMS: route.QueueTimeoutMS, }, true } target := s.resolveTarget(model) if target == "" { return routeDispatch{}, false } return routeDispatch{ NodeRef: s.cfg.NodeRef, ProviderID: strings.TrimSpace(s.cfg.ProviderID), UsageAttribution: config.UsageAttributionProvider, Adapter: s.resolveAdapter(), Target: target, SessionID: s.resolveSessionID(), TimeoutSec: s.resolveTimeoutSec(), }, true } func (s *Server) resolveSessionID() string { if s.cfg.SessionID != "" { return s.cfg.SessionID } return edgeservice.DefaultSessionID } func (s *Server) resolveTimeoutSec() int { if s.cfg.TimeoutSec > 0 { return s.cfg.TimeoutSec } return edgeservice.DefaultTimeoutSec }