Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions packages/actions/src/prepare-request-body.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,62 @@ describe("prepareRequestBody - Google AI Studio", () => {
});
});

describe("prepareRequestBody - Alibaba", () => {
test("should enable thinking for Alibaba DeepSeek v3.2 when reasoning is requested", async () => {
const requestBody = (await prepareRequestBody(
"alibaba",
"deepseek-v3.2",
[{ role: "user", content: "Solve this carefully" }],
false,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"medium",
true,
false,
)) as unknown as {
enable_thinking?: boolean;
thinking_budget?: number;
reasoning_effort?: string;
};

expect(requestBody.enable_thinking).toBe(true);
expect(requestBody.thinking_budget).toBe(8192);
expect(requestBody).not.toHaveProperty("reasoning_effort");
});
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

Test coverage only exercises the reasoning_effortthinking_budget mapping for the default ("medium") case. Since the implementation also supports reasoning_max_tokens (including clamping to 32768) and multiple effort levels, add unit tests covering the reasoning_max_tokens path and at least one non-default effort (and/or clamping) to prevent regressions.

Suggested change
});
});
test("should map non-default reasoning_effort to a different thinking_budget for Alibaba DeepSeek v3.2", async () => {
const requestBody = (await prepareRequestBody(
"alibaba",
"deepseek-v3.2",
[{ role: "user", content: "Solve this with extra care" }],
false,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"high",
true,
false,
)) as unknown as {
enable_thinking?: boolean;
thinking_budget?: number;
reasoning_effort?: string;
};
// Non-default effort should still enable thinking,
// and should result in a thinking_budget that differs from the medium default.
expect(requestBody.enable_thinking).toBe(true);
expect(requestBody.thinking_budget).toBeDefined();
expect(requestBody.thinking_budget).not.toBe(8192);
});
test("should derive thinking_budget from reasoning_max_tokens for Alibaba DeepSeek v3.2", async () => {
// First, use a small reasoning_max_tokens value to ensure it is used directly.
const smallReasoningBody = (await prepareRequestBody(
"alibaba",
"deepseek-v3.2",
[{ role: "user", content: "Use a small reasoning budget" }],
false,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
2048, // reasoning_max_tokens (small, should be used as-is)
undefined,
true,
false,
)) as unknown as {
enable_thinking?: boolean;
thinking_budget?: number;
reasoning_max_tokens?: number;
};
expect(smallReasoningBody.enable_thinking).toBe(true);
expect(smallReasoningBody.thinking_budget).toBe(2048);
// Next, use a very large reasoning_max_tokens value to ensure clamping at 32768.
const largeReasoningBody = (await prepareRequestBody(
"alibaba",
"deepseek-v3.2",
[{ role: "user", content: "Use a very large reasoning budget" }],
false,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
50000, // reasoning_max_tokens (large, should be clamped to 32768)
undefined,
true,
false,
)) as unknown as {
enable_thinking?: boolean;
thinking_budget?: number;
reasoning_max_tokens?: number;
};
expect(largeReasoningBody.enable_thinking).toBe(true);
expect(largeReasoningBody.thinking_budget).toBe(32768);
});

Copilot uses AI. Check for mistakes.

test("should preserve reasoning_effort for non-DeepSeek Alibaba models", async () => {
const requestBody = (await prepareRequestBody(
"alibaba",
"qwen3-vl-235b-a22b-thinking",
[{ role: "user", content: "Solve this carefully" }],
false,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"high",
true,
false,
)) as unknown as {
enable_thinking?: boolean;
reasoning_effort?: string;
};

expect(requestBody.reasoning_effort).toBe("high");
expect(requestBody).not.toHaveProperty("enable_thinking");
});
});

describe("prepareRequestBody - AWS Bedrock", () => {
test("should sanitize complex tool schemas for Bedrock Converse", async () => {
const requestBody = (await prepareRequestBody(
Expand Down
59 changes: 59 additions & 0 deletions packages/actions/src/prepare-request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1878,6 +1878,65 @@ export async function prepareRequestBody(
}
break;
}
case "alibaba": {
if (stream) {
requestBody.stream_options = {
include_usage: true,
};
}
if (response_format) {
requestBody.response_format = response_format;
}

if (temperature !== undefined) {
requestBody.temperature = temperature;
}
if (max_tokens !== undefined) {
requestBody.max_tokens = max_tokens;
}
if (top_p !== undefined) {
requestBody.top_p = top_p;
}
if (frequency_penalty !== undefined) {
requestBody.frequency_penalty = frequency_penalty;
}
if (presence_penalty !== undefined) {
requestBody.presence_penalty = presence_penalty;
}

if (
usedModel === "deepseek-v3.2" &&
supportsReasoning &&
(reasoning_effort !== undefined || reasoning_max_tokens !== undefined)
) {
Comment on lines +1907 to +1911
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 effort forwarding for non-DeepSeek Alibaba models

The new case "alibaba" branch gates reasoning handling on usedModel === "deepseek-v3.2" and does not include a fallback to forward reasoning_effort for other Alibaba models. Before this commit, Alibaba requests used the default branch, which forwarded reasoning_effort when provided; after this change, reasoning-capable Alibaba mappings (including multiple providerId: "alibaba" entries marked reasoning: true in the model registry) will silently ignore user-specified effort, changing existing API behavior and removing user control over reasoning level.

Useful? React with 👍 / 👎.

requestBody.enable_thinking = true;

if (reasoning_max_tokens !== undefined) {
requestBody.thinking_budget = Math.min(reasoning_max_tokens, 32768);
} else if (reasoning_effort !== undefined) {
Comment on lines +1914 to +1916
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize reasoning_max_tokens before assigning thinking_budget.

At Line 1915, negative/non-finite values can propagate directly into thinking_budget, producing malformed payloads.

Proposed fix
-				if (reasoning_max_tokens !== undefined) {
-					requestBody.thinking_budget = Math.min(reasoning_max_tokens, 32768);
+				if (reasoning_max_tokens !== undefined) {
+					const normalizedBudget = Number.isFinite(reasoning_max_tokens)
+						? Math.max(1, Math.min(Math.floor(reasoning_max_tokens), 32768))
+						: 8192;
+					requestBody.thinking_budget = normalizedBudget;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/actions/src/prepare-request-body.ts` around lines 1914 - 1916, The
code assigns requestBody.thinking_budget directly from reasoning_max_tokens
allowing negative or non-finite values; normalize reasoning_max_tokens first
(e.g., if Number.isFinite(reasoning_max_tokens) then let n = Math.max(0,
Math.floor(reasoning_max_tokens)) and set requestBody.thinking_budget =
Math.min(n, 32768)); keep the existing else/else-if behavior for
reasoning_effort unchanged and ensure you only set thinking_budget when
reasoning_max_tokens is a valid finite number.

const getThinkingBudget = (effort: string) => {
switch (effort) {
case "minimal":
return 512;
case "low":
return 2048;
case "high":
return 24576;
case "xhigh":
return 32768;
case "medium":
default:
return 8192;
}
};

requestBody.thinking_budget = getThinkingBudget(reasoning_effort);
}
Comment on lines +1910 to +1934
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The new Alibaba handling uses reasoning_max_tokens to set thinking_budget, but (unless the model mapping is updated) reasoning.max_tokens will currently fail validateModelCapabilities() because it requires provider.reasoningMaxTokens === true. Either ensure the model registry marks Alibaba DeepSeek v3.2 as reasoningMaxTokens: true (and align validation/docs accordingly) or drop the reasoning_max_tokens branch here to avoid dead/unreachable behavior from the public API.

Suggested change
(reasoning_effort !== undefined || reasoning_max_tokens !== undefined)
) {
requestBody.enable_thinking = true;
if (reasoning_max_tokens !== undefined) {
requestBody.thinking_budget = Math.min(reasoning_max_tokens, 32768);
} else if (reasoning_effort !== undefined) {
const getThinkingBudget = (effort: string) => {
switch (effort) {
case "minimal":
return 512;
case "low":
return 2048;
case "high":
return 24576;
case "xhigh":
return 32768;
case "medium":
default:
return 8192;
}
};
requestBody.thinking_budget = getThinkingBudget(reasoning_effort);
}
reasoning_effort !== undefined
) {
requestBody.enable_thinking = true;
const getThinkingBudget = (effort: string) => {
switch (effort) {
case "minimal":
return 512;
case "low":
return 2048;
case "high":
return 24576;
case "xhigh":
return 32768;
case "medium":
default:
return 8192;
}
};
requestBody.thinking_budget = getThinkingBudget(reasoning_effort);

Copilot uses AI. Check for mistakes.
} else if (reasoning_effort !== undefined) {
requestBody.reasoning_effort = reasoning_effort;
}
break;
}
Comment on lines +1881 to +1939
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Alibaba model mappings with reasoning flags =="
rg -nP --type=ts -C3 'providerId:\s*"alibaba"|modelName:\s*"|reasoning:\s*true|supportedParameters' packages/models/src/models

echo
echo "== Alibaba request-body handling =="
rg -nP --type=ts -C4 'case\s+"alibaba"|reasoning_effort|reasoning_max_tokens|enable_thinking|thinking_budget' packages/actions/src/prepare-request-body.ts

Repository: theopenco/llmgateway

Length of output: 50377


🏁 Script executed:

# Search for deepseek-v3.2 model in the codebase to verify it exists
rg -n "deepseek-v3\.2" packages/models/src/models

# Get full Alibaba case from prepare-request-body.ts
sed -n '1881,1937p' packages/actions/src/prepare-request-body.ts

# Find all Alibaba models to see the complete list
rg -n 'providerId:\s*"alibaba"' packages/models/src/models --type=ts -A 5 | grep -E 'modelName|providerId|reasoning' | head -50

Repository: theopenco/llmgateway

Length of output: 5286


🏁 Script executed:

# Check if deepseek-v3.2 has reasoning flag set
sed -n '119,185p' packages/models/src/models/deepseek.ts

# Check kimi-k2-thinking Alibaba model for reasoning flag
sed -n '164,180p' packages/models/src/models/moonshot.ts

# Check kimi-k2.5 Alibaba model for reasoning flag
sed -n '266,281p' packages/models/src/models/moonshot.ts

# Check glm-5 Alibaba model for reasoning flag
sed -n '76,105p' packages/models/src/models/zai.ts

# Check glm-4.7 Alibaba model for reasoning flag
sed -n '487,516p' packages/models/src/models/zai.ts

Repository: theopenco/llmgateway

Length of output: 3619


🏁 Script executed:

# Find where supportsReasoning is defined
rg -n "supportsReasoning" packages/actions/src/prepare-request-body.ts -B 5 | head -50

# Search for test cases related to Alibaba reasoning
fd -e test.ts -e spec.ts packages/ | xargs grep -l "alibaba.*reason" 2>/dev/null || echo "No Alibaba reasoning tests found"

# Check how supportsReasoning is derived - look for reasoning field usage
rg -n "\.reasoning" packages/actions/src/prepare-request-body.ts -B 3 -A 1

Repository: theopenco/llmgateway

Length of output: 3029


🏁 Script executed:

# Get context around line 804-807 to see full logic for supportsReasoning
sed -n '800,815p' packages/actions/src/prepare-request-body.ts

# Check if deepseek-v3.2 alibaba provider has any reasoning field definition
rg -A 8 'providerId.*alibaba.*deepseek-v3' packages/models/src/models/deepseek.ts

# Check what happens after the Alibaba case - is there any fallback for other models?
sed -n '1937,1945p' packages/actions/src/prepare-request-body.ts

Repository: theopenco/llmgateway

Length of output: 741


🏁 Script executed:

# Check the full default case for any reasoning handling
sed -n '1937,2000p' packages/actions/src/prepare-request-body.ts

Repository: theopenco/llmgateway

Length of output: 1618


🏁 Script executed:

# Find where enable_thinking is initially set for Alibaba models
rg -n "enable_thinking" packages/actions/src/prepare-request-body.ts | head -20

# Check if there's any reasoning handling before the Alibaba case that might catch it
sed -n '1800,1881p' packages/actions/src/prepare-request-body.ts | grep -A 10 -B 10 "enable_thinking\|thinking_budget\|reasoning"

Repository: theopenco/llmgateway

Length of output: 733


Fix Alibaba reasoning support for all capable models.

The current implementation only attempts reasoning handling for deepseek-v3.2, but that model lacks the reasoning: true flag, so the condition will never activate. Meanwhile, other Alibaba models with reasoning capability (kimi-k2-thinking, kimi-k2.5, glm-5, glm-4.7) are silently blocked.

Replace the model-specific check with a general condition that applies reasoning fields to all Alibaba models with supportsReasoning === true:

if (supportsReasoning && (reasoning_effort !== undefined || reasoning_max_tokens !== undefined)) {
	requestBody.enable_thinking = true;
	
	if (reasoning_max_tokens !== undefined) {
		requestBody.thinking_budget = Math.min(reasoning_max_tokens, 32768);
	} else if (reasoning_effort !== undefined) {
		const getThinkingBudget = (effort: string) => {
			switch (effort) {
				case "minimal":
					return 512;
				case "low":
					return 2048;
				case "high":
					return 24576;
				case "xhigh":
					return 32768;
				case "medium":
				default:
					return 8192;
			}
		};
		requestBody.thinking_budget = getThinkingBudget(reasoning_effort);
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/actions/src/prepare-request-body.ts` around lines 1881 - 1937, The
Alibaba branch currently gates reasoning logic on a specific model name
("deepseek-v3.2"), which prevents reasoning flags from being set for other
capable models; remove the usedModel check and change the condition to: if
(supportsReasoning && (reasoning_effort !== undefined || reasoning_max_tokens
!== undefined)) then set requestBody.enable_thinking = true and set
requestBody.thinking_budget to Math.min(reasoning_max_tokens, 32768) when
reasoning_max_tokens is provided, otherwise map reasoning_effort to the same
budgets via the existing getThinkingBudget logic; update the code paths that
reference supportsReasoning, reasoning_effort, reasoning_max_tokens,
requestBody.enable_thinking, and requestBody.thinking_budget accordingly.

default: {
if (stream) {
requestBody.stream_options = {
Expand Down
2 changes: 1 addition & 1 deletion packages/models/src/models/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const deepseekModels = [
requestPrice: 0,
contextSize: 131072,
maxOutput: 65536,
reasoning: false,
reasoning: true,
streaming: true,
vision: false,
tools: true,
Expand Down
Loading