@contentgrowth/llm-service 1.0.1 → 1.0.3

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/dist/index.d.cts CHANGED
@@ -473,6 +473,29 @@ declare function handleApiError(error: Error, operation?: string, context?: stri
473
473
  */
474
474
  declare function sanitizeError(error: Error, context?: string): Error;
475
475
 
476
+ /**
477
+ * Google Auth Helper for Cloudflare Workers / Node.js
478
+ * Generates an OAuth2 access token using a Service Account JSON.
479
+ */
480
+ declare class GoogleAuth {
481
+ /**
482
+ * @param {Object} credentials - Parsed Service Account JSON
483
+ */
484
+ constructor(credentials: any);
485
+ credentials: any;
486
+ tokenUrl: string;
487
+ scopes: string[];
488
+ /**
489
+ * Generate a signed JWT for the token exchange
490
+ */
491
+ createSignedJWT(): Promise<string>;
492
+ /**
493
+ * Get an access token from Google OAuth2
494
+ * @returns {Promise<string>} Access Token
495
+ */
496
+ getAccessToken(): Promise<string>;
497
+ }
498
+
476
499
  /**
477
500
  * Transcription Service - Separate from LLM Service
478
501
  * Handles audio-to-text transcription using various providers.
@@ -500,6 +523,8 @@ declare class TranscriptionService {
500
523
  endpoint: string;
501
524
  model: string;
502
525
  client: OpenAI;
526
+ googleAuth: GoogleAuth;
527
+ projectId: any;
503
528
  /**
504
529
  * Transcribe audio to text
505
530
  * @param {File|Blob} audioFile - Audio file to transcribe
@@ -520,6 +545,9 @@ declare class TranscriptionService {
520
545
  _transcribeGeneric(audioFile: any, model: any, language: any): Promise<{
521
546
  text: any;
522
547
  }>;
548
+ _transcribeGoogle(audioFile: any, model: any, language: any): Promise<{
549
+ text: string;
550
+ }>;
523
551
  }
524
552
  declare class TranscriptionServiceException extends Error {
525
553
  constructor(message: any, statusCode?: number);
package/dist/index.d.ts CHANGED
@@ -473,6 +473,29 @@ declare function handleApiError(error: Error, operation?: string, context?: stri
473
473
  */
474
474
  declare function sanitizeError(error: Error, context?: string): Error;
475
475
 
476
+ /**
477
+ * Google Auth Helper for Cloudflare Workers / Node.js
478
+ * Generates an OAuth2 access token using a Service Account JSON.
479
+ */
480
+ declare class GoogleAuth {
481
+ /**
482
+ * @param {Object} credentials - Parsed Service Account JSON
483
+ */
484
+ constructor(credentials: any);
485
+ credentials: any;
486
+ tokenUrl: string;
487
+ scopes: string[];
488
+ /**
489
+ * Generate a signed JWT for the token exchange
490
+ */
491
+ createSignedJWT(): Promise<string>;
492
+ /**
493
+ * Get an access token from Google OAuth2
494
+ * @returns {Promise<string>} Access Token
495
+ */
496
+ getAccessToken(): Promise<string>;
497
+ }
498
+
476
499
  /**
477
500
  * Transcription Service - Separate from LLM Service
478
501
  * Handles audio-to-text transcription using various providers.
@@ -500,6 +523,8 @@ declare class TranscriptionService {
500
523
  endpoint: string;
501
524
  model: string;
502
525
  client: OpenAI;
526
+ googleAuth: GoogleAuth;
527
+ projectId: any;
503
528
  /**
504
529
  * Transcribe audio to text
505
530
  * @param {File|Blob} audioFile - Audio file to transcribe
@@ -520,6 +545,9 @@ declare class TranscriptionService {
520
545
  _transcribeGeneric(audioFile: any, model: any, language: any): Promise<{
521
546
  text: any;
522
547
  }>;
548
+ _transcribeGoogle(audioFile: any, model: any, language: any): Promise<{
549
+ text: string;
550
+ }>;
523
551
  }
524
552
  declare class TranscriptionServiceException extends Error {
525
553
  constructor(message: any, statusCode?: number);
package/dist/index.js CHANGED
@@ -1333,6 +1333,58 @@ function sanitizeError(error, context = "general") {
1333
1333
 
1334
1334
  // src/transcription-service.js
1335
1335
  import OpenAI2 from "openai";
1336
+
1337
+ // src/utils/google-auth.js
1338
+ import * as jose from "jose";
1339
+ var GoogleAuth = class {
1340
+ /**
1341
+ * @param {Object} credentials - Parsed Service Account JSON
1342
+ */
1343
+ constructor(credentials) {
1344
+ if (!credentials || !credentials.private_key || !credentials.client_email) {
1345
+ throw new Error("Invalid Google credentials. Missing private_key or client_email.");
1346
+ }
1347
+ this.credentials = credentials;
1348
+ this.tokenUrl = "https://oauth2.googleapis.com/token";
1349
+ this.scopes = ["https://www.googleapis.com/auth/cloud-platform"];
1350
+ }
1351
+ /**
1352
+ * Generate a signed JWT for the token exchange
1353
+ */
1354
+ async createSignedJWT() {
1355
+ const alg = "RS256";
1356
+ const privateKey = await jose.importPKCS8(this.credentials.private_key, alg);
1357
+ const jwt = await new jose.SignJWT({
1358
+ scope: this.scopes.join(" ")
1359
+ }).setProtectedHeader({ alg }).setIssuedAt().setIssuer(this.credentials.client_email).setAudience(this.tokenUrl).setExpirationTime("1h").sign(privateKey);
1360
+ return jwt;
1361
+ }
1362
+ /**
1363
+ * Get an access token from Google OAuth2
1364
+ * @returns {Promise<string>} Access Token
1365
+ */
1366
+ async getAccessToken() {
1367
+ const jwt = await this.createSignedJWT();
1368
+ const params = new URLSearchParams();
1369
+ params.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
1370
+ params.append("assertion", jwt);
1371
+ const response = await fetch(this.tokenUrl, {
1372
+ method: "POST",
1373
+ headers: {
1374
+ "Content-Type": "application/x-www-form-urlencoded"
1375
+ },
1376
+ body: params
1377
+ });
1378
+ if (!response.ok) {
1379
+ const errorText = await response.text();
1380
+ throw new Error(`Failed to get Google Access Token: ${response.status} ${errorText}`);
1381
+ }
1382
+ const data = await response.json();
1383
+ return data.access_token;
1384
+ }
1385
+ };
1386
+
1387
+ // src/transcription-service.js
1336
1388
  var TranscriptionService = class {
1337
1389
  /**
1338
1390
  * @param {Object} config
@@ -1348,6 +1400,9 @@ var TranscriptionService = class {
1348
1400
  this.model = config.model || "whisper-1";
1349
1401
  if (this.provider === "openai") {
1350
1402
  this.client = new OpenAI2({ apiKey: this.apiKey });
1403
+ } else if (this.provider === "google") {
1404
+ this.googleAuth = new GoogleAuth(config.googleCredentials);
1405
+ this.projectId = config.googleCredentials.project_id;
1351
1406
  }
1352
1407
  }
1353
1408
  /**
@@ -1365,6 +1420,8 @@ var TranscriptionService = class {
1365
1420
  return this._transcribeOpenAI(audioFile, model, language);
1366
1421
  } else if (this.provider === "generic") {
1367
1422
  return this._transcribeGeneric(audioFile, model, language);
1423
+ } else if (this.provider === "google") {
1424
+ return this._transcribeGoogle(audioFile, model, language);
1368
1425
  } else {
1369
1426
  throw new TranscriptionServiceException(
1370
1427
  `Unsupported transcription provider: ${this.provider}`,
@@ -1423,6 +1480,60 @@ var TranscriptionService = class {
1423
1480
  );
1424
1481
  }
1425
1482
  }
1483
+ async _transcribeGoogle(audioFile, model, language) {
1484
+ try {
1485
+ const accessToken = await this.googleAuth.getAccessToken();
1486
+ const arrayBuffer = await audioFile.arrayBuffer();
1487
+ const audioBytes = Buffer.from(arrayBuffer).toString("base64");
1488
+ let location = "global";
1489
+ if (model === "chirp_3") {
1490
+ location = "us";
1491
+ } else if (model && model.includes("chirp")) {
1492
+ location = "us-central1";
1493
+ }
1494
+ const recognizer = `projects/${this.projectId}/locations/${location}/recognizers/_`;
1495
+ const apiEndpoint = location === "global" ? "https://speech.googleapis.com" : `https://${location}-speech.googleapis.com`;
1496
+ const url = `${apiEndpoint}/v2/${recognizer}:recognize`;
1497
+ const languageCodes = language ? [language] : ["en-US"];
1498
+ const body = {
1499
+ config: {
1500
+ autoDecodingConfig: {},
1501
+ languageCodes,
1502
+ // Sanitize model: Strict allowlist for Google v2 models
1503
+ model: model || "chirp_3"
1504
+ },
1505
+ content: audioBytes
1506
+ };
1507
+ const response = await fetch(url, {
1508
+ method: "POST",
1509
+ headers: {
1510
+ "Authorization": `Bearer ${accessToken}`,
1511
+ "Content-Type": "application/json",
1512
+ "X-Goog-User-Project": this.projectId
1513
+ },
1514
+ body: JSON.stringify(body)
1515
+ });
1516
+ if (!response.ok) {
1517
+ const errorText = await response.text();
1518
+ throw new Error(`Google Speech API Error ${response.status}: ${errorText}`);
1519
+ }
1520
+ const data = await response.json();
1521
+ let transcript = "";
1522
+ if (data.results) {
1523
+ transcript = data.results.map((r) => {
1524
+ var _a, _b;
1525
+ return ((_b = (_a = r.alternatives) == null ? void 0 : _a[0]) == null ? void 0 : _b.transcript) || "";
1526
+ }).join(" ");
1527
+ }
1528
+ return { text: transcript.trim() };
1529
+ } catch (error) {
1530
+ console.error("[TranscriptionService] Google transcription failed:", error);
1531
+ throw new TranscriptionServiceException(
1532
+ `Google Transcription failed: ${error.message}`,
1533
+ 500
1534
+ );
1535
+ }
1536
+ }
1426
1537
  };
1427
1538
  var TranscriptionServiceException = class extends Error {
1428
1539
  constructor(message, statusCode = 500) {
@@ -1437,9 +1548,14 @@ function createSpeechHandler(app, getConfig) {
1437
1548
  app.post("/transcribe", async (c) => {
1438
1549
  try {
1439
1550
  const config = await getConfig(c);
1440
- if (!config || !config.apiKey) {
1551
+ if (!config) {
1441
1552
  return c.json({ error: "Transcription service not configured" }, 503);
1442
1553
  }
1554
+ if (config.provider === "google" && !config.googleCredentials) {
1555
+ return c.json({ error: "Google credentials not configured" }, 503);
1556
+ } else if (config.provider !== "google" && !config.apiKey) {
1557
+ return c.json({ error: "API key not configured" }, 503);
1558
+ }
1443
1559
  const formData = await c.req.formData();
1444
1560
  const audioFile = formData.get("audio");
1445
1561
  if (!audioFile) {