Skip to content

fix: cap OpenAI Chat/Responses max_tokens fields without forcing a default - #1294

Open
0x0079 wants to merge 1 commit into
mainfrom
claude/golang-tests-checks-qkilx7
Open

fix: cap OpenAI Chat/Responses max_tokens fields without forcing a default#1294
0x0079 wants to merge 1 commit into
mainfrom
claude/golang-tests-checks-qkilx7

Conversation

@0x0079

@0x0079 0x0079 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #1287. MaxTokensTransform gained OpenAI Chat Completions / Responses support in that PR, but nothing actually wired it into the OpenAI request paths — NewMaxTokensTransform was only ever appended by buildAnthropicPreChain. As a result, OpenAI requests using max_completion_tokens or max_output_tokens above the model's cap still went upstream uncapped, and the pre-existing ad-hoc cap in openai_chat.go only ever looked at max_tokens.

Changes

  • Added executeOpenAIChatPreChain / executeOpenAIResponsesPreChain in internal/server/protocol_transform.go and call them from runOpenAIChatAttempt / runOpenAIResponsesAttempt.
  • defaultMaxTokens is passed as 0 at both call sites (not s.config.GetDefaultMaxTokens(), which is documented as Anthropic-only and always non-zero) — this means omitted fields stay omitted; only present values above maxAllowed get capped. This avoids silently forcing a max_tokens/max_output_tokens value onto OpenAI-style requests that never set one, which would have been a real behavior change for clients relying on the upstream provider's own default.

Test plan

  • go build ./...
  • go vet ./internal/server/...
  • go test ./internal/server/...

https://claude.ai/code/session_01MqN2PrHaQfhFyLNR2tf5Z2


Generated by Claude Code

The Anthropic-only max_tokens pre-chain (buildAnthropicPreChain) was the
only caller of MaxTokensTransform; OpenAI Chat and Responses never used
it, so requests with max_completion_tokens/max_output_tokens above the
model's cap went upstream uncapped, and the existing ad-hoc cap in
openai_chat.go only ever looked at max_tokens.

Add executeOpenAIChatPreChain/executeOpenAIResponsesPreChain and call
them from runOpenAIChatAttempt/runOpenAIResponsesAttempt. defaultMaxTokens
is passed as 0 at both call sites (not GetDefaultMaxTokens(), which is
documented as Anthropic-only and always non-zero) so omitted fields stay
omitted — only present values above maxAllowed get capped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqN2PrHaQfhFyLNR2tf5Z2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46d6197ddf

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// setting (max_tokens is mandatory on that API); OpenAI callers that omit
// max_tokens get no field injected, same as before this cap existed, and the
// upstream provider applies its own default.
if err := executeOpenAIChatPreChain(req.ChatCompletionNewParams, 0, maxAllowed); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the max_tokens cap when both fields are present

When Chat Completions clients send both max_tokens and max_completion_tokens, this replacement no longer caps max_tokens: MaxTokensTransform.applyOpenAIChat returns as soon as MaxCompletionTokens.Valid() is true, whereas the removed guard capped req.MaxTokens independently. In that compatibility scenario an oversized max_tokens is still forwarded to OpenAI-style providers and can be rejected even if max_completion_tokens was capped; please cap any present max_tokens as well or retain the old guard after this call.

Useful? React with 👍 / 👎.

// applies its own default (the WithMaxTokens(maxAllowed) option further
// below is unrelated — it only seeds a fallback for cross-protocol
// conversions that require a value, e.g. when the target is Anthropic).
if err := executeOpenAIResponsesPreChain(req.ResponseNewParams, 0, maxAllowed); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clone Responses attempts from the uncapped template

This cap mutates req.ResponseNewParams in place, but in the multi-service path above areq := req means assigning the per-attempt clone back onto areq.ResponseNewParams also replaces the shared template. If the first Responses attempt caps max_output_tokens for a lower-limit model and then fails over to a higher-limit model, the retry clones the already-capped params and silently keeps the lower cap, producing shorter outputs than requested. Keep an immutable template or allocate a fresh ResponseCreateRequest wrapper per attempt before running this mutating pre-chain.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants