Add an option to disable Stainless telemetry headers - #962
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a066d1389c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| headers.put("X-Stainless-Runtime", "JRE") | ||
| headers.put("X-Stainless-Runtime-Version", getJavaVersion()) | ||
| headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString()) | ||
| if (sendStainlessHeaders) { |
There was a problem hiding this comment.
Honor the switch for headers added during request execution
When this option is false, only the static headers stored in ClientOptions.headers are omitted. Every request still receives X-Stainless-Retry-Count in RetryingHttpClient.kt lines 36-45/80-92, and the default OkHttp transport adds X-Stainless-Read-Timeout and X-Stainless-Timeout in OkHttpClient.kt lines 255-269. Consequently, users near Azure's custom-header limit still send three SDK telemetry headers and can continue receiving HTTP 431; propagate the setting into these request-time injection paths and verify the headers captured on the wire.
Useful? React with 👍 / 👎.
| fun sendStainlessHeaders(sendStainlessHeaders: Boolean) = apply { | ||
| this.sendStainlessHeaders = sendStainlessHeaders | ||
| } |
There was a problem hiding this comment.
Expose the option through the public OkHttp builders
Azure users normally construct clients with OpenAIOkHttpClient.builder() or OpenAIOkHttpClientAsync.builder(), but neither builder delegates this new setting to its internal ClientOptions.Builder, unlike the other client options. Therefore .sendStainlessHeaders(false) is unavailable in the standard construction flow targeted by this workaround; users would have to discover the indirect post-build withOptions path instead.
Useful? React with 👍 / 👎.
| } | ||
| azureServiceVersion = clientOptions.azureServiceVersion | ||
| azureUrlPathMode = clientOptions.azureUrlPathMode | ||
| sendStainlessHeaders = clientOptions.sendStainlessHeaders |
There was a problem hiding this comment.
Remove materialized headers when disabling after cloning
When sendStainlessHeaders(false) is applied to a builder obtained from toBuilder() whose source used the default true, from() has already copied the source's materialized X-Stainless-* headers into this.headers at line 243. The subsequent build skips generating new defaults but restores those copied headers at line 739, leaving the option false while all eight static telemetry headers are still sent. This also makes the post-build withOptions workaround ineffective; remove the SDK-generated headers when disabling or keep user-supplied and generated headers separate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05378f305b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| val client = clientBuilder.build() | ||
| return client.newCall(request.toRequest(client)) | ||
| return client.newCall(request.toRequest(client, sendStainlessHeaders)) |
There was a problem hiding this comment.
Propagate opt-out through withOptions transport clones
When a client initially uses the default and is later cloned with client.withOptions(b -> b.sendStainlessHeaders(false)), ClientOptions.Builder.from() reuses the original HttpClient, whose new transport-level flag was fixed to true at construction. The clone omits static and retry headers, but this call still causes the reused OkHttp transport to add X-Stainless-Read-Timeout and X-Stainless-Timeout; with the nonzero default timeouts, the Azure workaround remains incomplete. Fresh evidence in this revision is that the transport now captures the flag immutably while withOptions continues reusing originalHttpClient; propagate the updated policy to cloned transports or make request-time injection consult the current options, and cover the public withOptions flow on the wire.
AGENTS.md reference: AGENTS.md:L41-L45
Useful? React with 👍 / 👎.
| .maxIdleConnections(maxIdleConnections) | ||
| .keepAliveDuration(keepAliveDuration) | ||
| .dispatcherExecutorService(dispatcherExecutorService) | ||
| .sendStainlessHeaders(sendStainlessHeaders) |
There was a problem hiding this comment.
Apply the opt-out to X.509 transports
When sendStainlessHeaders(false) is combined with x509WorkloadIdentity(...), build() selects the x509ClientOptions branch and never reaches this only propagation into the OkHttp builder. X509Transport.bind() constructs both underlying OkHttp clients with the default true, so their requests still receive the timeout telemetry headers. Fresh evidence beyond the earlier general transport concern is this alternate X.509 construction path; pass the setting into the bound transport and add a wire-level X.509 regression test.
AGENTS.md reference: AGENTS.md:L41-L45
Useful? React with 👍 / 👎.
| if (!sendStainlessHeaders) { | ||
| headers.removeAll(STAINLESS_HEADER_NAMES) | ||
| } |
There was a problem hiding this comment.
Preserve explicit Stainless headers across no-op clones
If options are built with .sendStainlessHeaders(false).putHeader("X-Stainless-Lang", "custom"), the later explicit header is retained by build(), but a no-op toBuilder().build() copies it and then removes it here solely because the saved flag is false. Consequently, withOptions for an unrelated setting can silently discard caller-provided headers. Fresh evidence in this revision is the new blanket cleanup added to address materialized defaults; distinguish generated defaults from user headers rather than deleting every matching name during cloning, and cover cloning after an explicit post-opt-out header.
AGENTS.md reference: AGENTS.md:L41-L45
Useful? React with 👍 / 👎.
What
Add a
ClientOptions.Builder.sendStainlessHeaders(boolean)option.The option defaults to
trueto preserve the existing behavior. When disabled,the SDK does not add its default
X-Stainless-*telemetry headers.Why
Azure OpenAI can reject requests with HTTP 431 when the request contains too
many custom headers. This gives users a supported workaround without changing
the default behavior.
Fixes #715
Tests
git diff --check./gradlew :openai-java-core:test --tests com.openai.core.ClientOptionsTest