package openai_compat import ( "net/http" "net/url" "strings" "go.uber.org/zap" "iop/packages/go/config" ) const Name = "openai_compat" // Adapter is the OpenAI-compatible adapter. It carries an explicit provider // label, per-request auth/header injection and top-level option passthrough so // a provider boundary and its config contract stay distinct. type Adapter struct { instanceName string provider string endpoint string headers map[string]string capacity int maxQueue int queueTimeoutMS int requestTimeoutMS int client *http.Client logger *zap.Logger // profile is the resolved concrete protocol profile snapshot. When non-nil, // the adapter resolves operation URLs from the profile's operation paths // instead of the legacy endpoint + /v1 suffix heuristic. profile *config.ConcreteProtocolProfile } // New creates an OpenAI-compatible adapter. The optional instanceName is the // registry instance key used to disambiguate multiple instances on a node. func New(cfg config.OpenAICompatConf, logger *zap.Logger, instanceName ...string) *Adapter { endpoint := strings.TrimRight(cfg.Endpoint, "/") name := Name if len(instanceName) > 0 && instanceName[0] != "" { name = instanceName[0] } var headers map[string]string if len(cfg.Headers) > 0 { headers = make(map[string]string, len(cfg.Headers)) for k, v := range cfg.Headers { headers[k] = v } } var profile *config.ConcreteProtocolProfile if cfg.RuntimeProfile != nil { cloned := cfg.RuntimeProfile.Clone() profile = &cloned } return &Adapter{ instanceName: name, provider: cfg.Provider, endpoint: endpoint, headers: headers, capacity: cfg.Capacity, maxQueue: cfg.MaxQueue, queueTimeoutMS: cfg.QueueTimeoutMS, requestTimeoutMS: cfg.RequestTimeoutMS, client: &http.Client{}, logger: logger, profile: profile, } } func (a *Adapter) Name() string { return Name } // operationURL resolves an operation id to its full URL. When the adapter has // a concrete profile, the profile's operation path is used. When no profile // is present (legacy payload), the endpoint is used with the legacy path // appended. func (a *Adapter) operationURL(op string, legacyPath string) (string, error) { if a.profile != nil { return a.profile.ResolveOperationURL(op) } if a.endpoint == "" { return "", errEndpointRequired } return resolveLegacyURL(a.endpoint, legacyPath), nil } // resolveLegacyURL appends a legacy path to the endpoint, stripping a // duplicate /v1 suffix to avoid /v1/v1. func resolveLegacyURL(endpoint, path string) string { u, err := url.Parse(endpoint) if err != nil { return strings.TrimRight(endpoint, "/") + path } basePath := strings.TrimRight(u.Path, "/") if strings.HasSuffix(basePath, "/v1") && strings.HasPrefix(path, "/v1/") { path = strings.TrimPrefix(path, "/v1") } u.Path = basePath + path return u.String() } var errEndpointRequired = fmtError("openai_compat adapter: endpoint is required") // fmtError is a small helper to avoid importing fmt just for error creation // in the legacy fallback path. func fmtError(msg string) error { return &adapterError{msg: msg} } type adapterError struct{ msg string } func (e *adapterError) Error() string { return e.msg }