@contentgrowth/llm-service 1.0.2 → 1.0.4

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.cjs CHANGED
@@ -602,11 +602,13 @@ var GeminiProvider = class extends BaseLLMProvider {
602
602
  return { text: response.content };
603
603
  }
604
604
  async chatCompletion(messages, systemPrompt, tools = null, options = {}) {
605
+ const tier = options.tier || "default";
606
+ const modelName = this.models[tier] || this.defaultModel;
605
607
  return this._chatCompletionWithModel(
606
608
  messages,
607
609
  systemPrompt,
608
610
  tools,
609
- this.defaultModel,
611
+ modelName,
610
612
  this.config.maxTokens,
611
613
  this.config.temperature,
612
614
  options
@@ -1381,6 +1383,58 @@ function sanitizeError(error, context = "general") {
1381
1383
 
1382
1384
  // src/transcription-service.js
1383
1385
  var import_openai2 = __toESM(require("openai"), 1);
1386
+
1387
+ // src/utils/google-auth.js
1388
+ var jose = __toESM(require("jose"), 1);
1389
+ var GoogleAuth = class {
1390
+ /**
1391
+ * @param {Object} credentials - Parsed Service Account JSON
1392
+ */
1393
+ constructor(credentials) {
1394
+ if (!credentials || !credentials.private_key || !credentials.client_email) {
1395
+ throw new Error("Invalid Google credentials. Missing private_key or client_email.");
1396
+ }
1397
+ this.credentials = credentials;
1398
+ this.tokenUrl = "https://oauth2.googleapis.com/token";
1399
+ this.scopes = ["https://www.googleapis.com/auth/cloud-platform"];
1400
+ }
1401
+ /**
1402
+ * Generate a signed JWT for the token exchange
1403
+ */
1404
+ async createSignedJWT() {
1405
+ const alg = "RS256";
1406
+ const privateKey = await jose.importPKCS8(this.credentials.private_key, alg);
1407
+ const jwt = await new jose.SignJWT({
1408
+ scope: this.scopes.join(" ")
1409
+ }).setProtectedHeader({ alg }).setIssuedAt().setIssuer(this.credentials.client_email).setAudience(this.tokenUrl).setExpirationTime("1h").sign(privateKey);
1410
+ return jwt;
1411
+ }
1412
+ /**
1413
+ * Get an access token from Google OAuth2
1414
+ * @returns {Promise<string>} Access Token
1415
+ */
1416
+ async getAccessToken() {
1417
+ const jwt = await this.createSignedJWT();
1418
+ const params = new URLSearchParams();
1419
+ params.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
1420
+ params.append("assertion", jwt);
1421
+ const response = await fetch(this.tokenUrl, {
1422
+ method: "POST",
1423
+ headers: {
1424
+ "Content-Type": "application/x-www-form-urlencoded"
1425
+ },
1426
+ body: params
1427
+ });
1428
+ if (!response.ok) {
1429
+ const errorText = await response.text();
1430
+ throw new Error(`Failed to get Google Access Token: ${response.status} ${errorText}`);
1431
+ }
1432
+ const data = await response.json();
1433
+ return data.access_token;
1434
+ }
1435
+ };
1436
+
1437
+ // src/transcription-service.js
1384
1438
  var TranscriptionService = class {
1385
1439
  /**
1386
1440
  * @param {Object} config
@@ -1396,6 +1450,9 @@ var TranscriptionService = class {
1396
1450
  this.model = config.model || "whisper-1";
1397
1451
  if (this.provider === "openai") {
1398
1452
  this.client = new import_openai2.default({ apiKey: this.apiKey });
1453
+ } else if (this.provider === "google") {
1454
+ this.googleAuth = new GoogleAuth(config.googleCredentials);
1455
+ this.projectId = config.googleCredentials.project_id;
1399
1456
  }
1400
1457
  }
1401
1458
  /**
@@ -1413,6 +1470,8 @@ var TranscriptionService = class {
1413
1470
  return this._transcribeOpenAI(audioFile, model, language);
1414
1471
  } else if (this.provider === "generic") {
1415
1472
  return this._transcribeGeneric(audioFile, model, language);
1473
+ } else if (this.provider === "google") {
1474
+ return this._transcribeGoogle(audioFile, model, language);
1416
1475
  } else {
1417
1476
  throw new TranscriptionServiceException(
1418
1477
  `Unsupported transcription provider: ${this.provider}`,
@@ -1471,6 +1530,68 @@ var TranscriptionService = class {
1471
1530
  );
1472
1531
  }
1473
1532
  }
1533
+ async _transcribeGoogle(audioFile, model, language) {
1534
+ try {
1535
+ const accessToken = await this.googleAuth.getAccessToken();
1536
+ const arrayBuffer = await audioFile.arrayBuffer();
1537
+ const audioBytes = Buffer.from(arrayBuffer).toString("base64");
1538
+ let location = "global";
1539
+ if (model === "chirp_3") {
1540
+ location = "us";
1541
+ } else if (model && model.includes("chirp")) {
1542
+ location = "us-central1";
1543
+ }
1544
+ const recognizer = `projects/${this.projectId}/locations/${location}/recognizers/_`;
1545
+ const apiEndpoint = location === "global" ? "https://speech.googleapis.com" : `https://${location}-speech.googleapis.com`;
1546
+ const url = `${apiEndpoint}/v2/${recognizer}:recognize`;
1547
+ const languageCodes = language ? [language] : ["en-US"];
1548
+ let decodingConfig = { autoDecodingConfig: {} };
1549
+ if (audioFile.type && audioFile.type.includes("webm")) {
1550
+ decodingConfig = {
1551
+ explicitDecodingConfig: {
1552
+ encoding: "WEBM_OPUS"
1553
+ }
1554
+ };
1555
+ }
1556
+ const body = {
1557
+ config: {
1558
+ ...decodingConfig,
1559
+ languageCodes,
1560
+ // Sanitize model: Strict allowlist for Google v2 models
1561
+ model: model || "chirp_3"
1562
+ },
1563
+ content: audioBytes
1564
+ };
1565
+ const response = await fetch(url, {
1566
+ method: "POST",
1567
+ headers: {
1568
+ "Authorization": `Bearer ${accessToken}`,
1569
+ "Content-Type": "application/json",
1570
+ "X-Goog-User-Project": this.projectId
1571
+ },
1572
+ body: JSON.stringify(body)
1573
+ });
1574
+ if (!response.ok) {
1575
+ const errorText = await response.text();
1576
+ throw new Error(`Google Speech API Error ${response.status}: ${errorText}`);
1577
+ }
1578
+ const data = await response.json();
1579
+ let transcript = "";
1580
+ if (data.results) {
1581
+ transcript = data.results.map((r) => {
1582
+ var _a, _b;
1583
+ return ((_b = (_a = r.alternatives) == null ? void 0 : _a[0]) == null ? void 0 : _b.transcript) || "";
1584
+ }).join(" ");
1585
+ }
1586
+ return { text: transcript.trim() };
1587
+ } catch (error) {
1588
+ console.error("[TranscriptionService] Google transcription failed:", error);
1589
+ throw new TranscriptionServiceException(
1590
+ `Google Transcription failed: ${error.message}`,
1591
+ 500
1592
+ );
1593
+ }
1594
+ }
1474
1595
  };
1475
1596
  var TranscriptionServiceException = class extends Error {
1476
1597
  constructor(message, statusCode = 500) {
@@ -1485,9 +1606,14 @@ function createSpeechHandler(app, getConfig) {
1485
1606
  app.post("/transcribe", async (c) => {
1486
1607
  try {
1487
1608
  const config = await getConfig(c);
1488
- if (!config || !config.apiKey) {
1609
+ if (!config) {
1489
1610
  return c.json({ error: "Transcription service not configured" }, 503);
1490
1611
  }
1612
+ if (config.provider === "google" && !config.googleCredentials) {
1613
+ return c.json({ error: "Google credentials not configured" }, 503);
1614
+ } else if (config.provider !== "google" && !config.apiKey) {
1615
+ return c.json({ error: "API key not configured" }, 503);
1616
+ }
1491
1617
  const formData = await c.req.formData();
1492
1618
  const audioFile = formData.get("audio");
1493
1619
  if (!audioFile) {