@contentgrowth/llm-service 1.3.7 → 1.4.0
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 +235 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -28
- package/dist/index.d.ts +117 -28
- package/dist/index.js +235 -39
- package/dist/index.js.map +1 -1
- 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,17 +747,24 @@ 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
|
-
const vertexLocation = config.location || "global";
|
|
748
760
|
if (config.apiKey) {
|
|
749
761
|
this.client = new import_genai.GoogleGenAI({
|
|
750
762
|
vertexai: true,
|
|
751
763
|
apiKey: config.apiKey,
|
|
752
|
-
location: vertexLocation,
|
|
753
764
|
...httpOptions ? { httpOptions } : {}
|
|
754
765
|
});
|
|
755
766
|
} else {
|
|
767
|
+
const vertexLocation = config.location || "global";
|
|
756
768
|
if (!config.project) {
|
|
757
769
|
console.warn("[GoogleProvider] Vertex AI: no project ID and no API key. Calls will likely fail.");
|
|
758
770
|
}
|
|
@@ -776,8 +788,65 @@ var GoogleProvider = class extends BaseLLMProvider {
|
|
|
776
788
|
* the client was constructed.
|
|
777
789
|
*/
|
|
778
790
|
async _generateContent(requestOptions) {
|
|
791
|
+
if (this.useDirectAPI && this.accessToken) {
|
|
792
|
+
return this._directVertexAPICall(requestOptions);
|
|
793
|
+
}
|
|
779
794
|
return this.client.models.generateContent(requestOptions);
|
|
780
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
|
+
}
|
|
781
850
|
async chat(userMessage, systemPrompt = "", options = {}) {
|
|
782
851
|
const messages = [{ role: "user", content: userMessage }];
|
|
783
852
|
const tier = options.tier || "default";
|
|
@@ -1124,15 +1193,22 @@ ${msg.content}`;
|
|
|
1124
1193
|
}
|
|
1125
1194
|
async imageGeneration(prompt, systemPrompt, options = {}) {
|
|
1126
1195
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1127
|
-
const modelName = normalizeModelId(options.model) || this.models.image || "gemini-
|
|
1128
|
-
|
|
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()})`);
|
|
1129
1204
|
const hasReferenceImages = options.images && options.images.length > 0;
|
|
1130
1205
|
const generationConfig = {
|
|
1131
1206
|
responseModalities: hasReferenceImages ? ["TEXT", "IMAGE"] : ["IMAGE"]
|
|
1132
1207
|
};
|
|
1133
|
-
if (options.aspectRatio) {
|
|
1208
|
+
if (options.aspectRatio || options.thinkingLevel) {
|
|
1134
1209
|
generationConfig.imageConfig = {
|
|
1135
|
-
aspectRatio: options.aspectRatio
|
|
1210
|
+
aspectRatio: options.aspectRatio || "1:1",
|
|
1211
|
+
...options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}
|
|
1136
1212
|
};
|
|
1137
1213
|
}
|
|
1138
1214
|
const parts = [{ text: prompt }];
|
|
@@ -2025,53 +2101,173 @@ function sanitizeError(error, context = "general") {
|
|
|
2025
2101
|
var import_openai2 = __toESM(require("openai"), 1);
|
|
2026
2102
|
|
|
2027
2103
|
// src/utils/google-auth.js
|
|
2028
|
-
var
|
|
2029
|
-
|
|
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 {
|
|
2030
2121
|
/**
|
|
2031
|
-
* @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)
|
|
2032
2126
|
*/
|
|
2033
|
-
constructor(
|
|
2034
|
-
|
|
2035
|
-
|
|
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;
|
|
2036
2146
|
}
|
|
2037
|
-
this.
|
|
2038
|
-
this.
|
|
2039
|
-
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;
|
|
2040
2157
|
}
|
|
2041
2158
|
/**
|
|
2042
|
-
*
|
|
2159
|
+
* Check if cached token is still valid
|
|
2160
|
+
*
|
|
2161
|
+
* @returns {boolean} True if token is valid
|
|
2043
2162
|
*/
|
|
2044
|
-
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
const jwt = await new jose.SignJWT({
|
|
2048
|
-
scope: this.scopes.join(" ")
|
|
2049
|
-
}).setProtectedHeader({ alg }).setIssuedAt().setIssuer(this.credentials.client_email).setAudience(this.tokenUrl).setExpirationTime("1h").sign(privateKey);
|
|
2050
|
-
return jwt;
|
|
2163
|
+
isValid() {
|
|
2164
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2165
|
+
return this._tokenCache && now < this._expiresAt - 300;
|
|
2051
2166
|
}
|
|
2052
2167
|
/**
|
|
2053
|
-
* Get
|
|
2054
|
-
*
|
|
2168
|
+
* Get time until token expires (in seconds)
|
|
2169
|
+
*
|
|
2170
|
+
* @returns {number} Seconds until expiration (0 if no token or expired)
|
|
2055
2171
|
*/
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
const
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2172
|
+
getTimeToExpiry() {
|
|
2173
|
+
if (!this._tokenCache) return 0;
|
|
2174
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2175
|
+
return Math.max(0, this._expiresAt - now);
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Generate OAuth2 token via JWT flow
|
|
2179
|
+
*
|
|
2180
|
+
* @private
|
|
2181
|
+
* @returns {Promise<string>} Access token
|
|
2182
|
+
*/
|
|
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", {
|
|
2062
2198
|
method: "POST",
|
|
2063
2199
|
headers: {
|
|
2064
2200
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
2065
2201
|
},
|
|
2066
|
-
body:
|
|
2202
|
+
body: new URLSearchParams({
|
|
2203
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
2204
|
+
assertion: jwt
|
|
2205
|
+
})
|
|
2067
2206
|
});
|
|
2068
2207
|
if (!response.ok) {
|
|
2069
|
-
const
|
|
2070
|
-
throw new Error(`Failed to get
|
|
2208
|
+
const error = await response.text();
|
|
2209
|
+
throw new Error(`Failed to get OAuth2 token: ${response.status} - ${error}`);
|
|
2071
2210
|
}
|
|
2072
2211
|
const data = await response.json();
|
|
2212
|
+
if (!data.access_token) {
|
|
2213
|
+
throw new Error("No access_token in OAuth2 response");
|
|
2214
|
+
}
|
|
2073
2215
|
return data.access_token;
|
|
2074
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
|
+
}
|
|
2075
2271
|
};
|
|
2076
2272
|
|
|
2077
2273
|
// src/transcription-service.js
|
|
@@ -2091,7 +2287,7 @@ var TranscriptionService = class {
|
|
|
2091
2287
|
if (this.provider === "openai") {
|
|
2092
2288
|
this.client = new import_openai2.default({ apiKey: this.apiKey });
|
|
2093
2289
|
} else if (this.provider === "google") {
|
|
2094
|
-
this.
|
|
2290
|
+
this.googleTokenManager = new GoogleTokenManager(config.googleCredentials);
|
|
2095
2291
|
this.projectId = config.googleCredentials.project_id;
|
|
2096
2292
|
}
|
|
2097
2293
|
}
|
|
@@ -2172,7 +2368,7 @@ var TranscriptionService = class {
|
|
|
2172
2368
|
}
|
|
2173
2369
|
async _transcribeGoogle(audioFile, model, language) {
|
|
2174
2370
|
try {
|
|
2175
|
-
const accessToken = await this.
|
|
2371
|
+
const accessToken = await this.googleTokenManager.getToken();
|
|
2176
2372
|
const arrayBuffer = await audioFile.arrayBuffer();
|
|
2177
2373
|
const audioBytes = Buffer.from(arrayBuffer).toString("base64");
|
|
2178
2374
|
let location = "global";
|