fix: route all providers through proxy to patch thinking.type for Claude 4

Opus 4.7 requires thinking.type="adaptive" instead of "enabled".
The CLI sends "enabled" which upstream APIs reject. Now all providers
(including Anthropic-format) route through the proxy, which patches
the thinking parameter before forwarding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 21:42:46 +08:00
co-authored by Claude Opus 4.6
parent e3b1c3525b
commit a3cddb89c6
2 changed files with 62 additions and 17 deletions
+61 -16
View File
@@ -18,6 +18,17 @@ import { openaiChatStreamToAnthropic } from './streaming/openaiChatStreamToAnthr
import { openaiResponsesStreamToAnthropic } from './streaming/openaiResponsesStreamToAnthropic.js'
import type { AnthropicRequest } from './transform/types.js'
/**
* Patch thinking parameter for newer Claude models (4.x / Opus 4.7+).
* These models require `thinking.type = "adaptive"` instead of `"enabled"`.
*/
function patchThinkingForModel(body: AnthropicRequest): void {
if (!body.thinking) return
if (body.thinking.type === 'enabled') {
body.thinking.type = 'adaptive'
}
}
const providerService = new ProviderService()
export async function handleProxyRequest(req: Request, url: URL): Promise<Response> {
@@ -53,21 +64,6 @@ export async function handleProxyRequest(req: Request, url: URL): Promise<Respon
)
}
if (config.apiFormat === 'anthropic') {
return Response.json(
{
type: 'error',
error: {
type: 'invalid_request_error',
message: providerId
? `Provider "${providerId}" uses anthropic format — proxy not needed`
: 'Active provider uses anthropic format — proxy not needed',
},
},
{ status: 400 },
)
}
// Parse request body
let body: AnthropicRequest
try {
@@ -82,8 +78,13 @@ export async function handleProxyRequest(req: Request, url: URL): Promise<Respon
const isStream = body.stream === true
const baseUrl = config.baseUrl.replace(/\/+$/, '')
// Always patch thinking parameter for newer models
patchThinkingForModel(body)
try {
if (config.apiFormat === 'openai_chat') {
if (config.apiFormat === 'anthropic') {
return await handleAnthropicPassthrough(req, body, baseUrl, config.apiKey, isStream)
} else if (config.apiFormat === 'openai_chat') {
return await handleOpenaiChat(body, baseUrl, config.apiKey, isStream)
} else {
return await handleOpenaiResponses(body, baseUrl, config.apiKey, isStream)
@@ -103,6 +104,50 @@ export async function handleProxyRequest(req: Request, url: URL): Promise<Respon
}
}
async function handleAnthropicPassthrough(
originalReq: Request,
body: AnthropicRequest,
baseUrl: string,
apiKey: string,
isStream: boolean,
): Promise<Response> {
const url = `${baseUrl}/v1/messages`
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': originalReq.headers.get('anthropic-version') || '2023-06-01',
}
const authHeader = originalReq.headers.get('authorization')
if (authHeader) {
headers['Authorization'] = authHeader
}
const upstream = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(body),
signal: AbortSignal.timeout(600_000),
})
if (isStream && upstream.ok && upstream.body) {
return new Response(upstream.body, {
status: upstream.status,
headers: {
'Content-Type': upstream.headers.get('Content-Type') || 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}
const text = await upstream.text()
return new Response(text, {
status: upstream.status,
headers: { 'Content-Type': 'application/json; charset=utf-8' },
})
}
async function handleOpenaiChat(
body: AnthropicRequest,
baseUrl: string,
@@ -247,7 +247,7 @@ export class ProviderService {
provider: SavedProvider,
options?: { proxyPath?: string },
): Record<string, string> {
const needsProxy = provider.apiFormat != null && provider.apiFormat !== 'anthropic'
const needsProxy = provider.apiFormat != null
const proxyPath = options?.proxyPath ?? '/proxy'
const baseUrl = needsProxy
? `http://127.0.0.1:${ProviderService.serverPort}${proxyPath}`