Skip to content

Commit 70cbaf4

Browse files
committed
cr
1 parent a2e3835 commit 70cbaf4

File tree

9 files changed

+1154
-173
lines changed

9 files changed

+1154
-173
lines changed

libs/providers/langchain-google/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
},
3232
"dependencies": {
3333
"eventsource-parser": "^3.0.6",
34-
"google-auth-library": "^10.5.0"
34+
"google-auth-library": "^10.5.0",
35+
"jose": "^6.1.2"
3536
},
3637
"peerDependencies": {
3738
"@langchain/core": "^1.0.0"
Lines changed: 241 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,269 @@
11
import { getEnvironmentVariable } from "@langchain/core/utils/env";
2-
import { GOOGLE_API_KEY_HEADER } from "../const.js";
2+
import { GCP_API_KEY_HEADER, GOOGLE_API_KEY_HEADER } from "../const.js";
3+
import {
4+
GCPCredentials,
5+
getGCPCredentialsAccessToken,
6+
normalizeGCPCredentials,
7+
} from "../utils/gcp-auth.js";
8+
import { iife } from "../utils/misc.js";
39

10+
/**
11+
* Abstract base class for API clients that interact with Google services.
12+
*
13+
* This class defines the contract for API clients, requiring implementations
14+
* to provide a fetch method that handles HTTP requests with appropriate
15+
* authentication headers. Concrete implementations should handle different
16+
* authentication strategies (API keys, service account credentials, etc.)
17+
* based on the runtime environment.
18+
*
19+
* @abstract
20+
*
21+
* @example
22+
* ```typescript
23+
* class MyApiClient extends ApiClient {
24+
* async fetch(request: Request): Promise<Response> {
25+
* // Add authentication headers
26+
* request.headers.set('Authorization', 'Bearer token');
27+
* return fetch(request);
28+
* }
29+
* }
30+
* ```
31+
*/
432
export abstract class ApiClient {
33+
/**
34+
* Executes an HTTP request with appropriate authentication.
35+
*
36+
* Implementations should add necessary authentication headers to the request
37+
* before forwarding it to the underlying fetch implementation.
38+
*
39+
* @param request - The HTTP request to execute
40+
* @returns A Promise that resolves to the HTTP response
41+
*
42+
* @abstract
43+
*/
544
abstract fetch(request: Request): Promise<Response>;
645
}
746

47+
/**
48+
* Configuration parameters for the WebApiClient.
49+
*
50+
* This interface defines the authentication options available for web-based
51+
* API clients. Authentication can be provided via an API key or GCP service
52+
* account credentials. The client will automatically check environment variables
53+
* if explicit values are not provided.
54+
*
55+
* @interface
56+
*/
857
export interface WebApiClientParams {
58+
/**
59+
* Google API key for authentication.
60+
*
61+
* If not provided, the client will attempt to read from the
62+
* `GOOGLE_API_KEY` environment variable.
63+
*
64+
* @see https://cloud.google.com/docs/authentication/api-keys
65+
*/
966
apiKey?: string;
67+
1068
/**
11-
* @deprecated Import from `@langchain/google/node` to configure google auth options
69+
* GCP service account credentials for authentication.
70+
*
71+
* Can be provided as either:
72+
* - A JSON string containing the service account key
73+
* - A GCPCredentials object
74+
*
75+
* If not provided, the client will attempt to read from the
76+
* `GOOGLE_CLOUD_CREDENTIALS` environment variable.
77+
*
78+
* @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys
79+
*/
80+
credentials?: string | GCPCredentials;
81+
82+
/**
83+
* Google Auth options configuration.
84+
*
85+
* @deprecated This option is not supported in web environments.
86+
* Import from `@langchain/google/node` to configure google auth options
87+
* using the NodeApiClient instead.
1288
*/
1389
googleAuthOptions?: never;
1490
}
1591

92+
/**
93+
* Web-compatible API client for Google services.
94+
*
95+
* This client provides authentication for Google API requests in web environments
96+
* (browsers, edge functions, etc.) where the google-auth-library is not available.
97+
* It supports two authentication methods:
98+
*
99+
* 1. **API Key Authentication**: Simple key-based authentication suitable for
100+
* public APIs and client-side applications.
101+
*
102+
* 2. **Service Account Credentials**: OAuth2-based authentication using GCP
103+
* service account keys. The client automatically handles token generation
104+
* and caching.
105+
*
106+
* The client follows this authentication priority:
107+
* 1. Explicit `apiKey` parameter
108+
* 2. `GOOGLE_API_KEY` environment variable
109+
* 3. Explicit `credentials` parameter
110+
* 4. `GOOGLE_CLOUD_CREDENTIALS` environment variable
111+
*
112+
* @extends ApiClient
113+
*
114+
* @example
115+
* ```typescript
116+
* // Using API key
117+
* const client = new WebApiClient({
118+
* apiKey: 'your-api-key'
119+
* });
120+
*
121+
* // Using service account credentials
122+
* const client = new WebApiClient({
123+
* credentials: {
124+
* type: "service_account",
125+
* project_id: "my-project",
126+
* private_key_id: "key-id",
127+
* private_key: "-----BEGIN PRIVATE KEY-----\n...",
128+
* client_email: "service-account@project.iam.gserviceaccount.com",
129+
* // ... other required fields
130+
* }
131+
* });
132+
*
133+
* // Using environment variables
134+
* // Set GOOGLE_API_KEY or GOOGLE_CLOUD_CREDENTIALS
135+
* const client = new WebApiClient({});
136+
*
137+
* // Make authenticated requests
138+
* const request = new Request('https://api.google.com/...');
139+
* const response = await client.fetch(request);
140+
* ```
141+
*
142+
* @remarks
143+
* - API keys are added via the `X-Goog-Api-Key` header
144+
* - Service account tokens are added via the `Authorization` header with Bearer scheme
145+
* - Tokens are automatically cached and refreshed as needed
146+
* - For Node.js environments with advanced auth requirements, use NodeApiClient instead
147+
*
148+
* @see {@link NodeApiClient} for Node.js-specific authentication options
149+
* @see {@link GCPCredentials} for service account credential structure
150+
*/
16151
export class WebApiClient extends ApiClient {
152+
/**
153+
* The Google API key used for authentication, if provided.
154+
*
155+
* This value is resolved from either the constructor parameter or the
156+
* `GOOGLE_API_KEY` environment variable.
157+
*
158+
* @protected
159+
*/
17160
protected apiKey?: string;
18161

162+
/**
163+
* The normalized GCP service account credentials, if provided.
164+
*
165+
* This value is resolved from either the constructor parameter or the
166+
* `GOOGLE_CLOUD_CREDENTIALS` environment variable, and is normalized
167+
* to ensure immutability.
168+
*
169+
* @protected
170+
*/
171+
protected credentials?: GCPCredentials;
172+
173+
/**
174+
* Creates a new WebApiClient instance.
175+
*
176+
* The constructor initializes authentication credentials by checking:
177+
* 1. Explicit parameters passed to the constructor
178+
* 2. Environment variables (`GOOGLE_API_KEY`, `GOOGLE_CLOUD_CREDENTIALS`)
179+
*
180+
* Credentials are normalized and frozen to prevent accidental modification.
181+
*
182+
* @param params - Configuration parameters for the client
183+
*
184+
* @example
185+
* ```typescript
186+
* // Minimal configuration (uses environment variables)
187+
* const client = new WebApiClient({});
188+
*
189+
* // With explicit API key
190+
* const client = new WebApiClient({
191+
* apiKey: 'your-api-key'
192+
* });
193+
*
194+
* // With service account credentials as JSON string
195+
* const client = new WebApiClient({
196+
* credentials: '{"type":"service_account",...}'
197+
* });
198+
*
199+
* // With service account credentials as object
200+
* const client = new WebApiClient({
201+
* credentials: {
202+
* type: "service_account",
203+
* // ... other fields
204+
* }
205+
* });
206+
* ```
207+
*/
19208
constructor(protected params: WebApiClientParams) {
20209
super();
21210
this.apiKey = params.apiKey ?? getEnvironmentVariable("GOOGLE_API_KEY");
211+
this.credentials = iife(() => {
212+
if (params.credentials) {
213+
return normalizeGCPCredentials(params.credentials);
214+
}
215+
const credentialsVar = getEnvironmentVariable("GOOGLE_CLOUD_CREDENTIALS");
216+
if (credentialsVar) {
217+
return normalizeGCPCredentials(credentialsVar);
218+
}
219+
return undefined;
220+
});
22221
}
23222

223+
/**
224+
* Executes an HTTP request with appropriate Google API authentication.
225+
*
226+
* This method adds authentication headers to the request based on the
227+
* configured authentication method:
228+
*
229+
* - If an API key is configured, adds the `X-Goog-Api-Key` header
230+
* - If service account credentials are configured, generates an access token
231+
* and adds it via the `Authorization` header with Bearer scheme
232+
*
233+
* The authentication priority is:
234+
* 1. API key (if provided)
235+
* 2. Service account credentials (if provided)
236+
*
237+
* @param request - The HTTP request to execute. Headers will be modified
238+
* in-place to include authentication information.
239+
* @returns A Promise that resolves to the HTTP response
240+
*
241+
* @throws {AuthError} If service account authentication fails (e.g., invalid
242+
* credentials, network error, or token generation failure)
243+
*
244+
* @example
245+
* ```typescript
246+
* const client = new WebApiClient({ apiKey: 'your-key' });
247+
* const request = new Request('https://generativelanguage.googleapis.com/v1/models');
248+
* const response = await client.fetch(request);
249+
* const data = await response.json();
250+
* ```
251+
*
252+
* @remarks
253+
* - The request object is modified in-place by adding headers
254+
* - Service account tokens are automatically cached and refreshed
255+
* - If both API key and credentials are provided, API key takes precedence
256+
*/
24257
fetch(request: Request): Promise<Response> {
25258
if (this.params.apiKey) {
26259
request.headers.set(GOOGLE_API_KEY_HEADER, this.params.apiKey);
27260
}
261+
if (this.credentials) {
262+
request.headers.set(
263+
GCP_API_KEY_HEADER,
264+
`Bearer ${getGCPCredentialsAccessToken(this.credentials)}`
265+
);
266+
}
28267
return fetch(request);
29268
}
30269
}

0 commit comments

Comments
 (0)