From 461b1bdd26be399eaba9812d46ad0106aae199bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:23:48 +0000 Subject: [PATCH 1/2] Initial plan From bd3e549c9bb679027b32b327041602b5d39b0aaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:29:37 +0000 Subject: [PATCH 2/2] fix: propagate MaxAICredits in buildExternalDetectorWorkflowData Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/threat_detection_helpers.go | 3 + pkg/workflow/threat_detection_test.go | 73 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/pkg/workflow/threat_detection_helpers.go b/pkg/workflow/threat_detection_helpers.go index 8b1574325ec..b6a9f68bc87 100644 --- a/pkg/workflow/threat_detection_helpers.go +++ b/pkg/workflow/threat_detection_helpers.go @@ -158,6 +158,9 @@ func buildExternalDetectorWorkflowData(data *WorkflowData, engineID string) *Wor if d.EngineConfig.APITarget == "" && data.EngineConfig != nil { d.EngineConfig.APITarget = data.EngineConfig.APITarget } + if data.SafeOutputs != nil && data.SafeOutputs.ThreatDetection != nil && data.SafeOutputs.ThreatDetection.MaxAICredits != 0 { + d.EngineConfig.MaxAICredits = data.SafeOutputs.ThreatDetection.MaxAICredits + } return d } diff --git a/pkg/workflow/threat_detection_test.go b/pkg/workflow/threat_detection_test.go index f43cc562a9b..1a06458b06d 100644 --- a/pkg/workflow/threat_detection_test.go +++ b/pkg/workflow/threat_detection_test.go @@ -2945,3 +2945,76 @@ func TestBuildDetectionEngineExecutionStepPropagatesModelCostsProviders(t *testi t.Errorf("expected detection awf-config.json to contain custom model pricing key; got:\n%s", allSteps) } } + +func TestBuildExternalDetectorWorkflowDataMaxAICredits(t *testing.T) { + compiler := NewCompiler() + + t.Run("uses detection runtime default expression when threat-detection max-ai-credits is unset", func(t *testing.T) { + data := &WorkflowData{ + AI: "copilot", + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{}, + }, + } + + steps := compiler.buildExternalDetectorExecutionStep(data) + allSteps := strings.Join(steps, "") + if !strings.Contains(allSteps, "vars."+compilerenv.DefaultDetectionMaxAICredits) { + t.Fatalf("expected external detector steps to reference vars.%s, got:\n%s", compilerenv.DefaultDetectionMaxAICredits, allSteps) + } + if !strings.Contains(allSteps, "'400'") { + t.Fatalf("expected external detector steps to include default fallback '400', got:\n%s", allSteps) + } + }) + + t.Run("uses explicit threat-detection max-ai-credits when provided", func(t *testing.T) { + data := &WorkflowData{ + AI: "copilot", + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{ + MaxAICredits: 777, + }, + }, + } + + steps := compiler.buildExternalDetectorExecutionStep(data) + allSteps := strings.Join(steps, "") + if strings.Contains(allSteps, "vars."+compilerenv.DefaultDetectionMaxAICredits) { + t.Fatalf("expected external detector steps not to reference vars.%s when explicit max-ai-credits is set, got:\n%s", compilerenv.DefaultDetectionMaxAICredits, allSteps) + } + if !strings.Contains(allSteps, `"maxAiCredits":777`) { + t.Fatalf("expected external detector steps to include maxAiCredits 777, got:\n%s", allSteps) + } + }) +} + +func TestBuildExternalDetectorWorkflowDataMaxAICreditsNotInheritedFromMainAgent(t *testing.T) { + compiler := NewCompiler() + + // When the main agent has an explicit MaxAICredits budget but + // safe-outputs.threat-detection.max-ai-credits is not set, the external + // detector must use its own runtime default expression rather than silently + // inheriting the agent budget. + data := &WorkflowData{ + AI: "copilot", + EngineConfig: &EngineConfig{ + MaxAICredits: 500, // explicit agent budget + }, + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{ + // max-ai-credits intentionally omitted + }, + }, + } + + steps := compiler.buildExternalDetectorExecutionStep(data) + allSteps := strings.Join(steps, "") + + if !strings.Contains(allSteps, "vars."+compilerenv.DefaultDetectionMaxAICredits) { + t.Fatalf("expected external detector steps to use runtime default expression vars.%s when detection max-ai-credits is unset, got:\n%s", + compilerenv.DefaultDetectionMaxAICredits, allSteps) + } + if strings.Contains(allSteps, `"maxAiCredits":500`) { + t.Fatalf("expected external detector steps NOT to inherit agent maxAiCredits=500, got:\n%s", allSteps) + } +}