d3581077 made Devin's "Bash:0#a65b…" ids safe in the built-in Devin only. Any other upstream whose output reaches Claude Code through /v1/messages (a plugin's provider, a Chat or Responses relay, another vendor) could give an id with another character, and Claude Code drops the call and fails the turn "could not be parsed". The mapping now sits at the Anthropic boundary: anthropicEncoder and renderAnthropic give the client anthropicOutID (an unsafe id, or one beginning "mp_", as "mp_" and its base64url), and parseAnthropic turns tool_use ids and tool_result tool_use_ids back with anthropicInID, so the upstream gets its own id on the next turn, before unsignCalls reads Gemini's signatures off it. It is stateless, the same scheme as Devin's (safeCallID/rawCallID, which devinOutID/devinInID now call with "dv_"). An id that is safe already, Devin's "dv_…" ones (built-in and plugin) among them, goes both ways byte for byte, so prompt caching and every upstream that works today see no change. buildAnthropic still gives an Anthropic upstream the ids as they came. Chat's tool_calls id and Responses' call_id are left as they are: Codex and pi take any id, and no client that refuses one is known.
TestAnthropicCallIDsSafeBothWays gives the reporter's literal id from a Chat upstream through New().Handler() on /v1/messages, streamed and whole, and answers the call the way Claude Code does: with anthropicOutID/anthropicInID returning the id as it came (the old code) it fails "stream=true: call id \"Bash:0#a65b6a5e02194b87bbc796c4428c946d\", want one of [A-Za-z0-9_-] only"; with only anthropicInID taken out it fails with the upstream getting "mp_QmFz…" for its call and result. TestAnthropicSafeCallIDsUnchanged keeps toolu_/call_ ids and Devin's dv_ ids as they came, end to end too; with the mark set to "dv_" it fails on the double-encoded Devin ids.
go test -tags nogui ./internal/gateway ./internal/provider pass on Linux (golang:1.26.8 in Docker, temp HOME, module cache read-only; this Mac's syspolicyd is pegged and new binaries hang at launch, so no darwin test run). go vet -tags nogui ./internal/gateway for darwin, GOOS=linux and GOOS=windows, and gofmt -l pass. Not tried with a real Devin account or a real Claude Code session; an Anthropic upstream relayed as-is (passthrough) keeps its ids as it gave them.