@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/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import OpenAI from 'openai';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
- import * as _google_genai from '@google/genai';
4
3
  import { GoogleGenAI } from '@google/genai';
5
4
  export { Modality } from '@google/genai';
5
+ import { GoogleTokenManager } from './utils/google-auth.cjs';
6
6
 
7
7
  declare namespace FINISH_REASONS {
8
8
  let COMPLETED: string;
@@ -567,6 +567,8 @@ declare namespace MODEL_CONFIGS {
567
567
  * Invalid model IDs that earlier versions of this library shipped as defaults.
568
568
  * Consumers persisted some of them (e.g. saved BYOK key configs), so we map them
569
569
  * to a valid equivalent instead of letting the provider return 404.
570
+ *
571
+ * NOTE: Imagen models are NOT included here - they should fail with clear error.
570
572
  */
571
573
  declare const LEGACY_MODEL_ALIASES: {
572
574
  'gpt-5.5-instant': string;
@@ -575,6 +577,10 @@ declare const LEGACY_MODEL_ALIASES: {
575
577
  'gemini-3.7-pro': string;
576
578
  'gemini-3.5-pro-image-preview': string;
577
579
  'gemini-3.7-pro-image-preview': string;
580
+ 'gemini-3-pro-image': string;
581
+ 'gemini-3.1-flash-image': string;
582
+ 'gemini-3.1-flash-lite-image': string;
583
+ 'gemini-2.5-flash-image': string;
578
584
  'claude-4.5-haiku': string;
579
585
  'claude-4.6-sonnet': string;
580
586
  'claude-4.8-opus': string;
@@ -619,13 +625,28 @@ declare class GoogleProvider extends BaseLLMProvider {
619
625
  models: {};
620
626
  defaultModel: any;
621
627
  _pendingOperations: Map<any, any>;
628
+ accessToken: any;
629
+ project: any;
630
+ location: any;
631
+ useDirectAPI: boolean;
622
632
  client: GoogleGenAI;
623
633
  /**
624
634
  * Perform the actual API call. Both AI Studio and Vertex AI use the
625
635
  * same @google/genai SDK method — the routing is determined by how
626
636
  * the client was constructed.
627
637
  */
628
- _generateContent(requestOptions: any): Promise<_google_genai.GenerateContentResponse>;
638
+ _generateContent(requestOptions: any): Promise<any>;
639
+ /**
640
+ * Direct Vertex AI API call using pre-generated access token
641
+ * @private
642
+ */
643
+ private _directVertexAPICall;
644
+ /**
645
+ * Determine authentication mode from config for logging/debugging
646
+ * @private
647
+ * @returns {string} Auth mode description
648
+ */
649
+ private _getAuthMode;
629
650
  chat(userMessage: any, systemPrompt?: string, options?: {}): Promise<{
630
651
  text: string;
631
652
  }>;
@@ -637,18 +658,18 @@ declare class GoogleProvider extends BaseLLMProvider {
637
658
  tool_calls: {
638
659
  type: string;
639
660
  function: {
640
- name: string;
661
+ name: any;
641
662
  arguments: string;
642
663
  };
643
664
  thought_signature: any;
644
665
  }[];
645
666
  finishReason: string;
646
- _rawFinishReason: _google_genai.FinishReason;
667
+ _rawFinishReason: any;
647
668
  _responseFormat: any;
648
669
  usage: {
649
- prompt_tokens: number;
650
- completion_tokens: number;
651
- total_tokens: number;
670
+ prompt_tokens: any;
671
+ completion_tokens: any;
672
+ total_tokens: any;
652
673
  };
653
674
  }>;
654
675
  _chatCompletionWithModel(messages: any, systemPrompt: any, tools: any, modelName: any, maxTokens: any, temperature: any, options?: {}): Promise<{
@@ -659,18 +680,18 @@ declare class GoogleProvider extends BaseLLMProvider {
659
680
  tool_calls: {
660
681
  type: string;
661
682
  function: {
662
- name: string;
683
+ name: any;
663
684
  arguments: string;
664
685
  };
665
686
  thought_signature: any;
666
687
  }[];
667
688
  finishReason: string;
668
- _rawFinishReason: _google_genai.FinishReason;
689
+ _rawFinishReason: any;
669
690
  _responseFormat: any;
670
691
  usage: {
671
- prompt_tokens: number;
672
- completion_tokens: number;
673
- total_tokens: number;
692
+ prompt_tokens: any;
693
+ completion_tokens: any;
694
+ total_tokens: any;
674
695
  };
675
696
  }>;
676
697
  _buildGenerationConfig(options: any, maxTokens: any, temperature: any): {
@@ -684,8 +705,8 @@ declare class GoogleProvider extends BaseLLMProvider {
684
705
  _safeJsonParse(content: any): any;
685
706
  executeTools(tool_calls: any, messages: any, tenantId: any, toolImplementations: any, env: any): Promise<void>;
686
707
  imageGeneration(prompt: any, systemPrompt: any, options?: {}): Promise<{
687
- imageData: string;
688
- mimeType: string;
708
+ imageData: any;
709
+ mimeType: any;
689
710
  thought_signature: any;
690
711
  }>;
691
712
  _getModelForTier(tier: any): any;
@@ -787,29 +808,6 @@ declare function handleApiError(error: Error, operation?: string, context?: stri
787
808
  */
788
809
  declare function sanitizeError(error: Error, context?: string): Error;
789
810
 
790
- /**
791
- * Google Auth Helper for Cloudflare Workers / Node.js
792
- * Generates an OAuth2 access token using a Service Account JSON.
793
- */
794
- declare class GoogleAuth {
795
- /**
796
- * @param {Object} credentials - Parsed Service Account JSON
797
- */
798
- constructor(credentials: any);
799
- credentials: any;
800
- tokenUrl: string;
801
- scopes: string[];
802
- /**
803
- * Generate a signed JWT for the token exchange
804
- */
805
- createSignedJWT(): Promise<string>;
806
- /**
807
- * Get an access token from Google OAuth2
808
- * @returns {Promise<string>} Access Token
809
- */
810
- getAccessToken(): Promise<string>;
811
- }
812
-
813
811
  /**
814
812
  * Transcription Service - Separate from LLM Service
815
813
  * Handles audio-to-text transcription using various providers.
@@ -837,7 +835,7 @@ declare class TranscriptionService {
837
835
  endpoint: string;
838
836
  model: string;
839
837
  client: OpenAI;
840
- googleAuth: GoogleAuth;
838
+ googleTokenManager: GoogleTokenManager;
841
839
  projectId: any;
842
840
  /**
843
841
  * Transcribe audio to text
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import OpenAI from 'openai';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
- import * as _google_genai from '@google/genai';
4
3
  import { GoogleGenAI } from '@google/genai';
5
4
  export { Modality } from '@google/genai';
5
+ import { GoogleTokenManager } from './utils/google-auth.js';
6
6
 
7
7
  declare namespace FINISH_REASONS {
8
8
  let COMPLETED: string;
@@ -567,6 +567,8 @@ declare namespace MODEL_CONFIGS {
567
567
  * Invalid model IDs that earlier versions of this library shipped as defaults.
568
568
  * Consumers persisted some of them (e.g. saved BYOK key configs), so we map them
569
569
  * to a valid equivalent instead of letting the provider return 404.
570
+ *
571
+ * NOTE: Imagen models are NOT included here - they should fail with clear error.
570
572
  */
571
573
  declare const LEGACY_MODEL_ALIASES: {
572
574
  'gpt-5.5-instant': string;
@@ -575,6 +577,10 @@ declare const LEGACY_MODEL_ALIASES: {
575
577
  'gemini-3.7-pro': string;
576
578
  'gemini-3.5-pro-image-preview': string;
577
579
  'gemini-3.7-pro-image-preview': string;
580
+ 'gemini-3-pro-image': string;
581
+ 'gemini-3.1-flash-image': string;
582
+ 'gemini-3.1-flash-lite-image': string;
583
+ 'gemini-2.5-flash-image': string;
578
584
  'claude-4.5-haiku': string;
579
585
  'claude-4.6-sonnet': string;
580
586
  'claude-4.8-opus': string;
@@ -619,13 +625,28 @@ declare class GoogleProvider extends BaseLLMProvider {
619
625
  models: {};
620
626
  defaultModel: any;
621
627
  _pendingOperations: Map<any, any>;
628
+ accessToken: any;
629
+ project: any;
630
+ location: any;
631
+ useDirectAPI: boolean;
622
632
  client: GoogleGenAI;
623
633
  /**
624
634
  * Perform the actual API call. Both AI Studio and Vertex AI use the
625
635
  * same @google/genai SDK method — the routing is determined by how
626
636
  * the client was constructed.
627
637
  */
628
- _generateContent(requestOptions: any): Promise<_google_genai.GenerateContentResponse>;
638
+ _generateContent(requestOptions: any): Promise<any>;
639
+ /**
640
+ * Direct Vertex AI API call using pre-generated access token
641
+ * @private
642
+ */
643
+ private _directVertexAPICall;
644
+ /**
645
+ * Determine authentication mode from config for logging/debugging
646
+ * @private
647
+ * @returns {string} Auth mode description
648
+ */
649
+ private _getAuthMode;
629
650
  chat(userMessage: any, systemPrompt?: string, options?: {}): Promise<{
630
651
  text: string;
631
652
  }>;
@@ -637,18 +658,18 @@ declare class GoogleProvider extends BaseLLMProvider {
637
658
  tool_calls: {
638
659
  type: string;
639
660
  function: {
640
- name: string;
661
+ name: any;
641
662
  arguments: string;
642
663
  };
643
664
  thought_signature: any;
644
665
  }[];
645
666
  finishReason: string;
646
- _rawFinishReason: _google_genai.FinishReason;
667
+ _rawFinishReason: any;
647
668
  _responseFormat: any;
648
669
  usage: {
649
- prompt_tokens: number;
650
- completion_tokens: number;
651
- total_tokens: number;
670
+ prompt_tokens: any;
671
+ completion_tokens: any;
672
+ total_tokens: any;
652
673
  };
653
674
  }>;
654
675
  _chatCompletionWithModel(messages: any, systemPrompt: any, tools: any, modelName: any, maxTokens: any, temperature: any, options?: {}): Promise<{
@@ -659,18 +680,18 @@ declare class GoogleProvider extends BaseLLMProvider {
659
680
  tool_calls: {
660
681
  type: string;
661
682
  function: {
662
- name: string;
683
+ name: any;
663
684
  arguments: string;
664
685
  };
665
686
  thought_signature: any;
666
687
  }[];
667
688
  finishReason: string;
668
- _rawFinishReason: _google_genai.FinishReason;
689
+ _rawFinishReason: any;
669
690
  _responseFormat: any;
670
691
  usage: {
671
- prompt_tokens: number;
672
- completion_tokens: number;
673
- total_tokens: number;
692
+ prompt_tokens: any;
693
+ completion_tokens: any;
694
+ total_tokens: any;
674
695
  };
675
696
  }>;
676
697
  _buildGenerationConfig(options: any, maxTokens: any, temperature: any): {
@@ -684,8 +705,8 @@ declare class GoogleProvider extends BaseLLMProvider {
684
705
  _safeJsonParse(content: any): any;
685
706
  executeTools(tool_calls: any, messages: any, tenantId: any, toolImplementations: any, env: any): Promise<void>;
686
707
  imageGeneration(prompt: any, systemPrompt: any, options?: {}): Promise<{
687
- imageData: string;
688
- mimeType: string;
708
+ imageData: any;
709
+ mimeType: any;
689
710
  thought_signature: any;
690
711
  }>;
691
712
  _getModelForTier(tier: any): any;
@@ -787,29 +808,6 @@ declare function handleApiError(error: Error, operation?: string, context?: stri
787
808
  */
788
809
  declare function sanitizeError(error: Error, context?: string): Error;
789
810
 
790
- /**
791
- * Google Auth Helper for Cloudflare Workers / Node.js
792
- * Generates an OAuth2 access token using a Service Account JSON.
793
- */
794
- declare class GoogleAuth {
795
- /**
796
- * @param {Object} credentials - Parsed Service Account JSON
797
- */
798
- constructor(credentials: any);
799
- credentials: any;
800
- tokenUrl: string;
801
- scopes: string[];
802
- /**
803
- * Generate a signed JWT for the token exchange
804
- */
805
- createSignedJWT(): Promise<string>;
806
- /**
807
- * Get an access token from Google OAuth2
808
- * @returns {Promise<string>} Access Token
809
- */
810
- getAccessToken(): Promise<string>;
811
- }
812
-
813
811
  /**
814
812
  * Transcription Service - Separate from LLM Service
815
813
  * Handles audio-to-text transcription using various providers.
@@ -837,7 +835,7 @@ declare class TranscriptionService {
837
835
  endpoint: string;
838
836
  model: string;
839
837
  client: OpenAI;
840
- googleAuth: GoogleAuth;
838
+ googleTokenManager: GoogleTokenManager;
841
839
  projectId: any;
842
840
  /**
843
841
  * Transcribe audio to text
package/dist/index.js CHANGED
@@ -196,7 +196,7 @@ var MODEL_CONFIGS = {
196
196
  live: "gemini-3.8-live",
197
197
  liveThinking: "gemini-3.8-live-extended-thinking",
198
198
  video: "veo",
199
- image: "gemini-3-pro-image"
199
+ image: "gemini-nano-banana-2.1"
200
200
  },
201
201
  // Vertex AI / Gemini Enterprise Agent Platform
202
202
  vertex: {
@@ -208,7 +208,7 @@ var MODEL_CONFIGS = {
208
208
  live: "gemini-3.8-live",
209
209
  liveThinking: "gemini-3.8-live-extended-thinking",
210
210
  video: "veo",
211
- image: "gemini-3.1-flash-image"
211
+ image: "gemini-nano-banana-2.1"
212
212
  },
213
213
  // Anthropic IDs are "claude-<family>-<major>-<minor>", e.g. claude-sonnet-5-5 (not claude-5.5-sonnet).
214
214
  anthropic: {
@@ -223,11 +223,16 @@ var LEGACY_MODEL_ALIASES = {
223
223
  // OpenAI: ChatGPT product names, not API model IDs
224
224
  "gpt-5.5-instant": "gpt-5.6-luna",
225
225
  "gpt-5.5-thinking": "gpt-5.6-sol",
226
- // Gemini: never existed
226
+ // Gemini: never existed or were preview names
227
227
  "gemini-3.5-pro": "gemini-3.1-pro-preview",
228
228
  "gemini-3.7-pro": "gemini-3.1-pro-preview",
229
229
  "gemini-3.5-pro-image-preview": "gemini-3-pro-image",
230
230
  "gemini-3.7-pro-image-preview": "gemini-3-pro-image",
231
+ // Old Gemini image models (pre-Nano Banana branding)
232
+ "gemini-3-pro-image": "gemini-nano-banana-2.1",
233
+ "gemini-3.1-flash-image": "gemini-nano-banana-2.1",
234
+ "gemini-3.1-flash-lite-image": "gemini-nano-banana-2.1",
235
+ "gemini-2.5-flash-image": "gemini-nano-banana-2.1",
231
236
  // Anthropic: wrong segment order
232
237
  "claude-4.5-haiku": "claude-haiku-4-5",
233
238
  "claude-4.6-sonnet": "claude-sonnet-4-6",
@@ -688,6 +693,14 @@ var GoogleProvider = class extends BaseLLMProvider {
688
693
  this.models = normalizeModelMap(config.models);
689
694
  this.defaultModel = this.models.default;
690
695
  this._pendingOperations = /* @__PURE__ */ new Map();
696
+ if (config.accessToken) {
697
+ this.accessToken = config.accessToken;
698
+ this.project = config.project;
699
+ this.location = config.location || "global";
700
+ this.useDirectAPI = true;
701
+ console.log(`[GoogleProvider] Using pre-generated access token for project: ${this.project}`);
702
+ return;
703
+ }
691
704
  const httpOptions = config.baseURL ? { baseUrl: config.baseURL } : void 0;
692
705
  if (config.provider === "vertex") {
693
706
  if (config.apiKey) {
@@ -721,8 +734,65 @@ var GoogleProvider = class extends BaseLLMProvider {
721
734
  * the client was constructed.
722
735
  */
723
736
  async _generateContent(requestOptions) {
737
+ if (this.useDirectAPI && this.accessToken) {
738
+ return this._directVertexAPICall(requestOptions);
739
+ }
724
740
  return this.client.models.generateContent(requestOptions);
725
741
  }
742
+ /**
743
+ * Direct Vertex AI API call using pre-generated access token
744
+ * @private
745
+ */
746
+ async _directVertexAPICall(requestOptions) {
747
+ var _a, _b, _c;
748
+ const modelPath = `projects/${this.project}/locations/${this.location}/publishers/google/models/${requestOptions.model}`;
749
+ const endpoint = `https://${this.location}-aiplatform.googleapis.com/v1/${modelPath}:generateContent`;
750
+ const body = {
751
+ contents: requestOptions.contents,
752
+ generationConfig: requestOptions.config
753
+ };
754
+ if ((_a = requestOptions.config) == null ? void 0 : _a.systemInstruction) {
755
+ body.systemInstruction = requestOptions.config.systemInstruction;
756
+ }
757
+ if ((_b = requestOptions.config) == null ? void 0 : _b.tools) {
758
+ body.tools = requestOptions.config.tools;
759
+ }
760
+ const response = await fetch(endpoint, {
761
+ method: "POST",
762
+ headers: {
763
+ "Authorization": `Bearer ${this.accessToken}`,
764
+ "Content-Type": "application/json"
765
+ },
766
+ body: JSON.stringify(body)
767
+ });
768
+ if (!response.ok) {
769
+ const errorText = await response.text();
770
+ let errorData;
771
+ try {
772
+ errorData = JSON.parse(errorText);
773
+ } catch (e) {
774
+ errorData = { message: errorText };
775
+ }
776
+ const errorMessage = ((_c = errorData.error) == null ? void 0 : _c.message) || errorData.message || `Vertex API error: ${response.status}`;
777
+ throw new LLMServiceException(errorMessage, response.status);
778
+ }
779
+ return response.json();
780
+ }
781
+ /**
782
+ * Determine authentication mode from config for logging/debugging
783
+ * @private
784
+ * @returns {string} Auth mode description
785
+ */
786
+ _getAuthMode() {
787
+ if (this.config.provider === "vertex") {
788
+ if (this.config.apiKey) {
789
+ return "Vertex Express (API Key)";
790
+ } else {
791
+ return "Vertex ADC (Service Account)";
792
+ }
793
+ }
794
+ return "Gemini AI Studio";
795
+ }
726
796
  async chat(userMessage, systemPrompt = "", options = {}) {
727
797
  const messages = [{ role: "user", content: userMessage }];
728
798
  const tier = options.tier || "default";
@@ -1069,15 +1139,22 @@ ${msg.content}`;
1069
1139
  }
1070
1140
  async imageGeneration(prompt, systemPrompt, options = {}) {
1071
1141
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1072
- const modelName = normalizeModelId(options.model) || this.models.image || "gemini-3-pro-image";
1073
- console.log(`[${this.constructor.name}] Generating image with model: ${modelName}`);
1142
+ const modelName = normalizeModelId(options.model) || this.models.image || "gemini-nano-banana-2.1";
1143
+ if (modelName.startsWith("imagen-") || modelName.startsWith("imagegeneration@")) {
1144
+ throw new LLMServiceException(
1145
+ `Imagen models were shut down August 17, 2026. Requested: "${modelName}". Use Gemini image models instead (e.g., "gemini-nano-banana-2.1").`,
1146
+ 400
1147
+ );
1148
+ }
1149
+ console.log(`[${this.constructor.name}] Generating image with model: ${modelName} (${this._getAuthMode()})`);
1074
1150
  const hasReferenceImages = options.images && options.images.length > 0;
1075
1151
  const generationConfig = {
1076
1152
  responseModalities: hasReferenceImages ? ["TEXT", "IMAGE"] : ["IMAGE"]
1077
1153
  };
1078
- if (options.aspectRatio) {
1154
+ if (options.aspectRatio || options.thinkingLevel) {
1079
1155
  generationConfig.imageConfig = {
1080
- aspectRatio: options.aspectRatio
1156
+ aspectRatio: options.aspectRatio || "1:1",
1157
+ ...options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}
1081
1158
  };
1082
1159
  }
1083
1160
  const parts = [{ text: prompt }];
@@ -1970,53 +2047,173 @@ function sanitizeError(error, context = "general") {
1970
2047
  import OpenAI2 from "openai";
1971
2048
 
1972
2049
  // src/utils/google-auth.js
1973
- import * as jose from "jose";
1974
- var GoogleAuth = class {
2050
+ import { Buffer as Buffer2 } from "buffer";
2051
+ function validateServiceAccount(sa) {
2052
+ if (!sa || typeof sa !== "object") {
2053
+ throw new Error("Service account must be an object");
2054
+ }
2055
+ if (!sa.client_email) {
2056
+ throw new Error("Service account missing client_email");
2057
+ }
2058
+ if (!sa.private_key) {
2059
+ throw new Error("Service account missing private_key");
2060
+ }
2061
+ if (!sa.project_id) {
2062
+ console.warn("[GoogleAuth] Service account missing project_id - may need to specify project explicitly");
2063
+ }
2064
+ return true;
2065
+ }
2066
+ var GoogleTokenManager = class {
1975
2067
  /**
1976
- * @param {Object} credentials - Parsed Service Account JSON
2068
+ * @param {object} serviceAccount - Parsed service account with client_email and private_key
2069
+ * @param {object} options - Optional configuration
2070
+ * @param {string} options.scope - OAuth2 scope (default: cloud-platform)
2071
+ * @param {number} options.tokenLifetime - Token lifetime in seconds (default: 3600)
1977
2072
  */
1978
- constructor(credentials) {
1979
- if (!credentials || !credentials.private_key || !credentials.client_email) {
1980
- throw new Error("Invalid Google credentials. Missing private_key or client_email.");
2073
+ constructor(serviceAccount, options = {}) {
2074
+ validateServiceAccount(serviceAccount);
2075
+ this.clientEmail = serviceAccount.client_email;
2076
+ this.privateKey = serviceAccount.private_key;
2077
+ this.projectId = serviceAccount.project_id;
2078
+ this.scope = options.scope || "https://www.googleapis.com/auth/cloud-platform";
2079
+ this.tokenLifetime = options.tokenLifetime || 3600;
2080
+ this._tokenCache = null;
2081
+ this._expiresAt = 0;
2082
+ }
2083
+ /**
2084
+ * Get a valid access token (cached or fresh)
2085
+ *
2086
+ * @returns {Promise<string>} OAuth2 access token
2087
+ */
2088
+ async getToken() {
2089
+ const now = Math.floor(Date.now() / 1e3);
2090
+ if (this._tokenCache && now < this._expiresAt - 300) {
2091
+ return this._tokenCache;
1981
2092
  }
1982
- this.credentials = credentials;
1983
- this.tokenUrl = "https://oauth2.googleapis.com/token";
1984
- this.scopes = ["https://www.googleapis.com/auth/cloud-platform"];
2093
+ this._tokenCache = await this._generateToken();
2094
+ this._expiresAt = now + this.tokenLifetime;
2095
+ return this._tokenCache;
2096
+ }
2097
+ /**
2098
+ * Clear cached token (force refresh on next getToken call)
2099
+ */
2100
+ invalidate() {
2101
+ this._tokenCache = null;
2102
+ this._expiresAt = 0;
2103
+ }
2104
+ /**
2105
+ * Check if cached token is still valid
2106
+ *
2107
+ * @returns {boolean} True if token is valid
2108
+ */
2109
+ isValid() {
2110
+ const now = Math.floor(Date.now() / 1e3);
2111
+ return this._tokenCache && now < this._expiresAt - 300;
1985
2112
  }
1986
2113
  /**
1987
- * Generate a signed JWT for the token exchange
2114
+ * Get time until token expires (in seconds)
2115
+ *
2116
+ * @returns {number} Seconds until expiration (0 if no token or expired)
1988
2117
  */
1989
- async createSignedJWT() {
1990
- const alg = "RS256";
1991
- const privateKey = await jose.importPKCS8(this.credentials.private_key, alg);
1992
- const jwt = await new jose.SignJWT({
1993
- scope: this.scopes.join(" ")
1994
- }).setProtectedHeader({ alg }).setIssuedAt().setIssuer(this.credentials.client_email).setAudience(this.tokenUrl).setExpirationTime("1h").sign(privateKey);
1995
- return jwt;
2118
+ getTimeToExpiry() {
2119
+ if (!this._tokenCache) return 0;
2120
+ const now = Math.floor(Date.now() / 1e3);
2121
+ return Math.max(0, this._expiresAt - now);
1996
2122
  }
1997
2123
  /**
1998
- * Get an access token from Google OAuth2
1999
- * @returns {Promise<string>} Access Token
2124
+ * Generate OAuth2 token via JWT flow
2125
+ *
2126
+ * @private
2127
+ * @returns {Promise<string>} Access token
2000
2128
  */
2001
- async getAccessToken() {
2002
- const jwt = await this.createSignedJWT();
2003
- const params = new URLSearchParams();
2004
- params.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
2005
- params.append("assertion", jwt);
2006
- const response = await fetch(this.tokenUrl, {
2129
+ async _generateToken() {
2130
+ const now = Math.floor(Date.now() / 1e3);
2131
+ const header = {
2132
+ alg: "RS256",
2133
+ typ: "JWT"
2134
+ };
2135
+ const payload = {
2136
+ iss: this.clientEmail,
2137
+ scope: this.scope,
2138
+ aud: "https://oauth2.googleapis.com/token",
2139
+ exp: now + this.tokenLifetime,
2140
+ iat: now
2141
+ };
2142
+ const jwt = await this._signJWT(header, payload);
2143
+ const response = await fetch("https://oauth2.googleapis.com/token", {
2007
2144
  method: "POST",
2008
2145
  headers: {
2009
2146
  "Content-Type": "application/x-www-form-urlencoded"
2010
2147
  },
2011
- body: params
2148
+ body: new URLSearchParams({
2149
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
2150
+ assertion: jwt
2151
+ })
2012
2152
  });
2013
2153
  if (!response.ok) {
2014
- const errorText = await response.text();
2015
- throw new Error(`Failed to get Google Access Token: ${response.status} ${errorText}`);
2154
+ const error = await response.text();
2155
+ throw new Error(`Failed to get OAuth2 token: ${response.status} - ${error}`);
2016
2156
  }
2017
2157
  const data = await response.json();
2158
+ if (!data.access_token) {
2159
+ throw new Error("No access_token in OAuth2 response");
2160
+ }
2018
2161
  return data.access_token;
2019
2162
  }
2163
+ /**
2164
+ * Sign JWT using service account private key
2165
+ *
2166
+ * @private
2167
+ * @param {object} header - JWT header
2168
+ * @param {object} payload - JWT payload
2169
+ * @returns {Promise<string>} Signed JWT
2170
+ */
2171
+ async _signJWT(header, payload) {
2172
+ const encoder = new TextEncoder();
2173
+ const headerB64 = this._base64UrlEncode(JSON.stringify(header));
2174
+ const payloadB64 = this._base64UrlEncode(JSON.stringify(payload));
2175
+ const unsignedToken = `${headerB64}.${payloadB64}`;
2176
+ const privateKey = await this._importPrivateKey(this.privateKey);
2177
+ const signature = await crypto.subtle.sign(
2178
+ { name: "RSASSA-PKCS1-v1_5" },
2179
+ privateKey,
2180
+ encoder.encode(unsignedToken)
2181
+ );
2182
+ const signatureB64 = this._base64UrlEncode(signature);
2183
+ return `${unsignedToken}.${signatureB64}`;
2184
+ }
2185
+ /**
2186
+ * Import PEM private key for signing
2187
+ *
2188
+ * @private
2189
+ * @param {string} pemKey - PEM formatted private key
2190
+ * @returns {Promise<CryptoKey>} Imported key
2191
+ */
2192
+ async _importPrivateKey(pemKey) {
2193
+ const pemContents = pemKey.replace(/-----BEGIN PRIVATE KEY-----/, "").replace(/-----END PRIVATE KEY-----/, "").replace(/\\n/g, "").replace(/\n/g, "").trim();
2194
+ const binaryKey = Buffer2.from(pemContents, "base64");
2195
+ return crypto.subtle.importKey(
2196
+ "pkcs8",
2197
+ binaryKey,
2198
+ {
2199
+ name: "RSASSA-PKCS1-v1_5",
2200
+ hash: "SHA-256"
2201
+ },
2202
+ false,
2203
+ ["sign"]
2204
+ );
2205
+ }
2206
+ /**
2207
+ * Base64 URL encode (without padding)
2208
+ *
2209
+ * @private
2210
+ * @param {string|ArrayBuffer} input - Data to encode
2211
+ * @returns {string} Base64 URL encoded string
2212
+ */
2213
+ _base64UrlEncode(input) {
2214
+ const buffer = typeof input === "string" ? Buffer2.from(input, "utf-8") : Buffer2.from(input);
2215
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2216
+ }
2020
2217
  };
2021
2218
 
2022
2219
  // src/transcription-service.js
@@ -2036,7 +2233,7 @@ var TranscriptionService = class {
2036
2233
  if (this.provider === "openai") {
2037
2234
  this.client = new OpenAI2({ apiKey: this.apiKey });
2038
2235
  } else if (this.provider === "google") {
2039
- this.googleAuth = new GoogleAuth(config.googleCredentials);
2236
+ this.googleTokenManager = new GoogleTokenManager(config.googleCredentials);
2040
2237
  this.projectId = config.googleCredentials.project_id;
2041
2238
  }
2042
2239
  }
@@ -2117,7 +2314,7 @@ var TranscriptionService = class {
2117
2314
  }
2118
2315
  async _transcribeGoogle(audioFile, model, language) {
2119
2316
  try {
2120
- const accessToken = await this.googleAuth.getAccessToken();
2317
+ const accessToken = await this.googleTokenManager.getToken();
2121
2318
  const arrayBuffer = await audioFile.arrayBuffer();
2122
2319
  const audioBytes = Buffer.from(arrayBuffer).toString("base64");
2123
2320
  let location = "global";