Skip to content

Commit bdafeda

Browse files
fix(ai-gateway): enable Vercel thinking display for Opus 4.7 (#2800)
* fix(ai-gateway): enable Vercel thinking display for Opus 4.7 * Cleanup * Extract function and add tests * Format --------- Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-authored-by: Christiaan Arnoldus <christiaan@kilocode.ai> Co-authored-by: Christiaan Arnoldus <christiaan.arnoldus@outlook.com>
1 parent 453a8e9 commit bdafeda

3 files changed

Lines changed: 118 additions & 10 deletions

File tree

apps/web/src/lib/ai-gateway/providers/openrouter/request-helpers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,25 @@ export function isReasoningExplicitlyDisabled(request: GatewayRequest) {
240240
);
241241
}
242242

243+
export function isReasoningExplicitlyEnabled(request: GatewayRequest) {
244+
if (request.kind === 'messages') {
245+
return request.body.thinking?.type === 'enabled' || request.body.thinking?.type === 'adaptive';
246+
}
247+
if (request.kind === 'responses') {
248+
return request.body.reasoning?.effort !== undefined && request.body.reasoning.effort !== 'none';
249+
}
250+
if (request.body.reasoning?.enabled === false) {
251+
return false;
252+
}
253+
return (
254+
request.body.reasoning?.enabled === true ||
255+
(request.body.reasoning?.effort !== undefined && request.body.reasoning.effort !== 'none') ||
256+
(request.body.reasoning_effort !== undefined && request.body.reasoning_effort !== 'none') ||
257+
request.body.enable_thinking === true || // Alibaba
258+
request.body.thinking?.type === 'enabled' // Bytedance
259+
);
260+
}
261+
243262
export function enableReasoningSummaries(request: GatewayRequest) {
244263
if (
245264
request.kind === 'messages' &&
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import { getAnthropicProviderOptionsForVercel } from '@/lib/ai-gateway/providers/vercel';
3+
import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types';
4+
5+
describe('getAnthropicProviderOptionsForVercel', () => {
6+
it('enables summarized thinking for Opus 4.7 when reasoning is explicitly enabled', () => {
7+
const request: GatewayRequest = {
8+
kind: 'chat_completions',
9+
body: {
10+
model: 'anthropic/claude-opus-4.7',
11+
messages: [{ role: 'user', content: 'hello' }],
12+
reasoning: { enabled: true },
13+
},
14+
};
15+
16+
expect(getAnthropicProviderOptionsForVercel('anthropic/claude-opus-4.7', request)).toEqual({
17+
thinking: { type: 'adaptive', display: 'summarized' },
18+
});
19+
});
20+
21+
it('maps chat completion verbosity to Anthropic effort', () => {
22+
const request: GatewayRequest = {
23+
kind: 'chat_completions',
24+
body: {
25+
model: 'anthropic/claude-sonnet-4.5',
26+
messages: [{ role: 'user', content: 'hello' }],
27+
verbosity: 'high',
28+
},
29+
};
30+
31+
expect(getAnthropicProviderOptionsForVercel('anthropic/claude-sonnet-4.5', request)).toEqual({
32+
effort: 'high',
33+
});
34+
});
35+
36+
it('maps responses text verbosity to Anthropic effort', () => {
37+
const request: GatewayRequest = {
38+
kind: 'responses',
39+
body: {
40+
model: 'anthropic/claude-sonnet-4.5',
41+
input: 'hello',
42+
text: { verbosity: 'low' },
43+
},
44+
};
45+
46+
expect(getAnthropicProviderOptionsForVercel('anthropic/claude-sonnet-4.5', request)).toEqual({
47+
effort: 'low',
48+
});
49+
});
50+
51+
it('returns undefined when no Anthropic options are needed', () => {
52+
const request: GatewayRequest = {
53+
kind: 'chat_completions',
54+
body: {
55+
model: 'anthropic/claude-sonnet-4.5',
56+
messages: [{ role: 'user', content: 'hello' }],
57+
},
58+
};
59+
60+
expect(getAnthropicProviderOptionsForVercel('anthropic/claude-sonnet-4.5', request)).toBe(
61+
undefined
62+
);
63+
});
64+
});

apps/web/src/lib/ai-gateway/providers/vercel/index.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import type {
1212
VercelInferenceProviderConfig,
1313
VercelProviderConfig,
1414
} from '@/lib/ai-gateway/providers/openrouter/types';
15-
import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers';
15+
import {
16+
isReasoningExplicitlyDisabled,
17+
isReasoningExplicitlyEnabled,
18+
} from '@/lib/ai-gateway/providers/openrouter/request-helpers';
1619
import { mapModelIdToVercel } from '@/lib/ai-gateway/providers/vercel/mapModelIdToVercel';
1720
import { redisGet } from '@/lib/redis';
1821
import { createCachedFetch } from '@/lib/cached-fetch';
@@ -23,6 +26,7 @@ import {
2326
import { VERCEL_ROUTING_REDIS_KEY } from '@/lib/redis-keys';
2427
import { getRandomNumber } from '@/lib/ai-gateway/getRandomNumber';
2528
import { getVercelModels } from '@/lib/ai-gateway/providers/gateway-models-cache';
29+
import type { AnthropicProviderOptions } from '@ai-sdk/anthropic';
2630

2731
const getVercelRoutingPercentage = createCachedFetch(
2832
async () => {
@@ -92,6 +96,33 @@ function parseAwsCredentials(input: string) {
9296
}
9397
}
9498

99+
export function getAnthropicProviderOptionsForVercel(
100+
requestedModel: string,
101+
request: GatewayRequest
102+
): AnthropicProviderOptions | undefined {
103+
const anthropicOptions: AnthropicProviderOptions = {};
104+
105+
// Workaround for Vercel not displaying thinking by default, unlike OpenRouter.
106+
const isOpus47Thinking =
107+
requestedModel.includes('opus-4.7') && isReasoningExplicitlyEnabled(request);
108+
if (isOpus47Thinking) {
109+
anthropicOptions.thinking = { type: 'adaptive', display: 'summarized' };
110+
}
111+
112+
if (request.kind === 'chat_completions' && request.body.verbosity) {
113+
anthropicOptions.effort = request.body.verbosity;
114+
}
115+
if (request.kind === 'responses' && request.body.text?.verbosity) {
116+
anthropicOptions.effort = request.body.text.verbosity;
117+
}
118+
119+
if (Object.keys(anthropicOptions).length === 0) {
120+
return undefined;
121+
}
122+
123+
return anthropicOptions;
124+
}
125+
95126
export function getVercelInferenceProviderConfigForUserByok(
96127
provider: BYOKResult
97128
): [VercelUserByokInferenceProviderId, VercelInferenceProviderConfig[]] {
@@ -153,15 +184,9 @@ export function applyVercelSettings(
153184
}
154185

155186
if (requestToMutate.body.providerOptions) {
156-
if (requestToMutate.kind === 'chat_completions' && requestToMutate.body.verbosity) {
157-
requestToMutate.body.providerOptions.anthropic = {
158-
effort: requestToMutate.body.verbosity,
159-
};
160-
}
161-
if (requestToMutate.kind === 'responses' && requestToMutate.body.text?.verbosity) {
162-
requestToMutate.body.providerOptions.anthropic = {
163-
effort: requestToMutate.body.text.verbosity,
164-
};
187+
const anthropicOptions = getAnthropicProviderOptionsForVercel(requestedModel, requestToMutate);
188+
if (anthropicOptions) {
189+
requestToMutate.body.providerOptions.anthropic = anthropicOptions;
165190
}
166191
}
167192

0 commit comments

Comments
 (0)