Skip to content

fix(azure): send max_completion_tokens for gpt-5/o-series reasoning deployments#2691

Open
chy1211 wants to merge 2 commits into
decolua:masterfrom
chy1211:fix/azure-max-completion-tokens
Open

fix(azure): send max_completion_tokens for gpt-5/o-series reasoning deployments#2691
chy1211 wants to merge 2 commits into
decolua:masterfrom
chy1211:fix/azure-max-completion-tokens

Conversation

@chy1211

@chy1211 chy1211 commented Jul 18, 2026

Copy link
Copy Markdown

Summary

  • AzureExecutor.transformRequest was a pure passthrough (return body), so it never translated max_tokens for reasoning-model deployments.
  • Add requiresMaxCompletionTokens(model) + rename max_tokens -> max_completion_tokens for gpt-5* / o1- / o3- / o4- deployments, mirroring the pattern already used by the github executor for Copilot-hosted reasoning models.
  • Non-reasoning deployments (e.g. gpt-4o) are untouched — transformRequest still returns the original body reference when no rename is needed.

Why

Azure AI Foundry reasoning-model deployments reject the legacy max_tokens parameter:

{"error":{"message":"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.","type":"invalid_request_error","param":"max_tokens","code":"unsupported_parameter"}}

Any client that sends max_tokens (the common/default param name in most OpenAI-compatible SDKs) gets a 400 on every request to a gpt-5*/o-series Azure deployment, with no way to work around it from the client side through 9Router. The github executor already special-cases this for Copilot; azure was missing the same handling.

Reproduced end-to-end against a live Azure AI Foundry gpt-5.6-luna (GlobalStandard) deployment:

  • POST .../openai/deployments/gpt-5.6-luna/chat/completions?api-version=2025-04-01-preview with max_tokens in the body -> 400 unsupported_parameter
  • Same request with max_tokens renamed to max_completion_tokens -> 200 OK

Test Plan

NODE_PATH=./node_modules ./tests/node_modules/.bin/vitest run tests/unit/azure-executor.test.js --reporter=verbose

Result: 1 file passed, 7 tests passed (new file, covers registry wiring, the gpt-5/o[134]- regex, and transformRequest rename/no-op/passthrough behavior including non-mutation of the original body object).

Also ran the full tests/unit suite before and after this change to confirm no regressions:

  • Before (upstream/master, stashed): 794 passed / 84 failed / 76 skipped
  • After (this branch): 798 passed / 80 failed / 76 skipped

The 4-passed / 4-failed delta is exactly this PR's 7 new tests (net vs. pre-existing flake in that run); all pre-existing failures are unrelated open-sse workspace-alias resolution errors in tests/unit/xai-oauth-service.test.js and friends, present identically on unmodified upstream/master in this environment (Node 20 portable build without the maintainer's workspace linking) — not caused by this change.

…eployments

Azure OpenAI reasoning-model deployments (gpt-5*, o1-/o3-/o4- series) reject
the legacy max_tokens param with a 400 Unsupported parameter error and
require max_completion_tokens instead. AzureExecutor.transformRequest was a
pure passthrough with no param translation, so every request carrying
max_tokens 400d against these deployments.

Mirrors the requiresMaxCompletionTokens regex + rename-and-delete pattern
already used by the github executor for Copilot-hosted reasoning models.

Reproduced against a live Azure AI Foundry gpt-5.6-luna deployment:
- POST .../chat/completions with max_tokens -> 400 unsupported_parameter
- same request with max_completion_tokens -> 200 OK
Copilot AI review requested due to automatic review settings July 18, 2026 08:51

Copilot AI 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.

Pull request overview

This PR fixes Azure AI Foundry reasoning-model compatibility by translating the OpenAI-style max_tokens request field into Azure-required max_completion_tokens for reasoning deployments, aligning Azure behavior with the existing Copilot/GitHub executor handling.

Changes:

  • Add AzureExecutor.requiresMaxCompletionTokens(model) to detect reasoning deployments (gpt-5*, o1-, o3-, o4-).
  • Update AzureExecutor.transformRequest to rename max_tokensmax_completion_tokens only when needed (and otherwise preserve passthrough behavior).
  • Add a new Vitest unit test file covering Azure executor registry wiring and request transformation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
open-sse/executors/azure.js Adds reasoning-model detection and renames max_tokens to max_completion_tokens for Azure reasoning deployments.
tests/unit/azure-executor.test.js New unit tests validating Azure executor registration and transformRequest rename/no-op behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +70
const transformed = { ...body };
transformed.max_completion_tokens = transformed.max_tokens;
delete transformed.max_tokens;
return transformed;
Comment on lines +37 to +63
it("renames max_tokens to max_completion_tokens for reasoning deployments", () => {
const body = { messages: [{ role: "user", content: "hi" }], max_tokens: 50 };
const out = executor.transformRequest("gpt-5.6-luna", body, true, {});

expect(out.max_tokens).toBeUndefined();
expect(out.max_completion_tokens).toBe(50);
// original body must not be mutated
expect(body.max_tokens).toBe(50);
});

it("leaves max_tokens untouched for non-reasoning deployments", () => {
const body = { messages: [{ role: "user", content: "hi" }], max_tokens: 50 };
const out = executor.transformRequest("gpt-4o", body, true, {});

expect(out).toBe(body);
expect(out.max_tokens).toBe(50);
expect(out.max_completion_tokens).toBeUndefined();
});

it("is a no-op when max_tokens is absent", () => {
const body = { messages: [{ role: "user", content: "hi" }] };
const out = executor.transformRequest("gpt-5.6-luna", body, true, {});

expect(out).toBe(body);
expect(out.max_completion_tokens).toBeUndefined();
});
});
Per Copilot review on PR decolua#2691: transformRequest unconditionally
overwrote an explicit max_completion_tokens with max_tokens when both
were present in the request body. Only fall back to max_tokens when
max_completion_tokens is absent; max_tokens is always deleted either
way since Azure rejects it outright on these deployments.

Adds a regression test locking in the precedence.
@chy1211

chy1211 commented Jul 18, 2026

Copy link
Copy Markdown
Author

Good catch — fixed in 6764532 : transformRequest now only falls back to max_tokens when max_completion_tokens is absent, and always strips max_tokens either way. Added a regression test covering the both-fields-present case.

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