11// Google plugin module implements vertex adc behavior.
2- import { existsSync , readFileSync } from "node:fs" ;
3- import { readFile } from "node:fs/promises" ;
2+ import { existsSync } from "node:fs" ;
43import os from "node:os" ;
54import path from "node:path" ;
65import { gunzipSync } from "node:zlib" ;
6+ import type { GoogleAuthOptions } from "google-auth-library" ;
77import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared" ;
88import {
99 asDateTimestampMs ,
1010 resolveExpiresAtMsFromDurationMs ,
1111 resolveExpiresAtMsFromDurationSeconds ,
1212} from "openclaw/plugin-sdk/number-runtime" ;
1313import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime" ;
14+ import { readSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime" ;
1415import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime" ;
1516import { withTimeout } from "openclaw/plugin-sdk/text-utility-runtime" ;
1617
@@ -149,19 +150,25 @@ function resolveGoogleApplicationCredentialsPath(
149150 return existsSync ( appDataFallback ) ? appDataFallback : undefined ;
150151}
151152
152- async function readGoogleAuthorizedUserCredentials (
153- credentialsPath : string ,
154- ) : Promise < GoogleAuthorizedUserCredentials | undefined > {
155- let parsed : unknown ;
156- try {
157- parsed = JSON . parse ( await readFile ( credentialsPath , "utf8" ) ) as unknown ;
158- } catch {
159- return undefined ;
160- }
153+ type GoogleAdcConfig = NonNullable < GoogleAuthOptions [ "credentials" ] > ;
154+ const GOOGLE_VERTEX_ADC_FILE_MAX_BYTES = 1024 * 1024 ;
155+
156+ function readGoogleAdcCredentials ( adcPath : string ) : GoogleAdcConfig {
157+ const text = readSecretFileSync ( adcPath , "Google Vertex ADC credentials" , {
158+ maxBytes : GOOGLE_VERTEX_ADC_FILE_MAX_BYTES ,
159+ rejectHardlinks : false ,
160+ } ) ;
161+ const parsed = JSON . parse ( text ) as unknown ;
161162 if ( ! parsed || typeof parsed !== "object" || Array . isArray ( parsed ) ) {
162- return undefined ;
163+ throw new Error ( `Google Vertex ADC credentials must be a JSON object: ${ adcPath } ` ) ;
163164 }
164- const record = parsed as Record < string , unknown > ;
165+ return parsed as GoogleAdcConfig ;
166+ }
167+
168+ function resolveGoogleAuthorizedUserCredentials (
169+ adcConfig : GoogleAdcConfig ,
170+ ) : GoogleAuthorizedUserCredentials | undefined {
171+ const record = adcConfig as Record < string , unknown > ;
165172 if ( record . type !== "authorized_user" ) {
166173 return undefined ;
167174 }
@@ -175,11 +182,7 @@ async function readGoogleAuthorizedUserCredentials(
175182
176183function readGoogleAdcCredentialsTypeSync ( credentialsPath : string ) : string | undefined {
177184 try {
178- const parsed = JSON . parse ( readFileSync ( credentialsPath , "utf8" ) ) as unknown ;
179- if ( ! parsed || typeof parsed !== "object" || Array . isArray ( parsed ) ) {
180- return undefined ;
181- }
182- const type = ( parsed as { type ?: unknown } ) . type ;
185+ const type = ( readGoogleAdcCredentials ( credentialsPath ) as { type ?: unknown } ) . type ;
183186 return typeof type === "string" ? type : undefined ;
184187 } catch {
185188 return undefined ;
@@ -353,7 +356,9 @@ function shouldGunzipGoogleOauthTokenResponse(
353356 . includes ( "gzip" ) ;
354357}
355358
356- async function resolveGoogleVertexAccessTokenViaGoogleAuth ( ) : Promise < string > {
359+ async function resolveGoogleVertexAccessTokenViaGoogleAuth (
360+ adcConfig ?: GoogleAdcConfig ,
361+ ) : Promise < string > {
357362 // Lazy-import + cache so we don't pay the google-auth-library load cost on
358363 // gateway startup; only when we actually need a non-authorized_user token.
359364 if ( ! cachedGoogleAuthClient ) {
@@ -367,6 +372,7 @@ async function resolveGoogleVertexAccessTokenViaGoogleAuth(): Promise<string> {
367372 // It also caches tokens internally and refreshes before expiry.
368373 return new GoogleAuth ( {
369374 scopes : [ GOOGLE_VERTEX_OAUTH_SCOPE ] ,
375+ ...( adcConfig ? { credentials : adcConfig } : { } ) ,
370376 // Best-effort cancellation for clients that use the shared transporter.
371377 // WIF STS and GCE metadata need the owner-level deadline below.
372378 clientOptions : {
@@ -442,13 +448,15 @@ async function resolveGoogleVertexAccessTokenViaGoogleAuth(): Promise<string> {
442448export async function resolveGoogleVertexAuthorizedUserHeaders (
443449 fetchImpl ?: typeof fetch ,
444450) : Promise < Record < string , string > > {
445- const credentialsPath = resolveGoogleApplicationCredentialsPath ( ) ;
446- if ( credentialsPath ) {
447- const credentials = await readGoogleAuthorizedUserCredentials ( credentialsPath ) ;
448- if ( credentials ) {
451+ const adcPath = resolveGoogleApplicationCredentialsPath ( ) ;
452+ let adcConfig : GoogleAdcConfig | undefined ;
453+ if ( adcPath ) {
454+ adcConfig = readGoogleAdcCredentials ( adcPath ) ;
455+ const userAdc = resolveGoogleAuthorizedUserCredentials ( adcConfig ) ;
456+ if ( userAdc ) {
449457 const token = await refreshGoogleVertexAuthorizedUserAccessToken ( {
450- credentialsPath,
451- credentials,
458+ credentialsPath : adcPath ,
459+ credentials : userAdc ,
452460 fetchImpl,
453461 } ) ;
454462 return { Authorization : `Bearer ${ token } ` } ;
@@ -457,6 +465,6 @@ export async function resolveGoogleVertexAuthorizedUserHeaders(
457465 // No file-based authorized_user ADC. Fall back to google-auth-library which
458466 // handles GKE Workload Identity (metadata server), Workload Identity
459467 // Federation (external_account), and service-account keys.
460- const token = await resolveGoogleVertexAccessTokenViaGoogleAuth ( ) ;
468+ const token = await resolveGoogleVertexAccessTokenViaGoogleAuth ( adcConfig ) ;
461469 return { Authorization : `Bearer ${ token } ` } ;
462470}
0 commit comments