mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 21:35:52 +08:00
fix: remove trailing api version in ANTHROPIC_BASE_URL (#12145)
This commit is contained in:
parent
ab3bce33b8
commit
99b431ec92
@ -35,3 +35,56 @@ export const defaultAppHeaders = () => {
|
|||||||
// return value
|
// return value
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the trailing API version segment from a URL path.
|
||||||
|
*
|
||||||
|
* This function extracts API version patterns (e.g., `v1`, `v2beta`) from the end of a URL.
|
||||||
|
* Only versions at the end of the path are extracted, not versions in the middle.
|
||||||
|
* The returned version string does not include leading or trailing slashes.
|
||||||
|
*
|
||||||
|
* @param {string} url - The URL string to parse.
|
||||||
|
* @returns {string | undefined} The trailing API version found (e.g., 'v1', 'v2beta'), or undefined if none found.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* getTrailingApiVersion('https://api.example.com/v1') // 'v1'
|
||||||
|
* getTrailingApiVersion('https://api.example.com/v2beta/') // 'v2beta'
|
||||||
|
* getTrailingApiVersion('https://api.example.com/v1/chat') // undefined (version not at end)
|
||||||
|
* getTrailingApiVersion('https://gateway.ai.cloudflare.com/v1/xxx/v1beta') // 'v1beta'
|
||||||
|
* getTrailingApiVersion('https://api.example.com') // undefined
|
||||||
|
*/
|
||||||
|
export function getTrailingApiVersion(url: string): string | undefined {
|
||||||
|
const match = url.match(TRAILING_VERSION_REGEX)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
// Extract version without leading slash and trailing slash
|
||||||
|
return match[0].replace(/^\//, '').replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches an API version at the end of a URL (with optional trailing slash).
|
||||||
|
* Used to detect and extract versions only from the trailing position.
|
||||||
|
*/
|
||||||
|
const TRAILING_VERSION_REGEX = /\/v\d+(?:alpha|beta)?\/?$/i
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the trailing API version segment from a URL path.
|
||||||
|
*
|
||||||
|
* This function removes API version patterns (e.g., `/v1`, `/v2beta`) from the end of a URL.
|
||||||
|
* Only versions at the end of the path are removed, not versions in the middle.
|
||||||
|
*
|
||||||
|
* @param {string} url - The URL string to process.
|
||||||
|
* @returns {string} The URL with the trailing API version removed, or the original URL if no trailing version found.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* withoutTrailingApiVersion('https://api.example.com/v1') // 'https://api.example.com'
|
||||||
|
* withoutTrailingApiVersion('https://api.example.com/v2beta/') // 'https://api.example.com'
|
||||||
|
* withoutTrailingApiVersion('https://api.example.com/v1/chat') // 'https://api.example.com/v1/chat' (no change)
|
||||||
|
* withoutTrailingApiVersion('https://api.example.com') // 'https://api.example.com'
|
||||||
|
*/
|
||||||
|
export function withoutTrailingApiVersion(url: string): string {
|
||||||
|
return url.replace(TRAILING_VERSION_REGEX, '')
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { validateModelId } from '@main/apiServer/utils'
|
|||||||
import { isWin } from '@main/constant'
|
import { isWin } from '@main/constant'
|
||||||
import { autoDiscoverGitBash } from '@main/utils/process'
|
import { autoDiscoverGitBash } from '@main/utils/process'
|
||||||
import getLoginShellEnvironment from '@main/utils/shell-env'
|
import getLoginShellEnvironment from '@main/utils/shell-env'
|
||||||
|
import { withoutTrailingApiVersion } from '@shared/utils'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
import type { GetAgentSessionResponse } from '../..'
|
import type { GetAgentSessionResponse } from '../..'
|
||||||
@ -112,6 +113,13 @@ class ClaudeCodeService implements AgentServiceInterface {
|
|||||||
// Auto-discover Git Bash path on Windows (already logs internally)
|
// Auto-discover Git Bash path on Windows (already logs internally)
|
||||||
const customGitBashPath = isWin ? autoDiscoverGitBash() : null
|
const customGitBashPath = isWin ? autoDiscoverGitBash() : null
|
||||||
|
|
||||||
|
// Claude Agent SDK builds the final endpoint as `${ANTHROPIC_BASE_URL}/v1/messages`.
|
||||||
|
// To avoid malformed URLs like `/v1/v1/messages`, we normalize the provider host
|
||||||
|
// by stripping any trailing API version (e.g. `/v1`).
|
||||||
|
const anthropicBaseUrl = withoutTrailingApiVersion(
|
||||||
|
modelInfo.provider.anthropicApiHost?.trim() || modelInfo.provider.apiHost
|
||||||
|
)
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
...loginShellEnvWithoutProxies,
|
...loginShellEnvWithoutProxies,
|
||||||
// TODO: fix the proxy api server
|
// TODO: fix the proxy api server
|
||||||
@ -120,7 +128,7 @@ class ClaudeCodeService implements AgentServiceInterface {
|
|||||||
// ANTHROPIC_BASE_URL: `http://${apiConfig.host}:${apiConfig.port}/${modelInfo.provider.id}`,
|
// ANTHROPIC_BASE_URL: `http://${apiConfig.host}:${apiConfig.port}/${modelInfo.provider.id}`,
|
||||||
ANTHROPIC_API_KEY: modelInfo.provider.apiKey,
|
ANTHROPIC_API_KEY: modelInfo.provider.apiKey,
|
||||||
ANTHROPIC_AUTH_TOKEN: modelInfo.provider.apiKey,
|
ANTHROPIC_AUTH_TOKEN: modelInfo.provider.apiKey,
|
||||||
ANTHROPIC_BASE_URL: modelInfo.provider.anthropicApiHost?.trim() || modelInfo.provider.apiHost,
|
ANTHROPIC_BASE_URL: anthropicBaseUrl,
|
||||||
ANTHROPIC_MODEL: modelInfo.modelId,
|
ANTHROPIC_MODEL: modelInfo.modelId,
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL: modelInfo.modelId,
|
ANTHROPIC_DEFAULT_OPUS_MODEL: modelInfo.modelId,
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL: modelInfo.modelId,
|
ANTHROPIC_DEFAULT_SONNET_MODEL: modelInfo.modelId,
|
||||||
|
|||||||
@ -46,7 +46,6 @@ import type {
|
|||||||
GeminiSdkRawOutput,
|
GeminiSdkRawOutput,
|
||||||
GeminiSdkToolCall
|
GeminiSdkToolCall
|
||||||
} from '@renderer/types/sdk'
|
} from '@renderer/types/sdk'
|
||||||
import { getTrailingApiVersion, withoutTrailingApiVersion } from '@renderer/utils'
|
|
||||||
import { isToolUseModeFunction } from '@renderer/utils/assistant'
|
import { isToolUseModeFunction } from '@renderer/utils/assistant'
|
||||||
import {
|
import {
|
||||||
geminiFunctionCallToMcpTool,
|
geminiFunctionCallToMcpTool,
|
||||||
@ -56,6 +55,7 @@ import {
|
|||||||
} from '@renderer/utils/mcp-tools'
|
} from '@renderer/utils/mcp-tools'
|
||||||
import { findFileBlocks, findImageBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, findImageBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
import { defaultTimeout, MB } from '@shared/config/constant'
|
import { defaultTimeout, MB } from '@shared/config/constant'
|
||||||
|
import { getTrailingApiVersion, withoutTrailingApiVersion } from '@shared/utils'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
|
||||||
import type { GenericChunk } from '../../middleware/schemas'
|
import type { GenericChunk } from '../../middleware/schemas'
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { loggerService } from '@logger'
|
|||||||
import { isSupportedModel } from '@renderer/config/models'
|
import { isSupportedModel } from '@renderer/config/models'
|
||||||
import type { Provider } from '@renderer/types'
|
import type { Provider } from '@renderer/types'
|
||||||
import { objectKeys } from '@renderer/types'
|
import { objectKeys } from '@renderer/types'
|
||||||
import { formatApiHost, withoutTrailingApiVersion } from '@renderer/utils'
|
import { formatApiHost } from '@renderer/utils'
|
||||||
|
import { withoutTrailingApiVersion } from '@shared/utils'
|
||||||
|
|
||||||
import { OpenAIAPIClient } from '../openai/OpenAIApiClient'
|
import { OpenAIAPIClient } from '../openai/OpenAIApiClient'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import type { VertexProvider } from '@renderer/types'
|
import type { VertexProvider } from '@renderer/types'
|
||||||
|
import { getTrailingApiVersion, withoutTrailingApiVersion } from '@shared/utils'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -8,14 +9,12 @@ import {
|
|||||||
formatAzureOpenAIApiHost,
|
formatAzureOpenAIApiHost,
|
||||||
formatOllamaApiHost,
|
formatOllamaApiHost,
|
||||||
formatVertexApiHost,
|
formatVertexApiHost,
|
||||||
getTrailingApiVersion,
|
|
||||||
hasAPIVersion,
|
hasAPIVersion,
|
||||||
isWithTrailingSharp,
|
isWithTrailingSharp,
|
||||||
maskApiKey,
|
maskApiKey,
|
||||||
routeToEndpoint,
|
routeToEndpoint,
|
||||||
splitApiKeyString,
|
splitApiKeyString,
|
||||||
validateApiHost,
|
validateApiHost,
|
||||||
withoutTrailingApiVersion,
|
|
||||||
withoutTrailingSharp
|
withoutTrailingSharp
|
||||||
} from '../api'
|
} from '../api'
|
||||||
|
|
||||||
|
|||||||
@ -19,12 +19,6 @@ export function formatApiKeys(value: string): string {
|
|||||||
*/
|
*/
|
||||||
const VERSION_REGEX_PATTERN = '\\/v\\d+(?:alpha|beta)?(?=\\/|$)'
|
const VERSION_REGEX_PATTERN = '\\/v\\d+(?:alpha|beta)?(?=\\/|$)'
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches an API version at the end of a URL (with optional trailing slash).
|
|
||||||
* Used to detect and extract versions only from the trailing position.
|
|
||||||
*/
|
|
||||||
const TRAILING_VERSION_REGEX = /\/v\d+(?:alpha|beta)?\/?$/i
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断 host 的 path 中是否包含形如版本的字符串(例如 /v1、/v2beta 等),
|
* 判断 host 的 path 中是否包含形如版本的字符串(例如 /v1、/v2beta 等),
|
||||||
*
|
*
|
||||||
@ -272,50 +266,3 @@ export function splitApiKeyString(keyStr: string): string[] {
|
|||||||
.map((k) => k.replace(/\\,/g, ','))
|
.map((k) => k.replace(/\\,/g, ','))
|
||||||
.filter((k) => k)
|
.filter((k) => k)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the trailing API version segment from a URL path.
|
|
||||||
*
|
|
||||||
* This function extracts API version patterns (e.g., `v1`, `v2beta`) from the end of a URL.
|
|
||||||
* Only versions at the end of the path are extracted, not versions in the middle.
|
|
||||||
* The returned version string does not include leading or trailing slashes.
|
|
||||||
*
|
|
||||||
* @param {string} url - The URL string to parse.
|
|
||||||
* @returns {string | undefined} The trailing API version found (e.g., 'v1', 'v2beta'), or undefined if none found.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* getTrailingApiVersion('https://api.example.com/v1') // 'v1'
|
|
||||||
* getTrailingApiVersion('https://api.example.com/v2beta/') // 'v2beta'
|
|
||||||
* getTrailingApiVersion('https://api.example.com/v1/chat') // undefined (version not at end)
|
|
||||||
* getTrailingApiVersion('https://gateway.ai.cloudflare.com/v1/xxx/v1beta') // 'v1beta'
|
|
||||||
* getTrailingApiVersion('https://api.example.com') // undefined
|
|
||||||
*/
|
|
||||||
export function getTrailingApiVersion(url: string): string | undefined {
|
|
||||||
const match = url.match(TRAILING_VERSION_REGEX)
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
// Extract version without leading slash and trailing slash
|
|
||||||
return match[0].replace(/^\//, '').replace(/\/$/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the trailing API version segment from a URL path.
|
|
||||||
*
|
|
||||||
* This function removes API version patterns (e.g., `/v1`, `/v2beta`) from the end of a URL.
|
|
||||||
* Only versions at the end of the path are removed, not versions in the middle.
|
|
||||||
*
|
|
||||||
* @param {string} url - The URL string to process.
|
|
||||||
* @returns {string} The URL with the trailing API version removed, or the original URL if no trailing version found.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* withoutTrailingApiVersion('https://api.example.com/v1') // 'https://api.example.com'
|
|
||||||
* withoutTrailingApiVersion('https://api.example.com/v2beta/') // 'https://api.example.com'
|
|
||||||
* withoutTrailingApiVersion('https://api.example.com/v1/chat') // 'https://api.example.com/v1/chat' (no change)
|
|
||||||
* withoutTrailingApiVersion('https://api.example.com') // 'https://api.example.com'
|
|
||||||
*/
|
|
||||||
export function withoutTrailingApiVersion(url: string): string {
|
|
||||||
return url.replace(TRAILING_VERSION_REGEX, '')
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user