From e421b81fcaf72d3c17dd23ee4e35933411ec6d93 Mon Sep 17 00:00:00 2001 From: suyao Date: Sat, 21 Jun 2025 20:31:24 +0800 Subject: [PATCH] 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. --- .../patches/@ai-sdk__google-vertex.patch | 26 ++++++ packages/aiCore/pnpm-lock.yaml | 9 +- packages/aiCore/pnpm-workspace.yaml | 2 + packages/aiCore/src/providers/factory.ts | 6 +- packages/aiCore/src/providers/utils.ts | 86 +++++++++++++++++++ yarn.lock | 3 +- 6 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 packages/aiCore/patches/@ai-sdk__google-vertex.patch create mode 100644 packages/aiCore/pnpm-workspace.yaml create mode 100644 packages/aiCore/src/providers/utils.ts diff --git a/packages/aiCore/patches/@ai-sdk__google-vertex.patch b/packages/aiCore/patches/@ai-sdk__google-vertex.patch new file mode 100644 index 0000000000..ba9d839d6b --- /dev/null +++ b/packages/aiCore/patches/@ai-sdk__google-vertex.patch @@ -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; diff --git a/packages/aiCore/pnpm-lock.yaml b/packages/aiCore/pnpm-lock.yaml index 6d16b5d833..929b55d666 100644 --- a/packages/aiCore/pnpm-lock.yaml +++ b/packages/aiCore/pnpm-lock.yaml @@ -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) diff --git a/packages/aiCore/pnpm-workspace.yaml b/packages/aiCore/pnpm-workspace.yaml new file mode 100644 index 0000000000..e9ddfafb21 --- /dev/null +++ b/packages/aiCore/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +patchedDependencies: + '@ai-sdk/google-vertex': patches/@ai-sdk__google-vertex.patch diff --git a/packages/aiCore/src/providers/factory.ts b/packages/aiCore/src/providers/factory.ts index 1a90531b4d..bbd914373d 100644 --- a/packages/aiCore/src/providers/factory.ts +++ b/packages/aiCore/src/providers/factory.ts @@ -4,6 +4,7 @@ */ import type { ProviderId, ProviderSettingsMap } from './registry' +import { formatPrivateKey } from './utils' /** * 通用配置基础类型,包含所有 Provider 共有的属性 @@ -141,7 +142,10 @@ export class ProviderConfigBuilder { 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 } diff --git a/packages/aiCore/src/providers/utils.ts b/packages/aiCore/src/providers/utils.ts new file mode 100644 index 0000000000..08f08fa5d1 --- /dev/null +++ b/packages/aiCore/src/providers/utils.ts @@ -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-----` +} diff --git a/yarn.lock b/yarn.lock index 778a66d8ad..b3d6bf6e57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"