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