feat: add patch for Google Vertex AI and enhance private key handling

- Introduced a patch for the @ai-sdk/google-vertex package to improve URL handling based on region.
- Added a new utility function to format private keys, ensuring correct PEM structure and validation.
- Updated the ProviderConfigBuilder to utilize the new private key formatting function for Google credentials.
- Created a pnpm workspace configuration to manage patched dependencies effectively.
This commit is contained in:
suyao 2025-06-21 20:31:24 +08:00
parent 2f58b3360e
commit e421b81fca
No known key found for this signature in database
6 changed files with 128 additions and 4 deletions

View File

@ -0,0 +1,26 @@
diff --git a/edge/dist/index.js b/edge/dist/index.js
index 87cb4e77c6e02c9cc16082f47d9a80878bea1006..8061d52940bfef4f4815051fd09bb700cf0034c7 100644
--- a/edge/dist/index.js
+++ b/edge/dist/index.js
@@ -248,7 +248,7 @@ function createVertex(options = {}) {
var _a;
const region = loadVertexLocation();
const project = loadVertexProject();
- return (_a = (0, import_provider_utils4.withoutTrailingSlash)(options.baseURL)) != null ? _a : `https://${region}-aiplatform.googleapis.com/v1/projects/${project}/locations/${region}/publishers/google`;
+ return (_a = (0, import_provider_utils4.withoutTrailingSlash)(options.baseURL)) != null ? _a : region === "global" ? `https://aiplatform.googleapis.com/v1/projects/${project}/locations/global/publishers/google` : `https://${region}-aiplatform.googleapis.com/v1/projects/${project}/locations/${region}/publishers/google`;
};
const createConfig = (name) => {
var _a;
diff --git a/edge/dist/index.mjs b/edge/dist/index.mjs
index bd4ed39abe78fae60673607ab46241cc29dfa0a2..2e6f36d3668635d93b17660eda18f89972b4ccfd 100644
--- a/edge/dist/index.mjs
+++ b/edge/dist/index.mjs
@@ -238,7 +238,7 @@ function createVertex(options = {}) {
var _a;
const region = loadVertexLocation();
const project = loadVertexProject();
- return (_a = withoutTrailingSlash(options.baseURL)) != null ? _a : `https://${region}-aiplatform.googleapis.com/v1/projects/${project}/locations/${region}/publishers/google`;
+ return (_a = withoutTrailingSlash(options.baseURL)) != null ? _a : region === "global" ? `https://aiplatform.googleapis.com/v1/projects/${project}/locations/global/publishers/google` : `https://${region}-aiplatform.googleapis.com/v1/projects/${project}/locations/${region}/publishers/google`;
};
const createConfig = (name) => {
var _a;

View File

@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
'@ai-sdk/google-vertex':
hash: b7c4a8a2274e90367ea6efb1e3056a9c6c83cb14329086354e8d20db1139e2f1
path: patches/@ai-sdk__google-vertex.patch
importers:
.:
@ -40,7 +45,7 @@ importers:
version: 1.2.19(zod@3.25.67)
'@ai-sdk/google-vertex':
specifier: ^2.2.24
version: 2.2.24(zod@3.25.67)
version: 2.2.24(patch_hash=b7c4a8a2274e90367ea6efb1e3056a9c6c83cb14329086354e8d20db1139e2f1)(zod@3.25.67)
'@ai-sdk/groq':
specifier: ^1.2.9
version: 1.2.9(zod@3.25.67)
@ -612,7 +617,7 @@ snapshots:
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.67)
zod: 3.25.67
'@ai-sdk/google-vertex@2.2.24(zod@3.25.67)':
'@ai-sdk/google-vertex@2.2.24(patch_hash=b7c4a8a2274e90367ea6efb1e3056a9c6c83cb14329086354e8d20db1139e2f1)(zod@3.25.67)':
dependencies:
'@ai-sdk/anthropic': 1.2.12(zod@3.25.67)
'@ai-sdk/google': 1.2.19(zod@3.25.67)

View File

@ -0,0 +1,2 @@
patchedDependencies:
'@ai-sdk/google-vertex': patches/@ai-sdk__google-vertex.patch

View File

@ -4,6 +4,7 @@
*/
import type { ProviderId, ProviderSettingsMap } from './registry'
import { formatPrivateKey } from './utils'
/**
* Provider
@ -141,7 +142,10 @@ export class ProviderConfigBuilder<T extends ProviderId = ProviderId> {
withGoogleCredentials(credentials: any): any {
if (this.providerId === 'google-vertex') {
const vertexConfig = this.config as CompleteProviderConfig<'google-vertex'>
vertexConfig.googleCredentials = credentials
vertexConfig.googleCredentials = {
clientEmail: credentials.clientEmail,
privateKey: formatPrivateKey(credentials.privateKey)
}
}
return this
}

View File

@ -0,0 +1,86 @@
/**
* PEM头部和尾部
*/
export function formatPrivateKey(privateKey: string): string {
if (!privateKey || typeof privateKey !== 'string') {
throw new Error('Private key must be a non-empty string')
}
// 先处理 JSON 字符串中的转义换行符
const key = privateKey.replace(/\\n/g, '\n')
// 检查是否已经是正确格式的 PEM 私钥
const hasBeginMarker = key.includes('-----BEGIN PRIVATE KEY-----')
const hasEndMarker = key.includes('-----END PRIVATE KEY-----')
if (hasBeginMarker && hasEndMarker) {
// 已经是 PEM 格式,但可能格式不规范,重新格式化
return normalizePemFormat(key)
}
// 如果没有完整的 PEM 头尾,尝试重新构建
return reconstructPemKey(key)
}
/**
* PEM
*/
function normalizePemFormat(pemKey: string): string {
// 分离头部、内容和尾部
const lines = pemKey
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
let keyContent = ''
let foundBegin = false
let foundEnd = false
for (const line of lines) {
if (line === '-----BEGIN PRIVATE KEY-----') {
foundBegin = true
continue
}
if (line === '-----END PRIVATE KEY-----') {
foundEnd = true
break
}
if (foundBegin && !foundEnd) {
keyContent += line
}
}
if (!foundBegin || !foundEnd || !keyContent) {
throw new Error('Invalid PEM format: missing BEGIN/END markers or key content')
}
// 重新格式化为 64 字符一行
const formattedContent = keyContent.match(/.{1,64}/g)?.join('\n') || keyContent
return `-----BEGIN PRIVATE KEY-----\n${formattedContent}\n-----END PRIVATE KEY-----`
}
/**
* PEM
*/
function reconstructPemKey(key: string): string {
// 移除所有空白字符和可能存在的不完整头尾
let cleanKey = key.replace(/\s+/g, '')
cleanKey = cleanKey.replace(/-----BEGIN[^-]*-----/g, '')
cleanKey = cleanKey.replace(/-----END[^-]*-----/g, '')
// 确保私钥内容不为空
if (!cleanKey) {
throw new Error('Private key content is empty after cleaning')
}
// 验证是否是有效的 Base64 字符
if (!/^[A-Za-z0-9+/=]+$/.test(cleanKey)) {
throw new Error('Private key contains invalid characters (not valid Base64)')
}
// 格式化为 64 字符一行
const formattedKey = cleanKey.match(/.{1,64}/g)?.join('\n') || cleanKey
return `-----BEGIN PRIVATE KEY-----\n${formattedKey}\n-----END PRIVATE KEY-----`
}

View File

@ -241,7 +241,7 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:0.2.14":
"@ai-sdk/openai-compatible@npm:0.2.14, @ai-sdk/openai-compatible@npm:^0.2.14":
version: 0.2.14
resolution: "@ai-sdk/openai-compatible@npm:0.2.14"
dependencies:
@ -944,6 +944,7 @@ __metadata:
"@ai-sdk/groq": "npm:^1.2.9"
"@ai-sdk/mistral": "npm:^1.2.8"
"@ai-sdk/openai": "npm:^1.3.22"
"@ai-sdk/openai-compatible": "npm:^0.2.14"
"@ai-sdk/perplexity": "npm:^1.1.9"
"@ai-sdk/replicate": "npm:^0.2.8"
"@ai-sdk/togetherai": "npm:^0.2.14"