11package io .forgeai .jenkins .config ;
22
3- import com .cloudbees .plugins .credentials .CredentialsMatchers ;
4- import com .cloudbees .plugins .credentials .CredentialsProvider ;
5- import com .cloudbees .plugins .credentials .common .StandardListBoxModel ;
6- import com .cloudbees .plugins .credentials .domains .DomainRequirement ;
73import hudson .Extension ;
8- import hudson .security .ACL ;
94import hudson .util .FormValidation ;
10- import hudson .util .ListBoxModel ;
5+ import io .forgeai .jenkins .llm .LLMProvider ;
6+ import io .forgeai .jenkins .llm .LLMProvider .LLMProviderDescriptor ;
7+ import io .forgeai .jenkins .llm .OpenAICompatibleProvider ;
118import jenkins .model .GlobalConfiguration ;
129import jenkins .model .Jenkins ;
1310import net .sf .json .JSONObject ;
1411import org .jenkinsci .Symbol ;
15- import org .jenkinsci .plugins .plaincredentials .StringCredentials ;
1612import org .kohsuke .stapler .DataBoundSetter ;
17- import org .kohsuke .stapler .QueryParameter ;
1813import org .kohsuke .stapler .StaplerRequest ;
1914import org .kohsuke .stapler .verb .POST ;
20- import io .forgeai .jenkins .llm .*;
21-
22- import java .util .Collections ;
2315
2416/**
2517 * Global configuration page for ForgeAI Pipeline Intelligence.
2921@ Symbol ("forgeAI" )
3022public class ForgeAIGlobalConfiguration extends GlobalConfiguration {
3123
32- // ── LLM Provider Settings ──────────────────────────────────────────
33- private String providerType = "openai" ; // openai | anthropic | ollama
34- private String llmEndpoint = "https://api.openai.com/" ;
35- private String modelId = "gpt-4o" ;
36- private String apiKeyCredentialId = "" ;
37- private double temperature = 0.2 ;
24+ // ── LLM Provider (selected via hetero-radio) ───────────────────
25+ private LLMProvider provider = new OpenAICompatibleProvider ();
26+
27+ // ── Common LLM call settings ───────────────────────────────────
28+ private double temperature = 0.2 ;
3829 private int timeoutSeconds = 120 ;
39- private int maxTokens = 4096 ;
30+ private int maxTokens = 4096 ;
4031
41- // ── Feature Toggles ────────────────────────────────────────────────
42- private boolean enableCodeReview = true ;
32+ // ── Feature Toggles ────────────────────────────────────────────
33+ private boolean enableCodeReview = true ;
4334 private boolean enableVulnerabilityAnalysis = true ;
44- private boolean enableArchitectureDrift = true ;
45- private boolean enableTestGapAnalysis = true ;
46- private boolean enableReleaseReadiness = true ;
47- private boolean enableCommitIntelligence = true ;
48- private boolean enableDependencyRisk = true ;
49- private boolean enablePipelineAdvisor = true ;
50-
51- // ── Reporting ──────────────────────────────────────────────────────
52- private boolean publishHtmlReport = true ;
53- private boolean failOnCritical = false ;
54- private int criticalThreshold = 3 ; // fail if score < 3/10
35+ private boolean enableArchitectureDrift = true ;
36+ private boolean enableTestGapAnalysis = true ;
37+ private boolean enableReleaseReadiness = true ;
38+ private boolean enableCommitIntelligence = true ;
39+ private boolean enableDependencyRisk = true ;
40+ private boolean enablePipelineAdvisor = true ;
41+
42+ // ── Reporting ──────────────────────────────────────────────────
43+ private boolean publishHtmlReport = true ;
44+ private boolean failOnCritical = false ;
45+ private int criticalThreshold = 3 ;
5546 private String customSystemPrompt = "" ;
5647
5748 public ForgeAIGlobalConfiguration () {
@@ -62,115 +53,43 @@ public static ForgeAIGlobalConfiguration get() {
6253 return GlobalConfiguration .all ().get (ForgeAIGlobalConfiguration .class );
6354 }
6455
65- // ── Persist ────────────────────────────────────────────────────────
6656 @ Override
6757 public boolean configure (StaplerRequest req , JSONObject json ) throws FormException {
6858 req .bindJSON (this , json );
6959 save ();
7060 return true ;
7161 }
7262
73- // ── Resolve the API key from Jenkins Credentials store ────────────
74- public String resolveApiKey () {
75- if (apiKeyCredentialId == null || apiKeyCredentialId .isBlank ()) return "" ;
76- StringCredentials cred = CredentialsMatchers .firstOrNull (
77- CredentialsProvider .lookupCredentialsInItemGroup (
78- StringCredentials .class ,
79- Jenkins .get (),
80- ACL .SYSTEM2 ,
81- Collections .<DomainRequirement >emptyList ()),
82- CredentialsMatchers .withId (apiKeyCredentialId ));
83- return cred != null ? cred .getSecret ().getPlainText () : "" ;
84- }
85-
86- // ── Validation helpers shown in real-time in the UI ────────────────
87- @ POST
88- public FormValidation doCheckLlmEndpoint (@ QueryParameter String value ) {
89- Jenkins .get ().checkPermission (Jenkins .ADMINISTER );
90- if (value == null || value .isBlank ()) return FormValidation .error ("Endpoint is required" );
91- if (!value .startsWith ("http" )) return FormValidation .error ("Must start with http:// or https://" );
92- return FormValidation .ok ();
93- }
94-
95- @ POST
96- public FormValidation doCheckModelId (@ QueryParameter String value ) {
97- Jenkins .get ().checkPermission (Jenkins .ADMINISTER );
98- if (value == null || value .isBlank ()) return FormValidation .error ("Model ID is required" );
99- return FormValidation .ok ();
63+ /** Returns all registered LLMProvider descriptors for the hetero-radio widget. */
64+ public java .util .List <LLMProviderDescriptor > getProviderDescriptors () {
65+ return LLMProvider .all ();
10066 }
10167
10268 @ POST
103- public FormValidation doTestConnection (@ QueryParameter String providerType ,
104- @ QueryParameter String llmEndpoint ,
105- @ QueryParameter String modelId ,
106- @ QueryParameter String apiKeyCredentialId ) {
69+ public FormValidation doTestConnection () {
10770 Jenkins .get ().checkPermission (Jenkins .ADMINISTER );
71+ if (provider == null ) return FormValidation .error ("No provider configured." );
10872 try {
109- // Temporarily build a provider for the test
110- ForgeAIGlobalConfiguration temp = new ForgeAIGlobalConfiguration ();
111- temp .setProviderType (providerType );
112- temp .setLlmEndpoint (llmEndpoint );
113- temp .setModelId (modelId );
114- temp .setApiKeyCredentialId (apiKeyCredentialId );
115-
116- LLMProvider provider = LLMProviderFactory .create (temp );
117- if (provider .healthCheck ()) {
118- return FormValidation .ok ("Connection successful — %s is reachable." , provider .displayName ());
119- } else {
120- return FormValidation .error ("Health-check failed. Verify endpoint, API key, and model ID." );
121- }
73+ return provider .healthCheck ()
74+ ? FormValidation .ok ("Connection successful — %s is reachable." , provider .displayName ())
75+ : FormValidation .error ("Health-check failed. Verify endpoint, API key, and model ID." );
12276 } catch (Exception e ) {
12377 return FormValidation .error ("Connection test failed: " + e .getMessage ());
12478 }
12579 }
12680
127- /** Populate the API-key credential dropdown. */
128- @ POST
129- public ListBoxModel doFillApiKeyCredentialIdItems (@ QueryParameter String apiKeyCredentialId ) {
130- if (!Jenkins .get ().hasPermission (Jenkins .ADMINISTER )) {
131- return new StandardListBoxModel ().includeCurrentValue (apiKeyCredentialId );
132- }
133- return new StandardListBoxModel ()
134- .includeEmptyValue ()
135- .includeMatchingAs (
136- ACL .SYSTEM2 ,
137- Jenkins .get (),
138- StringCredentials .class ,
139- Collections .<DomainRequirement >emptyList (),
140- CredentialsMatchers .always ())
141- .includeCurrentValue (apiKeyCredentialId );
142- }
143-
144- /** Provider type dropdown. */
145- public ListBoxModel doFillProviderTypeItems () {
146- ListBoxModel items = new ListBoxModel ();
147- items .add ("OpenAI / OpenAI-Compatible (LM Studio, vLLM, LocalAI)" , "openai" );
148- items .add ("Anthropic Claude" , "anthropic" );
149- items .add ("Ollama (Local)" , "ollama" );
150- return items ;
151- }
152-
153- // ── Getters & Setters (DataBoundSetter for Jenkins persistence) ───
154-
155- public String getProviderType () { return providerType ; }
156- @ DataBoundSetter public void setProviderType (String v ) { this .providerType = v ; }
157-
158- public String getLlmEndpoint () { return llmEndpoint ; }
159- @ DataBoundSetter public void setLlmEndpoint (String v ) { this .llmEndpoint = v ; }
160-
161- public String getModelId () { return modelId ; }
162- @ DataBoundSetter public void setModelId (String v ) { this .modelId = v ; }
81+ // ── Getters & Setters ──────────────────────────────────────────
16382
164- public String getApiKeyCredentialId () { return apiKeyCredentialId ; }
165- @ DataBoundSetter public void setApiKeyCredentialId ( String v ) { this .apiKeyCredentialId = v ; }
83+ public LLMProvider getProvider () { return provider ; }
84+ @ DataBoundSetter public void setProvider ( LLMProvider v ) { this .provider = v ; }
16685
16786 public double getTemperature () { return temperature ; }
16887 @ DataBoundSetter public void setTemperature (double v ) { this .temperature = v ; }
16988
170- public int getTimeoutSeconds () { return timeoutSeconds ; }
89+ public int getTimeoutSeconds () { return timeoutSeconds > 0 ? timeoutSeconds : 120 ; }
17190 @ DataBoundSetter public void setTimeoutSeconds (int v ) { this .timeoutSeconds = v ; }
17291
173- public int getMaxTokens () { return maxTokens ; }
92+ public int getMaxTokens () { return maxTokens > 0 ? maxTokens : 4096 ; }
17493 @ DataBoundSetter public void setMaxTokens (int v ) { this .maxTokens = v ; }
17594
17695 public boolean isEnableCodeReview () { return enableCodeReview ; }
0 commit comments