@contentgrowth/llm-service 1.1.2 → 1.2.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.cjs CHANGED
@@ -35,13 +35,15 @@ __export(index_exports, {
35
35
  ConfigManager: () => ConfigManager,
36
36
  DefaultConfigProvider: () => DefaultConfigProvider,
37
37
  FINISH_REASONS: () => FINISH_REASONS,
38
- GeminiProvider: () => GeminiProvider,
38
+ GeminiProvider: () => GoogleProvider,
39
+ GoogleProvider: () => GoogleProvider,
39
40
  LLMService: () => LLMService,
40
41
  LLMServiceException: () => LLMServiceException,
41
42
  MODEL_CONFIGS: () => MODEL_CONFIGS,
42
43
  OpenAIProvider: () => OpenAIProvider,
43
44
  TranscriptionService: () => TranscriptionService,
44
45
  TranscriptionServiceException: () => TranscriptionServiceException,
46
+ VertexProvider: () => GoogleProvider,
45
47
  createSpeechHandler: () => createSpeechHandler,
46
48
  extractJsonFromResponse: () => extractJsonFromResponse,
47
49
  extractTextAndJson: () => extractTextAndJson,
@@ -119,8 +121,10 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
119
121
  _buildTenantConfig(tenantConfig, env) {
120
122
  return {
121
123
  provider: tenantConfig.provider,
122
- apiKey: tenantConfig.api_key,
123
124
  models: MODEL_CONFIGS[tenantConfig.provider],
125
+ apiKey: tenantConfig.api_key,
126
+ project: tenantConfig.project,
127
+ location: tenantConfig.location,
124
128
  temperature: parseFloat(env.DEFAULT_TEMPERATURE || "0.7"),
125
129
  maxTokens: parseInt(env.DEFAULT_MAX_TOKENS || "65536"),
126
130
  capabilities: tenantConfig.capabilities || { chat: true, image: false, video: false },
@@ -153,6 +157,28 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
153
157
  image: env.GEMINI_IMAGE_MODEL || providerDefaults.image,
154
158
  video: env.GEMINI_VIDEO_MODEL || providerDefaults.video
155
159
  };
160
+ } else if (provider === "vertex") {
161
+ apiKey = env.VERTEX_API_KEY;
162
+ const project = env.VERTEX_PROJECT || env.GOOGLE_CLOUD_PROJECT;
163
+ const location = env.VERTEX_LOCATION || env.GOOGLE_CLOUD_LOCATION || "us-central1";
164
+ models = {
165
+ default: env.VERTEX_MODEL || providerDefaults.default,
166
+ edge: env.VERTEX_MODEL_EDGE || providerDefaults.edge,
167
+ fast: env.VERTEX_MODEL_FAST || providerDefaults.fast,
168
+ cost: env.VERTEX_MODEL_COST || providerDefaults.cost,
169
+ free: env.VERTEX_MODEL_FREE || providerDefaults.free,
170
+ image: env.VERTEX_IMAGE_MODEL || providerDefaults.image,
171
+ video: env.VERTEX_VIDEO_MODEL || providerDefaults.video
172
+ };
173
+ return {
174
+ provider,
175
+ apiKey,
176
+ project,
177
+ location,
178
+ models,
179
+ temperature: parseFloat(env.DEFAULT_TEMPERATURE || "0.7"),
180
+ maxTokens: parseInt(env.DEFAULT_MAX_TOKENS || "65536")
181
+ };
156
182
  }
157
183
  return {
158
184
  provider,
@@ -187,6 +213,15 @@ var MODEL_CONFIGS = {
187
213
  video: "veo",
188
214
  image: "gemini-3-pro-image-preview"
189
215
  // Default image generation model
216
+ },
217
+ vertex: {
218
+ default: "gemini-3-flash-preview",
219
+ edge: "gemini-3-pro-preview",
220
+ fast: "gemini-3-flash-preview",
221
+ cost: "gemini-3-flash-preview",
222
+ free: "gemini-3-flash-preview",
223
+ video: "veo",
224
+ image: "gemini-3-pro-image-preview"
190
225
  }
191
226
  };
192
227
  var ConfigManager = class {
@@ -584,25 +619,43 @@ var OpenAIProvider = class extends BaseLLMProvider {
584
619
  }
585
620
  };
586
621
 
587
- // src/llm/providers/gemini-provider.js
622
+ // src/llm/providers/google-provider.js
588
623
  var import_genai = require("@google/genai");
589
- var GeminiProvider = class extends BaseLLMProvider {
624
+ var GoogleProvider = class extends BaseLLMProvider {
590
625
  constructor(config) {
591
626
  super(config);
592
- const clientConfig = {};
593
- if (config.project || config.location) {
594
- console.log(`[GeminiProvider] Initializing with Vertex AI (Project: ${config.project}, Location: ${config.location || "us-central1"})`);
595
- clientConfig.vertexAI = {
596
- project: config.project,
597
- location: config.location || "us-central1"
598
- };
599
- } else {
600
- clientConfig.apiKey = config.apiKey;
601
- }
602
- this.client = new import_genai.GoogleGenAI(clientConfig);
603
627
  this.models = config.models;
604
628
  this.defaultModel = config.models.default;
605
629
  this._pendingOperations = /* @__PURE__ */ new Map();
630
+ if (config.provider === "vertex") {
631
+ if (config.apiKey) {
632
+ this.client = new import_genai.GoogleGenAI({
633
+ vertexai: true,
634
+ apiKey: config.apiKey
635
+ });
636
+ } else {
637
+ if (!config.project) {
638
+ console.warn("[GoogleProvider] Vertex AI: no project ID and no API key. Calls will likely fail.");
639
+ }
640
+ this.client = new import_genai.GoogleGenAI({
641
+ vertexai: true,
642
+ project: config.project,
643
+ location: config.location || "us-central1"
644
+ });
645
+ }
646
+ } else {
647
+ this.client = new import_genai.GoogleGenAI({
648
+ apiKey: config.apiKey
649
+ });
650
+ }
651
+ }
652
+ /**
653
+ * Perform the actual API call. Both AI Studio and Vertex AI use the
654
+ * same @google/genai SDK method — the routing is determined by how
655
+ * the client was constructed.
656
+ */
657
+ async _generateContent(requestOptions) {
658
+ return this.client.models.generateContent(requestOptions);
606
659
  }
607
660
  async chat(userMessage, systemPrompt = "", options = {}) {
608
661
  const messages = [{ role: "user", content: userMessage }];
@@ -634,7 +687,7 @@ var GeminiProvider = class extends BaseLLMProvider {
634
687
  );
635
688
  }
636
689
  async _chatCompletionWithModel(messages, systemPrompt, tools, modelName, maxTokens, temperature, options = {}) {
637
- var _a, _b, _c, _d, _e, _f, _g;
690
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
638
691
  const generationConfig = {
639
692
  temperature: (_a = options.temperature) != null ? _a : temperature,
640
693
  maxOutputTokens: (_b = options.maxTokens) != null ? _b : maxTokens
@@ -660,8 +713,41 @@ ${msg.content}`;
660
713
  }
661
714
  }
662
715
  }
663
- const contents = geminiMessages.map((msg, index) => {
664
- var _a2, _b2, _c2, _d2;
716
+ const contents = [];
717
+ let pendingToolParts = [];
718
+ for (let index = 0; index < geminiMessages.length; index++) {
719
+ const msg = geminiMessages[index];
720
+ if (msg.role === "tool") {
721
+ let assistantMsg = null;
722
+ for (let j = index - 1; j >= 0; j--) {
723
+ if (geminiMessages[j].role === "assistant" && geminiMessages[j].tool_calls) {
724
+ assistantMsg = geminiMessages[j];
725
+ break;
726
+ }
727
+ }
728
+ const toolCall = (_c = assistantMsg == null ? void 0 : assistantMsg.tool_calls) == null ? void 0 : _c.find((tc) => tc.id === msg.tool_call_id);
729
+ pendingToolParts.push({
730
+ functionResponse: {
731
+ name: ((_d = toolCall == null ? void 0 : toolCall.function) == null ? void 0 : _d.name) || "unknown_tool",
732
+ response: { content: msg.content }
733
+ }
734
+ });
735
+ const nextMsg = geminiMessages[index + 1];
736
+ if (!nextMsg || nextMsg.role !== "tool") {
737
+ if (options.responseFormat === "json" || ((_e = options.responseFormat) == null ? void 0 : _e.type) === "json_schema" || options.responseSchema) {
738
+ pendingToolParts.push({ text: "\n\n[SYSTEM NOTE: The output MUST be valid JSON as per the schema. Do not include markdown formatting or explanations.]" });
739
+ } else {
740
+ pendingToolParts.push({ text: "\n\n[SYSTEM NOTE: Please ensure your response adheres strictly to the constraints defined in the System Prompt.]" });
741
+ }
742
+ contents.push({ role: "user", parts: pendingToolParts });
743
+ pendingToolParts = [];
744
+ }
745
+ continue;
746
+ }
747
+ if (pendingToolParts.length > 0) {
748
+ contents.push({ role: "user", parts: pendingToolParts });
749
+ pendingToolParts = [];
750
+ }
665
751
  let role = "";
666
752
  let parts2;
667
753
  switch (msg.role) {
@@ -670,7 +756,7 @@ ${msg.content}`;
670
756
  parts2 = [{ text: msg.content }];
671
757
  if (index === geminiMessages.length - 1) {
672
758
  let reminder = "";
673
- if (options.responseFormat === "json" || ((_a2 = options.responseFormat) == null ? void 0 : _a2.type) === "json_schema" || options.responseSchema) {
759
+ if (options.responseFormat === "json" || ((_f = options.responseFormat) == null ? void 0 : _f.type) === "json_schema" || options.responseSchema) {
674
760
  reminder = "\n\n[SYSTEM NOTE: The output MUST be valid JSON as per the schema. Do not include markdown formatting or explanations.]";
675
761
  } else {
676
762
  reminder = "\n\n[SYSTEM NOTE: Please ensure your response adheres strictly to the constraints defined in the System Prompt.]";
@@ -704,27 +790,11 @@ ${msg.content}`;
704
790
  parts2 = [part];
705
791
  }
706
792
  break;
707
- case "tool":
708
- role = "user";
709
- const preceding_message = messages[index - 1];
710
- const tool_call = (_b2 = preceding_message == null ? void 0 : preceding_message.tool_calls) == null ? void 0 : _b2.find((tc) => tc.id === msg.tool_call_id);
711
- parts2 = [{
712
- functionResponse: {
713
- name: ((_c2 = tool_call == null ? void 0 : tool_call.function) == null ? void 0 : _c2.name) || "unknown_tool",
714
- response: { content: msg.content }
715
- }
716
- }];
717
- if (options.responseFormat === "json" || ((_d2 = options.responseFormat) == null ? void 0 : _d2.type) === "json_schema" || options.responseSchema) {
718
- parts2.push({ text: "\n\n[SYSTEM NOTE: The output MUST be valid JSON as per the schema. Do not include markdown formatting or explanations.]" });
719
- } else {
720
- parts2.push({ text: "\n\n[SYSTEM NOTE: Please ensure your response adheres strictly to the constraints defined in the System Prompt.]" });
721
- }
722
- break;
723
793
  default:
724
- return null;
794
+ continue;
725
795
  }
726
- return { role, parts: parts2 };
727
- }).filter(Boolean);
796
+ contents.push({ role, parts: parts2 });
797
+ }
728
798
  while (contents.length > 0 && contents[0].role !== "user") {
729
799
  contents.shift();
730
800
  }
@@ -742,23 +812,17 @@ ${msg.content}`;
742
812
  if (tools && tools.length > 0) {
743
813
  requestOptions.config.tools = [{ functionDeclarations: tools.map((t) => t.function) }];
744
814
  if (requestOptions.config.responseMimeType === "application/json") {
745
- console.warn("[GeminiProvider] Disabling strict JSON mode because tools are present. Relying on system prompt.");
815
+ console.warn(`[${this.constructor.name}] Disabling strict JSON mode because tools are present. Relying on system prompt.`);
746
816
  delete requestOptions.config.responseMimeType;
747
817
  delete requestOptions.config.responseSchema;
748
818
  }
749
819
  }
750
- let response;
751
- try {
752
- response = await this.client.models.generateContent(requestOptions);
753
- } catch (error) {
754
- console.error(`[GeminiProvider] generateContent failed (API Key: ${this._getMaskedApiKey()}):`, error);
755
- throw error;
756
- }
757
- const candidate = (_c = response.candidates) == null ? void 0 : _c[0];
820
+ const response = await this._generateContent(requestOptions);
821
+ const candidate = (_g = response.candidates) == null ? void 0 : _g[0];
758
822
  if (!candidate) {
759
823
  throw new LLMServiceException("No candidates returned from model", 500);
760
824
  }
761
- const parts = ((_d = candidate.content) == null ? void 0 : _d.parts) || [];
825
+ const parts = ((_h = candidate.content) == null ? void 0 : _h.parts) || [];
762
826
  let textContent = "";
763
827
  let toolCalls = null;
764
828
  let responseThoughtSignature = null;
@@ -783,10 +847,8 @@ ${msg.content}`;
783
847
  }
784
848
  }
785
849
  if (!textContent && (!toolCalls || toolCalls.length === 0)) {
786
- console.error("[GeminiProvider] Model returned empty response (no text, no tool calls)");
787
- console.error("[GeminiProvider] Finish Reason:", candidate.finishReason);
788
- console.error("[GeminiProvider] Safety Ratings:", JSON.stringify(candidate.safetyRatings, null, 2));
789
- console.error("[GeminiProvider] Full Candidate:", JSON.stringify(candidate, null, 2));
850
+ console.error(`[${this.constructor.name}] Model returned empty response (no text, no tool calls)`);
851
+ console.error(`[${this.constructor.name}] Finish Reason:`, candidate.finishReason);
790
852
  throw new LLMServiceException(
791
853
  `Model returned empty response. Finish Reason: ${candidate.finishReason}.`,
792
854
  500
@@ -796,22 +858,18 @@ ${msg.content}`;
796
858
  return {
797
859
  content: textContent,
798
860
  thought_signature: responseThoughtSignature,
799
- // Return signature to caller
800
861
  tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map((fc) => ({
801
862
  type: "function",
802
863
  function: fc,
803
864
  thought_signature: fc.thought_signature
804
865
  })) : null,
805
866
  finishReason: normalizedFinishReason,
806
- // Standardized: 'completed', 'truncated', etc.
807
867
  _rawFinishReason: candidate.finishReason,
808
- // Keep original for debugging
809
868
  _responseFormat: options.responseFormat,
810
- // Return usage stats
811
869
  usage: {
812
- prompt_tokens: ((_e = response.usageMetadata) == null ? void 0 : _e.promptTokenCount) || 0,
813
- completion_tokens: ((_f = response.usageMetadata) == null ? void 0 : _f.candidatesTokenCount) || 0,
814
- total_tokens: ((_g = response.usageMetadata) == null ? void 0 : _g.totalTokenCount) || 0
870
+ prompt_tokens: ((_i = response.usageMetadata) == null ? void 0 : _i.promptTokenCount) || 0,
871
+ completion_tokens: ((_j = response.usageMetadata) == null ? void 0 : _j.candidatesTokenCount) || 0,
872
+ total_tokens: ((_k = response.usageMetadata) == null ? void 0 : _k.totalTokenCount) || 0
815
873
  },
816
874
  ...options.responseFormat && this._shouldAutoParse(options) ? {
817
875
  parsedContent: this._safeJsonParse(textContent)
@@ -833,7 +891,7 @@ ${msg.content}`;
833
891
  if (schema) {
834
892
  config.responseSchema = this._convertToGeminiSchema(schema);
835
893
  } else {
836
- console.warn("[GeminiProvider] Using legacy JSON mode without schema - may produce markdown wrappers");
894
+ console.warn(`[${this.constructor.name}] Using legacy JSON mode without schema - may produce markdown wrappers`);
837
895
  }
838
896
  }
839
897
  }
@@ -891,8 +949,7 @@ ${msg.content}`;
891
949
  if (!content) return null;
892
950
  const parsed = extractJsonFromResponse(content);
893
951
  if (!parsed) {
894
- console.error("[GeminiProvider] Failed to extract valid JSON from response");
895
- console.error("[GeminiProvider] Content preview:", content.substring(0, 200));
952
+ console.error(`[${this.constructor.name}] Failed to extract valid JSON from response`);
896
953
  }
897
954
  return parsed;
898
955
  }
@@ -922,9 +979,9 @@ ${msg.content}`;
922
979
  toolResults.forEach((result) => messages.push({ role: "tool", tool_call_id: result.tool_call_id, content: result.output }));
923
980
  }
924
981
  async imageGeneration(prompt, systemPrompt, options = {}) {
925
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
982
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
926
983
  const modelName = options.model || this.models.image || "gemini-3-pro-image-preview";
927
- console.log(`[GeminiProvider] Generating image with model: ${modelName}`);
984
+ console.log(`[${this.constructor.name}] Generating image with model: ${modelName}`);
928
985
  const hasReferenceImages = options.images && options.images.length > 0;
929
986
  const generationConfig = {
930
987
  responseModalities: hasReferenceImages ? ["TEXT", "IMAGE"] : ["IMAGE"]
@@ -956,7 +1013,7 @@ ${msg.content}`;
956
1013
  if (systemPrompt) {
957
1014
  requestOptions.config.systemInstruction = { parts: [{ text: systemPrompt }] };
958
1015
  }
959
- const response = await this.client.models.generateContent(requestOptions);
1016
+ const response = await this._generateContent(requestOptions);
960
1017
  const imagePart = (_d = (_c = (_b = (_a = response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) == null ? void 0 : _d.find(
961
1018
  (part) => {
962
1019
  var _a2;
@@ -964,30 +1021,21 @@ ${msg.content}`;
964
1021
  }
965
1022
  );
966
1023
  if (!imagePart || !imagePart.inlineData) {
967
- const textPart = (_h = (_g = (_f = (_e = response.candidates) == null ? void 0 : _e[0]) == null ? void 0 : _f.content) == null ? void 0 : _g.parts) == null ? void 0 : _h.find((p) => p.text);
968
- const candidate = (_i = response.candidates) == null ? void 0 : _i[0];
969
- console.error("[GeminiProvider] Image generation failed (no image data)");
970
- if (candidate) {
971
- console.error("[GeminiProvider] Finish Reason:", candidate.finishReason);
972
- console.error("[GeminiProvider] Safety Ratings:", JSON.stringify(candidate.safetyRatings, null, 2));
973
- console.error("[GeminiProvider] Full Candidate:", JSON.stringify(candidate, null, 2));
974
- }
975
- if (textPart) {
976
- console.warn("[GeminiProvider] Model returned text instead of image:", textPart.text);
977
- }
1024
+ const candidate = (_e = response.candidates) == null ? void 0 : _e[0];
1025
+ console.error(`[${this.constructor.name}] Image generation failed (no image data)`);
978
1026
  throw new Error(`No image data in response. Finish Reason: ${candidate == null ? void 0 : candidate.finishReason}`);
979
1027
  }
980
1028
  let thoughtSignature = null;
981
1029
  if (imagePart.thought_signature || imagePart.thoughtSignature) {
982
1030
  thoughtSignature = imagePart.thought_signature || imagePart.thoughtSignature;
983
1031
  } else {
984
- const signaturePart = (_m = (_l = (_k = (_j = response.candidates) == null ? void 0 : _j[0]) == null ? void 0 : _k.content) == null ? void 0 : _l.parts) == null ? void 0 : _m.find((p) => p.thought_signature || p.thoughtSignature);
1032
+ const signaturePart = (_i = (_h = (_g = (_f = response.candidates) == null ? void 0 : _f[0]) == null ? void 0 : _g.content) == null ? void 0 : _h.parts) == null ? void 0 : _i.find((p) => p.thought_signature || p.thoughtSignature);
985
1033
  if (signaturePart) {
986
1034
  thoughtSignature = signaturePart.thought_signature || signaturePart.thoughtSignature;
987
1035
  }
988
1036
  }
989
1037
  if (thoughtSignature && thoughtSignature.length > 5e4) {
990
- console.warn(`[GeminiProvider] \u26A0\uFE0F Thought signature is abnormally large (${thoughtSignature.length} chars). Replacing with bypass token to save context.`);
1038
+ console.warn(`[${this.constructor.name}] \u26A0\uFE0F Thought signature is abnormally large (${thoughtSignature.length} chars). Replacing with bypass token.`);
991
1039
  thoughtSignature = "skip_thought_signature_validator";
992
1040
  }
993
1041
  return {
@@ -1010,31 +1058,20 @@ ${prompt}` : prompt;
1010
1058
  durationSeconds: options.durationSeconds || 6,
1011
1059
  aspectRatio: options.aspectRatio || "16:9",
1012
1060
  numberOfVideos: 1,
1013
- // Pass reference images if provided
1014
1061
  ...images && images.length > 0 ? { referenceImages: images } : {}
1015
1062
  }
1016
1063
  };
1017
- const logConfig = JSON.parse(JSON.stringify(requestConfig));
1018
- if (logConfig.config && logConfig.config.referenceImages) {
1019
- logConfig.config.referenceImages = logConfig.config.referenceImages.map((img) => ({
1020
- ...img,
1021
- data: `... (${img.data ? img.data.length : 0} bytes)`
1022
- // Summarize data
1023
- }));
1024
- }
1025
- console.log("[GeminiProvider] startVideoGeneration request:", JSON.stringify(logConfig, null, 2));
1026
1064
  try {
1027
1065
  const operation = await this.client.models.generateVideos(requestConfig);
1028
1066
  this._pendingOperations.set(operation.name, operation);
1029
1067
  return { operationName: operation.name };
1030
1068
  } catch (error) {
1031
- console.error(`[GeminiProvider] startVideoGeneration failed (API Key: ${this._getMaskedApiKey()}):`, error);
1069
+ console.error(`[${this.constructor.name}] startVideoGeneration failed (API Key: ${this._getMaskedApiKey()}):`, error);
1032
1070
  throw error;
1033
1071
  }
1034
1072
  }
1035
1073
  async getVideoGenerationStatus(operationName) {
1036
1074
  var _a, _b, _c, _d, _e, _f;
1037
- console.log(`[GeminiProvider] Checking status for operation: ${operationName}`);
1038
1075
  let operation = this._pendingOperations.get(operationName);
1039
1076
  if (!operation) {
1040
1077
  operation = await this.client.models.getOperation(operationName);
@@ -1046,11 +1083,9 @@ ${prompt}` : prompt;
1046
1083
  progress: ((_a = operation.metadata) == null ? void 0 : _a.progressPercent) || 0,
1047
1084
  state: ((_b = operation.metadata) == null ? void 0 : _b.state) || (operation.done ? "COMPLETED" : "PROCESSING")
1048
1085
  };
1049
- console.log(`[GeminiProvider] Operation status: ${result.state}, Progress: ${result.progress}%`);
1050
1086
  if (operation.done) {
1051
1087
  this._pendingOperations.delete(operationName);
1052
1088
  if (operation.error) {
1053
- console.error("[GeminiProvider] Video generation failed:", JSON.stringify(operation.error, null, 2));
1054
1089
  result.error = operation.error;
1055
1090
  } else {
1056
1091
  const videoResult = operation.response;
@@ -1062,20 +1097,17 @@ ${prompt}` : prompt;
1062
1097
  }
1063
1098
  async startDeepResearch(prompt, options = {}) {
1064
1099
  const agent = options.agent || "deep-research-pro-preview-12-2025";
1065
- console.log(`[GeminiProvider] Starting Deep Research with agent: ${agent}`);
1100
+ console.log(`[${this.constructor.name}] Starting Deep Research with agent: ${agent}`);
1066
1101
  try {
1067
1102
  const interaction = await this.client.interactions.create({
1068
1103
  agent,
1069
1104
  input: prompt,
1070
1105
  background: true,
1071
- // Required for long running
1072
1106
  store: true
1073
- // Required for polling
1074
1107
  });
1075
- console.log(`[GeminiProvider] Deep Research started. Interaction ID: ${interaction.id}`);
1076
1108
  return { operationId: interaction.id };
1077
1109
  } catch (error) {
1078
- console.error(`[GeminiProvider] startDeepResearch failed:`, error);
1110
+ console.error(`[${this.constructor.name}] startDeepResearch failed:`, error);
1079
1111
  throw error;
1080
1112
  }
1081
1113
  }
@@ -1094,18 +1126,10 @@ ${prompt}` : prompt;
1094
1126
  }
1095
1127
  return response;
1096
1128
  } catch (error) {
1097
- console.error(`[GeminiProvider] getDeepResearchStatus failed for ${operationId}:`, error);
1129
+ console.error(`[${this.constructor.name}] getDeepResearchStatus failed for ${operationId}:`, error);
1098
1130
  throw error;
1099
1131
  }
1100
1132
  }
1101
- /**
1102
- * Extract structured data from a file (PDF, Image, etc.) using Gemini Multimodal capabilities.
1103
- * @param {Buffer|string} fileData - Base64 string or Buffer of the file
1104
- * @param {string} mimeType - Mime type (e.g., 'application/pdf', 'image/png')
1105
- * @param {string} prompt - Extraction prompt
1106
- * @param {Object} schema - JSON schema for the output
1107
- * @param {Object} options - Additional options
1108
- */
1109
1133
  async extractWithLLM(fileData, mimeType, prompt, schema = null, options = {}) {
1110
1134
  var _a, _b, _c, _d;
1111
1135
  const tier = options.tier || "default";
@@ -1117,9 +1141,7 @@ ${prompt}` : prompt;
1117
1141
  maxTokens,
1118
1142
  temperature
1119
1143
  );
1120
- const parts = [
1121
- { text: prompt }
1122
- ];
1144
+ const parts = [{ text: prompt }];
1123
1145
  let base64Data = fileData;
1124
1146
  if (typeof fileData !== "string") {
1125
1147
  try {
@@ -1142,7 +1164,7 @@ ${prompt}` : prompt;
1142
1164
  config: generationConfig
1143
1165
  };
1144
1166
  try {
1145
- const response = await this.client.models.generateContent(requestOptions);
1167
+ const response = await this._generateContent(requestOptions);
1146
1168
  const candidate = (_a = response.candidates) == null ? void 0 : _a[0];
1147
1169
  if (!candidate) {
1148
1170
  throw new LLMServiceException("No candidates returned from model during extraction", 500);
@@ -1153,7 +1175,7 @@ ${prompt}` : prompt;
1153
1175
  }
1154
1176
  return textContent;
1155
1177
  } catch (error) {
1156
- console.error(`[GeminiProvider] extractWithLLM failed (API Key: ${this._getMaskedApiKey()}):`, error);
1178
+ console.error(`[${this.constructor.name}] extractWithLLM failed (API Key: ${this._getMaskedApiKey()}):`, error);
1157
1179
  throw error;
1158
1180
  }
1159
1181
  }
@@ -1172,14 +1194,14 @@ var LLMService = class {
1172
1194
  return this.providerCache.get(cacheKey);
1173
1195
  }
1174
1196
  const config = await ConfigManager.getConfig(tenantId, this.env);
1175
- if (!config.apiKey) {
1197
+ if (!config.apiKey && config.provider !== "vertex") {
1176
1198
  throw new LLMServiceException(`LLM service is not configured for ${config.provider}. Missing API Key.`, 500);
1177
1199
  }
1178
1200
  let provider;
1179
1201
  if (config.provider === "openai") {
1180
1202
  provider = new OpenAIProvider(config);
1181
- } else if (config.provider === "gemini") {
1182
- provider = new GeminiProvider(config);
1203
+ } else if (config.provider === "gemini" || config.provider === "vertex") {
1204
+ provider = new GoogleProvider(config);
1183
1205
  } else {
1184
1206
  throw new LLMServiceException(`Unsupported LLM provider: ${config.provider}`, 500);
1185
1207
  }
@@ -1753,12 +1775,14 @@ function createSpeechHandler(app, getConfig) {
1753
1775
  DefaultConfigProvider,
1754
1776
  FINISH_REASONS,
1755
1777
  GeminiProvider,
1778
+ GoogleProvider,
1756
1779
  LLMService,
1757
1780
  LLMServiceException,
1758
1781
  MODEL_CONFIGS,
1759
1782
  OpenAIProvider,
1760
1783
  TranscriptionService,
1761
1784
  TranscriptionServiceException,
1785
+ VertexProvider,
1762
1786
  createSpeechHandler,
1763
1787
  extractJsonFromResponse,
1764
1788
  extractTextAndJson,