@contentgrowth/llm-service 1.3.8 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -1
- package/dist/index.cjs +234 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -38
- package/dist/index.d.ts +36 -38
- package/dist/index.js +234 -37
- package/dist/index.js.map +1 -1
- package/dist/utils/google-auth.cjs +257 -0
- package/dist/utils/google-auth.cjs.map +1 -0
- package/dist/utils/google-auth.d.cts +151 -0
- package/dist/utils/google-auth.d.ts +151 -0
- package/dist/utils/google-auth.js +230 -0
- package/dist/utils/google-auth.js.map +1 -0
- package/dist/utils/index.cjs +259 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +230 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ VERTEX_LOCATION=us-central1
|
|
|
124
124
|
VERTEX_MODEL=gemini-3.5-flash
|
|
125
125
|
VERTEX_MODEL_EDGE=gemini-3.5-pro
|
|
126
126
|
VERTEX_MODEL_FAST=gemini-3.5-flash
|
|
127
|
-
VERTEX_IMAGE_MODEL=gemini-
|
|
127
|
+
VERTEX_IMAGE_MODEL=gemini-nano-banana-2.1
|
|
128
128
|
VERTEX_VIDEO_MODEL=veo
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -409,6 +409,71 @@ await chatCompletion(messages, tenant, prompt, tools, { responseFormat: 'json' }
|
|
|
409
409
|
|
|
410
410
|
## Testing
|
|
411
411
|
|
|
412
|
+
### Google Cloud Authentication Utilities
|
|
413
|
+
|
|
414
|
+
The package exports utilities for working with Google Cloud service accounts in edge runtime environments (Cloudflare Workers, Vercel Edge, etc.) where traditional ADC (Application Default Credentials) is not available.
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
import {
|
|
418
|
+
parseGoogleCredentials,
|
|
419
|
+
GoogleTokenManager,
|
|
420
|
+
getGoogleOAuth2Token
|
|
421
|
+
} from '@contentgrowth/llm-service/utils/google';
|
|
422
|
+
|
|
423
|
+
// Parse service account from various formats
|
|
424
|
+
const sa = parseGoogleCredentials(jsonString); // or base64 or object
|
|
425
|
+
|
|
426
|
+
// Create token manager for automatic refresh
|
|
427
|
+
const tokenManager = new GoogleTokenManager(sa);
|
|
428
|
+
|
|
429
|
+
// Get token (cached or fresh)
|
|
430
|
+
const token = await tokenManager.getToken();
|
|
431
|
+
|
|
432
|
+
// Check if token is still valid
|
|
433
|
+
if (tokenManager.isValid()) {
|
|
434
|
+
console.log(`Token valid for ${tokenManager.getTimeToExpiry()} seconds`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Force refresh
|
|
438
|
+
tokenManager.invalidate();
|
|
439
|
+
|
|
440
|
+
// Or get token directly (no caching)
|
|
441
|
+
const freshToken = await getGoogleOAuth2Token(sa);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Using with llm-service
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
import { LLMService } from '@contentgrowth/llm-service';
|
|
448
|
+
import { parseGoogleCredentials, GoogleTokenManager } from '@contentgrowth/llm-service/utils/google';
|
|
449
|
+
|
|
450
|
+
// Backend manages token lifecycle
|
|
451
|
+
const sa = parseGoogleCredentials(serviceAccountJson);
|
|
452
|
+
const tokenManager = new GoogleTokenManager(sa);
|
|
453
|
+
const token = await tokenManager.getToken();
|
|
454
|
+
|
|
455
|
+
// Pass pre-generated token to llm-service
|
|
456
|
+
const llmService = new LLMService(env, {
|
|
457
|
+
provider: 'vertex',
|
|
458
|
+
accessToken: token, // Pre-generated token
|
|
459
|
+
project: sa.project_id,
|
|
460
|
+
location: 'us-central1',
|
|
461
|
+
models: {
|
|
462
|
+
default: 'gemini-3.8-flash',
|
|
463
|
+
image: 'gemini-nano-banana-2.1'
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Use normally - llm-service will use the token
|
|
468
|
+
const response = await llmService.chat('Hello!', 'tenant-id');
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Benefits of this approach:**
|
|
472
|
+
- Backend controls token lifecycle and caching
|
|
473
|
+
- Service account credentials stay in backend
|
|
474
|
+
- llm-service receives only the token
|
|
475
|
+
- Works in edge runtime environments
|
|
476
|
+
|
|
412
477
|
### Running JSON Mode Tests
|
|
413
478
|
|
|
414
479
|
1. **Create `.env` file** (copy from `.env.example`):
|
package/dist/index.cjs
CHANGED
|
@@ -250,7 +250,7 @@ var MODEL_CONFIGS = {
|
|
|
250
250
|
live: "gemini-3.8-live",
|
|
251
251
|
liveThinking: "gemini-3.8-live-extended-thinking",
|
|
252
252
|
video: "veo",
|
|
253
|
-
image: "gemini-
|
|
253
|
+
image: "gemini-nano-banana-2.1"
|
|
254
254
|
},
|
|
255
255
|
// Vertex AI / Gemini Enterprise Agent Platform
|
|
256
256
|
vertex: {
|
|
@@ -262,7 +262,7 @@ var MODEL_CONFIGS = {
|
|
|
262
262
|
live: "gemini-3.8-live",
|
|
263
263
|
liveThinking: "gemini-3.8-live-extended-thinking",
|
|
264
264
|
video: "veo",
|
|
265
|
-
image: "gemini-
|
|
265
|
+
image: "gemini-nano-banana-2.1"
|
|
266
266
|
},
|
|
267
267
|
// Anthropic IDs are "claude-<family>-<major>-<minor>", e.g. claude-sonnet-5-5 (not claude-5.5-sonnet).
|
|
268
268
|
anthropic: {
|
|
@@ -277,11 +277,16 @@ var LEGACY_MODEL_ALIASES = {
|
|
|
277
277
|
// OpenAI: ChatGPT product names, not API model IDs
|
|
278
278
|
"gpt-5.5-instant": "gpt-5.6-luna",
|
|
279
279
|
"gpt-5.5-thinking": "gpt-5.6-sol",
|
|
280
|
-
// Gemini: never existed
|
|
280
|
+
// Gemini: never existed or were preview names
|
|
281
281
|
"gemini-3.5-pro": "gemini-3.1-pro-preview",
|
|
282
282
|
"gemini-3.7-pro": "gemini-3.1-pro-preview",
|
|
283
283
|
"gemini-3.5-pro-image-preview": "gemini-3-pro-image",
|
|
284
284
|
"gemini-3.7-pro-image-preview": "gemini-3-pro-image",
|
|
285
|
+
// Old Gemini image models (pre-Nano Banana branding)
|
|
286
|
+
"gemini-3-pro-image": "gemini-nano-banana-2.1",
|
|
287
|
+
"gemini-3.1-flash-image": "gemini-nano-banana-2.1",
|
|
288
|
+
"gemini-3.1-flash-lite-image": "gemini-nano-banana-2.1",
|
|
289
|
+
"gemini-2.5-flash-image": "gemini-nano-banana-2.1",
|
|
285
290
|
// Anthropic: wrong segment order
|
|
286
291
|
"claude-4.5-haiku": "claude-haiku-4-5",
|
|
287
292
|
"claude-4.6-sonnet": "claude-sonnet-4-6",
|
|
@@ -742,6 +747,14 @@ var GoogleProvider = class extends BaseLLMProvider {
|
|
|
742
747
|
this.models = normalizeModelMap(config.models);
|
|
743
748
|
this.defaultModel = this.models.default;
|
|
744
749
|
this._pendingOperations = /* @__PURE__ */ new Map();
|
|
750
|
+
if (config.accessToken) {
|
|
751
|
+
this.accessToken = config.accessToken;
|
|
752
|
+
this.project = config.project;
|
|
753
|
+
this.location = config.location || "global";
|
|
754
|
+
this.useDirectAPI = true;
|
|
755
|
+
console.log(`[GoogleProvider] Using pre-generated access token for project: ${this.project}`);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
745
758
|
const httpOptions = config.baseURL ? { baseUrl: config.baseURL } : void 0;
|
|
746
759
|
if (config.provider === "vertex") {
|
|
747
760
|
if (config.apiKey) {
|
|
@@ -775,8 +788,65 @@ var GoogleProvider = class extends BaseLLMProvider {
|
|
|
775
788
|
* the client was constructed.
|
|
776
789
|
*/
|
|
777
790
|
async _generateContent(requestOptions) {
|
|
791
|
+
if (this.useDirectAPI && this.accessToken) {
|
|
792
|
+
return this._directVertexAPICall(requestOptions);
|
|
793
|
+
}
|
|
778
794
|
return this.client.models.generateContent(requestOptions);
|
|
779
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Direct Vertex AI API call using pre-generated access token
|
|
798
|
+
* @private
|
|
799
|
+
*/
|
|
800
|
+
async _directVertexAPICall(requestOptions) {
|
|
801
|
+
var _a, _b, _c;
|
|
802
|
+
const modelPath = `projects/${this.project}/locations/${this.location}/publishers/google/models/${requestOptions.model}`;
|
|
803
|
+
const endpoint = `https://${this.location}-aiplatform.googleapis.com/v1/${modelPath}:generateContent`;
|
|
804
|
+
const body = {
|
|
805
|
+
contents: requestOptions.contents,
|
|
806
|
+
generationConfig: requestOptions.config
|
|
807
|
+
};
|
|
808
|
+
if ((_a = requestOptions.config) == null ? void 0 : _a.systemInstruction) {
|
|
809
|
+
body.systemInstruction = requestOptions.config.systemInstruction;
|
|
810
|
+
}
|
|
811
|
+
if ((_b = requestOptions.config) == null ? void 0 : _b.tools) {
|
|
812
|
+
body.tools = requestOptions.config.tools;
|
|
813
|
+
}
|
|
814
|
+
const response = await fetch(endpoint, {
|
|
815
|
+
method: "POST",
|
|
816
|
+
headers: {
|
|
817
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
818
|
+
"Content-Type": "application/json"
|
|
819
|
+
},
|
|
820
|
+
body: JSON.stringify(body)
|
|
821
|
+
});
|
|
822
|
+
if (!response.ok) {
|
|
823
|
+
const errorText = await response.text();
|
|
824
|
+
let errorData;
|
|
825
|
+
try {
|
|
826
|
+
errorData = JSON.parse(errorText);
|
|
827
|
+
} catch (e) {
|
|
828
|
+
errorData = { message: errorText };
|
|
829
|
+
}
|
|
830
|
+
const errorMessage = ((_c = errorData.error) == null ? void 0 : _c.message) || errorData.message || `Vertex API error: ${response.status}`;
|
|
831
|
+
throw new LLMServiceException(errorMessage, response.status);
|
|
832
|
+
}
|
|
833
|
+
return response.json();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Determine authentication mode from config for logging/debugging
|
|
837
|
+
* @private
|
|
838
|
+
* @returns {string} Auth mode description
|
|
839
|
+
*/
|
|
840
|
+
_getAuthMode() {
|
|
841
|
+
if (this.config.provider === "vertex") {
|
|
842
|
+
if (this.config.apiKey) {
|
|
843
|
+
return "Vertex Express (API Key)";
|
|
844
|
+
} else {
|
|
845
|
+
return "Vertex ADC (Service Account)";
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return "Gemini AI Studio";
|
|
849
|
+
}
|
|
780
850
|
async chat(userMessage, systemPrompt = "", options = {}) {
|
|
781
851
|
const messages = [{ role: "user", content: userMessage }];
|
|
782
852
|
const tier = options.tier || "default";
|
|
@@ -1123,15 +1193,22 @@ ${msg.content}`;
|
|
|
1123
1193
|
}
|
|
1124
1194
|
async imageGeneration(prompt, systemPrompt, options = {}) {
|
|
1125
1195
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1126
|
-
const modelName = normalizeModelId(options.model) || this.models.image || "gemini-
|
|
1127
|
-
|
|
1196
|
+
const modelName = normalizeModelId(options.model) || this.models.image || "gemini-nano-banana-2.1";
|
|
1197
|
+
if (modelName.startsWith("imagen-") || modelName.startsWith("imagegeneration@")) {
|
|
1198
|
+
throw new LLMServiceException(
|
|
1199
|
+
`Imagen models were shut down August 17, 2026. Requested: "${modelName}". Use Gemini image models instead (e.g., "gemini-nano-banana-2.1").`,
|
|
1200
|
+
400
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
console.log(`[${this.constructor.name}] Generating image with model: ${modelName} (${this._getAuthMode()})`);
|
|
1128
1204
|
const hasReferenceImages = options.images && options.images.length > 0;
|
|
1129
1205
|
const generationConfig = {
|
|
1130
1206
|
responseModalities: hasReferenceImages ? ["TEXT", "IMAGE"] : ["IMAGE"]
|
|
1131
1207
|
};
|
|
1132
|
-
if (options.aspectRatio) {
|
|
1208
|
+
if (options.aspectRatio || options.thinkingLevel) {
|
|
1133
1209
|
generationConfig.imageConfig = {
|
|
1134
|
-
aspectRatio: options.aspectRatio
|
|
1210
|
+
aspectRatio: options.aspectRatio || "1:1",
|
|
1211
|
+
...options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}
|
|
1135
1212
|
};
|
|
1136
1213
|
}
|
|
1137
1214
|
const parts = [{ text: prompt }];
|
|
@@ -2024,53 +2101,173 @@ function sanitizeError(error, context = "general") {
|
|
|
2024
2101
|
var import_openai2 = __toESM(require("openai"), 1);
|
|
2025
2102
|
|
|
2026
2103
|
// src/utils/google-auth.js
|
|
2027
|
-
var
|
|
2028
|
-
|
|
2104
|
+
var import_node_buffer = require("buffer");
|
|
2105
|
+
function validateServiceAccount(sa) {
|
|
2106
|
+
if (!sa || typeof sa !== "object") {
|
|
2107
|
+
throw new Error("Service account must be an object");
|
|
2108
|
+
}
|
|
2109
|
+
if (!sa.client_email) {
|
|
2110
|
+
throw new Error("Service account missing client_email");
|
|
2111
|
+
}
|
|
2112
|
+
if (!sa.private_key) {
|
|
2113
|
+
throw new Error("Service account missing private_key");
|
|
2114
|
+
}
|
|
2115
|
+
if (!sa.project_id) {
|
|
2116
|
+
console.warn("[GoogleAuth] Service account missing project_id - may need to specify project explicitly");
|
|
2117
|
+
}
|
|
2118
|
+
return true;
|
|
2119
|
+
}
|
|
2120
|
+
var GoogleTokenManager = class {
|
|
2029
2121
|
/**
|
|
2030
|
-
* @param {
|
|
2122
|
+
* @param {object} serviceAccount - Parsed service account with client_email and private_key
|
|
2123
|
+
* @param {object} options - Optional configuration
|
|
2124
|
+
* @param {string} options.scope - OAuth2 scope (default: cloud-platform)
|
|
2125
|
+
* @param {number} options.tokenLifetime - Token lifetime in seconds (default: 3600)
|
|
2031
2126
|
*/
|
|
2032
|
-
constructor(
|
|
2033
|
-
|
|
2034
|
-
|
|
2127
|
+
constructor(serviceAccount, options = {}) {
|
|
2128
|
+
validateServiceAccount(serviceAccount);
|
|
2129
|
+
this.clientEmail = serviceAccount.client_email;
|
|
2130
|
+
this.privateKey = serviceAccount.private_key;
|
|
2131
|
+
this.projectId = serviceAccount.project_id;
|
|
2132
|
+
this.scope = options.scope || "https://www.googleapis.com/auth/cloud-platform";
|
|
2133
|
+
this.tokenLifetime = options.tokenLifetime || 3600;
|
|
2134
|
+
this._tokenCache = null;
|
|
2135
|
+
this._expiresAt = 0;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Get a valid access token (cached or fresh)
|
|
2139
|
+
*
|
|
2140
|
+
* @returns {Promise<string>} OAuth2 access token
|
|
2141
|
+
*/
|
|
2142
|
+
async getToken() {
|
|
2143
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2144
|
+
if (this._tokenCache && now < this._expiresAt - 300) {
|
|
2145
|
+
return this._tokenCache;
|
|
2035
2146
|
}
|
|
2036
|
-
this.
|
|
2037
|
-
this.
|
|
2038
|
-
this.
|
|
2147
|
+
this._tokenCache = await this._generateToken();
|
|
2148
|
+
this._expiresAt = now + this.tokenLifetime;
|
|
2149
|
+
return this._tokenCache;
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Clear cached token (force refresh on next getToken call)
|
|
2153
|
+
*/
|
|
2154
|
+
invalidate() {
|
|
2155
|
+
this._tokenCache = null;
|
|
2156
|
+
this._expiresAt = 0;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Check if cached token is still valid
|
|
2160
|
+
*
|
|
2161
|
+
* @returns {boolean} True if token is valid
|
|
2162
|
+
*/
|
|
2163
|
+
isValid() {
|
|
2164
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2165
|
+
return this._tokenCache && now < this._expiresAt - 300;
|
|
2039
2166
|
}
|
|
2040
2167
|
/**
|
|
2041
|
-
*
|
|
2168
|
+
* Get time until token expires (in seconds)
|
|
2169
|
+
*
|
|
2170
|
+
* @returns {number} Seconds until expiration (0 if no token or expired)
|
|
2042
2171
|
*/
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
scope: this.scopes.join(" ")
|
|
2048
|
-
}).setProtectedHeader({ alg }).setIssuedAt().setIssuer(this.credentials.client_email).setAudience(this.tokenUrl).setExpirationTime("1h").sign(privateKey);
|
|
2049
|
-
return jwt;
|
|
2172
|
+
getTimeToExpiry() {
|
|
2173
|
+
if (!this._tokenCache) return 0;
|
|
2174
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2175
|
+
return Math.max(0, this._expiresAt - now);
|
|
2050
2176
|
}
|
|
2051
2177
|
/**
|
|
2052
|
-
*
|
|
2053
|
-
*
|
|
2178
|
+
* Generate OAuth2 token via JWT flow
|
|
2179
|
+
*
|
|
2180
|
+
* @private
|
|
2181
|
+
* @returns {Promise<string>} Access token
|
|
2054
2182
|
*/
|
|
2055
|
-
async
|
|
2056
|
-
const
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2183
|
+
async _generateToken() {
|
|
2184
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2185
|
+
const header = {
|
|
2186
|
+
alg: "RS256",
|
|
2187
|
+
typ: "JWT"
|
|
2188
|
+
};
|
|
2189
|
+
const payload = {
|
|
2190
|
+
iss: this.clientEmail,
|
|
2191
|
+
scope: this.scope,
|
|
2192
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
2193
|
+
exp: now + this.tokenLifetime,
|
|
2194
|
+
iat: now
|
|
2195
|
+
};
|
|
2196
|
+
const jwt = await this._signJWT(header, payload);
|
|
2197
|
+
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
2061
2198
|
method: "POST",
|
|
2062
2199
|
headers: {
|
|
2063
2200
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
2064
2201
|
},
|
|
2065
|
-
body:
|
|
2202
|
+
body: new URLSearchParams({
|
|
2203
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
2204
|
+
assertion: jwt
|
|
2205
|
+
})
|
|
2066
2206
|
});
|
|
2067
2207
|
if (!response.ok) {
|
|
2068
|
-
const
|
|
2069
|
-
throw new Error(`Failed to get
|
|
2208
|
+
const error = await response.text();
|
|
2209
|
+
throw new Error(`Failed to get OAuth2 token: ${response.status} - ${error}`);
|
|
2070
2210
|
}
|
|
2071
2211
|
const data = await response.json();
|
|
2212
|
+
if (!data.access_token) {
|
|
2213
|
+
throw new Error("No access_token in OAuth2 response");
|
|
2214
|
+
}
|
|
2072
2215
|
return data.access_token;
|
|
2073
2216
|
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Sign JWT using service account private key
|
|
2219
|
+
*
|
|
2220
|
+
* @private
|
|
2221
|
+
* @param {object} header - JWT header
|
|
2222
|
+
* @param {object} payload - JWT payload
|
|
2223
|
+
* @returns {Promise<string>} Signed JWT
|
|
2224
|
+
*/
|
|
2225
|
+
async _signJWT(header, payload) {
|
|
2226
|
+
const encoder = new TextEncoder();
|
|
2227
|
+
const headerB64 = this._base64UrlEncode(JSON.stringify(header));
|
|
2228
|
+
const payloadB64 = this._base64UrlEncode(JSON.stringify(payload));
|
|
2229
|
+
const unsignedToken = `${headerB64}.${payloadB64}`;
|
|
2230
|
+
const privateKey = await this._importPrivateKey(this.privateKey);
|
|
2231
|
+
const signature = await crypto.subtle.sign(
|
|
2232
|
+
{ name: "RSASSA-PKCS1-v1_5" },
|
|
2233
|
+
privateKey,
|
|
2234
|
+
encoder.encode(unsignedToken)
|
|
2235
|
+
);
|
|
2236
|
+
const signatureB64 = this._base64UrlEncode(signature);
|
|
2237
|
+
return `${unsignedToken}.${signatureB64}`;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Import PEM private key for signing
|
|
2241
|
+
*
|
|
2242
|
+
* @private
|
|
2243
|
+
* @param {string} pemKey - PEM formatted private key
|
|
2244
|
+
* @returns {Promise<CryptoKey>} Imported key
|
|
2245
|
+
*/
|
|
2246
|
+
async _importPrivateKey(pemKey) {
|
|
2247
|
+
const pemContents = pemKey.replace(/-----BEGIN PRIVATE KEY-----/, "").replace(/-----END PRIVATE KEY-----/, "").replace(/\\n/g, "").replace(/\n/g, "").trim();
|
|
2248
|
+
const binaryKey = import_node_buffer.Buffer.from(pemContents, "base64");
|
|
2249
|
+
return crypto.subtle.importKey(
|
|
2250
|
+
"pkcs8",
|
|
2251
|
+
binaryKey,
|
|
2252
|
+
{
|
|
2253
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
2254
|
+
hash: "SHA-256"
|
|
2255
|
+
},
|
|
2256
|
+
false,
|
|
2257
|
+
["sign"]
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Base64 URL encode (without padding)
|
|
2262
|
+
*
|
|
2263
|
+
* @private
|
|
2264
|
+
* @param {string|ArrayBuffer} input - Data to encode
|
|
2265
|
+
* @returns {string} Base64 URL encoded string
|
|
2266
|
+
*/
|
|
2267
|
+
_base64UrlEncode(input) {
|
|
2268
|
+
const buffer = typeof input === "string" ? import_node_buffer.Buffer.from(input, "utf-8") : import_node_buffer.Buffer.from(input);
|
|
2269
|
+
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2270
|
+
}
|
|
2074
2271
|
};
|
|
2075
2272
|
|
|
2076
2273
|
// src/transcription-service.js
|
|
@@ -2090,7 +2287,7 @@ var TranscriptionService = class {
|
|
|
2090
2287
|
if (this.provider === "openai") {
|
|
2091
2288
|
this.client = new import_openai2.default({ apiKey: this.apiKey });
|
|
2092
2289
|
} else if (this.provider === "google") {
|
|
2093
|
-
this.
|
|
2290
|
+
this.googleTokenManager = new GoogleTokenManager(config.googleCredentials);
|
|
2094
2291
|
this.projectId = config.googleCredentials.project_id;
|
|
2095
2292
|
}
|
|
2096
2293
|
}
|
|
@@ -2171,7 +2368,7 @@ var TranscriptionService = class {
|
|
|
2171
2368
|
}
|
|
2172
2369
|
async _transcribeGoogle(audioFile, model, language) {
|
|
2173
2370
|
try {
|
|
2174
|
-
const accessToken = await this.
|
|
2371
|
+
const accessToken = await this.googleTokenManager.getToken();
|
|
2175
2372
|
const arrayBuffer = await audioFile.arrayBuffer();
|
|
2176
2373
|
const audioBytes = Buffer.from(arrayBuffer).toString("base64");
|
|
2177
2374
|
let location = "global";
|