| import { BaseExecutor } from "./base.js"; |
| import { PROVIDERS } from "../config/providers.js"; |
| import { parseVertexSaJson, refreshVertexToken, refreshGoogleToken } from "../services/tokenRefresh.js"; |
| import { proxyAwareFetch } from "../utils/proxyFetch.js"; |
|
|
| |
| const projectIdCache = new Map(); |
|
|
| |
| |
| |
| |
| function parseVertexAdcJson(apiKey) { |
| if (typeof apiKey !== "string") return null; |
| try { |
| const parsed = JSON.parse(apiKey); |
| if ( |
| parsed.type === "authorized_user" && |
| parsed.client_id && |
| parsed.client_secret && |
| parsed.refresh_token |
| ) { |
| return parsed; |
| } |
| return null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| async function resolveProjectId(apiKey) { |
| if (projectIdCache.has(apiKey)) return projectIdCache.get(apiKey); |
|
|
| const res = await fetch( |
| `https://aiplatform.googleapis.com/v1/publishers/google/models/__probe__:generateContent?key=${apiKey}`, |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" } |
| ); |
| const json = await res.json().catch(() => null); |
| const msg = json?.[0]?.error?.message || json?.error?.message || ""; |
| const match = msg.match(/projects\/([^/]+)\//); |
| const projectId = match?.[1] || null; |
|
|
| if (projectId) projectIdCache.set(apiKey, projectId); |
| return projectId; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export class VertexExecutor extends BaseExecutor { |
| constructor(providerId = "vertex") { |
| super(providerId, PROVIDERS[providerId] || {}); |
| } |
|
|
| buildUrl(model, stream, urlIndex = 0, credentials = null) { |
| const saJson = parseVertexSaJson(credentials?.apiKey); |
| const adcJson = parseVertexAdcJson(credentials?.apiKey); |
| const usesOAuth = !!saJson || !!adcJson || !!credentials?.accessToken; |
| const rawKey = !usesOAuth ? credentials?.apiKey : null; |
| const projectId = |
| saJson?.project_id || |
| adcJson?.quota_project_id || |
| credentials?.providerSpecificData?.projectId; |
|
|
| if (this.provider === "vertex-partner") { |
| |
| if (!projectId) throw new Error("Vertex partner models require a project_id. Add it in providerSpecificData or use Service Account JSON."); |
| const url = `https://aiplatform.googleapis.com/v1/projects/${projectId}/locations/global/endpoints/openapi/chat/completions`; |
| return rawKey ? `${url}?key=${rawKey}` : url; |
| } |
|
|
| |
| const action = stream ? "streamGenerateContent" : "generateContent"; |
|
|
| if (usesOAuth) { |
| |
| if (!projectId) { |
| throw new Error( |
| "Vertex OAuth/ADC requires a project_id. " + |
| "Add quota_project_id to your ADC JSON or set providerSpecificData.projectId." |
| ); |
| } |
| const location = credentials?.providerSpecificData?.location || "us-central1"; |
| let url = `https://aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:${action}`; |
| if (stream) url += "?alt=sse"; |
| return url; |
| } |
|
|
| |
| |
| let url = `https://aiplatform.googleapis.com/v1/publishers/google/models/${model}:${action}`; |
| if (stream) url += "?alt=sse"; |
| if (rawKey) url += stream ? `&key=${rawKey}` : `?key=${rawKey}`; |
| return url; |
| } |
|
|
| buildHeaders(credentials, stream = true) { |
| const headers = { "Content-Type": "application/json" }; |
|
|
| |
| if (credentials.accessToken) { |
| headers["Authorization"] = `Bearer ${credentials.accessToken}`; |
| } |
|
|
| if (stream) headers["Accept"] = "text/event-stream"; |
|
|
| return headers; |
| } |
|
|
| async refreshCredentials(credentials, log) { |
| const saJson = parseVertexSaJson(credentials?.apiKey); |
| if (!saJson) return null; |
|
|
| const result = await refreshVertexToken(saJson, log); |
| if (!result) return null; |
|
|
| return { accessToken: result.accessToken, expiresAt: result.expiresAt }; |
| } |
|
|
| async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) { |
| const saJson = parseVertexSaJson(credentials?.apiKey); |
| const adcJson = parseVertexAdcJson(credentials?.apiKey); |
|
|
| |
| if (saJson) { |
| const result = await refreshVertexToken(saJson, log); |
| if (!result?.accessToken) throw new Error("Vertex: failed to mint access token from Service Account JSON"); |
| credentials.accessToken = result.accessToken; |
| } |
|
|
| |
| if (adcJson) { |
| const result = await refreshGoogleToken( |
| adcJson.refresh_token, |
| adcJson.client_id, |
| adcJson.client_secret, |
| log |
| ); |
| if (!result?.accessToken) throw new Error("Vertex: failed to refresh access token from ADC JSON (authorized_user)"); |
| credentials.accessToken = result.accessToken; |
| } |
|
|
| |
| if (this.provider === "vertex-partner" && !saJson && !adcJson && !credentials?.providerSpecificData?.projectId) { |
| const projectId = await resolveProjectId(credentials.apiKey); |
| if (!projectId) throw new Error("Vertex: could not resolve project_id from API key. Please add it manually in provider settings."); |
| log?.debug?.("VERTEX", `Resolved project_id: ${projectId}`); |
| credentials.providerSpecificData = { ...credentials.providerSpecificData, projectId }; |
| } |
|
|
| const url = this.buildUrl(model, stream, 0, credentials); |
| const headers = this.buildHeaders(credentials, stream); |
| const transformedBody = this.transformRequest(model, body, stream, credentials); |
|
|
| const response = await proxyAwareFetch(url, { |
| method: "POST", |
| headers, |
| body: JSON.stringify(transformedBody), |
| signal, |
| }, proxyOptions); |
|
|
| return { response, url, headers, transformedBody }; |
| } |
| } |
|
|
| export default VertexExecutor; |
|
|