Masking ran its rules over every string of the request except a fixed list
of keys, including two kinds of text the vendor itself wrote and checks
when it comes back:
- A thinking block (Anthropic's signature; also Gemini's thoughtSignature
and Responses' encrypted reasoning). The vendor thinks about the
placeholders it was given, the agent gets the values back (Restore), and
on the next turn the rules masked that text again. Rules depend on
what is around a value: "the password is {{SECRET_…}}" came back as
"the password is hunter2abc1", which no rule matches without its
DB_PASSWORD=, an email ending a sentence fails the email rule's bound,
and a number the vendor wrote of its own ("13800138000") turned into a
{{PHONE_…}}. The text no longer matched its signature, and Anthropic
answers 400 "messages.1.content.0: Invalid `signature` in `thinking`
block" — and the value itself went to the vendor in the clear.
Now an object that carries a non-empty signature, thoughtSignature,
thought_signature or encrypted_content is masked only by putting back
the placeholders of the values masked elsewhere in the same request
(longest first): what the vendor wrote, its own words untouched.
- A chat tool result's tool_call_id. "id", "tool_use_id" and "call_id"
were kept, "tool_call_id" wasn't. GLM's ids are call_ and 19 digits,
and ~5% of them pass Luhn as a bank card number, so the tool message's
id became call_-{{BANK_CARD_…}} while the assistant's tool_calls id
stayed, and the upstream refused the orphaned tool result ("messages
with role 'tool' must be a response to a preceeding message with
'tool_calls'"). Now every key named id, …_id, …Id (and their plurals)
is left alone.
Account masking (账号打码) is display only and doesn't touch requests.
Tests: TestSignedThinkingGoesBackAsSigned (a fake Anthropic upstream that
signs its thinking with an HMAC and checks it, two turns through the
gateway) and TestToolCallIDsGoBackAsGiven (a fake chat upstream that
refuses a tool result with no matching call) fail on origin/main with
exactly those 400s; TestMaskJSONSignedGoesBackAsWritten (Anthropic,
Gemini thought and functionCall, Responses reasoning shapes, plus an
unsigned thinking block still masked) and TestMaskJSONKeepsIDs fail there
too. All pass with the change.
Run: gofmt -l (nothing), go vet -tags nogui ./... on darwin, GOOS=linux
and GOOS=windows, go build -tags nogui (darwin, linux, windows),
go test -tags nogui ./... under a temp HOME: all ok. Not tried against the
real OpenCode Zen, and the reporter's exact error text and model weren't
seen; both upstream checks are modeled on Anthropic's and OpenAI's
documented errors.