fix(encoder): honor default struct tag so JSONOutputFormatParam.Type is never elided#339
Open
spor3006 wants to merge 1 commit into
Open
fix(encoder): honor default struct tag so JSONOutputFormatParam.Type is never elided#339spor3006 wants to merge 1 commit into
spor3006 wants to merge 1 commit into
Conversation
The generated SDK declares constant discriminator fields like:
Type constant.JSONSchema `json:"type" default:"json_schema"`
with a contract that the zero value marshals as "json_schema". The
internal apijson encoder honored this tag, but the shim json encoder —
which is the one param.MarshalObject reaches for request-body
serialization — did not. When a `constant.*` typed field was left at
its zero value the field could be elided from outgoing requests,
producing `output_config:{format:{schema:...}}` without
`"type":"json_schema"`. The Anthropic API silently hangs structured-
output requests missing this discriminator until the client times out.
Teach the shim struct encoder to read `default:"..."` for string-kinded
fields, pre-marshal the literal once at field-discovery time, and emit
it whenever the field's value is zero. Non-zero values are still
encoded by the regular field encoder, so callers that set Type
explicitly are unaffected.
Adds a unit test against the encoder directly and an end-to-end
regression test against JSONOutputFormatParam covering the three call
shapes from the issue: bare struct, explicit Type, and nested inside
MessageNewParams.
Fixes anthropics#328
Signed-off-by: Sparshal Kothari <41056517+spor3006@users.noreply.github.com>
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.
Fixes #328.
Problem
JSONOutputFormatParam(and the beta variant) declare a constant discriminator that the SDK promises will marshal as"json_schema"even at the zero value:The
default:"json_schema"tag is honored byinternal/apijson/encoder.go(used for response decoding paths), butparam.MarshalObject— the path that produces request bodies — calls through to the shimmedencoding/jsonininternal/encoding/json/, which never read the tag. When the discriminator was missing, the Anthropic API silently hung the request rather than 400-ing, and the SDK's 10-minute timeout fired withcontext deadline exceeded. The Python SDK is unaffected because it serializes the discriminator explicitly.Fix
Teach the shim struct encoder to honor
default:"..."on string-kinded fields. At field-discovery time we pre-marshal the literal once; at encode time, if the field's value is zero, we write the pre-marshaled literal in place of running the field encoder. Non-zero values bypass the default and round-trip unchanged.This is the smallest fix consistent with the existing tag contract: nothing in the generated code needs to change, the discriminator continues to be elidable at the call site, and the
default:tag now means the same thing on both the request and response sides of the SDK.Tests
TestDefaultStructTag(packages/param/encoder_test.go) — direct encoder-level coverage: zero plain-string, zero named-string, non-zero preserved, no-default-tag still emits empty. Confirmed to fail onmainand pass with the fix.TestJSONOutputFormatParamTypeAlwaysMarshaled(messageutil_test.go) — end-to-end regression: the three shapes from the issue (JSONOutputFormatParamalone with zeroType, with explicitType, and nested insideMessageNewParams).Notes
reflect.String-kinded fields; that matches the only kind generated code usesdefault:on today (constant.*named string types) and matchesparseDefaultStructTagininternal/apijson/tag.go. Widening later if other kinds need it is mechanical.EDIT(begin)/EDIT(end)markers; this change keeps that convention so the diff against upstream stays auditable.