getClient resolves the LLM client to use, optionally overriding provider/model. If clientAPIKey is non-empty or providerConfig has entries, creates a new client using the caller's credentials instead of the server's default ones. When metrics are enabled, the returned client is wrapped with instrume
(provider, model, clientAPIKey string, providerConfig map[string]string)
| 155 | // using the caller's credentials instead of the server's default ones. |
| 156 | // When metrics are enabled, the returned client is wrapped with instrumentation. |
| 157 | func (h *Handler) getClient(provider, model, clientAPIKey string, providerConfig map[string]string) (client.LLMClient, error) { |
| 158 | if provider == "" { |
| 159 | provider = h.defaultProvider |
| 160 | } |
| 161 | if model == "" { |
| 162 | model = h.defaultModel |
| 163 | } |
| 164 | |
| 165 | var ( |
| 166 | c client.LLMClient |
| 167 | err error |
| 168 | ) |
| 169 | |
| 170 | // Security: SSRF prevention — validate provider_config URLs before use (H1) |
| 171 | if len(providerConfig) > 0 && h.ssrfValidator != nil { |
| 172 | if err := h.ssrfValidator.ValidateProviderConfig(providerConfig); err != nil { |
| 173 | h.logger.Warn("SSRF: blocked provider config", |
| 174 | zap.String("provider", provider), |
| 175 | zap.Error(err), |
| 176 | ) |
| 177 | return nil, fmt.Errorf("invalid provider configuration: %w", err) |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // Client-forwarded credentials with provider-specific config (StackSpot, Ollama, etc.) |
| 182 | if len(providerConfig) > 0 { |
| 183 | h.logger.Info(i18n.T("server.handler.client_config"), |
| 184 | zap.String("provider", provider), |
| 185 | zap.Int("config_keys", len(providerConfig)), |
| 186 | ) |
| 187 | c, err = h.llmManager.CreateClientWithConfig(provider, model, clientAPIKey, providerConfig) |
| 188 | } else if clientAPIKey != "" { |
| 189 | // Client-forwarded API key only (OpenAI, Claude, Google, xAI) |
| 190 | h.logger.Info(i18n.T("server.handler.client_api_key"), |
| 191 | zap.String("provider", provider), |
| 192 | ) |
| 193 | c, err = h.llmManager.CreateClientWithKey(provider, model, clientAPIKey) |
| 194 | } else { |
| 195 | c, err = h.llmManager.GetClient(provider, model) |
| 196 | } |
| 197 | |
| 198 | if err != nil { |
| 199 | return nil, err |
| 200 | } |
| 201 | |
| 202 | // Wrap with metrics instrumentation if enabled |
| 203 | if h.llmMetrics != nil { |
| 204 | return client.NewInstrumentedClient(c, &llmMetricsAdapter{m: h.llmMetrics}, provider), nil |
| 205 | } |
| 206 | |
| 207 | return c, nil |
| 208 | } |
| 209 | |
| 210 | // llmMetricsAdapter bridges metrics.LLMMetrics to client.MetricsRecorder interface. |
| 211 | type llmMetricsAdapter struct { |
no test coverage detected