Is there an existing issue for this?
Current Behavior
A streaming interception on the OpenAI Responses API route (/openai/v1/responses) records zero token usage when the RequestBridge is constructed with a nil mcp.ServerProxier. The client still receives the complete streamed response and a 200, so the request looks entirely healthy; only the usage record is missing.
recordTokenUsage is called from inside the MCP branch in aibridge/intercept/responses/streaming.go:244-254:
if i.mcpProxy != nil && completedResponse != nil {
pending := i.getPendingInjectedToolCalls(completedResponse)
shouldLoop, innerLoopErr = i.handleInnerAgenticLoop(ctx, pending, completedResponse)
if innerLoopErr != nil {
i.sendCustomErr(ctx, w, http.StatusInternalServerError, innerLoopErr)
shouldLoop = false
}
// Record token usage for each inner loop iteration
i.recordTokenUsage(ctx, completedResponse)
}
i.recordModelThoughts(ctx, completedResponse)
Upstream reports usage on the response.completed event independently of tool injection, so the MCP proxier is not a valid precondition for recording it.
Why a nil proxier is reachable. coderd/aibridged/pool.go:255-265 treats proxier construction failure as non-fatal and continues with a nil value:
mcpServers, err = mcpProxyFactory.Build(ctx, req, p.tracer)
if err != nil {
p.logger.Warn(ctx, "failed to create MCP server proxiers", slog.Error(err))
// Don't fail here; MCP server injection can gracefully degrade.
}
MCPProxyFactory.Build returns nil, err when retrieveMCPServerConfigs fails (coderd/aibridged/mcp.go:53-60). The resulting bridge is then cached via p.cache.SetWithTTL, so a single transient config-retrieval failure suppresses token recording for every streaming Responses request served by that cached bridge until its TTL expires. aibridge/bridge.go:455 (if b.mcpProxy != nil) confirms nil is an expected state rather than a programming error.
The other implementations do not have this gate.
| Implementation |
Records token usage |
responses/blocking.go:128 |
Unconditionally |
chatcompletions/streaming.go:274-277 |
Unconditionally (gated only on CompletionTokens > 0) |
chatcompletions/blocking.go:126 |
Unconditionally |
messages/streaming.go:269,294 |
Unconditionally, inside the SSE event switch |
responses/streaming.go:253 |
Only when mcpProxy != nil |
In messages/streaming.go the mcpProxy checks govern whether events are relayed downstream and occur after recording, so usage is unaffected there.
Why the existing suite does not catch it. aibridge/internal/integrationtest/setupbridge.go:153-155 substitutes a non-nil noop manager whenever no proxier is supplied:
if cfg.mcpProxy == nil {
cfg.mcpProxy = newNoopMCPManager()
}
newNoopMCPManager returns a real ServerProxyManager with no proxiers (mockmcp.go:84-86), so i.mcpProxy != nil is always true in the integration suite and the nil path is never exercised.
Relevant Log Output
# Identical request and fixture (fixtures.OaiResponsesStreamingSimple) against
# /openai/v1/responses, varying only the MCP proxier:
nilMCP=true -> token usage records=0 input=0 output=0
nilMCP=false -> token usage records=1 input=11 output=18
# Both returned 200 with a non-empty streamed body.
Expected Behavior
Token usage is recorded for a completed streaming Responses interception regardless of whether an MCP server proxier is configured, matching responses/blocking.go and both chatcompletions implementations.
Steps to Reproduce
- Construct an
aibridge.RequestBridge with a nil mcp.ServerProxier (the state coderd/aibridged/pool.go reaches when mcpProxyFactory.Build returns an error).
- Issue a streaming request to
/openai/v1/responses that completes normally and emits a response.completed event carrying usage.
- Inspect the recorder: no
TokenUsageRecord is emitted for the interception.
- Repeat with a non-nil proxier (for example
NoopMCPManager, which reports zero tools). One record is emitted, with non-zero input and output tokens.
Environment
- Host OS: macOS 26.5 (darwin/arm64)
- Coder version:
main at commit c62079c05, Go 1.26.5
Additional Context
The issue occurs consistently, I have tested this on the latest version
Impact
Token usage underpins usage recording and downstream billing and quota accounting. In the degraded state, streaming Responses traffic is served normally but metered as zero, silently, with no error surfaced to the operator.
Suggested Fix
Move the recordTokenUsage call out of the MCP branch, guarded only on completedResponse != nil, matching responses/blocking.go. Per-iteration semantics are preserved for the inner agentic loop. I have this working locally with regression coverage and will open a PR.
Is there an existing issue for this?
Current Behavior
A streaming interception on the OpenAI Responses API route (
/openai/v1/responses) records zero token usage when theRequestBridgeis constructed with a nilmcp.ServerProxier. The client still receives the complete streamed response and a200, so the request looks entirely healthy; only the usage record is missing.recordTokenUsageis called from inside the MCP branch inaibridge/intercept/responses/streaming.go:244-254:Upstream reports usage on the
response.completedevent independently of tool injection, so the MCP proxier is not a valid precondition for recording it.Why a nil proxier is reachable.
coderd/aibridged/pool.go:255-265treats proxier construction failure as non-fatal and continues with a nil value:MCPProxyFactory.Buildreturnsnil, errwhenretrieveMCPServerConfigsfails (coderd/aibridged/mcp.go:53-60). The resulting bridge is then cached viap.cache.SetWithTTL, so a single transient config-retrieval failure suppresses token recording for every streaming Responses request served by that cached bridge until its TTL expires.aibridge/bridge.go:455(if b.mcpProxy != nil) confirms nil is an expected state rather than a programming error.The other implementations do not have this gate.
responses/blocking.go:128chatcompletions/streaming.go:274-277CompletionTokens > 0)chatcompletions/blocking.go:126messages/streaming.go:269,294responses/streaming.go:253mcpProxy != nilIn
messages/streaming.gothemcpProxychecks govern whether events are relayed downstream and occur after recording, so usage is unaffected there.Why the existing suite does not catch it.
aibridge/internal/integrationtest/setupbridge.go:153-155substitutes a non-nil noop manager whenever no proxier is supplied:newNoopMCPManagerreturns a realServerProxyManagerwith no proxiers (mockmcp.go:84-86), soi.mcpProxy != nilis always true in the integration suite and the nil path is never exercised.Relevant Log Output
Expected Behavior
Token usage is recorded for a completed streaming Responses interception regardless of whether an MCP server proxier is configured, matching
responses/blocking.goand bothchatcompletionsimplementations.Steps to Reproduce
aibridge.RequestBridgewith a nilmcp.ServerProxier(the statecoderd/aibridged/pool.goreaches whenmcpProxyFactory.Buildreturns an error)./openai/v1/responsesthat completes normally and emits aresponse.completedevent carryingusage.TokenUsageRecordis emitted for the interception.NoopMCPManager, which reports zero tools). One record is emitted, with non-zero input and output tokens.Environment
mainat commitc62079c05, Go 1.26.5Additional Context
The issue occurs consistently, I have tested this on the latest version
Impact
Token usage underpins usage recording and downstream billing and quota accounting. In the degraded state, streaming Responses traffic is served normally but metered as zero, silently, with no error surfaced to the operator.
Suggested Fix
Move the
recordTokenUsagecall out of the MCP branch, guarded only oncompletedResponse != nil, matchingresponses/blocking.go. Per-iteration semantics are preserved for the inner agentic loop. I have this working locally with regression coverage and will open a PR.