fix(sdk/go/ai): two follow-ups to the Infron gateway integration (#874) - #884
Merged
Conversation
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>
Contributor
Performance
✓ No regressions detected |
Contributor
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
Contributor
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
normalizeNativeCostsynthesized an emptyUsage{}whenever a body carried a top-levelcostwithout ausageblock. Stream consumers accumulate usage last-non-nil-wins (agent.go'sAIStream), so a cost-bearing chunk arriving after the usage chunk replaced genuine counts with zeros — recorded downstream asinput=0 / output=0withcost_source="provider", an authoritative-looking row that had lost its tokens. Reproduced against the realSSEDecoderbefore fixing:usage-then-costchunks yieldedin=0 out=0 cost=0.00042on the old code,in=10 out=5plus 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 fromusagethere'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 consultedAGENTFIELD_OPENROUTER_ATTRIBUTION. A deployment that had set that tofalse— 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 literalsdk-go.ymlstepsgo test -count=1 ./...acrosssdk/go— all packages okgo test -race ./ai/— clean./scripts/coverage-surface.sh sdk-go— the literal coverage.yml step, passes locallyinclusionai/ling-3.0-flash:free):DefaultConfigpickup,infron/prefix stripped on the wire, sync + streaming both return real token counts withUsage.Costfolded 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.coststill wins, opted-out values are not inherited while enabled-path inheritance still works.🤖 Generated with Claude Code