Skip to content

fix(sdk/go/ai): two follow-ups to the Infron gateway integration (#874) - #884

Merged
AbirAbbas merged 2 commits into
mainfrom
fix/infron-cost-attribution-followup
Aug 5, 2026
Merged

fix(sdk/go/ai): two follow-ups to the Infron gateway integration (#874)#884
AbirAbbas merged 2 commits into
mainfrom
fix/infron-cost-attribution-followup

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

Follow-up to #874, which merged with two known rough edges. Both were flagged in review; this closes them out.

1. A cost-only stream chunk erased real token counts

normalizeNativeCost synthesized an empty Usage{} whenever a body carried a top-level cost without a usage block. Stream consumers accumulate usage last-non-nil-wins (agent.go's AIStream), so a cost-bearing chunk arriving after the usage chunk replaced genuine counts with zeros — recorded downstream as input=0 / output=0 with cost_source="provider", an authoritative-looking row that had lost its tokens. Reproduced against the real SSEDecoder before fixing: usage-then-cost chunks yielded in=0 out=0 cost=0.00042 on the old code, in=10 out=5 plus cost on this branch.

The #874 review cleared the ungated call site ("early-returns on Cost == nil, no shipped provider sends top-level cost") — but Infron ships top-level cost by design as of that merge, and once cost is decoupled from usage there's no per-chunk co-occurrence guarantee. The fix folds cost only into a usage block the provider actually sent; a body with neither loses nothing that was previously recorded.

2. Attribution values were inherited past their opt-out

Infron attribution falls back to the OpenRouter-scoped site URL / app name (AGENTFIELD_OPENROUTER_SITE_URL, OR_SITE_URL, and the app-name twins) but never consulted AGENTFIELD_OPENROUTER_ATTRIBUTION. A deployment that had set that to false — typically because the values name internal hosts or products — had them sent to a different vendor on the first Infron call, with no opt-out it could have known to set in advance. Values now inherit only while OpenRouter attribution is enabled; the Infron defaults apply otherwise, and the Infron-scoped vars and kill switch behave as before.

The #874 review consciously waived this one in favor of documenting a workaround, so this commit is a deliberate re-open: the operator already said "don't send these" once, and a doc note only reaches people who read it. It's a standalone commit (don't inherit attribution values past their opt-out) — drop it before merge if the doc-only stance is preferred; commit 1 stands alone.

Test plan

  • go mod tidy (no drift) + go build ./... — the literal sdk-go.yml steps
  • go test -count=1 ./... across sdk/go — all packages ok
  • go test -race ./ai/ — clean
  • ./scripts/coverage-surface.sh sdk-go — the literal coverage.yml step, passes locally
  • Live end-to-end through the real SDK against the real gateway (inclusionai/ling-3.0-flash:free): DefaultConfig pickup, infron/ prefix stripped on the wire, sync + streaming both return real token counts with Usage.Cost folded from the top-level field ($0 for the free model — an explicit zero, not "unknown"), caller config unmutated.

New tests pin all the above: cost-only bodies fabricate nothing (sync and stream, including the last-usage-wins accumulation scenario), explicit usage.cost still wins, opted-out values are not inherited while enabled-path inheritance still works.


🤖 Generated with Claude Code

AbirAbbas and others added 2 commits August 5, 2026 13:52
normalizeNativeCost synthesized an empty Usage{} when a body carried a
top-level cost without a usage block. On the streaming path every consumer
accumulates usage last-non-nil-wins, so a cost-only chunk arriving after
the real usage chunk replaced genuine token counts with zeros — recorded
downstream as input=0/output=0 with cost_source "provider", an
authoritative-looking row that has lost its tokens. Fold the cost only
into a usage block the provider actually sent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Infron attribution fell back to the OpenRouter-scoped site URL and app
name but never consulted AGENTFIELD_OPENROUTER_ATTRIBUTION, so values a
deployment had explicitly suppressed — often internal hostnames or
product names — were sent to a different vendor on the first Infron
call. Inherit the values only while OpenRouter attribution is enabled;
the Infron defaults apply otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas requested a review from a team as a code owner August 5, 2026 17:55
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Performance

SDK Memory Δ Latency Δ Tests Status
Go 240 B -14% 0.69 µs -31%

✓ No regressions detected

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 87.10% 87.40% ↓ -0.30 pp 🟡
sdk-go 92.70% 92.00% ↑ +0.70 pp 🟢
sdk-python 93.82% 93.73% ↑ +0.09 pp 🟢
sdk-typescript 91.17% 90.42% ↑ +0.75 pp 🟢
web-ui 84.76% 84.79% ↓ -0.03 pp 🟡
aggregate 85.62% 85.75% ↓ -0.13 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 0 ➖ no changes
sdk-go 15 100.00%
sdk-python 0 ➖ no changes
sdk-typescript 0 ➖ no changes
web-ui 0 ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

@AbirAbbas
AbirAbbas merged commit a2dee0b into main Aug 5, 2026
28 checks passed
@AbirAbbas
AbirAbbas deleted the fix/infron-cost-attribution-followup branch August 5, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant