| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "reasonix/internal/config" |
| 6 | "reasonix/internal/netclient" |
| 7 | "reasonix/internal/provider" |
| 8 | ) |
| 9 | |
| 10 | // NewProvider builds a provider.Provider from a configured entry. Exported so |
| 11 | // custom assemblers (e.g. the ACP per-session factory) can reuse it without |
| 12 | // going through the full Build. |
| 13 | func NewProvider(e *config.ProviderEntry) (provider.Provider, error) { |
| 14 | return NewProviderWithProxy(e, netclient.ProxySpec{Mode: netclient.ModeAuto}) |
| 15 | } |
| 16 | |
| 17 | // NewProviderWithProxy builds a provider.Provider with the configured ordinary |
| 18 | // network proxy settings. |
| 19 | func NewProviderWithProxy(e *config.ProviderEntry, proxy netclient.ProxySpec) (provider.Provider, error) { |
| 20 | return NewProviderWithProxyAndModelInfo(e, proxy, nil) |
| 21 | } |
| 22 | |
| 23 | // NewProviderWithProxyAndModelInfo builds a provider while preserving the |
| 24 | // adapter-resolved metadata for the exact model instance. |
| 25 | func NewProviderWithProxyAndModelInfo(e *config.ProviderEntry, proxy netclient.ProxySpec, modelInfo *provider.ModelInfo) (provider.Provider, error) { |
| 26 | return newProviderWithSearchMode(e, proxy, modelInfo, true) |
| 27 | } |
| 28 | |
| 29 | // clientSearch suppresses new native searches while retaining the adapter's |
| 30 | // ability to read and replay existing native search history. |
| 31 | func newProviderWithSearchMode(e *config.ProviderEntry, proxy netclient.ProxySpec, modelInfo *provider.ModelInfo, clientSearch bool) (provider.Provider, error) { |
| 32 | // Runtime callers may share the resolved config entry across role/provider |
| 33 | // factories. Repair a private copy so construction cannot race or mutate the |
| 34 | // caller while still honoring exact catalog routes. |
| 35 | e = config.ResolveReasoningEntry(e) |
| 36 | if err := config.ValidateProviderEndpoint(e); err != nil { |
| 37 | return nil, err |
| 38 | } |
| 39 | reasoning := config.ReasoningCapabilityForEntry(e) |
| 40 | if err := reasoning.Validate(e.Model, config.EffectiveEffort(e)); err != nil { |
| 41 | return nil, err |
| 42 | } |
| 43 | if modelInfo == nil { |
| 44 | resolved := config.NewModelCapabilityResolver().Resolve(e) |
| 45 | modelInfo = &resolved.ModelInfo |
| 46 | } |
| 47 | var tunnelClient *http.Client |
| 48 | if endpoint := e.CredentialProxyURL(); endpoint != "" { |
| 49 | var err error |
| 50 | tunnelClient, err = netclient.NewModelCredentialProxyClient(endpoint, e.APIKey()) |
| 51 | if err != nil { |
| 52 | return nil, err |
| 53 | } |
| 54 | } |
| 55 | return provider.New(e.Kind, provider.Config{ |
| 56 | HTTPClient: tunnelClient, |
| 57 | Name: e.Name, DisplayName: e.DisplayName, Protocol: e.Kind, |
| 58 | BaseURL: e.BaseURL, Model: e.Model, APIKey: e.APIKey(), ModelInfo: modelInfo, |
| 59 | // Pass the key's env var so auth failures can name where to fix it, plus |
| 60 | // provider-kind-specific knobs. EffectiveEffort applies a configured |
| 61 | // default_effort when the user has not explicitly selected /effort. |
| 62 | Extra: map[string]any{ |
| 63 | "api_key_env": e.APIKeyEnv, |
| 64 | "api_key_source": e.APIKeySourceLabel(), |
| 65 | "thinking": e.Thinking, |
| 66 | "effort": config.EffectiveEffort(e), |
| 67 | "supported_efforts": e.SupportedEfforts, |
| 68 | "default_effort": reasoning.Default, |
| 69 | "reasoning_protocol": config.ReasoningProtocolForEntry(e), |
| 70 | "max_output_tokens": e.MaxOutputTokens, |
| 71 | "chat_url": e.ChatURL, |
| 72 | "request_url": e.RequestURL, |
| 73 | "headers": e.Headers, |
| 74 | "extra_body": e.ExtraBody, |
| 75 | "auth_header": e.AuthHeader, |
| 76 | "proxy_spec": proxy, |
| 77 | "vision": config.EffectiveVision(e), |
| 78 | "vision_detail": e.VisionDetail, |
| 79 | "web_search": config.EffectiveWebSearch(e), |
| 80 | "client_web_search": clientSearch, |
| 81 | "reject_redirects": !clientSearch, |
| 82 | "mode": e.ResponsesMode, |
| 83 | // Keep nil as nil so the responses provider can vendor-detect its |
| 84 | // default instead of accidentally treating every endpoint as stateful. |
| 85 | "stateful": e.ResponsesStateful, |
| 86 | }, |
| 87 | }) |
| 88 | } |
| 89 |