test: regression tests for Copilot SDK model-missing AI credits bug#4797
test: regression tests for Copilot SDK model-missing AI credits bug#4797lpcox wants to merge 4 commits into
Conversation
When Copilot CLI streams through api-proxy (copilot-sdk mode), the upstream response SSE chunks may omit the `model` field. The token tracker falls back to model='unknown', which has no pricing entry, causing AI credits to be silently dropped (GH_AW_AIC is empty). These tests document the current broken behavior: 1. Streaming SSE without model → onUsage gets model='unknown' 2. Non-streaming JSON without model → same issue 3. AI credits guard returns null for 'unknown' model (credits lost) Observed in production: gh-aw run #27371175049 (pr-triage-agent) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
This PR adds a new api-proxy regression test suite to document (and prevent reintroduction of) a Copilot SDK behavior where streaming (SSE) and non-streaming responses can omit the model field, causing the token tracker to fall back to model='unknown' and AI credits to be dropped.
Changes:
- Add regression tests covering SSE chunks without
model, SSE chunks withmodel(control), and JSON responses withoutmodel. - Add a test demonstrating the AI credits impact when
model='unknown'(credits calculation returnsnullwith no default pricing).
Show a summary per file
| File | Description |
|---|---|
| containers/api-proxy/token-tracker.copilot-sdk-model.test.js | New Jest regression tests reproducing the missing-model → unknown fallback and AI credits drop behavior. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 1/1 changed files
- Comments generated: 3
| afterAll(async () => { | ||
| await closeLogStream(); | ||
| }); | ||
|
|
||
| describe('Copilot SDK model extraction gap', () => { |
| * Copilot CLI is the intermediary. The usage chunk includes token counts | ||
| * but the `model` field is absent from the SSE data. | ||
| */ | ||
| test('streaming response without model field results in model=null (BUG: AI credits lost)', (done) => { |
| * Non-streaming variant: Copilot API returns JSON without model field. | ||
| * Same bug manifests for non-streaming responses. | ||
| */ | ||
| test('non-streaming response without model field results in model=null (BUG)', (done) => { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Tests now assert what SHOULD happen (model != 'unknown', credits != null) rather than documenting the broken behavior. This ensures CI fails until the fix is implemented. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
When the upstream response (e.g., Copilot SDK streaming via port 10002) omits the model field in SSE chunks, the token tracker now falls back to: 1. requestModel — extracted from the JSON request body 2. provider — as a last resort (still not 'unknown') This ensures AI credits are always computed rather than silently dropped. Changes: - token-tracker-http.js: accept requestModel opt; use fallback chain model || requestModel || provider || 'unknown' - upstream-response.js: extract model from request body and pass it as requestModel to trackTokenUsage - ai-credits-guard.js: add BUILTIN_FALLBACK_PRICING for the 'unknown' sentinel so credits are tracked at conservative rates even when both response and request omit the model name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 Smoke Test: Copilot BYOK (Direct Mode)Status: ✅ PASS Test Results:
Mode: Direct BYOK (COPILOT_PROVIDER_API_KEY) via api-proxy sidecar → api.githubcopilot.com PR: #4797 by @lpcox All core paths verified. BYOK flow working correctly.
|
🤖 Copilot Smoke Test Results
Overall: FAIL — pre-step template variables ( PR: test: regression tests for Copilot SDK model-missing AI credits bug — author @lpcox, no assignees.
|
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra Overall: PASS
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
📡 OTel Tracing Smoke Test Results
All scenarios pass or are expected-pending. Scenario 3 has a test-script discrepancy: OTEL env vars are forwarded in
|
Chroot Version Comparison Results
Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.
|
Smoke Test: GitHub Actions Services Connectivity
Overall: FAIL —
|
|
fix: skip symlink assertion for pre-existing path segments ✅
|
|
Smoke tests for "test: regression tests for Copilot SDK model-missing AI credits bug":
Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw)
|
|
GitHub API: ✅ PASS Total: PASS
|
🔥 Smoke Test: Copilot PAT Auth — PASS
Overall: PASS — Auth mode: PAT (COPILOT_GITHUB_TOKEN) PR: test: regression tests for Copilot SDK model-missing AI credits bug by @lpcox
|
Problem
When Copilot CLI runs inside the AWF agent container (copilot-sdk mode) and routes through api-proxy port 10002, the upstream Copilot API streaming response may not include the
modelfield in SSE data chunks.The token tracker (
extractUsageFromSseLine) extracts model fromjson.model, getsnull, and falls back to'unknown'. Since'unknown'has no AI credits pricing entry,calculateAiCredits()returnsnulland AI credits are silently dropped.This results in
GH_AW_AICbeing empty at the end of the run.Observed in production: gh-aw run #27371175049 (pr-triage-agent) —
GH_AW_AIC:empty, onlyGH_AW_THREAT_DETECTION_AIC: 38.842tracked.What these tests do
4 tests document the current broken behavior (all pass today):
onUsagecalled withmodel='unknown''unknown'fallbackapplyAiCreditsUsage()returnsnull(credits lost)Suggested fix direction (not implemented here)
The request body always contains the model (e.g.,
{"model": "claude-sonnet-4-20250514", ...}). The token tracker should extract the model from the request body as a fallback when the response doesn't include it. This requires passing the parsed request model intotrackTokenUsage().Files
containers/api-proxy/token-tracker.copilot-sdk-model.test.js(new)