Skip to content

Propagate models.providers pricing into AWF apiProxy (including threat-detection runs)#48107

Open
pelikhan with Copilot wants to merge 10 commits into
mainfrom
copilot/fix-models-providers-pricing
Open

Propagate models.providers pricing into AWF apiProxy (including threat-detection runs)#48107
pelikhan with Copilot wants to merge 10 commits into
mainfrom
copilot/fix-models-providers-pricing

Conversation

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

models.providers overrides were reaching only the agent-side catalog, while the API proxy guardrail evaluated a config that lacked provider pricing; custom/BYOK models still failed at turn 1 with unknown_model_ai_credits. The same gap affected threat-detection runs, which use their own guarded AWF config.

  • AWF config now carries provider pricing overlays

    • Added apiProxy.providers to the generated AWF config shape and emit path.
    • Mapped WorkflowData.ModelCosts.providers into apiProxy.providers so proxy-side AI-credit pricing can resolve custom models.
  • Schema alignment for emitted config

    • Extended pkg/workflow/schemas/awf-config.schema.json to allow apiProxy.providers, preventing validation-time rejection/stripping of emitted provider catalogs.
  • Threat-detection parity with main agent path

    • Propagated ModelCosts into threat-detection workflow data construction so detection AWF config receives the same pricing overrides as the primary agent run.
  • Focused coverage for regression protection

    • Added tests asserting:
      • BuildAWFConfigJSON includes apiProxy.providers with custom model cost entries.
      • Detection execution step generation embeds provider pricing in detection awf-config.json.
models:
  providers:
    anthropic:
      models:
        accounts/fireworks/models/minimax-m3:
          cost:
            input: "3e-07"
            output: "1.5e-06"
            cache_read: "3e-08"
            cache_write: "3.75e-07"

With this change, those entries are emitted into apiProxy.providers (main + detection), where the AI-credits guardrail performs pricing lookup.


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 14 AIC · ⌖ 8.18 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 13.6 AIC · ⌖ 9.97 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 26, 2026 03:39
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix models.providers custom pricing not reaching api-proxy Propagate models.providers pricing into AWF apiProxy (including threat-detection runs) Jul 26, 2026
Copilot AI requested a review from pelikhan July 26, 2026 03:49
@pelikhan
pelikhan marked this pull request as ready for review July 26, 2026 03:50
Copilot AI review requested due to automatic review settings July 26, 2026 03:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Propagates custom model pricing into AWF API-proxy configuration for primary and threat-detection runs.

Changes:

  • Adds apiProxy.providers generation and schema support.
  • Propagates model costs into threat-detection data.
  • Adds regression tests and updates workflow skill references.
Show a summary per file
File Description
pkg/workflow/awf_config.go Emits provider pricing overlays.
pkg/workflow/awf_config_test.go Tests provider emission.
pkg/workflow/schemas/awf-config.schema.json Allows the providers field.
pkg/workflow/threat_detection_external.go Propagates detection pricing data.
pkg/workflow/threat_detection_test.go Tests detection propagation.
.github/skills/agentic-workflows/SKILL.md Adds release workflow reference.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread pkg/workflow/awf_config.go Outdated
Comment on lines +568 to +570
if providers := extractModelCostProviders(config.WorkflowData); len(providers) > 0 {
apiProxy.Providers = providers
awfConfigLog.Printf("API proxy: %d model-cost provider override(s) configured", len(providers))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1fc2916: apiProxy.providers is now version-gated behind AWFAPIProxyProvidersMinVersion so older AWF versions (including current default) do not emit an unsupported field. I also preserved firewall version propagation into detection/evals workflow data so supported versions still receive the overlay.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Propagate models.providers pricing into AWF apiProxy

The implementation is well-structured with good test coverage. One existing inline comment flags the key issue (version compatibility of apiProxy.providers vs DefaultFirewallVersion v0.27.41) — that should be resolved before merging.

No additional blocking issues found. The extractModelCostProviders logic, schema extension, and threat-detection propagation are all correct.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 14.9 AIC · ⌖ 4.5 AIC · ⊞ 5K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 60/100 — Acceptable

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 0 (0%)
Duplicate clusters 0
Inflation YES (threat_detection_test.go: 43 added lines vs 1 prod line)
🚨 Violations 0
Test File Classification Issues
TestBuildAWFConfigJSON / models.providers cost overlay is emitted awf_config_test.go design_test, high_value Happy-path only
TestBuildDetectionEngineExecutionStepPropagatesModelCostsProviders threat_detection_test.go design_test, high_value Happy-path only; test inflation
⚠️ Flagged Tests (1)

TestBuildDetectionEngineExecutionStepPropagatesModelCostsProviders (threat_detection_test.go) — The production change is a single-line addition in threat_detection_external.go, but the test adds 43 lines (43:1 ratio, threshold: 2:1). The test is still high-value as it validates end-to-end propagation through buildDetectionEngineExecutionStep and BuildAWFConfigJSON. Both new tests are happy-path only — consider adding edge cases such as nil ModelCosts, empty providers map, or non-map providers value to exercise the guard branches in extractModelCostProviders.

Verdict

passed. 0% implementation tests (threshold: 30%). Score penalized for lack of edge-case coverage and test inflation in threat_detection_test.go.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 38.7 AIC · ⌖ 8.47 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 60/100 — Acceptable. 0% implementation tests (threshold: 30%). No violations. Score penalized for test inflation and lack of edge-case coverage in threat_detection_test.go.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (109 new lines in pkg/ directories) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/48107-propagate-model-provider-pricing-into-awf-apiproxy-config.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-48107: Propagate model.providers pricing into AWF apiProxy config

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 48107-propagate-model-provider-pricing-into-awf-apiproxy-config.md for PR #48107).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 39.3 AIC · ⌖ 11.4 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — commenting (not blocking) on three targeted hardening opportunities.

📋 Key Themes & Highlights

Key Themes

  • Version compatibility — already flagged in the existing Copilot comment at line 570: the DefaultFirewallVersion (v0.27.41) predates the apiProxy.providers field. This is the highest-priority issue and already under review.
  • Silent failure modeextractModelCostProviders type-asserts without logging the !ok case, so a type mismatch would silently reproduce the original bug.
  • Untyped map[string]any fieldProviders breaks the struct's otherwise-typed pattern (AWFAPITargetConfig, AiCreditsPricingConfig), making the shape opaque.
  • Shallow test assertionsassert.Contains(jsonStr, "providers") doesn't verify the key lands under apiProxy.

Positive Highlights

  • ✅ Minimal, focused fix: 110 additions, 0 deletions
  • ✅ Both main and detection paths covered
  • ✅ Schema updated to prevent validation stripping
  • ✅ Good regression test structure overall

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 38.2 AIC · ⌖ 4.91 AIC · ⊞ 6.7K
Comment /matt to run again

if workflowData == nil || len(workflowData.ModelCosts) == 0 {
return nil
}
providers, ok := workflowData.ModelCosts["providers"].(map[string]any)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] The type assertion workflowData.ModelCosts["providers"].(map[string]any) silently discards the value if the concrete type differs from map[string]any (e.g. after JSON round-trip). A mismatch produces silent no-op behaviour identical to the original bug — no log, no error.

💡 Suggested hardening

Add a log line in the !ok branch, consistent with the pattern used elsewhere in this file:

providers, ok := workflowData.ModelCosts["providers"].(map[string]any)
if !ok {
    awfConfigLog.Printf("API proxy: models.providers has unexpected type %T; skipping provider overlay",
        workflowData.ModelCosts["providers"])
    return nil
}

This surfaces future type mismatches at runtime instead of masking them.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1fc2916: extractModelCostProviders now logs an explicit message when models.providers has an unexpected type and skips the overlay safely.

// AI-credits guardrails for models not present in the built-in pricing table.
// Structure matches models.json provider format:
// providers.<provider>.models.<model>.cost.{input,output,cache_read,cache_write,reasoning}
Providers map[string]any `json:"providers,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] Providers is typed map[string]any, which gives no compile-time safety and makes it easy for callers to pass structurally wrong data that only fails at AWF runtime. Consider defining a typed struct (e.g. AWFProviderConfig) mirroring the models.json provider shape.

💡 Why this matters

Other fields in this struct use concrete types (AWFAPITargetConfig, AiCreditsPricingConfig, etc.). Using map[string]any here is inconsistent and makes the shape opaque to future readers. A typed struct:

  • provides schema documentation in code
  • enables Go's type checker to catch structural errors before they reach the AWF binary
  • is consistent with how AWFAPITargetConfig handles targets

If the shape needs to remain flexible, at minimum add a doc comment listing the expected keys.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept Providers as map[string]any intentionally in 1fc2916 because this field forwards the upstream AWF provider catalog shape as-is and that shape is intentionally open-ended across providers/models. I added stricter behavioral guardrails instead (version gating, type-check logging, and extraction hardening) so unsupported or malformed input fails safely.

Comment thread pkg/workflow/awf_config_test.go Outdated
jsonStr, err := BuildAWFConfigJSON(config)
require.NoError(t, err)
assert.Contains(t, jsonStr, `"providers"`, "apiProxy should emit providers when model cost overlay is configured")
assert.Contains(t, jsonStr, `"anthropic"`, "apiProxy.providers should include anthropic provider key")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The test asserts that "providers" appears as a JSON substring, but does not verify the key appears specifically under apiProxy. A misconfigured serialisation that emits providers at the top level would pass this check.

💡 Tighter assertion

Unmarshal the JSON and walk to apiProxy.providers directly:

var parsed map[string]any
require.NoError(t, json.Unmarshal([]byte(jsonStr), &parsed))
apiProxy, ok := parsed["apiProxy"].(map[string]any)
require.True(t, ok, "expected apiProxy object")
_, hasProviders := apiProxy["providers"]
assert.True(t, hasProviders, "apiProxy should contain providers")

This matches how other tests in this file verify nested structure.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1fc2916: the test now unmarshals the generated JSON and asserts apiProxy.providers structurally (including nested provider/model/cost keys) instead of relying on substring checks.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: non-blocking with one fixable concern

The fix is correct and well-scoped: propagating ModelCosts into both the main AWF config and the threat-detection path closes a real gap. Tests are meaningful and regression-targeted.

One medium issue flagged inline; the existing comment about version-gating is also worth tracking.

Findings summary

Medium — map alias in extractModelCostProviders (see inline comment): The providers sub-map is returned by direct type-assertion reference. Any mutation of apiProxy.Providers post-assignment silently writes back into WorkflowData.ModelCosts. A shallow clone breaks the alias safely.

Pre-existing (already commented)apiProxy.providers emitted against AWF binary v0.27.41 which may not support the field. The version-gate pattern used elsewhere in this file (awfSupports* checks) should guard this new field too.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 70 AIC · ⌖ 4.72 AIC · ⊞ 5.7K
Comment /review to run again

Comment thread pkg/workflow/awf_config.go Outdated
if !ok || len(providers) == 0 {
return nil
}
return providers

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Aliased map reference returned directly — mutations to apiProxy.Providers will silently corrupt WorkflowData.ModelCosts.

💡 Suggested fix

The type assertion on line 841 returns the live sub-map from workflowData.ModelCosts["providers"]. Assigning it to apiProxy.Providers creates an alias — any downstream mutation (normalization, schema validation, or future serialization helpers) will corrupt the originating WorkflowData in place.

func extractModelCostProviders(workflowData *WorkflowData) map[string]any {
	if workflowData == nil || len(workflowData.ModelCosts) == 0 {
		return nil
	}
	providers, ok := workflowData.ModelCosts["providers"].(map[string]any)
	if !ok || len(providers) == 0 {
		return nil
	}
	// Break alias so AWFAPIProxyConfig.Providers cannot corrupt WorkflowData
	clone := make(map[string]any, len(providers))
	for k, v := range providers {
		clone[k] = v
	}
	return clone
}

This is especially relevant given buildThreatDetectionWorkflowData now shares the same ModelCosts map reference — a mutation via either path reaches the same underlying map.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1fc2916: extractModelCostProviders now returns a cloned map (using maps.Copy) so apiProxy.Providers no longer aliases WorkflowData.ModelCosts["providers"].

@github-actions

Copy link
Copy Markdown
Contributor

Great work! 🎯 This PR looks ready for review. The fix properly propagates models.providers pricing overrides into the API proxy config for both main and threat-detection runs, addressing the root cause of custom/BYOK models failing at the AI-credits guardrail.

What stands out:

  • Clean separation of concerns: schema extension, data mapping, and threat-detection parity are all isolated and testable
  • Tests cover both the happy path (BuildAWFConfigJSON with provider entries) and the detection execution step generation
  • Comprehensive description with concrete examples (the YAML block)
  • Fixes the gap that was blocking users with custom pricing models

The implementation looks solid with appropriate coverage for regression protection. Ready for maintainer review! 🚀

Generated by ✅ Contribution Check · sonnet46 · 61.5 AIC · ⌖ 7.79 AIC · ⊞ 6.2K ·

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage — §30192186368

Field Value
Category bug
Risk 🟡 medium
Score 69 / 100 (impact 38 + urgency 15 + quality 16)
Action ⚡ fast_track

Why: models.providers pricing not propagated to AWF apiProxy or threat-detection config, causing BYOK/custom models to fail with unknown_model_ai_credits on turn 1. Bot-approved, tests included. CI status unknown.

Next: Verify CI passes, then fast-track merge.

Generated by 🔧 PR Triage Agent · sonnet46 · 48.8 AIC · ⌖ 8.75 AIC · ⊞ 5.7K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot also propagate to evals job

Copilot AI and others added 2 commits July 26, 2026 08:33
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot also propagate to evals job

Implemented in 1296ec0. Evals now propagates WorkflowData.ModelCosts into evals AWF config generation, and I added a regression test to verify apiProxy.providers pricing entries are present in evals awf-config.json.

Copilot AI and others added 2 commits July 26, 2026 08:51
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…gation

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot July 26, 2026 09:31
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback and the maintainer request to also propagate pricing to the evals job, then summarize any remaining blocker for maintainers.

Unresolved review feedback:

Maintainer follow-up:

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 13.6 AIC · ⌖ 9.97 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved review feedback and the maintainer request to also propagate pricing to the evals job, then summarize any remaining blocker for main...

Done. The maintainer follow-up to propagate pricing to evals is already implemented in 1296ec0, and the unresolved github-actions review items were already addressed in 1fc2916 (type-mismatch logging, structural test assertion, alias-safe provider map clone, and version gating behavior). I reran local checks (make fmt, make lint, make test-unit, make test, make recompile) and targeted regression tests for main/detection/evals provider propagation all pass on current HEAD.

Remaining blocker: the review threads are still marked unresolved in GitHub UI and need maintainer/reviewer resolution plus CI re-trigger on this HEAD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

models.providers custom pricing never reaches the api-proxy; custom/BYOK models still 400 with unknown_model_ai_credits

4 participants