Skip to content

feat: implement usage quota for AI provider calls with configuration#136

Open
shenxianpeng wants to merge 7 commits intomainfrom
fix-132
Open

feat: implement usage quota for AI provider calls with configuration#136
shenxianpeng wants to merge 7 commits intomainfrom
fix-132

Conversation

@shenxianpeng
Copy link
Copy Markdown
Member

@shenxianpeng shenxianpeng commented Apr 7, 2026

closes #132

Testing done

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@shenxianpeng shenxianpeng added the enhancement For changelog: Minor enhancement. use `major-rfe` for changes to be highlighted label Apr 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements an optional, controller-scoped usage quota to limit real AI provider calls (while keeping cache hits free), helping administrators control cost/usage across the Jenkins controller.

Changes:

  • Adds global quota configuration (enable flag, window size, max calls) and UI fields in global config.
  • Introduces QuotaEnforcer + QuotaWindow and enforces quota checks before provider calls in ErrorExplainer.
  • Extends test coverage to validate quota-rejected behavior, quota-disabled behavior, and “cache hit does not consume quota”.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/main/java/io/jenkins/plugins/explain_error/GlobalConfigurationImpl.java Stores quota settings and exposes UI binding/validation endpoints + tryAcquireQuota()
src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java Applies quota enforcement for pipeline-step and console-action provider calls
src/main/java/io/jenkins/plugins/explain_error/QuotaEnforcer.java Thread-safe rolling-window counter for quota enforcement
src/main/java/io/jenkins/plugins/explain_error/QuotaWindow.java Defines HOURLY/DAILY windows and their durations
src/main/resources/io/jenkins/plugins/explain_error/GlobalConfigurationImpl/config.jelly Adds quota configuration controls to Jenkins global config UI
src/test/java/io/jenkins/plugins/explain_error/UsageTrackingTest.java Adds quota-related usage-event assertions and cache-hit behavior validation
src/test/java/io/jenkins/plugins/explain_error/QuotaEnforcerTest.java Adds unit tests for quota enforcement and reset behavior
src/test/java/io/jenkins/plugins/explain_error/GlobalConfigurationImplTest.java Verifies default quota values and persistence of quota settings

Comment on lines +187 to +200
public ListBoxModel doFillQuotaWindowItems() {
ListBoxModel items = new ListBoxModel();
for (QuotaWindow window : QuotaWindow.values()) {
items.add(window.getDisplayName(), window.name());
}
return items;
}

public FormValidation doCheckMaxProviderCallsPerWindow(@QueryParameter int value) {
if (value < 0) {
return FormValidation.error("Max provider calls per window must be 0 or greater.");
}
return FormValidation.ok();
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doFillQuotaWindowItems and doCheckMaxProviderCallsPerWindow are exposed Stapler endpoints but currently lack the @POST annotation and any permission check/suppression. In this codebase, comparable descriptor validation endpoints use @POST (and either check Jenkins.ADMINISTER or explicitly suppress the permission-check warning). Please align these methods with that pattern to avoid CSRF / permission-audit findings.

Copilot uses AI. Check for mistakes.
Comment on lines 117 to +130
@@ -118,6 +118,17 @@ String explainError(Run<?, ?> run, TaskListener listener, String logPattern, int
String effectiveCustomContext = StringUtils.isNotBlank(customContext) ? customContext : GlobalConfigurationImpl.get().getCustomContext();
logToConsole(listener, "Custom context source: " + resolveCustomContextSource(customContext) + ".");

// Check quota before making a real provider call
if (!GlobalConfigurationImpl.get().tryAcquireQuota()) {
GlobalConfigurationImpl config = GlobalConfigurationImpl.get();
String msg = "Provider call quota exceeded. Limit: " + config.getMaxProviderCallsPerWindow()
+ " calls per " + config.getQuotaWindow().getDisplayName().toLowerCase() + " window.";
logToConsole(listener, msg);
recordUsage(entryPoint, UsageEvent.Result.QUOTA_REJECTED, provider, startTimeNanos,
inputLogLineCount, collectDownstreamLogs);
return null;
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quota is checked only after running log extraction/filtering. When quota is exhausted, this still performs potentially expensive PipelineLogExtractor work even though the provider call will be rejected. Consider moving the quota check earlier (before extracting logs) so quota-rejected requests are cheap and reduce controller load under sustained quota exhaustion.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +56
void windowRolloverResetsCount() throws InterruptedException {
// Use a very short custom duration to simulate window rollover without waiting an hour.
// We do this by resetting the enforcer, which simulates a fresh window.
QuotaEnforcer enforcer = new QuotaEnforcer();

// Fill up the window
assertTrue(enforcer.tryAcquire(QuotaWindow.HOURLY, 1));
assertFalse(enforcer.tryAcquire(QuotaWindow.HOURLY, 1));

// Simulate rollover by resetting
enforcer.reset();

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test’s comment says it uses a “very short custom duration” to simulate rollover, but the implementation actually just calls reset(). Also, the test declares throws InterruptedException without sleeping/waiting. Please update the comment/signature so the test accurately reflects the behavior being verified.

Copilot uses AI. Check for mistakes.
@shenxianpeng shenxianpeng marked this pull request as ready for review April 7, 2026 22:18
@shenxianpeng shenxianpeng requested a review from a team as a code owner April 7, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement For changelog: Minor enhancement. use `major-rfe` for changes to be highlighted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add optional usage quotas for Explain Error provider calls

3 participants