feat: sideQuery 支持第三方 provider 路由 (OpenAI/Grok/Gemini)

- 新增 getProviderPrimaryModel() 从环境变量解析 provider 主模型
- getDefaultOpus/Sonnet/HaikuModel 在第三方 provider 下回退到用户配置的主模型
- sideQuery 根据 provider 类型分发到对应的 API 适配器
- 新增 sideQueryViaOpenAICompatible (OpenAI + Grok) 和 sideQueryViaGemini 适配函数
- 避免 sideQuery 后台任务在配置第三方端点时仍请求 Anthropic API
This commit is contained in:
claude-code-best 2026-05-31 14:08:30 +08:00 committed by James Feng
parent b9fcd991cc
commit 9ae258f917
2 changed files with 47 additions and 19 deletions

View File

@ -123,6 +123,19 @@ export function getBestModel(): ModelName {
return getDefaultOpusModel()
}
/**
* Resolve the provider's primary model from its env var (e.g. OPENAI_MODEL).
* Returns undefined for providers that don't have a primary-model env var
* (Bedrock, Vertex, Foundry, firstParty).
*/
function getProviderPrimaryModel(): ModelName | undefined {
const provider = getAPIProvider()
if (provider === 'openai') return process.env.OPENAI_MODEL
if (provider === 'gemini') return process.env.GEMINI_MODEL
if (provider === 'grok') return process.env.GROK_MODEL
return undefined
}
// @[MODEL LAUNCH]: Update the default Opus model (3P providers may lag so keep defaults unchanged).
export function getDefaultOpusModel(): ModelName {
const provider = getAPIProvider()
@ -138,10 +151,15 @@ export function getDefaultOpusModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) {
return process.env.ANTHROPIC_DEFAULT_OPUS_MODEL
}
// Fall back to user's configured model — custom providers may not
// recognize hardcoded Anthropic model IDs.
// Skip if the user setting is a model alias (e.g. "opus", "opus[1m]") to
// avoid infinite recursion: parseUserSpecifiedModel(alias) → getDefaultOpusModel().
// 3P providers: if user set a primary model (e.g. OPENAI_MODEL=glm-5.1),
// fall back to it instead of a hardcoded Anthropic model. This prevents
// sideQuery / background tasks from sending requests to Anthropic's API
// when the user configured a third-party provider.
const primaryModel = getProviderPrimaryModel()
if (primaryModel) return primaryModel
// Fall back to user's configured model — custom providers may not recognize
// hardcoded Anthropic model IDs. Skip aliases to avoid infinite recursion:
// parseUserSpecifiedModel(alias) → getDefaultOpusModel().
const userSpecifiedOpus = getUserSpecifiedModelSetting()
if (userSpecifiedOpus && !isAliasOrAliasWithSuffix(userSpecifiedOpus)) {
return parseUserSpecifiedModel(userSpecifiedOpus)
@ -170,9 +188,12 @@ export function getDefaultSonnetModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) {
return process.env.ANTHROPIC_DEFAULT_SONNET_MODEL
}
// Fall back to user's configured model (ANTHROPIC_MODEL / settings) —
// custom providers (proxies, national clouds) may not recognize the
// hardcoded Anthropic model IDs.
// 3P providers: fall back to user's primary model instead of a hardcoded
// Anthropic model name. Prevents background API calls from being routed to
// Anthropic when the user configured a third-party endpoint.
const primaryModel = getProviderPrimaryModel()
if (primaryModel) return primaryModel
// Fall back to user's configured model (ANTHROPIC_MODEL / settings).
// Skip if the user setting is a model alias to avoid infinite recursion.
const userSpecified = getUserSpecifiedModelSetting()
if (userSpecified && !isAliasOrAliasWithSuffix(userSpecified)) {
@ -200,9 +221,12 @@ export function getDefaultHaikuModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) {
return process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL
}
// Fall back to user's configured model — custom providers may not
// recognize hardcoded Anthropic model IDs.
// Skip if the user setting is a model alias to avoid infinite recursion.
// 3P providers: fall back to user's primary model instead of a hardcoded
// Anthropic model name.
const primaryModel = getProviderPrimaryModel()
if (primaryModel) return primaryModel
// Fall back to user's configured model — custom providers may not recognize
// hardcoded Anthropic model IDs. Skip aliases to avoid infinite recursion.
const userSpecifiedHaiku = getUserSpecifiedModelSetting()
if (userSpecifiedHaiku && !isAliasOrAliasWithSuffix(userSpecifiedHaiku)) {
return parseUserSpecifiedModel(userSpecifiedHaiku)

View File

@ -35,7 +35,6 @@ import { normalizeModelStringForAPI } from './model/model.js'
import { getAPIProvider } from './model/providers.js'
import { getOpenAIClient } from '../services/api/openai/client.js'
import { getGrokClient } from '../services/api/grok/client.js'
import { anthropicMessagesToOpenAI } from '../services/api/openai/convertMessages.js'
import { resolveOpenAIModel } from '../services/api/openai/modelMapping.js'
import { resolveGrokModel } from '../services/api/grok/modelMapping.js'
import {
@ -222,10 +221,9 @@ async function sideQueryViaOpenAICompatible(
if (openaiToolChoice) requestParams.tool_choice = openaiToolChoice
}
const response = await client.chat.completions.create(
requestParams as any,
{ signal },
)
const response = await client.chat.completions.create(requestParams as any, {
signal,
})
const choice = response.choices[0]
const message = choice?.message
@ -303,9 +301,14 @@ async function sideQueryViaOpenAICompatible(
* Use this instead of direct client.beta.messages.create() calls to ensure
* proper OAuth token validation with fingerprint attribution headers.
*
* Third-party provider routing (OpenAI, Grok, Gemini) is handled transparently
* when the user configures a third-party provider, sideQuery automatically
* routes to the correct API adapter instead of sending requests to Anthropic.
* This handles:
* - Fingerprint computation for OAuth validation
* - Attribution header injection
* - CLI system prompt prefix
* - Proper betas for the model
* - API metadata
* - Model string normalization (strips [1m] suffix for API)
* - Third-party provider routing (OpenAI, Grok, Gemini)
*
* @example
* // Permission explainer
@ -603,7 +606,8 @@ async function sideQueryViaGemini(
const body: Record<string, unknown> = {
contents,
...(systemInstruction && { systemInstruction }),
...(geminiTools && (geminiTools as unknown[]).length > 0 && { tools: geminiTools }),
...(geminiTools &&
(geminiTools as unknown[]).length > 0 && { tools: geminiTools }),
...(geminiToolConfig && {
toolConfig: { functionCallingConfig: geminiToolConfig },
}),