Add the option to use a custom Host header for openai-compatible (#2399)

This commit is contained in:
Matt Rubens 2025-04-08 22:02:27 -04:00 committed by GitHub
parent 9f724bd360
commit b01615f122
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 112 additions and 8 deletions

View file

@ -26,9 +26,11 @@ demo.gif
.prettierignore
.clinerules*
.roomodes
.roo/**
cline_docs/**
coverage/**
locales/**
benchmark/**
# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
webview-ui/src/**

View file

@ -55,10 +55,20 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
baseURL,
apiKey,
apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion,
defaultHeaders,
defaultHeaders: {
...defaultHeaders,
...(this.options.openAiHostHeader ? { Host: this.options.openAiHostHeader } : {}),
},
})
} else {
this.client = new OpenAI({ baseURL, apiKey, defaultHeaders })
this.client = new OpenAI({
baseURL,
apiKey,
defaultHeaders: {
...defaultHeaders,
...(this.options.openAiHostHeader ? { Host: this.options.openAiHostHeader } : {}),
},
})
}
}
@ -67,6 +77,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const modelUrl = this.options.openAiBaseUrl ?? ""
const modelId = this.options.openAiModelId ?? ""
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
const enabledLegacyFormat = this.options.openAiLegacyFormat ?? false
const isAzureAiInference = this._isAzureAiInference(modelUrl)
const urlHost = this._getUrlHost(modelUrl)
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
@ -85,7 +96,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
let convertedMessages
if (deepseekReasoner) {
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
} else if (ark) {
} else if (ark || enabledLegacyFormat) {
convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)]
} else {
if (modelInfo.supportsPromptCache) {
@ -190,7 +201,9 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
model: modelId,
messages: deepseekReasoner
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [systemMessage, ...convertToOpenAiMessages(messages)],
: enabledLegacyFormat
? [systemMessage, ...convertToSimpleMessages(messages)]
: [systemMessage, ...convertToOpenAiMessages(messages)],
}
const response = await this.client.chat.completions.create(
@ -330,7 +343,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
}
}
export async function getOpenAiModels(baseUrl?: string, apiKey?: string) {
export async function getOpenAiModels(baseUrl?: string, apiKey?: string, hostHeader?: string) {
try {
if (!baseUrl) {
return []
@ -341,9 +354,18 @@ export async function getOpenAiModels(baseUrl?: string, apiKey?: string) {
}
const config: Record<string, any> = {}
const headers: Record<string, string> = {}
if (apiKey) {
config["headers"] = { Authorization: `Bearer ${apiKey}` }
headers["Authorization"] = `Bearer ${apiKey}`
}
if (hostHeader) {
headers["Host"] = hostHeader
}
if (Object.keys(headers).length > 0) {
config["headers"] = headers
}
const response = await axios.get(`${baseUrl}/models`, config)

View file

@ -423,7 +423,11 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
case "refreshOpenAiModels":
if (message?.values?.baseUrl && message?.values?.apiKey) {
const openAiModels = await getOpenAiModels(message?.values?.baseUrl, message?.values?.apiKey)
const openAiModels = await getOpenAiModels(
message?.values?.baseUrl,
message?.values?.apiKey,
message?.values?.hostHeader,
)
provider.postMessageToWebview({ type: "openAiModels", openAiModels })
}

View file

@ -86,6 +86,8 @@ type ProviderSettings = {
vertexRegion?: string | undefined
openAiBaseUrl?: string | undefined
openAiApiKey?: string | undefined
openAiHostHeader?: string | undefined
openAiLegacyFormat?: boolean | undefined
openAiR1FormatEnabled?: boolean | undefined
openAiModelId?: string | undefined
openAiCustomModelInfo?:

View file

@ -87,6 +87,8 @@ type ProviderSettings = {
vertexRegion?: string | undefined
openAiBaseUrl?: string | undefined
openAiApiKey?: string | undefined
openAiHostHeader?: string | undefined
openAiLegacyFormat?: boolean | undefined
openAiR1FormatEnabled?: boolean | undefined
openAiModelId?: string | undefined
openAiCustomModelInfo?:

View file

@ -338,6 +338,8 @@ export const providerSettingsSchema = z.object({
// OpenAI
openAiBaseUrl: z.string().optional(),
openAiApiKey: z.string().optional(),
openAiHostHeader: z.string().optional(),
openAiLegacyFormat: z.boolean().optional(),
openAiR1FormatEnabled: z.boolean().optional(),
openAiModelId: z.string().optional(),
openAiCustomModelInfo: modelInfoSchema.nullish(),
@ -431,6 +433,8 @@ const providerSettingsRecord: ProviderSettingsRecord = {
// OpenAI
openAiBaseUrl: undefined,
openAiApiKey: undefined,
openAiHostHeader: undefined,
openAiLegacyFormat: undefined,
openAiR1FormatEnabled: undefined,
openAiModelId: undefined,
openAiCustomModelInfo: undefined,

View file

@ -103,6 +103,8 @@ const ApiOptions = ({
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
const [openRouterBaseUrlSelected, setOpenRouterBaseUrlSelected] = useState(!!apiConfiguration?.openRouterBaseUrl)
const [openAiHostHeaderSelected, setOpenAiHostHeaderSelected] = useState(!!apiConfiguration?.openAiHostHeader)
const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat)
const [googleGeminiBaseUrlSelected, setGoogleGeminiBaseUrlSelected] = useState(
!!apiConfiguration?.googleGeminiBaseUrl,
)
@ -145,7 +147,11 @@ const ApiOptions = ({
} else if (selectedProvider === "openai") {
vscode.postMessage({
type: "refreshOpenAiModels",
values: { baseUrl: apiConfiguration?.openAiBaseUrl, apiKey: apiConfiguration?.openAiApiKey },
values: {
baseUrl: apiConfiguration?.openAiBaseUrl,
apiKey: apiConfiguration?.openAiApiKey,
hostHeader: apiConfiguration?.openAiHostHeader,
},
})
} else if (selectedProvider === "ollama") {
vscode.postMessage({ type: "requestOllamaModels", text: apiConfiguration?.ollamaBaseUrl })
@ -779,6 +785,16 @@ const ApiOptions = ({
onChange={handleInputChange("openAiR1FormatEnabled", noTransform)}
openAiR1FormatEnabled={apiConfiguration?.openAiR1FormatEnabled ?? false}
/>
<div>
<Checkbox
checked={openAiLegacyFormatSelected}
onChange={(checked: boolean) => {
setOpenAiLegacyFormatSelected(checked)
setApiConfigurationField("openAiLegacyFormat", checked)
}}>
{t("settings:providers.useLegacyFormat")}
</Checkbox>
</div>
<Checkbox
checked={apiConfiguration?.openAiStreamingEnabled ?? true}
onChange={handleInputChange("openAiStreamingEnabled", noTransform)}>
@ -811,6 +827,28 @@ const ApiOptions = ({
)}
</div>
<div>
<Checkbox
checked={openAiHostHeaderSelected}
onChange={(checked: boolean) => {
setOpenAiHostHeaderSelected(checked)
if (!checked) {
setApiConfigurationField("openAiHostHeader", "")
}
}}>
{t("settings:providers.useHostHeader")}
</Checkbox>
{openAiHostHeaderSelected && (
<VSCodeTextField
value={apiConfiguration?.openAiHostHeader || ""}
onInput={handleInputChange("openAiHostHeader")}
placeholder="custom-api-hostname.example.com"
className="w-full mt-1"
/>
)}
</div>
<div className="flex flex-col gap-3">
<div className="text-sm text-vscode-descriptionForeground">
{t("settings:providers.customModel.capabilities")}

View file

@ -105,6 +105,8 @@
"awsCustomArnDesc": "Assegureu-vos que la regió a l'ARN coincideix amb la regió d'AWS seleccionada anteriorment.",
"apiKeyStorageNotice": "Les claus API s'emmagatzemen de forma segura a l'Emmagatzematge Secret de VSCode",
"useCustomBaseUrl": "Utilitzar URL base personalitzada",
"useHostHeader": "Utilitzar capçalera Host personalitzada",
"useLegacyFormat": "Utilitzar el format d'API OpenAI antic",
"openRouterTransformsText": "Comprimir prompts i cadenes de missatges a la mida del context (<a>Transformacions d'OpenRouter</a>)",
"model": "Model",
"getOpenRouterApiKey": "Obtenir clau API d'OpenRouter",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API-Schlüssel",
"getGlamaApiKey": "Glama API-Schlüssel erhalten",
"useCustomBaseUrl": "Benutzerdefinierte Basis-URL verwenden",
"useHostHeader": "Benutzerdefinierten Host-Header verwenden",
"useLegacyFormat": "Altes OpenAI API-Format verwenden",
"requestyApiKey": "Requesty API-Schlüssel",
"getRequestyApiKey": "Requesty API-Schlüssel erhalten",
"openRouterTransformsText": "Prompts und Nachrichtenketten auf Kontextgröße komprimieren (<a>OpenRouter Transformationen</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API Key",
"getGlamaApiKey": "Get Glama API Key",
"useCustomBaseUrl": "Use custom base URL",
"useHostHeader": "Use custom Host header",
"useLegacyFormat": "Use legacy OpenAI API format",
"requestyApiKey": "Requesty API Key",
"getRequestyApiKey": "Get Requesty API Key",
"openRouterTransformsText": "Compress prompts and message chains to the context size (<a>OpenRouter Transforms</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Clave API de Glama",
"getGlamaApiKey": "Obtener clave API de Glama",
"useCustomBaseUrl": "Usar URL base personalizada",
"useHostHeader": "Usar encabezado Host personalizado",
"useLegacyFormat": "Usar formato API de OpenAI heredado",
"requestyApiKey": "Clave API de Requesty",
"getRequestyApiKey": "Obtener clave API de Requesty",
"openRouterTransformsText": "Comprimir prompts y cadenas de mensajes al tamaño del contexto (<a>Transformaciones de OpenRouter</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Clé API Glama",
"getGlamaApiKey": "Obtenir la clé API Glama",
"useCustomBaseUrl": "Utiliser une URL de base personnalisée",
"useHostHeader": "Utiliser un en-tête Host personnalisé",
"useLegacyFormat": "Utiliser le format API OpenAI hérité",
"requestyApiKey": "Clé API Requesty",
"getRequestyApiKey": "Obtenir la clé API Requesty",
"openRouterTransformsText": "Compresser les prompts et chaînes de messages à la taille du contexte (<a>Transformations OpenRouter</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API कुंजी",
"getGlamaApiKey": "Glama API कुंजी प्राप्त करें",
"useCustomBaseUrl": "कस्टम बेस URL का उपयोग करें",
"useHostHeader": "कस्टम होस्ट हेडर का उपयोग करें",
"useLegacyFormat": "पुराने OpenAI API प्रारूप का उपयोग करें",
"requestyApiKey": "Requesty API कुंजी",
"getRequestyApiKey": "Requesty API कुंजी प्राप्त करें",
"openRouterTransformsText": "संदर्भ आकार के लिए प्रॉम्प्ट और संदेश श्रृंखलाओं को संपीड़ित करें (<a>OpenRouter ट्रांसफॉर्म</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Chiave API Glama",
"getGlamaApiKey": "Ottieni chiave API Glama",
"useCustomBaseUrl": "Usa URL base personalizzato",
"useHostHeader": "Usa intestazione Host personalizzata",
"useLegacyFormat": "Usa formato API OpenAI legacy",
"requestyApiKey": "Chiave API Requesty",
"getRequestyApiKey": "Ottieni chiave API Requesty",
"openRouterTransformsText": "Comprimi prompt e catene di messaggi alla dimensione del contesto (<a>Trasformazioni OpenRouter</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama APIキー",
"getGlamaApiKey": "Glama APIキーを取得",
"useCustomBaseUrl": "カスタムベースURLを使用",
"useHostHeader": "カスタムHostヘッダーを使用",
"useLegacyFormat": "レガシーOpenAI API形式を使用",
"requestyApiKey": "Requesty APIキー",
"getRequestyApiKey": "Requesty APIキーを取得",
"openRouterTransformsText": "プロンプトとメッセージチェーンをコンテキストサイズに圧縮 (<a>OpenRouter Transforms</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API 키",
"getGlamaApiKey": "Glama API 키 받기",
"useCustomBaseUrl": "사용자 정의 기본 URL 사용",
"useHostHeader": "사용자 정의 Host 헤더 사용",
"useLegacyFormat": "레거시 OpenAI API 형식 사용",
"requestyApiKey": "Requesty API 키",
"getRequestyApiKey": "Requesty API 키 받기",
"openRouterTransformsText": "프롬프트와 메시지 체인을 컨텍스트 크기로 압축 (<a>OpenRouter Transforms</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Klucz API Glama",
"getGlamaApiKey": "Uzyskaj klucz API Glama",
"useCustomBaseUrl": "Użyj niestandardowego URL bazowego",
"useHostHeader": "Użyj niestandardowego nagłówka Host",
"useLegacyFormat": "Użyj starszego formatu API OpenAI",
"requestyApiKey": "Klucz API Requesty",
"getRequestyApiKey": "Uzyskaj klucz API Requesty",
"openRouterTransformsText": "Kompresuj podpowiedzi i łańcuchy wiadomości do rozmiaru kontekstu (<a>Transformacje OpenRouter</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Chave de API Glama",
"getGlamaApiKey": "Obter chave de API Glama",
"useCustomBaseUrl": "Usar URL base personalizado",
"useHostHeader": "Usar cabeçalho Host personalizado",
"useLegacyFormat": "Usar formato de API OpenAI legado",
"requestyApiKey": "Chave de API Requesty",
"getRequestyApiKey": "Obter chave de API Requesty",
"openRouterTransformsText": "Comprimir prompts e cadeias de mensagens para o tamanho do contexto (<a>Transformações OpenRouter</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API Anahtarı",
"getGlamaApiKey": "Glama API Anahtarı Al",
"useCustomBaseUrl": "Özel temel URL kullan",
"useHostHeader": "Özel Host başlığı kullan",
"useLegacyFormat": "Eski OpenAI API formatını kullan",
"requestyApiKey": "Requesty API Anahtarı",
"getRequestyApiKey": "Requesty API Anahtarı Al",
"openRouterTransformsText": "İstem ve mesaj zincirlerini bağlam boyutuna sıkıştır (<a>OpenRouter Dönüşümleri</a>)",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Khóa API Glama",
"getGlamaApiKey": "Lấy khóa API Glama",
"useCustomBaseUrl": "Sử dụng URL cơ sở tùy chỉnh",
"useHostHeader": "Sử dụng tiêu đề Host tùy chỉnh",
"useLegacyFormat": "Sử dụng định dạng API OpenAI cũ",
"requestyApiKey": "Khóa API Requesty",
"getRequestyApiKey": "Lấy khóa API Requesty",
"anthropicApiKey": "Khóa API Anthropic",

View file

@ -107,6 +107,8 @@
"getOpenRouterApiKey": "获取 OpenRouter API 密钥",
"apiKeyStorageNotice": "API 密钥安全存储在 VSCode 的密钥存储中",
"useCustomBaseUrl": "使用自定义基础 URL",
"useHostHeader": "使用自定义 Host 标头",
"useLegacyFormat": "使用传统 OpenAI API 格式",
"glamaApiKey": "Glama API 密钥",
"getGlamaApiKey": "获取 Glama API 密钥",
"requestyApiKey": "Requesty API 密钥",

View file

@ -109,6 +109,8 @@
"glamaApiKey": "Glama API 金鑰",
"getGlamaApiKey": "取得 Glama API 金鑰",
"useCustomBaseUrl": "使用自訂基礎 URL",
"useHostHeader": "使用自訂 Host 標頭",
"useLegacyFormat": "使用舊版 OpenAI API 格式",
"requestyApiKey": "Requesty API 金鑰",
"getRequestyApiKey": "取得 Requesty API 金鑰",
"openRouterTransformsText": "將提示和訊息鏈壓縮到上下文大小 (<a>OpenRouter 轉換</a>)",